diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..ae0aa1f7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for XDebug", + "type": "php", + "request": "launch", + "port": 9000 + }, + { + "name": "Launch currently open script", + "type": "php", + "request": "launch", + "program": "${file}", + "cwd": "${fileDirname}", + "port": 9000 + } + ] +} \ No newline at end of file diff --git a/application/common/model/Images.php b/application/common/model/Images.php index b583f13a..c20d13fd 100644 --- a/application/common/model/Images.php +++ b/application/common/model/Images.php @@ -36,4 +36,4 @@ public function getDateAttr($date, $data) { return format_time($data['create_time']); } -} \ No newline at end of file +} diff --git a/composer.json b/composer.json index bdaf585b..9a968134 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,9 @@ "upyun/sdk": "^3.3", "qcloud/cos-sdk-v5": "^1.2", "topthink/think-image": "^1.0", - "phpmailer/phpmailer": "^6.0" + "phpmailer/phpmailer": "^6.0", + "knplabs/github-api": "^2.10", + "php-http/guzzle6-adapter": "^1.1" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index b0defeca..6748f400 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f3a07db17ca2f758f19303939f3bb3d6", + "content-hash": "8dc954d23d41815720bd345b1a0f6613", "packages": [ { "name": "aliyuncs/oss-sdk-php", @@ -47,6 +47,58 @@ "homepage": "http://www.aliyun.com/product/oss/", "time": "2018-01-08T06:59:35+00:00" }, + { + "name": "clue/stream-filter", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/clue/php-stream-filter.git", + "reference": "d80fdee9b3a7e0d16fc330a22f41f3ad0eeb09d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/php-stream-filter/zipball/d80fdee9b3a7e0d16fc330a22f41f3ad0eeb09d0", + "reference": "d80fdee9b3a7e0d16fc330a22f41f3ad0eeb09d0", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^5.0 || ^4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\StreamFilter\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "description": "A simple and modern approach to stream filtering in PHP", + "homepage": "https://github.com/clue/php-stream-filter", + "keywords": [ + "bucket brigade", + "callback", + "filter", + "php_user_filter", + "stream", + "stream_filter_append", + "stream_filter_register" + ], + "time": "2017-08-18T09:54:01+00:00" + }, { "name": "guzzle/guzzle", "version": "v3.9.3", @@ -326,6 +378,543 @@ ], "time": "2018-12-04T20:46:45+00:00" }, + { + "name": "knplabs/github-api", + "version": "2.10.1", + "source": { + "type": "git", + "url": "https://github.com/KnpLabs/php-github-api.git", + "reference": "493423ae7ad1fa9075924cdfb98537828b9e80b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/493423ae7ad1fa9075924cdfb98537828b9e80b5", + "reference": "493423ae7ad1fa9075924cdfb98537828b9e80b5", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "php-http/cache-plugin": "^1.4", + "php-http/client-common": "^1.6", + "php-http/client-implementation": "^1.0", + "php-http/discovery": "^1.0", + "php-http/httplug": "^1.1", + "psr/cache": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "cache/array-adapter": "^0.4", + "guzzlehttp/psr7": "^1.2", + "php-http/guzzle6-adapter": "^1.0", + "php-http/mock-client": "^1.0", + "phpunit/phpunit": "^5.5 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Github\\": "lib/Github/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thibault Duplessis", + "email": "thibault.duplessis@gmail.com", + "homepage": "http://ornicar.github.com" + }, + { + "name": "KnpLabs Team", + "homepage": "http://knplabs.com" + } + ], + "description": "GitHub API v3 client", + "homepage": "https://github.com/KnpLabs/php-github-api", + "keywords": [ + "api", + "gh", + "gist", + "github" + ], + "time": "2018-09-05T19:12:14+00:00" + }, + { + "name": "php-http/cache-plugin", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/cache-plugin.git", + "reference": "c573ac6ea9b4e33fad567f875b844229d18000b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/cache-plugin/zipball/c573ac6ea9b4e33fad567f875b844229d18000b9", + "reference": "c573ac6ea9b4e33fad567f875b844229d18000b9", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0", + "php-http/client-common": "^1.1", + "php-http/message-factory": "^1.0", + "psr/cache": "^1.0", + "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Client\\Common\\Plugin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "PSR-6 Cache plugin for HTTPlug", + "homepage": "http://httplug.io", + "keywords": [ + "cache", + "http", + "httplug", + "plugin" + ], + "time": "2017-11-29T20:45:41+00:00" + }, + { + "name": "php-http/client-common", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/client-common.git", + "reference": "9c21b6058caafdf2fcc99a0cabdf31b3ecb33961" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/client-common/zipball/9c21b6058caafdf2fcc99a0cabdf31b3ecb33961", + "reference": "9c21b6058caafdf2fcc99a0cabdf31b3ecb33961", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0", + "php-http/httplug": "^1.1", + "php-http/message": "^1.6", + "php-http/message-factory": "^1.0", + "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0" + }, + "require-dev": { + "guzzlehttp/psr7": "^1.4", + "phpspec/phpspec": "^2.5 || ^3.4 || ^4.2" + }, + "suggest": { + "php-http/cache-plugin": "PSR-6 Cache plugin", + "php-http/logger-plugin": "PSR-3 Logger plugin", + "php-http/stopwatch-plugin": "Symfony Stopwatch plugin" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9.x-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Client\\Common\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Common HTTP Client implementations and tools for HTTPlug", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "common", + "http", + "httplug" + ], + "time": "2019-01-03T10:59:55+00:00" + }, + { + "name": "php-http/discovery", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "ffef11d54171336d841a34816a35bc035fb8cef0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/ffef11d54171336d841a34816a35bc035fb8cef0", + "reference": "ffef11d54171336d841a34816a35bc035fb8cef0", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0" + }, + "conflict": { + "nyholm/psr7": "<1.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^2.0.2", + "php-http/httplug": "^1.0|^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^2.4", + "puli/composer-plugin": "1.0.0-beta10" + }, + "suggest": { + "php-http/message": "Allow to use Guzzle, Diactoros or Slim Framework factories", + "puli/composer-plugin": "Sets up Puli which is recommended for Discovery to work. Check http://docs.php-http.org/en/latest/discovery.html for more details." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds installed HTTPlug implementations and PSR-7 message factories", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr7" + ], + "time": "2018-12-31T07:31:26+00:00" + }, + { + "name": "php-http/guzzle6-adapter", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-http/guzzle6-adapter.git", + "reference": "a56941f9dc6110409cfcddc91546ee97039277ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/guzzle6-adapter/zipball/a56941f9dc6110409cfcddc91546ee97039277ab", + "reference": "a56941f9dc6110409cfcddc91546ee97039277ab", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "php": ">=5.5.0", + "php-http/httplug": "^1.0" + }, + "provide": { + "php-http/async-client-implementation": "1.0", + "php-http/client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "php-http/adapter-integration-tests": "^0.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Adapter\\Guzzle6\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + } + ], + "description": "Guzzle 6 HTTP Adapter", + "homepage": "http://httplug.io", + "keywords": [ + "Guzzle", + "http" + ], + "time": "2016-05-10T06:13:32+00:00" + }, + { + "name": "php-http/httplug", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/httplug.git", + "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/httplug/zipball/1c6381726c18579c4ca2ef1ec1498fdae8bdf018", + "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "php-http/promise": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTPlug, the HTTP client abstraction for PHP", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "http" + ], + "time": "2016-08-31T08:30:17+00:00" + }, + { + "name": "php-http/message", + "version": "1.7.2", + "source": { + "type": "git", + "url": "https://github.com/php-http/message.git", + "reference": "b159ffe570dffd335e22ef0b91a946eacb182fa1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message/zipball/b159ffe570dffd335e22ef0b91a946eacb182fa1", + "reference": "b159ffe570dffd335e22ef0b91a946eacb182fa1", + "shasum": "" + }, + "require": { + "clue/stream-filter": "^1.4", + "php": "^5.4 || ^7.0", + "php-http/message-factory": "^1.0.2", + "psr/http-message": "^1.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0" + }, + "require-dev": { + "akeneo/phpspec-skip-example-extension": "^1.0", + "coduo/phpspec-data-provider-extension": "^1.0", + "ext-zlib": "*", + "guzzlehttp/psr7": "^1.0", + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4", + "slim/slim": "^3.0", + "zendframework/zend-diactoros": "^1.0" + }, + "suggest": { + "ext-zlib": "Used with compressor/decompressor streams", + "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories", + "slim/slim": "Used with Slim Framework PSR-7 implementation", + "zendframework/zend-diactoros": "Used with Diactoros Factories" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + }, + "files": [ + "src/filters.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTP Message related tools", + "homepage": "http://php-http.org", + "keywords": [ + "http", + "message", + "psr-7" + ], + "time": "2018-11-01T09:32:41+00:00" + }, + { + "name": "php-http/message-factory", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "stream", + "uri" + ], + "time": "2015-12-19T14:08:53+00:00" + }, + { + "name": "php-http/promise", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/promise.git", + "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/promise/zipball/dc494cdc9d7160b9a09bd5573272195242ce7980", + "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980", + "shasum": "" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + } + ], + "description": "Promise used for asynchronous HTTP requests", + "homepage": "http://httplug.io", + "keywords": [ + "promise" + ], + "time": "2016-01-26T13:27:02+00:00" + }, { "name": "phpmailer/phpmailer", "version": "v6.0.6", @@ -392,6 +981,52 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "time": "2018-11-16T00:41:32+00:00" }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "time": "2016-08-06T20:24:11+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -639,18 +1274,72 @@ "homepage": "https://symfony.com", "time": "2018-11-21T14:20:20+00:00" }, + { + "name": "symfony/options-resolver", + "version": "v4.2.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "fbcb106aeee72f3450298bf73324d2cc00d083d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/fbcb106aeee72f3450298bf73324d2cc00d083d1", + "reference": "fbcb106aeee72f3450298bf73324d2cc00d083d1", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "time": "2019-01-03T09:07:35+00:00" + }, { "name": "topthink/framework", - "version": "v5.1.31", + "version": "v5.1.32", "source": { "type": "git", "url": "https://github.com/top-think/framework.git", - "reference": "93339b1a4df5a73e0143db0847a4c5e0b2e46fb0" + "reference": "88a2ab625b35e047896718db320e08375cf021da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/top-think/framework/zipball/93339b1a4df5a73e0143db0847a4c5e0b2e46fb0", - "reference": "93339b1a4df5a73e0143db0847a4c5e0b2e46fb0", + "url": "https://api.github.com/repos/top-think/framework/zipball/88a2ab625b35e047896718db320e08375cf021da", + "reference": "88a2ab625b35e047896718db320e08375cf021da", "shasum": "" }, "require": { @@ -688,7 +1377,7 @@ "orm", "thinkphp" ], - "time": "2018-12-09T12:41:21+00:00" + "time": "2018-12-23T13:42:11+00:00" }, { "name": "topthink/think-captcha", diff --git a/config/app.php b/config/app.php index 61dc0dd6..0d2c9163 100644 --- a/config/app.php +++ b/config/app.php @@ -89,7 +89,7 @@ // IP代理获取标识 'http_agent_ip' => 'X-REAL-IP', // URL伪静态后缀 - 'url_html_suffix' => 'html', + 'url_html_suffix' => '', // URL普通方式参数 用于自动生成 'url_common_param' => true, // URL参数方式 0 按名称成对解析 1 按顺序解析 @@ -139,7 +139,7 @@ // 错误显示信息,非调试模式有效 'error_message' => '页面错误!请稍后再试~', // 显示错误信息 - 'show_error_msg' => false, + 'show_error_msg' => true, // 异常处理handle类 留空使用 \think\exception\Handle 'exception_handle' => '', diff --git a/application/index/config/naming.php b/config/naming.php similarity index 100% rename from application/index/config/naming.php rename to config/naming.php diff --git a/config/strategy.php b/config/strategy.php index b448356b..d783290b 100644 --- a/config/strategy.php +++ b/config/strategy.php @@ -29,4 +29,8 @@ 'name' => '又拍云USS', 'class' => \strategy\driver\Uss::class ], + 'github' => [ + 'name' => 'Github', + 'class' => \strategy\driver\Github::class + ], ]; diff --git a/extend/strategy/Driver.php b/extend/strategy/Driver.php index 58b45098..ad3db291 100644 --- a/extend/strategy/Driver.php +++ b/extend/strategy/Driver.php @@ -47,4 +47,4 @@ public function deletes(array $list); * @return mixed */ public function getError(); -} \ No newline at end of file +} diff --git a/extend/strategy/driver/Github.php b/extend/strategy/driver/Github.php new file mode 100644 index 00000000..dfc1cb17 --- /dev/null +++ b/extend/strategy/driver/Github.php @@ -0,0 +1,121 @@ +options = $options; + try { + $this->client = new \Github\Client(); + $this->client->authenticate($this->options['github_token'],null, \Github\Client::AUTH_URL_TOKEN); + } catch (\Exception $e) { + $this->error = $e->getMessage(); + } + } + + /** + * 创建文件 + * + * @param $pathname + * @param $file + * + * @return bool + */ + public function create($pathname, $file) + { + try { + $image_info = getimagesize($file); + $content = fread(fopen($file, 'r'), filesize($file)); + $path = $this->options['github_path']."/".$pathname; + $fileInfo = $this->client->api('repo')->contents()->create($this->options['github_user'], $this->options['github_repo'], $path,$content,$this->options['github_message'],$this->options['github_branch']); + } catch (\Exception $e) { + $this->error = $e->getMessage(); + return false; + } + + return true; + } + + /** + * 删除文件 + * + * @param $pathname + * + * @return bool + */ + public function delete($pathname) + { + try { + $path = $this->options['github_path']."/".$pathname; + $oldFile =$this->client->api('repo')->contents()->show($this->options['github_user'], $this->options['github_repo'], $path,$this->options['github_branch']); + $fileInfo =$this->client->api('repo')->contents()->rm($this->options['github_user'], $this->options['github_repo'], $path, "", $oldFile['sha'],$this->options['github_branch']); + + } catch (\Exception $e) { + $this->error = $e->getMessage(); + return false; + } + + return true; + } + + /** + * 删除多个文件 + * + * @param array $list + * @return bool|mixed + */ + public function deletes(array $list) + { + try { + foreach ($list as $value) { + $this->delete($value); + } + } catch (\Exception $e) { + $this->error = $e->getMessage(); + return false; + } + + return true; + } + + public function getError() + { + return 'client:' . $this->error; + } +} diff --git a/install.sql b/install.sql index 8817edff..6de450d5 100644 --- a/install.sql +++ b/install.sql @@ -1,103 +1,111 @@ --- phpMyAdmin SQL Dump --- version 4.8.2 --- https://www.phpmyadmin.net/ --- --- Host: localhost:3306 --- Generation Time: 2018-09-28 17:54:47 --- 服务器版本: 5.7.21 --- PHP Version: 7.2.7 - -SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; -SET time_zone = "+00:00"; - --- --- Database: `lsky` --- - +-- -------------------------------------------------------- +-- 主机: 127.0.0.1 +-- 服务器版本: 8.0.13 - MySQL Community Server - GPL +-- 服务器操作系统: Win64 +-- HeidiSQL 版本: 9.4.0.5125 -- -------------------------------------------------------- --- --- 表的结构 `lsky_config` --- +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -DROP TABLE IF EXISTS `lsky_config`; +-- 导出 表 tuchuang.lsky_config 结构 CREATE TABLE IF NOT EXISTS `lsky_config` ( - `id` smallint(6) UNSIGNED NOT NULL, + `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT, `key` varchar(32) DEFAULT NULL COMMENT 'key', `type` varchar(32) NOT NULL DEFAULT 'text' COMMENT 'text|bool|textarea|select', `input_type` varchar(32) NOT NULL DEFAULT 'text' COMMENT 'input type属性', - `name` varchar(32) CHARACTER SET utf8mb4 NOT NULL COMMENT '配置名', - `title` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '配置标题', - `tip` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '配置描述', - `value` text CHARACTER SET utf8mb4 NOT NULL COMMENT '配置值', - `extend` text CHARACTER SET utf8mb4 NOT NULL COMMENT '扩展属性' -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置' ROW_FORMAT=COMPACT; - --- --- 转存表中的数据 `lsky_config` --- - + `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '配置名', + `title` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '配置标题', + `tip` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '配置描述', + `value` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '配置值', + `extend` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '扩展属性', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=54 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='系统配置'; + +-- 正在导出表 tuchuang.lsky_config 的数据:~5 rows (大约) +DELETE FROM `lsky_config`; +/*!40000 ALTER TABLE `lsky_config` DISABLE KEYS */; INSERT INTO `lsky_config` (`id`, `key`, `type`, `input_type`, `name`, `title`, `tip`, `value`, `extend`) VALUES -(1, 'basics', 'bool', 'checkbox', 'close_register', '关闭注册', NULL, '0', ''), -(2, 'basics', 'text', 'text', 'site_name', '网站标题', NULL, 'Lsky Pro', ''), -(3, 'basics', 'text', 'text', 'site_keywords', '网站关键字', NULL, 'Lsky Pro', ''), -(4, 'basics', 'text', 'text', 'site_description', '网站描述', NULL, 'Lsky Pro, Your photo album on the cloud.', ''), -(5, 'basics', 'text', 'text', 'icp_number', '备案号', NULL, '', ''), -(6, 'upload', 'bool', 'checkbox', 'allowed_tourist_upload', '允许游客上传', '是否允许游客上传', '1', ''), -(7, 'upload', 'text', 'text', 'upload_max_size', '最大上传大小', '单位:b,默认5242880:5M', '5242880', ''), -(8, 'upload', 'text', 'number', 'upload_single_num', '单次同时上传数量', NULL, '10', ''), -(9, 'upload', 'text', 'text', 'upload_allowed_exts', '允许上传的文件后缀', '逗号隔开', 'jpg,jpeg,gif,png,ico', ''), -(10, 'upload', 'text', 'text', 'path_naming_rule', '文件路径命名规则', '路径命名变量对照表', '{Y}/{m}/{d}', ''), -(11, 'upload', 'text', 'text', 'file_naming_rule', '文件命名规则', '文件命名变量对照表', '{uniqid}', ''), -(12, 'user', 'text', 'text', 'user_initial_quota', '用户初始配额容量', '单位:b,默认1073741824:1G,最大18位', '1073741824', ''), -(13, 'mail', 'select', 'text', 'mail_send_mode', '邮件发送方式', NULL, 'smtp', '{\"smtp\":\"SMTP\"}'), -(14, 'mail', 'select', 'text', 'mail_smtp_secure', 'SMTP验证方式', NULL, 'none', '{\"none\":\"None\",\"tls\":\"TLS\",\"ssl\":\"SSL\"}'), -(15, 'mail', 'text', 'text', 'mail_smtp_host', 'SMTP主机地址', NULL, '', ''), -(16, 'mail', 'text', 'text', 'mail_smtp_username', 'SMTP用户名', NULL, '', ''), -(17, 'mail', 'text', 'password', 'mail_smtp_password', 'SMTP密码', NULL, '', ''), -(18, 'mail', 'text', 'number', 'mail_smtp_port', 'SMTP端口', '25/465', '25', ''), -(19, 'mail', 'text', 'email', 'mail_form_email', '发件人邮箱', NULL, '', ''), -(20, 'other', 'bool', 'checkbox', 'soft_delete', '软删除', '删除图片时不删除源文件,不建议开启', '0', ''), - -(21, 'storage_strategy', 'select', 'text', 'storage_strategy', '储存策略', NULL, 'local', ''), -(22, 'local', 'text', 'text', 'local_cdn_domain', 'CDN加速域名', NULL, '', ''), -(23, 'oss', 'text', 'text', 'oss_cdn_domain', 'Bucket域名', NULL, '', ''), -(24, 'oss', 'text', 'text', 'oss_access_key_id', 'AccessKeyId', NULL, '', ''), -(25, 'oss', 'text', 'text', 'oss_access_key_secret', 'AccessKeySecret', NULL, '', ''), -(26, 'oss', 'text', 'text', 'oss_endpoint', 'Endpoint', '地域节点', '', ''), -(27, 'oss', 'text', 'text', 'oss_bucket', 'Bucket', NULL, '', ''), -(28, 'cos', 'text', 'text', 'cos_cdn_domain', 'CDN加速域名', NULL, '', ''), -(29, 'cos', 'text', 'text', 'cos_secret_id', 'SecretId', NULL, '', ''), -(30, 'cos', 'text', 'text', 'cos_secret_key', 'SecretKey', NULL, '', ''), -(31, 'cos', 'text', 'text', 'cos_region', '所属地域', NULL, '', ''), -(32, 'cos', 'text', 'text', 'cos_bucket', 'Bucket', '储存桶名称', '', ''), -(33, 'kodo', 'text', 'text', 'kodo_cdn_domain', 'CDN加速域名', NULL, '', ''), -(34, 'kodo', 'text', 'text', 'kodo_access_key', 'AccessKey', NULL, '', ''), -(35, 'kodo', 'text', 'text', 'kodo_secret_key', 'SecretKey', NULL, '', ''), -(36, 'kodo', 'text', 'text', 'kodo_bucket', 'Bucket', NULL, '', ''), -(37, 'uss', 'text', 'text', 'uss_cdn_domain', 'CDN加速域名', NULL, '', ''), -(38, 'uss', 'text', 'text', 'uss_operator_name', 'OperatorName', '操作员账号', '', ''), -(39, 'uss', 'text', 'password', 'uss_operator_pwd', 'OperatorPwd', '操作员密码', '', ''), -(40, 'uss', 'text', 'text', 'uss_service_name', 'ServiceName', '云储存服务名称', '', ''), -(41, '', 'text', 'text', 'system_version', '系统版本', NULL, '1.4.2', ''), - - -(42, 'audit', 'bool', 'checkbox', 'open_audit', '开启图片鉴黄', '鉴黄接口申请地址:https://www.moderatecontent.com', '0', ''), -(43, 'audit', 'text', 'text', 'audit_key', 'Key', NULL, '', ''), -(44, 'audit', 'select', 'text', 'audit_index', '内容评级', '1=所有人,2=少年,3=成人', '3', '{\"1\": \"所有人\", \"2\": \"少年\", \"3\": \"成人\"}'), -(45, 'other', 'bool', 'checkbox', 'open_api', '开启API', '是否开放接口', '0', ''); - --- -------------------------------------------------------- + (1, 'basics', 'bool', 'checkbox', 'close_register', '关闭注册', NULL, '0', ''), + (2, 'basics', 'text', 'text', 'site_name', '网站标题', NULL, 'Lsky Pro', ''), + (3, 'basics', 'text', 'text', 'site_keywords', '网站关键字', NULL, 'Lsky Pro', ''), + (4, 'basics', 'text', 'text', 'site_description', '网站描述', NULL, 'Lsky Pro, Your photo album on the cloud.', ''), + (5, 'basics', 'text', 'text', 'icp_number', '备案号', NULL, '', ''), + (6, 'upload', 'bool', 'checkbox', 'allowed_tourist_upload', '允许游客上传', '是否允许游客上传', '1', ''), + (7, 'upload', 'text', 'text', 'upload_max_size', '最大上传大小', '单位:b,默认5242880:5M', '5242880', ''), + (8, 'upload', 'text', 'number', 'upload_single_num', '单次同时上传数量', NULL, '10', ''), + (9, 'upload', 'text', 'text', 'upload_allowed_exts', '允许上传的文件后缀', '逗号隔开', 'jpg,jpeg,gif,png,ico', ''), + (10, 'upload', 'text', 'text', 'path_naming_rule', '文件路径命名规则', '路径命名变量对照表', '{Y}/{m}/{d}', ''), + (11, 'upload', 'text', 'text', 'file_naming_rule', '文件命名规则', '文件命名变量对照表', '{uniqid}', ''), + (12, 'user', 'text', 'text', 'user_initial_quota', '用户初始配额容量', '单位:b,默认1073741824:1G,最大18位', '1073741824', ''), + (13, 'mail', 'select', 'text', 'mail_send_mode', '邮件发送方式', NULL, 'smtp', '{"smtp":"SMTP"}'), + (14, 'mail', 'select', 'text', 'mail_smtp_secure', 'SMTP验证方式', NULL, 'none', '{"none":"None","tls":"TLS","ssl":"SSL"}'), + (15, 'mail', 'text', 'text', 'mail_smtp_host', 'SMTP主机地址', NULL, '', ''), + (16, 'mail', 'text', 'text', 'mail_smtp_username', 'SMTP用户名', NULL, '', ''), + (17, 'mail', 'text', 'password', 'mail_smtp_password', 'SMTP密码', NULL, '', ''), + (18, 'mail', 'text', 'number', 'mail_smtp_port', 'SMTP端口', '25/465', '25', ''), + (19, 'mail', 'text', 'email', 'mail_form_email', '发件人邮箱', NULL, '', ''), + (20, 'other', 'bool', 'checkbox', 'soft_delete', '软删除', '删除图片时不删除源文件,不建议开启', '0', ''), + (21, 'storage_strategy', 'select', 'text', 'storage_strategy', '储存策略', NULL, 'github', ''), + (22, 'local', 'text', 'text', 'local_cdn_domain', 'CDN加速域名', NULL, '', ''), + (23, 'oss', 'text', 'text', 'oss_cdn_domain', 'Bucket域名', NULL, '', ''), + (24, 'oss', 'text', 'text', 'oss_access_key_id', 'AccessKeyId', NULL, '', ''), + (25, 'oss', 'text', 'text', 'oss_access_key_secret', 'AccessKeySecret', NULL, '', ''), + (26, 'oss', 'text', 'text', 'oss_endpoint', 'Endpoint', '地域节点', '', ''), + (27, 'oss', 'text', 'text', 'oss_bucket', 'Bucket', NULL, '', ''), + (28, 'cos', 'text', 'text', 'cos_cdn_domain', 'CDN加速域名', NULL, '', ''), + (29, 'cos', 'text', 'text', 'cos_secret_id', 'SecretId', NULL, '', ''), + (30, 'cos', 'text', 'text', 'cos_secret_key', 'SecretKey', NULL, '', ''), + (31, 'cos', 'text', 'text', 'cos_region', '所属地域', NULL, '', ''), + (32, 'cos', 'text', 'text', 'cos_bucket', 'Bucket', '储存桶名称', '', ''), + (33, 'kodo', 'text', 'text', 'kodo_cdn_domain', 'CDN加速域名', NULL, '', ''), + (34, 'kodo', 'text', 'text', 'kodo_access_key', 'AccessKey', NULL, '', ''), + (35, 'kodo', 'text', 'text', 'kodo_secret_key', 'SecretKey', NULL, '', ''), + (36, 'kodo', 'text', 'text', 'kodo_bucket', 'Bucket', NULL, '', ''), + (37, 'uss', 'text', 'text', 'uss_cdn_domain', 'CDN加速域名', NULL, '', ''), + (38, 'uss', 'text', 'text', 'uss_operator_name', 'OperatorName', '操作员账号', '', ''), + (39, 'uss', 'text', 'password', 'uss_operator_pwd', 'OperatorPwd', '操作员密码', '', ''), + (40, 'uss', 'text', 'text', 'uss_service_name', 'ServiceName', '云储存服务名称', '', ''), + (41, '', 'text', 'text', 'system_version', '系统版本', NULL, '1.4.2', ''), + (42, 'audit', 'bool', 'checkbox', 'open_audit', '开启图片鉴黄', '鉴黄接口申请地址:https://www.moderatecontent.com', '0', ''), + (43, 'audit', 'text', 'text', 'audit_key', 'Key', NULL, '', ''), + (44, 'audit', 'select', 'text', 'audit_index', '内容评级', '1=所有人,2=少年,3=成人', '3', '{"1": "所有人", "2": "少年", "3": "成人"}'), + (45, 'other', 'bool', 'checkbox', 'open_api', '开启API', '是否开放接口', '1', ''), + (46, 'github', 'text', 'text', 'github_token', 'Token', '', '', ''), + (48, 'github', 'text', 'text', 'github_user', '用户账号', '', '', ''), + (49, 'github', 'text', 'text', 'github_repo', '仓库名称', '', '', ''), + (50, 'github', 'text', 'text', 'github_path', '存储路径', '', '', ''), + (51, 'github', 'text', 'text', 'github_message', '提交信息', '', '', ''), + (52, 'github', 'text', 'text', 'github_branch', '分支', '', '', ''), + (53, 'github', 'text', 'text', 'github_cdn_domain', '访问url', '', '', ''); +/*!40000 ALTER TABLE `lsky_config` ENABLE KEYS */; + +-- 导出 表 tuchuang.lsky_folders 结构 +CREATE TABLE IF NOT EXISTS `lsky_folders` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `user_id` int(11) NOT NULL COMMENT '用户ID', + `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '上级文件夹ID', + `name` varchar(100) NOT NULL COMMENT '文件夹名称', + `delete_time` int(11) DEFAULT NULL COMMENT '删除时间', + `update_time` int(11) DEFAULT NULL COMMENT '更新时间', + `create_time` int(11) DEFAULT NULL COMMENT '添加时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文件夹表'; --- --- 表的结构 `lsky_images` --- +-- 正在导出表 tuchuang.lsky_folders 的数据:~0 rows (大约) +DELETE FROM `lsky_folders`; +/*!40000 ALTER TABLE `lsky_folders` DISABLE KEYS */; +/*!40000 ALTER TABLE `lsky_folders` ENABLE KEYS */; -DROP TABLE IF EXISTS `lsky_images`; +-- 导出 表 tuchuang.lsky_images 结构 CREATE TABLE IF NOT EXISTS `lsky_images` ( - `id` int(11) UNSIGNED NOT NULL COMMENT 'ID', - `user_id` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID,为0表示游客上传', - `folder_id` int(11) NOT NULL DEFAULT 0 COMMENT '文件夹ID', + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', + `user_id` int(11) NOT NULL DEFAULT '0' COMMENT '用户ID,为0表示游客上传', + `folder_id` int(11) NOT NULL DEFAULT '0' COMMENT '文件夹ID', `strategy` varchar(32) NOT NULL DEFAULT 'local' COMMENT '储存策略,默认本地', `path` varchar(500) NOT NULL COMMENT '保存路径', `name` varchar(500) NOT NULL COMMENT '保存名称', @@ -107,18 +115,18 @@ CREATE TABLE IF NOT EXISTS `lsky_images` ( `sha1` varchar(100) NOT NULL COMMENT 'hash sha1', `md5` varchar(32) NOT NULL COMMENT 'hash md5', `ip` varchar(128) DEFAULT NULL COMMENT '上传者IP', - `create_time` int(11) NOT NULL COMMENT '创建时间' -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图片表'; + `create_time` int(11) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='图片表'; --- -------------------------------------------------------- - --- --- 表的结构 `lsky_users` --- +-- 正在导出表 tuchuang.lsky_images 的数据:~0 rows (大约) +DELETE FROM `lsky_images`; +/*!40000 ALTER TABLE `lsky_images` DISABLE KEYS */; +/*!40000 ALTER TABLE `lsky_images` ENABLE KEYS */; -DROP TABLE IF EXISTS `lsky_users`; +-- 导出 表 tuchuang.lsky_users 结构 CREATE TABLE IF NOT EXISTS `lsky_users` ( - `id` int(11) UNSIGNED NOT NULL, + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL COMMENT '用户名', `nickname` varchar(32) DEFAULT NULL COMMENT '昵称', `email` varchar(100) NOT NULL COMMENT '邮箱', @@ -131,79 +139,14 @@ CREATE TABLE IF NOT EXISTS `lsky_users` ( `reg_ip` varchar(32) DEFAULT NULL COMMENT '注册IP', `delete_time` int(11) DEFAULT NULL COMMENT '删除时间', `update_time` int(11) NOT NULL COMMENT '更新时间', - `create_time` int(11) NOT NULL COMMENT '添加时间' -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; - --- --- 表的结构 `lsky_folders` --- - -DROP TABLE IF EXISTS `lsky_folders`; -CREATE TABLE `lsky_folders` ( - `id` int(11) NOT NULL COMMENT 'ID', - `user_id` int(11) NOT NULL COMMENT '用户ID', - `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '上级文件夹ID', - `name` varchar(100) NOT NULL COMMENT '文件夹名称', - `delete_time` int(11) DEFAULT NULL COMMENT '删除时间', - `update_time` int(11) DEFAULT NULL COMMENT '更新时间', - `create_time` int(11) DEFAULT NULL COMMENT '添加时间' -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件夹表'; - --- --- Indexes for dumped tables --- - --- --- Indexes for table `lsky_config` --- -ALTER TABLE `lsky_config` - ADD PRIMARY KEY (`id`), - ADD UNIQUE KEY `name` (`name`); - --- --- Indexes for table `lsky_images` --- -ALTER TABLE `lsky_images` - ADD PRIMARY KEY (`id`); - --- --- Indexes for table `lsky_users` --- -ALTER TABLE `lsky_users` - ADD PRIMARY KEY (`id`), - ADD UNIQUE KEY `username` (`username`), - ADD UNIQUE KEY `email` (`email`); - --- --- Indexes for table `lsky_folders` --- -ALTER TABLE `lsky_folders` -ADD PRIMARY KEY (`id`); - --- --- 在导出的表使用AUTO_INCREMENT --- - --- --- 使用表AUTO_INCREMENT `lsky_config` --- -ALTER TABLE `lsky_config` - MODIFY `id` smallint(6) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=46; + `create_time` int(11) NOT NULL COMMENT '添加时间', + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`), + UNIQUE KEY `email` (`email`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户表'; --- --- 使用表AUTO_INCREMENT `lsky_images` --- -ALTER TABLE `lsky_images` - MODIFY `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID'; --- --- 使用表AUTO_INCREMENT `lsky_users` --- -ALTER TABLE `lsky_users` - MODIFY `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1; --- --- 使用表AUTO_INCREMENT `lsky_folders` --- -ALTER TABLE `lsky_folders` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', AUTO_INCREMENT=1; +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; diff --git a/thinkphp/library/think/App.php b/thinkphp/library/think/App.php index cfa2601e..ac8b0e91 100644 --- a/thinkphp/library/think/App.php +++ b/thinkphp/library/think/App.php @@ -20,7 +20,7 @@ */ class App extends Container { - const VERSION = '5.1.31 LTS'; + const VERSION = '5.1.32 LTS'; /** * 当前模块路径 @@ -722,9 +722,9 @@ public function controller($name, $layer = 'controller', $appendSuffix = false, list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); if (class_exists($class)) { - return $this->__get($class); + return $this->make($class, true); } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) { - return $this->__get($emptyClass); + return $this->make($emptyClass, true); } throw new ClassNotFoundException('class not exists:' . $class, $class); diff --git a/thinkphp/library/think/Controller.php b/thinkphp/library/think/Controller.php index a57da9e3..d16a1ed5 100644 --- a/thinkphp/library/think/Controller.php +++ b/thinkphp/library/think/Controller.php @@ -67,6 +67,8 @@ public function __construct(App $app = null) // 控制器初始化 $this->initialize(); + $this->registerMiddleware(); + // 前置操作方法 即将废弃 foreach ((array) $this->beforeActionList as $method => $options) { is_numeric($method) ? diff --git a/thinkphp/library/think/Model.php b/thinkphp/library/think/Model.php index e0dcecf6..9896f9ff 100644 --- a/thinkphp/library/think/Model.php +++ b/thinkphp/library/think/Model.php @@ -392,11 +392,12 @@ public function replace($replace = true) * 设置数据是否存在 * @access public * @param bool $exists - * @return void + * @return $this */ public function exists($exists) { $this->exists = $exists; + return $this; } /** diff --git a/thinkphp/library/think/Validate.php b/thinkphp/library/think/Validate.php index 4672a5cb..54c3a737 100644 --- a/thinkphp/library/think/Validate.php +++ b/thinkphp/library/think/Validate.php @@ -516,7 +516,7 @@ protected function checkItem($field, $value, $rules, $data, $title = '', $msg = $rules = array_merge($rules, $this->append[$field]); } - $i = 0; + $i = 0; $result = true; foreach ($rules as $key => $rule) { @@ -1002,10 +1002,14 @@ public function unique($value, $rule, $data, $field) // 支持多个字段验证 $fields = explode('^', $key); foreach ($fields as $key) { - $map[] = [$key, '=', $data[$key]]; + if (isset($data[$key])) { + $map[] = [$key, '=', $data[$key]]; + } } - } else { + } elseif (isset($data[$field])) { $map[] = [$key, '=', $data[$field]]; + } else { + $map = []; } $pk = !empty($rule[3]) ? $rule[3] : $db->getPk(); diff --git a/thinkphp/library/think/cache/Driver.php b/thinkphp/library/think/cache/Driver.php index f0ec7baf..f4c5dcbf 100644 --- a/thinkphp/library/think/cache/Driver.php +++ b/thinkphp/library/think/cache/Driver.php @@ -219,7 +219,7 @@ public function tag($name, $keys = null, $overlay = false) } elseif (is_null($keys)) { $this->tag = $name; } else { - $key = 'tag_' . md5($name); + $key = $this->getTagkey($name); if (is_string($keys)) { $keys = explode(',', $keys); @@ -248,14 +248,19 @@ public function tag($name, $keys = null, $overlay = false) protected function setTagItem($name) { if ($this->tag) { - $key = 'tag_' . md5($this->tag); + $key = $this->getTagkey($this->tag); $prev = $this->tag; $this->tag = null; if ($this->has($key)) { $value = explode(',', $this->get($key)); $value[] = $name; - $value = implode(',', array_unique($value)); + + if (count($value) > 1000) { + array_shift($value); + } + + $value = implode(',', array_unique($value)); } else { $value = $name; } @@ -273,7 +278,7 @@ protected function setTagItem($name) */ protected function getTagItem($tag) { - $key = 'tag_' . md5($tag); + $key = $this->getTagkey($tag); $value = $this->get($key); if ($value) { @@ -283,6 +288,11 @@ protected function getTagItem($tag) } } + protected function getTagKey($tag) + { + return 'tag_' . md5($tag); + } + /** * 序列化数据 * @access protected diff --git a/thinkphp/library/think/cache/driver/File.php b/thinkphp/library/think/cache/driver/File.php index 7c5661e3..93d321f2 100644 --- a/thinkphp/library/think/cache/driver/File.php +++ b/thinkphp/library/think/cache/driver/File.php @@ -266,7 +266,7 @@ public function clear($tag = null) foreach ($keys as $key) { $this->unlink($key); } - $this->rm('tag_' . md5($tag)); + $this->rm($this->getTagKey($tag)); return true; } diff --git a/thinkphp/library/think/cache/driver/Lite.php b/thinkphp/library/think/cache/driver/Lite.php index 544663c0..0cfe3907 100644 --- a/thinkphp/library/think/cache/driver/Lite.php +++ b/thinkphp/library/think/cache/driver/Lite.php @@ -198,7 +198,7 @@ public function clear($tag = null) unlink($key); } - $this->rm('tag_' . md5($tag)); + $this->rm($this->getTagKey($tag)); return true; } diff --git a/thinkphp/library/think/cache/driver/Memcache.php b/thinkphp/library/think/cache/driver/Memcache.php index 162ca520..1c535597 100644 --- a/thinkphp/library/think/cache/driver/Memcache.php +++ b/thinkphp/library/think/cache/driver/Memcache.php @@ -188,11 +188,13 @@ public function clear($tag = null) if ($tag) { // 指定标签清除 $keys = $this->getTagItem($tag); + foreach ($keys as $key) { $this->handler->delete($key); } - $this->rm('tag_' . md5($tag)); + $tagName = $this->getTagKey($tag); + $this->rm($tagName); return true; } @@ -200,4 +202,5 @@ public function clear($tag = null) return $this->handler->flush(); } + } diff --git a/thinkphp/library/think/cache/driver/Memcached.php b/thinkphp/library/think/cache/driver/Memcached.php index d04fac08..6af60d19 100644 --- a/thinkphp/library/think/cache/driver/Memcached.php +++ b/thinkphp/library/think/cache/driver/Memcached.php @@ -204,7 +204,7 @@ public function clear($tag = null) $keys = $this->getTagItem($tag); $this->handler->deleteMulti($keys); - $this->rm('tag_' . md5($tag)); + $this->rm($this->getTagKey($tag)); return true; } @@ -213,4 +213,67 @@ public function clear($tag = null) return $this->handler->flush(); } + + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return $this + */ + public function tag($name, $keys = null, $overlay = false) + { + if (is_null($keys)) { + $this->tag = $name; + } else { + $tagName = $this->getTagKey($name); + if ($overlay) { + $this->handler->delete($tagName); + } + + if (!$this->handler->has($tagName)) { + $this->handler->set($tagName, ''); + } + + foreach ($keys as $key) { + $this->handler->append($tagName, ',' . $key); + } + } + + return $this; + } + + /** + * 更新标签 + * @access protected + * @param string $name 缓存标识 + * @return void + */ + protected function setTagItem($name) + { + if ($this->tag) { + $tagName = $this->getTagKey($this->tag); + + if ($this->handler->has($tagName)) { + $this->handler->append($tagName, ',' . $name); + } else { + $this->handler->set($tagName, $name); + } + + $this->tag = null; + } + } + + /** + * 获取标签包含的缓存标识 + * @access public + * @param string $tag 缓存标签 + * @return array + */ + public function getTagItem($tag) + { + $tagName = $this->getTagKey($tag); + return explode(',', trim($this->handler->get($tagName), ',')); + } } diff --git a/thinkphp/library/think/cache/driver/Redis.php b/thinkphp/library/think/cache/driver/Redis.php index b924ec3d..813746e7 100644 --- a/thinkphp/library/think/cache/driver/Redis.php +++ b/thinkphp/library/think/cache/driver/Redis.php @@ -70,6 +70,10 @@ public function __construct($options = []) } } + if ('' == $this->options['password']) { + unset($this->options['password']); + } + $this->handler = new \Predis\Client($this->options, $params); $this->options['prefix'] = ''; @@ -187,7 +191,7 @@ public function rm($name) { $this->writeTimes++; - return $this->handler->delete($this->getCacheKey($name)); + return $this->handler->del($this->getCacheKey($name)); } /** @@ -202,11 +206,10 @@ public function clear($tag = null) // 指定标签清除 $keys = $this->getTagItem($tag); - foreach ($keys as $key) { - $this->handler->delete($key); - } + $this->handler->del($keys); - $this->rm('tag_' . md5($tag)); + $tagName = $this->getTagKey($tag); + $this->handler->del($tagName); return true; } @@ -215,4 +218,55 @@ public function clear($tag = null) return $this->handler->flushDB(); } + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return $this + */ + public function tag($name, $keys = null, $overlay = false) + { + if (is_null($keys)) { + $this->tag = $name; + } else { + $tagName = $this->getTagKey($name); + if ($overlay) { + $this->handler->del($tagName); + } + + foreach ($keys as $key) { + $this->handler->sAdd($tagName, $key); + } + } + + return $this; + } + + /** + * 更新标签 + * @access protected + * @param string $name 缓存标识 + * @return void + */ + protected function setTagItem($name) + { + if ($this->tag) { + $tagName = $this->getTagKey($this->tag); + $this->handler->sAdd($tagName, $name); + } + } + + /** + * 获取标签包含的缓存标识 + * @access protected + * @param string $tag 缓存标签 + * @return array + */ + protected function getTagItem($tag) + { + $tagName = $this->getTagKey($tag); + return $this->handler->sMembers($tagName); + } } diff --git a/thinkphp/library/think/cache/driver/Sqlite.php b/thinkphp/library/think/cache/driver/Sqlite.php index 7e78ec12..f57361e3 100644 --- a/thinkphp/library/think/cache/driver/Sqlite.php +++ b/thinkphp/library/think/cache/driver/Sqlite.php @@ -216,7 +216,7 @@ public function rm($name) public function clear($tag = null) { if ($tag) { - $name = sqlite_escape_string($tag); + $name = sqlite_escape_string($this->getTagKey($tag)); $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE tag=\'' . $name . '\''; sqlite_query($this->handler, $sql); return true; diff --git a/thinkphp/library/think/cache/driver/Wincache.php b/thinkphp/library/think/cache/driver/Wincache.php index 10966e78..ef157841 100644 --- a/thinkphp/library/think/cache/driver/Wincache.php +++ b/thinkphp/library/think/cache/driver/Wincache.php @@ -160,15 +160,16 @@ public function clear($tag = null) { if ($tag) { $keys = $this->getTagItem($tag); - foreach ($keys as $key) { - wincache_ucache_delete($key); - } - $this->rm('tag_' . md5($tag)); + + wincache_ucache_delete($keys); + + $tagName = $this->getTagkey($tag); + $this->rm($tagName); return true; - } else { - $this->writeTimes++; - return wincache_ucache_clear(); } + + $this->writeTimes++; + return wincache_ucache_clear(); } } diff --git a/thinkphp/library/think/cache/driver/Xcache.php b/thinkphp/library/think/cache/driver/Xcache.php index 6d1bf3f6..4e698597 100644 --- a/thinkphp/library/think/cache/driver/Xcache.php +++ b/thinkphp/library/think/cache/driver/Xcache.php @@ -159,10 +159,12 @@ public function clear($tag = null) if ($tag) { // 指定标签清除 $keys = $this->getTagItem($tag); + foreach ($keys as $key) { xcache_unset($key); } - $this->rm('tag_' . md5($tag)); + + $this->rm($this->getTagKey($tag)); return true; } diff --git a/thinkphp/library/think/db/Query.php b/thinkphp/library/think/db/Query.php index 00483328..2704c8df 100644 --- a/thinkphp/library/think/db/Query.php +++ b/thinkphp/library/think/db/Query.php @@ -628,9 +628,6 @@ public function aggregate($aggregate, $field, $force = false) $result = (float) $result; } - // 查询完成后清空聚合字段信息 - $this->removeOption('field'); - return $result; } diff --git a/thinkphp/library/think/model/concern/Attribute.php b/thinkphp/library/think/model/concern/Attribute.php index 75b02ab5..b7a80fd3 100644 --- a/thinkphp/library/think/model/concern/Attribute.php +++ b/thinkphp/library/think/model/concern/Attribute.php @@ -371,7 +371,8 @@ protected function autoWriteTimestamp($name) case 'datetime': case 'date': $format = !empty($param) ? $param : $this->dateFormat; - $value = $this->formatDateTime($format . '.u'); + $format .= strpos($format, 'u') || false !== strpos($format, '\\') ? '' : '.u'; + $value = $this->formatDateTime($format); break; case 'timestamp': case 'integer': @@ -384,7 +385,8 @@ protected function autoWriteTimestamp($name) 'date', 'timestamp', ])) { - $value = $this->formatDateTime($this->dateFormat . '.u'); + $format = strpos($this->dateFormat, 'u') || false !== strpos($this->dateFormat, '\\') ? '' : '.u'; + $value = $this->formatDateTime($this->dateFormat . $format); } else { $value = time(); } diff --git a/thinkphp/library/think/model/relation/BelongsToMany.php b/thinkphp/library/think/model/relation/BelongsToMany.php index b7cdebe1..747482b6 100644 --- a/thinkphp/library/think/model/relation/BelongsToMany.php +++ b/thinkphp/library/think/model/relation/BelongsToMany.php @@ -559,7 +559,9 @@ public function attach($data, $pivot = []) foreach ($ids as $id) { $pivot[$this->foreignKey] = $id; - $this->pivot->replace()->save($pivot); + $this->pivot->replace() + ->exists(false) + ->save($pivot); $result[] = $this->newPivot($pivot, true); } diff --git a/thinkphp/library/think/model/relation/HasMany.php b/thinkphp/library/think/model/relation/HasMany.php index 72d83144..dbb8fa08 100644 --- a/thinkphp/library/think/model/relation/HasMany.php +++ b/thinkphp/library/think/model/relation/HasMany.php @@ -241,9 +241,9 @@ protected function eagerlyOneToMany($where, $relation, $subRelation = '', $closu */ public function save($data, $replace = true) { - $model = $this->make($data); + $model = $this->make(); - return $model->replace($replace)->save() ? $model : false; + return $model->replace($replace)->save($data) ? $model : false; } /** diff --git a/thinkphp/library/think/model/relation/MorphMany.php b/thinkphp/library/think/model/relation/MorphMany.php index 1a7f15eb..a1f54889 100644 --- a/thinkphp/library/think/model/relation/MorphMany.php +++ b/thinkphp/library/think/model/relation/MorphMany.php @@ -277,7 +277,7 @@ protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $clo */ public function save($data) { - $model = $this->make($data); + $model = $this->make(); return $model->save($data) ? $model : false; } diff --git a/thinkphp/library/think/model/relation/MorphOne.php b/thinkphp/library/think/model/relation/MorphOne.php index 716539f4..775b2dfd 100644 --- a/thinkphp/library/think/model/relation/MorphOne.php +++ b/thinkphp/library/think/model/relation/MorphOne.php @@ -211,8 +211,8 @@ protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $clos */ public function save($data) { - $model = $this->make($data); - return $model->save() ? $model : false; + $model = $this->make(); + return $model->save($data) ? $model : false; } /** diff --git a/thinkphp/library/think/route/Rule.php b/thinkphp/library/think/route/Rule.php index 35730fee..fdfc29d1 100644 --- a/thinkphp/library/think/route/Rule.php +++ b/thinkphp/library/think/route/Rule.php @@ -722,13 +722,17 @@ public function parseRule($request, $rule, $route, $url, $option = [], $matches // 替换路由地址中的变量 if (is_string($route) && !empty($matches)) { - foreach ($matches as $key => $val) { - if (false !== strpos($route, '<' . $key . '>')) { - $route = str_replace('<' . $key . '>', $val, $route); - } elseif (false !== strpos($route, ':' . $key)) { - $route = str_replace(':' . $key, $val, $route); - } + $search = $replace = []; + + foreach ($matches as $key => $value) { + $search[] = '<' . $key . '>'; + $replace[] = $value; + + $search[] = ':' . $key; + $replace[] = $value; } + + $route = str_replace($search, $replace, $route); } // 解析额外参数 diff --git a/thinkphp/library/think/route/dispatch/Module.php b/thinkphp/library/think/route/dispatch/Module.php index dc1974ce..e8842cd3 100644 --- a/thinkphp/library/think/route/dispatch/Module.php +++ b/thinkphp/library/think/route/dispatch/Module.php @@ -69,10 +69,6 @@ public function init() // 获取控制器名 $controller = strip_tags($result[1] ?: $this->rule->getConfig('default_controller')); - if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) { - throw new HttpException(404, 'controller not exists:' . $controller); - } - $this->controller = $convert ? strtolower($controller) : $controller; // 获取操作名 @@ -97,10 +93,6 @@ public function exec() $this->rule->getConfig('url_controller_layer'), $this->rule->getConfig('controller_suffix'), $this->rule->getConfig('empty_controller')); - - if ($instance instanceof Controller) { - $instance->registerMiddleware(); - } } catch (ClassNotFoundException $e) { throw new HttpException(404, 'controller not exists:' . $e->getClass()); } diff --git a/thinkphp/library/think/route/dispatch/Url.php b/thinkphp/library/think/route/dispatch/Url.php index 95ee9e53..00dc8cca 100644 --- a/thinkphp/library/think/route/dispatch/Url.php +++ b/thinkphp/library/think/route/dispatch/Url.php @@ -60,6 +60,10 @@ protected function parseUrl($url) $controller = !empty($path) ? array_shift($path) : null; } + if ($controller && !preg_match('/^[A-Za-z][\w|\.]*$/', $controller)) { + throw new HttpException(404, 'controller not exists:' . $controller); + } + // 解析操作 $action = !empty($path) ? array_shift($path) : null; diff --git a/update.sql b/update.sql deleted file mode 100644 index b83bdcb5..00000000 --- a/update.sql +++ /dev/null @@ -1,48 +0,0 @@ --- --- Sql更新文件,选择数据库后导入,或直接复制内容执行 --- - --- v1.2.0 -UPDATE `lsky_config` SET `value` = '1.2.1' WHERE `lsky_config`.`name` = 'system_version'; - --- v1.3.2 -UPDATE `lsky_config` SET `value` = '1.3.2' WHERE `lsky_config`.`name` = 'system_version'; - -INSERT IGNORE INTO `lsky_config` (`id`, `key`, `type`, `input_type`, `name`, `title`, `tip`, `value`, `extend`) VALUES -(NULL, 'audit', 'bool', 'checkbox', 'open_audit', '开启图片鉴黄', '鉴黄接口申请地址:https://www.moderatecontent.com', '0', ''), -(NULL, 'audit', 'text', 'text', 'audit_key', 'Key', NULL, '', ''), -(NULL, 'audit', 'select', 'text', 'audit_index', '内容评级', '1=所有人,2=少年,3=成人', '3', '{\"1\": \"所有人\", \"2\": \"少年\", \"3\": \"成人\"}'), -(NULL, 'other', 'bool', 'checkbox', 'open_api', '开启API', '是否开放接口', '0', ''); - --- v1.3.3 -UPDATE `lsky_config` SET `value` = '1.3.3' WHERE `lsky_config`.`name` = 'system_version'; - --- v1.4.0 -UPDATE `lsky_config` SET `value` = '1.4.0' WHERE `lsky_config`.`name` = 'system_version'; -UPDATE `lsky_config` SET `key` = 'uss', `name` = 'uss_cdn_domain' WHERE `lsky_config`.`name` = 'upyun_cdn_domain'; -UPDATE `lsky_config` SET `key` = 'uss', `name` = 'uss_operator_name' WHERE `lsky_config`.`name` = 'upyun_operator_name'; -UPDATE `lsky_config` SET `key` = 'uss', `name` = 'uss_operator_pwd' WHERE `lsky_config`.`name` = 'upyun_operator_pwd'; -UPDATE `lsky_config` SET `key` = 'uss', `name` = 'uss_service_name' WHERE `lsky_config`.`name` = 'upyun_service_name'; -UPDATE `lsky_config` SET `key` = 'kodo', `name` = 'kodo_cdn_domain' WHERE `lsky_config`.`name` = 'qiniu_cdn_domain'; -UPDATE `lsky_config` SET `key` = 'kodo', `name` = 'kodo_access_key' WHERE `lsky_config`.`name` = 'qiniu_access_key'; -UPDATE `lsky_config` SET `key` = 'kodo', `name` = 'kodo_secret_key' WHERE `lsky_config`.`name` = 'qiniu_secret_key'; -UPDATE `lsky_config` SET `key` = 'kodo', `name` = 'kodo_bucket' WHERE `lsky_config`.`name` = 'qiniu_bucket'; -UPDATE `lsky_config` SET `value` = 'kodo' WHERE `lsky_config`.`value` = 'qiniu'; -UPDATE `lsky_config` SET `value` = 'uss' WHERE `lsky_config`.`value` = 'upyun'; - --- v1.4.1 -UPDATE `lsky_config` SET `value` = '1.4.1' WHERE `lsky_config`.`name` = 'system_version'; -CREATE TABLE IF NOT EXISTS `lsky_folders` ( - `id` int(11) PRIMARY KEY AUTO_INCREMENT NOT NULL COMMENT 'ID', - `user_id` int(11) NOT NULL COMMENT '用户ID', - `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '上级文件夹ID', - `name` varchar(100) NOT NULL COMMENT '文件夹名称', - `delete_time` int(11) DEFAULT NULL COMMENT '删除时间', - `update_time` int(11) DEFAULT NULL COMMENT '更新时间', - `create_time` int(11) DEFAULT NULL COMMENT '添加时间' -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件夹表'; - --- v1.4.2 -UPDATE `lsky_config` SET `value` = '1.4.2' WHERE `lsky_config`.`name` = 'system_version'; -UPDATE `lsky_images` SET `strategy` = 'uss' WHERE `lsky_images`.`strategy` = 'upyun'; -UPDATE `lsky_images` SET `strategy` = 'kodo' WHERE `lsky_images`.`strategy` = 'qiniu'; diff --git a/vendor/clue/stream-filter/.gitignore b/vendor/clue/stream-filter/.gitignore new file mode 100644 index 00000000..de4a392c --- /dev/null +++ b/vendor/clue/stream-filter/.gitignore @@ -0,0 +1,2 @@ +/vendor +/composer.lock diff --git a/vendor/clue/stream-filter/.travis.yml b/vendor/clue/stream-filter/.travis.yml new file mode 100644 index 00000000..a71864a3 --- /dev/null +++ b/vendor/clue/stream-filter/.travis.yml @@ -0,0 +1,26 @@ +language: php + +php: +# - 5.3 # requires old distro, see below + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - hhvm # ignore errors, see below + +# lock distro so new future defaults will not break the build +dist: trusty + +matrix: + include: + - php: 5.3 + dist: precise + allow_failures: + - php: hhvm + +install: + - composer install --no-interaction + +script: + - vendor/bin/phpunit --coverage-text diff --git a/vendor/clue/stream-filter/CHANGELOG.md b/vendor/clue/stream-filter/CHANGELOG.md new file mode 100644 index 00000000..9d53cd83 --- /dev/null +++ b/vendor/clue/stream-filter/CHANGELOG.md @@ -0,0 +1,54 @@ +# Changelog + +## 1.4.0 (2017-08-18) + +* Feature / Fix: The `fun()` function does not pass filter parameter `null` + to underlying `stream_filter_append()` by default + (#15 by @Nyholm) + + Certain filters (such as `convert.quoted-printable-encode`) do not accept + a filter parameter at all. If no explicit filter parameter is given, we no + longer pass a default `null` value. + + ```php + $encode = Filter\fun('convert.quoted-printable-encode'); + assert('t=C3=A4st' === $encode('täst')); + ``` + +* Add examples and improve documentation + (#13 and #20 by @clue and #18 by @Nyholm) + +* Improve test suite by adding PHPUnit to require-dev, + fix HHVM build for now again and ignore future HHVM build errors, + lock Travis distro so new future defaults will not break the build + and test on PHP 7.1 + (#12, #14 and #19 by @clue and #16 by @Nyholm) + +## 1.3.0 (2015-11-08) + +* Feature: Support accessing built-in filters as callbacks + (#5 by @clue) + + ```php + $fun = Filter\fun('zlib.deflate'); + + $ret = $fun('hello') . $fun('world') . $fun(); + assert('helloworld' === gzinflate($ret)); + ``` + +## 1.2.0 (2015-10-23) + +* Feature: Invoke close event when closing filter (flush buffer) + (#9 by @clue) + +## 1.1.0 (2015-10-22) + +* Feature: Abort filter operation when catching an Exception + (#10 by @clue) + +* Feature: Additional safeguards to prevent filter state corruption + (#7 by @clue) + +## 1.0.0 (2015-10-18) + +* First tagged release diff --git a/vendor/clue/stream-filter/LICENSE b/vendor/clue/stream-filter/LICENSE new file mode 100644 index 00000000..dc09d1e6 --- /dev/null +++ b/vendor/clue/stream-filter/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 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. diff --git a/vendor/clue/stream-filter/README.md b/vendor/clue/stream-filter/README.md new file mode 100644 index 00000000..d46c2b57 --- /dev/null +++ b/vendor/clue/stream-filter/README.md @@ -0,0 +1,297 @@ +# clue/stream-filter [![Build Status](https://travis-ci.org/clue/php-stream-filter.svg?branch=master)](https://travis-ci.org/clue/php-stream-filter) + +A simple and modern approach to stream filtering in PHP + +**Table of contents** + +* [Why?](#why) +* [Usage](#usage) + * [append()](#append) + * [prepend()](#prepend) + * [fun()](#fun) + * [remove()](#remove) +* [Install](#install) +* [Tests](#tests) +* [License](#license) + +## Why? + +PHP's stream filtering system is great! + +It offers very powerful stream filtering options and comes with a useful set of built-in filters. +These filters can be used to easily and efficiently perform various transformations on-the-fly, such as: + +* read from a gzip'ed input file, +* transcode from ISO-8859-1 (Latin1) to UTF-8, +* write to a bzip output file +* and much more. + +But let's face it: +Its API is [*difficult to work with*](http://php.net/manual/en/php-user-filter.filter.php) +and its documentation is [*subpar*](http://stackoverflow.com/questions/27103269/what-is-a-bucket-brigade). +This combined means its powerful features are often neglected. + +This project aims to make these features more accessible to a broader audience. +* **Lightweight, SOLID design** - + Provides a thin abstraction that is [*just good enough*](http://en.wikipedia.org/wiki/Principle_of_good_enough) + and does not get in your way. + Custom filters require trivial effort. +* **Good test coverage** - + Comes with an automated tests suite and is regularly tested in the *real world* + +## Usage + +This lightweight library consists only of a few simple functions. +All functions reside under the `Clue\StreamFilter` namespace. + +The below examples assume you use an import statement similar to this: + +```php +use Clue\StreamFilter as Filter; + +Filter\append(…); +``` + +Alternatively, you can also refer to them with their fully-qualified name: + +```php +\Clue\StreamFilter\append(…); +``` + +### append() + +The `append($stream, $callback, $read_write = STREAM_FILTER_ALL)` function can be used to +append a filter callback to the given stream. + +Each stream can have a list of filters attached. +This function appends a filter to the end of this list. + +This function returns a filter resource which can be passed to [`remove()`](#remove). +If the given filter can not be added, it throws an `Exception`. + +The `$stream` can be any valid stream resource, such as: + +```php +$stream = fopen('demo.txt', 'w+'); +``` + +The `$callback` should be a valid callable function which accepts an individual chunk of data +and should return the updated chunk: + +```php +$filter = Filter\append($stream, function ($chunk) { + // will be called each time you read or write a $chunk to/from the stream + return $chunk; +}); +``` + +As such, you can also use native PHP functions or any other `callable`: + +```php +Filter\append($stream, 'strtoupper'); + +// will write "HELLO" to the underlying stream +fwrite($stream, 'hello'); +``` + +If the `$callback` accepts invocation without parameters, then this signature +will be invoked once ending (flushing) the filter: + +```php +Filter\append($stream, function ($chunk = null) { + if ($chunk === null) { + // will be called once ending the filter + return 'end'; + } + // will be called each time you read or write a $chunk to/from the stream + return $chunk; +}); + +fclose($stream); +``` + +> Note: Legacy PHP versions (PHP < 5.4) do not support passing additional data +from the end signal handler if the stream is being closed. + +If your callback throws an `Exception`, then the filter process will be aborted. +In order to play nice with PHP's stream handling, the `Exception` will be +transformed to a PHP warning instead: + +```php +Filter\append($stream, function ($chunk) { + throw new \RuntimeException('Unexpected chunk'); +}); + +// raises an E_USER_WARNING with "Error invoking filter: Unexpected chunk" +fwrite($stream, 'hello'); +``` + +The optional `$read_write` parameter can be used to only invoke the `$callback` when either writing to the stream or only when reading from the stream: + +```php +Filter\append($stream, function ($chunk) { + // will be called each time you write to the stream + return $chunk; +}, STREAM_FILTER_WRITE); + +Filter\append($stream, function ($chunk) { + // will be called each time you read from the stream + return $chunk; +}, STREAM_FILTER_READ); +``` + +> Note that once a filter has been added to stream, the stream can no longer be passed to +> [`stream_select()`](http://php.net/manual/en/function.stream-select.php) +> (and family). +> +> > Warning: stream_select(): cannot cast a filtered stream on this system in {file} on line {line} +> +> This is due to limitations of PHP's stream filter support, as it can no longer reliably +> tell when the underlying stream resource is actually ready. +> As an alternative, consider calling `stream_select()` on the unfiltered stream and +> then pass the unfiltered data through the [`fun()`](#fun) function. + +### prepend() + +The `prepend($stream, $callback, $read_write = STREAM_FILTER_ALL)` function can be used to +prepend a filter callback to the given stream. + +Each stream can have a list of filters attached. +This function prepends a filter to the start of this list. + +This function returns a filter resource which can be passed to [`remove()`](#remove). +If the given filter can not be added, it throws an `Exception`. + +```php +$filter = Filter\prepend($stream, function ($chunk) { + // will be called each time you read or write a $chunk to/from the stream + return $chunk; +}); +``` + +Except for the position in the list of filters, this function behaves exactly +like the [`append()`](#append) function. +For more details about its behavior, see also the [`append()`](#append) function. + +### fun() + +The `fun($filter, $parameters = null)` function can be used to +create a filter function which uses the given built-in `$filter`. + +PHP comes with a useful set of [built-in filters](http://php.net/manual/en/filters.php). +Using `fun()` makes accessing these as easy as passing an input string to filter +and getting the filtered output string. + +```php +$fun = Filter\fun('string.rot13'); + +assert('grfg' === $fun('test')); +assert('test' === $fun($fun('test')); +``` + +Please note that not all filter functions may be available depending on installed +PHP extensions and the PHP version in use. +In particular, [HHVM](http://hhvm.com/) may not offer the same filter functions +or parameters as Zend PHP. +Accessing an unknown filter function will result in a `RuntimeException`: + +```php +Filter\fun('unknown'); // throws RuntimeException +``` + +Some filters may accept or require additional filter parameters – most +filters do not require filter parameters. +If given, the optional `$parameters` argument will be passed to the +underlying filter handler as-is. +In particular, note how *not passing* this parameter at all differs from +explicitly passing a `null` value (which many filters do not accept). +Please refer to the individual filter definition for more details. +For example, the `string.strip_tags` filter can be invoked like this: + +```php +$fun = Filter\fun('string.strip_tags', ''); + +$ret = $fun('h
i
'); +assert('hi' === $ret); +``` + +Under the hood, this function allocates a temporary memory stream, so it's +recommended to clean up the filter function after use. +Also, some filter functions (in particular the +[zlib compression filters](http://php.net/manual/en/filters.compression.php)) +may use internal buffers and may emit a final data chunk on close. +The filter function can be closed by invoking without any arguments: + +```php +$fun = Filter\fun('zlib.deflate'); + +$ret = $fun('hello') . $fun('world') . $fun(); +assert('helloworld' === gzinflate($ret)); +``` + +The filter function must not be used anymore after it has been closed. +Doing so will result in a `RuntimeException`: + +```php +$fun = Filter\fun('string.rot13'); +$fun(); + +$fun('test'); // throws RuntimeException +``` + +> Note: If you're using the zlib compression filters, then you should be wary +about engine inconsistencies between different PHP versions and HHVM. +These inconsistencies exist in the underlying PHP engines and there's little we +can do about this in this library. +[Our test suite](tests/) contains several test cases that exhibit these issues. +If you feel some test case is missing or outdated, we're happy to accept PRs! :) + +### remove() + +The `remove($filter)` function can be used to +remove a filter previously added via [`append()`](#append) or [`prepend()`](#prepend). + +```php +$filter = Filter\append($stream, function () { + // … +}); +Filter\remove($filter); +``` + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This will install the latest supported version: + +```bash +$ composer require clue/stream-filter:^1.4 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 7+ and +HHVM. +It's *highly recommended to use PHP 7+* for this project. +Older PHP versions may suffer from a number of inconsistencies documented above. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](http://getcomposer.org): + +```bash +$ composer install +``` + +To run the test suite, go to the project root and run: + +```bash +$ php vendor/bin/phpunit +``` + +## License + +MIT diff --git a/vendor/clue/stream-filter/composer.json b/vendor/clue/stream-filter/composer.json new file mode 100644 index 00000000..f8710531 --- /dev/null +++ b/vendor/clue/stream-filter/composer.json @@ -0,0 +1,23 @@ +{ + "name": "clue/stream-filter", + "description": "A simple and modern approach to stream filtering in PHP", + "keywords": ["stream", "callback", "filter", "php_user_filter", "stream_filter_append", "stream_filter_register", "bucket brigade"], + "homepage": "https://github.com/clue/php-stream-filter", + "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^5.0 || ^4.8" + }, + "autoload": { + "psr-4": { "Clue\\StreamFilter\\": "src/" }, + "files": [ "src/functions.php" ] + } +} diff --git a/vendor/clue/stream-filter/examples/base64_decode.php b/vendor/clue/stream-filter/examples/base64_decode.php new file mode 100644 index 00000000..2b49f102 --- /dev/null +++ b/vendor/clue/stream-filter/examples/base64_decode.php @@ -0,0 +1,29 @@ + + + + + + ./tests/ + + + + + ./src/ + + + \ No newline at end of file diff --git a/vendor/clue/stream-filter/src/CallbackFilter.php b/vendor/clue/stream-filter/src/CallbackFilter.php new file mode 100644 index 00000000..710940b6 --- /dev/null +++ b/vendor/clue/stream-filter/src/CallbackFilter.php @@ -0,0 +1,120 @@ +closed = false; + + if (!is_callable($this->params)) { + throw new InvalidArgumentException('No valid callback parameter given to stream_filter_(append|prepend)'); + } + $this->callback = $this->params; + + // callback supports end event if it accepts invocation without arguments + $ref = new ReflectionFunction($this->callback); + $this->supportsClose = ($ref->getNumberOfRequiredParameters() === 0); + + return true; + } + + public function onClose() + { + $this->closed = true; + + // callback supports closing and is not already closed + if ($this->supportsClose) { + $this->supportsClose = false; + // invoke without argument to signal end and discard resulting buffer + try { + call_user_func($this->callback); + } catch (Exception $ignored) { + // this might be called during engine shutdown, so it's not safe + // to raise any errors or exceptions here + // trigger_error('Error closing filter: ' . $ignored->getMessage(), E_USER_WARNING); + } + } + + $this->callback = null; + } + + public function filter($in, $out, &$consumed, $closing) + { + // concatenate whole buffer from input brigade + $data = ''; + while ($bucket = stream_bucket_make_writeable($in)) { + $consumed += $bucket->datalen; + $data .= $bucket->data; + } + + // skip processing callback that already ended + if ($this->closed) { + return PSFS_FEED_ME; + } + + // only invoke filter function if buffer is not empty + // this may skip flushing a closing filter + if ($data !== '') { + try { + $data = call_user_func($this->callback, $data); + } catch (Exception $e) { + // exception should mark filter as closed + $this->onClose(); + trigger_error('Error invoking filter: ' . $e->getMessage(), E_USER_WARNING); + + return PSFS_ERR_FATAL; + } + } + + // mark filter as closed after processing closing chunk + if ($closing) { + $this->closed = true; + + // callback supports closing and is not already closed + if ($this->supportsClose) { + $this->supportsClose = false; + + // invoke without argument to signal end and append resulting buffer + try { + $data .= call_user_func($this->callback); + } catch (Exception $e) { + trigger_error('Error ending filter: ' . $e->getMessage(), E_USER_WARNING); + + return PSFS_ERR_FATAL; + } + } + } + + if ($data !== '') { + // create a new bucket for writing the resulting buffer to the output brigade + // reusing an existing bucket turned out to be bugged in some environments (ancient PHP versions and HHVM) + $bucket = @stream_bucket_new($this->stream, $data); + + // legacy PHP versions (PHP < 5.4) do not support passing data from the event signal handler + // because closing the stream invalidates the stream and its stream bucket brigade before + // invoking the filter close handler. + if ($bucket !== false) { + stream_bucket_append($out, $bucket); + } + } + + return PSFS_PASS_ON; + } +} diff --git a/vendor/clue/stream-filter/src/functions.php b/vendor/clue/stream-filter/src/functions.php new file mode 100644 index 00000000..d1ca9dc0 --- /dev/null +++ b/vendor/clue/stream-filter/src/functions.php @@ -0,0 +1,146 @@ + ''); + throw new RuntimeException('Unable to append filter: ' . $error['message']); + } + + return $ret; +} + +/** + * prepend a callback filter to the given stream + * + * @param resource $stream + * @param callable $callback + * @param int $read_write + * @return resource filter resource which can be used for `remove()` + * @throws Exception on error + * @uses stream_filter_prepend() + */ +function prepend($stream, $callback, $read_write = STREAM_FILTER_ALL) +{ + $ret = @stream_filter_prepend($stream, register(), $read_write, $callback); + + if ($ret === false) { + $error = error_get_last() + array('message' => ''); + throw new RuntimeException('Unable to prepend filter: ' . $error['message']); + } + + return $ret; +} + +/** + * Creates filter fun (function) which uses the given built-in $filter + * + * Some filters may accept or require additional filter parameters – most + * filters do not require filter parameters. + * If given, the optional `$parameters` argument will be passed to the + * underlying filter handler as-is. + * In particular, note how *not passing* this parameter at all differs from + * explicitly passing a `null` value (which many filters do not accept). + * Please refer to the individual filter definition for more details. + * + * @param string $filter built-in filter name. See stream_get_filters() or http://php.net/manual/en/filters.php + * @param mixed $parameters (optional) parameters to pass to the built-in filter as-is + * @return callable a filter callback which can be append()'ed or prepend()'ed + * @throws RuntimeException on error + * @link http://php.net/manual/en/filters.php + * @see stream_get_filters() + * @see append() + */ +function fun($filter, $parameters = null) +{ + $fp = fopen('php://memory', 'w'); + if (func_num_args() === 1) { + $filter = @stream_filter_append($fp, $filter, STREAM_FILTER_WRITE); + } else { + $filter = @stream_filter_append($fp, $filter, STREAM_FILTER_WRITE, $parameters); + } + + if ($filter === false) { + fclose($fp); + $error = error_get_last() + array('message' => ''); + throw new RuntimeException('Unable to access built-in filter: ' . $error['message']); + } + + // append filter function which buffers internally + $buffer = ''; + append($fp, function ($chunk) use (&$buffer) { + $buffer .= $chunk; + + // always return empty string in order to skip actually writing to stream resource + return ''; + }, STREAM_FILTER_WRITE); + + $closed = false; + + return function ($chunk = null) use ($fp, $filter, &$buffer, &$closed) { + if ($closed) { + throw new \RuntimeException('Unable to perform operation on closed stream'); + } + if ($chunk === null) { + $closed = true; + $buffer = ''; + fclose($fp); + return $buffer; + } + // initialize buffer and invoke filters by attempting to write to stream + $buffer = ''; + fwrite($fp, $chunk); + + // buffer now contains everything the filter function returned + return $buffer; + }; +} + +/** + * remove a callback filter from the given stream + * + * @param resource $filter + * @return boolean true on success or false on error + * @throws Exception on error + * @uses stream_filter_remove() + */ +function remove($filter) +{ + if (@stream_filter_remove($filter) === false) { + throw new RuntimeException('Unable to remove given filter'); + } +} + +/** + * registers the callback filter and returns the resulting filter name + * + * There should be little reason to call this function manually. + * + * @return string filter name + * @uses CallbackFilter + */ +function register() +{ + static $registered = null; + if ($registered === null) { + $registered = 'stream-callback'; + stream_filter_register($registered, __NAMESPACE__ . '\CallbackFilter'); + } + return $registered; +} diff --git a/vendor/clue/stream-filter/tests/FilterTest.php b/vendor/clue/stream-filter/tests/FilterTest.php new file mode 100644 index 00000000..02aa3a40 --- /dev/null +++ b/vendor/clue/stream-filter/tests/FilterTest.php @@ -0,0 +1,386 @@ +createStream(); + + StreamFilter\append($stream, function ($chunk) { + return strtoupper($chunk); + }); + + fwrite($stream, 'hello'); + fwrite($stream, 'world'); + rewind($stream); + + $this->assertEquals('HELLOWORLD', stream_get_contents($stream)); + + fclose($stream); + } + + public function testAppendNativePhpFunction() + { + $stream = $this->createStream(); + + StreamFilter\append($stream, 'strtoupper'); + + fwrite($stream, 'hello'); + fwrite($stream, 'world'); + rewind($stream); + + $this->assertEquals('HELLOWORLD', stream_get_contents($stream)); + + fclose($stream); + } + + public function testAppendChangingChunkSize() + { + $stream = $this->createStream(); + + StreamFilter\append($stream, function ($chunk) { + return str_replace(array('a','e','i','o','u'), '', $chunk); + }); + + fwrite($stream, 'hello'); + fwrite($stream, 'world'); + rewind($stream); + + $this->assertEquals('hllwrld', stream_get_contents($stream)); + + fclose($stream); + } + + public function testAppendReturningEmptyStringWillNotPassThrough() + { + $stream = $this->createStream(); + + StreamFilter\append($stream, function ($chunk) { + return ''; + }); + + fwrite($stream, 'hello'); + fwrite($stream, 'world'); + rewind($stream); + + $this->assertEquals('', stream_get_contents($stream)); + + fclose($stream); + } + + public function testAppendEndEventCanBeBufferedOnClose() + { + if (PHP_VERSION < 5.4) $this->markTestSkipped('Not supported on legacy PHP'); + + $stream = $this->createStream(); + + StreamFilter\append($stream, function ($chunk = null) { + if ($chunk === null) { + // this signals the end event + return '!'; + } + return $chunk . ' '; + }, STREAM_FILTER_WRITE); + + $buffered = ''; + StreamFilter\append($stream, function ($chunk) use (&$buffered) { + $buffered .= $chunk; + return ''; + }); + + fwrite($stream, 'hello'); + fwrite($stream, 'world'); + + fclose($stream); + + $this->assertEquals('hello world !', $buffered); + } + + public function testAppendEndEventWillBeCalledOnRemove() + { + $stream = $this->createStream(); + + $ended = false; + $filter = StreamFilter\append($stream, function ($chunk = null) use (&$ended) { + if ($chunk === null) { + $ended = true; + } + return $chunk; + }, STREAM_FILTER_WRITE); + + $this->assertEquals(0, $ended); + StreamFilter\remove($filter); + $this->assertEquals(1, $ended); + } + + public function testAppendEndEventWillBeCalledOnClose() + { + $stream = $this->createStream(); + + $ended = false; + StreamFilter\append($stream, function ($chunk = null) use (&$ended) { + if ($chunk === null) { + $ended = true; + } + return $chunk; + }, STREAM_FILTER_WRITE); + + $this->assertEquals(0, $ended); + fclose($stream); + $this->assertEquals(1, $ended); + } + + public function testAppendWriteOnly() + { + $stream = $this->createStream(); + + $invoked = 0; + + StreamFilter\append($stream, function ($chunk) use (&$invoked) { + ++$invoked; + + return $chunk; + }, STREAM_FILTER_WRITE); + + fwrite($stream, 'a'); + fwrite($stream, 'b'); + fwrite($stream, 'c'); + rewind($stream); + + $this->assertEquals(3, $invoked); + $this->assertEquals('abc', stream_get_contents($stream)); + + fclose($stream); + } + + public function testAppendReadOnly() + { + $stream = $this->createStream(); + + $invoked = 0; + + StreamFilter\append($stream, function ($chunk) use (&$invoked) { + ++$invoked; + + return $chunk; + }, STREAM_FILTER_READ); + + fwrite($stream, 'a'); + fwrite($stream, 'b'); + fwrite($stream, 'c'); + rewind($stream); + + $this->assertEquals(0, $invoked); + $this->assertEquals('abc', stream_get_contents($stream)); + $this->assertEquals(1, $invoked); + + fclose($stream); + } + + public function testOrderCallingAppendAfterPrepend() + { + $stream = $this->createStream(); + + StreamFilter\append($stream, function ($chunk) { + return '[' . $chunk . ']'; + }, STREAM_FILTER_WRITE); + + StreamFilter\prepend($stream, function ($chunk) { + return '(' . $chunk . ')'; + }, STREAM_FILTER_WRITE); + + fwrite($stream, 'hello'); + rewind($stream); + + $this->assertEquals('[(hello)]', stream_get_contents($stream)); + + fclose($stream); + } + + public function testRemoveFilter() + { + $stream = $this->createStream(); + + $first = StreamFilter\append($stream, function ($chunk) { + return $chunk . '?'; + }, STREAM_FILTER_WRITE); + + StreamFilter\append($stream, function ($chunk) { + return $chunk . '!'; + }, STREAM_FILTER_WRITE); + + StreamFilter\remove($first); + + fwrite($stream, 'hello'); + rewind($stream); + + $this->assertEquals('hello!', stream_get_contents($stream)); + + fclose($stream); + } + + public function testAppendFunDechunk() + { + if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (dechunk filter does not exist)'); + + $stream = $this->createStream(); + + StreamFilter\append($stream, StreamFilter\fun('dechunk'), STREAM_FILTER_WRITE); + + fwrite($stream, "2\r\nhe\r\n"); + fwrite($stream, "3\r\nllo\r\n"); + fwrite($stream, "0\r\n\r\n"); + rewind($stream); + + $this->assertEquals('hello', stream_get_contents($stream)); + + fclose($stream); + } + + public function testAppendThrows() + { + $this->createErrorHandler($errors); + + $stream = $this->createStream(); + $this->createErrorHandler($errors); + + StreamFilter\append($stream, function ($chunk) { + throw new \DomainException($chunk); + }); + + fwrite($stream, 'test'); + + $this->removeErrorHandler(); + $this->assertCount(1, $errors); + $this->assertContains('test', $errors[0]); + } + + public function testAppendThrowsDuringEnd() + { + $stream = $this->createStream(); + $this->createErrorHandler($errors); + + StreamFilter\append($stream, function ($chunk = null) { + if ($chunk === null) { + throw new \DomainException('end'); + } + return $chunk; + }); + + fclose($stream); + + $this->removeErrorHandler(); + + // We can only assert we're not seeing an exception here… + // * php 5.3-5.6 sees one error here + // * php 7 does not see any error here + // * hhvm sees the same error twice + // + // If you're curious: + // + // var_dump($errors); + // $this->assertCount(1, $errors); + // $this->assertContains('end', $errors[0]); + } + + public function testAppendThrowsShouldTriggerEnd() + { + $stream = $this->createStream(); + $this->createErrorHandler($errors); + + $ended = false; + StreamFilter\append($stream, function ($chunk = null) use (&$ended) { + if ($chunk === null) { + $ended = true; + return ''; + } + throw new \DomainException($chunk); + }); + + $this->assertEquals(false, $ended); + fwrite($stream, 'test'); + $this->assertEquals(true, $ended); + + $this->removeErrorHandler(); + $this->assertCount(1, $errors); + $this->assertContains('test', $errors[0]); + } + + public function testAppendThrowsShouldTriggerEndButIgnoreExceptionDuringEnd() + { + //$this->markTestIncomplete(); + $stream = $this->createStream(); + $this->createErrorHandler($errors); + + StreamFilter\append($stream, function ($chunk = null) { + if ($chunk === null) { + $chunk = 'end'; + //return ''; + } + throw new \DomainException($chunk); + }); + + fwrite($stream, 'test'); + + $this->removeErrorHandler(); + $this->assertCount(1, $errors); + $this->assertContains('test', $errors[0]); + } + + /** + * @expectedException RuntimeException + */ + public function testAppendInvalidStreamIsRuntimeError() + { + if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (does not reject invalid stream)'); + StreamFilter\append(false, function () { }); + } + + /** + * @expectedException RuntimeException + */ + public function testPrependInvalidStreamIsRuntimeError() + { + if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (does not reject invalid stream)'); + StreamFilter\prepend(false, function () { }); + } + + /** + * @expectedException RuntimeException + */ + public function testRemoveInvalidFilterIsRuntimeError() + { + if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (does not reject invalid filters)'); + StreamFilter\remove(false); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidCallbackIsInvalidArgument() + { + $stream = $this->createStream(); + + StreamFilter\append($stream, 'a-b-c'); + } + + private function createStream() + { + return fopen('php://memory', 'r+'); + } + + private function createErrorHandler(&$errors) + { + $errors = array(); + set_error_handler(function ($_, $message) use (&$errors) { + $errors []= $message; + }); + } + + private function removeErrorHandler() + { + restore_error_handler(); + } +} diff --git a/vendor/clue/stream-filter/tests/FunTest.php b/vendor/clue/stream-filter/tests/FunTest.php new file mode 100644 index 00000000..a52668c0 --- /dev/null +++ b/vendor/clue/stream-filter/tests/FunTest.php @@ -0,0 +1,44 @@ +assertEquals('grfg', $rot('test')); + $this->assertEquals('test', $rot($rot('test'))); + $this->assertEquals(null, $rot()); + } + + public function testFunInQuotedPrintable() + { + $encode = Filter\fun('convert.quoted-printable-encode'); + $decode = Filter\fun('convert.quoted-printable-decode'); + + $this->assertEquals('t=C3=A4st', $encode('täst')); + $this->assertEquals('täst', $decode($encode('täst'))); + $this->assertEquals(null, $encode()); + } + + /** + * @expectedException RuntimeException + */ + public function testFunWriteAfterCloseRot13() + { + $rot = Filter\fun('string.rot13'); + + $this->assertEquals(null, $rot()); + $rot('test'); + } + + /** + * @expectedException RuntimeException + */ + public function testFunInvalid() + { + Filter\fun('unknown'); + } +} diff --git a/vendor/clue/stream-filter/tests/FunZlibTest.php b/vendor/clue/stream-filter/tests/FunZlibTest.php new file mode 100644 index 00000000..752c8a2c --- /dev/null +++ b/vendor/clue/stream-filter/tests/FunZlibTest.php @@ -0,0 +1,79 @@ +assertEquals(gzdeflate('hello world'), $data); + } + + public function testFunZlibDeflateEmpty() + { + if (PHP_VERSION >= 7) $this->markTestSkipped('Not supported on PHP7 (empty string does not invoke filter)'); + + $deflate = StreamFilter\fun('zlib.deflate'); + + //$data = gzdeflate(''); + $data = $deflate(); + + $this->assertEquals("\x03\x00", $data); + } + + public function testFunZlibDeflateBig() + { + $deflate = StreamFilter\fun('zlib.deflate'); + + $n = 1000; + $expected = str_repeat('hello', $n); + + $bytes = ''; + for ($i = 0; $i < $n; ++$i) { + $bytes .= $deflate('hello'); + } + $bytes .= $deflate(); + + $this->assertEquals($expected, gzinflate($bytes)); + } + + public function testFunZlibInflateHelloWorld() + { + $inflate = StreamFilter\fun('zlib.inflate'); + + $data = $inflate(gzdeflate('hello world')) . $inflate(); + + $this->assertEquals('hello world', $data); + } + + public function testFunZlibInflateEmpty() + { + $inflate = StreamFilter\fun('zlib.inflate'); + + $data = $inflate("\x03\x00") . $inflate(); + + $this->assertEquals('', $data); + } + + public function testFunZlibInflateBig() + { + if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (final chunk will not be emitted)'); + + $inflate = StreamFilter\fun('zlib.inflate'); + + $expected = str_repeat('hello', 10); + $bytes = gzdeflate($expected); + + $ret = ''; + foreach (str_split($bytes, 2) as $chunk) { + $ret .= $inflate($chunk); + } + $ret .= $inflate(); + + $this->assertEquals($expected, $ret); + } +} diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php index 23cf8ee9..c8fff915 100644 --- a/vendor/composer/autoload_files.php +++ b/vendor/composer/autoload_files.php @@ -7,8 +7,10 @@ return array( '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', - 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', + 'ddc0a4d7e61c0286f0f8593b1903e894' => $vendorDir . '/clue/stream-filter/src/functions.php', + 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', + '8cff32064859f4559445b89279f3199c' => $vendorDir . '/php-http/message/src/filters.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', '841780ea2e1d6545ea3a253239d59c05' => $vendorDir . '/qiniu/php-sdk/src/Qiniu/functions.php', '1cfd2761b63b0a29ed23657ea394cb2d' => $vendorDir . '/topthink/think-captcha/src/helper.php', diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index e7b950f6..fe9558c4 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -11,12 +11,23 @@ 'think\\' => array($vendorDir . '/topthink/think-image/src'), 'app\\' => array($baseDir . '/application'), 'Upyun\\' => array($vendorDir . '/upyun/sdk/src/Upyun'), + 'Symfony\\Component\\OptionsResolver\\' => array($vendorDir . '/symfony/options-resolver'), 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), 'Qiniu\\' => array($vendorDir . '/qiniu/php-sdk/src/Qiniu'), 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), + 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), 'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'), 'OSS\\' => array($vendorDir . '/aliyuncs/oss-sdk-php/src/OSS'), + 'Http\\Promise\\' => array($vendorDir . '/php-http/promise/src'), + 'Http\\Message\\' => array($vendorDir . '/php-http/message/src', $vendorDir . '/php-http/message-factory/src'), + 'Http\\Discovery\\' => array($vendorDir . '/php-http/discovery/src'), + 'Http\\Client\\Common\\Plugin\\' => array($vendorDir . '/php-http/cache-plugin/src'), + 'Http\\Client\\Common\\' => array($vendorDir . '/php-http/client-common/src'), + 'Http\\Client\\' => array($vendorDir . '/php-http/httplug/src'), + 'Http\\Adapter\\Guzzle6\\' => array($vendorDir . '/php-http/guzzle6-adapter/src'), 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), + 'Github\\' => array($vendorDir . '/knplabs/github-api/lib/Github'), + 'Clue\\StreamFilter\\' => array($vendorDir . '/clue/stream-filter/src'), ); diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 4e4ef451..d13af326 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -8,8 +8,10 @@ class ComposerStaticInit04f78adc0d26d025ab398ddde054e232 { public static $files = array ( '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', - 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', + 'ddc0a4d7e61c0286f0f8593b1903e894' => __DIR__ . '/..' . '/clue/stream-filter/src/functions.php', + 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', + '8cff32064859f4559445b89279f3199c' => __DIR__ . '/..' . '/php-http/message/src/filters.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', '841780ea2e1d6545ea3a253239d59c05' => __DIR__ . '/..' . '/qiniu/php-sdk/src/Qiniu/functions.php', '1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php', @@ -32,6 +34,7 @@ class ComposerStaticInit04f78adc0d26d025ab398ddde054e232 ), 'S' => array ( + 'Symfony\\Component\\OptionsResolver\\' => 34, 'Symfony\\Component\\EventDispatcher\\' => 34, ), 'Q' => @@ -41,17 +44,33 @@ class ComposerStaticInit04f78adc0d26d025ab398ddde054e232 'P' => array ( 'Psr\\Http\\Message\\' => 17, + 'Psr\\Cache\\' => 10, 'PHPMailer\\PHPMailer\\' => 20, ), 'O' => array ( 'OSS\\' => 4, ), + 'H' => + array ( + 'Http\\Promise\\' => 13, + 'Http\\Message\\' => 13, + 'Http\\Discovery\\' => 15, + 'Http\\Client\\Common\\Plugin\\' => 26, + 'Http\\Client\\Common\\' => 19, + 'Http\\Client\\' => 12, + 'Http\\Adapter\\Guzzle6\\' => 21, + ), 'G' => array ( 'GuzzleHttp\\Psr7\\' => 16, 'GuzzleHttp\\Promise\\' => 19, 'GuzzleHttp\\' => 11, + 'Github\\' => 7, + ), + 'C' => + array ( + 'Clue\\StreamFilter\\' => 18, ), ); @@ -76,6 +95,10 @@ class ComposerStaticInit04f78adc0d26d025ab398ddde054e232 array ( 0 => __DIR__ . '/..' . '/upyun/sdk/src/Upyun', ), + 'Symfony\\Component\\OptionsResolver\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/options-resolver', + ), 'Symfony\\Component\\EventDispatcher\\' => array ( 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', @@ -88,6 +111,10 @@ class ComposerStaticInit04f78adc0d26d025ab398ddde054e232 array ( 0 => __DIR__ . '/..' . '/psr/http-message/src', ), + 'Psr\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/cache/src', + ), 'PHPMailer\\PHPMailer\\' => array ( 0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src', @@ -96,6 +123,35 @@ class ComposerStaticInit04f78adc0d26d025ab398ddde054e232 array ( 0 => __DIR__ . '/..' . '/aliyuncs/oss-sdk-php/src/OSS', ), + 'Http\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/php-http/promise/src', + ), + 'Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/php-http/message/src', + 1 => __DIR__ . '/..' . '/php-http/message-factory/src', + ), + 'Http\\Discovery\\' => + array ( + 0 => __DIR__ . '/..' . '/php-http/discovery/src', + ), + 'Http\\Client\\Common\\Plugin\\' => + array ( + 0 => __DIR__ . '/..' . '/php-http/cache-plugin/src', + ), + 'Http\\Client\\Common\\' => + array ( + 0 => __DIR__ . '/..' . '/php-http/client-common/src', + ), + 'Http\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/php-http/httplug/src', + ), + 'Http\\Adapter\\Guzzle6\\' => + array ( + 0 => __DIR__ . '/..' . '/php-http/guzzle6-adapter/src', + ), 'GuzzleHttp\\Psr7\\' => array ( 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', @@ -108,6 +164,14 @@ class ComposerStaticInit04f78adc0d26d025ab398ddde054e232 array ( 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', ), + 'Github\\' => + array ( + 0 => __DIR__ . '/..' . '/knplabs/github-api/lib/Github', + ), + 'Clue\\StreamFilter\\' => + array ( + 0 => __DIR__ . '/..' . '/clue/stream-filter/src', + ), ); public static $prefixesPsr0 = array ( diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index cad0b7ec..f864ad9f 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -42,6 +42,60 @@ "description": "Aliyun OSS SDK for PHP", "homepage": "http://www.aliyun.com/product/oss/" }, + { + "name": "clue/stream-filter", + "version": "v1.4.0", + "version_normalized": "1.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/clue/php-stream-filter.git", + "reference": "d80fdee9b3a7e0d16fc330a22f41f3ad0eeb09d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/php-stream-filter/zipball/d80fdee9b3a7e0d16fc330a22f41f3ad0eeb09d0", + "reference": "d80fdee9b3a7e0d16fc330a22f41f3ad0eeb09d0", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^5.0 || ^4.8" + }, + "time": "2017-08-18T09:54:01+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Clue\\StreamFilter\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "description": "A simple and modern approach to stream filtering in PHP", + "homepage": "https://github.com/clue/php-stream-filter", + "keywords": [ + "bucket brigade", + "callback", + "filter", + "php_user_filter", + "stream", + "stream_filter_append", + "stream_filter_register" + ] + }, { "name": "guzzle/guzzle", "version": "v3.9.3", @@ -329,6 +383,561 @@ "url" ] }, + { + "name": "knplabs/github-api", + "version": "2.10.1", + "version_normalized": "2.10.1.0", + "source": { + "type": "git", + "url": "https://github.com/KnpLabs/php-github-api.git", + "reference": "493423ae7ad1fa9075924cdfb98537828b9e80b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/493423ae7ad1fa9075924cdfb98537828b9e80b5", + "reference": "493423ae7ad1fa9075924cdfb98537828b9e80b5", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "php-http/cache-plugin": "^1.4", + "php-http/client-common": "^1.6", + "php-http/client-implementation": "^1.0", + "php-http/discovery": "^1.0", + "php-http/httplug": "^1.1", + "psr/cache": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "cache/array-adapter": "^0.4", + "guzzlehttp/psr7": "^1.2", + "php-http/guzzle6-adapter": "^1.0", + "php-http/mock-client": "^1.0", + "phpunit/phpunit": "^5.5 || ^6.0" + }, + "time": "2018-09-05T19:12:14+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.10.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Github\\": "lib/Github/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thibault Duplessis", + "email": "thibault.duplessis@gmail.com", + "homepage": "http://ornicar.github.com" + }, + { + "name": "KnpLabs Team", + "homepage": "http://knplabs.com" + } + ], + "description": "GitHub API v3 client", + "homepage": "https://github.com/KnpLabs/php-github-api", + "keywords": [ + "api", + "gh", + "gist", + "github" + ] + }, + { + "name": "php-http/cache-plugin", + "version": "v1.5.0", + "version_normalized": "1.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/cache-plugin.git", + "reference": "c573ac6ea9b4e33fad567f875b844229d18000b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/cache-plugin/zipball/c573ac6ea9b4e33fad567f875b844229d18000b9", + "reference": "c573ac6ea9b4e33fad567f875b844229d18000b9", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0", + "php-http/client-common": "^1.1", + "php-http/message-factory": "^1.0", + "psr/cache": "^1.0", + "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.5" + }, + "time": "2017-11-29T20:45:41+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Client\\Common\\Plugin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "PSR-6 Cache plugin for HTTPlug", + "homepage": "http://httplug.io", + "keywords": [ + "cache", + "http", + "httplug", + "plugin" + ] + }, + { + "name": "php-http/client-common", + "version": "1.9.0", + "version_normalized": "1.9.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/client-common.git", + "reference": "9c21b6058caafdf2fcc99a0cabdf31b3ecb33961" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/client-common/zipball/9c21b6058caafdf2fcc99a0cabdf31b3ecb33961", + "reference": "9c21b6058caafdf2fcc99a0cabdf31b3ecb33961", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0", + "php-http/httplug": "^1.1", + "php-http/message": "^1.6", + "php-http/message-factory": "^1.0", + "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0" + }, + "require-dev": { + "guzzlehttp/psr7": "^1.4", + "phpspec/phpspec": "^2.5 || ^3.4 || ^4.2" + }, + "suggest": { + "php-http/cache-plugin": "PSR-6 Cache plugin", + "php-http/logger-plugin": "PSR-3 Logger plugin", + "php-http/stopwatch-plugin": "Symfony Stopwatch plugin" + }, + "time": "2019-01-03T10:59:55+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Client\\Common\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Common HTTP Client implementations and tools for HTTPlug", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "common", + "http", + "httplug" + ] + }, + { + "name": "php-http/discovery", + "version": "1.5.2", + "version_normalized": "1.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "ffef11d54171336d841a34816a35bc035fb8cef0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/ffef11d54171336d841a34816a35bc035fb8cef0", + "reference": "ffef11d54171336d841a34816a35bc035fb8cef0", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0" + }, + "conflict": { + "nyholm/psr7": "<1.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^2.0.2", + "php-http/httplug": "^1.0|^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^2.4", + "puli/composer-plugin": "1.0.0-beta10" + }, + "suggest": { + "php-http/message": "Allow to use Guzzle, Diactoros or Slim Framework factories", + "puli/composer-plugin": "Sets up Puli which is recommended for Discovery to work. Check http://docs.php-http.org/en/latest/discovery.html for more details." + }, + "time": "2018-12-31T07:31:26+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds installed HTTPlug implementations and PSR-7 message factories", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr7" + ] + }, + { + "name": "php-http/guzzle6-adapter", + "version": "v1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/guzzle6-adapter.git", + "reference": "a56941f9dc6110409cfcddc91546ee97039277ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/guzzle6-adapter/zipball/a56941f9dc6110409cfcddc91546ee97039277ab", + "reference": "a56941f9dc6110409cfcddc91546ee97039277ab", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "php": ">=5.5.0", + "php-http/httplug": "^1.0" + }, + "provide": { + "php-http/async-client-implementation": "1.0", + "php-http/client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "php-http/adapter-integration-tests": "^0.4" + }, + "time": "2016-05-10T06:13:32+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Adapter\\Guzzle6\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + } + ], + "description": "Guzzle 6 HTTP Adapter", + "homepage": "http://httplug.io", + "keywords": [ + "Guzzle", + "http" + ] + }, + { + "name": "php-http/httplug", + "version": "v1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/httplug.git", + "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/httplug/zipball/1c6381726c18579c4ca2ef1ec1498fdae8bdf018", + "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "php-http/promise": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "time": "2016-08-31T08:30:17+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTPlug, the HTTP client abstraction for PHP", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "http" + ] + }, + { + "name": "php-http/message", + "version": "1.7.2", + "version_normalized": "1.7.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/message.git", + "reference": "b159ffe570dffd335e22ef0b91a946eacb182fa1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message/zipball/b159ffe570dffd335e22ef0b91a946eacb182fa1", + "reference": "b159ffe570dffd335e22ef0b91a946eacb182fa1", + "shasum": "" + }, + "require": { + "clue/stream-filter": "^1.4", + "php": "^5.4 || ^7.0", + "php-http/message-factory": "^1.0.2", + "psr/http-message": "^1.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0" + }, + "require-dev": { + "akeneo/phpspec-skip-example-extension": "^1.0", + "coduo/phpspec-data-provider-extension": "^1.0", + "ext-zlib": "*", + "guzzlehttp/psr7": "^1.0", + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4", + "slim/slim": "^3.0", + "zendframework/zend-diactoros": "^1.0" + }, + "suggest": { + "ext-zlib": "Used with compressor/decompressor streams", + "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories", + "slim/slim": "Used with Slim Framework PSR-7 implementation", + "zendframework/zend-diactoros": "Used with Diactoros Factories" + }, + "time": "2018-11-01T09:32:41+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + }, + "files": [ + "src/filters.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTP Message related tools", + "homepage": "http://php-http.org", + "keywords": [ + "http", + "message", + "psr-7" + ] + }, + { + "name": "php-http/message-factory", + "version": "v1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0" + }, + "time": "2015-12-19T14:08:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "stream", + "uri" + ] + }, + { + "name": "php-http/promise", + "version": "v1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/promise.git", + "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/promise/zipball/dc494cdc9d7160b9a09bd5573272195242ce7980", + "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980", + "shasum": "" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "time": "2016-01-26T13:27:02+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + } + ], + "description": "Promise used for asynchronous HTTP requests", + "homepage": "http://httplug.io", + "keywords": [ + "promise" + ] + }, { "name": "phpmailer/phpmailer", "version": "v6.0.6", @@ -397,6 +1006,54 @@ ], "description": "PHPMailer is a full-featured email creation and transfer class for PHP" }, + { + "name": "psr/cache", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T20:24:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ] + }, { "name": "psr/http-message", "version": "1.0.1", @@ -654,19 +1311,75 @@ "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com" }, + { + "name": "symfony/options-resolver", + "version": "v4.2.2", + "version_normalized": "4.2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "fbcb106aeee72f3450298bf73324d2cc00d083d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/fbcb106aeee72f3450298bf73324d2cc00d083d1", + "reference": "fbcb106aeee72f3450298bf73324d2cc00d083d1", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "time": "2019-01-03T09:07:35+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ] + }, { "name": "topthink/framework", - "version": "v5.1.31", - "version_normalized": "5.1.31.0", + "version": "v5.1.32", + "version_normalized": "5.1.32.0", "source": { "type": "git", "url": "https://github.com/top-think/framework.git", - "reference": "93339b1a4df5a73e0143db0847a4c5e0b2e46fb0" + "reference": "88a2ab625b35e047896718db320e08375cf021da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/top-think/framework/zipball/93339b1a4df5a73e0143db0847a4c5e0b2e46fb0", - "reference": "93339b1a4df5a73e0143db0847a4c5e0b2e46fb0", + "url": "https://api.github.com/repos/top-think/framework/zipball/88a2ab625b35e047896718db320e08375cf021da", + "reference": "88a2ab625b35e047896718db320e08375cf021da", "shasum": "" }, "require": { @@ -682,7 +1395,7 @@ "sebastian/phpcpd": "2.*", "squizlabs/php_codesniffer": "2.*" }, - "time": "2018-12-09T12:41:21+00:00", + "time": "2018-12-23T13:42:11+00:00", "type": "think-framework", "installation-source": "dist", "notification-url": "https://packagist.org/downloads/", diff --git a/vendor/knplabs/github-api/.editorconfig b/vendor/knplabs/github-api/.editorconfig new file mode 100644 index 00000000..ed1247ac --- /dev/null +++ b/vendor/knplabs/github-api/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/vendor/knplabs/github-api/.php_cs b/vendor/knplabs/github-api/.php_cs new file mode 100644 index 00000000..83782b0f --- /dev/null +++ b/vendor/knplabs/github-api/.php_cs @@ -0,0 +1,14 @@ +in(__DIR__); + +$config = PhpCsFixer\Config::create() + ->setRiskyAllowed(true) + ->setRules([ + + ]) + ->setFinder($finder) +; + +return $config; diff --git a/vendor/knplabs/github-api/.styleci.yml b/vendor/knplabs/github-api/.styleci.yml new file mode 100644 index 00000000..504456e7 --- /dev/null +++ b/vendor/knplabs/github-api/.styleci.yml @@ -0,0 +1,5 @@ +preset: recommended + +disabled: + - align_double_arrow + - no_multiline_whitespace_before_semicolons diff --git a/vendor/knplabs/github-api/CHANGELOG.md b/vendor/knplabs/github-api/CHANGELOG.md new file mode 100644 index 00000000..d370a032 --- /dev/null +++ b/vendor/knplabs/github-api/CHANGELOG.md @@ -0,0 +1,249 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## 2.10.1 + +### Fixed + +- Convert the assignee parameter to array to avoid getting a 422 error on github (#738) +- Fix GraphQL test warnings when they do not assert anything (#735) + +### Changed + +- Check for BC breaks during the travis build (#734) + +## 2.10.0 + +### Added + +- Support for "before" parameter on Notification API (#724) + +### Changed + +- Allow unspecified `event` when creating review (#723) + +### Fixed + +- Adjust: installationn access token endpoint (#731) +- Fixed "get single label" example and add correct example for getting issue's labels (#732) +- Add comment about `Key` constructor argument (#722) + +## 2.9.0 + +### Added + +- API endpoint `Github\Api\Repo::transfer()` +- API endpoint `Github\Api\Notification::markThreadRead()` +- API endpoint `Github\Api\Search::topics()` + +### Fixed + +- Make sure to always reset the "per page" in `Github\ResultPager::fetchAll()`. + +## 2.8.0 + +### Added + +- Allow our HTTP plugins to show up in the Symfony web profiler page. (#687) +- Repository documentation to current user (#671) +- Add collaborator permission call (#678) +- Add missing parameters for User/CurrentUser Repositories (#684) +- Pimp the readme with badge poser (#686) + +### Fixed + +- Typo in assignee documentation +- Missing use statement in security example +- Fixed phpdoc typo (#695) +- Replace use of deprecated api to the correct one in the security docs (#697) + +### Changed + +- Updated requirements in readme (#689) + +## 2.7.0 + +### Added + +- Phpunit 6 compatibility +- `Github\Api\AbstractApi::setPage()` to allow you to set the page on all endpoints. +- Support for query parameters and request headers on `Github\Api\User::following` and `Github\Api\User::followers` +- API endpoint `Github\Api\CurrentUser\Emails::allPublic()` +- API endpoint `Github\Api\Search::commits()` +- API endpoint `Github\Api\Miscellaneous\CodeOfConduct` +- API endpoint `Github\Api\Repo::topics()` +- API endpoint `Github\Api\Repo::replaceTopics()` + +### Fixed + +- Fixed bug in `PathPrepend` plugin where "api/vX" could be duplicated. + +### Changed + +- Improved documentation and doc blocks + +### Removed + +- Dropped support for php 5.5 + +### Deprecated + +The following endpoints were deprecated by Github and are also deprecated in the client: + +- `Github\Api\Repo::find()` +- `Github\Api\User::find()` +- `Github\Api\Issue::find()` + +## 2.6.0 + +### Added + +- Support for graphql api [variables](https://developer.github.com/v4/guides/forming-calls/#working-with-variables) (#612) +- Added missing branch protection methods (#616) +- Helper function `fromFile ` to get GraphQL queries from a file (#628) +- Extra parameter `params` to collaborators api calls (#623) +- Documentation for GitData API (#613) + +### Fixed +- Remove `body` as a required parameter when creating an issue (#624) +- Minor fixes in example code (#617) + +## 2.5.0 + +### Added + +- Stable support for graphql api (V4) (#593) +- Stable support for apps (previously integrations) (#592) +- `Repo::events()` + +### Fixed + +- Incorrect link in repository search docs (#594) +- Added the required parameter `$message` on `Review::dismiss`. + +## 2.4.0 + +### Added + +- `Integrations::configure` to allow accessing early access program endpoints. +- Add support for pagination and parameters in the pull request comments +- Add the ability to fetch user installations (`CurrentUser::installations`) +- Allow getting repo info by id (`Repo::showById`) +- Allow fetching repositories for a specific installation and user (`CurrentUser::repositoriesByInstallation`) + +### Changed + +- `PullRequest\Review` and `PullRequest\ReviewRequest` is now part of the official API. No need to call `configure`. + +## 2.3.0 + +### Fixed + +- Issue where we serve the wrong cached response. We vary on authorization header now. + +### Added + +- `PullRequest::status` +- Throw InvalidArgumentException on `PullRequest::merge` when wrong merge method is used. +- Added `Protection::configure` + +### Changed + +- First argument to `Integrations::listRepositories()` is now optional. +- Moved tests from "functional" to "integration" + +## 2.2.0 + +### Added + +- API support for Pull Request Review Requests. +- API support for Traffic. +- API support for issue Assignees. +- API support for Miscellaneous Gitignore and Emojis. +- Added endpoints for issue lock, unlock and issue label show. +- Added more parameters to `User::starred`. +- Fluid interface by allowing `configure()` to return `$this`. +- `configure()` support for issues API. + +### Fixed + +- Cache issue where some requests are not cached +- Issue with `User::all()` creates a query with double question marks. + +## 2.1.0 + +### Added + +- Add support for retrieving a single notification info using his ID +- Add a function to get user organizations +- Added GraphQL support +- Add page variable to organization repo list (Organization::repositories()) +- Add support for pull request review. +- Add support for adding branch protection. + +### Fixed + +- Bug with double slashes when using enterprise URL. +- Bug when headers not being passed to request (#529) + +## 2.0.0 + +### Added + +- Support for JWT authentication +- API for Organization\Members +- API for Integrations +- API for Repo\Cards +- API for Repo\Columns +- API for Repo\Projects +- API for User\MyRepositories +- Methods in Repo API for frequency and participation + +### Changed + +- `ApiLimitExceedException::__construct` has a new second parameter for the remaining API calls. +- First parameter of `Github\Client` has changed type from `\Http\Client\HttpClient` to +`Github\HttpClient\Builder`. A factory class was also added. To upgrade you need to change: + +```php +// Old way does not work: +$github = new Github\Client($httpClient); + +// New way will work: +$github = new Github\Client(new Github\HttpClient\Builder($httpClient)); +$github = Github\Client::createWithHttpClient($httpClient); +``` +- Renamed the currentuser `DeployKeys` api class to `PublicKeys` to reflect to github api name. + +## 2.0.0-rc4 + +### Added + +- HTTPlug to decouple from Guzzle +- `Github\Client::getLastResponse` was added +- Support for PSR-6 cache +- `Github\Client::addPlugin` and `Github\Client::removePlugin` +- `Github\Client::getApiVersion` +- `Github\Client::removeCache` + +### Changed + +- Uses of `Github\HttpClient\HttpClientInterface` is replaced by `Http\Client\HttpClient` ie the constructor of `Github\Client`. +- We use PSR-7's representation of HTTP message instead of `Guzzle\Http\Message\Response` and `Guzzle\Http\Message\Request`. +- `Github\Client::addHeaders` was added instead of `Github\Client::setHeaders` +- Signature of `Github\Client::useCache` has changed. First argument must be a `CacheItemPoolInterface` +- We use PSR-4 instead of PSR-0 + +### Removed + +- Support for PHP 5.3 and 5.4 +- `Github/HttpClient/HttpClientInterface` was removed +- `Github/HttpClient/HttpClient` was removed +- All classes in `Github/HttpClient/HttpClient/Listener/*` were removed +- `Github/HttpClient/CachedHttpClient` was removed +- All classes in `Github/HttpClient/Cache/*` were removed + +## 1.7.1 + +No change log before this version diff --git a/vendor/knplabs/github-api/LICENSE b/vendor/knplabs/github-api/LICENSE new file mode 100644 index 00000000..0fd8dd8e --- /dev/null +++ b/vendor/knplabs/github-api/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2012 KnpLabs +Copyright (c) 2010 Thibault Duplessis + +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. diff --git a/vendor/knplabs/github-api/README.md b/vendor/knplabs/github-api/README.md new file mode 100644 index 00000000..86bcb041 --- /dev/null +++ b/vendor/knplabs/github-api/README.md @@ -0,0 +1,111 @@ +# PHP GitHub API + +[![Build Status](https://travis-ci.org/KnpLabs/php-github-api.svg?branch=master)](https://travis-ci.org/KnpLabs/php-github-api) +[![StyleCI](https://styleci.io/repos/3948501/shield?style=flat)](https://styleci.io/repos/3948501) +[![Latest Stable Version](https://poser.pugx.org/knplabs/github-api/v/stable)](https://packagist.org/packages/knplabs/github-api) +[![Total Downloads](https://poser.pugx.org/knplabs/github-api/downloads)](https://packagist.org/packages/knplabs/github-api) +[![Latest Unstable Version](https://poser.pugx.org/knplabs/github-api/v/unstable)](https://packagist.org/packages/knplabs/github-api) +[![Monthly Downloads](https://poser.pugx.org/knplabs/github-api/d/monthly)](https://packagist.org/packages/knplabs/github-api) +[![Daily Downloads](https://poser.pugx.org/knplabs/github-api/d/daily)](https://packagist.org/packages/knplabs/github-api) + +A simple Object Oriented wrapper for GitHub API, written with PHP5. + +Uses [GitHub API v3](http://developer.github.com/v3/) & supports [GitHub API v4](http://developer.github.com/v4). The object API (v3) is very similar to the RESTful API. + +## Features + +* Light and fast thanks to lazy loading of API classes +* Extensively tested and documented + +## Requirements + +* PHP >= 5.6 +* A [HTTP client](https://packagist.org/providers/php-http/client-implementation) +* A [PSR-7 implementation](https://packagist.org/providers/psr/http-message-implementation) +* (optional) PHPUnit to run tests. + +## Install + +Via Composer: + +```bash +$ composer require knplabs/github-api php-http/guzzle6-adapter +``` + +Why `php-http/guzzle6-adapter`? We are decoupled from any HTTP messaging client with help by [HTTPlug](http://httplug.io/). Read about clients in our [docs](doc/customize.md). + + +## Using Laravel? + +[Laravel GitHub](https://github.com/GrahamCampbell/Laravel-GitHub) by [Graham Campbell](https://github.com/GrahamCampbell) might interest you. + +## Basic usage of `php-github-api` client + +```php +api('user')->repositories('ornicar'); +``` + +From `$client` object, you can access to all GitHub. + +## Cache usage + +This example uses the PSR6 cache pool [redis-adapter](https://github.com/php-cache/redis-adapter). See http://www.php-cache.com/ for alternatives. + +```php +connect('127.0.0.1', 6379); +// Create a PSR6 cache pool +$pool = new RedisCachePool($client); + +$client = new \Github\Client(); +$client->addCache($pool); + +// Do some request + +// Stop using cache +$client->removeCache(); +``` + +Using cache, the client will get cached responses if resources haven't changed since last time, +**without** reaching the `X-Rate-Limit` [imposed by github](http://developer.github.com/v3/#rate-limiting). + + +## Documentation + +See the [`doc` directory](doc/) for more detailed documentation. + +## License + +`php-github-api` is licensed under the MIT License - see the LICENSE file for details + +## Credits + +### Sponsored by + +[![KnpLabs Team](http://knplabs.com/front/images/knp-labs-logo.png)](http://knplabs.com) + +### Contributors + +- Thanks to [Thibault Duplessis aka. ornicar](http://github.com/ornicar) for his first version of this library. +- Thanks to [Joseph Bielawski aka. stloyd](http://github.com/stloyd) for his contributions and support. +- Thanks to [noloh](http://github.com/noloh) for his contribution on the Object API. +- Thanks to [bshaffer](http://github.com/bshaffer) for his contribution on the Repo API. +- Thanks to [Rolf van de Krol](http://github.com/rolfvandekrol) for his countless contributions. +- Thanks to [Nicolas Pastorino](http://github.com/jeanvoye) for his contribution on the Pull Request API. +- Thanks to [Edoardo Rivello](http://github.com/erivello) for his contribution on the Gists API. +- Thanks to [Miguel Piedrafita](https://github.com/m1guelpf) for his contribution to the v4 & Apps API. + +Thanks to GitHub for the high quality API and documentation. diff --git a/vendor/knplabs/github-api/composer.json b/vendor/knplabs/github-api/composer.json new file mode 100644 index 00000000..d3641f47 --- /dev/null +++ b/vendor/knplabs/github-api/composer.json @@ -0,0 +1,49 @@ +{ + "name": "knplabs/github-api", + "type": "library", + "description": "GitHub API v3 client", + "homepage": "https://github.com/KnpLabs/php-github-api", + "keywords": ["github", "gh", "api", "gist"], + "license": "MIT", + "authors": [ + { + "name": "KnpLabs Team", + "homepage": "http://knplabs.com" + }, + { + "name": "Thibault Duplessis", + "email": "thibault.duplessis@gmail.com", + "homepage": "http://ornicar.github.com" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/http-message": "^1.0", + "psr/cache": "^1.0", + "php-http/httplug": "^1.1", + "php-http/discovery": "^1.0", + "php-http/client-implementation": "^1.0", + "php-http/client-common": "^1.6", + "php-http/cache-plugin": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^5.5 || ^6.0", + "php-http/guzzle6-adapter": "^1.0", + "php-http/mock-client": "^1.0", + "guzzlehttp/psr7": "^1.2", + "cache/array-adapter": "^0.4" + }, + "autoload": { + "psr-4": { "Github\\": "lib/Github/" } + }, + "autoload-dev": { + "psr-4": { "Github\\Tests\\": "test/Github/Tests/"} + }, + "minimum-stability": "dev", + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-master": "2.10.x-dev" + } + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/AbstractApi.php b/vendor/knplabs/github-api/lib/Github/Api/AbstractApi.php new file mode 100644 index 00000000..135ac2b2 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/AbstractApi.php @@ -0,0 +1,243 @@ + + */ +abstract class AbstractApi implements ApiInterface +{ + /** + * The client. + * + * @var Client + */ + protected $client; + + /** + * The requested page (GitHub pagination). + * + * @var null|int + */ + private $page; + + /** + * Number of items per page (GitHub pagination). + * + * @var null|int + */ + protected $perPage; + + /** + * @param Client $client + */ + public function __construct(Client $client) + { + $this->client = $client; + } + + public function configure() + { + } + + /** + * @return null|int + */ + public function getPage() + { + return $this->page; + } + + /** + * @param null|int $page + */ + public function setPage($page) + { + $this->page = (null === $page ? $page : (int) $page); + + return $this; + } + + /** + * @return null|int + */ + public function getPerPage() + { + return $this->perPage; + } + + /** + * @param null|int $perPage + */ + public function setPerPage($perPage) + { + $this->perPage = (null === $perPage ? $perPage : (int) $perPage); + + return $this; + } + + /** + * Send a GET request with query parameters. + * + * @param string $path Request path. + * @param array $parameters GET parameters. + * @param array $requestHeaders Request Headers. + * + * @return array|string + */ + protected function get($path, array $parameters = [], array $requestHeaders = []) + { + if (null !== $this->page && !isset($parameters['page'])) { + $parameters['page'] = $this->page; + } + if (null !== $this->perPage && !isset($parameters['per_page'])) { + $parameters['per_page'] = $this->perPage; + } + if (array_key_exists('ref', $parameters) && is_null($parameters['ref'])) { + unset($parameters['ref']); + } + + if (count($parameters) > 0) { + $path .= '?'.http_build_query($parameters); + } + + $response = $this->client->getHttpClient()->get($path, $requestHeaders); + + return ResponseMediator::getContent($response); + } + + /** + * Send a HEAD request with query parameters. + * + * @param string $path Request path. + * @param array $parameters HEAD parameters. + * @param array $requestHeaders Request headers. + * + * @return \Psr\Http\Message\ResponseInterface + */ + protected function head($path, array $parameters = [], array $requestHeaders = []) + { + if (array_key_exists('ref', $parameters) && is_null($parameters['ref'])) { + unset($parameters['ref']); + } + + $response = $this->client->getHttpClient()->head($path.'?'.http_build_query($parameters), $requestHeaders); + + return $response; + } + + /** + * Send a POST request with JSON-encoded parameters. + * + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. + * + * @return array|string + */ + protected function post($path, array $parameters = [], array $requestHeaders = []) + { + return $this->postRaw( + $path, + $this->createJsonBody($parameters), + $requestHeaders + ); + } + + /** + * Send a POST request with raw data. + * + * @param string $path Request path. + * @param string $body Request body. + * @param array $requestHeaders Request headers. + * + * @return array|string + */ + protected function postRaw($path, $body, array $requestHeaders = []) + { + $response = $this->client->getHttpClient()->post( + $path, + $requestHeaders, + $body + ); + + return ResponseMediator::getContent($response); + } + + /** + * Send a PATCH request with JSON-encoded parameters. + * + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. + * + * @return array|string + */ + protected function patch($path, array $parameters = [], array $requestHeaders = []) + { + $response = $this->client->getHttpClient()->patch( + $path, + $requestHeaders, + $this->createJsonBody($parameters) + ); + + return ResponseMediator::getContent($response); + } + + /** + * Send a PUT request with JSON-encoded parameters. + * + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. + * + * @return array|string + */ + protected function put($path, array $parameters = [], array $requestHeaders = []) + { + $response = $this->client->getHttpClient()->put( + $path, + $requestHeaders, + $this->createJsonBody($parameters) + ); + + return ResponseMediator::getContent($response); + } + + /** + * Send a DELETE request with JSON-encoded parameters. + * + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. + * + * @return array|string + */ + protected function delete($path, array $parameters = [], array $requestHeaders = []) + { + $response = $this->client->getHttpClient()->delete( + $path, + $requestHeaders, + $this->createJsonBody($parameters) + ); + + return ResponseMediator::getContent($response); + } + + /** + * Create a JSON encoded version of an array of parameters. + * + * @param array $parameters Request parameters + * + * @return null|string + */ + protected function createJsonBody(array $parameters) + { + return (count($parameters) === 0) ? null : json_encode($parameters, empty($parameters) ? JSON_FORCE_OBJECT : 0); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/AcceptHeaderTrait.php b/vendor/knplabs/github-api/lib/Github/Api/AcceptHeaderTrait.php new file mode 100644 index 00000000..4a7e7a46 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/AcceptHeaderTrait.php @@ -0,0 +1,63 @@ + + */ +trait AcceptHeaderTrait +{ + protected $acceptHeaderValue = null; + + protected function get($path, array $parameters = [], array $requestHeaders = []) + { + return parent::get($path, $parameters, $this->mergeHeaders($requestHeaders)); + } + + protected function head($path, array $parameters = [], array $requestHeaders = []) + { + return parent::head($path, $parameters, $this->mergeHeaders($requestHeaders)); + } + + protected function post($path, array $parameters = [], array $requestHeaders = []) + { + return parent::post($path, $parameters, $this->mergeHeaders($requestHeaders)); + } + + protected function postRaw($path, $body, array $requestHeaders = []) + { + return parent::postRaw($path, $body, $this->mergeHeaders($requestHeaders)); + } + + protected function patch($path, array $parameters = [], array $requestHeaders = []) + { + return parent::patch($path, $parameters, $this->mergeHeaders($requestHeaders)); + } + + protected function put($path, array $parameters = [], array $requestHeaders = []) + { + return parent::put($path, $parameters, $this->mergeHeaders($requestHeaders)); + } + + protected function delete($path, array $parameters = [], array $requestHeaders = []) + { + return parent::delete($path, $parameters, $this->mergeHeaders($requestHeaders)); + } + + /** + * Append a new accept header on all requests. + * + * @return array + */ + private function mergeHeaders(array $headers = []) + { + $default = []; + if ($this->acceptHeaderValue) { + $default = ['Accept' => $this->acceptHeaderValue]; + } + + return array_merge($default, $headers); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/ApiInterface.php b/vendor/knplabs/github-api/lib/Github/Api/ApiInterface.php new file mode 100644 index 00000000..49d5167c --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/ApiInterface.php @@ -0,0 +1,15 @@ + + */ +interface ApiInterface +{ + public function getPerPage(); + + public function setPerPage($perPage); +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Apps.php b/vendor/knplabs/github-api/lib/Github/Api/Apps.php new file mode 100644 index 00000000..1467d2aa --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Apps.php @@ -0,0 +1,93 @@ + + */ +class Apps extends AbstractApi +{ + /** + * Create an access token for an installation. + * + * @param int $installationId An integration installation id + * @param int $userId An optional user id on behalf of whom the + * token will be requested + * + * @link https://developer.github.com/v3/apps/#create-a-new-installation-token + * + * @return array token and token metadata + */ + public function createInstallationToken($installationId, $userId = null) + { + $parameters = []; + if ($userId) { + $parameters['user_id'] = $userId; + } + + return $this->post('/app/installations/'.rawurlencode($installationId).'/access_tokens', $parameters); + } + + /** + * Find all installations for the authenticated application. + * + * @link https://developer.github.com/v3/apps/#find-installations + * + * @return array + */ + public function findInstallations() + { + return $this->get('/app/installations'); + } + + /** + * List repositories that are accessible to the authenticated installation. + * + * @link https://developer.github.com/v3/apps/installations/#list-repositories + * + * @param int $userId + * + * @return array + */ + public function listRepositories($userId = null) + { + $parameters = []; + if ($userId) { + $parameters['user_id'] = $userId; + } + + return $this->get('/installation/repositories', $parameters); + } + + /** + * Add a single repository to an installation. + * + * @link https://developer.github.com/v3/apps/installations/#add-repository-to-installation + * + * @param int $installationId + * @param int $repositoryId + * + * @return array + */ + public function addRepository($installationId, $repositoryId) + { + return $this->put('/installations/'.rawurlencode($installationId).'/repositories/'.rawurlencode($repositoryId)); + } + + /** + * Remove a single repository from an installation. + * + * @link https://developer.github.com/v3/apps/installations/#remove-repository-from-installation + * + * @param int $installationId + * @param int $repositoryId + * + * @return array + */ + public function removeRepository($installationId, $repositoryId) + { + return $this->delete('/installations/'.rawurlencode($installationId).'/repositories/'.rawurlencode($repositoryId)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Authorizations.php b/vendor/knplabs/github-api/lib/Github/Api/Authorizations.php new file mode 100644 index 00000000..fd8e9b23 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Authorizations.php @@ -0,0 +1,122 @@ + + */ +class Authorizations extends AbstractApi +{ + /** + * List all authorizations. + * + * @return array + */ + public function all() + { + return $this->get('/authorizations'); + } + + /** + * Show a single authorization. + * + * @param $clientId + * + * @return array + */ + public function show($clientId) + { + return $this->get('/authorizations/'.rawurlencode($clientId)); + } + + /** + * Create an authorization. + * + * @param array $params + * @param null $OTPCode + * + * @return array + */ + public function create(array $params, $OTPCode = null) + { + $headers = null === $OTPCode ? [] : ['X-GitHub-OTP' => $OTPCode]; + + return $this->post('/authorizations', $params, $headers); + } + + /** + * Update an authorization. + * + * @param $clientId + * @param array $params + * + * @return array + */ + public function update($clientId, array $params) + { + return $this->patch('/authorizations/'.rawurlencode($clientId), $params); + } + + /** + * Remove an authorization. + * + * @param $clientId + * + * @return array + */ + public function remove($clientId) + { + return $this->delete('/authorizations/'.rawurlencode($clientId)); + } + + /** + * Check an authorization. + * + * @param $clientId + * @param $token + * + * @return array + */ + public function check($clientId, $token) + { + return $this->get('/applications/'.rawurlencode($clientId).'/tokens/'.rawurlencode($token)); + } + + /** + * Reset an authorization. + * + * @param $clientId + * @param $token + * + * @return array + */ + public function reset($clientId, $token) + { + return $this->post('/applications/'.rawurlencode($clientId).'/tokens/'.rawurlencode($token)); + } + + /** + * Remove an authorization. + * + * @param $clientId + * @param $token + */ + public function revoke($clientId, $token) + { + $this->delete('/applications/'.rawurlencode($clientId).'/tokens/'.rawurlencode($token)); + } + + /** + * Revoke all authorizations. + * + * @param $clientId + */ + public function revokeAll($clientId) + { + $this->delete('/applications/'.rawurlencode($clientId).'/tokens'); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/CurrentUser.php b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser.php new file mode 100644 index 00000000..0c858b07 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser.php @@ -0,0 +1,207 @@ + + * @author Felipe Valtl de Mello + */ +class CurrentUser extends AbstractApi +{ + public function show() + { + return $this->get('/user'); + } + + public function update(array $params) + { + return $this->patch('/user', $params); + } + + /** + * @return Emails + */ + public function emails() + { + return new Emails($this->client); + } + + /** + * @return Followers + */ + public function follow() + { + return new Followers($this->client); + } + + public function followers($page = 1) + { + return $this->get('/user/followers', [ + 'page' => $page, + ]); + } + + /** + * @link http://developer.github.com/v3/issues/#list-issues + * + * @param array $params + * @param bool $includeOrgIssues + * + * @return array + */ + public function issues(array $params = [], $includeOrgIssues = true) + { + return $this->get($includeOrgIssues ? '/issues' : '/user/issues', array_merge(['page' => 1], $params)); + } + + /** + * @return PublicKeys + */ + public function keys() + { + return new PublicKeys($this->client); + } + + /** + * @return Notifications + */ + public function notifications() + { + return new Notifications($this->client); + } + + /** + * @return Memberships + */ + public function memberships() + { + return new Memberships($this->client); + } + + /** + * @link http://developer.github.com/v3/orgs/#list-user-organizations + * + * @return array + */ + public function organizations() + { + return $this->get('/user/orgs'); + } + + /** + * @link https://developer.github.com/v3/orgs/teams/#list-user-teams + * + * @return array + */ + public function teams() + { + return $this->get('/user/teams'); + } + + /** + * @link http://developer.github.com/v3/repos/#list-your-repositories + * + * @param string $type role in the repository + * @param string $sort sort by + * @param string $direction direction of sort, asc or desc + * @param string $visibility visibility of repository + * @param string $affiliation relationship to repository + * + * @return array + */ + public function repositories($type = 'owner', $sort = 'full_name', $direction = 'asc', $visibility = null, $affiliation = null) + { + $params = [ + 'type' => $type, + 'sort' => $sort, + 'direction' => $direction, + ]; + + if (null !== $visibility) { + unset($params['type']); + $params['visibility'] = $visibility; + } + + if (null !== $affiliation) { + unset($params['type']); + $params['affiliation'] = $affiliation; + } + + return $this->get('/user/repos', $params); + } + + /** + * @return Watchers + */ + public function watchers() + { + return new Watchers($this->client); + } + + /** + * @deprecated Use watchers() instead + */ + public function watched($page = 1) + { + return $this->get('/user/watched', [ + 'page' => $page, + ]); + } + + /** + * @return Starring + */ + public function starring() + { + return new Starring($this->client); + } + + /** + * @deprecated Use starring() instead + */ + public function starred($page = 1) + { + return $this->get('/user/starred', [ + 'page' => $page, + ]); + } + + /** + * @link https://developer.github.com/v3/activity/watching/#list-repositories-being-watched + */ + public function subscriptions() + { + return $this->get('/user/subscriptions'); + } + + /** + * @link https://developer.github.com/v3/integrations/#list-installations-for-user + * + * @param array $params + */ + public function installations(array $params = []) + { + return $this->get('/user/installations', array_merge(['page' => 1], $params)); + } + + /** + * @link https://developer.github.com/v3/integrations/installations/#list-repositories-accessible-to-the-user-for-an-installation + * + * @param string $installationId the ID of the Installation + * @param array $params + */ + public function repositoriesByInstallation($installationId, array $params = []) + { + return $this->get(sprintf('/user/installations/%s/repositories', $installationId), array_merge(['page' => 1], $params)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Emails.php b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Emails.php new file mode 100644 index 00000000..15d4fad0 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Emails.php @@ -0,0 +1,94 @@ + + */ +class Emails extends AbstractApi +{ + /** + * List emails for the authenticated user. + * + * @link http://developer.github.com/v3/users/emails/ + * + * @return array + */ + public function all() + { + return $this->get('/user/emails'); + } + + /** + * List public email addresses for a user. + * + * @link https://developer.github.com/v3/users/emails/#list-public-email-addresses-for-a-user + * + * @return array + */ + public function allPublic() + { + return $this->get('/user/public_emails'); + } + + /** + * Adds one or more email for the authenticated user. + * + * @link http://developer.github.com/v3/users/emails/ + * + * @param string|array $emails + * + * @throws \Github\Exception\InvalidArgumentException + * + * @return array + */ + public function add($emails) + { + if (is_string($emails)) { + $emails = [$emails]; + } elseif (0 === count($emails)) { + throw new InvalidArgumentException(); + } + + return $this->post('/user/emails', $emails); + } + + /** + * Removes one or more email for the authenticated user. + * + * @link http://developer.github.com/v3/users/emails/ + * + * @param string|array $emails + * + * @throws \Github\Exception\InvalidArgumentException + * + * @return array + */ + public function remove($emails) + { + if (is_string($emails)) { + $emails = [$emails]; + } elseif (0 === count($emails)) { + throw new InvalidArgumentException(); + } + + return $this->delete('/user/emails', $emails); + } + + /** + * Toggle primary email visibility. + * + * @link https://developer.github.com/v3/users/emails/#toggle-primary-email-visibility + * + * @return array + */ + public function toggleVisibility() + { + return $this->patch('/user/email/visibility'); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Followers.php b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Followers.php new file mode 100644 index 00000000..52a712ca --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Followers.php @@ -0,0 +1,71 @@ + + */ +class Followers extends AbstractApi +{ + /** + * List followed users by the authenticated user. + * + * @link http://developer.github.com/v3/repos/followers/ + * + * @param int $page + * + * @return array + */ + public function all($page = 1) + { + return $this->get('/user/following', [ + 'page' => $page, + ]); + } + + /** + * Check that the authenticated user follows a user. + * + * @link http://developer.github.com/v3/repos/followers/ + * + * @param string $username the username to follow + * + * @return array + */ + public function check($username) + { + return $this->get('/user/following/'.rawurlencode($username)); + } + + /** + * Make the authenticated user follow a user. + * + * @link http://developer.github.com/v3/repos/followers/ + * + * @param string $username the username to follow + * + * @return array + */ + public function follow($username) + { + return $this->put('/user/following/'.rawurlencode($username)); + } + + /** + * Make the authenticated user un-follow a user. + * + * @link http://developer.github.com/v3/repos/followers/ + * + * @param string $username the username to un-follow + * + * @return array + */ + public function unfollow($username) + { + return $this->delete('/user/following/'.rawurlencode($username)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Memberships.php b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Memberships.php new file mode 100644 index 00000000..da727397 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Memberships.php @@ -0,0 +1,48 @@ +get('/user/memberships/orgs'); + } + + /** + * Get your organization membership. + * + * @link https://developer.github.com/v3/orgs/members/#get-your-organization-membership + * + * @param string $organization + * + * @return array + */ + public function organization($organization) + { + return $this->get('/user/memberships/orgs/'.rawurlencode($organization)); + } + + /** + * Edit your organization membership. + * + * @link https://developer.github.com/v3/orgs/members/#edit-your-organization-membership + * + * @param string $organization + * + * @return array + */ + public function edit($organization) + { + return $this->patch('/user/memberships/orgs/'.rawurlencode($organization), ['state' => 'active']); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Notifications.php b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Notifications.php new file mode 100644 index 00000000..36dfb57e --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Notifications.php @@ -0,0 +1,145 @@ + + */ +class Notifications extends AbstractApi +{ + /** + * List all notifications for the authenticated user. + * + * @link http://developer.github.com/v3/activity/notifications/#list-your-notifications + * + * @param array $params + * + * @return array + */ + public function all(array $params = []) + { + return $this->get('/notifications', $params); + } + + /** + * List all notifications for the authenticated user in selected repository. + * + * @link http://developer.github.com/v3/activity/notifications/#list-your-notifications-in-a-repository + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param array $params + * + * @return array + */ + public function allInRepository($username, $repository, array $params = []) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/notifications', $params); + } + + /** + * Mark all notifications as read. + * + * @link http://developer.github.com/v3/activity/notifications/#mark-as-read + * + * @param array $params + * + * @return array + */ + public function markAsReadAll(array $params = []) + { + return $this->put('/notifications', $params); + } + + /** + * Mark all notifications for a repository as read. + * + * @link http://developer.github.com/v3/activity/notifications/#mark-notifications-as-read-in-a-repository + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param array $params + * + * @return array + */ + public function markAsReadInRepository($username, $repository, array $params = []) + { + return $this->put('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/notifications', $params); + } + + /** + * Mark a notification as read. + * + * @link http://developer.github.com/v3/activity/notifications/#mark-a-thread-as-read + * + * @param int $id the notification number + * @param array $params + * + * @return array + */ + public function markAsRead($id, array $params) + { + return $this->patch('/notifications/threads/'.rawurlencode($id), $params); + } + + /** + * Show a notification. + * + * @link http://developer.github.com/v3/activity/notifications/#view-a-single-thread + * + * @param int $id the notification number + * + * @return array + */ + public function show($id) + { + return $this->get('/notifications/threads/'.rawurlencode($id)); + } + + /** + * Show a subscription. + * + * @link http://developer.github.com/v3/activity/notifications/#get-a-thread-subscription + * + * @param int $id the notification number + * + * @return array + */ + public function showSubscription($id) + { + return $this->get('/notifications/threads/'.rawurlencode($id).'/subscription'); + } + + /** + * Create a subscription. + * + * @link http://developer.github.com/v3/activity/notifications/#set-a-thread-subscription + * + * @param int $id the notification number + * @param array $params + * + * @return array + */ + public function createSubscription($id, array $params) + { + return $this->put('/notifications/threads/'.rawurlencode($id).'/subscription', $params); + } + + /** + * Delete a subscription. + * + * @link http://developer.github.com/v3/activity/notifications/#delete-a-thread-subscription + * + * @param int $id the notification number + * + * @return array + */ + public function removeSubscription($id) + { + return $this->delete('/notifications/threads/'.rawurlencode($id).'/subscription'); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/PublicKeys.php b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/PublicKeys.php new file mode 100644 index 00000000..706e1405 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/PublicKeys.php @@ -0,0 +1,74 @@ + + */ +class PublicKeys extends AbstractApi +{ + /** + * List deploy keys for the authenticated user. + * + * @link https://developer.github.com/v3/users/keys/ + * + * @return array + */ + public function all() + { + return $this->get('/user/keys'); + } + + /** + * Shows deploy key for the authenticated user. + * + * @link https://developer.github.com/v3/users/keys/ + * + * @param int $id + * + * @return array + */ + public function show($id) + { + return $this->get('/user/keys/'.rawurlencode($id)); + } + + /** + * Adds deploy key for the authenticated user. + * + * @link https://developer.github.com/v3/users/keys/ + * + * @param array $params + * + * @throws \Github\Exception\MissingArgumentException + * + * @return array + */ + public function create(array $params) + { + if (!isset($params['title'], $params['key'])) { + throw new MissingArgumentException(['title', 'key']); + } + + return $this->post('/user/keys', $params); + } + + /** + * Removes deploy key for the authenticated user. + * + * @link https://developer.github.com/v3/users/keys/ + * + * @param int $id + * + * @return array + */ + public function remove($id) + { + return $this->delete('/user/keys/'.rawurlencode($id)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Starring.php b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Starring.php new file mode 100644 index 00000000..d823c0bf --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Starring.php @@ -0,0 +1,76 @@ + + */ +class Starring extends AbstractApi +{ + /** + * List repositories starred by the authenticated user. + * + * @link https://developer.github.com/v3/activity/starring/ + * + * @param int $page + * @param int $perPage + * + * @return array + */ + public function all($page = 1, $perPage = 30) + { + return $this->get('/user/starred', [ + 'page' => $page, + 'per_page' => $perPage, + ]); + } + + /** + * Check that the authenticated user starres a repository. + * + * @link https://developer.github.com/v3/activity/starring/ + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function check($username, $repository) + { + return $this->get('/user/starred/'.rawurlencode($username).'/'.rawurlencode($repository)); + } + + /** + * Make the authenticated user star a repository. + * + * @link https://developer.github.com/v3/activity/starring/ + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function star($username, $repository) + { + return $this->put('/user/starred/'.rawurlencode($username).'/'.rawurlencode($repository)); + } + + /** + * Make the authenticated user unstar a repository. + * + * @link https://developer.github.com/v3/activity/starring + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function unstar($username, $repository) + { + return $this->delete('/user/starred/'.rawurlencode($username).'/'.rawurlencode($repository)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Watchers.php b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Watchers.php new file mode 100644 index 00000000..1ef35972 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Watchers.php @@ -0,0 +1,75 @@ + + * @revised Felipe Valtl de Mello + */ +class Watchers extends AbstractApi +{ + /** + * List repositories watched by the authenticated user. + * + * @link https://developer.github.com/v3/activity/watching/ + * + * @param int $page + * + * @return array + */ + public function all($page = 1) + { + return $this->get('/user/subscriptions', [ + 'page' => $page, + ]); + } + + /** + * Check that the authenticated user watches a repository. + * + * @link https://developer.github.com/v3/activity/watching/ + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function check($username, $repository) + { + return $this->get('/user/subscriptions/'.rawurlencode($username).'/'.rawurlencode($repository)); + } + + /** + * Make the authenticated user watch a repository. + * + * @link https://developer.github.com/v3/activity/watching/ + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function watch($username, $repository) + { + return $this->put('/user/subscriptions/'.rawurlencode($username).'/'.rawurlencode($repository)); + } + + /** + * Make the authenticated user unwatch a repository. + * + * @link https://developer.github.com/v3/activity/watching/ + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function unwatch($username, $repository) + { + return $this->delete('/user/subscriptions/'.rawurlencode($username).'/'.rawurlencode($repository)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Deployment.php b/vendor/knplabs/github-api/lib/Github/Api/Deployment.php new file mode 100644 index 00000000..a6e9bacd --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Deployment.php @@ -0,0 +1,107 @@ +get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/deployments', $params); + } + + /** + * Get a deployment in selected repository. + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the deployment + * + * @return array + */ + public function show($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/deployments/'.rawurlencode($id)); + } + + /** + * Create a new deployment for the given username and repo. + * + * @link https://developer.github.com/v3/repos/deployments/#create-a-deployment + * + * Important: Once a deployment is created, it cannot be updated. Changes are indicated by creating new statuses. + * @see updateStatus + * + * @param string $username the username + * @param string $repository the repository + * @param array $params the new deployment data + * + * @throws MissingArgumentException + * + * @return array information about the deployment + */ + public function create($username, $repository, array $params) + { + if (!isset($params['ref'])) { + throw new MissingArgumentException(['ref']); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/deployments', $params); + } + + /** + * Updates a deployment by creating a new status update. + * + * @link https://developer.github.com/v3/repos/deployments/#create-a-deployment-status + * + * @param string $username the username + * @param string $repository the repository + * @param int $id the deployment number + * @param array $params The information about the deployment update. + * Must include a "state" field of pending, success, error, or failure. + * May also be given a target_url and description, ßee link for more details. + * + * @throws MissingArgumentException + * + * @return array information about the deployment + */ + public function updateStatus($username, $repository, $id, array $params) + { + if (!isset($params['state'])) { + throw new MissingArgumentException(['state']); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/deployments/'.rawurlencode($id).'/statuses', $params); + } + + /** + * Gets all of the status updates tied to a given deployment. + * + * @param string $username the username + * @param string $repository the repository + * @param int $id the deployment identifier + * + * @return array the deployment statuses + */ + public function getStatuses($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/deployments/'.rawurlencode($id).'/statuses'); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Enterprise.php b/vendor/knplabs/github-api/lib/Github/Api/Enterprise.php new file mode 100644 index 00000000..3dbbee3e --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Enterprise.php @@ -0,0 +1,51 @@ + + * @author Guillermo A. Fisher + */ +class Enterprise extends AbstractApi +{ + /** + * @return Stats + */ + public function stats() + { + return new Stats($this->client); + } + + /** + * @return License + */ + public function license() + { + return new License($this->client); + } + + /** + * @return ManagementConsole + */ + public function console() + { + return new ManagementConsole($this->client); + } + + /** + * @return UserAdmin + */ + public function userAdmin() + { + return new UserAdmin($this->client); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Enterprise/License.php b/vendor/knplabs/github-api/lib/Github/Api/Enterprise/License.php new file mode 100644 index 00000000..67e1c2a9 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Enterprise/License.php @@ -0,0 +1,20 @@ +get('/enterprise/settings/license'); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Enterprise/ManagementConsole.php b/vendor/knplabs/github-api/lib/Github/Api/Enterprise/ManagementConsole.php new file mode 100644 index 00000000..f11c4764 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Enterprise/ManagementConsole.php @@ -0,0 +1,77 @@ +getWithLicenseHash('/setup/api/configcheck', $hash); + } + + /** + * Retrieves your installation’s settings. + * + * @link https://developer.github.com/v3/enterprise/management_console/#retrieve-settings + * + * @param string $hash md5 hash of your license + * + * @return array array of settings + */ + public function settings($hash) + { + return $this->getWithLicenseHash('/setup/api/settings', $hash); + } + + /** + * Checks your installation’s maintenance status. + * + * @link https://developer.github.com/v3/enterprise/management_console/#check-maintenance-status + * + * @param string $hash md5 hash of your license + * + * @return array array of maintenance status information + */ + public function maintenance($hash) + { + return $this->getWithLicenseHash('/setup/api/maintenance', $hash); + } + + /** + * Retrieves your installation’s authorized SSH keys. + * + * @link https://developer.github.com/v3/enterprise/management_console/#retrieve-authorized-ssh-keys + * + * @param string $hash md5 hash of your license + * + * @return array array of authorized keys + */ + public function keys($hash) + { + return $this->getWithLicenseHash('/setup/api/settings/authorized-keys', $hash); + } + + /** + * Sends an authenticated GET request. + * + * @param string $uri the request URI + * @param string $hash md5 hash of your license + * + * @return array|string + */ + protected function getWithLicenseHash($uri, $hash) + { + return $this->get($uri, ['license_md5' => rawurlencode($hash)]); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Enterprise/Stats.php b/vendor/knplabs/github-api/lib/Github/Api/Enterprise/Stats.php new file mode 100644 index 00000000..78ba4256 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Enterprise/Stats.php @@ -0,0 +1,128 @@ +show('issues'); + } + + /** + * Returns the number of active and inactive hooks. + * + * @return array array with totals of active and inactive hooks + */ + public function hooks() + { + return $this->show('hooks'); + } + + /** + * Returns the number of open and closed milestones. + * + * @return array array with totals of open and closed milestones + */ + public function milestones() + { + return $this->show('milestones'); + } + + /** + * Returns the number of organizations, teams, team members, and disabled organizations. + * + * @return array array with totals of organizations, teams, team members, and disabled organizations + */ + public function orgs() + { + return $this->show('orgs'); + } + + /** + * Returns the number of comments on issues, pull requests, commits, and gists. + * + * @return array array with totals of comments on issues, pull requests, commits, and gists + */ + public function comments() + { + return $this->show('comments'); + } + + /** + * Returns the number of GitHub Pages sites. + * + * @return array array with totals of GitHub Pages sites + */ + public function pages() + { + return $this->show('pages'); + } + + /** + * Returns the number of suspended and admin users. + * + * @return array array with totals of suspended and admin users + */ + public function users() + { + return $this->show('users'); + } + + /** + * Returns the number of private and public gists. + * + * @return array array with totals of private and public gists + */ + public function gists() + { + return $this->show('gists'); + } + + /** + * Returns the number of merged, mergeable, and unmergeable pull requests. + * + * @return array array with totals of merged, mergeable, and unmergeable pull requests + */ + public function pulls() + { + return $this->show('pulls'); + } + + /** + * Returns the number of organization-owned repositories, root repositories, forks, pushed commits, and wikis. + * + * @return array array with totals of organization-owned repositories, root repositories, forks, pushed commits, and wikis + */ + public function repos() + { + return $this->show('repos'); + } + + /** + * Returns all of the statistics. + * + * @return array array with all of the statistics + */ + public function all() + { + return $this->show('all'); + } + + /** + * @param string $type The type of statistics to show + * + * @return array + */ + public function show($type) + { + return $this->get('/enterprise/stats/'.rawurlencode($type)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Enterprise/UserAdmin.php b/vendor/knplabs/github-api/lib/Github/Api/Enterprise/UserAdmin.php new file mode 100644 index 00000000..0cd55a38 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Enterprise/UserAdmin.php @@ -0,0 +1,36 @@ +put('/users/'.rawurldecode($username).'/suspended', ['Content-Length' => 0]); + } + + /** + * Unsuspend a user. + * + * @link https://developer.github.com/v3/users/administration/#unsuspend-a-user + * + * @param string $username + * + * @return array + */ + public function unsuspend($username) + { + return $this->delete('/users/'.rawurldecode($username).'/suspended'); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Gist/Comments.php b/vendor/knplabs/github-api/lib/Github/Api/Gist/Comments.php new file mode 100644 index 00000000..0e022622 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Gist/Comments.php @@ -0,0 +1,101 @@ + + */ +class Comments extends AbstractApi +{ + use AcceptHeaderTrait; + + /** + * Configure the body type. + * + * @link https://developer.github.com/v3/gists/comments/#custom-media-types + * + * @param string|null $bodyType + * + * @return self + */ + public function configure($bodyType = null) + { + if (!in_array($bodyType, ['text', 'html', 'full'])) { + $bodyType = 'raw'; + } + + $this->acceptHeaderValue = sprintf('application/vnd.github.%s.%s+json', $this->client->getApiVersion(), $bodyType); + + return $this; + } + + /** + * Get all comments for a gist. + * + * @param string $gist + * + * @return array + */ + public function all($gist) + { + return $this->get('/gists/'.rawurlencode($gist).'/comments'); + } + + /** + * Get a comment of a gist. + * + * @param string $gist + * @param int $comment + * + * @return array + */ + public function show($gist, $comment) + { + return $this->get('/gists/'.rawurlencode($gist).'/comments/'.rawurlencode($comment)); + } + + /** + * Create a comment for gist. + * + * @param string $gist + * @param string $body + * + * @return array + */ + public function create($gist, $body) + { + return $this->post('/gists/'.rawurlencode($gist).'/comments', ['body' => $body]); + } + + /** + * Create a comment for a gist. + * + * @param string $gist + * @param int $comment_id + * @param string $body + * + * @return array + */ + public function update($gist, $comment_id, $body) + { + return $this->patch('/gists/'.rawurlencode($gist).'/comments/'.rawurlencode($comment_id), ['body' => $body]); + } + + /** + * Delete a comment for a gist. + * + * @param string $gist + * @param int $comment + * + * @return array + */ + public function remove($gist, $comment) + { + return $this->delete('/gists/'.rawurlencode($gist).'/comments/'.rawurlencode($comment)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Gists.php b/vendor/knplabs/github-api/lib/Github/Api/Gists.php new file mode 100644 index 00000000..e6bdc430 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Gists.php @@ -0,0 +1,116 @@ + + * @author Edoardo Rivello + */ +class Gists extends AbstractApi +{ + use AcceptHeaderTrait; + + /** + * Configure the body type. + * + * @link https://developer.github.com/v3/gists/#custom-media-types + * + * @param string|null $bodyType + * + * @return self + */ + public function configure($bodyType = null) + { + if (!in_array($bodyType, ['base64'])) { + $bodyType = 'raw'; + } + + $this->acceptHeaderValue = sprintf('application/vnd.github.%s.%s', $this->client->getApiVersion(), $bodyType); + + return $this; + } + + public function all($type = null) + { + if (!in_array($type, ['public', 'starred'])) { + return $this->get('/gists'); + } + + return $this->get('/gists/'.rawurlencode($type)); + } + + public function show($number) + { + return $this->get('/gists/'.rawurlencode($number)); + } + + public function create(array $params) + { + if (!isset($params['files']) || (!is_array($params['files']) || 0 === count($params['files']))) { + throw new MissingArgumentException('files'); + } + + $params['public'] = (bool) $params['public']; + + return $this->post('/gists', $params); + } + + public function update($id, array $params) + { + return $this->patch('/gists/'.rawurlencode($id), $params); + } + + public function commits($id) + { + return $this->get('/gists/'.rawurlencode($id).'/commits'); + } + + public function fork($id) + { + return $this->post('/gists/'.rawurlencode($id).'/fork'); + } + + public function forks($id) + { + return $this->get('/gists/'.rawurlencode($id).'/forks'); + } + + public function remove($id) + { + return $this->delete('/gists/'.rawurlencode($id)); + } + + public function check($id) + { + return $this->get('/gists/'.rawurlencode($id).'/star'); + } + + public function star($id) + { + return $this->put('/gists/'.rawurlencode($id).'/star'); + } + + public function unstar($id) + { + return $this->delete('/gists/'.rawurlencode($id).'/star'); + } + + /** + * Get a gist's comments. + * + * @link http://developer.github.com/v3/gists/comments/ + * + * @return Comments + */ + public function comments() + { + return new Comments($this->client); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/GitData.php b/vendor/knplabs/github-api/lib/Github/Api/GitData.php new file mode 100644 index 00000000..d431b788 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/GitData.php @@ -0,0 +1,59 @@ + + */ +class GitData extends AbstractApi +{ + /** + * @return Blobs + */ + public function blobs() + { + return new Blobs($this->client); + } + + /** + * @return Commits + */ + public function commits() + { + return new Commits($this->client); + } + + /** + * @return References + */ + public function references() + { + return new References($this->client); + } + + /** + * @return Tags + */ + public function tags() + { + return new Tags($this->client); + } + + /** + * @return Trees + */ + public function trees() + { + return new Trees($this->client); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/GitData/Blobs.php b/vendor/knplabs/github-api/lib/Github/Api/GitData/Blobs.php new file mode 100644 index 00000000..be68771d --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/GitData/Blobs.php @@ -0,0 +1,70 @@ + + * @author Tobias Nyholm + */ +class Blobs extends AbstractApi +{ + use AcceptHeaderTrait; + + /** + * Configure the Accept header depending on the blob type. + * + * @param string|null $bodyType + * + * @return self + */ + public function configure($bodyType = null) + { + if ('raw' === $bodyType) { + $this->acceptHeaderValue = sprintf('application/vnd.github.%s.raw', $this->client->getApiVersion()); + } + + return $this; + } + + /** + * Show a blob of a sha for a repository. + * + * @param string $username + * @param string $repository + * @param string $sha + * + * @return array + */ + public function show($username, $repository, $sha) + { + $response = $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/blobs/'.rawurlencode($sha)); + + return $response; + } + + /** + * Create a blob of a sha for a repository. + * + * @param string $username + * @param string $repository + * @param array $params + * + * @throws \Github\Exception\MissingArgumentException + * + * @return array + */ + public function create($username, $repository, array $params) + { + if (!isset($params['content'], $params['encoding'])) { + throw new MissingArgumentException(['content', 'encoding']); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/blobs', $params); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/GitData/Commits.php b/vendor/knplabs/github-api/lib/Github/Api/GitData/Commits.php new file mode 100644 index 00000000..4205931f --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/GitData/Commits.php @@ -0,0 +1,48 @@ + + */ +class Commits extends AbstractApi +{ + /** + * Show a commit for a repository. + * + * @param string $username + * @param string $repository + * @param string $sha + * + * @return array + */ + public function show($username, $repository, $sha) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/commits/'.rawurlencode($sha)); + } + + /** + * Create a commit for a repository. + * + * @param string $username + * @param string $repository + * @param array $params + * + * @throws \Github\Exception\MissingArgumentException + * + * @return array + */ + public function create($username, $repository, array $params) + { + if (!isset($params['message'], $params['tree'], $params['parents'])) { + throw new MissingArgumentException(['message', 'tree', 'parents']); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/commits', $params); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/GitData/References.php b/vendor/knplabs/github-api/lib/Github/Api/GitData/References.php new file mode 100644 index 00000000..c54c0c8a --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/GitData/References.php @@ -0,0 +1,140 @@ + + */ +class References extends AbstractApi +{ + /** + * Get all references of a repository. + * + * @param string $username + * @param string $repository + * + * @return array + */ + public function all($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs'); + } + + /** + * Get all branches of a repository. + * + * @param string $username + * @param string $repository + * + * @return array + */ + public function branches($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs/heads'); + } + + /** + * Get all tags of a repository. + * + * @param string $username + * @param string $repository + * + * @return array + */ + public function tags($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs/tags'); + } + + /** + * Show the reference of a repository. + * + * @param string $username + * @param string $repository + * @param string $reference + * + * @return array + */ + public function show($username, $repository, $reference) + { + $reference = $this->encodeReference($reference); + + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs/'.$reference); + } + + /** + * Create a reference for a repository. + * + * @param string $username + * @param string $repository + * @param array $params + * + * @throws \Github\Exception\MissingArgumentException + * + * @return array + */ + public function create($username, $repository, array $params) + { + if (!isset($params['ref'], $params['sha'])) { + throw new MissingArgumentException(['ref', 'sha']); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs', $params); + } + + /** + * Update a reference for a repository. + * + * @param string $username + * @param string $repository + * @param string $reference + * @param array $params + * + * @throws \Github\Exception\MissingArgumentException + * + * @return array + */ + public function update($username, $repository, $reference, array $params) + { + if (!isset($params['sha'])) { + throw new MissingArgumentException('sha'); + } + + $reference = $this->encodeReference($reference); + + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs/'.$reference, $params); + } + + /** + * Delete a reference of a repository. + * + * @param string $username + * @param string $repository + * @param string $reference + * + * @return array + */ + public function remove($username, $repository, $reference) + { + $reference = $this->encodeReference($reference); + + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs/'.$reference); + } + + /** + * Encode the raw reference. + * + * @param string $rawReference + * + * @return string + */ + private function encodeReference($rawReference) + { + return implode('/', array_map('rawurlencode', explode('/', $rawReference))); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/GitData/Tags.php b/vendor/knplabs/github-api/lib/Github/Api/GitData/Tags.php new file mode 100644 index 00000000..09f48bc0 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/GitData/Tags.php @@ -0,0 +1,69 @@ + + */ +class Tags extends AbstractApi +{ + /** + * Get all tags for a repository. + * + * @param string $username + * @param string $repository + * + * @return array + */ + public function all($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs/tags'); + } + + /** + * Get a tag for a repository. + * + * @param string $username + * @param string $repository + * @param string $sha + * + * @return array + */ + public function show($username, $repository, $sha) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/tags/'.rawurlencode($sha)); + } + + /** + * Create a tag for a repository. + * + * @param string $username + * @param string $repository + * @param array $params + * + * @throws \Github\Exception\MissingArgumentException + * + * @return array + */ + public function create($username, $repository, array $params) + { + if (!isset($params['tag'], $params['message'], $params['object'], $params['type'])) { + throw new MissingArgumentException(['tag', 'message', 'object', 'type']); + } + + if (!isset($params['tagger'])) { + throw new MissingArgumentException('tagger'); + } + + if (!isset($params['tagger']['name'], $params['tagger']['email'], $params['tagger']['date'])) { + throw new MissingArgumentException(['tagger.name', 'tagger.email', 'tagger.date']); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/tags', $params); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/GitData/Trees.php b/vendor/knplabs/github-api/lib/Github/Api/GitData/Trees.php new file mode 100644 index 00000000..d514d9f8 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/GitData/Trees.php @@ -0,0 +1,64 @@ + + */ +class Trees extends AbstractApi +{ + /** + * Get the tree for a repository. + * + * @param string $username + * @param string $repository + * @param string $sha + * @param bool $recursive + * + * @return array + */ + public function show($username, $repository, $sha, $recursive = false) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/trees/'.rawurlencode($sha), $recursive ? ['recursive' => 1] : []); + } + + /** + * Create tree for a repository. + * + * @param string $username + * @param string $repository + * @param array $params + * + * @throws \Github\Exception\MissingArgumentException + * + * @return array + */ + public function create($username, $repository, array $params) + { + if (!isset($params['tree']) || !is_array($params['tree'])) { + throw new MissingArgumentException('tree'); + } + + if (!isset($params['tree'][0])) { + $params['tree'] = [$params['tree']]; + } + + foreach ($params['tree'] as $key => $tree) { + if (!isset($tree['path'], $tree['mode'], $tree['type'])) { + throw new MissingArgumentException(["tree.$key.path", "tree.$key.mode", "tree.$key.type"]); + } + + // If `sha` is not set, `content` is required + if (!isset($tree['sha']) && !isset($tree['content'])) { + throw new MissingArgumentException("tree.$key.content"); + } + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/trees', $params); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/GraphQL.php b/vendor/knplabs/github-api/lib/Github/Api/GraphQL.php new file mode 100644 index 00000000..9d66b512 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/GraphQL.php @@ -0,0 +1,47 @@ + + */ +class GraphQL extends AbstractApi +{ + use AcceptHeaderTrait; + + /** + * @param string $query + * @param array $variables + * + * @return array + */ + public function execute($query, array $variables = []) + { + $this->acceptHeaderValue = 'application/vnd.github.v4+json'; + $params = [ + 'query' => $query, + ]; + if (!empty($variables)) { + $params['variables'] = json_encode($variables); + } + + return $this->post('/graphql', $params); + } + + /** + * @param string $file + * @param array $variables + * + * @return array + */ + public function fromFile($file, array $variables = []) + { + return $this->execute(file_get_contents($file), $variables); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Integrations.php b/vendor/knplabs/github-api/lib/Github/Api/Integrations.php new file mode 100644 index 00000000..bd4f78a4 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Integrations.php @@ -0,0 +1,26 @@ + + */ +class Integrations extends Apps +{ + /** + * @deprecated + * Configure the accept header for Early Access to the integrations api (DEPRECATED) + * @see https://developer.github.com/v3/apps/ + * + * @return self + */ + public function configure() + { + return $this; + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Issue.php b/vendor/knplabs/github-api/lib/Github/Api/Issue.php new file mode 100644 index 00000000..9b7d1d04 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Issue.php @@ -0,0 +1,263 @@ + + * @author Joseph Bielawski + */ +class Issue extends AbstractApi +{ + use AcceptHeaderTrait; + + /** + * Configure the body type. + * + * @link https://developer.github.com/v3/issues/#custom-media-types + * + * @param string|null $bodyType + * + * @return self + */ + public function configure($bodyType = null) + { + if (!in_array($bodyType, ['text', 'html', 'full'])) { + $bodyType = 'raw'; + } + + $this->acceptHeaderValue = sprintf('application/vnd.github.%s.%s+json', $this->client->getApiVersion(), $bodyType); + + return $this; + } + + /** + * List issues by username, repo and state. + * + * @link http://developer.github.com/v3/issues/ + * + * @param string $username the username + * @param string $repository the repository + * @param array $params the additional parameters like milestone, assignees, labels, sort, direction + * + * @return array list of issues found + */ + public function all($username, $repository, array $params = []) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues', array_merge(['page' => 1], $params)); + } + + /** + * Search issues by username, repo, state and keyword. + * + * @deprecated This method is deprecated use the Search api instead. See https://developer.github.com/v3/search/legacy/#legacy-search-api-is-deprecated + * @link http://developer.github.com/v3/search/#search-issues + * + * @param string $username the username + * @param string $repository the repository + * @param string $state the issue state, can be open or closed + * @param string $keyword the keyword to filter issues by + * + * @return array list of issues found + */ + public function find($username, $repository, $state, $keyword) + { + if (!in_array($state, ['open', 'closed'])) { + $state = 'open'; + } + + return $this->get('/legacy/issues/search/'.rawurlencode($username).'/'.rawurlencode($repository).'/'.rawurlencode($state).'/'.rawurlencode($keyword)); + } + + /** + * List issues by organization. + * + * @link http://developer.github.com/v3/issues/ + * + * @param string $organization the organization + * @param string $state the issue state, can be open or closed + * @param array $params the additional parameters like milestone, assignees, labels, sort, direction + * + * @return array list of issues found + */ + public function org($organization, $state, array $params = []) + { + if (!in_array($state, ['open', 'closed'])) { + $state = 'open'; + } + + return $this->get('/orgs/'.rawurlencode($organization).'/issues', array_merge(['page' => 1, 'state' => $state], $params)); + } + + /** + * Get extended information about an issue by its username, repo and number. + * + * @link http://developer.github.com/v3/issues/ + * + * @param string $username the username + * @param string $repository the repository + * @param int $id the issue number + * + * @return array information about the issue + */ + public function show($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($id)); + } + + /** + * Create a new issue for the given username and repo. + * The issue is assigned to the authenticated user. Requires authentication. + * + * @link http://developer.github.com/v3/issues/ + * + * @param string $username the username + * @param string $repository the repository + * @param array $params the new issue data + * + * @throws MissingArgumentException + * + * @return array information about the issue + */ + public function create($username, $repository, array $params) + { + if (!isset($params['title'])) { + throw new MissingArgumentException(['title']); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues', $params); + } + + /** + * Update issue information's by username, repo and issue number. Requires authentication. + * + * @link http://developer.github.com/v3/issues/ + * + * @param string $username the username + * @param string $repository the repository + * @param int $id the issue number + * @param array $params key=>value user attributes to update. + * key can be title or body + * + * @return array information about the issue + */ + public function update($username, $repository, $id, array $params) + { + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($id), $params); + } + + /** + * Lock an issue. Users with push access can lock an issue's conversation. + * + * @link https://developer.github.com/v3/issues/#lock-an-issue + * + * @param string $username + * @param string $repository + * @param int $id + * + * @return string + */ + public function lock($username, $repository, $id) + { + return $this->put('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($id).'/lock'); + } + + /** + * Unlock an issue. Users with push access can unlock an issue's conversation. + * + * @link https://developer.github.com/v3/issues/#lock-an-issue + * + * @param string $username + * @param string $repository + * @param int $id + * + * @return string + */ + public function unlock($username, $repository, $id) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($id).'/lock'); + } + + /** + * List an issue comments. + * + * @link http://developer.github.com/v3/issues/comments/ + * + * @return Comments + */ + public function comments() + { + return new Comments($this->client); + } + + /** + * List all project events. + * + * @link http://developer.github.com/v3/issues/events/ + * + * @return Events + */ + public function events() + { + return new Events($this->client); + } + + /** + * List all project labels. + * + * @link http://developer.github.com/v3/issues/labels/ + * + * @return Labels + */ + public function labels() + { + return new Labels($this->client); + } + + /** + * List all project milestones. + * + * @link http://developer.github.com/v3/issues/milestones/ + * + * @return Milestones + */ + public function milestones() + { + return new Milestones($this->client); + } + + /** + * List all assignees. + * + * @link https://developer.github.com/v3/issues/assignees/ + * + * @return Assignees + */ + public function assignees() + { + return new Assignees($this->client); + } + + /** + * List all events. + * + * @link https://developer.github.com/v3/issues/timeline/ + * + * @return Timeline + */ + public function timeline() + { + return new Timeline($this->client); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Issue/Assignees.php b/vendor/knplabs/github-api/lib/Github/Api/Issue/Assignees.php new file mode 100644 index 00000000..1e5e0b72 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Issue/Assignees.php @@ -0,0 +1,91 @@ +get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/assignees', $parameters); + } + + /** + * Check to see if a particular user is an assignee for a repository. + * + * @link https://developer.github.com/v3/issues/assignees/#check-assignee + * + * @param string $username + * @param string $repository + * @param string $assignee + * + * @return array + */ + public function check($username, $repository, $assignee) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/assignees/'.rawurlencode($assignee)); + } + + /** + * Add assignees to an Issue. + * + * @link https://developer.github.com/v3/issues/assignees/#add-assignees-to-an-issue + * + * @param string $username + * @param string $repository + * @param string $issue + * @param array $parameters + * + * @throws MissingArgumentException + * + * @return string + */ + public function add($username, $repository, $issue, array $parameters) + { + if (!isset($parameters['assignees'])) { + throw new MissingArgumentException('assignees'); + } + + if (!is_array($parameters['assignees'])) { + @trigger_error(sprintf('Passing the "assignees" parameter as a string in "%s" is deprecated and will throw an exception in php-github-api version 3.0. Pass an array of strings instead', __METHOD__), E_USER_DEPRECATED); + + $parameters['assignees'] = [$parameters['assignees']]; + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/assignees', $parameters); + } + + /** + * Remove assignees from an Issue. + * + * @link https://developer.github.com/v3/issues/assignees/#remove-assignees-from-an-issue + * + * @param string $username + * @param string $repository + * @param string $issue + * @param array $parameters + * + * @throws MissingArgumentException + * + * @return string + */ + public function remove($username, $repository, $issue, array $parameters) + { + if (!isset($parameters['assignees'])) { + throw new MissingArgumentException('assignees'); + } + + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/assignees', $parameters); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Issue/Comments.php b/vendor/knplabs/github-api/lib/Github/Api/Issue/Comments.php new file mode 100644 index 00000000..a664d533 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Issue/Comments.php @@ -0,0 +1,135 @@ + + * @author Tobias Nyholm + */ +class Comments extends AbstractApi +{ + use AcceptHeaderTrait; + + /** + * Configure the body type. + * + * @link https://developer.github.com/v3/issues/comments/#custom-media-types + * + * @param string|null $bodyType + * + * @return self + */ + public function configure($bodyType = null) + { + if (!in_array($bodyType, ['raw', 'text', 'html'])) { + $bodyType = 'full'; + } + + $this->acceptHeaderValue = sprintf('application/vnd.github.%s.%s+json', $this->client->getApiVersion(), $bodyType); + + return $this; + } + + /** + * Get all comments for an issue. + * + * @link https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue + * + * @param string $username + * @param string $repository + * @param int $issue + * @param int $page + * + * @return array + */ + public function all($username, $repository, $issue, $page = 1) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/comments', [ + 'page' => $page, + ]); + } + + /** + * Get a comment for an issue. + * + * @link https://developer.github.com/v3/issues/comments/#get-a-single-comment + * + * @param string $username + * @param string $repository + * @param int $comment + * + * @return array + */ + public function show($username, $repository, $comment) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/comments/'.rawurlencode($comment)); + } + + /** + * Create a comment for an issue. + * + * @link https://developer.github.com/v3/issues/comments/#create-a-comment + * + * @param string $username + * @param string $repository + * @param int $issue + * @param array $params + * + * @throws \Github\Exception\MissingArgumentException + * + * @return array + */ + public function create($username, $repository, $issue, array $params) + { + if (!isset($params['body'])) { + throw new MissingArgumentException('body'); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/comments', $params); + } + + /** + * Update a comment for an issue. + * + * @link https://developer.github.com/v3/issues/comments/#edit-a-comment + * + * @param string $username + * @param string $repository + * @param int $comment + * @param array $params + * + * @throws \Github\Exception\MissingArgumentException + * + * @return array + */ + public function update($username, $repository, $comment, array $params) + { + if (!isset($params['body'])) { + throw new MissingArgumentException('body'); + } + + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/comments/'.rawurlencode($comment), $params); + } + + /** + * Delete a comment for an issue. + * + * @link https://developer.github.com/v3/issues/comments/#delete-a-comment + * + * @param string $username + * @param string $repository + * @param int $comment + * + * @return array + */ + public function remove($username, $repository, $comment) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/comments/'.rawurlencode($comment)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Issue/Events.php b/vendor/knplabs/github-api/lib/Github/Api/Issue/Events.php new file mode 100644 index 00000000..8b70ec79 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Issue/Events.php @@ -0,0 +1,54 @@ + + */ +class Events extends AbstractApi +{ + /** + * Get all events for an issue. + * + * @link https://developer.github.com/v3/issues/events/#list-events-for-an-issue + * + * @param string $username + * @param string $repository + * @param int|null $issue + * @param int $page + * + * @return array + */ + public function all($username, $repository, $issue = null, $page = 1) + { + if (null !== $issue) { + $path = '/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/events'; + } else { + $path = '/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/events'; + } + + return $this->get($path, [ + 'page' => $page, + ]); + } + + /** + * Display an event for an issue. + * + * @link https://developer.github.com/v3/issues/events/#get-a-single-event + * + * @param $username + * @param $repository + * @param $event + * + * @return array + */ + public function show($username, $repository, $event) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/events/'.rawurlencode($event)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Issue/Labels.php b/vendor/knplabs/github-api/lib/Github/Api/Issue/Labels.php new file mode 100644 index 00000000..1159e153 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Issue/Labels.php @@ -0,0 +1,192 @@ + + */ +class Labels extends AbstractApi +{ + /** + * Get all labels for a repository or the labels for a specific issue. + * + * @link https://developer.github.com/v3/issues/labels/#list-labels-on-an-issue + * + * @param string $username + * @param string $repository + * @param int|null $issue + * + * @return array + */ + public function all($username, $repository, $issue = null) + { + if ($issue === null) { + $path = '/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels'; + } else { + $path = '/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/labels'; + } + + return $this->get($path); + } + + /** + * Get a single label. + * + * @link https://developer.github.com/v3/issues/labels/#get-a-single-label + * + * @param string $username + * @param string $repository + * @param string $label + * + * @return array + */ + public function show($username, $repository, $label) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels/'.rawurlencode($label)); + } + + /** + * Create a label for a repository. + * + * @link https://developer.github.com/v3/issues/labels/#create-a-label + * + * @param string $username + * @param string $repository + * @param array $params + * + * @throws \Github\Exception\MissingArgumentException + * + * @return array + */ + public function create($username, $repository, array $params) + { + if (!isset($params['name'])) { + throw new MissingArgumentException('name'); + } + if (!isset($params['color'])) { + $params['color'] = 'FFFFFF'; + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels', $params); + } + + /** + * Delete a label for a repository. + * + * @link https://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue + * + * @param string $username + * @param string $repository + * @param string $label + * + * @return array + */ + public function deleteLabel($username, $repository, $label) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels/'.rawurlencode($label)); + } + + /** + * Edit a label for a repository. + * + * @link https://developer.github.com/v3/issues/labels/#update-a-label + * + * @param string $username + * @param string $repository + * @param string $label + * @param string $newName + * @param string $color + * + * @return array + */ + public function update($username, $repository, $label, $newName, $color) + { + $params = [ + 'name' => $newName, + 'color' => $color, + ]; + + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels/'.rawurlencode($label), $params); + } + + /** + * Add a label to an issue. + * + * @link https://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue + * + * @param string $username + * @param string $repository + * @param int $issue + * @param string $labels + * + * @return array + * + * @thorws \Github\Exception\InvalidArgumentException + */ + public function add($username, $repository, $issue, $labels) + { + if (is_string($labels)) { + $labels = [$labels]; + } elseif (0 === count($labels)) { + throw new InvalidArgumentException(); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/labels', $labels); + } + + /** + * Replace labels for an issue. + * + * @link https://developer.github.com/v3/issues/labels/#replace-all-labels-for-an-issue + * + * @param string $username + * @param string $repository + * @param int $issue + * @param array $params + * + * @return array + */ + public function replace($username, $repository, $issue, array $params) + { + return $this->put('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/labels', $params); + } + + /** + * Remove a label for an issue. + * + * @link https://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue + * + * @param string $username + * @param string $repository + * @param string $issue + * @param string $label + * + * @return null + */ + public function remove($username, $repository, $issue, $label) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/labels/'.rawurlencode($label)); + } + + /** + * Remove all labels from an issue. + * + * @link https://developer.github.com/v3/issues/labels/#replace-all-labels-for-an-issue + * + * @param string $username + * @param string $repository + * @param string $issue + * + * @return null + */ + public function clear($username, $repository, $issue) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/labels'); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Issue/Milestones.php b/vendor/knplabs/github-api/lib/Github/Api/Issue/Milestones.php new file mode 100644 index 00000000..11f0e94c --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Issue/Milestones.php @@ -0,0 +1,139 @@ + + */ +class Milestones extends AbstractApi +{ + /** + * Get all milestones for a repository. + * + * @link https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository + * + * @param string $username + * @param string $repository + * @param array $params + * + * @return array + */ + public function all($username, $repository, array $params = []) + { + if (isset($params['state']) && !in_array($params['state'], ['open', 'closed', 'all'])) { + $params['state'] = 'open'; + } + if (isset($params['sort']) && !in_array($params['sort'], ['due_date', 'completeness'])) { + $params['sort'] = 'due_date'; + } + if (isset($params['direction']) && !in_array($params['direction'], ['asc', 'desc'])) { + $params['direction'] = 'asc'; + } + + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/milestones', array_merge([ + 'page' => 1, + 'state' => 'open', + 'sort' => 'due_date', + 'direction' => 'asc', + ], $params)); + } + + /** + * Get a milestone for a repository. + * + * @link https://developer.github.com/v3/issues/milestones/#get-a-single-milestone + * + * @param string $username + * @param string $repository + * @param int $id + * + * @return array + */ + public function show($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/milestones/'.rawurlencode($id)); + } + + /** + * Create a milestone for a repository. + * + * @link https://developer.github.com/v3/issues/milestones/#create-a-milestone + * + * @param string $username + * @param string $repository + * @param array $params + * + * @throws \Github\Exception\MissingArgumentException + * + * @return array + */ + public function create($username, $repository, array $params) + { + if (!isset($params['title'])) { + throw new MissingArgumentException('title'); + } + if (isset($params['state']) && !in_array($params['state'], ['open', 'closed'])) { + $params['state'] = 'open'; + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/milestones', $params); + } + + /** + * Update a milestone for a repository. + * + * @link https://developer.github.com/v3/issues/milestones/#update-a-milestone + * + * @param string $username + * @param string $repository + * @param int $id + * @param array $params + * + * @return array + */ + public function update($username, $repository, $id, array $params) + { + if (isset($params['state']) && !in_array($params['state'], ['open', 'closed'])) { + $params['state'] = 'open'; + } + + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/milestones/'.rawurlencode($id), $params); + } + + /** + * Delete a milestone for a repository. + * + * @link https://developer.github.com/v3/issues/milestones/#delete-a-milestone + * + * @param string $username + * @param string $repository + * @param int $id + * + * @return null + */ + public function remove($username, $repository, $id) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/milestones/'.rawurlencode($id)); + } + + /** + * Get the labels of a milestone. + * + * @link https://developer.github.com/v3/issues/labels/#get-labels-for-every-issue-in-a-milestone + * + * @param string $username + * @param string $repository + * @param int $id + * + * @return array + */ + public function labels($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/milestones/'.rawurlencode($id).'/labels'); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Issue/Timeline.php b/vendor/knplabs/github-api/lib/Github/Api/Issue/Timeline.php new file mode 100644 index 00000000..c0f76f2f --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Issue/Timeline.php @@ -0,0 +1,34 @@ +acceptHeaderValue = 'application/vnd.github.mockingbird-preview'; + + return $this; + } + + /** + * Get all events for a specific issue. + * + * @link https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue + * + * @param string $username + * @param string $repository + * @param int $issue + * + * @return array + */ + public function all($username, $repository, $issue) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/timeline'); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Markdown.php b/vendor/knplabs/github-api/lib/Github/Api/Markdown.php new file mode 100644 index 00000000..977b1d04 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Markdown.php @@ -0,0 +1,49 @@ + + */ +class Markdown extends AbstractApi +{ + /** + * @param string $text + * @param string $mode + * @param string $context + * + * @return string + */ + public function render($text, $mode = 'markdown', $context = null) + { + if (!in_array($mode, ['gfm', 'markdown'])) { + $mode = 'markdown'; + } + + $params = [ + 'text' => $text, + 'mode' => $mode, + ]; + if (null !== $context && 'gfm' === $mode) { + $params['context'] = $context; + } + + return $this->post('/markdown', $params); + } + + /** + * @param string $file + * + * @return string + */ + public function renderRaw($file) + { + return $this->post('/markdown/raw', [ + 'file' => $file, + ]); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Meta.php b/vendor/knplabs/github-api/lib/Github/Api/Meta.php new file mode 100644 index 00000000..0ec81f65 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Meta.php @@ -0,0 +1,23 @@ + + */ +class Meta extends AbstractApi +{ + /** + * Get the ip address of the hook and git servers for the GitHub.com service. + * + * @return array Information about the service of GitHub.com + */ + public function service() + { + return $this->get('/meta'); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Miscellaneous/CodeOfConduct.php b/vendor/knplabs/github-api/lib/Github/Api/Miscellaneous/CodeOfConduct.php new file mode 100644 index 00000000..64bfaa8b --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Miscellaneous/CodeOfConduct.php @@ -0,0 +1,44 @@ +acceptHeaderValue = 'application/vnd.github.scarlet-witch-preview+json'; + + return $this; + } + + /** + * List all codes of conduct. + * + * @link https://developer.github.com/v3/codes_of_conduct/#list-all-codes-of-conduct + * + * @return array + */ + public function all() + { + return $this->get('/codes_of_conduct'); + } + + /** + * Get an individual code of conduct. + * + * @link https://developer.github.com/v3/codes_of_conduct/#get-an-individual-code-of-conduct + * + * @param string $key + * + * @return array + */ + public function show($key) + { + return $this->get('/codes_of_conduct/'.rawurlencode($key)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Miscellaneous/Emojis.php b/vendor/knplabs/github-api/lib/Github/Api/Miscellaneous/Emojis.php new file mode 100644 index 00000000..2a940f6d --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Miscellaneous/Emojis.php @@ -0,0 +1,20 @@ +get('/emojis'); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Miscellaneous/Gitignore.php b/vendor/knplabs/github-api/lib/Github/Api/Miscellaneous/Gitignore.php new file mode 100644 index 00000000..c5f03e7e --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Miscellaneous/Gitignore.php @@ -0,0 +1,34 @@ +get('/gitignore/templates'); + } + + /** + * Get a single template. + * + * @link https://developer.github.com/v3/gitignore/#get-a-single-template + * + * @param string $template + * + * @return array + */ + public function show($template) + { + return $this->get('/gitignore/templates/'.rawurlencode($template)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Notification.php b/vendor/knplabs/github-api/lib/Github/Api/Notification.php new file mode 100644 index 00000000..0d73d607 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Notification.php @@ -0,0 +1,90 @@ + + */ +class Notification extends AbstractApi +{ + /** + * Get a listing of notifications. + * + * @link https://developer.github.com/v3/activity/notifications/ + * + * @param bool $includingRead + * @param bool $participating + * @param DateTime|null $since + * + * @return array array of notifications + */ + public function all($includingRead = false, $participating = false, DateTime $since = null, DateTime $before = null) + { + $parameters = [ + 'all' => $includingRead, + 'participating' => $participating, + ]; + + if ($since !== null) { + $parameters['since'] = $since->format(DateTime::ISO8601); + } + + if ($before !== null) { + $parameters['before'] = $before->format(DateTime::ISO8601); + } + + return $this->get('/notifications', $parameters); + } + + /** + * Marks all notifications as read from the current date. + * + * Optionally give DateTime to mark as read before that date. + * + * @link https://developer.github.com/v3/activity/notifications/#mark-as-read + * + * @param DateTime|null $since + */ + public function markRead(DateTime $since = null) + { + $parameters = []; + + if ($since !== null) { + $parameters['last_read_at'] = $since->format(DateTime::ISO8601); + } + + $this->put('/notifications', $parameters); + } + + /** + * Mark a single thread as read using its ID. + * + * @link https://developer.github.com/v3/activity/notifications/#mark-a-thread-as-read + * + * @param int $id + */ + public function markThreadRead($id) + { + $this->patch('/notifications/threads/'.$id); + } + + /** + * Gets a single thread using its ID. + * + * @link https://developer.github.com/v3/activity/notifications/#view-a-single-thread + * + * @param int $id + */ + public function id($id) + { + return $this->get('/notifications/threads/'.$id); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Organization.php b/vendor/knplabs/github-api/lib/Github/Api/Organization.php new file mode 100644 index 00000000..49ca5c91 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Organization.php @@ -0,0 +1,104 @@ + + * @author Joseph Bielawski + */ +class Organization extends AbstractApi +{ + /** + * @link https://developer.github.com/v3/orgs/#list-all-organizations + * + * @return array the organizations + */ + public function all($since = '') + { + return $this->get('/organizations?since='.rawurlencode($since)); + } + + /** + * Get extended information about an organization by its name. + * + * @link http://developer.github.com/v3/orgs/#get + * + * @param string $organization the organization to show + * + * @return array information about the organization + */ + public function show($organization) + { + return $this->get('/orgs/'.rawurlencode($organization)); + } + + public function update($organization, array $params) + { + return $this->patch('/orgs/'.rawurlencode($organization), $params); + } + + /** + * List all repositories across all the organizations that you can access. + * + * @link http://developer.github.com/v3/repos/#list-organization-repositories + * + * @param string $organization the user name + * @param string $type the type of repositories + * @param int $page the page + * + * @return array the repositories + */ + public function repositories($organization, $type = 'all', $page = 1) + { + return $this->get('/orgs/'.rawurlencode($organization).'/repos', [ + 'type' => $type, + 'page' => $page, + ]); + } + + /** + * @return Members + */ + public function members() + { + return new Members($this->client); + } + + /** + * @return Hooks + */ + public function hooks() + { + return new Hooks($this->client); + } + + /** + * @return Teams + */ + public function teams() + { + return new Teams($this->client); + } + + /** + * @link http://developer.github.com/v3/issues/#list-issues + * + * @param $organization + * @param array $params + * @param int $page + * + * @return array + */ + public function issues($organization, array $params = [], $page = 1) + { + return $this->get('/orgs/'.rawurlencode($organization).'/issues', array_merge(['page' => $page], $params)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Organization/Hooks.php b/vendor/knplabs/github-api/lib/Github/Api/Organization/Hooks.php new file mode 100644 index 00000000..b68a43bc --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Organization/Hooks.php @@ -0,0 +1,111 @@ +get('/orgs/'.rawurlencode($organization).'/hooks'); + } + + /** + * Get a single hook. + * + * @link https://developer.github.com/v3/orgs/hooks/#get-single-hook + * + * @param string $organization + * @param int $id + * + * @return array + */ + public function show($organization, $id) + { + return $this->get('/orgs/'.rawurlencode($organization).'/hooks/'.rawurlencode($id)); + } + + /** + * Create a hook. + * + * @link https://developer.github.com/v3/orgs/hooks/#create-a-hook + * + * @param string $organization + * @param array $params + * + * @throws \Github\Exception\MissingArgumentException + * + * @return array + */ + public function create($organization, array $params) + { + if (!isset($params['name'], $params['config'])) { + throw new MissingArgumentException(['name', 'config']); + } + + return $this->post('/orgs/'.rawurlencode($organization).'/hooks', $params); + } + + /** + * Edit a hook. + * + * @link https://developer.github.com/v3/orgs/hooks/#edit-a-hook + * + * @param string $organization + * @param int $id + * @param array $params + * + * @throws \Github\Exception\MissingArgumentException + * + * @return array + */ + public function update($organization, $id, array $params) + { + if (!isset($params['config'])) { + throw new MissingArgumentException(['config']); + } + + return $this->patch('/orgs/'.rawurlencode($organization).'/hooks/'.rawurlencode($id), $params); + } + + /** + * Ping a hook. + * + * @link https://developer.github.com/v3/orgs/hooks/#ping-a-hook + * + * @param string $organization + * @param int $id + * + * @return null + */ + public function ping($organization, $id) + { + return $this->post('/orgs/'.rawurlencode($organization).'/hooks/'.rawurlencode($id).'/pings'); + } + + /** + * Delete a hook. + * + * @link https://developer.github.com/v3/orgs/hooks/#delete-a-hook + * + * @param string $organization + * @param int $id + * + * @return null + */ + public function remove($organization, $id) + { + return $this->delete('/orgs/'.rawurlencode($organization).'/hooks/'.rawurlencode($id)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Organization/Members.php b/vendor/knplabs/github-api/lib/Github/Api/Organization/Members.php new file mode 100644 index 00000000..3639e3ba --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Organization/Members.php @@ -0,0 +1,75 @@ + + */ +class Members extends AbstractApi +{ + public function all($organization, $type = null, $filter = 'all', $role = null) + { + $parameters = []; + $path = '/orgs/'.rawurlencode($organization).'/'; + if (null === $type) { + $path .= 'members'; + if (null !== $filter) { + $parameters['filter'] = $filter; + } + if (null !== $role) { + $parameters['role'] = $role; + } + } else { + $path .= 'public_members'; + } + + return $this->get($path, $parameters); + } + + public function show($organization, $username) + { + return $this->get('/orgs/'.rawurlencode($organization).'/members/'.rawurlencode($username)); + } + + public function member($organization, $username) + { + return $this->get('/orgs/'.rawurlencode($organization).'/memberships/'.rawurlencode($username)); + } + + public function check($organization, $username) + { + return $this->get('/orgs/'.rawurlencode($organization).'/public_members/'.rawurlencode($username)); + } + + public function publicize($organization, $username) + { + return $this->put('/orgs/'.rawurlencode($organization).'/public_members/'.rawurlencode($username)); + } + + public function conceal($organization, $username) + { + return $this->delete('/orgs/'.rawurlencode($organization).'/public_members/'.rawurlencode($username)); + } + + /* + * Add user to organization + */ + public function add($organization, $username) + { + return $this->put('/orgs/'.rawurlencode($organization).'/memberships/'.rawurlencode($username)); + } + + public function addMember($organization, $username) + { + return $this->add($organization, $username); + } + + public function remove($organization, $username) + { + return $this->delete('/orgs/'.rawurlencode($organization).'/members/'.rawurlencode($username)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Organization/Projects.php b/vendor/knplabs/github-api/lib/Github/Api/Organization/Projects.php new file mode 100644 index 00000000..2bb7196e --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Organization/Projects.php @@ -0,0 +1,23 @@ +get('/orgs/'.rawurlencode($organization).'/projects', array_merge(['page' => 1], $params)); + } + + public function create($organization, array $params) + { + if (!isset($params['name'])) { + throw new MissingArgumentException(['name']); + } + + return $this->post('/orgs/'.rawurlencode($organization).'/projects', $params); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Organization/Teams.php b/vendor/knplabs/github-api/lib/Github/Api/Organization/Teams.php new file mode 100644 index 00000000..401dbe4f --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Organization/Teams.php @@ -0,0 +1,100 @@ + + */ +class Teams extends AbstractApi +{ + public function all($organization) + { + return $this->get('/orgs/'.rawurlencode($organization).'/teams'); + } + + public function create($organization, array $params) + { + if (!isset($params['name'])) { + throw new MissingArgumentException('name'); + } + if (isset($params['repo_names']) && !is_array($params['repo_names'])) { + $params['repo_names'] = [$params['repo_names']]; + } + if (isset($params['permission']) && !in_array($params['permission'], ['pull', 'push', 'admin'])) { + $params['permission'] = 'pull'; + } + + return $this->post('/orgs/'.rawurlencode($organization).'/teams', $params); + } + + public function show($team) + { + return $this->get('/teams/'.rawurlencode($team)); + } + + public function update($team, array $params) + { + if (!isset($params['name'])) { + throw new MissingArgumentException('name'); + } + if (isset($params['permission']) && !in_array($params['permission'], ['pull', 'push', 'admin'])) { + $params['permission'] = 'pull'; + } + + return $this->patch('/teams/'.rawurlencode($team), $params); + } + + public function remove($team) + { + return $this->delete('/teams/'.rawurlencode($team)); + } + + public function members($team) + { + return $this->get('/teams/'.rawurlencode($team).'/members'); + } + + public function check($team, $username) + { + return $this->get('/teams/'.rawurlencode($team).'/memberships/'.rawurlencode($username)); + } + + public function addMember($team, $username) + { + return $this->put('/teams/'.rawurlencode($team).'/memberships/'.rawurlencode($username)); + } + + public function removeMember($team, $username) + { + return $this->delete('/teams/'.rawurlencode($team).'/memberships/'.rawurlencode($username)); + } + + public function repositories($team) + { + return $this->get('/teams/'.rawurlencode($team).'/repos'); + } + + public function repository($team, $organization, $repository) + { + return $this->get('/teams/'.rawurlencode($team).'/repos/'.rawurlencode($organization).'/'.rawurlencode($repository)); + } + + public function addRepository($team, $organization, $repository, $params = []) + { + if (isset($params['permission']) && !in_array($params['permission'], ['pull', 'push', 'admin'])) { + $params['permission'] = 'pull'; + } + + return $this->put('/teams/'.rawurlencode($team).'/repos/'.rawurlencode($organization).'/'.rawurlencode($repository), $params); + } + + public function removeRepository($team, $organization, $repository) + { + return $this->delete('/teams/'.rawurlencode($team).'/repos/'.rawurlencode($organization).'/'.rawurlencode($repository)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Project/AbstractProjectApi.php b/vendor/knplabs/github-api/lib/Github/Api/Project/AbstractProjectApi.php new file mode 100644 index 00000000..15274d7d --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Project/AbstractProjectApi.php @@ -0,0 +1,45 @@ +acceptHeaderValue = 'application/vnd.github.inertia-preview+json'; + + return $this; + } + + public function show($id, array $params = []) + { + return $this->get('/projects/'.rawurlencode($id), array_merge(['page' => 1], $params)); + } + + public function update($id, array $params) + { + return $this->patch('/projects/'.rawurlencode($id), $params); + } + + public function deleteProject($id) + { + return $this->delete('/projects/'.rawurlencode($id)); + } + + public function columns() + { + return new Columns($this->client); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Project/Cards.php b/vendor/knplabs/github-api/lib/Github/Api/Project/Cards.php new file mode 100644 index 00000000..758e7708 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Project/Cards.php @@ -0,0 +1,60 @@ +acceptHeaderValue = 'application/vnd.github.inertia-preview+json'; + + return $this; + } + + public function all($columnId, array $params = []) + { + return $this->get('/projects/columns/'.rawurlencode($columnId).'/cards', array_merge(['page' => 1], $params)); + } + + public function show($id) + { + return $this->get('/projects/columns/cards/'.rawurlencode($id)); + } + + public function create($columnId, array $params) + { + return $this->post('/projects/columns/'.rawurlencode($columnId).'/cards', $params); + } + + public function update($id, array $params) + { + return $this->patch('/projects/columns/cards/'.rawurlencode($id), $params); + } + + public function deleteCard($id) + { + return $this->delete('/projects/columns/cards/'.rawurlencode($id)); + } + + public function move($id, array $params) + { + if (!isset($params['position'])) { + throw new MissingArgumentException(['position']); + } + + return $this->post('/projects/columns/cards/'.rawurlencode($id).'/moves', $params); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Project/Columns.php b/vendor/knplabs/github-api/lib/Github/Api/Project/Columns.php new file mode 100644 index 00000000..22e5cbaf --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Project/Columns.php @@ -0,0 +1,73 @@ +acceptHeaderValue = 'application/vnd.github.inertia-preview+json'; + + return $this; + } + + public function all($projectId, array $params = []) + { + return $this->get('/projects/'.rawurlencode($projectId).'/columns', array_merge(['page' => 1], $params)); + } + + public function show($id) + { + return $this->get('/projects/columns/'.rawurlencode($id)); + } + + public function create($projectId, array $params) + { + if (!isset($params['name'])) { + throw new MissingArgumentException(['name']); + } + + return $this->post('/projects/'.rawurlencode($projectId).'/columns', $params); + } + + public function update($id, array $params) + { + if (!isset($params['name'])) { + throw new MissingArgumentException(['name']); + } + + return $this->patch('/projects/columns/'.rawurlencode($id), $params); + } + + public function deleteColumn($id) + { + return $this->delete('/projects/columns/'.rawurlencode($id)); + } + + public function move($id, array $params) + { + if (!isset($params['position'])) { + throw new MissingArgumentException(['position']); + } + + return $this->post('/projects/columns/'.rawurlencode($id).'/moves', $params); + } + + public function cards() + { + return new Cards($this->client); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/PullRequest.php b/vendor/knplabs/github-api/lib/Github/Api/PullRequest.php new file mode 100644 index 00000000..933dd7b1 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/PullRequest.php @@ -0,0 +1,203 @@ + + */ +class PullRequest extends AbstractApi +{ + use AcceptHeaderTrait; + + /** + * Configure the body type. + * + * @link https://developer.github.com/v3/pulls/#custom-media-types + * + * @param string|null $bodyType + * @param string|null $apiVersion + * + * @return self + */ + public function configure($bodyType = null, $apiVersion = null) + { + if (!in_array($apiVersion, [])) { + $apiVersion = $this->client->getApiVersion(); + } + + if (!in_array($bodyType, ['text', 'html', 'full', 'diff', 'patch'])) { + $bodyType = 'raw'; + } + + if (!in_array($bodyType, ['diff', 'patch'])) { + $bodyType .= '+json'; + } + + $this->acceptHeaderValue = sprintf('application/vnd.github.%s.%s', $apiVersion, $bodyType); + + return $this; + } + + /** + * Get a listing of a project's pull requests by the username, repository and (optionally) state. + * + * @link http://developer.github.com/v3/pulls/ + * + * @param string $username the username + * @param string $repository the repository + * @param array $params a list of extra parameters. + * + * @return array array of pull requests for the project + */ + public function all($username, $repository, array $params = []) + { + $parameters = array_merge([ + 'page' => 1, + 'per_page' => 30, + ], $params); + + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls', $parameters); + } + + /** + * Show all details of a pull request, including the discussions. + * + * @link http://developer.github.com/v3/pulls/ + * + * @param string $username the username + * @param string $repository the repository + * @param int $id the ID of the pull request for which details are retrieved + * + * @return array|string pull request details + */ + public function show($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($id)); + } + + public function commits($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($id).'/commits'); + } + + public function files($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($id).'/files'); + } + + /** + * All statuses which are the statuses of its head branch. + * + * @see http://developer.github.com/v3/pulls/ + * + * @param string $username the username + * @param string $repository the repository + * @param int $id the ID of the pull request for which statuses are retrieved + * + * @return array array of statuses for the project + */ + public function status($username, $repository, $id) + { + $link = $this->show($username, $repository, $id)['_links']['statuses']['href']; + + return $this->get($link); + } + + public function comments() + { + return new Comments($this->client); + } + + public function reviews() + { + return new Review($this->client); + } + + public function reviewRequests() + { + return new ReviewRequest($this->client); + } + + /** + * Create a pull request. + * + * @link http://developer.github.com/v3/pulls/ + * + * @param string $username the username + * @param string $repository the repository + * @param array $params A String of the branch or commit SHA that you want your changes to be pulled to. + * A String of the branch or commit SHA of your changes. Typically this will be a branch. + * If the branch is in a fork of the original repository, specify the username first: + * "my-user:some-branch". The String title of the Pull Request. The String body of + * the Pull Request. The issue number. Used when title and body is not set. + * + * @throws MissingArgumentException + * + * @return array + */ + public function create($username, $repository, array $params) + { + // Two ways to create PR, using issue or title + if (!isset($params['issue']) && !isset($params['title'])) { + throw new MissingArgumentException(['issue', 'title']); + } + + if (!isset($params['base'], $params['head'])) { + throw new MissingArgumentException(['base', 'head']); + } + + // If `issue` is not sent, then `body` must be sent + if (!isset($params['issue']) && !isset($params['body'])) { + throw new MissingArgumentException(['issue', 'body']); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls', $params); + } + + public function update($username, $repository, $id, array $params) + { + if (isset($params['state']) && !in_array($params['state'], ['open', 'closed'])) { + $params['state'] = 'open'; + } + + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($id), $params); + } + + public function merged($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($id).'/merge'); + } + + public function merge($username, $repository, $id, $message, $sha, $mergeMethod = 'merge', $title = null) + { + if (is_bool($mergeMethod)) { + $mergeMethod = $mergeMethod ? 'squash' : 'merge'; + } + + if (!in_array($mergeMethod, ['merge', 'squash', 'rebase'], true)) { + throw new InvalidArgumentException(sprintf('"$mergeMethod" must be one of ["merge", "squash", "rebase"] ("%s" given).', $mergeMethod)); + } + + $params = [ + 'commit_message' => $message, + 'sha' => $sha, + 'merge_method' => $mergeMethod, + ]; + + if (is_string($title)) { + $params['commit_title'] = $title; + } + + return $this->put('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($id).'/merge', $params); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/PullRequest/Comments.php b/vendor/knplabs/github-api/lib/Github/Api/PullRequest/Comments.php new file mode 100644 index 00000000..183bfbe0 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/PullRequest/Comments.php @@ -0,0 +1,153 @@ + + */ +class Comments extends AbstractApi +{ + use AcceptHeaderTrait; + + /** + * Configure the body type. + * + * @link https://developer.github.com/v3/pulls/comments/#custom-media-types + * + * @param string|null $bodyType + * @param string|null @apiVersion + * + * @return self + */ + public function configure($bodyType = null, $apiVersion = null) + { + if (!in_array($apiVersion, ['squirrel-girl-preview'])) { + $apiVersion = $this->client->getApiVersion(); + } + + if (!in_array($bodyType, ['text', 'html', 'full'])) { + $bodyType = 'raw'; + } + + $this->acceptHeaderValue = sprintf('application/vnd.github.%s.%s+json', $apiVersion, $bodyType); + + return $this; + } + + /** + * Get a listing of a pull request's comments by the username, repository and pull request number + * or all repository comments by the username and repository. + * + * @link https://developer.github.com/v3/pulls/comments/#list-comments-on-a-pull-request + * @link https://developer.github.com/v3/pulls/comments/#list-comments-in-a-repository + * + * @param string $username the username + * @param string $repository the repository + * @param int|null $pullRequest the pull request number + * @param array $params a list of extra parameters. + * + * @return array + */ + public function all($username, $repository, $pullRequest = null, array $params = []) + { + if (null !== $pullRequest) { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($pullRequest).'/comments'); + } + + $parameters = array_merge([ + 'page' => 1, + 'per_page' => 30, + ], $params); + + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/comments', $parameters); + } + + /** + * Get a single pull request comment by the username, repository and comment id. + * + * @link https://developer.github.com/v3/pulls/comments/#get-a-single-comment + * + * @param string $username the username + * @param string $repository the repository + * @param int $comment the comment id + * + * @return array + */ + public function show($username, $repository, $comment) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/comments/'.rawurlencode($comment)); + } + + /** + * Create a pull request comment by the username, repository and pull request number. + * + * @link https://developer.github.com/v3/pulls/comments/#create-a-comment + * + * @param string $username the username + * @param string $repository the repository + * @param int $pullRequest the pull request number + * @param array $params a list of extra parameters. + * + * @throws MissingArgumentException + * + * @return array + */ + public function create($username, $repository, $pullRequest, array $params) + { + if (!isset($params['body'])) { + throw new MissingArgumentException('body'); + } + + // If `in_reply_to` is set, other options are not necessary anymore + if (!isset($params['in_reply_to']) && !isset($params['commit_id'], $params['path'], $params['position'])) { + throw new MissingArgumentException(['commit_id', 'path', 'position']); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($pullRequest).'/comments', $params); + } + + /** + * Update a pull request comment by the username, repository and comment id. + * + * @link https://developer.github.com/v3/pulls/comments/#edit-a-comment + * + * @param string $username the username + * @param string $repository the repository + * @param int $comment the comment id + * @param array $params a list of extra parameters. + * + * @throws MissingArgumentException + * + * @return array + */ + public function update($username, $repository, $comment, array $params) + { + if (!isset($params['body'])) { + throw new MissingArgumentException('body'); + } + + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/comments/'.rawurlencode($comment), $params); + } + + /** + * Delete a pull request comment by the username, repository and comment id. + * + * @link https://developer.github.com/v3/pulls/comments/#delete-a-comment + * + * @param string $username the username + * @param string $repository the repository + * @param int $comment the comment id + * + * @return string + */ + public function remove($username, $repository, $comment) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/comments/'.rawurlencode($comment)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/PullRequest/Review.php b/vendor/knplabs/github-api/lib/Github/Api/PullRequest/Review.php new file mode 100644 index 00000000..f9795de0 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/PullRequest/Review.php @@ -0,0 +1,177 @@ + + */ +class Review extends AbstractApi +{ + use AcceptHeaderTrait; + + public function configure() + { + return $this; + } + + /** + * Get a listing of a pull request's reviews by the username, repository and pull request number. + * + * @link https://developer.github.com/v3/pulls/reviews/#list-reviews-on-a-pull-request + * + * @param string $username the username + * @param string $repository the repository + * @param int $pullRequest the pull request number + * @param array $params a list of extra parameters. + * + * @return array array of pull request reviews for the pull request + */ + public function all($username, $repository, $pullRequest, array $params = []) + { + $parameters = array_merge([ + 'page' => 1, + 'per_page' => 30, + ], $params); + + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.$pullRequest.'/reviews', $parameters); + } + + /** + * Get a single pull request review by the username, repository, pull request number and the review id. + * + * @link https://developer.github.com/v3/pulls/reviews/#get-a-single-review + * + * @param string $username the username + * @param string $repository the repository + * @param int $pullRequest the pull request number + * @param int $id the review id + * + * @return array the pull request review + */ + public function show($username, $repository, $pullRequest, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.$pullRequest.'/reviews/'.$id); + } + + /** + * Delete a single pull request review by the username, repository, pull request number and the review id. + * + * @link https://developer.github.com/v3/pulls/reviews/#delete-a-pending-review + * + * @param string $username the username + * @param string $repository the repository + * @param int $pullRequest the pull request number + * @param int $id the review id + * + * @return array|string + */ + public function remove($username, $repository, $pullRequest, $id) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.$pullRequest.'/reviews/'.$id); + } + + /** + * Get comments for a single pull request review. + * + * @link https://developer.github.com/v3/pulls/reviews/#get-comments-for-a-single-review + * + * @param string $username the username + * @param string $repository the repository + * @param int $pullRequest the pull request number + * @param int $id the review id + * + * @return array|string + */ + public function comments($username, $repository, $pullRequest, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($pullRequest).'/reviews/'.rawurlencode($id).'/comments'); + } + + /** + * Create a pull request review by the username, repository and pull request number. + * + * @link https://developer.github.com/v3/pulls/reviews/#create-a-pull-request-review + * + * @param string $username the username + * @param string $repository the repository + * @param int $pullRequest the pull request number + * @param array $params a list of extra parameters. + * + * @throws MissingArgumentException + * + * @return array the pull request review + */ + public function create($username, $repository, $pullRequest, array $params = []) + { + if (array_key_exists('event', $params) && !in_array($params['event'], ['APPROVE', 'REQUEST_CHANGES', 'COMMENT'], true)) { + throw new InvalidArgumentException(sprintf('"event" must be one of ["APPROVE", "REQUEST_CHANGES", "COMMENT"] ("%s" given).', $params['event'])); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.$pullRequest.'/reviews', $params); + } + + /** + * Submit a pull request review by the username, repository, pull request number and the review id. + * + * @link https://developer.github.com/v3/pulls/reviews/#submit-a-pull-request-review + * + * @param string $username the username + * @param string $repository the repository + * @param int $pullRequest the pull request number + * @param int $id the review id + * @param array $params a list of extra parameters. + * + * @throws MissingArgumentException + * + * @return array the pull request review + */ + public function submit($username, $repository, $pullRequest, $id, array $params = []) + { + if (!isset($params['event'])) { + throw new MissingArgumentException('event'); + } + + if (!in_array($params['event'], ['APPROVE', 'REQUEST_CHANGES', 'COMMENT'], true)) { + throw new InvalidArgumentException(sprintf('"event" must be one of ["APPROVE", "REQUEST_CHANGES", "COMMENT"] ("%s" given).', $params['event'])); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.$pullRequest.'/reviews/'.$id.'/events', $params); + } + + /** + * Dismiss a pull request review by the username, repository, pull request number and the review id. + * + * @link https://developer.github.com/v3/pulls/reviews/#dismiss-a-pull-request-review + * + * @param string $username the username + * @param string $repository the repository + * @param int $pullRequest the pull request number + * @param int $id the review id + * @param string $message a mandatory dismissal message + * + * @return array|string + */ + public function dismiss($username, $repository, $pullRequest, $id, $message) + { + if (!is_string($message)) { + throw new InvalidArgumentException(sprintf('"message" must be a valid string ("%s" given).', gettype($message))); + } + + if (empty($message)) { + throw new InvalidArgumentException('"message" is mandatory and cannot be empty'); + } + + return $this->put('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.$pullRequest.'/reviews/'.$id.'/dismissals', [ + 'message' => $message, + ]); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/PullRequest/ReviewRequest.php b/vendor/knplabs/github-api/lib/Github/Api/PullRequest/ReviewRequest.php new file mode 100644 index 00000000..8321fd27 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/PullRequest/ReviewRequest.php @@ -0,0 +1,64 @@ +get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.$pullRequest.'/requested_reviewers', $params); + } + + /** + * @link https://developer.github.com/v3/pulls/review_requests/#create-a-review-request + * + * @param string $username + * @param string $repository + * @param int $pullRequest + * @param array $reviewers + * + * @return string + */ + public function create($username, $repository, $pullRequest, array $reviewers) + { + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.$pullRequest.'/requested_reviewers', ['reviewers' => $reviewers]); + } + + /** + * @link https://developer.github.com/v3/pulls/review_requests/#delete-a-review-request + * + * @param string $username + * @param string $repository + * @param int $pullRequest + * @param array $reviewers + * + * @return string + */ + public function remove($username, $repository, $pullRequest, array $reviewers) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.$pullRequest.'/requested_reviewers', ['reviewers' => $reviewers]); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/RateLimit.php b/vendor/knplabs/github-api/lib/Github/Api/RateLimit.php new file mode 100644 index 00000000..ba5301df --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/RateLimit.php @@ -0,0 +1,47 @@ + + */ +class RateLimit extends AbstractApi +{ + /** + * Get rate limits. + * + * @return array + */ + public function getRateLimits() + { + return $this->get('/rate_limit'); + } + + /** + * Get core rate limit. + * + * @return int + */ + public function getCoreLimit() + { + $response = $this->getRateLimits(); + + return $response['resources']['core']['limit']; + } + + /** + * Get search rate limit. + * + * @return int + */ + public function getSearchLimit() + { + $response = $this->getRateLimits(); + + return $response['resources']['search']['limit']; + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repo.php b/vendor/knplabs/github-api/lib/Github/Api/Repo.php new file mode 100644 index 00000000..c0c54a95 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repo.php @@ -0,0 +1,682 @@ + + * @author Thibault Duplessis + */ +class Repo extends AbstractApi +{ + use AcceptHeaderTrait; + + /** + * Search repositories by keyword. + * + * @deprecated This method is deprecated use the Search api instead. See https://developer.github.com/v3/search/legacy/#legacy-search-api-is-deprecated + * @link http://developer.github.com/v3/search/#search-repositories + * + * @param string $keyword the search query + * @param array $params + * + * @return array list of found repositories + */ + public function find($keyword, array $params = []) + { + return $this->get('/legacy/repos/search/'.rawurlencode($keyword), array_merge(['start_page' => 1], $params)); + } + + /** + * List all public repositories. + * + * @link https://developer.github.com/v3/repos/#list-all-public-repositories + * + * @param int|null $id The integer ID of the last Repository that you’ve seen. + * + * @return array list of users found + */ + public function all($id = null) + { + if (!is_int($id)) { + return $this->get('/repositories'); + } + + return $this->get('/repositories?since='.rawurldecode($id)); + } + + /** + * Get the last year of commit activity for a repository grouped by week. + * + * @link http://developer.github.com/v3/repos/statistics/#commit-activity + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * + * @return array commit activity grouped by week + */ + public function activity($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/stats/commit_activity'); + } + + /** + * Get contributor commit statistics for a repository. + * + * @link http://developer.github.com/v3/repos/statistics/#contributors + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * + * @return array list of contributors and their commit statistics + */ + public function statistics($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/stats/contributors'); + } + + /** + * Get a weekly aggregate of the number of additions and deletions pushed to a repository. + * + * @link http://developer.github.com/v3/repos/statistics/#code-frequency + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * + * @return array list of weeks and their commit statistics + */ + public function frequency($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/stats/code_frequency'); + } + + /** + * Get the weekly commit count for the repository owner and everyone else. + * + * @link http://developer.github.com/v3/repos/statistics/#participation + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * + * @return array list of weekly commit count grouped by 'all' and 'owner' + */ + public function participation($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/stats/participation'); + } + + /** + * List all repositories for an organization. + * + * @link http://developer.github.com/v3/repos/#list-organization-repositories + * + * @param string $organization the name of the organization + * @param array $params + * + * @return array list of organization repositories + */ + public function org($organization, array $params = []) + { + return $this->get('/orgs/'.$organization.'/repos', array_merge(['start_page' => 1], $params)); + } + + /** + * Get extended information about a repository by its username and repository name. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * + * @return array information about the repository + */ + public function show($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository)); + } + + /** + * Get extended information about a repository by its id. + * Note: at time of writing this is an undocumented feature but GitHub support have advised that it can be relied on. + * + * @link http://developer.github.com/v3/repos/ + * @link https://github.com/piotrmurach/github/issues/283 + * @link https://github.com/piotrmurach/github/issues/282 + * + * @param int $id the id of the repository + * + * @return array information about the repository + */ + public function showById($id) + { + return $this->get('/repositories/'.rawurlencode($id)); + } + + /** + * Create repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $name name of the repository + * @param string $description repository description + * @param string $homepage homepage url + * @param bool $public `true` for public, `false` for private + * @param null|string $organization username of organization if applicable + * @param bool $hasIssues `true` to enable issues for this repository, `false` to disable them + * @param bool $hasWiki `true` to enable the wiki for this repository, `false` to disable it + * @param bool $hasDownloads `true` to enable downloads for this repository, `false` to disable them + * @param int $teamId The id of the team that will be granted access to this repository. This is only valid when creating a repo in an organization. + * @param bool $autoInit `true` to create an initial commit with empty README, `false` for no initial commit + * + * @return array returns repository data + */ + public function create( + $name, + $description = '', + $homepage = '', + $public = true, + $organization = null, + $hasIssues = false, + $hasWiki = false, + $hasDownloads = false, + $teamId = null, + $autoInit = false + ) { + $path = null !== $organization ? '/orgs/'.$organization.'/repos' : '/user/repos'; + + $parameters = [ + 'name' => $name, + 'description' => $description, + 'homepage' => $homepage, + 'private' => !$public, + 'has_issues' => $hasIssues, + 'has_wiki' => $hasWiki, + 'has_downloads' => $hasDownloads, + 'auto_init' => $autoInit, + ]; + + if ($organization && $teamId) { + $parameters['team_id'] = $teamId; + } + + return $this->post($path, $parameters); + } + + /** + * Set information of a repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param array $values the key => value pairs to post + * + * @return array information about the repository + */ + public function update($username, $repository, array $values) + { + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository), $values); + } + + /** + * Delete a repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * + * @return mixed null on success, array on error with 'message' + */ + public function remove($username, $repository) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository)); + } + + /** + * Get the readme content for a repository by its username and repository name. + * + * @link http://developer.github.com/v3/repos/contents/#get-the-readme + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param string $format one of formats: "raw", "html", or "v3+json" + * + * @return string|array the readme content + */ + public function readme($username, $repository, $format = 'raw') + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/readme', [], [ + 'Accept' => "application/vnd.github.$format", + ]); + } + + /** + * Manage the collaborators of a repository. + * + * @link http://developer.github.com/v3/repos/collaborators/ + * + * @return Collaborators + */ + public function collaborators() + { + return new Collaborators($this->client); + } + + /** + * Manage the comments of a repository. + * + * @link http://developer.github.com/v3/repos/comments/ + * + * @return Comments + */ + public function comments() + { + return new Comments($this->client); + } + + /** + * Manage the commits of a repository. + * + * @link http://developer.github.com/v3/repos/commits/ + * + * @return Commits + */ + public function commits() + { + return new Commits($this->client); + } + + /** + * Manage the content of a repository. + * + * @link http://developer.github.com/v3/repos/contents/ + * + * @return Contents + */ + public function contents() + { + return new Contents($this->client); + } + + /** + * Manage the content of a repository. + * + * @link http://developer.github.com/v3/repos/downloads/ + * + * @return Downloads + */ + public function downloads() + { + return new Downloads($this->client); + } + + /** + * Manage the releases of a repository (Currently Undocumented). + * + * @link http://developer.github.com/v3/repos/ + * + * @return Releases + */ + public function releases() + { + return new Releases($this->client); + } + + /** + * Manage the deploy keys of a repository. + * + * @link http://developer.github.com/v3/repos/keys/ + * + * @return DeployKeys + */ + public function keys() + { + return new DeployKeys($this->client); + } + + /** + * Manage the forks of a repository. + * + * @link http://developer.github.com/v3/repos/forks/ + * + * @return Forks + */ + public function forks() + { + return new Forks($this->client); + } + + /** + * Manage the stargazers of a repository. + * + * @link https://developer.github.com/v3/activity/starring/#list-stargazers + * + * @return Stargazers + */ + public function stargazers() + { + return new Stargazers($this->client); + } + + /** + * Manage the hooks of a repository. + * + * @link http://developer.github.com/v3/issues/jooks/ + * + * @return Hooks + */ + public function hooks() + { + return new Hooks($this->client); + } + + /** + * Manage the labels of a repository. + * + * @link http://developer.github.com/v3/issues/labels/ + * + * @return Labels + */ + public function labels() + { + return new Labels($this->client); + } + + /** + * Manage the statuses of a repository. + * + * @link http://developer.github.com/v3/repos/statuses/ + * + * @return Statuses + */ + public function statuses() + { + return new Statuses($this->client); + } + + /** + * Get the branch(es) of a repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the username + * @param string $repository the name of the repository + * @param string $branch the name of the branch + * + * @return array list of the repository branches + */ + public function branches($username, $repository, $branch = null) + { + $url = '/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches'; + if (null !== $branch) { + $url .= '/'.rawurlencode($branch); + } + + return $this->get($url); + } + + /** + * Manage the protection of a repository branch. + * + * @link https://developer.github.com/v3/repos/branches/#get-branch-protection + * + * @return Protection + */ + public function protection() + { + return new Protection($this->client); + } + + /** + * Get the contributors of a repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param bool $includingAnonymous by default, the list only shows GitHub users. + * You can include non-users too by setting this to true + * + * @return array list of the repo contributors + */ + public function contributors($username, $repository, $includingAnonymous = false) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/contributors', [ + 'anon' => $includingAnonymous ?: null, + ]); + } + + /** + * Get the language breakdown of a repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * + * @return array list of the languages + */ + public function languages($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/languages'); + } + + /** + * Get the tags of a repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param array $params the additional parameters like milestone, assignees, labels, sort, direction + * + * @return array list of the repository tags + */ + public function tags($username, $repository, array $params = []) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/tags', $params); + } + + /** + * Get the teams of a repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array list of the languages + */ + public function teams($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/teams'); + } + + /** + * @deprecated see subscribers method + * + * @param string $username + * @param string $repository + * @param int $page + * + * @return array + */ + public function watchers($username, $repository, $page = 1) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/watchers', [ + 'page' => $page, + ]); + } + + /** + * @param string $username + * @param string $repository + * @param int $page + * + * @return array + */ + public function subscribers($username, $repository, $page = 1) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/subscribers', [ + 'page' => $page, + ]); + } + + /** + * Perform a merge. + * + * @link http://developer.github.com/v3/repos/merging/ + * + * @param string $username + * @param string $repository + * @param string $base The name of the base branch that the head will be merged into. + * @param string $head The head to merge. This can be a branch name or a commit SHA1. + * @param string $message Commit message to use for the merge commit. If omitted, a default message will be used. + * + * @return array|null + */ + public function merge($username, $repository, $base, $head, $message = null) + { + $parameters = [ + 'base' => $base, + 'head' => $head, + ]; + + if (is_string($message)) { + $parameters['commit_message'] = $message; + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/merges', $parameters); + } + + /** + * @param string $username + * @param string $repository + * + * @return array + */ + public function milestones($username, $repository) + { + return $this->get('/repos/'.rawurldecode($username).'/'.rawurldecode($repository).'/milestones'); + } + + public function projects() + { + return new Projects($this->client); + } + + public function traffic() + { + return new Traffic($this->client); + } + + /** + * @param string $username + * @param string $repository + * @param int $page + * + * @return array|string + * + * @see https://developer.github.com/v3/activity/events/#list-repository-events + */ + public function events($username, $repository, $page = 1) + { + return $this->get('/repos/'.rawurldecode($username).'/'.rawurldecode($repository).'/events', ['page' => $page]); + } + + /** + * Get the contents of a repository's code of conduct. + * + * @link https://developer.github.com/v3/codes_of_conduct/#get-the-contents-of-a-repositorys-code-of-conduct + * + * @param string $username + * @param string $repository + * + * @return array + */ + public function codeOfConduct($username, $repository) + { + //This api is in preview mode, so set the correct accept-header + $this->acceptHeaderValue = 'application/vnd.github.scarlet-witch-preview+json'; + + return $this->get('/repos/'.rawurldecode($username).'/'.rawurldecode($repository).'/community/code_of_conduct'); + } + + /** + * List all topics for a repository. + * + * @link https://developer.github.com/v3/repos/#list-all-topics-for-a-repository + * + * @param string $username + * @param string $repository + * + * @return array + */ + public function topics($username, $repository) + { + //This api is in preview mode, so set the correct accept-header + $this->acceptHeaderValue = 'application/vnd.github.mercy-preview+json'; + + return $this->get('/repos/'.rawurldecode($username).'/'.rawurldecode($repository).'/topics'); + } + + /** + * Replace all topics for a repository. + * + * @link https://developer.github.com/v3/repos/#replace-all-topics-for-a-repository + * + * @param string $username + * @param string $repository + * @param array $topics + * + * @return array + */ + public function replaceTopics($username, $repository, array $topics) + { + //This api is in preview mode, so set the correct accept-header + $this->acceptHeaderValue = 'application/vnd.github.mercy-preview+json'; + + return $this->put('/repos/'.rawurldecode($username).'/'.rawurldecode($repository).'/topics', ['names' => $topics]); + } + + /** + * Transfer a repository. + * + * @link https://developer.github.com/v3/repos/#transfer-a-repository + * + * @param string $username + * @param string $repository + * @param string $newOwner + * @param array $teamId + * + * @return array + */ + public function transfer($username, $repository, $newOwner, $teamId = []) + { + //This api is in preview mode, so set the correct accept-header + $this->acceptHeaderValue = 'application/vnd.github.nightshade-preview+json'; + + return $this->post('/repos/'.rawurldecode($username).'/'.rawurldecode($repository).'/transfer', ['new_owner' => $newOwner, 'team_id' => $teamId]); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Assets.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Assets.php new file mode 100644 index 00000000..dbe6da22 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Assets.php @@ -0,0 +1,118 @@ + + */ +class Assets extends AbstractApi +{ + /** + * Get all release's assets in selected repository + * GET /repos/:owner/:repo/releases/:id/assets. + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the release + * + * @return array + */ + public function all($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/'.rawurlencode($id).'/assets'); + } + + /** + * Get an asset in selected repository's release + * GET /repos/:owner/:repo/releases/assets/:id. + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the asset + * + * @return array + */ + public function show($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/assets/'.rawurlencode($id)); + } + + /** + * Create an asset for selected repository's release + * POST /repos/:owner/:repo/releases/:id/assets?name=:filename. + * + * Creating an asset requires support for server name indentification (SNI) + * so this must be supported by your PHP version. + * + * @see http://developer.github.com/v3/repos/releases/#upload-a-release-asset + * @see http://php.net/manual/en/openssl.constsni.php + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the release + * @param string $name the filename for the asset + * @param string $contentType the content type for the asset + * @param string $content the content of the asset + * + * @throws MissingArgumentException + * @throws ErrorException + * + * @return array + */ + public function create($username, $repository, $id, $name, $contentType, $content) + { + if (!defined('OPENSSL_TLSEXT_SERVER_NAME') || !OPENSSL_TLSEXT_SERVER_NAME) { + throw new ErrorException('Asset upload support requires Server Name Indication. This is not supported by your PHP version. See http://php.net/manual/en/openssl.constsni.php.'); + } + + // Asset creation requires a separate endpoint, uploads.github.com. + // Change the base url for the HTTP client temporarily while we execute + // this request. + $response = $this->postRaw('https://uploads.github.com/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/'.rawurlencode($id).'/assets?name='.$name, $content, ['Content-Type' => $contentType]); + + return $response; + } + + /** + * Edit an asset in selected repository's release + * PATCH /repos/:owner/:repo/releases/assets/:id. + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the asset + * @param array $params request parameters + * + * @throws MissingArgumentException + * + * @return array + */ + public function edit($username, $repository, $id, array $params) + { + if (!isset($params['name'])) { + throw new MissingArgumentException('name'); + } + + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/assets/'.rawurlencode($id), $params); + } + + /** + * Delete an asset in selected repository's release + * DELETE /repos/:owner/:repo/releases/assets/:id. + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the asset + * + * @return array + */ + public function remove($username, $repository, $id) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/assets/'.rawurlencode($id)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Collaborators.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Collaborators.php new file mode 100644 index 00000000..34411884 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Collaborators.php @@ -0,0 +1,84 @@ + + */ +class Collaborators extends AbstractApi +{ + /** + * @link https://developer.github.com/v3/repos/collaborators/#list-collaborators + * + * @param $username + * @param $repository + * @param array $params + * + * @return array|string + */ + public function all($username, $repository, array $params = []) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/collaborators', $params); + } + + /** + * @link https://developer.github.com/v3/repos/collaborators/#check-if-a-user-is-a-collaborator + * + * @param $username + * @param $repository + * @param $collaborator + * + * @return array|string + */ + public function check($username, $repository, $collaborator) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/collaborators/'.rawurlencode($collaborator)); + } + + /** + * @link https://developer.github.com/v3/repos/collaborators/#add-user-as-a-collaborator + * + * @param $username + * @param $repository + * @param $collaborator + * @param array $params + * + * @return array|string + */ + public function add($username, $repository, $collaborator, array $params = []) + { + return $this->put('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/collaborators/'.rawurlencode($collaborator), $params); + } + + /** + * @link https://developer.github.com/v3/repos/collaborators/#remove-user-as-a-collaborator + * + * @param $username + * @param $repository + * @param $collaborator + * + * @return array|string + */ + public function remove($username, $repository, $collaborator) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/collaborators/'.rawurlencode($collaborator)); + } + + /** + * @link https://developer.github.com/v3/repos/collaborators/#review-a-users-permission-level + * + * @param $username + * @param $repository + * @param $collaborator + * + * @return array|string + */ + public function permission($username, $repository, $collaborator) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/collaborators/'.rawurlencode($collaborator).'/permission'); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Comments.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Comments.php new file mode 100644 index 00000000..53feb72c --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Comments.php @@ -0,0 +1,75 @@ + + * @author Tobias Nyholm + */ +class Comments extends AbstractApi +{ + use AcceptHeaderTrait; + + /** + * Configure the body type. + * + * @link https://developer.github.com/v3/repos/comments/#custom-media-types + * + * @param string|null $bodyType + * + * @return self + */ + public function configure($bodyType = null) + { + if (!in_array($bodyType, ['raw', 'text', 'html'])) { + $bodyType = 'full'; + } + + $this->acceptHeaderValue = sprintf('application/vnd.github.%s.%s+json', $this->client->getApiVersion(), $bodyType); + + return $this; + } + + public function all($username, $repository, $sha = null) + { + if (null === $sha) { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/comments'); + } + + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/commits/'.rawurlencode($sha).'/comments'); + } + + public function show($username, $repository, $comment) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/comments/'.rawurlencode($comment)); + } + + public function create($username, $repository, $sha, array $params) + { + if (!isset($params['body'])) { + throw new MissingArgumentException('body'); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/commits/'.rawurlencode($sha).'/comments', $params); + } + + public function update($username, $repository, $comment, array $params) + { + if (!isset($params['body'])) { + throw new MissingArgumentException('body'); + } + + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/comments/'.rawurlencode($comment), $params); + } + + public function remove($username, $repository, $comment) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/comments/'.rawurlencode($comment)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Commits.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Commits.php new file mode 100644 index 00000000..8195e4ba --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Commits.php @@ -0,0 +1,33 @@ + + */ +class Commits extends AbstractApi +{ + public function all($username, $repository, array $params) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/commits', $params); + } + + public function compare($username, $repository, $base, $head, $mediaType = null) + { + $headers = []; + if (null !== $mediaType) { + $headers['Accept'] = $mediaType; + } + + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/compare/'.rawurlencode($base).'...'.rawurlencode($head), [], $headers); + } + + public function show($username, $repository, $sha) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/commits/'.rawurlencode($sha)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Contents.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Contents.php new file mode 100644 index 00000000..64346e62 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Contents.php @@ -0,0 +1,297 @@ + + */ +class Contents extends AbstractApi +{ + use AcceptHeaderTrait; + + /** + * Configure the body type. + * + * @link https://developer.github.com/v3/repo/contents/#custom-media-types + * + * @param string|null $bodyType + * + * @return self + */ + public function configure($bodyType = null) + { + if (!in_array($bodyType, ['html', 'object'])) { + $bodyType = 'raw'; + } + + $this->acceptHeaderValue = sprintf('application/vnd.github.%s.%s', $this->client->getApiVersion(), $bodyType); + + return $this; + } + + /** + * Get content of README file in a repository. + * + * @link http://developer.github.com/v3/repos/contents/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param null|string $reference reference to a branch or commit + * + * @return array information for README file + */ + public function readme($username, $repository, $reference = null) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/readme', [ + 'ref' => $reference, + ]); + } + + /** + * Get contents of any file or directory in a repository. + * + * @link http://developer.github.com/v3/repos/contents/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param null|string $path path to file or directory + * @param null|string $reference reference to a branch or commit + * + * @return array|string information for file | information for each item in directory + */ + public function show($username, $repository, $path = null, $reference = null) + { + $url = '/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/contents'; + if (null !== $path) { + $url .= '/'.rawurlencode($path); + } + + return $this->get($url, [ + 'ref' => $reference, + ]); + } + + /** + * Creates a new file in a repository. + * + * @link http://developer.github.com/v3/repos/contents/#create-a-file + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param string $path path to file + * @param string $content contents of the new file + * @param string $message the commit message + * @param null|string $branch name of a branch + * @param null|array $committer information about the committer + * + * @throws MissingArgumentException + * + * @return array information about the new file + */ + public function create($username, $repository, $path, $content, $message, $branch = null, array $committer = null) + { + $url = '/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/contents/'.rawurlencode($path); + + $parameters = [ + 'content' => base64_encode($content), + 'message' => $message, + ]; + + if (null !== $branch) { + $parameters['branch'] = $branch; + } + + if (null !== $committer) { + if (!isset($committer['name'], $committer['email'])) { + throw new MissingArgumentException(['name', 'email']); + } + $parameters['committer'] = $committer; + } + + return $this->put($url, $parameters); + } + + /** + * Checks that a given path exists in a repository. + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param string $path path of file to check + * @param null|string $reference reference to a branch or commit + * + * @return bool + */ + public function exists($username, $repository, $path, $reference = null) + { + $url = '/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/contents'; + + if (null !== $path) { + $url .= '/'.rawurlencode($path); + } + + try { + $response = $this->head($url, [ + 'ref' => $reference, + ]); + + if ($response->getStatusCode() != 200) { + return false; + } + } catch (TwoFactorAuthenticationRequiredException $ex) { + throw $ex; + } catch (\Exception $ex) { + return false; + } + + return true; + } + + /** + * Updates the contents of a file in a repository. + * + * @link http://developer.github.com/v3/repos/contents/#update-a-file + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param string $path path to file + * @param string $content contents of the new file + * @param string $message the commit message + * @param string $sha blob SHA of the file being replaced + * @param null|string $branch name of a branch + * @param null|array $committer information about the committer + * + * @throws MissingArgumentException + * + * @return array information about the updated file + */ + public function update($username, $repository, $path, $content, $message, $sha, $branch = null, array $committer = null) + { + $url = '/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/contents/'.rawurlencode($path); + + $parameters = [ + 'content' => base64_encode($content), + 'message' => $message, + 'sha' => $sha, + ]; + + if (null !== $branch) { + $parameters['branch'] = $branch; + } + + if (null !== $committer) { + if (!isset($committer['name'], $committer['email'])) { + throw new MissingArgumentException(['name', 'email']); + } + $parameters['committer'] = $committer; + } + + return $this->put($url, $parameters); + } + + /** + * Deletes a file from a repository. + * + * @link http://developer.github.com/v3/repos/contents/#delete-a-file + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param string $path path to file + * @param string $message the commit message + * @param string $sha blob SHA of the file being deleted + * @param null|string $branch name of a branch + * @param null|array $committer information about the committer + * + * @throws MissingArgumentException + * + * @return array information about the updated file + */ + public function rm($username, $repository, $path, $message, $sha, $branch = null, array $committer = null) + { + $url = '/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/contents/'.rawurlencode($path); + + $parameters = [ + 'message' => $message, + 'sha' => $sha, + ]; + + if (null !== $branch) { + $parameters['branch'] = $branch; + } + + if (null !== $committer) { + if (!isset($committer['name'], $committer['email'])) { + throw new MissingArgumentException(['name', 'email']); + } + $parameters['committer'] = $committer; + } + + return $this->delete($url, $parameters); + } + + /** + * Get content of archives in a repository. + * + * @link http://developer.github.com/v3/repos/contents/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param string $format format of archive: tarball or zipball + * @param null|string $reference reference to a branch or commit + * + * @return string repository archive binary data + */ + public function archive($username, $repository, $format, $reference = null) + { + if (!in_array($format, ['tarball', 'zipball'])) { + $format = 'tarball'; + } + + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/'.rawurlencode($format). + ((null !== $reference) ? ('/'.rawurlencode($reference)) : '')); + } + + /** + * Get the contents of a file in a repository. + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param string $path path to file + * @param null|string $reference reference to a branch or commit + * + * @throws InvalidArgumentException If $path is not a file or if its encoding is different from base64 + * @throws ErrorException If $path doesn't include a 'content' index + * + * @return null|string content of file, or null in case of base64_decode failure + */ + public function download($username, $repository, $path, $reference = null) + { + $file = $this->show($username, $repository, $path, $reference); + + if (!isset($file['type']) || 'file' !== $file['type']) { + throw new InvalidArgumentException(sprintf('Path "%s" is not a file.', $path)); + } + + if (!isset($file['content'])) { + throw new ErrorException(sprintf('Unable to access "content" for file "%s" (possible keys: "%s").', $path, implode(', ', array_keys($file)))); + } + + if (!isset($file['encoding'])) { + throw new InvalidArgumentException(sprintf('Can\'t decode content of file "%s", as no encoding is defined.', $path)); + } + + if ('base64' !== $file['encoding']) { + throw new InvalidArgumentException(sprintf('Encoding "%s" of file "%s" is not supported.', $file['encoding'], $path)); + } + + return base64_decode($file['content']) ?: null; + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/DeployKeys.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/DeployKeys.php new file mode 100644 index 00000000..c6c8a2ce --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/DeployKeys.php @@ -0,0 +1,47 @@ + + */ +class DeployKeys extends AbstractApi +{ + public function all($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/keys'); + } + + public function show($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/keys/'.rawurlencode($id)); + } + + public function create($username, $repository, array $params) + { + if (!isset($params['title'], $params['key'])) { + throw new MissingArgumentException(['title', 'key']); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/keys', $params); + } + + public function update($username, $repository, $id, array $params) + { + if (!isset($params['title'], $params['key'])) { + throw new MissingArgumentException(['title', 'key']); + } + + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/keys/'.rawurlencode($id), $params); + } + + public function remove($username, $repository, $id) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/keys/'.rawurlencode($id)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Downloads.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Downloads.php new file mode 100644 index 00000000..ed4c42f2 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Downloads.php @@ -0,0 +1,60 @@ + + */ +class Downloads extends AbstractApi +{ + /** + * List downloads in selected repository. + * + * @link http://developer.github.com/v3/repos/downloads/#list-downloads-for-a-repository + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function all($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/downloads'); + } + + /** + * Get a download in selected repository. + * + * @link http://developer.github.com/v3/repos/downloads/#get-a-single-download + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the download file + * + * @return array + */ + public function show($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/downloads/'.rawurlencode($id)); + } + + /** + * Delete a download in selected repository. + * + * @link http://developer.github.com/v3/repos/downloads/#delete-a-download + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the download file + * + * @return array + */ + public function remove($username, $repository, $id) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/downloads/'.rawurlencode($id)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Forks.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Forks.php new file mode 100644 index 00000000..961dc649 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Forks.php @@ -0,0 +1,27 @@ + + */ +class Forks extends AbstractApi +{ + public function all($username, $repository, array $params = []) + { + if (isset($params['sort']) && !in_array($params['sort'], ['newest', 'oldest', 'watchers'])) { + $params['sort'] = 'newest'; + } + + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/forks', array_merge(['page' => 1], $params)); + } + + public function create($username, $repository, array $params = []) + { + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/forks', $params); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Hooks.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Hooks.php new file mode 100644 index 00000000..db67f402 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Hooks.php @@ -0,0 +1,57 @@ + + */ +class Hooks extends AbstractApi +{ + public function all($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/hooks'); + } + + public function show($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/hooks/'.rawurlencode($id)); + } + + public function create($username, $repository, array $params) + { + if (!isset($params['name'], $params['config'])) { + throw new MissingArgumentException(['name', 'config']); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/hooks', $params); + } + + public function update($username, $repository, $id, array $params) + { + if (!isset($params['config'])) { + throw new MissingArgumentException(['config']); + } + + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/hooks/'.rawurlencode($id), $params); + } + + public function ping($username, $repository, $id) + { + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/hooks/'.rawurlencode($id).'/pings'); + } + + public function test($username, $repository, $id) + { + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/hooks/'.rawurlencode($id).'/test'); + } + + public function remove($username, $repository, $id) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/hooks/'.rawurlencode($id)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Labels.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Labels.php new file mode 100644 index 00000000..7829e551 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Labels.php @@ -0,0 +1,47 @@ + + */ +class Labels extends AbstractApi +{ + public function all($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels'); + } + + public function show($username, $repository, $label) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels/'.rawurlencode($label)); + } + + public function create($username, $repository, array $params) + { + if (!isset($params['name'], $params['color'])) { + throw new MissingArgumentException(['name', 'color']); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels', $params); + } + + public function update($username, $repository, $label, array $params) + { + if (!isset($params['name'], $params['color'])) { + throw new MissingArgumentException(['name', 'color']); + } + + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels/'.rawurlencode($label), $params); + } + + public function remove($username, $repository, $label) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels/'.rawurlencode($label)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Projects.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Projects.php new file mode 100644 index 00000000..9db29f55 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Projects.php @@ -0,0 +1,23 @@ +get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/projects', array_merge(['page' => 1], $params)); + } + + public function create($username, $repository, array $params) + { + if (!isset($params['name'])) { + throw new MissingArgumentException(['name']); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/projects', $params); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Protection.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Protection.php new file mode 100644 index 00000000..73d1e6b5 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Protection.php @@ -0,0 +1,441 @@ + + */ +class Protection extends AbstractApi +{ + use AcceptHeaderTrait; + + public function configure() + { + $this->acceptHeaderValue = 'application/vnd.github.loki-preview+json'; + + return $this; + } + + /** + * Retrieves configured protection for the provided branch. + * + * @link https://developer.github.com/v3/repos/branches/#get-branch-protection + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * + * @return array The branch protection information + */ + public function show($username, $repository, $branch) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection'); + } + + /** + * Updates the repo's branch protection. + * + * @link https://developer.github.com/v3/repos/branches/#update-branch-protection + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * @param array $params The branch protection information + * + * @return array The updated branch protection information + */ + public function update($username, $repository, $branch, array $params = []) + { + return $this->put('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection', $params); + } + + /** + * Remove the repo's branch protection. + * + * @link https://developer.github.com/v3/repos/branches/#remove-branch-protection + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + */ + public function remove($username, $repository, $branch) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection'); + } + + /** + * Get required status checks of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#get-required-status-checks-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * + * @return array The required status checks information + */ + public function showStatusChecks($username, $repository, $branch) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/required_status_checks'); + } + + /** + * Update required status checks of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#update-required-status-checks-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * @param array $params The branch status checks information + * + * @return array The updated branch status checks information + */ + public function updateStatusChecks($username, $repository, $branch, array $params = []) + { + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/required_status_checks', $params); + } + + /** + * Remove required status checks of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#remove-required-status-checks-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + */ + public function removeStatusChecks($username, $repository, $branch) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/required_status_checks'); + } + + /** + * List required status checks contexts of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#list-required-status-checks-contexts-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * + * @return array The required status checks contexts information + */ + public function showStatusChecksContexts($username, $repository, $branch) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/required_status_checks/contexts'); + } + + /** + * Replace required status checks contexts of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#replace-required-status-checks-contexts-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * @param array $params The branch status checks contexts information + * + * @return array The new branch status checks contexts information + */ + public function replaceStatusChecksContexts($username, $repository, $branch, array $params = []) + { + return $this->put('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/required_status_checks/contexts', $params); + } + + /** + * Add required status checks contexts of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#add-required-status-checks-contexts-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * @param array $params The branch status checks contexts information + * + * @return array The updated branch status checks contexts information + */ + public function addStatusChecksContexts($username, $repository, $branch, array $params = []) + { + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/required_status_checks/contexts', $params); + } + + /** + * Remove required status checks contexts of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#remove-required-status-checks-contexts-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * @param array $params The branch status checks contexts information + * + * @return array The updated branch status checks contexts information + */ + public function removeStatusChecksContexts($username, $repository, $branch, array $params = []) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/required_status_checks/contexts', $params); + } + + /** + * Get pull request review enforcement of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#get-pull-request-review-enforcement-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * + * @return array The pull request review enforcement information + */ + public function showPullRequestReviewEnforcement($username, $repository, $branch) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/required_pull_request_reviews'); + } + + /** + * Update pull request review enforcement of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#update-pull-request-review-enforcement-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * @param array $params The branch status checks information + * + * @return array The updated branch status checks information + */ + public function updatePullRequestReviewEnforcement($username, $repository, $branch, array $params = []) + { + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/required_pull_request_reviews', $params); + } + + /** + * Remove pull request review enforcement of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#remove-pull-request-review-enforcement-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + */ + public function removePullRequestReviewEnforcement($username, $repository, $branch) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/required_pull_request_reviews'); + } + + /** + * Get admin enforcement of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#get-admin-enforcement-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * + * @return array The admin enforcement information + */ + public function showAdminEnforcement($username, $repository, $branch) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/enforce_admins'); + } + + /** + * Add admin enforcement of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#add-admin-enforcement-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * + * @return array The updated admin enforcement information + */ + public function addAdminEnforcement($username, $repository, $branch) + { + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/enforce_admins'); + } + + /** + * Remove admin enforcement of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#remove-admin-enforcement-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + */ + public function removeAdminEnforcement($username, $repository, $branch) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/enforce_admins'); + } + + /** + * Get restrictions of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#get-restrictions-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * + * @return array The branch restrictions information + */ + public function showRestrictions($username, $repository, $branch) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/restrictions'); + } + + /** + * Remove restrictions of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#remove-restrictions-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + */ + public function removeRestrictions($username, $repository, $branch) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/restrictions'); + } + + /** + * List team restrictions of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#list-team-restrictions-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * + * @return array The branch team restrictions information + */ + public function showTeamRestrictions($username, $repository, $branch) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/restrictions/teams'); + } + + /** + * Replace team restrictions of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#replace-team-restrictions-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * @param array $params The list of team slugs with push access + * + * @return array The new branch team restrictions information + */ + public function replaceTeamRestrictions($username, $repository, $branch, array $params = []) + { + return $this->put('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/restrictions/teams', $params); + } + + /** + * Add team restrictions of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#add-team-restrictions-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * @param array $params The list of team slugs with push access + * + * @return array The branch team restrictions information + */ + public function addTeamRestrictions($username, $repository, $branch, array $params = []) + { + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/restrictions/teams', $params); + } + + /** + * Remove team restrictions of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#remove-team-restrictions-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * @param array $params The list of team slugs with push access + * + * @return array The updated branch team restrictions information + */ + public function removeTeamRestrictions($username, $repository, $branch, array $params = []) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/restrictions/teams', $params); + } + + /** + * List user restrictions of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#list-user-restrictions-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * + * @return array The branch user restrictions information + */ + public function showUserRestrictions($username, $repository, $branch) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/restrictions/users'); + } + + /** + * Replace user restrictions of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#replace-user-restrictions-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * @param array $params The list of user logins with push access + * + * @return array The new branch user restrictions information + */ + public function replaceUserRestrictions($username, $repository, $branch, array $params = []) + { + return $this->put('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/restrictions/users', $params); + } + + /** + * Add user restrictions of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#add-user-restrictions-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * @param array $params The list of user logins with push access + * + * @return array The branch user restrictions information + */ + public function addUserRestrictions($username, $repository, $branch, array $params = []) + { + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/restrictions/users', $params); + } + + /** + * Remove user restrictions of protected branch. + * + * @link https://developer.github.com/v3/repos/branches/#remove-user-restrictions-of-protected-branch + * + * @param string $username The user who owns the repository + * @param string $repository The name of the repo + * @param string $branch The name of the branch + * @param array $params The list of user logins with push access + * + * @return array The updated branch user restrictions information + */ + public function removeUserRestrictions($username, $repository, $branch, array $params = []) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches/'.rawurlencode($branch).'/protection/restrictions/users', $params); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Releases.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Releases.php new file mode 100644 index 00000000..73fd080b --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Releases.php @@ -0,0 +1,127 @@ + + * @author Evgeniy Guseletov + */ +class Releases extends AbstractApi +{ + /** + * Get the latest release. + * + * @param $username + * @param $repository + * + * @return array + */ + public function latest($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/latest'); + } + + /** + * List releases for a tag. + * + * @param $username + * @param $repository + * @param $tag + * + * @return array + */ + public function tag($username, $repository, $tag) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/tags/'.rawurlencode($tag)); + } + + /** + * List releases in selected repository. + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param array $params the additional parameters like milestone, assignees, labels, sort, direction + * + * @return array + */ + public function all($username, $repository, array $params = []) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases', $params); + } + + /** + * Get a release in selected repository. + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the release + * + * @return array + */ + public function show($username, $repository, $id) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/'.rawurlencode($id)); + } + + /** + * Create new release in selected repository. + * + * @param string $username + * @param string $repository + * @param array $params + * + * @throws MissingArgumentException + * + * @return array + */ + public function create($username, $repository, array $params) + { + if (!isset($params['tag_name'])) { + throw new MissingArgumentException('tag_name'); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases', $params); + } + + /** + * Edit release in selected repository. + * + * @param string $username + * @param string $repository + * @param int $id + * @param array $params + * + * @return array + */ + public function edit($username, $repository, $id, array $params) + { + return $this->patch('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/'.rawurlencode($id), $params); + } + + /** + * Delete a release in selected repository (Not thoroughly tested!). + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the release + * + * @return array + */ + public function remove($username, $repository, $id) + { + return $this->delete('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/'.rawurlencode($id)); + } + + /** + * @return Assets + */ + public function assets() + { + return new Assets($this->client); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Stargazers.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Stargazers.php new file mode 100644 index 00000000..e7177774 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Stargazers.php @@ -0,0 +1,40 @@ + + * @author Tobias Nyholm + */ +class Stargazers extends AbstractApi +{ + use AcceptHeaderTrait; + + /** + * Configure the body type. + * + * @see https://developer.github.com/v3/activity/starring/#alternative-response-with-star-creation-timestamps + * + * @param string $bodyType + * + * @return self + */ + public function configure($bodyType = null) + { + if ('star' === $bodyType) { + $this->acceptHeaderValue = sprintf('application/vnd.github.%s.star+json', $this->client->getApiVersion()); + } + + return $this; + } + + public function all($username, $repository) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/stargazers'); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Statuses.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Statuses.php new file mode 100644 index 00000000..c62f4edd --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Statuses.php @@ -0,0 +1,63 @@ + + */ +class Statuses extends AbstractApi +{ + /** + * @link http://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-sha + * + * @param string $username + * @param string $repository + * @param string $sha + * + * @return array + */ + public function show($username, $repository, $sha) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/commits/'.rawurlencode($sha).'/statuses'); + } + + /** + * @link https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref + * + * @param string $username + * @param string $repository + * @param string $sha + * + * @return array + */ + public function combined($username, $repository, $sha) + { + return $this->get('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/commits/'.rawurlencode($sha).'/status'); + } + + /** + * @link http://developer.github.com/v3/repos/statuses/#create-a-status + * + * @param string $username + * @param string $repository + * @param string $sha + * @param array $params + * + * @throws MissingArgumentException + * + * @return array + */ + public function create($username, $repository, $sha, array $params = []) + { + if (!isset($params['state'])) { + throw new MissingArgumentException('state'); + } + + return $this->post('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/statuses/'.rawurlencode($sha), $params); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Repository/Traffic.php b/vendor/knplabs/github-api/lib/Github/Api/Repository/Traffic.php new file mode 100644 index 00000000..d5b550c8 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Repository/Traffic.php @@ -0,0 +1,67 @@ + + */ +class Traffic extends AbstractApi +{ + /** + * @link https://developer.github.com/v3/repos/traffic/#list-referrers + * + * @param string $owner + * @param string $repository + * + * @return array + */ + public function referers($owner, $repository) + { + return $this->get('/repos/'.rawurlencode($owner).'/'.rawurlencode($repository).'/traffic/popular/referrers'); + } + + /** + * @link https://developer.github.com/v3/repos/traffic/#list-paths + * + * @param string $owner + * @param string $repository + * + * @return array + */ + public function paths($owner, $repository) + { + return $this->get('/repos/'.rawurlencode($owner).'/'.rawurlencode($repository).'/traffic/popular/paths'); + } + + /** + * @link https://developer.github.com/v3/repos/traffic/#views + * + * @param string $owner + * @param string $repository + * @param string $per + * + * @return array + */ + public function views($owner, $repository, $per = 'day') + { + return $this->get('/repos/'.rawurlencode($owner).'/'.rawurlencode($repository).'/traffic/views?per='.rawurlencode($per)); + } + + /** + * @link https://developer.github.com/v3/repos/traffic/#clones + * + * @param string $owner + * @param string $repository + * @param string $per + * + * @return array + */ + public function clones($owner, $repository, $per = 'day') + { + return $this->get('/repos/'.rawurlencode($owner).'/'.rawurlencode($repository).'/traffic/clones?per='.rawurlencode($per)); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/Search.php b/vendor/knplabs/github-api/lib/Github/Api/Search.php new file mode 100644 index 00000000..84626ae3 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/Search.php @@ -0,0 +1,115 @@ + + */ +class Search extends AbstractApi +{ + use AcceptHeaderTrait; + + /** + * Search repositories by filter (q). + * + * @link https://developer.github.com/v3/search/#search-repositories + * + * @param string $q the filter + * @param string $sort the sort field + * @param string $order asc/desc + * + * @return array list of repositories found + */ + public function repositories($q, $sort = 'updated', $order = 'desc') + { + return $this->get('/search/repositories', ['q' => $q, 'sort' => $sort, 'order' => $order]); + } + + /** + * Search issues by filter (q). + * + * @link https://developer.github.com/v3/search/#search-issues + * + * @param string $q the filter + * @param string $sort the sort field + * @param string $order asc/desc + * + * @return array list of issues found + */ + public function issues($q, $sort = 'updated', $order = 'desc') + { + return $this->get('/search/issues', ['q' => $q, 'sort' => $sort, 'order' => $order]); + } + + /** + * Search code by filter (q). + * + * @link https://developer.github.com/v3/search/#search-code + * + * @param string $q the filter + * @param string $sort the sort field + * @param string $order asc/desc + * + * @return array list of code found + */ + public function code($q, $sort = 'updated', $order = 'desc') + { + return $this->get('/search/code', ['q' => $q, 'sort' => $sort, 'order' => $order]); + } + + /** + * Search users by filter (q). + * + * @link https://developer.github.com/v3/search/#search-users + * + * @param string $q the filter + * @param string $sort the sort field + * @param string $order asc/desc + * + * @return array list of users found + */ + public function users($q, $sort = 'updated', $order = 'desc') + { + return $this->get('/search/users', ['q' => $q, 'sort' => $sort, 'order' => $order]); + } + + /** + * Search commits by filter (q). + * + * @link https://developer.github.com/v3/search/#search-commits + * + * @param string $q the filter + * @param string $sort the sort field + * @param string $order sort order. asc/desc + * + * @return array + */ + public function commits($q, $sort = null, $order = 'desc') + { + //This api is in preview mode, so set the correct accept-header + $this->acceptHeaderValue = 'application/vnd.github.cloak-preview'; + + return $this->get('/search/commits', ['q' => $q, 'sort' => $sort, 'order' => $order]); + } + + /** + * Search commits by filter (q). + * + * @link https://developer.github.com/v3/search/#search-topics + * + * @param string $q the filter + * + * @return array + */ + public function topics($q) + { + //This api is in preview mode, so set the correct accept-header + $this->acceptHeaderValue = 'application/vnd.github.mercy-preview+json'; + + return $this->get('/search/topics', ['q' => $q]); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Api/User.php b/vendor/knplabs/github-api/lib/Github/Api/User.php new file mode 100644 index 00000000..c4414c56 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Api/User.php @@ -0,0 +1,251 @@ + + * @author Thibault Duplessis + */ +class User extends AbstractApi +{ + /** + * Search users by username. + * + * @deprecated This method is deprecated use the Search api instead. See https://developer.github.com/v3/search/legacy/#legacy-search-api-is-deprecated + * @link http://developer.github.com/v3/search/#search-users + * + * @param string $keyword the keyword to search + * + * @return array list of users found + */ + public function find($keyword) + { + return $this->get('/legacy/user/search/'.rawurlencode($keyword)); + } + + /** + * Request all users. + * + * @link https://developer.github.com/v3/users/#get-all-users + * + * @param int|null $id ID of the last user that you've seen + * + * @return array list of users found + */ + public function all($id = null) + { + if (!is_int($id)) { + return $this->get('/users'); + } + + return $this->get('/users', ['since' => rawurldecode($id)]); + } + + /** + * Get extended information about a user by its username. + * + * @link http://developer.github.com/v3/users/ + * + * @param string $username the username to show + * + * @return array information about the user + */ + public function show($username) + { + return $this->get('/users/'.rawurlencode($username)); + } + + /** + * Get extended information about a user by its username. + * + * @link https://developer.github.com/v3/orgs/ + * + * @param string $username the username to show + * + * @return array information about organizations that user belongs to + */ + public function organizations($username) + { + return $this->get('/users/'.rawurlencode($username).'/orgs'); + } + + /** + * Get user organizations. + * + * @link https://developer.github.com/v3/orgs/#list-your-organizations + * + * @return array information about organizations that authenticated user belongs to + */ + public function orgs() + { + return $this->get('/user/orgs'); + } + + /** + * Request the users that a specific user is following. + * + * @link http://developer.github.com/v3/users/followers/ + * + * @param string $username the username + * @param array $parameters parameters for the query string + * @param array $requestHeaders additional headers to set in the request + * + * @return array list of followed users + */ + public function following($username, array $parameters = [], array $requestHeaders = []) + { + return $this->get('/users/'.rawurlencode($username).'/following', $parameters, $requestHeaders); + } + + /** + * Request the users following a specific user. + * + * @link http://developer.github.com/v3/users/followers/ + * + * @param string $username the username + * @param array $parameters parameters for the query string + * @param array $requestHeaders additional headers to set in the request + * + * @return array list of following users + */ + public function followers($username, array $parameters = [], array $requestHeaders = []) + { + return $this->get('/users/'.rawurlencode($username).'/followers', $parameters, $requestHeaders); + } + + /** + * Request the repository that a specific user is watching. + * + * @deprecated see subscriptions method + * + * @param string $username the username + * + * @return array list of watched repositories + */ + public function watched($username) + { + return $this->get('/users/'.rawurlencode($username).'/watched'); + } + + /** + * Request starred repositories that a specific user has starred. + * + * @link http://developer.github.com/v3/activity/starring/ + * + * @param string $username the username + * @param int $page the page number of the paginated result set + * @param int $perPage the number of results per page + * @param string $sort sort by (possible values: created, updated) + * @param string $direction direction of sort (possible values: asc, desc) + * + * @return array list of starred repositories + */ + public function starred($username, $page = 1, $perPage = 30, $sort = 'created', $direction = 'desc') + { + return $this->get('/users/'.rawurlencode($username).'/starred', [ + 'page' => $page, + 'per_page' => $perPage, + 'sort' => $sort, + 'direction' => $direction, + ]); + } + + /** + * Request the repository that a specific user is watching. + * + * @link http://developer.github.com/v3/activity/watching/ + * + * @param string $username the username + * + * @return array list of watched repositories + */ + public function subscriptions($username) + { + return $this->get('/users/'.rawurlencode($username).'/subscriptions'); + } + + /** + * List public repositories for the specified user. + * + * @link https://developer.github.com/v3/repos/#list-user-repositories + * + * @param string $username the username + * @param string $type role in the repository + * @param string $sort sort by + * @param string $direction direction of sort, asc or desc + * @param string $visibility visibility of repository + * @param string $affiliation relationship to repository + * + * @return array list of the user repositories + */ + public function repositories($username, $type = 'owner', $sort = 'full_name', $direction = 'asc', $visibility = 'all', $affiliation = 'owner,collaborator,organization_member') + { + return $this->get('/users/'.rawurlencode($username).'/repos', [ + 'type' => $type, + 'sort' => $sort, + 'direction' => $direction, + 'visibility' => $visibility, + 'affiliation' => $affiliation, + ]); + } + + /** + * List repositories that are accessible to the authenticated user. + * + * @link https://developer.github.com/v3/repos/#list-your-repositories + * + * @param array $params visibility, affiliation, type, sort, direction + * + * @return array list of the user repositories + */ + public function myRepositories(array $params = []) + { + return $this->get('/user/repos', $params); + } + + /** + * Get the public gists for a user. + * + * @link http://developer.github.com/v3/gists/ + * + * @param string $username the username + * + * @return array list of the user gists + */ + public function gists($username) + { + return $this->get('/users/'.rawurlencode($username).'/gists'); + } + + /** + * Get the public keys for a user. + * + * @link http://developer.github.com/v3/users/keys/#list-public-keys-for-a-user + * + * @param string $username the username + * + * @return array list of the user public keys + */ + public function keys($username) + { + return $this->get('/users/'.rawurlencode($username).'/keys'); + } + + /** + * List events performed by a user. + * + * @link http://developer.github.com/v3/activity/events/#list-public-events-performed-by-a-user + * + * @param string $username + * + * @return array + */ + public function publicEvents($username) + { + return $this->get('/users/'.rawurlencode($username).'/events/public'); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Client.php b/vendor/knplabs/github-api/lib/Github/Client.php new file mode 100644 index 00000000..8e2ea275 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Client.php @@ -0,0 +1,414 @@ + + * + * Website: http://github.com/KnpLabs/php-github-api + */ +class Client +{ + /** + * Constant for authentication method. Indicates the default, but deprecated + * login with username and token in URL. + */ + const AUTH_URL_TOKEN = 'url_token'; + + /** + * Constant for authentication method. Not indicates the new login, but allows + * usage of unauthenticated rate limited requests for given client_id + client_secret. + */ + const AUTH_URL_CLIENT_ID = 'url_client_id'; + + /** + * Constant for authentication method. Indicates the new favored login method + * with username and password via HTTP Authentication. + */ + const AUTH_HTTP_PASSWORD = 'http_password'; + + /** + * Constant for authentication method. Indicates the new login method with + * with username and token via HTTP Authentication. + */ + const AUTH_HTTP_TOKEN = 'http_token'; + + /** + * Constant for authentication method. Indicates JSON Web Token + * authentication required for integration access to the API. + */ + const AUTH_JWT = 'jwt'; + + /** + * @var string + */ + private $apiVersion; + + /** + * @var Builder + */ + private $httpClientBuilder; + + /** + * @var History + */ + private $responseHistory; + + /** + * Instantiate a new GitHub client. + * + * @param Builder|null $httpClientBuilder + * @param string|null $apiVersion + * @param string|null $enterpriseUrl + */ + public function __construct(Builder $httpClientBuilder = null, $apiVersion = null, $enterpriseUrl = null) + { + $this->responseHistory = new History(); + $this->httpClientBuilder = $builder = $httpClientBuilder ?: new Builder(); + + $builder->addPlugin(new GithubExceptionThrower()); + $builder->addPlugin(new Plugin\HistoryPlugin($this->responseHistory)); + $builder->addPlugin(new Plugin\RedirectPlugin()); + $builder->addPlugin(new Plugin\AddHostPlugin(UriFactoryDiscovery::find()->createUri('https://api.github.com'))); + $builder->addPlugin(new Plugin\HeaderDefaultsPlugin([ + 'User-Agent' => 'php-github-api (http://github.com/KnpLabs/php-github-api)', + ])); + + $this->apiVersion = $apiVersion ?: 'v3'; + $builder->addHeaderValue('Accept', sprintf('application/vnd.github.%s+json', $this->apiVersion)); + + if ($enterpriseUrl) { + $this->setEnterpriseUrl($enterpriseUrl); + } + } + + /** + * Create a Github\Client using a HttpClient. + * + * @param HttpClient $httpClient + * + * @return Client + */ + public static function createWithHttpClient(HttpClient $httpClient) + { + $builder = new Builder($httpClient); + + return new self($builder); + } + + /** + * @param string $name + * + * @throws InvalidArgumentException + * + * @return ApiInterface + */ + public function api($name) + { + switch ($name) { + case 'me': + case 'current_user': + case 'currentUser': + $api = new Api\CurrentUser($this); + break; + case 'codeOfConduct': + $api = new Api\Miscellaneous\CodeOfConduct($this); + break; + + case 'deployment': + case 'deployments': + $api = new Api\Deployment($this); + break; + + case 'ent': + case 'enterprise': + $api = new Api\Enterprise($this); + break; + + case 'emojis': + $api = new Api\Miscellaneous\Emojis($this); + break; + + case 'git': + case 'git_data': + case 'gitData': + $api = new Api\GitData($this); + break; + + case 'gist': + case 'gists': + $api = new Api\Gists($this); + break; + + case 'gitignore': + $api = new Api\Miscellaneous\Gitignore($this); + break; + + case 'integration': + case 'integrations': + $api = new Api\Integrations($this); + break; + + case 'apps': + $api = new Api\Apps($this); + break; + + case 'issue': + case 'issues': + $api = new Api\Issue($this); + break; + + case 'markdown': + $api = new Api\Markdown($this); + break; + + case 'notification': + case 'notifications': + $api = new Api\Notification($this); + break; + + case 'organization': + case 'organizations': + $api = new Api\Organization($this); + break; + + case 'org_project': + case 'orgProject': + case 'org_projects': + case 'orgProjects': + case 'organization_project': + case 'organizationProject': + case 'organization_projects': + case 'organizationProjects': + $api = new Api\Organization\Projects($this); + break; + + case 'pr': + case 'pulls': + case 'pullRequest': + case 'pull_request': + case 'pullRequests': + case 'pull_requests': + $api = new Api\PullRequest($this); + break; + + case 'rateLimit': + case 'rate_limit': + $api = new Api\RateLimit($this); + break; + + case 'repo': + case 'repos': + case 'repository': + case 'repositories': + $api = new Api\Repo($this); + break; + + case 'search': + $api = new Api\Search($this); + break; + + case 'team': + case 'teams': + $api = new Api\Organization\Teams($this); + break; + + case 'member': + case 'members': + $api = new Api\Organization\Members($this); + break; + + case 'user': + case 'users': + $api = new Api\User($this); + break; + + case 'authorization': + case 'authorizations': + $api = new Api\Authorizations($this); + break; + + case 'meta': + $api = new Api\Meta($this); + break; + + case 'graphql': + $api = new Api\GraphQL($this); + break; + + default: + throw new InvalidArgumentException(sprintf('Undefined api instance called: "%s"', $name)); + } + + return $api; + } + + /** + * Authenticate a user for all next requests. + * + * @param string $tokenOrLogin GitHub private token/username/client ID + * @param null|string $password GitHub password/secret (optionally can contain $authMethod) + * @param null|string $authMethod One of the AUTH_* class constants + * + * @throws InvalidArgumentException If no authentication method was given + */ + public function authenticate($tokenOrLogin, $password = null, $authMethod = null) + { + if (null === $password && null === $authMethod) { + throw new InvalidArgumentException('You need to specify authentication method!'); + } + + if (null === $authMethod && in_array($password, [self::AUTH_URL_TOKEN, self::AUTH_URL_CLIENT_ID, self::AUTH_HTTP_PASSWORD, self::AUTH_HTTP_TOKEN, self::AUTH_JWT])) { + $authMethod = $password; + $password = null; + } + + if (null === $authMethod) { + $authMethod = self::AUTH_HTTP_PASSWORD; + } + + $this->getHttpClientBuilder()->removePlugin(Authentication::class); + $this->getHttpClientBuilder()->addPlugin(new Authentication($tokenOrLogin, $password, $authMethod)); + } + + /** + * Sets the URL of your GitHub Enterprise instance. + * + * @param string $enterpriseUrl URL of the API in the form of http(s)://hostname + */ + private function setEnterpriseUrl($enterpriseUrl) + { + $builder = $this->getHttpClientBuilder(); + $builder->removePlugin(Plugin\AddHostPlugin::class); + $builder->removePlugin(PathPrepend::class); + + $builder->addPlugin(new Plugin\AddHostPlugin(UriFactoryDiscovery::find()->createUri($enterpriseUrl))); + $builder->addPlugin(new PathPrepend(sprintf('/api/%s', $this->getApiVersion()))); + } + + /** + * @return string + */ + public function getApiVersion() + { + return $this->apiVersion; + } + + /** + * Add a cache plugin to cache responses locally. + * + * @param CacheItemPoolInterface $cache + * @param array $config + */ + public function addCache(CacheItemPoolInterface $cachePool, array $config = []) + { + $this->getHttpClientBuilder()->addCache($cachePool, $config); + } + + /** + * Remove the cache plugin. + */ + public function removeCache() + { + $this->getHttpClientBuilder()->removeCache(); + } + + /** + * @param string $name + * + * @throws BadMethodCallException + * + * @return ApiInterface + */ + public function __call($name, $args) + { + try { + return $this->api($name); + } catch (InvalidArgumentException $e) { + throw new BadMethodCallException(sprintf('Undefined method called: "%s"', $name)); + } + } + + /** + * @return null|\Psr\Http\Message\ResponseInterface + */ + public function getLastResponse() + { + return $this->responseHistory->getLastResponse(); + } + + /** + * @return HttpMethodsClient + */ + public function getHttpClient() + { + return $this->getHttpClientBuilder()->getHttpClient(); + } + + /** + * @return Builder + */ + protected function getHttpClientBuilder() + { + return $this->httpClientBuilder; + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Exception/ApiLimitExceedException.php b/vendor/knplabs/github-api/lib/Github/Exception/ApiLimitExceedException.php new file mode 100644 index 00000000..0283175f --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Exception/ApiLimitExceedException.php @@ -0,0 +1,32 @@ + + */ +class ApiLimitExceedException extends RuntimeException +{ + private $limit; + private $reset; + + public function __construct($limit = 5000, $reset = 1800, $code = 0, $previous = null) + { + $this->limit = (int) $limit; + $this->reset = (int) $reset; + + parent::__construct(sprintf('You have reached GitHub hourly limit! Actual limit is: %d', $limit), $code, $previous); + } + + public function getLimit() + { + return $this->limit; + } + + public function getResetTime() + { + return $this->reset; + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Exception/BadMethodCallException.php b/vendor/knplabs/github-api/lib/Github/Exception/BadMethodCallException.php new file mode 100644 index 00000000..83e05437 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Exception/BadMethodCallException.php @@ -0,0 +1,12 @@ + + */ +class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface +{ +} diff --git a/vendor/knplabs/github-api/lib/Github/Exception/ErrorException.php b/vendor/knplabs/github-api/lib/Github/Exception/ErrorException.php new file mode 100644 index 00000000..61f61f36 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Exception/ErrorException.php @@ -0,0 +1,12 @@ + + */ +class ErrorException extends \ErrorException implements ExceptionInterface +{ +} diff --git a/vendor/knplabs/github-api/lib/Github/Exception/ExceptionInterface.php b/vendor/knplabs/github-api/lib/Github/Exception/ExceptionInterface.php new file mode 100644 index 00000000..87e6d2f7 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Exception/ExceptionInterface.php @@ -0,0 +1,9 @@ + + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/knplabs/github-api/lib/Github/Exception/MissingArgumentException.php b/vendor/knplabs/github-api/lib/Github/Exception/MissingArgumentException.php new file mode 100644 index 00000000..96e217ac --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Exception/MissingArgumentException.php @@ -0,0 +1,20 @@ + + */ +class MissingArgumentException extends ErrorException +{ + public function __construct($required, $code = 0, $previous = null) + { + if (is_string($required)) { + $required = [$required]; + } + + parent::__construct(sprintf('One or more of required ("%s") parameters is missing!', implode('", "', $required)), $code, $previous); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Exception/RuntimeException.php b/vendor/knplabs/github-api/lib/Github/Exception/RuntimeException.php new file mode 100644 index 00000000..676cb957 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Exception/RuntimeException.php @@ -0,0 +1,12 @@ + + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/knplabs/github-api/lib/Github/Exception/TwoFactorAuthenticationRequiredException.php b/vendor/knplabs/github-api/lib/Github/Exception/TwoFactorAuthenticationRequiredException.php new file mode 100644 index 00000000..6f93fe40 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Exception/TwoFactorAuthenticationRequiredException.php @@ -0,0 +1,19 @@ +type = $type; + parent::__construct('Two factor authentication is enabled on this account', $code, $previous); + } + + public function getType() + { + return $this->type; + } +} diff --git a/vendor/knplabs/github-api/lib/Github/Exception/ValidationFailedException.php b/vendor/knplabs/github-api/lib/Github/Exception/ValidationFailedException.php new file mode 100644 index 00000000..c689e2d6 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/Exception/ValidationFailedException.php @@ -0,0 +1,12 @@ + + */ +class ValidationFailedException extends ErrorException +{ +} diff --git a/vendor/knplabs/github-api/lib/Github/HttpClient/Builder.php b/vendor/knplabs/github-api/lib/Github/HttpClient/Builder.php new file mode 100644 index 00000000..6a635b2d --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/HttpClient/Builder.php @@ -0,0 +1,200 @@ + + */ +class Builder +{ + /** + * The object that sends HTTP messages. + * + * @var HttpClient + */ + private $httpClient; + + /** + * A HTTP client with all our plugins. + * + * @var HttpMethodsClient + */ + private $pluginClient; + + /** + * @var MessageFactory + */ + private $requestFactory; + + /** + * @var StreamFactory + */ + private $streamFactory; + + /** + * True if we should create a new Plugin client at next request. + * + * @var bool + */ + private $httpClientModified = true; + + /** + * @var Plugin[] + */ + private $plugins = []; + + /** + * This plugin is special treated because it has to be the very last plugin. + * + * @var Plugin\CachePlugin + */ + private $cachePlugin; + + /** + * Http headers. + * + * @var array + */ + private $headers = []; + + /** + * @param HttpClient $httpClient + * @param RequestFactory $requestFactory + * @param StreamFactory $streamFactory + */ + public function __construct( + HttpClient $httpClient = null, + RequestFactory $requestFactory = null, + StreamFactory $streamFactory = null + ) { + $this->httpClient = $httpClient ?: HttpClientDiscovery::find(); + $this->requestFactory = $requestFactory ?: MessageFactoryDiscovery::find(); + $this->streamFactory = $streamFactory ?: StreamFactoryDiscovery::find(); + } + + /** + * @return HttpMethodsClient + */ + public function getHttpClient() + { + if ($this->httpClientModified) { + $this->httpClientModified = false; + + $plugins = $this->plugins; + if ($this->cachePlugin) { + $plugins[] = $this->cachePlugin; + } + + $this->pluginClient = new HttpMethodsClient( + (new PluginClientFactory())->createClient($this->httpClient, $plugins), + $this->requestFactory + ); + } + + return $this->pluginClient; + } + + /** + * Add a new plugin to the end of the plugin chain. + * + * @param Plugin $plugin + */ + public function addPlugin(Plugin $plugin) + { + $this->plugins[] = $plugin; + $this->httpClientModified = true; + } + + /** + * Remove a plugin by its fully qualified class name (FQCN). + * + * @param string $fqcn + */ + public function removePlugin($fqcn) + { + foreach ($this->plugins as $idx => $plugin) { + if ($plugin instanceof $fqcn) { + unset($this->plugins[$idx]); + $this->httpClientModified = true; + } + } + } + + /** + * Clears used headers. + */ + public function clearHeaders() + { + $this->headers = []; + + $this->removePlugin(Plugin\HeaderAppendPlugin::class); + $this->addPlugin(new Plugin\HeaderAppendPlugin($this->headers)); + } + + /** + * @param array $headers + */ + public function addHeaders(array $headers) + { + $this->headers = array_merge($this->headers, $headers); + + $this->removePlugin(Plugin\HeaderAppendPlugin::class); + $this->addPlugin(new Plugin\HeaderAppendPlugin($this->headers)); + } + + /** + * @param string $header + * @param string $headerValue + */ + public function addHeaderValue($header, $headerValue) + { + if (!isset($this->headers[$header])) { + $this->headers[$header] = $headerValue; + } else { + $this->headers[$header] = array_merge((array) $this->headers[$header], [$headerValue]); + } + + $this->removePlugin(Plugin\HeaderAppendPlugin::class); + $this->addPlugin(new Plugin\HeaderAppendPlugin($this->headers)); + } + + /** + * Add a cache plugin to cache responses locally. + * + * @param CacheItemPoolInterface $cachePool + * @param array $config + */ + public function addCache(CacheItemPoolInterface $cachePool, array $config = []) + { + if (!isset($config['cache_key_generator'])) { + $config['cache_key_generator'] = new HeaderCacheKeyGenerator(['Authorization', 'Cookie', 'Accept', 'Content-type']); + } + $this->cachePlugin = Plugin\CachePlugin::clientCache($cachePool, $this->streamFactory, $config); + $this->httpClientModified = true; + } + + /** + * Remove the cache plugin. + */ + public function removeCache() + { + $this->cachePlugin = null; + $this->httpClientModified = true; + } +} diff --git a/vendor/knplabs/github-api/lib/Github/HttpClient/Message/ResponseMediator.php b/vendor/knplabs/github-api/lib/Github/HttpClient/Message/ResponseMediator.php new file mode 100644 index 00000000..6b944165 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/HttpClient/Message/ResponseMediator.php @@ -0,0 +1,82 @@ +getBody()->__toString(); + if (strpos($response->getHeaderLine('Content-Type'), 'application/json') === 0) { + $content = json_decode($body, true); + if (JSON_ERROR_NONE === json_last_error()) { + return $content; + } + } + + return $body; + } + + /** + * @param ResponseInterface $response + * + * @return array|null + */ + public static function getPagination(ResponseInterface $response) + { + if (!$response->hasHeader('Link')) { + return; + } + + $header = self::getHeader($response, 'Link'); + $pagination = []; + foreach (explode(',', $header) as $link) { + preg_match('/<(.*)>; rel="(.*)"/i', trim($link, ','), $match); + + if (3 === count($match)) { + $pagination[$match[2]] = $match[1]; + } + } + + return $pagination; + } + + /** + * @param ResponseInterface $response + * + * @return null|string + */ + public static function getApiLimit(ResponseInterface $response) + { + $remainingCalls = self::getHeader($response, 'X-RateLimit-Remaining'); + + if (null !== $remainingCalls && 1 > $remainingCalls) { + throw new ApiLimitExceedException($remainingCalls); + } + + return $remainingCalls; + } + + /** + * Get the value for a single header. + * + * @param ResponseInterface $response + * @param string $name + * + * @return string|null + */ + public static function getHeader(ResponseInterface $response, $name) + { + $headers = $response->getHeader($name); + + return array_shift($headers); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/HttpClient/Plugin/Authentication.php b/vendor/knplabs/github-api/lib/Github/HttpClient/Plugin/Authentication.php new file mode 100644 index 00000000..920d6572 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/HttpClient/Plugin/Authentication.php @@ -0,0 +1,85 @@ + + */ +class Authentication implements Plugin +{ + private $tokenOrLogin; + private $password; + private $method; + + public function __construct($tokenOrLogin, $password, $method) + { + $this->tokenOrLogin = $tokenOrLogin; + $this->password = $password; + $this->method = $method; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + switch ($this->method) { + case Client::AUTH_HTTP_PASSWORD: + $request = $request->withHeader( + 'Authorization', + sprintf('Basic %s', base64_encode($this->tokenOrLogin.':'.$this->password)) + ); + break; + + case Client::AUTH_HTTP_TOKEN: + $request = $request->withHeader('Authorization', sprintf('token %s', $this->tokenOrLogin)); + break; + + case Client::AUTH_URL_CLIENT_ID: + $uri = $request->getUri(); + $query = $uri->getQuery(); + + $parameters = [ + 'client_id' => $this->tokenOrLogin, + 'client_secret' => $this->password, + ]; + + $query .= empty($query) ? '' : '&'; + $query .= utf8_encode(http_build_query($parameters, '', '&')); + + $uri = $uri->withQuery($query); + $request = $request->withUri($uri); + break; + + case Client::AUTH_URL_TOKEN: + $uri = $request->getUri(); + $query = $uri->getQuery(); + + $parameters = ['access_token' => $this->tokenOrLogin]; + + $query .= empty($query) ? '' : '&'; + $query .= utf8_encode(http_build_query($parameters, '', '&')); + + $uri = $uri->withQuery($query); + $request = $request->withUri($uri); + break; + + case Client::AUTH_JWT: + $request = $request->withHeader('Authorization', sprintf('Bearer %s', $this->tokenOrLogin)); + break; + + default: + throw new RuntimeException(sprintf('%s not yet implemented', $this->method)); + break; + } + + return $next($request); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/HttpClient/Plugin/GithubExceptionThrower.php b/vendor/knplabs/github-api/lib/Github/HttpClient/Plugin/GithubExceptionThrower.php new file mode 100644 index 00000000..7d46fe9c --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/HttpClient/Plugin/GithubExceptionThrower.php @@ -0,0 +1,90 @@ + + * @author Tobias Nyholm + */ +class GithubExceptionThrower implements Plugin +{ + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + return $next($request)->then(function (ResponseInterface $response) use ($request) { + if ($response->getStatusCode() < 400 || $response->getStatusCode() > 600) { + return $response; + } + + // If error: + $remaining = ResponseMediator::getHeader($response, 'X-RateLimit-Remaining'); + if (null != $remaining && 1 > $remaining && 'rate_limit' !== substr($request->getRequestTarget(), 1, 10)) { + $limit = ResponseMediator::getHeader($response, 'X-RateLimit-Limit'); + $reset = ResponseMediator::getHeader($response, 'X-RateLimit-Reset'); + + throw new ApiLimitExceedException($limit, $reset); + } + + if (401 === $response->getStatusCode()) { + if ($response->hasHeader('X-GitHub-OTP') && 0 === strpos((string) ResponseMediator::getHeader($response, 'X-GitHub-OTP'), 'required;')) { + $type = substr((string) ResponseMediator::getHeader($response, 'X-GitHub-OTP'), 9); + + throw new TwoFactorAuthenticationRequiredException($type); + } + } + + $content = ResponseMediator::getContent($response); + if (is_array($content) && isset($content['message'])) { + if (400 == $response->getStatusCode()) { + throw new ErrorException($content['message'], 400); + } elseif (422 == $response->getStatusCode() && isset($content['errors'])) { + $errors = []; + foreach ($content['errors'] as $error) { + switch ($error['code']) { + case 'missing': + $errors[] = sprintf('The %s %s does not exist, for resource "%s"', $error['field'], $error['value'], $error['resource']); + break; + + case 'missing_field': + $errors[] = sprintf('Field "%s" is missing, for resource "%s"', $error['field'], $error['resource']); + break; + + case 'invalid': + if (isset($error['message'])) { + $errors[] = sprintf('Field "%s" is invalid, for resource "%s": "%s"', $error['field'], $error['resource'], $error['message']); + } else { + $errors[] = sprintf('Field "%s" is invalid, for resource "%s"', $error['field'], $error['resource']); + } + break; + + case 'already_exists': + $errors[] = sprintf('Field "%s" already exists, for resource "%s"', $error['field'], $error['resource']); + break; + + default: + $errors[] = $error['message']; + break; + + } + } + + throw new ValidationFailedException('Validation Failed: '.implode(', ', $errors), 422); + } + } + + throw new RuntimeException(isset($content['message']) ? $content['message'] : $content, $response->getStatusCode()); + }); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/HttpClient/Plugin/History.php b/vendor/knplabs/github-api/lib/Github/HttpClient/Plugin/History.php new file mode 100644 index 00000000..303d8140 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/HttpClient/Plugin/History.php @@ -0,0 +1,38 @@ + + */ +class History implements Journal +{ + /** + * @var ResponseInterface + */ + private $lastResponse; + + /** + * @return ResponseInterface|null + */ + public function getLastResponse() + { + return $this->lastResponse; + } + + public function addSuccess(RequestInterface $request, ResponseInterface $response) + { + $this->lastResponse = $response; + } + + public function addFailure(RequestInterface $request, Exception $exception) + { + } +} diff --git a/vendor/knplabs/github-api/lib/Github/HttpClient/Plugin/PathPrepend.php b/vendor/knplabs/github-api/lib/Github/HttpClient/Plugin/PathPrepend.php new file mode 100644 index 00000000..2c91bf74 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/HttpClient/Plugin/PathPrepend.php @@ -0,0 +1,38 @@ + + */ +class PathPrepend implements Plugin +{ + private $path; + + /** + * @param string $path + */ + public function __construct($path) + { + $this->path = $path; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + $currentPath = $request->getUri()->getPath(); + if (strpos($currentPath, $this->path) !== 0) { + $uri = $request->getUri()->withPath($this->path.$currentPath); + $request = $request->withUri($uri); + } + + return $next($request); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/ResultPager.php b/vendor/knplabs/github-api/lib/Github/ResultPager.php new file mode 100644 index 00000000..24f2a32f --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/ResultPager.php @@ -0,0 +1,194 @@ + + * @author Mitchel Verschoof + */ +class ResultPager implements ResultPagerInterface +{ + /** + * The GitHub Client to use for pagination. + * + * @var \Github\Client + */ + protected $client; + + /** + * Comes from pagination headers in Github API results. + * + * @var array + */ + protected $pagination; + + /** + * The Github client to use for pagination. + * + * This must be the same instance that you got the Api instance from. + * + * Example code: + * + * $client = new \Github\Client(); + * $api = $client->api('someApi'); + * $pager = new \Github\ResultPager($client); + * + * @param \Github\Client $client + */ + public function __construct(Client $client) + { + $this->client = $client; + } + + /** + * {@inheritdoc} + */ + public function getPagination() + { + return $this->pagination; + } + + /** + * {@inheritdoc} + */ + public function fetch(ApiInterface $api, $method, array $parameters = []) + { + $result = $this->callApi($api, $method, $parameters); + $this->postFetch(); + + return $result; + } + + /** + * {@inheritdoc} + */ + public function fetchAll(ApiInterface $api, $method, array $parameters = []) + { + $isSearch = $api instanceof Search; + + // get the perPage from the api + $perPage = $api->getPerPage(); + + // set parameters per_page to GitHub max to minimize number of requests + $api->setPerPage(100); + + try { + $result = $this->callApi($api, $method, $parameters); + $this->postFetch(); + + if ($isSearch) { + $result = isset($result['items']) ? $result['items'] : $result; + } + + while ($this->hasNext()) { + $next = $this->fetchNext(); + + if ($isSearch) { + $result = array_merge($result, $next['items']); + } else { + $result = array_merge($result, $next); + } + } + } finally { + // restore the perPage + $api->setPerPage($perPage); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function postFetch() + { + $this->pagination = ResponseMediator::getPagination($this->client->getLastResponse()); + } + + /** + * {@inheritdoc} + */ + public function hasNext() + { + return $this->has('next'); + } + + /** + * {@inheritdoc} + */ + public function fetchNext() + { + return $this->get('next'); + } + + /** + * {@inheritdoc} + */ + public function hasPrevious() + { + return $this->has('prev'); + } + + /** + * {@inheritdoc} + */ + public function fetchPrevious() + { + return $this->get('prev'); + } + + /** + * {@inheritdoc} + */ + public function fetchFirst() + { + return $this->get('first'); + } + + /** + * {@inheritdoc} + */ + public function fetchLast() + { + return $this->get('last'); + } + + /** + * @param string $key + */ + protected function has($key) + { + return !empty($this->pagination) && isset($this->pagination[$key]); + } + + /** + * @param string $key + */ + protected function get($key) + { + if ($this->has($key)) { + $result = $this->client->getHttpClient()->get($this->pagination[$key]); + $this->postFetch(); + + return ResponseMediator::getContent($result); + } + } + + /** + * @param ApiInterface $api + * @param string $method + * @param array $parameters + * + * @return mixed + */ + protected function callApi(ApiInterface $api, $method, array $parameters) + { + return call_user_func_array([$api, $method], $parameters); + } +} diff --git a/vendor/knplabs/github-api/lib/Github/ResultPagerInterface.php b/vendor/knplabs/github-api/lib/Github/ResultPagerInterface.php new file mode 100644 index 00000000..c5add903 --- /dev/null +++ b/vendor/knplabs/github-api/lib/Github/ResultPagerInterface.php @@ -0,0 +1,90 @@ + + * @author Mitchel Verschoof + */ +interface ResultPagerInterface +{ + /** + * @return null|array pagination result of last request + */ + public function getPagination(); + + /** + * Fetch a single result (page) from an api call. + * + * @param ApiInterface $api the Api instance + * @param string $method the method name to call on the Api instance + * @param array $parameters the method parameters in an array + * + * @return array returns the result of the Api::$method() call + */ + public function fetch(ApiInterface $api, $method, array $parameters = []); + + /** + * Fetch all results (pages) from an api call. + * + * Use with care - there is no maximum. + * + * @param ApiInterface $api the Api instance + * @param string $method the method name to call on the Api instance + * @param array $parameters the method parameters in an array + * + * @return array returns a merge of the results of the Api::$method() call + */ + public function fetchAll(ApiInterface $api, $method, array $parameters = []); + + /** + * Method that performs the actual work to refresh the pagination property. + */ + public function postFetch(); + + /** + * Check to determine the availability of a next page. + * + * @return bool + */ + public function hasNext(); + + /** + * Check to determine the availability of a previous page. + * + * @return bool + */ + public function hasPrevious(); + + /** + * Fetch the next page. + * + * @return array + */ + public function fetchNext(); + + /** + * Fetch the previous page. + * + * @return array + */ + public function fetchPrevious(); + + /** + * Fetch the first page. + * + * @return array + */ + public function fetchFirst(); + + /** + * Fetch the last page. + * + * @return array + */ + public function fetchLast(); +} diff --git a/vendor/php-http/cache-plugin/CHANGELOG.md b/vendor/php-http/cache-plugin/CHANGELOG.md new file mode 100644 index 00000000..795a3ff7 --- /dev/null +++ b/vendor/php-http/cache-plugin/CHANGELOG.md @@ -0,0 +1,74 @@ +# Change Log + +## 1.5.0 - 2017-11-29 + +### Added + +* Support for Symfony 4 + +### Changed + +* Removed check if etag is a string. Etag can never be a string, it is always an array. + +## 1.4.0 - 2017-04-05 + +### Added + +- `CacheKeyGenerator` interface that allow you to configure how the PSR-6 cache key is created. There are two implementations +of this interface: `SimpleGenerator` (default) and `HeaderCacheKeyGenerator`. + +### Fixed + +- Issue where deprecation warning always was triggered. Not it is just triggered if `respect_cache_headers` is used. + +## 1.3.0 - 2017-03-28 + +### Added + +- New `methods` option which allows to configure the request methods which can be cached. +- New `respect_response_cache_directives` option to define specific cache directives to respect when handling responses. +- Introduced `CachePlugin::clientCache` and `CachePlugin::serverCache` factory methods to easily setup the plugin with + the correct config settigns for each usecase. + +### Changed + +- The `no-cache` directive is now respected by the plugin and will not cache the response. If you need the previous behaviour, configure `respect_response_cache_directives`. +- We always rewind the stream after loading response from cache. + +### Deprecated + +- The `respect_cache_headers` option is deprecated and will be removed in 2.0. This option is replaced by the new `respect_response_cache_directives` option. + If you had set `respect_cache_headers` to `false`, set the directives to `[]` to ignore all directives. + + +## 1.2.0 - 2016-08-16 + +### Changed + +- The default value for `default_ttl` is changed from `null` to `0`. + +### Fixed + +- Issue when you use `respect_cache_headers=>false` in combination with `default_ttl=>null`. +- We allow `cache_lifetime` to be set to `null`. + + +## 1.1.0 - 2016-08-04 + +### Added + +- Support for cache validation with ETag and Last-Modified headers. (Enabled automatically when the server sends the relevant headers.) +- `hash_algo` config option used for cache key generation (defaults to **sha1**). + +### Changed + +- Default hash algo used for cache generation (from **md5** to **sha1**). + +### Fixed + +- Cast max age header to integer in order to get valid expiration value. + + +## 1.0.0 - 2016-05-05 + +- Initial release diff --git a/vendor/php-http/cache-plugin/LICENSE b/vendor/php-http/cache-plugin/LICENSE new file mode 100644 index 00000000..4558d6f0 --- /dev/null +++ b/vendor/php-http/cache-plugin/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2016 PHP HTTP Team + +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. diff --git a/vendor/php-http/cache-plugin/README.md b/vendor/php-http/cache-plugin/README.md new file mode 100644 index 00000000..ce07b090 --- /dev/null +++ b/vendor/php-http/cache-plugin/README.md @@ -0,0 +1,46 @@ +# Cache Plugin + +[![Latest Version](https://img.shields.io/github/release/php-http/cache-plugin.svg?style=flat-square)](https://github.com/php-http/cache-plugin/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Build Status](https://img.shields.io/travis/php-http/cache-plugin.svg?style=flat-square)](https://travis-ci.org/php-http/cache-plugin) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/cache-plugin.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/cache-plugin) +[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/cache-plugin.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/cache-plugin) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/cache-plugin.svg?style=flat-square)](https://packagist.org/packages/php-http/cache-plugin) + +**PSR-6 Cache plugin for HTTPlug.** + + +## Install + +Via Composer + +``` bash +$ composer require php-http/cache-plugin +``` + + +## Documentation + +Please see the [official documentation](http://docs.php-http.org/en/latest/plugins/cache.html). + + +## Testing + +``` bash +$ composer test +``` + + +## Contributing + +Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html). + + +## Security + +If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org). + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/vendor/php-http/cache-plugin/composer.json b/vendor/php-http/cache-plugin/composer.json new file mode 100644 index 00000000..3ce18bee --- /dev/null +++ b/vendor/php-http/cache-plugin/composer.json @@ -0,0 +1,43 @@ +{ + "name": "php-http/cache-plugin", + "description": "PSR-6 Cache plugin for HTTPlug", + "license": "MIT", + "keywords": ["cache", "http", "httplug", "plugin"], + "homepage": "http://httplug.io", + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "require": { + "php": "^5.4 || ^7.0", + "psr/cache": "^1.0", + "php-http/client-common": "^1.1", + "php-http/message-factory": "^1.0", + "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5", + "henrikbjorn/phpspec-code-coverage" : "^1.0" + }, + "autoload": { + "psr-4": { + "Http\\Client\\Common\\Plugin\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "spec\\Http\\Client\\Common\\Plugin\\": "spec/" + } + }, + "scripts": { + "test": "vendor/bin/phpspec run", + "test-ci": "vendor/bin/phpspec run -c phpspec.ci.yml" + }, + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + } +} diff --git a/vendor/php-http/cache-plugin/src/Cache/Generator/CacheKeyGenerator.php b/vendor/php-http/cache-plugin/src/Cache/Generator/CacheKeyGenerator.php new file mode 100644 index 00000000..d351e57c --- /dev/null +++ b/vendor/php-http/cache-plugin/src/Cache/Generator/CacheKeyGenerator.php @@ -0,0 +1,22 @@ + + */ +interface CacheKeyGenerator +{ + /** + * Generate a cache key from a Request. + * + * @param RequestInterface $request + * + * @return string + */ + public function generate(RequestInterface $request); +} diff --git a/vendor/php-http/cache-plugin/src/Cache/Generator/HeaderCacheKeyGenerator.php b/vendor/php-http/cache-plugin/src/Cache/Generator/HeaderCacheKeyGenerator.php new file mode 100644 index 00000000..562ed484 --- /dev/null +++ b/vendor/php-http/cache-plugin/src/Cache/Generator/HeaderCacheKeyGenerator.php @@ -0,0 +1,38 @@ + + */ +class HeaderCacheKeyGenerator implements CacheKeyGenerator +{ + /** + * The header names we should take into account when creating the cache key. + * + * @var array + */ + private $headerNames; + + /** + * @param $headerNames + */ + public function __construct(array $headerNames) + { + $this->headerNames = $headerNames; + } + + public function generate(RequestInterface $request) + { + $concatenatedHeaders = []; + foreach ($this->headerNames as $headerName) { + $concatenatedHeaders[] = sprintf(' %s:"%s"', $headerName, $request->getHeaderLine($headerName)); + } + + return $request->getMethod().' '.$request->getUri().implode('', $concatenatedHeaders).' '.$request->getBody(); + } +} diff --git a/vendor/php-http/cache-plugin/src/Cache/Generator/SimpleGenerator.php b/vendor/php-http/cache-plugin/src/Cache/Generator/SimpleGenerator.php new file mode 100644 index 00000000..4f0ee903 --- /dev/null +++ b/vendor/php-http/cache-plugin/src/Cache/Generator/SimpleGenerator.php @@ -0,0 +1,23 @@ + + */ +class SimpleGenerator implements CacheKeyGenerator +{ + public function generate(RequestInterface $request) + { + $body = (string) $request->getBody(); + if (!empty($body)) { + $body = ' '.$body; + } + + return $request->getMethod().' '.$request->getUri().$body; + } +} diff --git a/vendor/php-http/cache-plugin/src/CachePlugin.php b/vendor/php-http/cache-plugin/src/CachePlugin.php new file mode 100644 index 00000000..166cf9be --- /dev/null +++ b/vendor/php-http/cache-plugin/src/CachePlugin.php @@ -0,0 +1,444 @@ + + */ +final class CachePlugin implements Plugin +{ + /** + * @var CacheItemPoolInterface + */ + private $pool; + + /** + * @var StreamFactory + */ + private $streamFactory; + + /** + * @var array + */ + private $config; + + /** + * Cache directives indicating if a response can not be cached. + * + * @var array + */ + private $noCacheFlags = ['no-cache', 'private', 'no-store']; + + /** + * @param CacheItemPoolInterface $pool + * @param StreamFactory $streamFactory + * @param array $config { + * + * @var bool $respect_cache_headers Whether to look at the cache directives or ignore them + * @var int $default_ttl (seconds) If we do not respect cache headers or can't calculate a good ttl, use this + * value + * @var string $hash_algo The hashing algorithm to use when generating cache keys + * @var int $cache_lifetime (seconds) To support serving a previous stale response when the server answers 304 + * we have to store the cache for a longer time than the server originally says it is valid for. + * We store a cache item for $cache_lifetime + max age of the response. + * @var array $methods list of request methods which can be cached + * @var array $respect_response_cache_directives list of cache directives this plugin will respect while caching responses + * @var CacheKeyGenerator $cache_key_generator an object to generate the cache key. Defaults to a new instance of SimpleGenerator + * } + */ + public function __construct(CacheItemPoolInterface $pool, StreamFactory $streamFactory, array $config = []) + { + $this->pool = $pool; + $this->streamFactory = $streamFactory; + + if (isset($config['respect_cache_headers']) && isset($config['respect_response_cache_directives'])) { + throw new \InvalidArgumentException( + 'You can\'t provide config option "respect_cache_headers" and "respect_response_cache_directives". '. + 'Use "respect_response_cache_directives" instead.' + ); + } + + $optionsResolver = new OptionsResolver(); + $this->configureOptions($optionsResolver); + $this->config = $optionsResolver->resolve($config); + + if (null === $this->config['cache_key_generator']) { + $this->config['cache_key_generator'] = new SimpleGenerator(); + } + } + + /** + * This method will setup the cachePlugin in client cache mode. When using the client cache mode the plugin will + * cache responses with `private` cache directive. + * + * @param CacheItemPoolInterface $pool + * @param StreamFactory $streamFactory + * @param array $config For all possible config options see the constructor docs + * + * @return CachePlugin + */ + public static function clientCache(CacheItemPoolInterface $pool, StreamFactory $streamFactory, array $config = []) + { + // Allow caching of private requests + if (isset($config['respect_response_cache_directives'])) { + $config['respect_response_cache_directives'][] = 'no-cache'; + $config['respect_response_cache_directives'][] = 'max-age'; + $config['respect_response_cache_directives'] = array_unique($config['respect_response_cache_directives']); + } else { + $config['respect_response_cache_directives'] = ['no-cache', 'max-age']; + } + + return new self($pool, $streamFactory, $config); + } + + /** + * This method will setup the cachePlugin in server cache mode. This is the default caching behavior it refuses to + * cache responses with the `private`or `no-cache` directives. + * + * @param CacheItemPoolInterface $pool + * @param StreamFactory $streamFactory + * @param array $config For all possible config options see the constructor docs + * + * @return CachePlugin + */ + public static function serverCache(CacheItemPoolInterface $pool, StreamFactory $streamFactory, array $config = []) + { + return new self($pool, $streamFactory, $config); + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + $method = strtoupper($request->getMethod()); + // if the request not is cachable, move to $next + if (!in_array($method, $this->config['methods'])) { + return $next($request); + } + + // If we can cache the request + $key = $this->createCacheKey($request); + $cacheItem = $this->pool->getItem($key); + + if ($cacheItem->isHit()) { + $data = $cacheItem->get(); + // The array_key_exists() is to be removed in 2.0. + if (array_key_exists('expiresAt', $data) && (null === $data['expiresAt'] || time() < $data['expiresAt'])) { + // This item is still valid according to previous cache headers + return new FulfilledPromise($this->createResponseFromCacheItem($cacheItem)); + } + + // Add headers to ask the server if this cache is still valid + if ($modifiedSinceValue = $this->getModifiedSinceHeaderValue($cacheItem)) { + $request = $request->withHeader('If-Modified-Since', $modifiedSinceValue); + } + + if ($etag = $this->getETag($cacheItem)) { + $request = $request->withHeader('If-None-Match', $etag); + } + } + + return $next($request)->then(function (ResponseInterface $response) use ($cacheItem) { + if (304 === $response->getStatusCode()) { + if (!$cacheItem->isHit()) { + /* + * We do not have the item in cache. This plugin did not add If-Modified-Since + * or If-None-Match headers. Return the response from server. + */ + return $response; + } + + // The cached response we have is still valid + $data = $cacheItem->get(); + $maxAge = $this->getMaxAge($response); + $data['expiresAt'] = $this->calculateResponseExpiresAt($maxAge); + $cacheItem->set($data)->expiresAfter($this->calculateCacheItemExpiresAfter($maxAge)); + $this->pool->save($cacheItem); + + return $this->createResponseFromCacheItem($cacheItem); + } + + if ($this->isCacheable($response)) { + $bodyStream = $response->getBody(); + $body = $bodyStream->__toString(); + if ($bodyStream->isSeekable()) { + $bodyStream->rewind(); + } else { + $response = $response->withBody($this->streamFactory->createStream($body)); + } + + $maxAge = $this->getMaxAge($response); + $cacheItem + ->expiresAfter($this->calculateCacheItemExpiresAfter($maxAge)) + ->set([ + 'response' => $response, + 'body' => $body, + 'expiresAt' => $this->calculateResponseExpiresAt($maxAge), + 'createdAt' => time(), + 'etag' => $response->getHeader('ETag'), + ]); + $this->pool->save($cacheItem); + } + + return $response; + }); + } + + /** + * Calculate the timestamp when this cache item should be dropped from the cache. The lowest value that can be + * returned is $maxAge. + * + * @param int|null $maxAge + * + * @return int|null Unix system time passed to the PSR-6 cache + */ + private function calculateCacheItemExpiresAfter($maxAge) + { + if (null === $this->config['cache_lifetime'] && null === $maxAge) { + return; + } + + return $this->config['cache_lifetime'] + $maxAge; + } + + /** + * Calculate the timestamp when a response expires. After that timestamp, we need to send a + * If-Modified-Since / If-None-Match request to validate the response. + * + * @param int|null $maxAge + * + * @return int|null Unix system time. A null value means that the response expires when the cache item expires + */ + private function calculateResponseExpiresAt($maxAge) + { + if (null === $maxAge) { + return; + } + + return time() + $maxAge; + } + + /** + * Verify that we can cache this response. + * + * @param ResponseInterface $response + * + * @return bool + */ + protected function isCacheable(ResponseInterface $response) + { + if (!in_array($response->getStatusCode(), [200, 203, 300, 301, 302, 404, 410])) { + return false; + } + + $nocacheDirectives = array_intersect($this->config['respect_response_cache_directives'], $this->noCacheFlags); + foreach ($nocacheDirectives as $nocacheDirective) { + if ($this->getCacheControlDirective($response, $nocacheDirective)) { + return false; + } + } + + return true; + } + + /** + * Get the value of a parameter in the cache control header. + * + * @param ResponseInterface $response + * @param string $name The field of Cache-Control to fetch + * + * @return bool|string The value of the directive, true if directive without value, false if directive not present + */ + private function getCacheControlDirective(ResponseInterface $response, $name) + { + $headers = $response->getHeader('Cache-Control'); + foreach ($headers as $header) { + if (preg_match(sprintf('|%s=?([0-9]+)?|i', $name), $header, $matches)) { + // return the value for $name if it exists + if (isset($matches[1])) { + return $matches[1]; + } + + return true; + } + } + + return false; + } + + /** + * @param RequestInterface $request + * + * @return string + */ + private function createCacheKey(RequestInterface $request) + { + $key = $this->config['cache_key_generator']->generate($request); + + return hash($this->config['hash_algo'], $key); + } + + /** + * Get a ttl in seconds. It could return null if we do not respect cache headers and got no defaultTtl. + * + * @param ResponseInterface $response + * + * @return int|null + */ + private function getMaxAge(ResponseInterface $response) + { + if (!in_array('max-age', $this->config['respect_response_cache_directives'], true)) { + return $this->config['default_ttl']; + } + + // check for max age in the Cache-Control header + $maxAge = $this->getCacheControlDirective($response, 'max-age'); + if (!is_bool($maxAge)) { + $ageHeaders = $response->getHeader('Age'); + foreach ($ageHeaders as $age) { + return $maxAge - ((int) $age); + } + + return (int) $maxAge; + } + + // check for ttl in the Expires header + $headers = $response->getHeader('Expires'); + foreach ($headers as $header) { + return (new \DateTime($header))->getTimestamp() - (new \DateTime())->getTimestamp(); + } + + return $this->config['default_ttl']; + } + + /** + * Configure an options resolver. + * + * @param OptionsResolver $resolver + */ + private function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'cache_lifetime' => 86400 * 30, // 30 days + 'default_ttl' => 0, + //Deprecated as of v1.3, to be removed in v2.0. Use respect_response_cache_directives instead + 'respect_cache_headers' => null, + 'hash_algo' => 'sha1', + 'methods' => ['GET', 'HEAD'], + 'respect_response_cache_directives' => ['no-cache', 'private', 'max-age', 'no-store'], + 'cache_key_generator' => null, + ]); + + $resolver->setAllowedTypes('cache_lifetime', ['int', 'null']); + $resolver->setAllowedTypes('default_ttl', ['int', 'null']); + $resolver->setAllowedTypes('respect_cache_headers', ['bool', 'null']); + $resolver->setAllowedTypes('methods', 'array'); + $resolver->setAllowedTypes('cache_key_generator', ['null', 'Http\Client\Common\Plugin\Cache\Generator\CacheKeyGenerator']); + $resolver->setAllowedValues('hash_algo', hash_algos()); + $resolver->setAllowedValues('methods', function ($value) { + /* RFC7230 sections 3.1.1 and 3.2.6 except limited to uppercase characters. */ + $matches = preg_grep('/[^A-Z0-9!#$%&\'*+\-.^_`|~]/', $value); + + return empty($matches); + }); + + $resolver->setNormalizer('respect_cache_headers', function (Options $options, $value) { + if (null !== $value) { + @trigger_error('The option "respect_cache_headers" is deprecated since version 1.3 and will be removed in 2.0. Use "respect_response_cache_directives" instead.', E_USER_DEPRECATED); + } + + return null === $value ? true : $value; + }); + + $resolver->setNormalizer('respect_response_cache_directives', function (Options $options, $value) { + if (false === $options['respect_cache_headers']) { + return []; + } + + return $value; + }); + } + + /** + * @param CacheItemInterface $cacheItem + * + * @return ResponseInterface + */ + private function createResponseFromCacheItem(CacheItemInterface $cacheItem) + { + $data = $cacheItem->get(); + + /** @var ResponseInterface $response */ + $response = $data['response']; + $stream = $this->streamFactory->createStream($data['body']); + + try { + $stream->rewind(); + } catch (\Exception $e) { + throw new RewindStreamException('Cannot rewind stream.', 0, $e); + } + + $response = $response->withBody($stream); + + return $response; + } + + /** + * Get the value of the "If-Modified-Since" header. + * + * @param CacheItemInterface $cacheItem + * + * @return string|null + */ + private function getModifiedSinceHeaderValue(CacheItemInterface $cacheItem) + { + $data = $cacheItem->get(); + // The isset() is to be removed in 2.0. + if (!isset($data['createdAt'])) { + return; + } + + $modified = new \DateTime('@'.$data['createdAt']); + $modified->setTimezone(new \DateTimeZone('GMT')); + + return sprintf('%s GMT', $modified->format('l, d-M-y H:i:s')); + } + + /** + * Get the ETag from the cached response. + * + * @param CacheItemInterface $cacheItem + * + * @return string|null + */ + private function getETag(CacheItemInterface $cacheItem) + { + $data = $cacheItem->get(); + // The isset() is to be removed in 2.0. + if (!isset($data['etag'])) { + return; + } + + foreach ($data['etag'] as $etag) { + if (!empty($etag)) { + return $etag; + } + } + } +} diff --git a/vendor/php-http/cache-plugin/src/Exception/RewindStreamException.php b/vendor/php-http/cache-plugin/src/Exception/RewindStreamException.php new file mode 100644 index 00000000..1b9eaee3 --- /dev/null +++ b/vendor/php-http/cache-plugin/src/Exception/RewindStreamException.php @@ -0,0 +1,12 @@ + + */ +class RewindStreamException extends \RuntimeException implements Exception +{ +} diff --git a/vendor/php-http/client-common/CHANGELOG.md b/vendor/php-http/client-common/CHANGELOG.md new file mode 100644 index 00000000..ff6b43cb --- /dev/null +++ b/vendor/php-http/client-common/CHANGELOG.md @@ -0,0 +1,179 @@ +# Change Log + +## 1.9.0 - 2019-01-03 + +### Added + +- Support for PSR-18 clients +- Added traits `VersionBridgePlugin` and `VersionBridgeClient` to help plugins and clients to support both + 1.x and 2.x version of `php-http/client-common` and `php-http/httplug`. + +### Changed + +- [RetryPlugin] Renamed the configuration options for the exception retry callback from `decider` to `exception_decider` + and `delay` to `exception_delay`. The old names still work but are deprecated. + +## 1.8.2 - 2018-12-14 + +### Changed + +- When multiple cookies exist, a single header with all cookies is sent as per RFC 6265 Section 5.4 +- AddPathPlugin will now trim of ending slashes in paths + +## 1.8.1 - 2018-10-09 + +### Fixed + +- Reverted change to RetryPlugin so it again waits when retrying to avoid "can only throw objects" error. + +## 1.8.0 - 2018-09-21 + +### Added + + - Add an option on ErrorPlugin to only throw exception on response with 5XX status code. + +### Changed + +- AddPathPlugin no longer add prefix multiple times if a request is restarted - it now only adds the prefix if that request chain has not yet passed through the AddPathPlugin +- RetryPlugin no longer wait for retried requests and use a deferred promise instead + +### Fixed + +- Decoder plugin will now remove header when there is no more encoding, instead of setting to an empty array + + +## 1.7.0 - 2017-11-30 + +### Added + +- Symfony 4 support + +### Changed + +- Strict comparison in DecoderPlugin + +## 1.6.0 - 2017-10-16 + +### Added + +- Add HttpClientPool client to leverage load balancing and fallback mechanism [see the documentation](http://docs.php-http.org/en/latest/components/client-common.html) for more details. +- `PluginClientFactory` to create `PluginClient` instances. +- Added new option 'delay' for `RetryPlugin`. +- Added new option 'decider' for `RetryPlugin`. +- Supports more cookie date formats in the Cookie Plugin + +### Changed + +- The `RetryPlugin` does now wait between retries. To disable/change this feature you must write something like: + +```php +$plugin = new RetryPlugin(['delay' => function(RequestInterface $request, Exception $e, $retries) { + return 0; +}); +``` + +### Deprecated + +- The `debug_plugins` option for `PluginClient` is deprecated and will be removed in 2.0. Use the decorator design pattern instead like in [ProfilePlugin](https://github.com/php-http/HttplugBundle/blob/de33f9c14252f22093a5ec7d84f17535ab31a384/Collector/ProfilePlugin.php). + +## 1.5.0 - 2017-03-30 + +### Added + +- `QueryDefaultsPlugin` to add default query parameters. + +## 1.4.2 - 2017-03-18 + +### Deprecated + +- `DecoderPlugin` does not longer claim to support `compress` content encoding + +### Fixed + +- `CookiePlugin` allows main domain cookies to be sent/stored for subdomains +- `DecoderPlugin` uses the right `FilteredStream` to handle `deflate` content encoding + + +## 1.4.1 - 2017-02-20 + +### Fixed + +- Cast return value of `StreamInterface::getSize` to string in `ContentLengthPlugin` + + +## 1.4.0 - 2016-11-04 + +### Added + +- Add Path plugin +- Base URI plugin that combines Add Host and Add Path plugins + + +## 1.3.0 - 2016-10-16 + +### Changed + +- Fix Emulated Trait to use Http based promise which respect the HttpAsyncClient interface +- Require Httplug 1.1 where we use HTTP specific promises. +- RedirectPlugin: use the full URL instead of the URI to properly keep track of redirects +- Add AddPathPlugin for API URLs with base path +- Add BaseUriPlugin that combines AddHostPlugin and AddPathPlugin + + +## 1.2.1 - 2016-07-26 + +### Changed + +- AddHostPlugin also sets the port if specified + + +## 1.2.0 - 2016-07-14 + +### Added + +- Suggest separate plugins in composer.json +- Introduced `debug_plugins` option for `PluginClient` + + +## 1.1.0 - 2016-05-04 + +### Added + +- Add a flexible http client providing both contract, and only emulating what's necessary +- HTTP Client Router: route requests to underlying clients +- Plugin client and core plugins moved here from `php-http/plugins` + +### Deprecated + +- Extending client classes, they will be made final in version 2.0 + + +## 1.0.0 - 2016-01-27 + +### Changed + +- Remove useless interface in BatchException + + +## 0.2.0 - 2016-01-12 + +### Changed + +- Updated package files +- Updated HTTPlug to RC1 + + +## 0.1.1 - 2015-12-26 + +### Added + +- Emulated clients + + +## 0.1.0 - 2015-12-25 + +### Added + +- Batch client from utils +- Methods client from utils +- Emulators and decorators from client-tools diff --git a/vendor/php-http/client-common/LICENSE b/vendor/php-http/client-common/LICENSE new file mode 100644 index 00000000..4558d6f0 --- /dev/null +++ b/vendor/php-http/client-common/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2016 PHP HTTP Team + +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. diff --git a/vendor/php-http/client-common/README.md b/vendor/php-http/client-common/README.md new file mode 100644 index 00000000..017bfcec --- /dev/null +++ b/vendor/php-http/client-common/README.md @@ -0,0 +1,55 @@ +# HTTP Client Common + +[![Latest Version](https://img.shields.io/github/release/php-http/client-common.svg?style=flat-square)](https://github.com/php-http/client-common/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Build Status](https://img.shields.io/travis/php-http/client-common.svg?style=flat-square)](https://travis-ci.org/php-http/client-common) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/client-common.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/client-common) +[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/client-common.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/client-common) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/client-common.svg?style=flat-square)](https://packagist.org/packages/php-http/client-common) + +**Common HTTP Client implementations and tools for HTTPlug.** + + +## Install + +Via Composer + +``` bash +$ composer require php-http/client-common +``` + + +## Usage + +This package provides common tools for HTTP Clients: + +- BatchClient to handle sending requests in parallel +- A convenience client with HTTP method names as class methods +- Emulator, decorator layers for sync/async clients + + +## Documentation + +Please see the [official documentation](http://docs.php-http.org/en/latest/components/client-common.html). + + +## Testing + +``` bash +$ composer test +``` + + +## Contributing + +Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html). + + +## Security + +If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org). + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/vendor/php-http/client-common/composer.json b/vendor/php-http/client-common/composer.json new file mode 100644 index 00000000..c502f778 --- /dev/null +++ b/vendor/php-http/client-common/composer.json @@ -0,0 +1,43 @@ +{ + "name": "php-http/client-common", + "description": "Common HTTP Client implementations and tools for HTTPlug", + "license": "MIT", + "keywords": ["http", "client", "httplug", "common"], + "homepage": "http://httplug.io", + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "require": { + "php": "^5.4 || ^7.0", + "php-http/httplug": "^1.1", + "php-http/message-factory": "^1.0", + "php-http/message": "^1.6", + "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.4 || ^4.2", + "guzzlehttp/psr7": "^1.4" + }, + "suggest": { + "php-http/logger-plugin": "PSR-3 Logger plugin", + "php-http/cache-plugin": "PSR-6 Cache plugin", + "php-http/stopwatch-plugin": "Symfony Stopwatch plugin" + }, + "autoload": { + "psr-4": { + "Http\\Client\\Common\\": "src/" + } + }, + "scripts": { + "test": "vendor/bin/phpspec run", + "test-ci": "vendor/bin/phpspec run -c phpspec.ci.yml" + }, + "extra": { + "branch-alias": { + "dev-master": "1.9.x-dev" + } + } +} diff --git a/vendor/php-http/client-common/src/BatchClient.php b/vendor/php-http/client-common/src/BatchClient.php new file mode 100644 index 00000000..1aa99507 --- /dev/null +++ b/vendor/php-http/client-common/src/BatchClient.php @@ -0,0 +1,78 @@ + + */ +class BatchClient implements HttpClient +{ + /** + * @var HttpClient|ClientInterface + */ + private $client; + + /** + * @param HttpClient|ClientInterface $client + */ + public function __construct($client) + { + if (!($client instanceof HttpClient) && !($client instanceof ClientInterface)) { + throw new \LogicException('Client must be an instance of Http\\Client\\HttpClient or Psr\\Http\\Client\\ClientInterface'); + } + + $this->client = $client; + } + + /** + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request) + { + return $this->client->sendRequest($request); + } + + /** + * Send several requests. + * + * You may not assume that the requests are executed in a particular order. If the order matters + * for your application, use sendRequest sequentially. + * + * @param RequestInterface[] The requests to send + * + * @return BatchResult Containing one result per request + * + * @throws BatchException If one or more requests fails. The exception gives access to the + * BatchResult with a map of request to result for success, request to + * exception for failures + */ + public function sendRequests(array $requests) + { + $batchResult = new BatchResult(); + + foreach ($requests as $request) { + try { + $response = $this->sendRequest($request); + $batchResult = $batchResult->addResponse($request, $response); + } catch (Exception $e) { + $batchResult = $batchResult->addException($request, $e); + } + } + + if ($batchResult->hasExceptions()) { + throw new BatchException($batchResult); + } + + return $batchResult; + } +} diff --git a/vendor/php-http/client-common/src/BatchResult.php b/vendor/php-http/client-common/src/BatchResult.php new file mode 100644 index 00000000..710611d6 --- /dev/null +++ b/vendor/php-http/client-common/src/BatchResult.php @@ -0,0 +1,181 @@ + + */ +final class BatchResult +{ + /** + * @var \SplObjectStorage + */ + private $responses; + + /** + * @var \SplObjectStorage + */ + private $exceptions; + + public function __construct() + { + $this->responses = new \SplObjectStorage(); + $this->exceptions = new \SplObjectStorage(); + } + + /** + * Checks if there are any successful responses at all. + * + * @return bool + */ + public function hasResponses() + { + return $this->responses->count() > 0; + } + + /** + * Returns all successful responses. + * + * @return ResponseInterface[] + */ + public function getResponses() + { + $responses = []; + + foreach ($this->responses as $request) { + $responses[] = $this->responses[$request]; + } + + return $responses; + } + + /** + * Checks if there is a successful response for a request. + * + * @param RequestInterface $request + * + * @return bool + */ + public function isSuccessful(RequestInterface $request) + { + return $this->responses->contains($request); + } + + /** + * Returns the response for a successful request. + * + * @param RequestInterface $request + * + * @return ResponseInterface + * + * @throws \UnexpectedValueException If request was not part of the batch or failed + */ + public function getResponseFor(RequestInterface $request) + { + try { + return $this->responses[$request]; + } catch (\UnexpectedValueException $e) { + throw new \UnexpectedValueException('Request not found', $e->getCode(), $e); + } + } + + /** + * Adds a response in an immutable way. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * + * @return BatchResult the new BatchResult with this request-response pair added to it + */ + public function addResponse(RequestInterface $request, ResponseInterface $response) + { + $new = clone $this; + $new->responses->attach($request, $response); + + return $new; + } + + /** + * Checks if there are any unsuccessful requests at all. + * + * @return bool + */ + public function hasExceptions() + { + return $this->exceptions->count() > 0; + } + + /** + * Returns all exceptions for the unsuccessful requests. + * + * @return Exception[] + */ + public function getExceptions() + { + $exceptions = []; + + foreach ($this->exceptions as $request) { + $exceptions[] = $this->exceptions[$request]; + } + + return $exceptions; + } + + /** + * Checks if there is an exception for a request, meaning the request failed. + * + * @param RequestInterface $request + * + * @return bool + */ + public function isFailed(RequestInterface $request) + { + return $this->exceptions->contains($request); + } + + /** + * Returns the exception for a failed request. + * + * @param RequestInterface $request + * + * @return Exception + * + * @throws \UnexpectedValueException If request was not part of the batch or was successful + */ + public function getExceptionFor(RequestInterface $request) + { + try { + return $this->exceptions[$request]; + } catch (\UnexpectedValueException $e) { + throw new \UnexpectedValueException('Request not found', $e->getCode(), $e); + } + } + + /** + * Adds an exception in an immutable way. + * + * @param RequestInterface $request + * @param Exception $exception + * + * @return BatchResult the new BatchResult with this request-exception pair added to it + */ + public function addException(RequestInterface $request, Exception $exception) + { + $new = clone $this; + $new->exceptions->attach($request, $exception); + + return $new; + } + + public function __clone() + { + $this->responses = clone $this->responses; + $this->exceptions = clone $this->exceptions; + } +} diff --git a/vendor/php-http/client-common/src/Deferred.php b/vendor/php-http/client-common/src/Deferred.php new file mode 100644 index 00000000..075a30e1 --- /dev/null +++ b/vendor/php-http/client-common/src/Deferred.php @@ -0,0 +1,131 @@ +waitCallback = $waitCallback; + $this->state = Promise::PENDING; + $this->onFulfilledCallbacks = []; + $this->onRejectedCallbacks = []; + } + + /** + * {@inheritdoc} + */ + public function then(callable $onFulfilled = null, callable $onRejected = null) + { + $deferred = new self($this->waitCallback); + + $this->onFulfilledCallbacks[] = function (ResponseInterface $response) use ($onFulfilled, $deferred) { + try { + if (null !== $onFulfilled) { + $response = $onFulfilled($response); + } + $deferred->resolve($response); + } catch (Exception $exception) { + $deferred->reject($exception); + } + }; + + $this->onRejectedCallbacks[] = function (Exception $exception) use ($onRejected, $deferred) { + try { + if (null !== $onRejected) { + $response = $onRejected($exception); + $deferred->resolve($response); + + return; + } + $deferred->reject($exception); + } catch (Exception $newException) { + $deferred->reject($newException); + } + }; + + return $deferred; + } + + /** + * {@inheritdoc} + */ + public function getState() + { + return $this->state; + } + + /** + * Resolve this deferred with a Response. + */ + public function resolve(ResponseInterface $response) + { + if (self::PENDING !== $this->state) { + return; + } + + $this->value = $response; + $this->state = self::FULFILLED; + + foreach ($this->onFulfilledCallbacks as $onFulfilledCallback) { + $onFulfilledCallback($response); + } + } + + /** + * Reject this deferred with an Exception. + */ + public function reject(Exception $exception) + { + if (self::PENDING !== $this->state) { + return; + } + + $this->failure = $exception; + $this->state = self::REJECTED; + + foreach ($this->onRejectedCallbacks as $onRejectedCallback) { + $onRejectedCallback($exception); + } + } + + /** + * {@inheritdoc} + */ + public function wait($unwrap = true) + { + if (self::PENDING === $this->state) { + $callback = $this->waitCallback; + $callback(); + } + + if (!$unwrap) { + return; + } + + if (self::FULFILLED === $this->state) { + return $this->value; + } + + throw $this->failure; + } +} diff --git a/vendor/php-http/client-common/src/EmulatedHttpAsyncClient.php b/vendor/php-http/client-common/src/EmulatedHttpAsyncClient.php new file mode 100644 index 00000000..39f89cc0 --- /dev/null +++ b/vendor/php-http/client-common/src/EmulatedHttpAsyncClient.php @@ -0,0 +1,32 @@ + + */ +class EmulatedHttpAsyncClient implements HttpClient, HttpAsyncClient +{ + use HttpAsyncClientEmulator; + use HttpClientDecorator; + + /** + * @param HttpClient|ClientInterface $httpClient + */ + public function __construct($httpClient) + { + if (!($httpClient instanceof HttpClient) && !($httpClient instanceof ClientInterface)) { + throw new \LogicException('Client must be an instance of Http\\Client\\HttpClient or Psr\\Http\\Client\\ClientInterface'); + } + + $this->httpClient = $httpClient; + } +} diff --git a/vendor/php-http/client-common/src/EmulatedHttpClient.php b/vendor/php-http/client-common/src/EmulatedHttpClient.php new file mode 100644 index 00000000..01046c83 --- /dev/null +++ b/vendor/php-http/client-common/src/EmulatedHttpClient.php @@ -0,0 +1,27 @@ + + */ +class EmulatedHttpClient implements HttpClient, HttpAsyncClient +{ + use HttpAsyncClientDecorator; + use HttpClientEmulator; + + /** + * @param HttpAsyncClient $httpAsyncClient + */ + public function __construct(HttpAsyncClient $httpAsyncClient) + { + $this->httpAsyncClient = $httpAsyncClient; + } +} diff --git a/vendor/php-http/client-common/src/Exception/BatchException.php b/vendor/php-http/client-common/src/Exception/BatchException.php new file mode 100644 index 00000000..66a92719 --- /dev/null +++ b/vendor/php-http/client-common/src/Exception/BatchException.php @@ -0,0 +1,39 @@ + + */ +final class BatchException extends TransferException +{ + /** + * @var BatchResult + */ + private $result; + + /** + * @param BatchResult $result + */ + public function __construct(BatchResult $result) + { + $this->result = $result; + } + + /** + * Returns the BatchResult that contains all responses and exceptions. + * + * @return BatchResult + */ + public function getResult() + { + return $this->result; + } +} diff --git a/vendor/php-http/client-common/src/Exception/CircularRedirectionException.php b/vendor/php-http/client-common/src/Exception/CircularRedirectionException.php new file mode 100644 index 00000000..73ec521e --- /dev/null +++ b/vendor/php-http/client-common/src/Exception/CircularRedirectionException.php @@ -0,0 +1,14 @@ + + */ +class CircularRedirectionException extends HttpException +{ +} diff --git a/vendor/php-http/client-common/src/Exception/ClientErrorException.php b/vendor/php-http/client-common/src/Exception/ClientErrorException.php new file mode 100644 index 00000000..b1f6cc85 --- /dev/null +++ b/vendor/php-http/client-common/src/Exception/ClientErrorException.php @@ -0,0 +1,14 @@ + + */ +class ClientErrorException extends HttpException +{ +} diff --git a/vendor/php-http/client-common/src/Exception/HttpClientNotFoundException.php b/vendor/php-http/client-common/src/Exception/HttpClientNotFoundException.php new file mode 100644 index 00000000..5d33f983 --- /dev/null +++ b/vendor/php-http/client-common/src/Exception/HttpClientNotFoundException.php @@ -0,0 +1,14 @@ + + */ +class HttpClientNotFoundException extends TransferException +{ +} diff --git a/vendor/php-http/client-common/src/Exception/LoopException.php b/vendor/php-http/client-common/src/Exception/LoopException.php new file mode 100644 index 00000000..e834124d --- /dev/null +++ b/vendor/php-http/client-common/src/Exception/LoopException.php @@ -0,0 +1,14 @@ + + */ +class LoopException extends RequestException +{ +} diff --git a/vendor/php-http/client-common/src/Exception/MultipleRedirectionException.php b/vendor/php-http/client-common/src/Exception/MultipleRedirectionException.php new file mode 100644 index 00000000..ae514cd7 --- /dev/null +++ b/vendor/php-http/client-common/src/Exception/MultipleRedirectionException.php @@ -0,0 +1,14 @@ + + */ +class MultipleRedirectionException extends HttpException +{ +} diff --git a/vendor/php-http/client-common/src/Exception/ServerErrorException.php b/vendor/php-http/client-common/src/Exception/ServerErrorException.php new file mode 100644 index 00000000..665d7241 --- /dev/null +++ b/vendor/php-http/client-common/src/Exception/ServerErrorException.php @@ -0,0 +1,14 @@ + + */ +class ServerErrorException extends HttpException +{ +} diff --git a/vendor/php-http/client-common/src/FlexibleHttpClient.php b/vendor/php-http/client-common/src/FlexibleHttpClient.php new file mode 100644 index 00000000..d0a6e88c --- /dev/null +++ b/vendor/php-http/client-common/src/FlexibleHttpClient.php @@ -0,0 +1,40 @@ + + */ +final class FlexibleHttpClient implements HttpClient, HttpAsyncClient +{ + use HttpClientDecorator; + use HttpAsyncClientDecorator; + + /** + * @param HttpClient|HttpAsyncClient|ClientInterface $client + */ + public function __construct($client) + { + if (!($client instanceof HttpClient) && !($client instanceof HttpAsyncClient) && !($client instanceof ClientInterface)) { + throw new \LogicException('Client must be an instance of Http\\Client\\HttpClient or Http\\Client\\HttpAsyncClient'); + } + + $this->httpClient = $client; + $this->httpAsyncClient = $client; + + if (!($this->httpClient instanceof HttpClient) && !($client instanceof ClientInterface)) { + $this->httpClient = new EmulatedHttpClient($this->httpClient); + } + + if (!($this->httpAsyncClient instanceof HttpAsyncClient)) { + $this->httpAsyncClient = new EmulatedHttpAsyncClient($this->httpAsyncClient); + } + } +} diff --git a/vendor/php-http/client-common/src/HttpAsyncClientDecorator.php b/vendor/php-http/client-common/src/HttpAsyncClientDecorator.php new file mode 100644 index 00000000..6eb576cd --- /dev/null +++ b/vendor/php-http/client-common/src/HttpAsyncClientDecorator.php @@ -0,0 +1,29 @@ + + */ +trait HttpAsyncClientDecorator +{ + /** + * @var HttpAsyncClient + */ + protected $httpAsyncClient; + + /** + * {@inheritdoc} + * + * @see HttpAsyncClient::sendAsyncRequest + */ + public function sendAsyncRequest(RequestInterface $request) + { + return $this->httpAsyncClient->sendAsyncRequest($request); + } +} diff --git a/vendor/php-http/client-common/src/HttpAsyncClientEmulator.php b/vendor/php-http/client-common/src/HttpAsyncClientEmulator.php new file mode 100644 index 00000000..c0ba354f --- /dev/null +++ b/vendor/php-http/client-common/src/HttpAsyncClientEmulator.php @@ -0,0 +1,36 @@ + + */ +trait HttpAsyncClientEmulator +{ + /** + * {@inheritdoc} + * + * @see HttpClient::sendRequest + */ + abstract public function sendRequest(RequestInterface $request); + + /** + * {@inheritdoc} + * + * @see HttpAsyncClient::sendAsyncRequest + */ + public function sendAsyncRequest(RequestInterface $request) + { + try { + return new Promise\HttpFulfilledPromise($this->sendRequest($request)); + } catch (Exception $e) { + return new Promise\HttpRejectedPromise($e); + } + } +} diff --git a/vendor/php-http/client-common/src/HttpClientDecorator.php b/vendor/php-http/client-common/src/HttpClientDecorator.php new file mode 100644 index 00000000..0b7e48e5 --- /dev/null +++ b/vendor/php-http/client-common/src/HttpClientDecorator.php @@ -0,0 +1,30 @@ + + */ +trait HttpClientDecorator +{ + /** + * @var HttpClient|ClientInterface + */ + protected $httpClient; + + /** + * {@inheritdoc} + * + * @see HttpClient::sendRequest + */ + public function sendRequest(RequestInterface $request) + { + return $this->httpClient->sendRequest($request); + } +} diff --git a/vendor/php-http/client-common/src/HttpClientEmulator.php b/vendor/php-http/client-common/src/HttpClientEmulator.php new file mode 100644 index 00000000..dbec1aba --- /dev/null +++ b/vendor/php-http/client-common/src/HttpClientEmulator.php @@ -0,0 +1,32 @@ + + */ +trait HttpClientEmulator +{ + /** + * {@inheritdoc} + * + * @see HttpClient::sendRequest + */ + public function sendRequest(RequestInterface $request) + { + $promise = $this->sendAsyncRequest($request); + + return $promise->wait(); + } + + /** + * {@inheritdoc} + * + * @see HttpAsyncClient::sendAsyncRequest + */ + abstract public function sendAsyncRequest(RequestInterface $request); +} diff --git a/vendor/php-http/client-common/src/HttpClientPool.php b/vendor/php-http/client-common/src/HttpClientPool.php new file mode 100644 index 00000000..7ac292ca --- /dev/null +++ b/vendor/php-http/client-common/src/HttpClientPool.php @@ -0,0 +1,59 @@ +clientPool[] = $client; + } + + /** + * Return an http client given a specific strategy. + * + * @throws HttpClientNotFoundException When no http client has been found into the pool + * + * @return HttpClientPoolItem Return a http client that can do both sync or async + */ + abstract protected function chooseHttpClient(); + + /** + * {@inheritdoc} + */ + public function sendAsyncRequest(RequestInterface $request) + { + return $this->chooseHttpClient()->sendAsyncRequest($request); + } + + /** + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request) + { + return $this->chooseHttpClient()->sendRequest($request); + } +} diff --git a/vendor/php-http/client-common/src/HttpClientPool/LeastUsedClientPool.php b/vendor/php-http/client-common/src/HttpClientPool/LeastUsedClientPool.php new file mode 100644 index 00000000..6299cceb --- /dev/null +++ b/vendor/php-http/client-common/src/HttpClientPool/LeastUsedClientPool.php @@ -0,0 +1,45 @@ + + */ +final class LeastUsedClientPool extends HttpClientPool +{ + /** + * {@inheritdoc} + */ + protected function chooseHttpClient() + { + $clientPool = array_filter($this->clientPool, function (HttpClientPoolItem $clientPoolItem) { + return !$clientPoolItem->isDisabled(); + }); + + if (0 === count($clientPool)) { + throw new HttpClientNotFoundException('Cannot choose a http client as there is no one present in the pool'); + } + + usort($clientPool, function (HttpClientPoolItem $clientA, HttpClientPoolItem $clientB) { + if ($clientA->getSendingRequestCount() === $clientB->getSendingRequestCount()) { + return 0; + } + + if ($clientA->getSendingRequestCount() < $clientB->getSendingRequestCount()) { + return -1; + } + + return 1; + }); + + return reset($clientPool); + } +} diff --git a/vendor/php-http/client-common/src/HttpClientPool/RandomClientPool.php b/vendor/php-http/client-common/src/HttpClientPool/RandomClientPool.php new file mode 100644 index 00000000..3255f865 --- /dev/null +++ b/vendor/php-http/client-common/src/HttpClientPool/RandomClientPool.php @@ -0,0 +1,31 @@ + + */ +final class RandomClientPool extends HttpClientPool +{ + /** + * {@inheritdoc} + */ + protected function chooseHttpClient() + { + $clientPool = array_filter($this->clientPool, function (HttpClientPoolItem $clientPoolItem) { + return !$clientPoolItem->isDisabled(); + }); + + if (0 === count($clientPool)) { + throw new HttpClientNotFoundException('Cannot choose a http client as there is no one present in the pool'); + } + + return $clientPool[array_rand($clientPool)]; + } +} diff --git a/vendor/php-http/client-common/src/HttpClientPool/RoundRobinClientPool.php b/vendor/php-http/client-common/src/HttpClientPool/RoundRobinClientPool.php new file mode 100644 index 00000000..8d8e40a0 --- /dev/null +++ b/vendor/php-http/client-common/src/HttpClientPool/RoundRobinClientPool.php @@ -0,0 +1,41 @@ + + */ +final class RoundRobinClientPool extends HttpClientPool +{ + /** + * {@inheritdoc} + */ + protected function chooseHttpClient() + { + $last = current($this->clientPool); + + do { + $client = next($this->clientPool); + + if (false === $client) { + $client = reset($this->clientPool); + + if (false === $client) { + throw new HttpClientNotFoundException('Cannot choose a http client as there is no one present in the pool'); + } + } + + // Case when there is only one and the last one has been disabled + if ($last === $client && $client->isDisabled()) { + throw new HttpClientNotFoundException('Cannot choose a http client as there is no one enabled in the pool'); + } + } while ($client->isDisabled()); + + return $client; + } +} diff --git a/vendor/php-http/client-common/src/HttpClientPoolItem.php b/vendor/php-http/client-common/src/HttpClientPoolItem.php new file mode 100644 index 00000000..09cd6ddf --- /dev/null +++ b/vendor/php-http/client-common/src/HttpClientPoolItem.php @@ -0,0 +1,178 @@ + + */ +class HttpClientPoolItem implements HttpClient, HttpAsyncClient +{ + /** + * @var int Number of request this client is currently sending + */ + private $sendingRequestCount = 0; + + /** + * @var \DateTime|null Time when this client has been disabled or null if enable + */ + private $disabledAt; + + /** + * @var int|null Number of seconds after this client is reenable, by default null: never reenable this client + */ + private $reenableAfter; + + /** + * @var FlexibleHttpClient A http client responding to async and sync request + */ + private $client; + + /** + * @param HttpClient|HttpAsyncClient $client + * @param null|int $reenableAfter Number of seconds after this client is reenable + */ + public function __construct($client, $reenableAfter = null) + { + $this->client = new FlexibleHttpClient($client); + $this->reenableAfter = $reenableAfter; + } + + /** + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request) + { + if ($this->isDisabled()) { + throw new Exception\RequestException('Cannot send the request as this client has been disabled', $request); + } + + try { + $this->incrementRequestCount(); + $response = $this->client->sendRequest($request); + $this->decrementRequestCount(); + } catch (Exception $e) { + $this->disable(); + $this->decrementRequestCount(); + + throw $e; + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function sendAsyncRequest(RequestInterface $request) + { + if ($this->isDisabled()) { + throw new Exception\RequestException('Cannot send the request as this client has been disabled', $request); + } + + $this->incrementRequestCount(); + + return $this->client->sendAsyncRequest($request)->then(function ($response) { + $this->decrementRequestCount(); + + return $response; + }, function ($exception) { + $this->disable(); + $this->decrementRequestCount(); + + throw $exception; + }); + } + + /** + * Whether this client is disabled or not. + * + * Will also reactivate this client if possible + * + * @internal + * + * @return bool + */ + public function isDisabled() + { + $disabledAt = $this->getDisabledAt(); + + if (null !== $this->reenableAfter && null !== $disabledAt) { + // Reenable after a certain time + $now = new \DateTime(); + + if (($now->getTimestamp() - $disabledAt->getTimestamp()) >= $this->reenableAfter) { + $this->enable(); + + return false; + } + + return true; + } + + return null !== $disabledAt; + } + + /** + * Get current number of request that is send by the underlying http client. + * + * @internal + * + * @return int + */ + public function getSendingRequestCount() + { + return $this->sendingRequestCount; + } + + /** + * Return when this client has been disabled or null if it's enabled. + * + * @return \DateTime|null + */ + private function getDisabledAt() + { + return $this->disabledAt; + } + + /** + * Increment the request count. + */ + private function incrementRequestCount() + { + ++$this->sendingRequestCount; + } + + /** + * Decrement the request count. + */ + private function decrementRequestCount() + { + --$this->sendingRequestCount; + } + + /** + * Enable the current client. + */ + private function enable() + { + $this->disabledAt = null; + } + + /** + * Disable the current client. + */ + private function disable() + { + $this->disabledAt = new \DateTime('now'); + } +} diff --git a/vendor/php-http/client-common/src/HttpClientRouter.php b/vendor/php-http/client-common/src/HttpClientRouter.php new file mode 100644 index 00000000..9f721336 --- /dev/null +++ b/vendor/php-http/client-common/src/HttpClientRouter.php @@ -0,0 +1,74 @@ + + */ +final class HttpClientRouter implements HttpClient, HttpAsyncClient +{ + /** + * @var array + */ + private $clients = []; + + /** + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request) + { + $client = $this->chooseHttpClient($request); + + return $client->sendRequest($request); + } + + /** + * {@inheritdoc} + */ + public function sendAsyncRequest(RequestInterface $request) + { + $client = $this->chooseHttpClient($request); + + return $client->sendAsyncRequest($request); + } + + /** + * Add a client to the router. + * + * @param HttpClient|HttpAsyncClient $client + * @param RequestMatcher $requestMatcher + */ + public function addClient($client, RequestMatcher $requestMatcher) + { + $this->clients[] = [ + 'matcher' => $requestMatcher, + 'client' => new FlexibleHttpClient($client), + ]; + } + + /** + * Choose an HTTP client given a specific request. + * + * @param RequestInterface $request + * + * @return HttpClient|HttpAsyncClient + */ + protected function chooseHttpClient(RequestInterface $request) + { + foreach ($this->clients as $client) { + if ($client['matcher']->matches($request)) { + return $client['client']; + } + } + + throw new RequestException('No client found for the specified request', $request); + } +} diff --git a/vendor/php-http/client-common/src/HttpMethodsClient.php b/vendor/php-http/client-common/src/HttpMethodsClient.php new file mode 100644 index 00000000..c462c107 --- /dev/null +++ b/vendor/php-http/client-common/src/HttpMethodsClient.php @@ -0,0 +1,210 @@ +get('/foo') + * ->post('/bar') + * ; + * + * The client also exposes the sendRequest methods of the wrapped HttpClient. + * + * @author Márk Sági-Kazár + * @author David Buchmann + */ +class HttpMethodsClient implements HttpClient +{ + /** + * @var HttpClient|ClientInterface + */ + private $httpClient; + + /** + * @var RequestFactory + */ + private $requestFactory; + + /** + * @param HttpClient|ClientInterface $httpClient The client to send requests with + * @param RequestFactory $requestFactory The message factory to create requests + */ + public function __construct($httpClient, RequestFactory $requestFactory) + { + if (!($httpClient instanceof HttpClient) && !($httpClient instanceof ClientInterface)) { + throw new \LogicException('Client must be an instance of Http\\Client\\HttpClient or Psr\\Http\\Client\\ClientInterface'); + } + + $this->httpClient = $httpClient; + $this->requestFactory = $requestFactory; + } + + /** + * Sends a GET request. + * + * @param string|UriInterface $uri + * @param array $headers + * + * @throws Exception + * + * @return ResponseInterface + */ + public function get($uri, array $headers = []) + { + return $this->send('GET', $uri, $headers, null); + } + + /** + * Sends an HEAD request. + * + * @param string|UriInterface $uri + * @param array $headers + * + * @throws Exception + * + * @return ResponseInterface + */ + public function head($uri, array $headers = []) + { + return $this->send('HEAD', $uri, $headers, null); + } + + /** + * Sends a TRACE request. + * + * @param string|UriInterface $uri + * @param array $headers + * + * @throws Exception + * + * @return ResponseInterface + */ + public function trace($uri, array $headers = []) + { + return $this->send('TRACE', $uri, $headers, null); + } + + /** + * Sends a POST request. + * + * @param string|UriInterface $uri + * @param array $headers + * @param string|StreamInterface|null $body + * + * @throws Exception + * + * @return ResponseInterface + */ + public function post($uri, array $headers = [], $body = null) + { + return $this->send('POST', $uri, $headers, $body); + } + + /** + * Sends a PUT request. + * + * @param string|UriInterface $uri + * @param array $headers + * @param string|StreamInterface|null $body + * + * @throws Exception + * + * @return ResponseInterface + */ + public function put($uri, array $headers = [], $body = null) + { + return $this->send('PUT', $uri, $headers, $body); + } + + /** + * Sends a PATCH request. + * + * @param string|UriInterface $uri + * @param array $headers + * @param string|StreamInterface|null $body + * + * @throws Exception + * + * @return ResponseInterface + */ + public function patch($uri, array $headers = [], $body = null) + { + return $this->send('PATCH', $uri, $headers, $body); + } + + /** + * Sends a DELETE request. + * + * @param string|UriInterface $uri + * @param array $headers + * @param string|StreamInterface|null $body + * + * @throws Exception + * + * @return ResponseInterface + */ + public function delete($uri, array $headers = [], $body = null) + { + return $this->send('DELETE', $uri, $headers, $body); + } + + /** + * Sends an OPTIONS request. + * + * @param string|UriInterface $uri + * @param array $headers + * @param string|StreamInterface|null $body + * + * @throws Exception + * + * @return ResponseInterface + */ + public function options($uri, array $headers = [], $body = null) + { + return $this->send('OPTIONS', $uri, $headers, $body); + } + + /** + * Sends a request with any HTTP method. + * + * @param string $method HTTP method to use + * @param string|UriInterface $uri + * @param array $headers + * @param string|StreamInterface|null $body + * + * @throws Exception + * + * @return ResponseInterface + */ + public function send($method, $uri, array $headers = [], $body = null) + { + return $this->sendRequest($this->requestFactory->createRequest( + $method, + $uri, + $headers, + $body + )); + } + + /** + * Forward to the underlying HttpClient. + * + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request) + { + return $this->httpClient->sendRequest($request); + } +} diff --git a/vendor/php-http/client-common/src/Plugin.php b/vendor/php-http/client-common/src/Plugin.php new file mode 100644 index 00000000..89a2a622 --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin.php @@ -0,0 +1,32 @@ + + */ +interface Plugin +{ + /** + * Handle the request and return the response coming from the next callable. + * + * @see http://docs.php-http.org/en/latest/plugins/build-your-own.html + * + * @param RequestInterface $request + * @param callable $next Next middleware in the chain, the request is passed as the first argument + * @param callable $first First middleware in the chain, used to to restart a request + * + * @return Promise Resolves a PSR-7 Response or fails with an Http\Client\Exception (The same as HttpAsyncClient). + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first); +} diff --git a/vendor/php-http/client-common/src/Plugin/AddHostPlugin.php b/vendor/php-http/client-common/src/Plugin/AddHostPlugin.php new file mode 100644 index 00000000..29ab8ae8 --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/AddHostPlugin.php @@ -0,0 +1,77 @@ + + */ +final class AddHostPlugin implements Plugin +{ + /** + * @var UriInterface + */ + private $host; + + /** + * @var bool + */ + private $replace; + + /** + * @param UriInterface $host + * @param array $config { + * + * @var bool $replace True will replace all hosts, false will only add host when none is specified. + * } + */ + public function __construct(UriInterface $host, array $config = []) + { + if ('' === $host->getHost()) { + throw new \LogicException('Host can not be empty'); + } + + $this->host = $host; + + $resolver = new OptionsResolver(); + $this->configureOptions($resolver); + $options = $resolver->resolve($config); + + $this->replace = $options['replace']; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + if ($this->replace || '' === $request->getUri()->getHost()) { + $uri = $request->getUri() + ->withHost($this->host->getHost()) + ->withScheme($this->host->getScheme()) + ->withPort($this->host->getPort()) + ; + + $request = $request->withUri($uri); + } + + return $next($request); + } + + /** + * @param OptionsResolver $resolver + */ + private function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'replace' => false, + ]); + $resolver->setAllowedTypes('replace', 'bool'); + } +} diff --git a/vendor/php-http/client-common/src/Plugin/AddPathPlugin.php b/vendor/php-http/client-common/src/Plugin/AddPathPlugin.php new file mode 100644 index 00000000..0a1bca2b --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/AddPathPlugin.php @@ -0,0 +1,60 @@ + + */ +final class AddPathPlugin implements Plugin +{ + /** + * @var UriInterface + */ + private $uri; + + /** + * Stores identifiers of the already altered requests. + * + * @var array + */ + private $alteredRequests = []; + + /** + * @param UriInterface $uri + */ + public function __construct(UriInterface $uri) + { + if ('' === $uri->getPath()) { + throw new \LogicException('URI path cannot be empty'); + } + + if ('/' === substr($uri->getPath(), -1)) { + $uri = $uri->withPath(rtrim($uri->getPath(), '/')); + } + + $this->uri = $uri; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + $identifier = spl_object_hash((object) $first); + + if (!array_key_exists($identifier, $this->alteredRequests)) { + $request = $request->withUri($request->getUri() + ->withPath($this->uri->getPath().$request->getUri()->getPath()) + ); + $this->alteredRequests[$identifier] = $identifier; + } + + return $next($request); + } +} diff --git a/vendor/php-http/client-common/src/Plugin/AuthenticationPlugin.php b/vendor/php-http/client-common/src/Plugin/AuthenticationPlugin.php new file mode 100644 index 00000000..194712fc --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/AuthenticationPlugin.php @@ -0,0 +1,38 @@ + + */ +final class AuthenticationPlugin implements Plugin +{ + /** + * @var Authentication An authentication system + */ + private $authentication; + + /** + * @param Authentication $authentication + */ + public function __construct(Authentication $authentication) + { + $this->authentication = $authentication; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + $request = $this->authentication->authenticate($request); + + return $next($request); + } +} diff --git a/vendor/php-http/client-common/src/Plugin/BaseUriPlugin.php b/vendor/php-http/client-common/src/Plugin/BaseUriPlugin.php new file mode 100644 index 00000000..2c2a7752 --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/BaseUriPlugin.php @@ -0,0 +1,54 @@ + + */ +final class BaseUriPlugin implements Plugin +{ + /** + * @var AddHostPlugin + */ + private $addHostPlugin; + + /** + * @var AddPathPlugin|null + */ + private $addPathPlugin = null; + + /** + * @param UriInterface $uri Has to contain a host name and cans have a path. + * @param array $hostConfig Config for AddHostPlugin. @see AddHostPlugin::configureOptions + */ + public function __construct(UriInterface $uri, array $hostConfig = []) + { + $this->addHostPlugin = new AddHostPlugin($uri, $hostConfig); + + if (rtrim($uri->getPath(), '/')) { + $this->addPathPlugin = new AddPathPlugin($uri); + } + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + $addHostNext = function (RequestInterface $request) use ($next, $first) { + return $this->addHostPlugin->handleRequest($request, $next, $first); + }; + + if ($this->addPathPlugin) { + return $this->addPathPlugin->handleRequest($request, $addHostNext, $first); + } + + return $addHostNext($request); + } +} diff --git a/vendor/php-http/client-common/src/Plugin/ContentLengthPlugin.php b/vendor/php-http/client-common/src/Plugin/ContentLengthPlugin.php new file mode 100644 index 00000000..0f7aafae --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/ContentLengthPlugin.php @@ -0,0 +1,36 @@ + + */ +final class ContentLengthPlugin implements Plugin +{ + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + if (!$request->hasHeader('Content-Length')) { + $stream = $request->getBody(); + + // Cannot determine the size so we use a chunk stream + if (null === $stream->getSize()) { + $stream = new ChunkStream($stream); + $request = $request->withBody($stream); + $request = $request->withAddedHeader('Transfer-Encoding', 'chunked'); + } else { + $request = $request->withHeader('Content-Length', (string) $stream->getSize()); + } + } + + return $next($request); + } +} diff --git a/vendor/php-http/client-common/src/Plugin/ContentTypePlugin.php b/vendor/php-http/client-common/src/Plugin/ContentTypePlugin.php new file mode 100644 index 00000000..8ef1d62b --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/ContentTypePlugin.php @@ -0,0 +1,123 @@ + + */ +final class ContentTypePlugin implements Plugin +{ + /** + * Allow to disable the content type detection when stream is too large (as it can consume a lot of resource). + * + * @var bool + * + * true skip the content type detection + * false detect the content type (default value) + */ + protected $skipDetection; + + /** + * Determine the size stream limit for which the detection as to be skipped (default to 16Mb). + * + * @var int + */ + protected $sizeLimit; + + /** + * @param array $config { + * + * @var bool $skip_detection True skip detection if stream size is bigger than $size_limit. + * @var int $size_limit size stream limit for which the detection as to be skipped. + * } + */ + public function __construct(array $config = []) + { + $resolver = new OptionsResolver(); + $resolver->setDefaults([ + 'skip_detection' => false, + 'size_limit' => 16000000, + ]); + $resolver->setAllowedTypes('skip_detection', 'bool'); + $resolver->setAllowedTypes('size_limit', 'int'); + + $options = $resolver->resolve($config); + + $this->skipDetection = $options['skip_detection']; + $this->sizeLimit = $options['size_limit']; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + if (!$request->hasHeader('Content-Type')) { + $stream = $request->getBody(); + $streamSize = $stream->getSize(); + + if (!$stream->isSeekable()) { + return $next($request); + } + + if (0 === $streamSize) { + return $next($request); + } + + if ($this->skipDetection && (null === $streamSize || $streamSize >= $this->sizeLimit)) { + return $next($request); + } + + if ($this->isJson($stream)) { + $request = $request->withHeader('Content-Type', 'application/json'); + + return $next($request); + } + + if ($this->isXml($stream)) { + $request = $request->withHeader('Content-Type', 'application/xml'); + + return $next($request); + } + } + + return $next($request); + } + + /** + * @param $stream StreamInterface + * + * @return bool + */ + private function isJson($stream) + { + $stream->rewind(); + + json_decode($stream->getContents()); + + return JSON_ERROR_NONE === json_last_error(); + } + + /** + * @param $stream StreamInterface + * + * @return \SimpleXMLElement|false + */ + private function isXml($stream) + { + $stream->rewind(); + + $previousValue = libxml_use_internal_errors(true); + $isXml = simplexml_load_string($stream->getContents()); + libxml_use_internal_errors($previousValue); + + return $isXml; + } +} diff --git a/vendor/php-http/client-common/src/Plugin/CookiePlugin.php b/vendor/php-http/client-common/src/Plugin/CookiePlugin.php new file mode 100644 index 00000000..156532ab --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/CookiePlugin.php @@ -0,0 +1,185 @@ + + */ +final class CookiePlugin implements Plugin +{ + /** + * Cookie storage. + * + * @var CookieJar + */ + private $cookieJar; + + /** + * @param CookieJar $cookieJar + */ + public function __construct(CookieJar $cookieJar) + { + $this->cookieJar = $cookieJar; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + $cookies = []; + foreach ($this->cookieJar->getCookies() as $cookie) { + if ($cookie->isExpired()) { + continue; + } + + if (!$cookie->matchDomain($request->getUri()->getHost())) { + continue; + } + + if (!$cookie->matchPath($request->getUri()->getPath())) { + continue; + } + + if ($cookie->isSecure() && ('https' !== $request->getUri()->getScheme())) { + continue; + } + + $cookies[] = sprintf('%s=%s', $cookie->getName(), $cookie->getValue()); + } + + if (!empty($cookies)) { + $request = $request->withAddedHeader('Cookie', implode('; ', array_unique($cookies))); + } + + return $next($request)->then(function (ResponseInterface $response) use ($request) { + if ($response->hasHeader('Set-Cookie')) { + $setCookies = $response->getHeader('Set-Cookie'); + + foreach ($setCookies as $setCookie) { + $cookie = $this->createCookie($request, $setCookie); + + // Cookie invalid do not use it + if (null === $cookie) { + continue; + } + + // Restrict setting cookie from another domain + if (!preg_match("/\.{$cookie->getDomain()}$/", '.'.$request->getUri()->getHost())) { + continue; + } + + $this->cookieJar->addCookie($cookie); + } + } + + return $response; + }); + } + + /** + * Creates a cookie from a string. + * + * @param RequestInterface $request + * @param $setCookie + * + * @return Cookie|null + * + * @throws TransferException + */ + private function createCookie(RequestInterface $request, $setCookie) + { + $parts = array_map('trim', explode(';', $setCookie)); + + if (empty($parts) || !strpos($parts[0], '=')) { + return; + } + + list($name, $cookieValue) = $this->createValueKey(array_shift($parts)); + + $maxAge = null; + $expires = null; + $domain = $request->getUri()->getHost(); + $path = $request->getUri()->getPath(); + $secure = false; + $httpOnly = false; + + // Add the cookie pieces into the parsed data array + foreach ($parts as $part) { + list($key, $value) = $this->createValueKey($part); + + switch (strtolower($key)) { + case 'expires': + try { + $expires = CookieUtil::parseDate($value); + } catch (UnexpectedValueException $e) { + throw new TransferException( + sprintf( + 'Cookie header `%s` expires value `%s` could not be converted to date', + $name, + $value + ), + 0, + $e + ); + } + + break; + + case 'max-age': + $maxAge = (int) $value; + + break; + + case 'domain': + $domain = $value; + + break; + + case 'path': + $path = $value; + + break; + + case 'secure': + $secure = true; + + break; + + case 'httponly': + $httpOnly = true; + + break; + } + } + + return new Cookie($name, $cookieValue, $maxAge, $domain, $path, $secure, $httpOnly, $expires); + } + + /** + * Separates key/value pair from cookie. + * + * @param $part + * + * @return array + */ + private function createValueKey($part) + { + $parts = explode('=', $part, 2); + $key = trim($parts[0]); + $value = isset($parts[1]) ? trim($parts[1]) : true; + + return [$key, $value]; + } +} diff --git a/vendor/php-http/client-common/src/Plugin/DecoderPlugin.php b/vendor/php-http/client-common/src/Plugin/DecoderPlugin.php new file mode 100644 index 00000000..0239d402 --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/DecoderPlugin.php @@ -0,0 +1,144 @@ + + */ +final class DecoderPlugin implements Plugin +{ + /** + * @var bool Whether this plugin decode stream with value in the Content-Encoding header (default to true). + * + * If set to false only the Transfer-Encoding header will be used + */ + private $useContentEncoding; + + /** + * @param array $config { + * + * @var bool $use_content_encoding Whether this plugin should look at the Content-Encoding header first or only at the Transfer-Encoding (defaults to true). + * } + */ + public function __construct(array $config = []) + { + $resolver = new OptionsResolver(); + $resolver->setDefaults([ + 'use_content_encoding' => true, + ]); + $resolver->setAllowedTypes('use_content_encoding', 'bool'); + $options = $resolver->resolve($config); + + $this->useContentEncoding = $options['use_content_encoding']; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + $encodings = extension_loaded('zlib') ? ['gzip', 'deflate'] : ['identity']; + + if ($this->useContentEncoding) { + $request = $request->withHeader('Accept-Encoding', $encodings); + } + $encodings[] = 'chunked'; + $request = $request->withHeader('TE', $encodings); + + return $next($request)->then(function (ResponseInterface $response) { + return $this->decodeResponse($response); + }); + } + + /** + * Decode a response body given its Transfer-Encoding or Content-Encoding value. + * + * @param ResponseInterface $response Response to decode + * + * @return ResponseInterface New response decoded + */ + private function decodeResponse(ResponseInterface $response) + { + $response = $this->decodeOnEncodingHeader('Transfer-Encoding', $response); + + if ($this->useContentEncoding) { + $response = $this->decodeOnEncodingHeader('Content-Encoding', $response); + } + + return $response; + } + + /** + * Decode a response on a specific header (content encoding or transfer encoding mainly). + * + * @param string $headerName Name of the header + * @param ResponseInterface $response Response + * + * @return ResponseInterface A new instance of the response decoded + */ + private function decodeOnEncodingHeader($headerName, ResponseInterface $response) + { + if ($response->hasHeader($headerName)) { + $encodings = $response->getHeader($headerName); + $newEncodings = []; + + while ($encoding = array_pop($encodings)) { + $stream = $this->decorateStream($encoding, $response->getBody()); + + if (false === $stream) { + array_unshift($newEncodings, $encoding); + + continue; + } + + $response = $response->withBody($stream); + } + + if (\count($newEncodings) > 0) { + $response = $response->withHeader($headerName, $newEncodings); + } else { + $response = $response->withoutHeader($headerName); + } + } + + return $response; + } + + /** + * Decorate a stream given an encoding. + * + * @param string $encoding + * @param StreamInterface $stream + * + * @return StreamInterface|false A new stream interface or false if encoding is not supported + */ + private function decorateStream($encoding, StreamInterface $stream) + { + if ('chunked' === strtolower($encoding)) { + return new Encoding\DechunkStream($stream); + } + + if ('deflate' === strtolower($encoding)) { + return new Encoding\DecompressStream($stream); + } + + if ('gzip' === strtolower($encoding)) { + return new Encoding\GzipDecodeStream($stream); + } + + return false; + } +} diff --git a/vendor/php-http/client-common/src/Plugin/ErrorPlugin.php b/vendor/php-http/client-common/src/Plugin/ErrorPlugin.php new file mode 100644 index 00000000..bcc1c676 --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/ErrorPlugin.php @@ -0,0 +1,81 @@ + + */ +final class ErrorPlugin implements Plugin +{ + /** + * @var bool Whether this plugin should only throw 5XX Exceptions (default to false). + * + * If set to true 4XX Responses code will never throw an exception + */ + private $onlyServerException; + + /** + * @param array $config { + * + * @var bool only_server_exception Whether this plugin should only throw 5XX Exceptions (default to false). + * } + */ + public function __construct(array $config = []) + { + $resolver = new OptionsResolver(); + $resolver->setDefaults([ + 'only_server_exception' => false, + ]); + $resolver->setAllowedTypes('only_server_exception', 'bool'); + $options = $resolver->resolve($config); + + $this->onlyServerException = $options['only_server_exception']; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + $promise = $next($request); + + return $promise->then(function (ResponseInterface $response) use ($request) { + return $this->transformResponseToException($request, $response); + }); + } + + /** + * Transform response to an error if possible. + * + * @param RequestInterface $request Request of the call + * @param ResponseInterface $response Response of the call + * + * @throws ClientErrorException If response status code is a 4xx + * @throws ServerErrorException If response status code is a 5xx + * + * @return ResponseInterface If status code is not in 4xx or 5xx return response + */ + protected function transformResponseToException(RequestInterface $request, ResponseInterface $response) + { + if (!$this->onlyServerException && $response->getStatusCode() >= 400 && $response->getStatusCode() < 500) { + throw new ClientErrorException($response->getReasonPhrase(), $request, $response); + } + + if ($response->getStatusCode() >= 500 && $response->getStatusCode() < 600) { + throw new ServerErrorException($response->getReasonPhrase(), $request, $response); + } + + return $response; + } +} diff --git a/vendor/php-http/client-common/src/Plugin/HeaderAppendPlugin.php b/vendor/php-http/client-common/src/Plugin/HeaderAppendPlugin.php new file mode 100644 index 00000000..26fd8134 --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/HeaderAppendPlugin.php @@ -0,0 +1,45 @@ + + */ +final class HeaderAppendPlugin implements Plugin +{ + /** + * @var array + */ + private $headers = []; + + /** + * @param array $headers Hashmap of header name to header value + */ + public function __construct(array $headers) + { + $this->headers = $headers; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + foreach ($this->headers as $header => $headerValue) { + $request = $request->withAddedHeader($header, $headerValue); + } + + return $next($request); + } +} diff --git a/vendor/php-http/client-common/src/Plugin/HeaderDefaultsPlugin.php b/vendor/php-http/client-common/src/Plugin/HeaderDefaultsPlugin.php new file mode 100644 index 00000000..6dfc1115 --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/HeaderDefaultsPlugin.php @@ -0,0 +1,43 @@ + + */ +final class HeaderDefaultsPlugin implements Plugin +{ + /** + * @var array + */ + private $headers = []; + + /** + * @param array $headers Hashmap of header name to header value + */ + public function __construct(array $headers) + { + $this->headers = $headers; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + foreach ($this->headers as $header => $headerValue) { + if (!$request->hasHeader($header)) { + $request = $request->withHeader($header, $headerValue); + } + } + + return $next($request); + } +} diff --git a/vendor/php-http/client-common/src/Plugin/HeaderRemovePlugin.php b/vendor/php-http/client-common/src/Plugin/HeaderRemovePlugin.php new file mode 100644 index 00000000..fc9c19d1 --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/HeaderRemovePlugin.php @@ -0,0 +1,41 @@ + + */ +final class HeaderRemovePlugin implements Plugin +{ + /** + * @var array + */ + private $headers = []; + + /** + * @param array $headers List of header names to remove from the request + */ + public function __construct(array $headers) + { + $this->headers = $headers; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + foreach ($this->headers as $header) { + if ($request->hasHeader($header)) { + $request = $request->withoutHeader($header); + } + } + + return $next($request); + } +} diff --git a/vendor/php-http/client-common/src/Plugin/HeaderSetPlugin.php b/vendor/php-http/client-common/src/Plugin/HeaderSetPlugin.php new file mode 100644 index 00000000..75f11d40 --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/HeaderSetPlugin.php @@ -0,0 +1,41 @@ + + */ +final class HeaderSetPlugin implements Plugin +{ + /** + * @var array + */ + private $headers = []; + + /** + * @param array $headers Hashmap of header name to header value + */ + public function __construct(array $headers) + { + $this->headers = $headers; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + foreach ($this->headers as $header => $headerValue) { + $request = $request->withHeader($header, $headerValue); + } + + return $next($request); + } +} diff --git a/vendor/php-http/client-common/src/Plugin/HistoryPlugin.php b/vendor/php-http/client-common/src/Plugin/HistoryPlugin.php new file mode 100644 index 00000000..5abddbd8 --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/HistoryPlugin.php @@ -0,0 +1,49 @@ + + */ +final class HistoryPlugin implements Plugin +{ + /** + * Journal use to store request / responses / exception. + * + * @var Journal + */ + private $journal; + + /** + * @param Journal $journal + */ + public function __construct(Journal $journal) + { + $this->journal = $journal; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + $journal = $this->journal; + + return $next($request)->then(function (ResponseInterface $response) use ($request, $journal) { + $journal->addSuccess($request, $response); + + return $response; + }, function (Exception $exception) use ($request, $journal) { + $journal->addFailure($request, $exception); + + throw $exception; + }); + } +} diff --git a/vendor/php-http/client-common/src/Plugin/Journal.php b/vendor/php-http/client-common/src/Plugin/Journal.php new file mode 100644 index 00000000..15f30956 --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/Journal.php @@ -0,0 +1,31 @@ + + */ +interface Journal +{ + /** + * Record a successful call. + * + * @param RequestInterface $request Request use to make the call + * @param ResponseInterface $response Response returned by the call + */ + public function addSuccess(RequestInterface $request, ResponseInterface $response); + + /** + * Record a failed call. + * + * @param RequestInterface $request Request use to make the call + * @param Exception $exception Exception returned by the call + */ + public function addFailure(RequestInterface $request, Exception $exception); +} diff --git a/vendor/php-http/client-common/src/Plugin/QueryDefaultsPlugin.php b/vendor/php-http/client-common/src/Plugin/QueryDefaultsPlugin.php new file mode 100644 index 00000000..d9c06d6b --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/QueryDefaultsPlugin.php @@ -0,0 +1,47 @@ + + */ +final class QueryDefaultsPlugin implements Plugin +{ + /** + * @var array + */ + private $queryParams = []; + + /** + * @param array $queryParams Hashmap of query name to query value. Names and values must not be url encoded as + * this plugin will encode them + */ + public function __construct(array $queryParams) + { + $this->queryParams = $queryParams; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + $uri = $request->getUri(); + + parse_str($uri->getQuery(), $query); + $query += $this->queryParams; + + $request = $request->withUri( + $uri->withQuery(http_build_query($query)) + ); + + return $next($request); + } +} diff --git a/vendor/php-http/client-common/src/Plugin/RedirectPlugin.php b/vendor/php-http/client-common/src/Plugin/RedirectPlugin.php new file mode 100644 index 00000000..d2f442ed --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/RedirectPlugin.php @@ -0,0 +1,270 @@ + + */ +class RedirectPlugin implements Plugin +{ + /** + * Rule on how to redirect, change method for the new request. + * + * @var array + */ + protected $redirectCodes = [ + 300 => [ + 'switch' => [ + 'unless' => ['GET', 'HEAD'], + 'to' => 'GET', + ], + 'multiple' => true, + 'permanent' => false, + ], + 301 => [ + 'switch' => [ + 'unless' => ['GET', 'HEAD'], + 'to' => 'GET', + ], + 'multiple' => false, + 'permanent' => true, + ], + 302 => [ + 'switch' => [ + 'unless' => ['GET', 'HEAD'], + 'to' => 'GET', + ], + 'multiple' => false, + 'permanent' => false, + ], + 303 => [ + 'switch' => [ + 'unless' => ['GET', 'HEAD'], + 'to' => 'GET', + ], + 'multiple' => false, + 'permanent' => false, + ], + 307 => [ + 'switch' => false, + 'multiple' => false, + 'permanent' => false, + ], + 308 => [ + 'switch' => false, + 'multiple' => false, + 'permanent' => true, + ], + ]; + + /** + * Determine how header should be preserved from old request. + * + * @var bool|array + * + * true will keep all previous headers (default value) + * false will ditch all previous headers + * string[] will keep only headers with the specified names + */ + protected $preserveHeader; + + /** + * Store all previous redirect from 301 / 308 status code. + * + * @var array + */ + protected $redirectStorage = []; + + /** + * Whether the location header must be directly used for a multiple redirection status code (300). + * + * @var bool + */ + protected $useDefaultForMultiple; + + /** + * @var array + */ + protected $circularDetection = []; + + /** + * @param array $config { + * + * @var bool|string[] $preserve_header True keeps all headers, false remove all of them, an array is interpreted as a list of header names to keep + * @var bool $use_default_for_multiple Whether the location header must be directly used for a multiple redirection status code (300). + * } + */ + public function __construct(array $config = []) + { + $resolver = new OptionsResolver(); + $resolver->setDefaults([ + 'preserve_header' => true, + 'use_default_for_multiple' => true, + ]); + $resolver->setAllowedTypes('preserve_header', ['bool', 'array']); + $resolver->setAllowedTypes('use_default_for_multiple', 'bool'); + $resolver->setNormalizer('preserve_header', function (OptionsResolver $resolver, $value) { + if (is_bool($value) && false === $value) { + return []; + } + + return $value; + }); + $options = $resolver->resolve($config); + + $this->preserveHeader = $options['preserve_header']; + $this->useDefaultForMultiple = $options['use_default_for_multiple']; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + // Check in storage + if (array_key_exists((string) $request->getUri(), $this->redirectStorage)) { + $uri = $this->redirectStorage[(string) $request->getUri()]['uri']; + $statusCode = $this->redirectStorage[(string) $request->getUri()]['status']; + $redirectRequest = $this->buildRedirectRequest($request, $uri, $statusCode); + + return $first($redirectRequest); + } + + return $next($request)->then(function (ResponseInterface $response) use ($request, $first) { + $statusCode = $response->getStatusCode(); + + if (!array_key_exists($statusCode, $this->redirectCodes)) { + return $response; + } + + $uri = $this->createUri($response, $request); + $redirectRequest = $this->buildRedirectRequest($request, $uri, $statusCode); + $chainIdentifier = spl_object_hash((object) $first); + + if (!array_key_exists($chainIdentifier, $this->circularDetection)) { + $this->circularDetection[$chainIdentifier] = []; + } + + $this->circularDetection[$chainIdentifier][] = (string) $request->getUri(); + + if (in_array((string) $redirectRequest->getUri(), $this->circularDetection[$chainIdentifier])) { + throw new CircularRedirectionException('Circular redirection detected', $request, $response); + } + + if ($this->redirectCodes[$statusCode]['permanent']) { + $this->redirectStorage[(string) $request->getUri()] = [ + 'uri' => $uri, + 'status' => $statusCode, + ]; + } + + // Call redirect request in synchrone + $redirectPromise = $first($redirectRequest); + + return $redirectPromise->wait(); + }); + } + + /** + * Builds the redirect request. + * + * @param RequestInterface $request Original request + * @param UriInterface $uri New uri + * @param int $statusCode Status code from the redirect response + * + * @return MessageInterface|RequestInterface + */ + protected function buildRedirectRequest(RequestInterface $request, UriInterface $uri, $statusCode) + { + $request = $request->withUri($uri); + + if (false !== $this->redirectCodes[$statusCode]['switch'] && !in_array($request->getMethod(), $this->redirectCodes[$statusCode]['switch']['unless'])) { + $request = $request->withMethod($this->redirectCodes[$statusCode]['switch']['to']); + } + + if (is_array($this->preserveHeader)) { + $headers = array_keys($request->getHeaders()); + + foreach ($headers as $name) { + if (!in_array($name, $this->preserveHeader)) { + $request = $request->withoutHeader($name); + } + } + } + + return $request; + } + + /** + * Creates a new Uri from the old request and the location header. + * + * @param ResponseInterface $response The redirect response + * @param RequestInterface $request The original request + * + * @throws HttpException If location header is not usable (missing or incorrect) + * @throws MultipleRedirectionException If a 300 status code is received and default location cannot be resolved (doesn't use the location header or not present) + * + * @return UriInterface + */ + private function createUri(ResponseInterface $response, RequestInterface $request) + { + if ($this->redirectCodes[$response->getStatusCode()]['multiple'] && (!$this->useDefaultForMultiple || !$response->hasHeader('Location'))) { + throw new MultipleRedirectionException('Cannot choose a redirection', $request, $response); + } + + if (!$response->hasHeader('Location')) { + throw new HttpException('Redirect status code, but no location header present in the response', $request, $response); + } + + $location = $response->getHeaderLine('Location'); + $parsedLocation = parse_url($location); + + if (false === $parsedLocation) { + throw new HttpException(sprintf('Location %s could not be parsed', $location), $request, $response); + } + + $uri = $request->getUri(); + + if (array_key_exists('scheme', $parsedLocation)) { + $uri = $uri->withScheme($parsedLocation['scheme']); + } + + if (array_key_exists('host', $parsedLocation)) { + $uri = $uri->withHost($parsedLocation['host']); + } + + if (array_key_exists('port', $parsedLocation)) { + $uri = $uri->withPort($parsedLocation['port']); + } + + if (array_key_exists('path', $parsedLocation)) { + $uri = $uri->withPath($parsedLocation['path']); + } + + if (array_key_exists('query', $parsedLocation)) { + $uri = $uri->withQuery($parsedLocation['query']); + } else { + $uri = $uri->withQuery(''); + } + + if (array_key_exists('fragment', $parsedLocation)) { + $uri = $uri->withFragment($parsedLocation['fragment']); + } else { + $uri = $uri->withFragment(''); + } + + return $uri; + } +} diff --git a/vendor/php-http/client-common/src/Plugin/RequestMatcherPlugin.php b/vendor/php-http/client-common/src/Plugin/RequestMatcherPlugin.php new file mode 100644 index 00000000..5f72b02d --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/RequestMatcherPlugin.php @@ -0,0 +1,47 @@ + + */ +final class RequestMatcherPlugin implements Plugin +{ + /** + * @var RequestMatcher + */ + private $requestMatcher; + + /** + * @var Plugin + */ + private $delegatedPlugin; + + /** + * @param RequestMatcher $requestMatcher + * @param Plugin $delegatedPlugin + */ + public function __construct(RequestMatcher $requestMatcher, Plugin $delegatedPlugin) + { + $this->requestMatcher = $requestMatcher; + $this->delegatedPlugin = $delegatedPlugin; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + if ($this->requestMatcher->matches($request)) { + return $this->delegatedPlugin->handleRequest($request, $next, $first); + } + + return $next($request); + } +} diff --git a/vendor/php-http/client-common/src/Plugin/RetryPlugin.php b/vendor/php-http/client-common/src/Plugin/RetryPlugin.php new file mode 100644 index 00000000..3d09265f --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/RetryPlugin.php @@ -0,0 +1,139 @@ + + */ +final class RetryPlugin implements Plugin +{ + /** + * Number of retry before sending an exception. + * + * @var int + */ + private $retry; + + /** + * @var callable + */ + private $exceptionDelay; + + /** + * @var callable + */ + private $exceptionDecider; + + /** + * Store the retry counter for each request. + * + * @var array + */ + private $retryStorage = []; + + /** + * @param array $config { + * + * @var int $retries Number of retries to attempt if an exception occurs before letting the exception bubble up. + * @var callable $exception_decider A callback that gets a request and an exception to decide after a failure whether the request should be retried. + * @var callable $exception_delay A callback that gets a request, an exception and the number of retries and returns how many microseconds we should wait before trying again. + * } + */ + public function __construct(array $config = []) + { + if (array_key_exists('decider', $config)) { + if (array_key_exists('exception_decider', $config)) { + throw new \InvalidArgumentException('Do not set both the old "decider" and new "exception_decider" options'); + } + trigger_error('The "decider" option has been deprecated in favour of "exception_decider"', E_USER_DEPRECATED); + $config['exception_decider'] = $config['decider']; + unset($config['decider']); + } + if (array_key_exists('delay', $config)) { + if (array_key_exists('exception_delay', $config)) { + throw new \InvalidArgumentException('Do not set both the old "delay" and new "exception_delay" options'); + } + trigger_error('The "delay" option has been deprecated in favour of "exception_delay"', E_USER_DEPRECATED); + $config['exception_delay'] = $config['delay']; + unset($config['delay']); + } + + $resolver = new OptionsResolver(); + $resolver->setDefaults([ + 'retries' => 1, + 'exception_decider' => function (RequestInterface $request, Exception $e) { + return true; + }, + 'exception_delay' => __CLASS__.'::defaultDelay', + ]); + $resolver->setAllowedTypes('retries', 'int'); + $resolver->setAllowedTypes('exception_decider', 'callable'); + $resolver->setAllowedTypes('exception_delay', 'callable'); + $options = $resolver->resolve($config); + + $this->retry = $options['retries']; + $this->exceptionDecider = $options['exception_decider']; + $this->exceptionDelay = $options['exception_delay']; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + $chainIdentifier = spl_object_hash((object) $first); + + return $next($request)->then(function (ResponseInterface $response) use ($request, $chainIdentifier) { + if (array_key_exists($chainIdentifier, $this->retryStorage)) { + unset($this->retryStorage[$chainIdentifier]); + } + + return $response; + }, function (Exception $exception) use ($request, $next, $first, $chainIdentifier) { + if (!array_key_exists($chainIdentifier, $this->retryStorage)) { + $this->retryStorage[$chainIdentifier] = 0; + } + + if ($this->retryStorage[$chainIdentifier] >= $this->retry) { + unset($this->retryStorage[$chainIdentifier]); + + throw $exception; + } + + if (!call_user_func($this->exceptionDecider, $request, $exception)) { + throw $exception; + } + + $time = call_user_func($this->exceptionDelay, $request, $exception, $this->retryStorage[$chainIdentifier]); + usleep($time); + + // Retry in synchrone + ++$this->retryStorage[$chainIdentifier]; + $promise = $this->handleRequest($request, $next, $first); + + return $promise->wait(); + }); + } + + /** + * @param RequestInterface $request + * @param Exception $e + * @param int $retries The number of retries we made before. First time this get called it will be 0. + * + * @return int + */ + public static function defaultDelay(RequestInterface $request, Exception $e, $retries) + { + return pow(2, $retries) * 500000; + } +} diff --git a/vendor/php-http/client-common/src/Plugin/VersionBridgePlugin.php b/vendor/php-http/client-common/src/Plugin/VersionBridgePlugin.php new file mode 100644 index 00000000..f3891e5c --- /dev/null +++ b/vendor/php-http/client-common/src/Plugin/VersionBridgePlugin.php @@ -0,0 +1,21 @@ + + */ +trait VersionBridgePlugin +{ + abstract protected function doHandleRequest(RequestInterface $request, callable $next, callable $first); + + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + return $this->doHandleRequest($request, $next, $first); + } +} diff --git a/vendor/php-http/client-common/src/PluginClient.php b/vendor/php-http/client-common/src/PluginClient.php new file mode 100644 index 00000000..8cedcf65 --- /dev/null +++ b/vendor/php-http/client-common/src/PluginClient.php @@ -0,0 +1,180 @@ + + */ +final class PluginClient implements HttpClient, HttpAsyncClient +{ + /** + * An HTTP async client. + * + * @var HttpAsyncClient + */ + private $client; + + /** + * The plugin chain. + * + * @var Plugin[] + */ + private $plugins; + + /** + * A list of options. + * + * @var array + */ + private $options; + + /** + * @param HttpClient|HttpAsyncClient $client + * @param Plugin[] $plugins + * @param array $options { + * + * @var int $max_restarts + * @var Plugin[] $debug_plugins an array of plugins that are injected between each normal plugin + * } + * + * @throws \RuntimeException if client is not an instance of HttpClient or HttpAsyncClient + */ + public function __construct($client, array $plugins = [], array $options = []) + { + if ($client instanceof HttpAsyncClient) { + $this->client = $client; + } elseif ($client instanceof HttpClient || $client instanceof ClientInterface) { + $this->client = new EmulatedHttpAsyncClient($client); + } else { + throw new \RuntimeException('Client must be an instance of Http\\Client\\HttpClient or Http\\Client\\HttpAsyncClient'); + } + + $this->plugins = $plugins; + $this->options = $this->configure($options); + } + + /** + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request) + { + // If we don't have an http client, use the async call + if (!($this->client instanceof HttpClient)) { + return $this->sendAsyncRequest($request)->wait(); + } + + // Else we want to use the synchronous call of the underlying client, and not the async one in the case + // we have both an async and sync call + $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) { + try { + return new HttpFulfilledPromise($this->client->sendRequest($request)); + } catch (HttplugException $exception) { + return new HttpRejectedPromise($exception); + } + }); + + return $pluginChain($request)->wait(); + } + + /** + * {@inheritdoc} + */ + public function sendAsyncRequest(RequestInterface $request) + { + $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) { + return $this->client->sendAsyncRequest($request); + }); + + return $pluginChain($request); + } + + /** + * Configure the plugin client. + * + * @param array $options + * + * @return array + */ + private function configure(array $options = []) + { + if (isset($options['debug_plugins'])) { + @trigger_error('The "debug_plugins" option is deprecated since 1.5 and will be removed in 2.0.', E_USER_DEPRECATED); + } + + $resolver = new OptionsResolver(); + $resolver->setDefaults([ + 'max_restarts' => 10, + 'debug_plugins' => [], + ]); + + $resolver + ->setAllowedTypes('debug_plugins', 'array') + ->setAllowedValues('debug_plugins', function (array $plugins) { + foreach ($plugins as $plugin) { + // Make sure each object passed with the `debug_plugins` is an instance of Plugin. + if (!$plugin instanceof Plugin) { + return false; + } + } + + return true; + }); + + return $resolver->resolve($options); + } + + /** + * Create the plugin chain. + * + * @param Plugin[] $pluginList A list of plugins + * @param callable $clientCallable Callable making the HTTP call + * + * @return callable + */ + private function createPluginChain($pluginList, callable $clientCallable) + { + $firstCallable = $lastCallable = $clientCallable; + + /* + * Inject debug plugins between each plugin. + */ + $pluginListWithDebug = $this->options['debug_plugins']; + foreach ($pluginList as $plugin) { + $pluginListWithDebug[] = $plugin; + $pluginListWithDebug = array_merge($pluginListWithDebug, $this->options['debug_plugins']); + } + + while ($plugin = array_pop($pluginListWithDebug)) { + $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) { + return $plugin->handleRequest($request, $lastCallable, $firstCallable); + }; + + $firstCallable = $lastCallable; + } + + $firstCalls = 0; + $firstCallable = function (RequestInterface $request) use ($lastCallable, &$firstCalls) { + if ($firstCalls > $this->options['max_restarts']) { + throw new LoopException('Too many restarts in plugin client', $request); + } + + ++$firstCalls; + + return $lastCallable($request); + }; + + return $firstCallable; + } +} diff --git a/vendor/php-http/client-common/src/PluginClientFactory.php b/vendor/php-http/client-common/src/PluginClientFactory.php new file mode 100644 index 00000000..cbea3e13 --- /dev/null +++ b/vendor/php-http/client-common/src/PluginClientFactory.php @@ -0,0 +1,63 @@ + + */ +final class PluginClientFactory +{ + /** + * @var callable + */ + private static $factory; + + /** + * Set the factory to use. + * The callable to provide must have the same arguments and return type as PluginClientFactory::createClient. + * This is used by the HTTPlugBundle to provide a better Symfony integration. + * Unlike the createClient method, this one is static to allow zero configuration profiling by hooking into early + * application execution. + * + * @internal + * + * @param callable $factory + */ + public static function setFactory(callable $factory) + { + static::$factory = $factory; + } + + /** + * @param HttpClient|HttpAsyncClient|ClientInterface $client + * @param Plugin[] $plugins + * @param array $options { + * + * @var string $client_name to give client a name which may be used when displaying client information like in + * the HTTPlugBundle profiler. + * } + * + * @see PluginClient constructor for PluginClient specific $options. + * + * @return PluginClient + */ + public function createClient($client, array $plugins = [], array $options = []) + { + if (static::$factory) { + $factory = static::$factory; + + return $factory($client, $plugins, $options); + } + + unset($options['client_name']); + + return new PluginClient($client, $plugins, $options); + } +} diff --git a/vendor/php-http/client-common/src/VersionBridgeClient.php b/vendor/php-http/client-common/src/VersionBridgeClient.php new file mode 100644 index 00000000..048eeae3 --- /dev/null +++ b/vendor/php-http/client-common/src/VersionBridgeClient.php @@ -0,0 +1,21 @@ + + */ +trait VersionBridgeClient +{ + abstract protected function doSendRequest(RequestInterface $request); + + public function sendRequest(RequestInterface $request) + { + return $this->doSendRequest($request); + } +} diff --git a/vendor/php-http/discovery/CHANGELOG.md b/vendor/php-http/discovery/CHANGELOG.md new file mode 100644 index 00000000..ee43ffd2 --- /dev/null +++ b/vendor/php-http/discovery/CHANGELOG.md @@ -0,0 +1,221 @@ +# Change Log + +## 1.5.2 - 2018-12-31 + +Corrected mistakes in 1.5.1. The different between 1.5.2 and 1.5.0 is that +we removed some PHP 7 code. + +## 1.5.1 - 2018-12-31 + +This version added new features by mistake. These are reverted in 1.5.2. + +Do not use 1.5.1. + +### Fixed + +- Removed PHP 7 code + +## 1.5.0 - 2018-12-30 + +### Added + +- Support for `nyholm/psr7` version 1.0. +- `ClassDiscovery::safeClassExists` which will help Magento users. +- Support for HTTPlug 2.0 +- Support for Buzz 1.0 +- Better error message when nothing found by introducing a new exception: `NoCandidateFoundException`. + +### Fixed + +- Fixed condition evaluation, it should stop after first invalid condition. + +## 1.4.0 - 2018-02-06 + +### Added + +- Discovery support for nyholm/psr7 + +## 1.3.0 - 2017-08-03 + +### Added + +- Discovery support for CakePHP adapter +- Discovery support for Zend adapter +- Discovery support for Artax adapter + +## 1.2.1 - 2017-03-02 + +### Fixed + +- Fixed minor issue with `MockClientStrategy`, also added more tests. + +## 1.2.0 - 2017-02-12 + +### Added + +- MockClientStrategy class. + +## 1.1.1 - 2016-11-27 + +### Changed + +- Made exception messages clearer. `StrategyUnavailableException` is no longer the previous exception to `DiscoveryFailedException`. +- `CommonClassesStrategy` is using `self` instead of `static`. Using `static` makes no sense when `CommonClassesStrategy` is final. + +## 1.1.0 - 2016-10-20 + +### Added + +- Discovery support for Slim Framework factories + +## 1.0.0 - 2016-07-18 + +### Added + +- Added back `Http\Discovery\NotFoundException` to preserve BC with 0.8 version. You may upgrade from 0.8.x and 0.9.x to 1.0.0 without any BC breaks. +- Added interface `Http\Discovery\Exception` which is implemented by all our exceptions + +### Changed + +- Puli strategy renamed to Puli Beta strategy to prevent incompatibility with a future Puli stable + +### Deprecated + +- For BC reasons, the old `Http\Discovery\NotFoundException` (extending the new exception) will be thrown until version 2.0 + + +## 0.9.1 - 2016-06-28 + +### Changed + +- Dropping PHP 5.4 support because we use the ::class constant. + + +## 0.9.0 - 2016-06-25 + +### Added + +- Discovery strategies to find classes + +### Changed + +- [Puli](http://puli.io) made optional +- Improved exceptions +- **[BC] `NotFoundException` moved to `Http\Discovery\Exception\NotFoundException`** + + +## 0.8.0 - 2016-02-11 + +### Changed + +- Puli composer plugin must be installed separately + + +## 0.7.0 - 2016-01-15 + +### Added + +- Temporary puli.phar (Beta 10) executable + +### Changed + +- Updated HTTPlug dependencies +- Updated Puli dependencies +- Local configuration to make tests passing + +### Removed + +- Puli CLI dependency + + +## 0.6.4 - 2016-01-07 + +### Fixed + +- Puli [not working](https://twitter.com/PuliPHP/status/685132540588507137) with the latest json-schema + + +## 0.6.3 - 2016-01-04 + +### Changed + +- Adjust Puli dependencies + + +## 0.6.2 - 2016-01-04 + +### Changed + +- Make Puli CLI a requirement + + +## 0.6.1 - 2016-01-03 + +### Changed + +- More flexible Puli requirement + + +## 0.6.0 - 2015-12-30 + +### Changed + +- Use [Puli](http://puli.io) for discovery +- Improved exception messages + + +## 0.5.0 - 2015-12-25 + +### Changed + +- Updated message factory dependency (php-http/message) + + +## 0.4.0 - 2015-12-17 + +### Added + +- Array condition evaluation in the Class Discovery + +### Removed + +- Message factories (moved to php-http/utils) + + +## 0.3.0 - 2015-11-18 + +### Added + +- HTTP Async Client Discovery +- Stream factories + +### Changed + +- Discoveries and Factories are final +- Message and Uri factories have the type in their names +- Diactoros Message factory uses Stream factory internally + +### Fixed + +- Improved docblocks for API documentation generation + + +## 0.2.0 - 2015-10-31 + +### Changed + +- Renamed AdapterDiscovery to ClientDiscovery + + +## 0.1.1 - 2015-06-13 + +### Fixed + +- Bad HTTP Adapter class name for Guzzle 5 + + +## 0.1.0 - 2015-06-12 + +### Added + +- Initial release diff --git a/vendor/php-http/discovery/LICENSE b/vendor/php-http/discovery/LICENSE new file mode 100644 index 00000000..4558d6f0 --- /dev/null +++ b/vendor/php-http/discovery/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2016 PHP HTTP Team + +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. diff --git a/vendor/php-http/discovery/README.md b/vendor/php-http/discovery/README.md new file mode 100644 index 00000000..7c5151ea --- /dev/null +++ b/vendor/php-http/discovery/README.md @@ -0,0 +1,46 @@ +# HTTPlug Discovery + +[![Latest Version](https://img.shields.io/github/release/php-http/discovery.svg?style=flat-square)](https://github.com/php-http/discovery/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Build Status](https://img.shields.io/travis/php-http/discovery.svg?style=flat-square)](https://travis-ci.org/php-http/discovery) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/discovery.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/discovery) +[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/discovery.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/discovery) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/discovery.svg?style=flat-square)](https://packagist.org/packages/php-http/discovery) + +**Finds installed HTTPlug implementations and PSR-7 message factories.** + + +## Install + +Via Composer + +``` bash +$ composer require php-http/discovery +``` + + +## Documentation + +Please see the [official documentation](http://php-http.readthedocs.org/en/latest/discovery.html). + + +## Testing + +``` bash +$ composer test +``` + + +## Contributing + +Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html). + + +## Security + +If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org). + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/vendor/php-http/discovery/composer.json b/vendor/php-http/discovery/composer.json new file mode 100644 index 00000000..97141f3b --- /dev/null +++ b/vendor/php-http/discovery/composer.json @@ -0,0 +1,51 @@ +{ + "name": "php-http/discovery", + "description": "Finds installed HTTPlug implementations and PSR-7 message factories", + "license": "MIT", + "keywords": ["http", "discovery", "client", "adapter", "message", "factory", "psr7"], + "homepage": "http://php-http.org", + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "require": { + "php": "^5.5 || ^7.0" + }, + "require-dev": { + "php-http/httplug": "^1.0|^2.0", + "php-http/message-factory": "^1.0", + "puli/composer-plugin": "1.0.0-beta10", + "phpspec/phpspec": "^2.4", + "henrikbjorn/phpspec-code-coverage" : "^2.0.2" + }, + "suggest": { + "puli/composer-plugin": "Sets up Puli which is recommended for Discovery to work. Check http://docs.php-http.org/en/latest/discovery.html for more details.", + "php-http/message": "Allow to use Guzzle, Diactoros or Slim Framework factories" + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "spec\\Http\\Discovery\\": "spec/" + } + }, + "scripts": { + "test": "vendor/bin/phpspec run", + "test-ci": "vendor/bin/phpspec run -c phpspec.ci.yml" + }, + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "conflict": { + "nyholm/psr7": "<1.0" + }, + "prefer-stable": true, + "minimum-stability": "beta" +} diff --git a/vendor/php-http/discovery/src/ClassDiscovery.php b/vendor/php-http/discovery/src/ClassDiscovery.php new file mode 100644 index 00000000..40e2d1c5 --- /dev/null +++ b/vendor/php-http/discovery/src/ClassDiscovery.php @@ -0,0 +1,235 @@ + + * @author Márk Sági-Kazár + * @author Tobias Nyholm + */ +abstract class ClassDiscovery +{ + /** + * A list of strategies to find classes. + * + * @var array + */ + private static $strategies = [ + Strategy\PuliBetaStrategy::class, + Strategy\CommonClassesStrategy::class, + ]; + + /** + * Discovery cache to make the second time we use discovery faster. + * + * @var array + */ + private static $cache = []; + + /** + * Finds a class. + * + * @param string $type + * + * @return string|\Closure + * + * @throws DiscoveryFailedException + */ + protected static function findOneByType($type) + { + // Look in the cache + if (null !== ($class = self::getFromCache($type))) { + return $class; + } + + $exceptions = []; + foreach (self::$strategies as $strategy) { + try { + $candidates = call_user_func($strategy.'::getCandidates', $type); + } catch (StrategyUnavailableException $e) { + $exceptions[] = $e; + + continue; + } + + foreach ($candidates as $candidate) { + if (isset($candidate['condition'])) { + if (!self::evaluateCondition($candidate['condition'])) { + continue; + } + } + + // save the result for later use + self::storeInCache($type, $candidate); + + return $candidate['class']; + } + + $exceptions[] = new NoCandidateFoundException($strategy, $candidates); + } + + throw DiscoveryFailedException::create($exceptions); + } + + /** + * Get a value from cache. + * + * @param string $type + * + * @return string|null + */ + private static function getFromCache($type) + { + if (!isset(self::$cache[$type])) { + return; + } + + $candidate = self::$cache[$type]; + if (isset($candidate['condition'])) { + if (!self::evaluateCondition($candidate['condition'])) { + return; + } + } + + return $candidate['class']; + } + + /** + * Store a value in cache. + * + * @param string $type + * @param string $class + */ + private static function storeInCache($type, $class) + { + self::$cache[$type] = $class; + } + + /** + * Set new strategies and clear the cache. + * + * @param array $strategies string array of fully qualified class name to a DiscoveryStrategy + */ + public static function setStrategies(array $strategies) + { + self::$strategies = $strategies; + self::clearCache(); + } + + /** + * Append a strategy at the end of the strategy queue. + * + * @param string $strategy Fully qualified class name to a DiscoveryStrategy + */ + public static function appendStrategy($strategy) + { + self::$strategies[] = $strategy; + self::clearCache(); + } + + /** + * Prepend a strategy at the beginning of the strategy queue. + * + * @param string $strategy Fully qualified class name to a DiscoveryStrategy + */ + public static function prependStrategy($strategy) + { + array_unshift(self::$strategies, $strategy); + self::clearCache(); + } + + /** + * Clear the cache. + */ + public static function clearCache() + { + self::$cache = []; + } + + /** + * Evaluates conditions to boolean. + * + * @param mixed $condition + * + * @return bool + */ + protected static function evaluateCondition($condition) + { + if (is_string($condition)) { + // Should be extended for functions, extensions??? + return self::safeClassExists($condition); + } + if (is_callable($condition)) { + return (bool) $condition(); + } + if (is_bool($condition)) { + return $condition; + } + if (is_array($condition)) { + foreach ($condition as $c) { + if (false === static::evaluateCondition($c)) { + // Immediately stop execution if the condition is false + return false; + } + } + + return true; + } + + return false; + } + + /** + * Get an instance of the $class. + * + * @param string|\Closure $class A FQCN of a class or a closure that instantiate the class. + * + * @return object + * + * @throws ClassInstantiationFailedException + */ + protected static function instantiateClass($class) + { + try { + if (is_string($class)) { + return new $class(); + } + + if (is_callable($class)) { + return $class(); + } + } catch (\Exception $e) { + throw new ClassInstantiationFailedException('Unexpected exception when instantiating class.', 0, $e); + } + + throw new ClassInstantiationFailedException('Could not instantiate class because parameter is neither a callable nor a string'); + } + + /** + * We want to do a "safe" version of PHP's "class_exists" because Magento has a bug + * (or they call it a "feature"). Magento is throwing an exception if you do class_exists() + * on a class that ends with "Factory" and if that file does not exits. + * + * This function will catch all potential exceptions and make sure it returns a boolean. + * + * @param string $class + * @param bool $autoload + * + * @return bool + */ + public static function safeClassExists($class) + { + try { + return class_exists($class); + } catch (\Exception $e) { + return false; + } + } +} diff --git a/vendor/php-http/discovery/src/Exception.php b/vendor/php-http/discovery/src/Exception.php new file mode 100644 index 00000000..973c9087 --- /dev/null +++ b/vendor/php-http/discovery/src/Exception.php @@ -0,0 +1,12 @@ + + */ +interface Exception +{ +} diff --git a/vendor/php-http/discovery/src/Exception/ClassInstantiationFailedException.php b/vendor/php-http/discovery/src/Exception/ClassInstantiationFailedException.php new file mode 100644 index 00000000..e95bf5d8 --- /dev/null +++ b/vendor/php-http/discovery/src/Exception/ClassInstantiationFailedException.php @@ -0,0 +1,14 @@ + + */ +final class ClassInstantiationFailedException extends \RuntimeException implements Exception +{ +} diff --git a/vendor/php-http/discovery/src/Exception/DiscoveryFailedException.php b/vendor/php-http/discovery/src/Exception/DiscoveryFailedException.php new file mode 100644 index 00000000..304b7276 --- /dev/null +++ b/vendor/php-http/discovery/src/Exception/DiscoveryFailedException.php @@ -0,0 +1,51 @@ + + */ +final class DiscoveryFailedException extends \Exception implements Exception +{ + /** + * @var \Exception[] + */ + private $exceptions; + + /** + * @param string $message + * @param \Exception[] $exceptions + */ + public function __construct($message, array $exceptions = []) + { + $this->exceptions = $exceptions; + + parent::__construct($message); + } + + /** + * @param \Exception[] $exceptions + */ + public static function create($exceptions) + { + $message = 'Could not find resource using any discovery strategy. Find more information at http://docs.php-http.org/en/latest/discovery.html#common-errors'; + foreach ($exceptions as $e) { + $message .= "\n - ".$e->getMessage(); + } + $message .= "\n\n"; + + return new self($message, $exceptions); + } + + /** + * @return \Exception[] + */ + public function getExceptions() + { + return $this->exceptions; + } +} diff --git a/vendor/php-http/discovery/src/Exception/NoCandidateFoundException.php b/vendor/php-http/discovery/src/Exception/NoCandidateFoundException.php new file mode 100644 index 00000000..240b5688 --- /dev/null +++ b/vendor/php-http/discovery/src/Exception/NoCandidateFoundException.php @@ -0,0 +1,35 @@ + + */ +final class NoCandidateFoundException extends \Exception implements Exception +{ + /** + * @param string $strategy + * @param array $candidates + */ + public function __construct($strategy, array $candidates) + { + $classes = array_map( + function ($a) { + return $a['class']; + }, + $candidates + ); + + $message = sprintf( + 'No valid candidate found using strategy "%s". We tested the following candidates: %s.', + $strategy, + implode(', ', $classes) + ); + + parent::__construct($message); + } +} diff --git a/vendor/php-http/discovery/src/Exception/NotFoundException.php b/vendor/php-http/discovery/src/Exception/NotFoundException.php new file mode 100644 index 00000000..befbf488 --- /dev/null +++ b/vendor/php-http/discovery/src/Exception/NotFoundException.php @@ -0,0 +1,16 @@ + + */ +/*final */class NotFoundException extends \RuntimeException implements Exception +{ +} diff --git a/vendor/php-http/discovery/src/Exception/PuliUnavailableException.php b/vendor/php-http/discovery/src/Exception/PuliUnavailableException.php new file mode 100644 index 00000000..a6ade733 --- /dev/null +++ b/vendor/php-http/discovery/src/Exception/PuliUnavailableException.php @@ -0,0 +1,12 @@ + + */ +final class PuliUnavailableException extends StrategyUnavailableException +{ +} diff --git a/vendor/php-http/discovery/src/Exception/StrategyUnavailableException.php b/vendor/php-http/discovery/src/Exception/StrategyUnavailableException.php new file mode 100644 index 00000000..89ecf352 --- /dev/null +++ b/vendor/php-http/discovery/src/Exception/StrategyUnavailableException.php @@ -0,0 +1,15 @@ + + */ +class StrategyUnavailableException extends \RuntimeException implements Exception +{ +} diff --git a/vendor/php-http/discovery/src/HttpAsyncClientDiscovery.php b/vendor/php-http/discovery/src/HttpAsyncClientDiscovery.php new file mode 100644 index 00000000..6ef60e70 --- /dev/null +++ b/vendor/php-http/discovery/src/HttpAsyncClientDiscovery.php @@ -0,0 +1,36 @@ + + */ +final class HttpAsyncClientDiscovery extends ClassDiscovery +{ + /** + * Finds an HTTP Async Client. + * + * @return HttpAsyncClient + * + * @throws Exception\NotFoundException + */ + public static function find() + { + try { + $asyncClient = static::findOneByType(HttpAsyncClient::class); + } catch (DiscoveryFailedException $e) { + throw new NotFoundException( + 'No HTTPlug async clients found. Make sure to install a package providing "php-http/async-client-implementation". Example: "php-http/guzzle6-adapter".', + 0, + $e + ); + } + + return static::instantiateClass($asyncClient); + } +} diff --git a/vendor/php-http/discovery/src/HttpClientDiscovery.php b/vendor/php-http/discovery/src/HttpClientDiscovery.php new file mode 100644 index 00000000..2654b7ed --- /dev/null +++ b/vendor/php-http/discovery/src/HttpClientDiscovery.php @@ -0,0 +1,36 @@ + + */ +final class HttpClientDiscovery extends ClassDiscovery +{ + /** + * Finds an HTTP Client. + * + * @return HttpClient + * + * @throws Exception\NotFoundException + */ + public static function find() + { + try { + $client = static::findOneByType(HttpClient::class); + } catch (DiscoveryFailedException $e) { + throw new NotFoundException( + 'No HTTPlug clients found. Make sure to install a package providing "php-http/client-implementation". Example: "php-http/guzzle6-adapter".', + 0, + $e + ); + } + + return static::instantiateClass($client); + } +} diff --git a/vendor/php-http/discovery/src/MessageFactoryDiscovery.php b/vendor/php-http/discovery/src/MessageFactoryDiscovery.php new file mode 100644 index 00000000..c21b9bf4 --- /dev/null +++ b/vendor/php-http/discovery/src/MessageFactoryDiscovery.php @@ -0,0 +1,36 @@ + + */ +final class MessageFactoryDiscovery extends ClassDiscovery +{ + /** + * Finds a Message Factory. + * + * @return MessageFactory + * + * @throws Exception\NotFoundException + */ + public static function find() + { + try { + $messageFactory = static::findOneByType(MessageFactory::class); + } catch (DiscoveryFailedException $e) { + throw new NotFoundException( + 'No message factories found. To use Guzzle, Diactoros or Slim Framework factories install php-http/message and the chosen message implementation.', + 0, + $e + ); + } + + return static::instantiateClass($messageFactory); + } +} diff --git a/vendor/php-http/discovery/src/NotFoundException.php b/vendor/php-http/discovery/src/NotFoundException.php new file mode 100644 index 00000000..d59dadbf --- /dev/null +++ b/vendor/php-http/discovery/src/NotFoundException.php @@ -0,0 +1,14 @@ + + * + * @deprecated since since version 1.0, and will be removed in 2.0. Use {@link \Http\Discovery\Exception\NotFoundException} instead. + */ +final class NotFoundException extends \Http\Discovery\Exception\NotFoundException +{ +} diff --git a/vendor/php-http/discovery/src/Strategy/CommonClassesStrategy.php b/vendor/php-http/discovery/src/Strategy/CommonClassesStrategy.php new file mode 100644 index 00000000..eff2c0a6 --- /dev/null +++ b/vendor/php-http/discovery/src/Strategy/CommonClassesStrategy.php @@ -0,0 +1,101 @@ + + */ +final class CommonClassesStrategy implements DiscoveryStrategy +{ + /** + * @var array + */ + private static $classes = [ + MessageFactory::class => [ + ['class' => NyholmHttplugFactory::class, 'condition' => [NyholmHttplugFactory::class]], + ['class' => GuzzleMessageFactory::class, 'condition' => [GuzzleRequest::class, GuzzleMessageFactory::class]], + ['class' => DiactorosMessageFactory::class, 'condition' => [DiactorosRequest::class, DiactorosMessageFactory::class]], + ['class' => SlimMessageFactory::class, 'condition' => [SlimRequest::class, SlimMessageFactory::class]], + ], + StreamFactory::class => [ + ['class' => NyholmHttplugFactory::class, 'condition' => [NyholmHttplugFactory::class]], + ['class' => GuzzleStreamFactory::class, 'condition' => [GuzzleRequest::class, GuzzleStreamFactory::class]], + ['class' => DiactorosStreamFactory::class, 'condition' => [DiactorosRequest::class, DiactorosStreamFactory::class]], + ['class' => SlimStreamFactory::class, 'condition' => [SlimRequest::class, SlimStreamFactory::class]], + ], + UriFactory::class => [ + ['class' => NyholmHttplugFactory::class, 'condition' => [NyholmHttplugFactory::class]], + ['class' => GuzzleUriFactory::class, 'condition' => [GuzzleRequest::class, GuzzleUriFactory::class]], + ['class' => DiactorosUriFactory::class, 'condition' => [DiactorosRequest::class, DiactorosUriFactory::class]], + ['class' => SlimUriFactory::class, 'condition' => [SlimRequest::class, SlimUriFactory::class]], + ], + HttpAsyncClient::class => [ + ['class' => Guzzle6::class, 'condition' => Guzzle6::class], + ['class' => Curl::class, 'condition' => Curl::class], + ['class' => React::class, 'condition' => React::class], + ], + HttpClient::class => [ + ['class' => Guzzle6::class, 'condition' => Guzzle6::class], + ['class' => Guzzle5::class, 'condition' => Guzzle5::class], + ['class' => Curl::class, 'condition' => Curl::class], + ['class' => Socket::class, 'condition' => Socket::class], + ['class' => Buzz::class, 'condition' => Buzz::class], + ['class' => React::class, 'condition' => React::class], + ['class' => Cake::class, 'condition' => Cake::class], + ['class' => Zend::class, 'condition' => Zend::class], + ['class' => Artax::class, 'condition' => Artax::class], + [ + 'class' => [self::class, 'buzzInstantiate'], + 'condition' => [\Buzz\Client\FileGetContents::class, \Buzz\Message\ResponseBuilder::class], + ], + ], + ]; + + /** + * {@inheritdoc} + */ + public static function getCandidates($type) + { + if (isset(self::$classes[$type])) { + return self::$classes[$type]; + } + + return []; + } + + public static function buzzInstantiate() + { + return new \Buzz\Client\FileGetContents(MessageFactoryDiscovery::find()); + } +} diff --git a/vendor/php-http/discovery/src/Strategy/DiscoveryStrategy.php b/vendor/php-http/discovery/src/Strategy/DiscoveryStrategy.php new file mode 100644 index 00000000..641485a6 --- /dev/null +++ b/vendor/php-http/discovery/src/Strategy/DiscoveryStrategy.php @@ -0,0 +1,23 @@ + + */ +interface DiscoveryStrategy +{ + /** + * Find a resource of a specific type. + * + * @param string $type + * + * @return array The return value is always an array with zero or more elements. Each + * element is an array with two keys ['class' => string, 'condition' => mixed]. + * + * @throws StrategyUnavailableException if we cannot use this strategy. + */ + public static function getCandidates($type); +} diff --git a/vendor/php-http/discovery/src/Strategy/MockClientStrategy.php b/vendor/php-http/discovery/src/Strategy/MockClientStrategy.php new file mode 100644 index 00000000..57447877 --- /dev/null +++ b/vendor/php-http/discovery/src/Strategy/MockClientStrategy.php @@ -0,0 +1,24 @@ + + */ +final class MockClientStrategy implements DiscoveryStrategy +{ + /** + * {@inheritdoc} + */ + public static function getCandidates($type) + { + return (HttpClient::class === $type) + ? [['class' => Mock::class, 'condition' => Mock::class]] + : []; + } +} diff --git a/vendor/php-http/discovery/src/Strategy/PuliBetaStrategy.php b/vendor/php-http/discovery/src/Strategy/PuliBetaStrategy.php new file mode 100644 index 00000000..c1e1fb7e --- /dev/null +++ b/vendor/php-http/discovery/src/Strategy/PuliBetaStrategy.php @@ -0,0 +1,92 @@ + + * @author Márk Sági-Kazár + */ +class PuliBetaStrategy implements DiscoveryStrategy +{ + /** + * @var GeneratedPuliFactory + */ + protected static $puliFactory; + + /** + * @var Discovery + */ + protected static $puliDiscovery; + + /** + * @return GeneratedPuliFactory + * + * @throws PuliUnavailableException + */ + private static function getPuliFactory() + { + if (null === self::$puliFactory) { + if (!defined('PULI_FACTORY_CLASS')) { + throw new PuliUnavailableException('Puli Factory is not available'); + } + + $puliFactoryClass = PULI_FACTORY_CLASS; + + if (!ClassDiscovery::safeClassExists($puliFactoryClass)) { + throw new PuliUnavailableException('Puli Factory class does not exist'); + } + + self::$puliFactory = new $puliFactoryClass(); + } + + return self::$puliFactory; + } + + /** + * Returns the Puli discovery layer. + * + * @return Discovery + * + * @throws PuliUnavailableException + */ + private static function getPuliDiscovery() + { + if (!isset(self::$puliDiscovery)) { + $factory = self::getPuliFactory(); + $repository = $factory->createRepository(); + + self::$puliDiscovery = $factory->createDiscovery($repository); + } + + return self::$puliDiscovery; + } + + /** + * {@inheritdoc} + */ + public static function getCandidates($type) + { + $returnData = []; + $bindings = self::getPuliDiscovery()->findBindings($type); + + foreach ($bindings as $binding) { + $condition = true; + if ($binding->hasParameterValue('depends')) { + $condition = $binding->getParameterValue('depends'); + } + $returnData[] = ['class' => $binding->getClassName(), 'condition' => $condition]; + } + + return $returnData; + } +} diff --git a/vendor/php-http/discovery/src/StreamFactoryDiscovery.php b/vendor/php-http/discovery/src/StreamFactoryDiscovery.php new file mode 100644 index 00000000..7bcc8ce1 --- /dev/null +++ b/vendor/php-http/discovery/src/StreamFactoryDiscovery.php @@ -0,0 +1,36 @@ + + */ +final class StreamFactoryDiscovery extends ClassDiscovery +{ + /** + * Finds a Stream Factory. + * + * @return StreamFactory + * + * @throws Exception\NotFoundException + */ + public static function find() + { + try { + $streamFactory = static::findOneByType(StreamFactory::class); + } catch (DiscoveryFailedException $e) { + throw new NotFoundException( + 'No stream factories found. To use Guzzle, Diactoros or Slim Framework factories install php-http/message and the chosen message implementation.', + 0, + $e + ); + } + + return static::instantiateClass($streamFactory); + } +} diff --git a/vendor/php-http/discovery/src/UriFactoryDiscovery.php b/vendor/php-http/discovery/src/UriFactoryDiscovery.php new file mode 100644 index 00000000..1eef1e6f --- /dev/null +++ b/vendor/php-http/discovery/src/UriFactoryDiscovery.php @@ -0,0 +1,36 @@ + + */ +final class UriFactoryDiscovery extends ClassDiscovery +{ + /** + * Finds a URI Factory. + * + * @return UriFactory + * + * @throws Exception\NotFoundException + */ + public static function find() + { + try { + $uriFactory = static::findOneByType(UriFactory::class); + } catch (DiscoveryFailedException $e) { + throw new NotFoundException( + 'No uri factories found. To use Guzzle, Diactoros or Slim Framework factories install php-http/message and the chosen message implementation.', + 0, + $e + ); + } + + return static::instantiateClass($uriFactory); + } +} diff --git a/vendor/php-http/guzzle6-adapter/CHANGELOG.md b/vendor/php-http/guzzle6-adapter/CHANGELOG.md new file mode 100644 index 00000000..0fdb5069 --- /dev/null +++ b/vendor/php-http/guzzle6-adapter/CHANGELOG.md @@ -0,0 +1,66 @@ +# Change Log + + +## 1.1.1 - 2016-05-10 + +### Fixed + +- Adapter can again be instantiated without a guzzle client. + +## 1.1.0 - 2016-05-09 + +### Added + +- Factory method Client::createWithConfig to create an adapter with custom + configuration for the underlying guzzle client. + + +## 1.0.0 - 2016-01-26 + + +## 0.4.1 - 2016-01-13 + +### Changed + +- Updated integration tests + +### Removed + +- Client common dependency + + +## 0.4.0 - 2016-01-12 + +### Changed + +- Updated package files +- Updated HTTPlug to RC1 + + +## 0.2.1 - 2015-12-17 + +### Added + +- Puli configuration and bindings + +### Changed + +- Guzzle setup conforms to HTTPlug requirement now: Minimal functionality in client + + +## 0.2.0 - 2015-12-15 + +### Added + +- Async client capabalities + +### Changed + +- HTTPlug instead of HTTP Adapter + + +## 0.1.0 - 2015-06-12 + +### Added + +- Initial release diff --git a/vendor/php-http/guzzle6-adapter/LICENSE b/vendor/php-http/guzzle6-adapter/LICENSE new file mode 100644 index 00000000..48741e41 --- /dev/null +++ b/vendor/php-http/guzzle6-adapter/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2014-2015 Eric GELOEN +Copyright (c) 2015-2016 PHP HTTP Team + +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. diff --git a/vendor/php-http/guzzle6-adapter/README.md b/vendor/php-http/guzzle6-adapter/README.md new file mode 100644 index 00000000..623eb2f8 --- /dev/null +++ b/vendor/php-http/guzzle6-adapter/README.md @@ -0,0 +1,59 @@ +# Guzzle 6 HTTP Adapter + +[![Latest Version](https://img.shields.io/github/release/php-http/guzzle6-adapter.svg?style=flat-square)](https://github.com/php-http/guzzle6-adapter/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Build Status](https://img.shields.io/travis/php-http/guzzle6-adapter.svg?style=flat-square)](https://travis-ci.org/php-http/guzzle6-adapter) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/guzzle6-adapter.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/guzzle6-adapter) +[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/guzzle6-adapter.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/guzzle6-adapter) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/guzzle6-adapter.svg?style=flat-square)](https://packagist.org/packages/php-http/guzzle6-adapter) + +**Guzzle 6 HTTP Adapter.** + + +## Install + +Via Composer + +``` bash +$ composer require php-http/guzzle6-adapter +``` + + +## Documentation + +Please see the [official documentation](http://docs.php-http.org/en/latest/clients/guzzle6-adapter.html). + + +## Testing + +First launch the http server: + +```bash +$ ./vendor/bin/http_test_server > /dev/null 2>&1 & +``` + +Then the test suite: + +``` bash +$ composer test +``` + + +## Contributing + +Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html). + + +## Security + +If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org). + + +## Credits + +Thanks to [David de Boer](https://github.com/ddeboer) for implementing this adapter. + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/vendor/php-http/guzzle6-adapter/composer.json b/vendor/php-http/guzzle6-adapter/composer.json new file mode 100644 index 00000000..2f01d1a4 --- /dev/null +++ b/vendor/php-http/guzzle6-adapter/composer.json @@ -0,0 +1,49 @@ +{ + "name": "php-http/guzzle6-adapter", + "description": "Guzzle 6 HTTP Adapter", + "license": "MIT", + "keywords": ["guzzle", "http"], + "homepage": "http://httplug.io", + "authors": [ + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "require": { + "php": ">=5.5.0", + "php-http/httplug": "^1.0", + "guzzlehttp/guzzle": "^6.0" + }, + "require-dev": { + "ext-curl": "*", + "php-http/adapter-integration-tests": "^0.4" + }, + "provide": { + "php-http/client-implementation": "1.0", + "php-http/async-client-implementation": "1.0" + }, + "autoload": { + "psr-4": { + "Http\\Adapter\\Guzzle6\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Http\\Adapter\\Guzzle6\\Tests\\": "tests/" + } + }, + "scripts": { + "test": "vendor/bin/phpunit", + "test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml" + }, + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + } +} diff --git a/vendor/php-http/guzzle6-adapter/puli.json b/vendor/php-http/guzzle6-adapter/puli.json new file mode 100644 index 00000000..bd296146 --- /dev/null +++ b/vendor/php-http/guzzle6-adapter/puli.json @@ -0,0 +1,16 @@ +{ + "version": "1.0", + "name": "php-http/guzzle6-adapter", + "bindings": { + "04b5a002-71a8-473d-a8df-75671551b84a": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Adapter\\Guzzle6\\Client", + "type": "Http\\Client\\HttpClient" + }, + "9c856476-7f6b-43df-a740-15420a5f839c": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Adapter\\Guzzle6\\Client", + "type": "Http\\Client\\HttpAsyncClient" + } + } +} diff --git a/vendor/php-http/guzzle6-adapter/src/Client.php b/vendor/php-http/guzzle6-adapter/src/Client.php new file mode 100644 index 00000000..ded7494b --- /dev/null +++ b/vendor/php-http/guzzle6-adapter/src/Client.php @@ -0,0 +1,84 @@ + + */ +class Client implements HttpClient, HttpAsyncClient +{ + /** + * @var ClientInterface + */ + private $client; + + /** + * @param ClientInterface|null $client + */ + public function __construct(ClientInterface $client = null) + { + if (!$client) { + $client = static::buildClient(); + } + + $this->client = $client; + } + + /** + * Factory method to create the guzzle 6 adapter with custom configuration for guzzle. + * + * @param array $config Configuration to create guzzle with. + * + * @return Client + */ + public static function createWithConfig(array $config) + { + return new self(static::buildClient($config)); + } + + /** + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request) + { + $promise = $this->sendAsyncRequest($request); + + return $promise->wait(); + } + + /** + * {@inheritdoc} + */ + public function sendAsyncRequest(RequestInterface $request) + { + $promise = $this->client->sendAsync($request); + + return new Promise($promise, $request); + } + + /** + * Build the guzzle client instance. + * + * @param array $config Additional configuration + * + * @return GuzzleClient + */ + private static function buildClient(array $config = []) + { + $handlerStack = new HandlerStack(\GuzzleHttp\choose_handler()); + $handlerStack->push(Middleware::prepareBody(), 'prepare_body'); + $config = array_merge(['handler' => $handlerStack], $config); + + return new GuzzleClient($config); + } +} diff --git a/vendor/php-http/guzzle6-adapter/src/Promise.php b/vendor/php-http/guzzle6-adapter/src/Promise.php new file mode 100644 index 00000000..4d5eb8df --- /dev/null +++ b/vendor/php-http/guzzle6-adapter/src/Promise.php @@ -0,0 +1,140 @@ + + */ +class Promise implements HttpPromise +{ + /** + * @var PromiseInterface + */ + private $promise; + + /** + * @var string State of the promise + */ + private $state; + + /** + * @var ResponseInterface + */ + private $response; + + /** + * @var HttplugException + */ + private $exception; + + /** + * @var RequestInterface + */ + private $request; + + /** + * @param PromiseInterface $promise + * @param RequestInterface $request + */ + public function __construct(PromiseInterface $promise, RequestInterface $request) + { + $this->request = $request; + $this->state = self::PENDING; + $this->promise = $promise->then(function ($response) { + $this->response = $response; + $this->state = self::FULFILLED; + + return $response; + }, function ($reason) use ($request) { + $this->state = self::REJECTED; + + if ($reason instanceof HttplugException) { + $this->exception = $reason; + } elseif ($reason instanceof GuzzleExceptions\GuzzleException) { + $this->exception = $this->handleException($reason, $request); + } elseif ($reason instanceof \Exception) { + $this->exception = new \RuntimeException('Invalid exception returned from Guzzle6', 0, $reason); + } else { + $this->exception = new \UnexpectedValueException('Reason returned from Guzzle6 must be an Exception', 0, $reason); + } + + throw $this->exception; + }); + } + + /** + * {@inheritdoc} + */ + public function then(callable $onFulfilled = null, callable $onRejected = null) + { + return new static($this->promise->then($onFulfilled, $onRejected), $this->request); + } + + /** + * {@inheritdoc} + */ + public function getState() + { + return $this->state; + } + + /** + * {@inheritdoc} + */ + public function wait($unwrap = true) + { + $this->promise->wait(false); + + if ($unwrap) { + if ($this->getState() == self::REJECTED) { + throw $this->exception; + } + + return $this->response; + } + } + + /** + * Converts a Guzzle exception into an Httplug exception. + * + * @param GuzzleExceptions\GuzzleException $exception + * @param RequestInterface $request + * + * @return HttplugException + */ + private function handleException(GuzzleExceptions\GuzzleException $exception, RequestInterface $request) + { + if ($exception instanceof GuzzleExceptions\SeekException) { + return new HttplugException\RequestException($exception->getMessage(), $request, $exception); + } + + if ($exception instanceof GuzzleExceptions\ConnectException) { + return new HttplugException\NetworkException($exception->getMessage(), $exception->getRequest(), $exception); + } + + if ($exception instanceof GuzzleExceptions\RequestException) { + // Make sure we have a response for the HttpException + if ($exception->hasResponse()) { + return new HttplugException\HttpException( + $exception->getMessage(), + $exception->getRequest(), + $exception->getResponse(), + $exception + ); + } + + return new HttplugException\RequestException($exception->getMessage(), $exception->getRequest(), $exception); + } + + return new HttplugException\TransferException($exception->getMessage(), 0, $exception); + } +} diff --git a/vendor/php-http/httplug/CHANGELOG.md b/vendor/php-http/httplug/CHANGELOG.md new file mode 100644 index 00000000..8478966a --- /dev/null +++ b/vendor/php-http/httplug/CHANGELOG.md @@ -0,0 +1,72 @@ +# Change Log + +## 1.1.0 - 2016-08-31 + +- Added HttpFulfilledPromise and HttpRejectedPromise which respect the HttpAsyncClient interface + +## 1.0.0 - 2016-01-26 + +### Removed + +- Stability configuration from composer + + +## 1.0.0-RC1 - 2016-01-12 + +### Changed + +- Updated package files +- Updated promise dependency to RC1 + + +## 1.0.0-beta - 2015-12-17 + +### Added + +- Puli configuration and binding types + +### Changed + +- Exception concept + + +## 1.0.0-alpha3 - 2015-12-13 + +### Changed + +- Async client does not throw exceptions + +### Removed + +- Promise interface moved to its own repository: [php-http/promise](https://github.com/php-http/promise) + + +## 1.0.0-alpha2 - 2015-11-16 + +### Added + +- Async client and Promise interface + + +## 1.0.0-alpha - 2015-10-26 + +### Added + +- Better domain exceptions. + +### Changed + +- Purpose of the library: general HTTP CLient abstraction. + +### Removed + +- Request options: they should be configured at construction time. +- Multiple request sending: should be done asynchronously using Async Client. +- `getName` method + + +## 0.1.0 - 2015-06-03 + +### Added + +- Initial release diff --git a/vendor/php-http/httplug/LICENSE b/vendor/php-http/httplug/LICENSE new file mode 100644 index 00000000..48741e41 --- /dev/null +++ b/vendor/php-http/httplug/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2014-2015 Eric GELOEN +Copyright (c) 2015-2016 PHP HTTP Team + +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. diff --git a/vendor/php-http/httplug/README.md b/vendor/php-http/httplug/README.md new file mode 100644 index 00000000..f46212bc --- /dev/null +++ b/vendor/php-http/httplug/README.md @@ -0,0 +1,57 @@ +# HTTPlug + +[![Latest Version](https://img.shields.io/github/release/php-http/httplug.svg?style=flat-square)](https://github.com/php-http/httplug/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Build Status](https://img.shields.io/travis/php-http/httplug.svg?style=flat-square)](https://travis-ci.org/php-http/httplug) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/httplug.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/httplug) +[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/httplug.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/httplug) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/httplug.svg?style=flat-square)](https://packagist.org/packages/php-http/httplug) + +[![Slack Status](http://slack.httplug.io/badge.svg)](http://slack.httplug.io) +[![Email](https://img.shields.io/badge/email-team@httplug.io-blue.svg?style=flat-square)](mailto:team@httplug.io) + +**HTTPlug, the HTTP client abstraction for PHP.** + + +## Install + +Via Composer + +``` bash +$ composer require php-http/httplug +``` + + +## Intro + +This is the contract package for HTTP Client. +Use it to create HTTP Clients which are interoperable and compatible with [PSR-7](http://www.php-fig.org/psr/psr-7/). + +This library is the official successor of the [ivory http adapter](https://github.com/egeloen/ivory-http-adapter). + + +## Documentation + +Please see the [official documentation](http://docs.php-http.org). + + +## Testing + +``` bash +$ composer test +``` + + +## Contributing + +Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html). + + +## Security + +If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org). + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/vendor/php-http/httplug/composer.json b/vendor/php-http/httplug/composer.json new file mode 100644 index 00000000..f74c4d30 --- /dev/null +++ b/vendor/php-http/httplug/composer.json @@ -0,0 +1,40 @@ +{ + "name": "php-http/httplug", + "description": "HTTPlug, the HTTP client abstraction for PHP", + "license": "MIT", + "keywords": ["http", "client"], + "homepage": "http://httplug.io", + "authors": [ + { + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0", + "php-http/promise": "^1.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.4", + "henrikbjorn/phpspec-code-coverage" : "^1.0" + }, + "autoload": { + "psr-4": { + "Http\\Client\\": "src/" + } + }, + "scripts": { + "test": "vendor/bin/phpspec run", + "test-ci": "vendor/bin/phpspec run -c phpspec.ci.yml" + }, + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + } +} diff --git a/vendor/php-http/httplug/puli.json b/vendor/php-http/httplug/puli.json new file mode 100644 index 00000000..41683315 --- /dev/null +++ b/vendor/php-http/httplug/puli.json @@ -0,0 +1,12 @@ +{ + "version": "1.0", + "name": "php-http/httplug", + "binding-types": { + "Http\\Client\\HttpAsyncClient": { + "description": "Async HTTP Client" + }, + "Http\\Client\\HttpClient": { + "description": "HTTP Client" + } + } +} diff --git a/vendor/php-http/httplug/src/Exception.php b/vendor/php-http/httplug/src/Exception.php new file mode 100644 index 00000000..e7382c3c --- /dev/null +++ b/vendor/php-http/httplug/src/Exception.php @@ -0,0 +1,12 @@ + + */ +interface Exception +{ +} diff --git a/vendor/php-http/httplug/src/Exception/HttpException.php b/vendor/php-http/httplug/src/Exception/HttpException.php new file mode 100644 index 00000000..f4f32a4d --- /dev/null +++ b/vendor/php-http/httplug/src/Exception/HttpException.php @@ -0,0 +1,74 @@ + + */ +class HttpException extends RequestException +{ + /** + * @var ResponseInterface + */ + protected $response; + + /** + * @param string $message + * @param RequestInterface $request + * @param ResponseInterface $response + * @param \Exception|null $previous + */ + public function __construct( + $message, + RequestInterface $request, + ResponseInterface $response, + \Exception $previous = null + ) { + parent::__construct($message, $request, $previous); + + $this->response = $response; + $this->code = $response->getStatusCode(); + } + + /** + * Returns the response. + * + * @return ResponseInterface + */ + public function getResponse() + { + return $this->response; + } + + /** + * Factory method to create a new exception with a normalized error message. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param \Exception|null $previous + * + * @return HttpException + */ + public static function create( + RequestInterface $request, + ResponseInterface $response, + \Exception $previous = null + ) { + $message = sprintf( + '[url] %s [http method] %s [status code] %s [reason phrase] %s', + $request->getRequestTarget(), + $request->getMethod(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + + return new self($message, $request, $response, $previous); + } +} diff --git a/vendor/php-http/httplug/src/Exception/NetworkException.php b/vendor/php-http/httplug/src/Exception/NetworkException.php new file mode 100644 index 00000000..f2198e5b --- /dev/null +++ b/vendor/php-http/httplug/src/Exception/NetworkException.php @@ -0,0 +1,14 @@ + + */ +class NetworkException extends RequestException +{ +} diff --git a/vendor/php-http/httplug/src/Exception/RequestException.php b/vendor/php-http/httplug/src/Exception/RequestException.php new file mode 100644 index 00000000..cdce14bd --- /dev/null +++ b/vendor/php-http/httplug/src/Exception/RequestException.php @@ -0,0 +1,43 @@ + + */ +class RequestException extends TransferException +{ + /** + * @var RequestInterface + */ + private $request; + + /** + * @param string $message + * @param RequestInterface $request + * @param \Exception|null $previous + */ + public function __construct($message, RequestInterface $request, \Exception $previous = null) + { + $this->request = $request; + + parent::__construct($message, 0, $previous); + } + + /** + * Returns the request. + * + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } +} diff --git a/vendor/php-http/httplug/src/Exception/TransferException.php b/vendor/php-http/httplug/src/Exception/TransferException.php new file mode 100644 index 00000000..a858cf5e --- /dev/null +++ b/vendor/php-http/httplug/src/Exception/TransferException.php @@ -0,0 +1,14 @@ + + */ +class TransferException extends \RuntimeException implements Exception +{ +} diff --git a/vendor/php-http/httplug/src/HttpAsyncClient.php b/vendor/php-http/httplug/src/HttpAsyncClient.php new file mode 100644 index 00000000..492e511b --- /dev/null +++ b/vendor/php-http/httplug/src/HttpAsyncClient.php @@ -0,0 +1,27 @@ + + */ +interface HttpAsyncClient +{ + /** + * Sends a PSR-7 request in an asynchronous way. + * + * Exceptions related to processing the request are available from the returned Promise. + * + * @param RequestInterface $request + * + * @return Promise Resolves a PSR-7 Response or fails with an Http\Client\Exception. + * + * @throws \Exception If processing the request is impossible (eg. bad configuration). + */ + public function sendAsyncRequest(RequestInterface $request); +} diff --git a/vendor/php-http/httplug/src/HttpClient.php b/vendor/php-http/httplug/src/HttpClient.php new file mode 100644 index 00000000..0e51749f --- /dev/null +++ b/vendor/php-http/httplug/src/HttpClient.php @@ -0,0 +1,28 @@ + + * @author Márk Sági-Kazár + * @author David Buchmann + */ +interface HttpClient +{ + /** + * Sends a PSR-7 request. + * + * @param RequestInterface $request + * + * @return ResponseInterface + * + * @throws \Http\Client\Exception If an error happens during processing the request. + * @throws \Exception If processing the request is impossible (eg. bad configuration). + */ + public function sendRequest(RequestInterface $request); +} diff --git a/vendor/php-http/httplug/src/Promise/HttpFulfilledPromise.php b/vendor/php-http/httplug/src/Promise/HttpFulfilledPromise.php new file mode 100644 index 00000000..6779e447 --- /dev/null +++ b/vendor/php-http/httplug/src/Promise/HttpFulfilledPromise.php @@ -0,0 +1,57 @@ +response = $response; + } + + /** + * {@inheritdoc} + */ + public function then(callable $onFulfilled = null, callable $onRejected = null) + { + if (null === $onFulfilled) { + return $this; + } + + try { + return new self($onFulfilled($this->response)); + } catch (Exception $e) { + return new HttpRejectedPromise($e); + } + } + + /** + * {@inheritdoc} + */ + public function getState() + { + return Promise::FULFILLED; + } + + /** + * {@inheritdoc} + */ + public function wait($unwrap = true) + { + if ($unwrap) { + return $this->response; + } + } +} diff --git a/vendor/php-http/httplug/src/Promise/HttpRejectedPromise.php b/vendor/php-http/httplug/src/Promise/HttpRejectedPromise.php new file mode 100644 index 00000000..bfb0738f --- /dev/null +++ b/vendor/php-http/httplug/src/Promise/HttpRejectedPromise.php @@ -0,0 +1,56 @@ +exception = $exception; + } + + /** + * {@inheritdoc} + */ + public function then(callable $onFulfilled = null, callable $onRejected = null) + { + if (null === $onRejected) { + return $this; + } + + try { + return new HttpFulfilledPromise($onRejected($this->exception)); + } catch (Exception $e) { + return new self($e); + } + } + + /** + * {@inheritdoc} + */ + public function getState() + { + return Promise::REJECTED; + } + + /** + * {@inheritdoc} + */ + public function wait($unwrap = true) + { + if ($unwrap) { + throw $this->exception; + } + } +} diff --git a/vendor/php-http/message-factory/CHANGELOG.md b/vendor/php-http/message-factory/CHANGELOG.md new file mode 100644 index 00000000..4711924c --- /dev/null +++ b/vendor/php-http/message-factory/CHANGELOG.md @@ -0,0 +1,65 @@ +# Change Log + + +## 1.0.2 - 2015-12-19 + +### Added + +- Request and Response factory binding types to Puli + + +## 1.0.1 - 2015-12-17 + +### Added + +- Puli configuration and binding types + + +## 1.0.0 - 2015-12-15 + +### Added + +- Response Factory in order to be reused in Message and Server Message factories +- Request Factory + +### Changed + +- Message Factory extends Request and Response factories + + +## 1.0.0-RC1 - 2015-12-14 + +### Added + +- CS check + +### Changed + +- RuntimeException is thrown when the StreamFactory cannot write to the underlying stream + + +## 0.3.0 - 2015-11-16 + +### Removed + +- Client Context Factory +- Factory Awares and Templates + + +## 0.2.0 - 2015-11-16 + +### Changed + +- Reordered the parameters when creating a message to have the protocol last, +as its the least likely to need to be changed. + + +## 0.1.0 - 2015-06-01 + +### Added + +- Initial release + +### Changed + +- Helpers are renamed to templates diff --git a/vendor/php-http/message-factory/LICENSE b/vendor/php-http/message-factory/LICENSE new file mode 100644 index 00000000..8e2c4a0b --- /dev/null +++ b/vendor/php-http/message-factory/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 PHP HTTP Team + +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. diff --git a/vendor/php-http/message-factory/README.md b/vendor/php-http/message-factory/README.md new file mode 100644 index 00000000..4654495a --- /dev/null +++ b/vendor/php-http/message-factory/README.md @@ -0,0 +1,36 @@ +# PSR-7 Message Factory + +[![Latest Version](https://img.shields.io/github/release/php-http/message-factory.svg?style=flat-square)](https://github.com/php-http/message-factory/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/message-factory.svg?style=flat-square)](https://packagist.org/packages/php-http/message-factory) + +**Factory interfaces for PSR-7 HTTP Message.** + + +## Install + +Via Composer + +``` bash +$ composer require php-http/message-factory +``` + + +## Documentation + +Please see the [official documentation](http://php-http.readthedocs.org/en/latest/message-factory/). + + +## Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md) and [CONDUCT](CONDUCT.md) for details. + + +## Security + +If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org). + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/vendor/php-http/message-factory/composer.json b/vendor/php-http/message-factory/composer.json new file mode 100644 index 00000000..7c72febe --- /dev/null +++ b/vendor/php-http/message-factory/composer.json @@ -0,0 +1,27 @@ +{ + "name": "php-http/message-factory", + "description": "Factory interfaces for PSR-7 HTTP Message", + "license": "MIT", + "keywords": ["http", "factory", "message", "stream", "uri"], + "homepage": "http://php-http.org", + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0" + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/php-http/message-factory/puli.json b/vendor/php-http/message-factory/puli.json new file mode 100644 index 00000000..08d37627 --- /dev/null +++ b/vendor/php-http/message-factory/puli.json @@ -0,0 +1,43 @@ +{ + "version": "1.0", + "binding-types": { + "Http\\Message\\MessageFactory": { + "description": "PSR-7 Message Factory", + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + }, + "Http\\Message\\RequestFactory": { + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + }, + "Http\\Message\\ResponseFactory": { + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + }, + "Http\\Message\\StreamFactory": { + "description": "PSR-7 Stream Factory", + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + }, + "Http\\Message\\UriFactory": { + "description": "PSR-7 URI Factory", + "parameters": { + "depends": { + "description": "Optional class dependency which can be checked by consumers" + } + } + } + } +} diff --git a/vendor/php-http/message-factory/src/MessageFactory.php b/vendor/php-http/message-factory/src/MessageFactory.php new file mode 100644 index 00000000..965aaa80 --- /dev/null +++ b/vendor/php-http/message-factory/src/MessageFactory.php @@ -0,0 +1,12 @@ + + */ +interface MessageFactory extends RequestFactory, ResponseFactory +{ +} diff --git a/vendor/php-http/message-factory/src/RequestFactory.php b/vendor/php-http/message-factory/src/RequestFactory.php new file mode 100644 index 00000000..624e82f3 --- /dev/null +++ b/vendor/php-http/message-factory/src/RequestFactory.php @@ -0,0 +1,34 @@ + + */ +interface RequestFactory +{ + /** + * Creates a new PSR-7 request. + * + * @param string $method + * @param string|UriInterface $uri + * @param array $headers + * @param resource|string|StreamInterface|null $body + * @param string $protocolVersion + * + * @return RequestInterface + */ + public function createRequest( + $method, + $uri, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ); +} diff --git a/vendor/php-http/message-factory/src/ResponseFactory.php b/vendor/php-http/message-factory/src/ResponseFactory.php new file mode 100644 index 00000000..2411ed3a --- /dev/null +++ b/vendor/php-http/message-factory/src/ResponseFactory.php @@ -0,0 +1,35 @@ + + */ +interface ResponseFactory +{ + /** + * Creates a new PSR-7 response. + * + * @param int $statusCode + * @param string|null $reasonPhrase + * @param array $headers + * @param resource|string|StreamInterface|null $body + * @param string $protocolVersion + * + * @return ResponseInterface + */ + public function createResponse( + $statusCode = 200, + $reasonPhrase = null, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ); +} diff --git a/vendor/php-http/message-factory/src/StreamFactory.php b/vendor/php-http/message-factory/src/StreamFactory.php new file mode 100644 index 00000000..327a902f --- /dev/null +++ b/vendor/php-http/message-factory/src/StreamFactory.php @@ -0,0 +1,25 @@ + + */ +interface StreamFactory +{ + /** + * Creates a new PSR-7 stream. + * + * @param string|resource|StreamInterface|null $body + * + * @return StreamInterface + * + * @throws \InvalidArgumentException If the stream body is invalid. + * @throws \RuntimeException If creating the stream from $body fails. + */ + public function createStream($body = null); +} diff --git a/vendor/php-http/message-factory/src/UriFactory.php b/vendor/php-http/message-factory/src/UriFactory.php new file mode 100644 index 00000000..f05e6252 --- /dev/null +++ b/vendor/php-http/message-factory/src/UriFactory.php @@ -0,0 +1,24 @@ + + */ +interface UriFactory +{ + /** + * Creates an PSR-7 URI. + * + * @param string|UriInterface $uri + * + * @return UriInterface + * + * @throws \InvalidArgumentException If the $uri argument can not be converted into a valid URI. + */ + public function createUri($uri); +} diff --git a/vendor/php-http/message/CHANGELOG.md b/vendor/php-http/message/CHANGELOG.md new file mode 100644 index 00000000..48319fd0 --- /dev/null +++ b/vendor/php-http/message/CHANGELOG.md @@ -0,0 +1,194 @@ +# Change Log + + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + + +## Unreleased + +## [1.7.2] - 2018-10-30 + +### Fixed + +- FilteredStream uses `@trigger_error` instead of throwing exceptions to not + break careless users. You still need to fix your stream code to respect + `isSeekable`. Seeking does not work as expected, and we will add exceptions + in version 2. + +## [1.7.1] - 2018-10-29 + +### Fixed + +- FilteredStream is not actually seekable + + +## [1.7.0] - 2018-08-15 + +### Fixed + +- Fix CurlCommandFormatter for binary request payloads +- Fix QueryParam authentication to assemble proper URL regardless of PHP `arg_separator.output` directive +- Do not pass `null` parameters to `Clue\StreamFilter\fun` + +### Changed + +- Dropped tests on HHVM + + +## [1.6.0] - 2017-07-05 + +### Added + +- CookieUtil::parseDate to create a date from cookie date string + +### Fixed + +- Fix curl command of CurlFormatter when there is an user-agent header + + +## [1.5.0] - 2017-02-14 + +### Added + +- Check for empty string in Stream factories +- Cookie::createWithoutValidation Static constructor to create a cookie. Will not perform any attribute validation during instantiation. +- Cookie::isValid Method to check if cookie attributes are valid. + +### Fixed + +- FilteredStream::getSize returns null because the contents size is unknown. +- Stream factories does not rewinds streams. The previous behavior was not coherent between factories and inputs. + +### Deprecated + +- FilteredStream::getReadFilter The read filter is internal and should never be used by consuming code. +- FilteredStream::getWriteFilter We did not implement writing to the streams at all. And if we do, the filter is an internal information and should not be used by consuming code. + + +## [1.4.1] - 2016-12-16 + +### Fixed + +- Cookie::matchPath Cookie with root path (`/`) will not match sub path (e.g. `/cookie`). + + +## [1.4.0] - 2016-10-20 + +### Added + +- Message, stream and URI factories for [Slim Framework](https://github.com/slimphp/Slim) +- BufferedStream that allow you to decorate a non-seekable stream with a seekable one. +- cUrlFormatter to be able to redo the request with a cURL command + + +## [1.3.1] - 2016-07-15 + +### Fixed + +- FullHttpMessageFormatter will not read from streams that you cannot rewind (non-seekable) +- FullHttpMessageFormatter will not read from the stream if $maxBodyLength is zero +- FullHttpMessageFormatter rewinds streams after they are read + + +## [1.3.0] - 2016-07-14 + +### Added + +- FullHttpMessageFormatter to include headers and body in the formatted message + +### Fixed + +- #41: Response builder broke header value + + +## [1.2.0] - 2016-03-29 + +### Added + +- The RequestMatcher is built after the Symfony RequestMatcher and separates + scheme, host and path expressions and provides an option to filter on the + method +- New RequestConditional authentication method using request matchers +- Add automatic basic auth info detection based on the URL + +### Changed + +- Improved ResponseBuilder + +### Deprecated + +- RegexRequestMatcher, use RequestMatcher instead +- Matching authenitcation method, use RequestConditional instead + + +## [1.1.0] - 2016-02-25 + +### Added + + - Add a request matcher interface and regex implementation + - Add a callback request matcher implementation + - Add a ResponseBuilder, to create PSR7 Response from a string + +### Fixed + + - Fix casting string on a FilteredStream not filtering the output + + +## [1.0.0] - 2016-01-27 + + +## [0.2.0] - 2015-12-29 + +### Added + +- Autoregistration of stream filters using Composer autoload +- Cookie +- [Apigen](http://www.apigen.org/) configuration + + +## [0.1.2] - 2015-12-26 + +### Added + +- Request and response factory bindings + +### Fixed + +- Chunk filter namespace in Dechunk stream + + +## [0.1.1] - 2015-12-25 + +### Added + +- Formatter + + +## 0.1.0 - 2015-12-24 + +### Added + +- Authentication +- Encoding +- Message decorator +- Message factory (Guzzle, Diactoros) + + +[Unreleased]: https://github.com/php-http/message/compare/v1.7.1...HEAD +[1.7.1]: https://github.com/php-http/message/compare/1.7.0...v1.7.1 +[1.7.0]: https://github.com/php-http/message/compare/1.6.0...1.7.0 +[1.6.0]: https://github.com/php-http/message/compare/1.5.0...1.6.0 +[1.5.0]: https://github.com/php-http/message/compare/v1.4.1...1.5.0 +[1.4.1]: https://github.com/php-http/message/compare/v1.4.0...v1.4.1 +[1.4.0]: https://github.com/php-http/message/compare/v1.3.1...v1.4.0 +[1.3.1]: https://github.com/php-http/message/compare/v1.3.0...v1.3.1 +[1.3.0]: https://github.com/php-http/message/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/php-http/message/compare/v1.1.0...v1.2.0 +[1.1.0]: https://github.com/php-http/message/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/php-http/message/compare/0.2.0...v1.0.0 +[0.2.0]: https://github.com/php-http/message/compare/v0.1.2...0.2.0 +[0.1.2]: https://github.com/php-http/message/compare/v0.1.1...v0.1.2 +[0.1.1]: https://github.com/php-http/message/compare/v0.1.0...v0.1.1 diff --git a/vendor/php-http/message/LICENSE b/vendor/php-http/message/LICENSE new file mode 100644 index 00000000..4558d6f0 --- /dev/null +++ b/vendor/php-http/message/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2016 PHP HTTP Team + +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. diff --git a/vendor/php-http/message/README.md b/vendor/php-http/message/README.md new file mode 100644 index 00000000..f6f6e197 --- /dev/null +++ b/vendor/php-http/message/README.md @@ -0,0 +1,61 @@ +# HTTP Message + +[![Latest Version](https://img.shields.io/github/release/php-http/message.svg?style=flat-square)](https://github.com/php-http/message/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Build Status](https://img.shields.io/travis/php-http/message.svg?style=flat-square)](https://travis-ci.org/php-http/message) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/message.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/message) +[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/message.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/message) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/message.svg?style=flat-square)](https://packagist.org/packages/php-http/message) + +**HTTP Message related tools.** + + +## Install + +Via Composer + +``` bash +$ composer require php-http/message +``` + + +## Intro + +This package contains various PSR-7 tools which might be useful in an HTTP workflow: + +- Authentication method implementations +- Various Stream encoding tools +- Message decorators +- Message factory implementations for Guzzle PSR-7 and Diactoros +- Cookie implementation +- Request matchers + + +## Documentation + +Please see the [official documentation](http://docs.php-http.org/en/latest/message.html). + + +## Testing + +``` bash +$ composer test +``` + + +## Contributing + +Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html). + +## Credits + +Thanks to [Cuzzle](https://github.com/namshi/cuzzle) for inpiration for the `CurlCommandFormatter`. + +## Security + +If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org). + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/vendor/php-http/message/apigen.neon b/vendor/php-http/message/apigen.neon new file mode 100644 index 00000000..0ba1a185 --- /dev/null +++ b/vendor/php-http/message/apigen.neon @@ -0,0 +1,6 @@ +source: + - src/ + +destination: build/api/ + +templateTheme: bootstrap diff --git a/vendor/php-http/message/composer.json b/vendor/php-http/message/composer.json new file mode 100644 index 00000000..3a9b3ed4 --- /dev/null +++ b/vendor/php-http/message/composer.json @@ -0,0 +1,60 @@ +{ + "name": "php-http/message", + "description": "HTTP Message related tools", + "license": "MIT", + "keywords": ["message", "http", "psr-7"], + "homepage": "http://php-http.org", + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "require": { + "php": "^5.4 || ^7.0", + "psr/http-message": "^1.0", + "php-http/message-factory": "^1.0.2", + "clue/stream-filter": "^1.4" + }, + "provide": { + "php-http/message-factory-implementation": "1.0" + }, + "require-dev": { + "zendframework/zend-diactoros": "^1.0", + "guzzlehttp/psr7": "^1.0", + "ext-zlib": "*", + "phpspec/phpspec": "^2.4", + "henrikbjorn/phpspec-code-coverage" : "^1.0", + "coduo/phpspec-data-provider-extension": "^1.0", + "akeneo/phpspec-skip-example-extension": "^1.0", + "slim/slim": "^3.0" + }, + "suggest": { + "zendframework/zend-diactoros": "Used with Diactoros Factories", + "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories", + "slim/slim": "Used with Slim Framework PSR-7 implementation", + "ext-zlib": "Used with compressor/decompressor streams" + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + }, + "files": [ + "src/filters.php" + ] + }, + "autoload-dev": { + "psr-4": { + "spec\\Http\\Message\\": "spec/" + } + }, + "scripts": { + "test": "vendor/bin/phpspec run", + "test-ci": "vendor/bin/phpspec run -c phpspec.ci.yml" + }, + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + } +} diff --git a/vendor/php-http/message/puli.json b/vendor/php-http/message/puli.json new file mode 100644 index 00000000..024a85d9 --- /dev/null +++ b/vendor/php-http/message/puli.json @@ -0,0 +1,111 @@ +{ + "version": "1.0", + "name": "php-http/message", + "bindings": { + "064d003d-78a1-48c4-8f3b-1f92ff25da69": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\DiactorosMessageFactory", + "type": "Http\\Message\\MessageFactory", + "parameters": { + "depends": "Zend\\Diactoros\\Request" + } + }, + "0836751e-6558-4d1b-8993-4a52012947c3": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\SlimMessageFactory", + "type": "Http\\Message\\ResponseFactory" + }, + "1d127622-dc61-4bfa-b9da-d221548d72c3": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\SlimMessageFactory", + "type": "Http\\Message\\RequestFactory" + }, + "2438c2d0-0658-441f-8855-ddaf0f87d54d": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\GuzzleMessageFactory", + "type": "Http\\Message\\MessageFactory", + "parameters": { + "depends": "GuzzleHttp\\Psr7\\Request" + } + }, + "253aa08c-d705-46e7-b1d2-e28c97eef792": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\GuzzleMessageFactory", + "type": "Http\\Message\\RequestFactory", + "parameters": { + "depends": "GuzzleHttp\\Psr7\\Request" + } + }, + "273a34f9-62f4-4ba1-9801-b1284d49ff89": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\StreamFactory\\GuzzleStreamFactory", + "type": "Http\\Message\\StreamFactory", + "parameters": { + "depends": "GuzzleHttp\\Psr7\\Stream" + } + }, + "304b83db-b594-4d83-ae75-1f633adf92f7": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\UriFactory\\GuzzleUriFactory", + "type": "Http\\Message\\UriFactory", + "parameters": { + "depends": "GuzzleHttp\\Psr7\\Uri" + } + }, + "3f4bc1cd-aa95-4702-9fa7-65408e471691": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\UriFactory\\DiactorosUriFactory", + "type": "Http\\Message\\UriFactory", + "parameters": { + "depends": "Zend\\Diactoros\\Uri" + } + }, + "4672a6ee-ad9e-4109-a5d1-b7d46f26c7a1": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\SlimMessageFactory", + "type": "Http\\Message\\MessageFactory" + }, + "6234e947-d3bd-43eb-97d5-7f9e22e6bb1b": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\DiactorosMessageFactory", + "type": "Http\\Message\\ResponseFactory", + "parameters": { + "depends": "Zend\\Diactoros\\Response" + } + }, + "6a9ad6ce-d82c-470f-8e30-60f21d9d95bf": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\UriFactory\\SlimUriFactory", + "type": "Http\\Message\\UriFactory" + }, + "72c2afa0-ea56-4d03-adb6-a9f241a8a734": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\StreamFactory\\SlimStreamFactory", + "type": "Http\\Message\\StreamFactory" + }, + "95c1be8f-39fe-4abd-8351-92cb14379a75": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\StreamFactory\\DiactorosStreamFactory", + "type": "Http\\Message\\StreamFactory", + "parameters": { + "depends": "Zend\\Diactoros\\Stream" + } + }, + "a018af27-7590-4dcf-83a1-497f95604cd6": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\GuzzleMessageFactory", + "type": "Http\\Message\\ResponseFactory", + "parameters": { + "depends": "GuzzleHttp\\Psr7\\Response" + } + }, + "c07955b1-de46-43db-923b-d07fae9382cb": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Message\\MessageFactory\\DiactorosMessageFactory", + "type": "Http\\Message\\RequestFactory", + "parameters": { + "depends": "Zend\\Diactoros\\Request" + } + } + } +} diff --git a/vendor/php-http/message/src/Authentication.php b/vendor/php-http/message/src/Authentication.php new file mode 100644 index 00000000..b50366ff --- /dev/null +++ b/vendor/php-http/message/src/Authentication.php @@ -0,0 +1,22 @@ + + */ +interface Authentication +{ + /** + * Authenticates a request. + * + * @param RequestInterface $request + * + * @return RequestInterface + */ + public function authenticate(RequestInterface $request); +} diff --git a/vendor/php-http/message/src/Authentication/AutoBasicAuth.php b/vendor/php-http/message/src/Authentication/AutoBasicAuth.php new file mode 100644 index 00000000..7b6a4294 --- /dev/null +++ b/vendor/php-http/message/src/Authentication/AutoBasicAuth.php @@ -0,0 +1,48 @@ + + */ +final class AutoBasicAuth implements Authentication +{ + /** + * Whether user info should be removed from the URI. + * + * @var bool + */ + private $shouldRemoveUserInfo; + + /** + * @param bool|true $shouldRremoveUserInfo + */ + public function __construct($shouldRremoveUserInfo = true) + { + $this->shouldRemoveUserInfo = (bool) $shouldRremoveUserInfo; + } + + /** + * {@inheritdoc} + */ + public function authenticate(RequestInterface $request) + { + $uri = $request->getUri(); + $userInfo = $uri->getUserInfo(); + + if (!empty($userInfo)) { + if ($this->shouldRemoveUserInfo) { + $request = $request->withUri($uri->withUserInfo('')); + } + + $request = $request->withHeader('Authorization', sprintf('Basic %s', base64_encode($userInfo))); + } + + return $request; + } +} diff --git a/vendor/php-http/message/src/Authentication/BasicAuth.php b/vendor/php-http/message/src/Authentication/BasicAuth.php new file mode 100644 index 00000000..23618a53 --- /dev/null +++ b/vendor/php-http/message/src/Authentication/BasicAuth.php @@ -0,0 +1,44 @@ + + */ +final class BasicAuth implements Authentication +{ + /** + * @var string + */ + private $username; + + /** + * @var string + */ + private $password; + + /** + * @param string $username + * @param string $password + */ + public function __construct($username, $password) + { + $this->username = $username; + $this->password = $password; + } + + /** + * {@inheritdoc} + */ + public function authenticate(RequestInterface $request) + { + $header = sprintf('Basic %s', base64_encode(sprintf('%s:%s', $this->username, $this->password))); + + return $request->withHeader('Authorization', $header); + } +} diff --git a/vendor/php-http/message/src/Authentication/Bearer.php b/vendor/php-http/message/src/Authentication/Bearer.php new file mode 100644 index 00000000..a8fb21a1 --- /dev/null +++ b/vendor/php-http/message/src/Authentication/Bearer.php @@ -0,0 +1,37 @@ + + */ +final class Bearer implements Authentication +{ + /** + * @var string + */ + private $token; + + /** + * @param string $token + */ + public function __construct($token) + { + $this->token = $token; + } + + /** + * {@inheritdoc} + */ + public function authenticate(RequestInterface $request) + { + $header = sprintf('Bearer %s', $this->token); + + return $request->withHeader('Authorization', $header); + } +} diff --git a/vendor/php-http/message/src/Authentication/Chain.php b/vendor/php-http/message/src/Authentication/Chain.php new file mode 100644 index 00000000..71002bb1 --- /dev/null +++ b/vendor/php-http/message/src/Authentication/Chain.php @@ -0,0 +1,47 @@ + + */ +final class Chain implements Authentication +{ + /** + * @var Authentication[] + */ + private $authenticationChain = []; + + /** + * @param Authentication[] $authenticationChain + */ + public function __construct(array $authenticationChain = []) + { + foreach ($authenticationChain as $authentication) { + if (!$authentication instanceof Authentication) { + throw new \InvalidArgumentException( + 'Members of the authentication chain must be of type Http\Message\Authentication' + ); + } + } + + $this->authenticationChain = $authenticationChain; + } + + /** + * {@inheritdoc} + */ + public function authenticate(RequestInterface $request) + { + foreach ($this->authenticationChain as $authentication) { + $request = $authentication->authenticate($request); + } + + return $request; + } +} diff --git a/vendor/php-http/message/src/Authentication/Matching.php b/vendor/php-http/message/src/Authentication/Matching.php new file mode 100644 index 00000000..4b89b501 --- /dev/null +++ b/vendor/php-http/message/src/Authentication/Matching.php @@ -0,0 +1,74 @@ + + * + * @deprecated since since version 1.2, and will be removed in 2.0. Use {@link RequestConditional} instead. + */ +final class Matching implements Authentication +{ + /** + * @var Authentication + */ + private $authentication; + + /** + * @var CallbackRequestMatcher + */ + private $matcher; + + /** + * @param Authentication $authentication + * @param callable|null $matcher + */ + public function __construct(Authentication $authentication, callable $matcher = null) + { + if (is_null($matcher)) { + $matcher = function () { + return true; + }; + } + + $this->authentication = $authentication; + $this->matcher = new CallbackRequestMatcher($matcher); + } + + /** + * {@inheritdoc} + */ + public function authenticate(RequestInterface $request) + { + if ($this->matcher->matches($request)) { + return $this->authentication->authenticate($request); + } + + return $request; + } + + /** + * Creates a matching authentication for an URL. + * + * @param Authentication $authentication + * @param string $url + * + * @return self + */ + public static function createUrlMatcher(Authentication $authentication, $url) + { + $matcher = function (RequestInterface $request) use ($url) { + return preg_match($url, $request->getRequestTarget()); + }; + + return new static($authentication, $matcher); + } +} diff --git a/vendor/php-http/message/src/Authentication/QueryParam.php b/vendor/php-http/message/src/Authentication/QueryParam.php new file mode 100644 index 00000000..650cac72 --- /dev/null +++ b/vendor/php-http/message/src/Authentication/QueryParam.php @@ -0,0 +1,50 @@ + + */ +final class QueryParam implements Authentication +{ + /** + * @var array + */ + private $params = []; + + /** + * @param array $params + */ + public function __construct(array $params) + { + $this->params = $params; + } + + /** + * {@inheritdoc} + */ + public function authenticate(RequestInterface $request) + { + $uri = $request->getUri(); + $query = $uri->getQuery(); + $params = []; + + parse_str($query, $params); + + $params = array_merge($params, $this->params); + + $query = http_build_query($params, null, '&'); + + $uri = $uri->withQuery($query); + + return $request->withUri($uri); + } +} diff --git a/vendor/php-http/message/src/Authentication/RequestConditional.php b/vendor/php-http/message/src/Authentication/RequestConditional.php new file mode 100644 index 00000000..54774400 --- /dev/null +++ b/vendor/php-http/message/src/Authentication/RequestConditional.php @@ -0,0 +1,47 @@ + + */ +final class RequestConditional implements Authentication +{ + /** + * @var RequestMatcher + */ + private $requestMatcher; + + /** + * @var Authentication + */ + private $authentication; + + /** + * @param RequestMatcher $requestMatcher + * @param Authentication $authentication + */ + public function __construct(RequestMatcher $requestMatcher, Authentication $authentication) + { + $this->requestMatcher = $requestMatcher; + $this->authentication = $authentication; + } + + /** + * {@inheritdoc} + */ + public function authenticate(RequestInterface $request) + { + if ($this->requestMatcher->matches($request)) { + return $this->authentication->authenticate($request); + } + + return $request; + } +} diff --git a/vendor/php-http/message/src/Authentication/Wsse.php b/vendor/php-http/message/src/Authentication/Wsse.php new file mode 100644 index 00000000..fbbde336 --- /dev/null +++ b/vendor/php-http/message/src/Authentication/Wsse.php @@ -0,0 +1,58 @@ + + */ +final class Wsse implements Authentication +{ + /** + * @var string + */ + private $username; + + /** + * @var string + */ + private $password; + + /** + * @param string $username + * @param string $password + */ + public function __construct($username, $password) + { + $this->username = $username; + $this->password = $password; + } + + /** + * {@inheritdoc} + */ + public function authenticate(RequestInterface $request) + { + // TODO: generate better nonce? + $nonce = substr(md5(uniqid(uniqid().'_', true)), 0, 16); + $created = date('c'); + $digest = base64_encode(sha1(base64_decode($nonce).$created.$this->password, true)); + + $wsse = sprintf( + 'UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"', + $this->username, + $digest, + $nonce, + $created + ); + + return $request + ->withHeader('Authorization', 'WSSE profile="UsernameToken"') + ->withHeader('X-WSSE', $wsse) + ; + } +} diff --git a/vendor/php-http/message/src/Builder/ResponseBuilder.php b/vendor/php-http/message/src/Builder/ResponseBuilder.php new file mode 100644 index 00000000..de2e8828 --- /dev/null +++ b/vendor/php-http/message/src/Builder/ResponseBuilder.php @@ -0,0 +1,148 @@ +response = $response; + } + + /** + * Return response. + * + * @return ResponseInterface + */ + public function getResponse() + { + return $this->response; + } + + /** + * Add headers represented by an array of header lines. + * + * @param string[] $headers Response headers as array of header lines. + * + * @return $this + * + * @throws \UnexpectedValueException For invalid header values. + * @throws \InvalidArgumentException For invalid status code arguments. + */ + public function setHeadersFromArray(array $headers) + { + $status = array_shift($headers); + $this->setStatus($status); + + foreach ($headers as $headerLine) { + $headerLine = trim($headerLine); + if ('' === $headerLine) { + continue; + } + + $this->addHeader($headerLine); + } + + return $this; + } + + /** + * Add headers represented by a single string. + * + * @param string $headers Response headers as single string. + * + * @return $this + * + * @throws \InvalidArgumentException if $headers is not a string on object with __toString() + * @throws \UnexpectedValueException For invalid header values. + */ + public function setHeadersFromString($headers) + { + if (!(is_string($headers) + || (is_object($headers) && method_exists($headers, '__toString'))) + ) { + throw new \InvalidArgumentException( + sprintf( + '%s expects parameter 1 to be a string, %s given', + __METHOD__, + is_object($headers) ? get_class($headers) : gettype($headers) + ) + ); + } + + $this->setHeadersFromArray(explode("\r\n", $headers)); + + return $this; + } + + /** + * Set response status from a status string. + * + * @param string $statusLine Response status as a string. + * + * @return $this + * + * @throws \InvalidArgumentException For invalid status line. + */ + public function setStatus($statusLine) + { + $parts = explode(' ', $statusLine, 3); + if (count($parts) < 2 || 0 !== strpos(strtolower($parts[0]), 'http/')) { + throw new \InvalidArgumentException( + sprintf('"%s" is not a valid HTTP status line', $statusLine) + ); + } + + $reasonPhrase = count($parts) > 2 ? $parts[2] : ''; + $this->response = $this->response + ->withStatus((int) $parts[1], $reasonPhrase) + ->withProtocolVersion(substr($parts[0], 5)); + + return $this; + } + + /** + * Add header represented by a string. + * + * @param string $headerLine Response header as a string. + * + * @return $this + * + * @throws \InvalidArgumentException For invalid header names or values. + */ + public function addHeader($headerLine) + { + $parts = explode(':', $headerLine, 2); + if (2 !== count($parts)) { + throw new \InvalidArgumentException( + sprintf('"%s" is not a valid HTTP header line', $headerLine) + ); + } + $name = trim($parts[0]); + $value = trim($parts[1]); + if ($this->response->hasHeader($name)) { + $this->response = $this->response->withAddedHeader($name, $value); + } else { + $this->response = $this->response->withHeader($name, $value); + } + + return $this; + } +} diff --git a/vendor/php-http/message/src/Cookie.php b/vendor/php-http/message/src/Cookie.php new file mode 100644 index 00000000..98ac57c0 --- /dev/null +++ b/vendor/php-http/message/src/Cookie.php @@ -0,0 +1,526 @@ + + * + * @see http://tools.ietf.org/search/rfc6265 + */ +final class Cookie +{ + /** + * @var string + */ + private $name; + + /** + * @var string|null + */ + private $value; + + /** + * @var int|null + */ + private $maxAge; + + /** + * @var string|null + */ + private $domain; + + /** + * @var string + */ + private $path; + + /** + * @var bool + */ + private $secure; + + /** + * @var bool + */ + private $httpOnly; + + /** + * Expires attribute is HTTP 1.0 only and should be avoided. + * + * @var \DateTime|null + */ + private $expires; + + /** + * @param string $name + * @param string|null $value + * @param int|null $maxAge + * @param string|null $domain + * @param string|null $path + * @param bool $secure + * @param bool $httpOnly + * @param \DateTime|null $expires Expires attribute is HTTP 1.0 only and should be avoided. + * + * @throws \InvalidArgumentException If name, value or max age is not valid. + */ + public function __construct( + $name, + $value = null, + $maxAge = null, + $domain = null, + $path = null, + $secure = false, + $httpOnly = false, + \DateTime $expires = null + ) { + $this->validateName($name); + $this->validateValue($value); + $this->validateMaxAge($maxAge); + + $this->name = $name; + $this->value = $value; + $this->maxAge = $maxAge; + $this->expires = $expires; + $this->domain = $this->normalizeDomain($domain); + $this->path = $this->normalizePath($path); + $this->secure = (bool) $secure; + $this->httpOnly = (bool) $httpOnly; + } + + /** + * Creates a new cookie without any attribute validation. + * + * @param string $name + * @param string|null $value + * @param int $maxAge + * @param string|null $domain + * @param string|null $path + * @param bool $secure + * @param bool $httpOnly + * @param \DateTime|null $expires Expires attribute is HTTP 1.0 only and should be avoided. + */ + public static function createWithoutValidation( + $name, + $value = null, + $maxAge = null, + $domain = null, + $path = null, + $secure = false, + $httpOnly = false, + \DateTime $expires = null + ) { + $cookie = new self('name', null, null, $domain, $path, $secure, $httpOnly, $expires); + $cookie->name = $name; + $cookie->value = $value; + $cookie->maxAge = $maxAge; + + return $cookie; + } + + /** + * Returns the name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Returns the value. + * + * @return string|null + */ + public function getValue() + { + return $this->value; + } + + /** + * Checks if there is a value. + * + * @return bool + */ + public function hasValue() + { + return isset($this->value); + } + + /** + * Sets the value. + * + * @param string|null $value + * + * @return Cookie + */ + public function withValue($value) + { + $this->validateValue($value); + + $new = clone $this; + $new->value = $value; + + return $new; + } + + /** + * Returns the max age. + * + * @return int|null + */ + public function getMaxAge() + { + return $this->maxAge; + } + + /** + * Checks if there is a max age. + * + * @return bool + */ + public function hasMaxAge() + { + return isset($this->maxAge); + } + + /** + * Sets the max age. + * + * @param int|null $maxAge + * + * @return Cookie + */ + public function withMaxAge($maxAge) + { + $this->validateMaxAge($maxAge); + + $new = clone $this; + $new->maxAge = $maxAge; + + return $new; + } + + /** + * Returns the expiration time. + * + * @return \DateTime|null + */ + public function getExpires() + { + return $this->expires; + } + + /** + * Checks if there is an expiration time. + * + * @return bool + */ + public function hasExpires() + { + return isset($this->expires); + } + + /** + * Sets the expires. + * + * @param \DateTime|null $expires + * + * @return Cookie + */ + public function withExpires(\DateTime $expires = null) + { + $new = clone $this; + $new->expires = $expires; + + return $new; + } + + /** + * Checks if the cookie is expired. + * + * @return bool + */ + public function isExpired() + { + return isset($this->expires) and $this->expires < new \DateTime(); + } + + /** + * Returns the domain. + * + * @return string|null + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Checks if there is a domain. + * + * @return bool + */ + public function hasDomain() + { + return isset($this->domain); + } + + /** + * Sets the domain. + * + * @param string|null $domain + * + * @return Cookie + */ + public function withDomain($domain) + { + $new = clone $this; + $new->domain = $this->normalizeDomain($domain); + + return $new; + } + + /** + * Checks whether this cookie is meant for this domain. + * + * @see http://tools.ietf.org/html/rfc6265#section-5.1.3 + * + * @param string $domain + * + * @return bool + */ + public function matchDomain($domain) + { + // Domain is not set or exact match + if (!$this->hasDomain() || 0 === strcasecmp($domain, $this->domain)) { + return true; + } + + // Domain is not an IP address + if (filter_var($domain, FILTER_VALIDATE_IP)) { + return false; + } + + return (bool) preg_match(sprintf('/\b%s$/i', preg_quote($this->domain)), $domain); + } + + /** + * Returns the path. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Sets the path. + * + * @param string|null $path + * + * @return Cookie + */ + public function withPath($path) + { + $new = clone $this; + $new->path = $this->normalizePath($path); + + return $new; + } + + /** + * Checks whether this cookie is meant for this path. + * + * @see http://tools.ietf.org/html/rfc6265#section-5.1.4 + * + * @param string $path + * + * @return bool + */ + public function matchPath($path) + { + return $this->path === $path || (0 === strpos($path, rtrim($this->path, '/').'/')); + } + + /** + * Checks whether this cookie may only be sent over HTTPS. + * + * @return bool + */ + public function isSecure() + { + return $this->secure; + } + + /** + * Sets whether this cookie should only be sent over HTTPS. + * + * @param bool $secure + * + * @return Cookie + */ + public function withSecure($secure) + { + $new = clone $this; + $new->secure = (bool) $secure; + + return $new; + } + + /** + * Check whether this cookie may not be accessed through Javascript. + * + * @return bool + */ + public function isHttpOnly() + { + return $this->httpOnly; + } + + /** + * Sets whether this cookie may not be accessed through Javascript. + * + * @param bool $httpOnly + * + * @return Cookie + */ + public function withHttpOnly($httpOnly) + { + $new = clone $this; + $new->httpOnly = (bool) $httpOnly; + + return $new; + } + + /** + * Checks if this cookie represents the same cookie as $cookie. + * + * This does not compare the values, only name, domain and path. + * + * @param Cookie $cookie + * + * @return bool + */ + public function match(self $cookie) + { + return $this->name === $cookie->name && $this->domain === $cookie->domain and $this->path === $cookie->path; + } + + /** + * Validates cookie attributes. + * + * @return bool + */ + public function isValid() + { + try { + $this->validateName($this->name); + $this->validateValue($this->value); + $this->validateMaxAge($this->maxAge); + } catch (\InvalidArgumentException $e) { + return false; + } + + return true; + } + + /** + * Validates the name attribute. + * + * @see http://tools.ietf.org/search/rfc2616#section-2.2 + * + * @param string $name + * + * @throws \InvalidArgumentException If the name is empty or contains invalid characters. + */ + private function validateName($name) + { + if (strlen($name) < 1) { + throw new \InvalidArgumentException('The name cannot be empty'); + } + + // Name attribute is a token as per spec in RFC 2616 + if (preg_match('/[\x00-\x20\x22\x28-\x29\x2C\x2F\x3A-\x40\x5B-\x5D\x7B\x7D\x7F]/', $name)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); + } + } + + /** + * Validates a value. + * + * @see http://tools.ietf.org/html/rfc6265#section-4.1.1 + * + * @param string|null $value + * + * @throws \InvalidArgumentException If the value contains invalid characters. + */ + private function validateValue($value) + { + if (isset($value)) { + if (preg_match('/[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/', $value)) { + throw new \InvalidArgumentException(sprintf('The cookie value "%s" contains invalid characters.', $value)); + } + } + } + + /** + * Validates a Max-Age attribute. + * + * @param int|null $maxAge + * + * @throws \InvalidArgumentException If the Max-Age is not an empty or integer value. + */ + private function validateMaxAge($maxAge) + { + if (isset($maxAge)) { + if (!is_int($maxAge)) { + throw new \InvalidArgumentException('Max-Age must be integer'); + } + } + } + + /** + * Remove the leading '.' and lowercase the domain as per spec in RFC 6265. + * + * @see http://tools.ietf.org/html/rfc6265#section-4.1.2.3 + * @see http://tools.ietf.org/html/rfc6265#section-5.1.3 + * @see http://tools.ietf.org/html/rfc6265#section-5.2.3 + * + * @param string|null $domain + * + * @return string + */ + private function normalizeDomain($domain) + { + if (isset($domain)) { + $domain = ltrim(strtolower($domain), '.'); + } + + return $domain; + } + + /** + * Processes path as per spec in RFC 6265. + * + * @see http://tools.ietf.org/html/rfc6265#section-5.1.4 + * @see http://tools.ietf.org/html/rfc6265#section-5.2.4 + * + * @param string|null $path + * + * @return string + */ + private function normalizePath($path) + { + $path = rtrim($path, '/'); + + if (empty($path) or '/' !== substr($path, 0, 1)) { + $path = '/'; + } + + return $path; + } +} diff --git a/vendor/php-http/message/src/CookieJar.php b/vendor/php-http/message/src/CookieJar.php new file mode 100644 index 00000000..ab267d33 --- /dev/null +++ b/vendor/php-http/message/src/CookieJar.php @@ -0,0 +1,220 @@ + + */ +final class CookieJar implements \Countable, \IteratorAggregate +{ + /** + * @var \SplObjectStorage + */ + protected $cookies; + + public function __construct() + { + $this->cookies = new \SplObjectStorage(); + } + + /** + * Checks if there is a cookie. + * + * @param Cookie $cookie + * + * @return bool + */ + public function hasCookie(Cookie $cookie) + { + return $this->cookies->contains($cookie); + } + + /** + * Adds a cookie. + * + * @param Cookie $cookie + */ + public function addCookie(Cookie $cookie) + { + if (!$this->hasCookie($cookie)) { + $cookies = $this->getMatchingCookies($cookie); + + foreach ($cookies as $matchingCookie) { + if ($cookie->getValue() !== $matchingCookie->getValue() || $cookie->getMaxAge() > $matchingCookie->getMaxAge()) { + $this->removeCookie($matchingCookie); + + continue; + } + } + + if ($cookie->hasValue()) { + $this->cookies->attach($cookie); + } + } + } + + /** + * Removes a cookie. + * + * @param Cookie $cookie + */ + public function removeCookie(Cookie $cookie) + { + $this->cookies->detach($cookie); + } + + /** + * Returns the cookies. + * + * @return Cookie[] + */ + public function getCookies() + { + $match = function ($matchCookie) { + return true; + }; + + return $this->findMatchingCookies($match); + } + + /** + * Returns all matching cookies. + * + * @param Cookie $cookie + * + * @return Cookie[] + */ + public function getMatchingCookies(Cookie $cookie) + { + $match = function ($matchCookie) use ($cookie) { + return $matchCookie->match($cookie); + }; + + return $this->findMatchingCookies($match); + } + + /** + * Finds matching cookies based on a callable. + * + * @param callable $match + * + * @return Cookie[] + */ + protected function findMatchingCookies(callable $match) + { + $cookies = []; + + foreach ($this->cookies as $cookie) { + if ($match($cookie)) { + $cookies[] = $cookie; + } + } + + return $cookies; + } + + /** + * Checks if there are cookies. + * + * @return bool + */ + public function hasCookies() + { + return $this->cookies->count() > 0; + } + + /** + * Sets the cookies and removes any previous one. + * + * @param Cookie[] $cookies + */ + public function setCookies(array $cookies) + { + $this->clear(); + $this->addCookies($cookies); + } + + /** + * Adds some cookies. + * + * @param Cookie[] $cookies + */ + public function addCookies(array $cookies) + { + foreach ($cookies as $cookie) { + $this->addCookie($cookie); + } + } + + /** + * Removes some cookies. + * + * @param Cookie[] $cookies + */ + public function removeCookies(array $cookies) + { + foreach ($cookies as $cookie) { + $this->removeCookie($cookie); + } + } + + /** + * Removes cookies which match the given parameters. + * + * Null means that parameter should not be matched + * + * @param string|null $name + * @param string|null $domain + * @param string|null $path + */ + public function removeMatchingCookies($name = null, $domain = null, $path = null) + { + $match = function ($cookie) use ($name, $domain, $path) { + $match = true; + + if (isset($name)) { + $match = $match && ($cookie->getName() === $name); + } + + if (isset($domain)) { + $match = $match && $cookie->matchDomain($domain); + } + + if (isset($path)) { + $match = $match && $cookie->matchPath($path); + } + + return $match; + }; + + $cookies = $this->findMatchingCookies($match); + + $this->removeCookies($cookies); + } + + /** + * Removes all cookies. + */ + public function clear() + { + $this->cookies = new \SplObjectStorage(); + } + + /** + * {@inheritdoc} + */ + public function count() + { + return $this->cookies->count(); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return clone $this->cookies; + } +} diff --git a/vendor/php-http/message/src/CookieUtil.php b/vendor/php-http/message/src/CookieUtil.php new file mode 100644 index 00000000..5c670d46 --- /dev/null +++ b/vendor/php-http/message/src/CookieUtil.php @@ -0,0 +1,53 @@ + + */ +trait MessageDecorator +{ + /** + * @var MessageInterface + */ + private $message; + + /** + * Returns the decorated message. + * + * Since the underlying Message is immutable as well + * exposing it is not an issue, because it's state cannot be altered + * + * @return MessageInterface + */ + public function getMessage() + { + return $this->message; + } + + /** + * {@inheritdoc} + */ + public function getProtocolVersion() + { + return $this->message->getProtocolVersion(); + } + + /** + * {@inheritdoc} + */ + public function withProtocolVersion($version) + { + $new = clone $this; + $new->message = $this->message->withProtocolVersion($version); + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getHeaders() + { + return $this->message->getHeaders(); + } + + /** + * {@inheritdoc} + */ + public function hasHeader($header) + { + return $this->message->hasHeader($header); + } + + /** + * {@inheritdoc} + */ + public function getHeader($header) + { + return $this->message->getHeader($header); + } + + /** + * {@inheritdoc} + */ + public function getHeaderLine($header) + { + return $this->message->getHeaderLine($header); + } + + /** + * {@inheritdoc} + */ + public function withHeader($header, $value) + { + $new = clone $this; + $new->message = $this->message->withHeader($header, $value); + + return $new; + } + + /** + * {@inheritdoc} + */ + public function withAddedHeader($header, $value) + { + $new = clone $this; + $new->message = $this->message->withAddedHeader($header, $value); + + return $new; + } + + /** + * {@inheritdoc} + */ + public function withoutHeader($header) + { + $new = clone $this; + $new->message = $this->message->withoutHeader($header); + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getBody() + { + return $this->message->getBody(); + } + + /** + * {@inheritdoc} + */ + public function withBody(StreamInterface $body) + { + $new = clone $this; + $new->message = $this->message->withBody($body); + + return $new; + } +} diff --git a/vendor/php-http/message/src/Decorator/RequestDecorator.php b/vendor/php-http/message/src/Decorator/RequestDecorator.php new file mode 100644 index 00000000..7c50e588 --- /dev/null +++ b/vendor/php-http/message/src/Decorator/RequestDecorator.php @@ -0,0 +1,88 @@ + + */ +trait RequestDecorator +{ + use MessageDecorator { + getMessage as getRequest; + } + + /** + * Exchanges the underlying request with another. + * + * @param RequestInterface $request + * + * @return self + */ + public function withRequest(RequestInterface $request) + { + $new = clone $this; + $new->message = $request; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getRequestTarget() + { + return $this->message->getRequestTarget(); + } + + /** + * {@inheritdoc} + */ + public function withRequestTarget($requestTarget) + { + $new = clone $this; + $new->message = $this->message->withRequestTarget($requestTarget); + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getMethod() + { + return $this->message->getMethod(); + } + + /** + * {@inheritdoc} + */ + public function withMethod($method) + { + $new = clone $this; + $new->message = $this->message->withMethod($method); + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getUri() + { + return $this->message->getUri(); + } + + /** + * {@inheritdoc} + */ + public function withUri(UriInterface $uri, $preserveHost = false) + { + $new = clone $this; + $new->message = $this->message->withUri($uri, $preserveHost); + + return $new; + } +} diff --git a/vendor/php-http/message/src/Decorator/ResponseDecorator.php b/vendor/php-http/message/src/Decorator/ResponseDecorator.php new file mode 100644 index 00000000..82d9ae08 --- /dev/null +++ b/vendor/php-http/message/src/Decorator/ResponseDecorator.php @@ -0,0 +1,57 @@ + + */ +trait ResponseDecorator +{ + use MessageDecorator { + getMessage as getResponse; + } + + /** + * Exchanges the underlying response with another. + * + * @param ResponseInterface $response + * + * @return self + */ + public function withResponse(ResponseInterface $response) + { + $new = clone $this; + $new->message = $response; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getStatusCode() + { + return $this->message->getStatusCode(); + } + + /** + * {@inheritdoc} + */ + public function withStatus($code, $reasonPhrase = '') + { + $new = clone $this; + $new->message = $this->message->withStatus($code, $reasonPhrase); + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getReasonPhrase() + { + return $this->message->getReasonPhrase(); + } +} diff --git a/vendor/php-http/message/src/Decorator/StreamDecorator.php b/vendor/php-http/message/src/Decorator/StreamDecorator.php new file mode 100644 index 00000000..f405c7af --- /dev/null +++ b/vendor/php-http/message/src/Decorator/StreamDecorator.php @@ -0,0 +1,138 @@ + + */ +trait StreamDecorator +{ + /** + * @var StreamInterface + */ + protected $stream; + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->stream->__toString(); + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->stream->close(); + } + + /** + * {@inheritdoc} + */ + public function detach() + { + return $this->stream->detach(); + } + + /** + * {@inheritdoc} + */ + public function getSize() + { + return $this->stream->getSize(); + } + + /** + * {@inheritdoc} + */ + public function tell() + { + return $this->stream->tell(); + } + + /** + * {@inheritdoc} + */ + public function eof() + { + return $this->stream->eof(); + } + + /** + * {@inheritdoc} + */ + public function isSeekable() + { + return $this->stream->isSeekable(); + } + + /** + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + $this->stream->seek($offset, $whence); + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->stream->rewind(); + } + + /** + * {@inheritdoc} + */ + public function isWritable() + { + return $this->stream->isWritable(); + } + + /** + * {@inheritdoc} + */ + public function write($string) + { + return $this->stream->write($string); + } + + /** + * {@inheritdoc} + */ + public function isReadable() + { + return $this->stream->isReadable(); + } + + /** + * {@inheritdoc} + */ + public function read($length) + { + return $this->stream->read($length); + } + + /** + * {@inheritdoc} + */ + public function getContents() + { + return $this->stream->getContents(); + } + + /** + * {@inheritdoc} + */ + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } +} diff --git a/vendor/php-http/message/src/Encoding/ChunkStream.php b/vendor/php-http/message/src/Encoding/ChunkStream.php new file mode 100644 index 00000000..74c2fbd0 --- /dev/null +++ b/vendor/php-http/message/src/Encoding/ChunkStream.php @@ -0,0 +1,39 @@ + + */ +class ChunkStream extends FilteredStream +{ + /** + * {@inheritdoc} + */ + protected function readFilter() + { + return 'chunk'; + } + + /** + * {@inheritdoc} + */ + protected function writeFilter() + { + return 'dechunk'; + } + + /** + * {@inheritdoc} + */ + protected function fill() + { + parent::fill(); + + if ($this->stream->eof()) { + $this->buffer .= "0\r\n\r\n"; + } + } +} diff --git a/vendor/php-http/message/src/Encoding/CompressStream.php b/vendor/php-http/message/src/Encoding/CompressStream.php new file mode 100644 index 00000000..eeec6e03 --- /dev/null +++ b/vendor/php-http/message/src/Encoding/CompressStream.php @@ -0,0 +1,46 @@ + + */ +class CompressStream extends FilteredStream +{ + /** + * @param StreamInterface $stream + * @param int $level + */ + public function __construct(StreamInterface $stream, $level = -1) + { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + + parent::__construct($stream, ['window' => 15, 'level' => $level]); + + // @deprecated will be removed in 2.0 + $this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 15]); + } + + /** + * {@inheritdoc} + */ + protected function readFilter() + { + return 'zlib.deflate'; + } + + /** + * {@inheritdoc} + */ + protected function writeFilter() + { + return 'zlib.inflate'; + } +} diff --git a/vendor/php-http/message/src/Encoding/DechunkStream.php b/vendor/php-http/message/src/Encoding/DechunkStream.php new file mode 100644 index 00000000..4cade835 --- /dev/null +++ b/vendor/php-http/message/src/Encoding/DechunkStream.php @@ -0,0 +1,29 @@ + + */ +class DechunkStream extends FilteredStream +{ + /** + * {@inheritdoc} + */ + protected function readFilter() + { + return 'dechunk'; + } + + /** + * {@inheritdoc} + */ + protected function writeFilter() + { + return 'chunk'; + } +} diff --git a/vendor/php-http/message/src/Encoding/DecompressStream.php b/vendor/php-http/message/src/Encoding/DecompressStream.php new file mode 100644 index 00000000..9a950ea9 --- /dev/null +++ b/vendor/php-http/message/src/Encoding/DecompressStream.php @@ -0,0 +1,46 @@ + + */ +class DecompressStream extends FilteredStream +{ + /** + * @param StreamInterface $stream + * @param int $level + */ + public function __construct(StreamInterface $stream, $level = -1) + { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + + parent::__construct($stream, ['window' => 15]); + + // @deprecated will be removed in 2.0 + $this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 15, 'level' => $level]); + } + + /** + * {@inheritdoc} + */ + protected function readFilter() + { + return 'zlib.inflate'; + } + + /** + * {@inheritdoc} + */ + protected function writeFilter() + { + return 'zlib.deflate'; + } +} diff --git a/vendor/php-http/message/src/Encoding/DeflateStream.php b/vendor/php-http/message/src/Encoding/DeflateStream.php new file mode 100644 index 00000000..f7ce8e2d --- /dev/null +++ b/vendor/php-http/message/src/Encoding/DeflateStream.php @@ -0,0 +1,42 @@ + + */ +class DeflateStream extends FilteredStream +{ + /** + * @param StreamInterface $stream + * @param int $level + */ + public function __construct(StreamInterface $stream, $level = -1) + { + parent::__construct($stream, ['window' => -15, 'level' => $level]); + + // @deprecated will be removed in 2.0 + $this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => -15]); + } + + /** + * {@inheritdoc} + */ + protected function readFilter() + { + return 'zlib.deflate'; + } + + /** + * {@inheritdoc} + */ + protected function writeFilter() + { + return 'zlib.inflate'; + } +} diff --git a/vendor/php-http/message/src/Encoding/Filter/Chunk.php b/vendor/php-http/message/src/Encoding/Filter/Chunk.php new file mode 100644 index 00000000..0f8f53b3 --- /dev/null +++ b/vendor/php-http/message/src/Encoding/Filter/Chunk.php @@ -0,0 +1,30 @@ + + */ +class Chunk extends \php_user_filter +{ + /** + * {@inheritdoc} + */ + public function filter($in, $out, &$consumed, $closing) + { + while ($bucket = stream_bucket_make_writeable($in)) { + $lenbucket = stream_bucket_new($this->stream, dechex($bucket->datalen)."\r\n"); + stream_bucket_append($out, $lenbucket); + + $consumed += $bucket->datalen; + stream_bucket_append($out, $bucket); + + $lenbucket = stream_bucket_new($this->stream, "\r\n"); + stream_bucket_append($out, $lenbucket); + } + + return PSFS_PASS_ON; + } +} diff --git a/vendor/php-http/message/src/Encoding/FilteredStream.php b/vendor/php-http/message/src/Encoding/FilteredStream.php new file mode 100644 index 00000000..7e5713e8 --- /dev/null +++ b/vendor/php-http/message/src/Encoding/FilteredStream.php @@ -0,0 +1,236 @@ + + */ +abstract class FilteredStream implements StreamInterface +{ + const BUFFER_SIZE = 8192; + + use StreamDecorator { + rewind as private doRewind; + seek as private doSeek; + } + + /** + * @var callable + */ + protected $readFilterCallback; + + /** + * @var resource + * + * @deprecated since version 1.5, will be removed in 2.0 + */ + protected $readFilter; + + /** + * @var callable + * + * @deprecated since version 1.5, will be removed in 2.0 + */ + protected $writeFilterCallback; + + /** + * @var resource + * + * @deprecated since version 1.5, will be removed in 2.0 + */ + protected $writeFilter; + + /** + * Internal buffer. + * + * @var string + */ + protected $buffer = ''; + + /** + * @param StreamInterface $stream + * @param mixed|null $readFilterOptions + * @param mixed|null $writeFilterOptions deprecated since 1.5, will be removed in 2.0 + */ + public function __construct(StreamInterface $stream, $readFilterOptions = null, $writeFilterOptions = null) + { + if (null !== $readFilterOptions) { + $this->readFilterCallback = Filter\fun($this->readFilter(), $readFilterOptions); + } else { + $this->readFilterCallback = Filter\fun($this->readFilter()); + } + + if (null !== $writeFilterOptions) { + $this->writeFilterCallback = Filter\fun($this->writeFilter(), $writeFilterOptions); + + @trigger_error('The $writeFilterOptions argument is deprecated since version 1.5 and will be removed in 2.0.', E_USER_DEPRECATED); + } else { + $this->writeFilterCallback = Filter\fun($this->writeFilter()); + } + + $this->stream = $stream; + } + + /** + * {@inheritdoc} + */ + public function read($length) + { + if (strlen($this->buffer) >= $length) { + $read = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + + return $read; + } + + if ($this->stream->eof()) { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + $read = $this->buffer; + $this->buffer = ''; + $this->fill(); + + return $read.$this->read($length - strlen($read)); + } + + /** + * {@inheritdoc} + */ + public function eof() + { + return $this->stream->eof() && '' === $this->buffer; + } + + /** + * Buffer is filled by reading underlying stream. + * + * Callback is reading once more even if the stream is ended. + * This allow to get last data in the PHP buffer otherwise this + * bug is present : https://bugs.php.net/bug.php?id=48725 + */ + protected function fill() + { + $readFilterCallback = $this->readFilterCallback; + $this->buffer .= $readFilterCallback($this->stream->read(self::BUFFER_SIZE)); + + if ($this->stream->eof()) { + $this->buffer .= $readFilterCallback(); + } + } + + /** + * {@inheritdoc} + */ + public function getContents() + { + $buffer = ''; + + while (!$this->eof()) { + $buf = $this->read(self::BUFFER_SIZE); + // Using a loose equality here to match on '' and false. + if (null == $buf) { + break; + } + + $buffer .= $buf; + } + + return $buffer; + } + + /** + * Always returns null because we can't tell the size of a stream when we filter. + */ + public function getSize() + { + return null; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->getContents(); + } + + /** + * Filtered streams are not seekable. + * + * We would need to buffer and process everything to allow seeking. + */ + public function isSeekable() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + @trigger_error('Filtered streams are not seekable. This method will start raising an exception in the next major version', E_USER_DEPRECATED); + $this->doRewind(); + } + + /** + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + @trigger_error('Filtered streams are not seekable. This method will start raising an exception in the next major version', E_USER_DEPRECATED); + $this->doSeek($offset, $whence); + } + + /** + * Returns the read filter name. + * + * @return string + * + * @deprecated since version 1.5, will be removed in 2.0 + */ + public function getReadFilter() + { + @trigger_error('The '.__CLASS__.'::'.__METHOD__.' method is deprecated since version 1.5 and will be removed in 2.0.', E_USER_DEPRECATED); + + return $this->readFilter(); + } + + /** + * Returns the write filter name. + * + * @return string + */ + abstract protected function readFilter(); + + /** + * Returns the write filter name. + * + * @return string + * + * @deprecated since version 1.5, will be removed in 2.0 + */ + public function getWriteFilter() + { + @trigger_error('The '.__CLASS__.'::'.__METHOD__.' method is deprecated since version 1.5 and will be removed in 2.0.', E_USER_DEPRECATED); + + return $this->writeFilter(); + } + + /** + * Returns the write filter name. + * + * @return string + */ + abstract protected function writeFilter(); +} diff --git a/vendor/php-http/message/src/Encoding/GzipDecodeStream.php b/vendor/php-http/message/src/Encoding/GzipDecodeStream.php new file mode 100644 index 00000000..19a2b8f0 --- /dev/null +++ b/vendor/php-http/message/src/Encoding/GzipDecodeStream.php @@ -0,0 +1,46 @@ + + */ +class GzipDecodeStream extends FilteredStream +{ + /** + * @param StreamInterface $stream + * @param int $level + */ + public function __construct(StreamInterface $stream, $level = -1) + { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + + parent::__construct($stream, ['window' => 31]); + + // @deprecated will be removed in 2.0 + $this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 31, 'level' => $level]); + } + + /** + * {@inheritdoc} + */ + protected function readFilter() + { + return 'zlib.inflate'; + } + + /** + * {@inheritdoc} + */ + protected function writeFilter() + { + return 'zlib.deflate'; + } +} diff --git a/vendor/php-http/message/src/Encoding/GzipEncodeStream.php b/vendor/php-http/message/src/Encoding/GzipEncodeStream.php new file mode 100644 index 00000000..8555d4ae --- /dev/null +++ b/vendor/php-http/message/src/Encoding/GzipEncodeStream.php @@ -0,0 +1,46 @@ + + */ +class GzipEncodeStream extends FilteredStream +{ + /** + * @param StreamInterface $stream + * @param int $level + */ + public function __construct(StreamInterface $stream, $level = -1) + { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + + parent::__construct($stream, ['window' => 31, 'level' => $level]); + + // @deprecated will be removed in 2.0 + $this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => 31]); + } + + /** + * {@inheritdoc} + */ + protected function readFilter() + { + return 'zlib.deflate'; + } + + /** + * {@inheritdoc} + */ + protected function writeFilter() + { + return 'zlib.inflate'; + } +} diff --git a/vendor/php-http/message/src/Encoding/InflateStream.php b/vendor/php-http/message/src/Encoding/InflateStream.php new file mode 100644 index 00000000..863b73de --- /dev/null +++ b/vendor/php-http/message/src/Encoding/InflateStream.php @@ -0,0 +1,46 @@ + + */ +class InflateStream extends FilteredStream +{ + /** + * @param StreamInterface $stream + * @param int $level + */ + public function __construct(StreamInterface $stream, $level = -1) + { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + + parent::__construct($stream, ['window' => -15]); + + // @deprecated will be removed in 2.0 + $this->writeFilterCallback = Filter\fun($this->writeFilter(), ['window' => -15, 'level' => $level]); + } + + /** + * {@inheritdoc} + */ + protected function readFilter() + { + return 'zlib.inflate'; + } + + /** + * {@inheritdoc} + */ + protected function writeFilter() + { + return 'zlib.deflate'; + } +} diff --git a/vendor/php-http/message/src/Exception.php b/vendor/php-http/message/src/Exception.php new file mode 100644 index 00000000..80d4cd9d --- /dev/null +++ b/vendor/php-http/message/src/Exception.php @@ -0,0 +1,10 @@ + + */ +interface Formatter +{ + /** + * Formats a request. + * + * @param RequestInterface $request + * + * @return string + */ + public function formatRequest(RequestInterface $request); + + /** + * Formats a response. + * + * @param ResponseInterface $response + * + * @return string + */ + public function formatResponse(ResponseInterface $response); +} diff --git a/vendor/php-http/message/src/Formatter/CurlCommandFormatter.php b/vendor/php-http/message/src/Formatter/CurlCommandFormatter.php new file mode 100644 index 00000000..80602508 --- /dev/null +++ b/vendor/php-http/message/src/Formatter/CurlCommandFormatter.php @@ -0,0 +1,91 @@ + + */ +class CurlCommandFormatter implements Formatter +{ + /** + * {@inheritdoc} + */ + public function formatRequest(RequestInterface $request) + { + $command = sprintf('curl %s', escapeshellarg((string) $request->getUri()->withFragment(''))); + if ('1.0' === $request->getProtocolVersion()) { + $command .= ' --http1.0'; + } elseif ('2.0' === $request->getProtocolVersion()) { + $command .= ' --http2'; + } + + $method = strtoupper($request->getMethod()); + if ('HEAD' === $method) { + $command .= ' --head'; + } elseif ('GET' !== $method) { + $command .= ' --request '.$method; + } + + $command .= $this->getHeadersAsCommandOptions($request); + + $body = $request->getBody(); + if ($body->getSize() > 0) { + if ($body->isSeekable()) { + $data = $body->__toString(); + $body->rewind(); + if (preg_match('/[\x00-\x1F\x7F]/', $data)) { + $data = '[binary stream omitted]'; + } + } else { + $data = '[non-seekable stream omitted]'; + } + $escapedData = @escapeshellarg($data); + if (empty($escapedData)) { + $escapedData = 'We couldn\'t not escape the data properly'; + } + + $command .= sprintf(' --data %s', $escapedData); + } + + return $command; + } + + /** + * {@inheritdoc} + */ + public function formatResponse(ResponseInterface $response) + { + return ''; + } + + /** + * @param RequestInterface $request + * + * @return string + */ + private function getHeadersAsCommandOptions(RequestInterface $request) + { + $command = ''; + foreach ($request->getHeaders() as $name => $values) { + if ('host' === strtolower($name) && $values[0] === $request->getUri()->getHost()) { + continue; + } + + if ('user-agent' === strtolower($name)) { + $command .= sprintf(' -A %s', escapeshellarg($values[0])); + + continue; + } + + $command .= sprintf(' -H %s', escapeshellarg($name.': '.$request->getHeaderLine($name))); + } + + return $command; + } +} diff --git a/vendor/php-http/message/src/Formatter/FullHttpMessageFormatter.php b/vendor/php-http/message/src/Formatter/FullHttpMessageFormatter.php new file mode 100644 index 00000000..1918c597 --- /dev/null +++ b/vendor/php-http/message/src/Formatter/FullHttpMessageFormatter.php @@ -0,0 +1,91 @@ + + */ +class FullHttpMessageFormatter implements Formatter +{ + /** + * The maximum length of the body. + * + * @var int + */ + private $maxBodyLength; + + /** + * @param int $maxBodyLength + */ + public function __construct($maxBodyLength = 1000) + { + $this->maxBodyLength = $maxBodyLength; + } + + /** + * {@inheritdoc} + */ + public function formatRequest(RequestInterface $request) + { + $message = sprintf( + "%s %s HTTP/%s\n", + $request->getMethod(), + $request->getRequestTarget(), + $request->getProtocolVersion() + ); + + foreach ($request->getHeaders() as $name => $values) { + $message .= $name.': '.implode(', ', $values)."\n"; + } + + return $this->addBody($request, $message); + } + + /** + * {@inheritdoc} + */ + public function formatResponse(ResponseInterface $response) + { + $message = sprintf( + "HTTP/%s %s %s\n", + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + + foreach ($response->getHeaders() as $name => $values) { + $message .= $name.': '.implode(', ', $values)."\n"; + } + + return $this->addBody($response, $message); + } + + /** + * Add the message body if the stream is seekable. + * + * @param MessageInterface $request + * @param string $message + * + * @return string + */ + private function addBody(MessageInterface $request, $message) + { + $stream = $request->getBody(); + if (!$stream->isSeekable() || 0 === $this->maxBodyLength) { + // Do not read the stream + $message .= "\n"; + } else { + $message .= "\n".mb_substr($stream->__toString(), 0, $this->maxBodyLength); + $stream->rewind(); + } + + return $message; + } +} diff --git a/vendor/php-http/message/src/Formatter/SimpleFormatter.php b/vendor/php-http/message/src/Formatter/SimpleFormatter.php new file mode 100644 index 00000000..b1fcabdb --- /dev/null +++ b/vendor/php-http/message/src/Formatter/SimpleFormatter.php @@ -0,0 +1,42 @@ + + * @author Márk Sági-Kazár + */ +class SimpleFormatter implements Formatter +{ + /** + * {@inheritdoc} + */ + public function formatRequest(RequestInterface $request) + { + return sprintf( + '%s %s %s', + $request->getMethod(), + $request->getUri()->__toString(), + $request->getProtocolVersion() + ); + } + + /** + * {@inheritdoc} + */ + public function formatResponse(ResponseInterface $response) + { + return sprintf( + '%s %s %s', + $response->getStatusCode(), + $response->getReasonPhrase(), + $response->getProtocolVersion() + ); + } +} diff --git a/vendor/php-http/message/src/MessageFactory/DiactorosMessageFactory.php b/vendor/php-http/message/src/MessageFactory/DiactorosMessageFactory.php new file mode 100644 index 00000000..53f08ae0 --- /dev/null +++ b/vendor/php-http/message/src/MessageFactory/DiactorosMessageFactory.php @@ -0,0 +1,61 @@ + + */ +final class DiactorosMessageFactory implements MessageFactory +{ + /** + * @var DiactorosStreamFactory + */ + private $streamFactory; + + public function __construct() + { + $this->streamFactory = new DiactorosStreamFactory(); + } + + /** + * {@inheritdoc} + */ + public function createRequest( + $method, + $uri, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ) { + return (new Request( + $uri, + $method, + $this->streamFactory->createStream($body), + $headers + ))->withProtocolVersion($protocolVersion); + } + + /** + * {@inheritdoc} + */ + public function createResponse( + $statusCode = 200, + $reasonPhrase = null, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ) { + return (new Response( + $this->streamFactory->createStream($body), + $statusCode, + $headers + ))->withProtocolVersion($protocolVersion); + } +} diff --git a/vendor/php-http/message/src/MessageFactory/GuzzleMessageFactory.php b/vendor/php-http/message/src/MessageFactory/GuzzleMessageFactory.php new file mode 100644 index 00000000..59eb6551 --- /dev/null +++ b/vendor/php-http/message/src/MessageFactory/GuzzleMessageFactory.php @@ -0,0 +1,53 @@ + + */ +final class GuzzleMessageFactory implements MessageFactory +{ + /** + * {@inheritdoc} + */ + public function createRequest( + $method, + $uri, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ) { + return new Request( + $method, + $uri, + $headers, + $body, + $protocolVersion + ); + } + + /** + * {@inheritdoc} + */ + public function createResponse( + $statusCode = 200, + $reasonPhrase = null, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ) { + return new Response( + $statusCode, + $headers, + $body, + $protocolVersion, + $reasonPhrase + ); + } +} diff --git a/vendor/php-http/message/src/MessageFactory/SlimMessageFactory.php b/vendor/php-http/message/src/MessageFactory/SlimMessageFactory.php new file mode 100644 index 00000000..cdad2ec2 --- /dev/null +++ b/vendor/php-http/message/src/MessageFactory/SlimMessageFactory.php @@ -0,0 +1,72 @@ + + */ +final class SlimMessageFactory implements MessageFactory +{ + /** + * @var SlimStreamFactory + */ + private $streamFactory; + + /** + * @var SlimUriFactory + */ + private $uriFactory; + + public function __construct() + { + $this->streamFactory = new SlimStreamFactory(); + $this->uriFactory = new SlimUriFactory(); + } + + /** + * {@inheritdoc} + */ + public function createRequest( + $method, + $uri, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ) { + return (new Request( + $method, + $this->uriFactory->createUri($uri), + new Headers($headers), + [], + [], + $this->streamFactory->createStream($body), + [] + ))->withProtocolVersion($protocolVersion); + } + + /** + * {@inheritdoc} + */ + public function createResponse( + $statusCode = 200, + $reasonPhrase = null, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ) { + return (new Response( + $statusCode, + new Headers($headers), + $this->streamFactory->createStream($body) + ))->withProtocolVersion($protocolVersion); + } +} diff --git a/vendor/php-http/message/src/RequestMatcher.php b/vendor/php-http/message/src/RequestMatcher.php new file mode 100644 index 00000000..94fe5324 --- /dev/null +++ b/vendor/php-http/message/src/RequestMatcher.php @@ -0,0 +1,26 @@ + + */ +interface RequestMatcher +{ + /** + * Decides whether the rule(s) implemented by the strategy matches the supplied request. + * + * @param RequestInterface $request The PSR7 request to check for a match + * + * @return bool true if the request matches, false otherwise + */ + public function matches(RequestInterface $request); +} diff --git a/vendor/php-http/message/src/RequestMatcher/CallbackRequestMatcher.php b/vendor/php-http/message/src/RequestMatcher/CallbackRequestMatcher.php new file mode 100644 index 00000000..4d45e32e --- /dev/null +++ b/vendor/php-http/message/src/RequestMatcher/CallbackRequestMatcher.php @@ -0,0 +1,35 @@ + + */ +final class CallbackRequestMatcher implements RequestMatcher +{ + /** + * @var callable + */ + private $callback; + + /** + * @param callable $callback + */ + public function __construct(callable $callback) + { + $this->callback = $callback; + } + + /** + * {@inheritdoc} + */ + public function matches(RequestInterface $request) + { + return (bool) call_user_func($this->callback, $request); + } +} diff --git a/vendor/php-http/message/src/RequestMatcher/RegexRequestMatcher.php b/vendor/php-http/message/src/RequestMatcher/RegexRequestMatcher.php new file mode 100644 index 00000000..91f3729e --- /dev/null +++ b/vendor/php-http/message/src/RequestMatcher/RegexRequestMatcher.php @@ -0,0 +1,41 @@ + + * + * @deprecated since version 1.2 and will be removed in 2.0. Use {@link RequestMatcher} instead. + */ +final class RegexRequestMatcher implements RequestMatcher +{ + /** + * Matching regex. + * + * @var string + */ + private $regex; + + /** + * @param string $regex + */ + public function __construct($regex) + { + $this->regex = $regex; + } + + /** + * {@inheritdoc} + */ + public function matches(RequestInterface $request) + { + return (bool) preg_match($this->regex, (string) $request->getUri()); + } +} diff --git a/vendor/php-http/message/src/RequestMatcher/RequestMatcher.php b/vendor/php-http/message/src/RequestMatcher/RequestMatcher.php new file mode 100644 index 00000000..e2aa0212 --- /dev/null +++ b/vendor/php-http/message/src/RequestMatcher/RequestMatcher.php @@ -0,0 +1,78 @@ + + * @author Joel Wurtz + */ +final class RequestMatcher implements RequestMatcherInterface +{ + /** + * @var string + */ + private $path; + + /** + * @var string + */ + private $host; + + /** + * @var array + */ + private $methods = []; + + /** + * @var string[] + */ + private $schemes = []; + + /** + * The regular expressions used for path or host must be specified without delimiter. + * You do not need to escape the forward slash / to match it. + * + * @param string|null $path Regular expression for the path + * @param string|null $host Regular expression for the hostname + * @param string|string[]|null $methods Method or list of methods to match + * @param string|string[]|null $schemes Scheme or list of schemes to match (e.g. http or https) + */ + public function __construct($path = null, $host = null, $methods = [], $schemes = []) + { + $this->path = $path; + $this->host = $host; + $this->methods = array_map('strtoupper', (array) $methods); + $this->schemes = array_map('strtolower', (array) $schemes); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function matches(RequestInterface $request) + { + if ($this->schemes && !in_array($request->getUri()->getScheme(), $this->schemes)) { + return false; + } + + if ($this->methods && !in_array($request->getMethod(), $this->methods)) { + return false; + } + + if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getUri()->getPath()))) { + return false; + } + + if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getUri()->getHost())) { + return false; + } + + return true; + } +} diff --git a/vendor/php-http/message/src/Stream/BufferedStream.php b/vendor/php-http/message/src/Stream/BufferedStream.php new file mode 100644 index 00000000..1eac9747 --- /dev/null +++ b/vendor/php-http/message/src/Stream/BufferedStream.php @@ -0,0 +1,270 @@ +stream = $stream; + $this->size = $stream->getSize(); + + if ($useFileBuffer) { + $this->resource = fopen('php://temp/maxmemory:'.$memoryBuffer, 'rw+'); + } else { + $this->resource = fopen('php://memory', 'rw+'); + } + + if (false === $this->resource) { + throw new \RuntimeException('Cannot create a resource over temp or memory implementation'); + } + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + try { + $this->rewind(); + + return $this->getContents(); + } catch (\Throwable $throwable) { + return ''; + } catch (\Exception $exception) { // Layer to be BC with PHP 5, remove this when we only support PHP 7+ + return ''; + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + if (null === $this->resource) { + throw new \RuntimeException('Cannot close on a detached stream'); + } + + $this->stream->close(); + fclose($this->resource); + } + + /** + * {@inheritdoc} + */ + public function detach() + { + if (null === $this->resource) { + return; + } + + // Force reading the remaining data of the stream + $this->getContents(); + + $resource = $this->resource; + $this->stream->close(); + $this->stream = null; + $this->resource = null; + + return $resource; + } + + /** + * {@inheritdoc} + */ + public function getSize() + { + if (null === $this->resource) { + return; + } + + if (null === $this->size && $this->stream->eof()) { + return $this->written; + } + + return $this->size; + } + + /** + * {@inheritdoc} + */ + public function tell() + { + if (null === $this->resource) { + throw new \RuntimeException('Cannot tell on a detached stream'); + } + + return ftell($this->resource); + } + + /** + * {@inheritdoc} + */ + public function eof() + { + if (null === $this->resource) { + throw new \RuntimeException('Cannot call eof on a detached stream'); + } + + // We are at the end only when both our resource and underlying stream are at eof + return $this->stream->eof() && (ftell($this->resource) === $this->written); + } + + /** + * {@inheritdoc} + */ + public function isSeekable() + { + return null !== $this->resource; + } + + /** + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if (null === $this->resource) { + throw new \RuntimeException('Cannot seek on a detached stream'); + } + + fseek($this->resource, $offset, $whence); + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + if (null === $this->resource) { + throw new \RuntimeException('Cannot rewind on a detached stream'); + } + + rewind($this->resource); + } + + /** + * {@inheritdoc} + */ + public function isWritable() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function write($string) + { + throw new \RuntimeException('Cannot write on this stream'); + } + + /** + * {@inheritdoc} + */ + public function isReadable() + { + return null !== $this->resource; + } + + /** + * {@inheritdoc} + */ + public function read($length) + { + if (null === $this->resource) { + throw new \RuntimeException('Cannot read on a detached stream'); + } + + $read = ''; + + // First read from the resource + if (ftell($this->resource) !== $this->written) { + $read = fread($this->resource, $length); + } + + $bytesRead = strlen($read); + + if ($bytesRead < $length) { + $streamRead = $this->stream->read($length - $bytesRead); + + // Write on the underlying stream what we read + $this->written += fwrite($this->resource, $streamRead); + $read .= $streamRead; + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function getContents() + { + if (null === $this->resource) { + throw new \RuntimeException('Cannot read on a detached stream'); + } + + $read = ''; + + while (!$this->eof()) { + $read .= $this->read(8192); + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function getMetadata($key = null) + { + if (null === $this->resource) { + if (null === $key) { + return []; + } + + return; + } + + $metadata = stream_get_meta_data($this->resource); + + if (null === $key) { + return $metadata; + } + + if (!array_key_exists($key, $metadata)) { + return; + } + + return $metadata[$key]; + } +} diff --git a/vendor/php-http/message/src/StreamFactory/DiactorosStreamFactory.php b/vendor/php-http/message/src/StreamFactory/DiactorosStreamFactory.php new file mode 100644 index 00000000..a75ec988 --- /dev/null +++ b/vendor/php-http/message/src/StreamFactory/DiactorosStreamFactory.php @@ -0,0 +1,36 @@ + + */ +final class DiactorosStreamFactory implements StreamFactory +{ + /** + * {@inheritdoc} + */ + public function createStream($body = null) + { + if ($body instanceof StreamInterface) { + return $body; + } + + if (is_resource($body)) { + return new Stream($body); + } + + $stream = new Stream('php://memory', 'rw'); + if (null !== $body && '' !== $body) { + $stream->write((string) $body); + } + + return $stream; + } +} diff --git a/vendor/php-http/message/src/StreamFactory/GuzzleStreamFactory.php b/vendor/php-http/message/src/StreamFactory/GuzzleStreamFactory.php new file mode 100644 index 00000000..10f4d3f9 --- /dev/null +++ b/vendor/php-http/message/src/StreamFactory/GuzzleStreamFactory.php @@ -0,0 +1,21 @@ + + */ +final class GuzzleStreamFactory implements StreamFactory +{ + /** + * {@inheritdoc} + */ + public function createStream($body = null) + { + return \GuzzleHttp\Psr7\stream_for($body); + } +} diff --git a/vendor/php-http/message/src/StreamFactory/SlimStreamFactory.php b/vendor/php-http/message/src/StreamFactory/SlimStreamFactory.php new file mode 100644 index 00000000..efcadc43 --- /dev/null +++ b/vendor/php-http/message/src/StreamFactory/SlimStreamFactory.php @@ -0,0 +1,37 @@ + + */ +final class SlimStreamFactory implements StreamFactory +{ + /** + * {@inheritdoc} + */ + public function createStream($body = null) + { + if ($body instanceof StreamInterface) { + return $body; + } + + if (is_resource($body)) { + return new Stream($body); + } + + $resource = fopen('php://memory', 'r+'); + $stream = new Stream($resource); + if (null !== $body && '' !== $body) { + $stream->write((string) $body); + } + + return $stream; + } +} diff --git a/vendor/php-http/message/src/UriFactory/DiactorosUriFactory.php b/vendor/php-http/message/src/UriFactory/DiactorosUriFactory.php new file mode 100644 index 00000000..268c361d --- /dev/null +++ b/vendor/php-http/message/src/UriFactory/DiactorosUriFactory.php @@ -0,0 +1,29 @@ + + */ +final class DiactorosUriFactory implements UriFactory +{ + /** + * {@inheritdoc} + */ + public function createUri($uri) + { + if ($uri instanceof UriInterface) { + return $uri; + } elseif (is_string($uri)) { + return new Uri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); + } +} diff --git a/vendor/php-http/message/src/UriFactory/GuzzleUriFactory.php b/vendor/php-http/message/src/UriFactory/GuzzleUriFactory.php new file mode 100644 index 00000000..4c1c286c --- /dev/null +++ b/vendor/php-http/message/src/UriFactory/GuzzleUriFactory.php @@ -0,0 +1,22 @@ + + */ +final class GuzzleUriFactory implements UriFactory +{ + /** + * {@inheritdoc} + */ + public function createUri($uri) + { + return Psr7\uri_for($uri); + } +} diff --git a/vendor/php-http/message/src/UriFactory/SlimUriFactory.php b/vendor/php-http/message/src/UriFactory/SlimUriFactory.php new file mode 100644 index 00000000..c013d542 --- /dev/null +++ b/vendor/php-http/message/src/UriFactory/SlimUriFactory.php @@ -0,0 +1,31 @@ + + */ +final class SlimUriFactory implements UriFactory +{ + /** + * {@inheritdoc} + */ + public function createUri($uri) + { + if ($uri instanceof UriInterface) { + return $uri; + } + + if (is_string($uri)) { + return Uri::createFromString($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); + } +} diff --git a/vendor/php-http/message/src/filters.php b/vendor/php-http/message/src/filters.php new file mode 100644 index 00000000..15ed73de --- /dev/null +++ b/vendor/php-http/message/src/filters.php @@ -0,0 +1,6 @@ + + +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. diff --git a/vendor/php-http/promise/README.md b/vendor/php-http/promise/README.md new file mode 100644 index 00000000..adda2aeb --- /dev/null +++ b/vendor/php-http/promise/README.md @@ -0,0 +1,49 @@ +# Promise + +[![Latest Version](https://img.shields.io/github/release/php-http/promise.svg?style=flat-square)](https://github.com/php-http/promise/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Build Status](https://img.shields.io/travis/php-http/promise.svg?style=flat-square)](https://travis-ci.org/php-http/promise) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/promise.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/promise) +[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/promise.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/promise) +[![Total Downloads](https://img.shields.io/packagist/dt/php-http/promise.svg?style=flat-square)](https://packagist.org/packages/php-http/promise) + +**Promise used for asynchronous HTTP requests.** + +**Note:** This will eventually be removed/deprecated and replaced with the upcoming Promise PSR. + + +## Install + +Via Composer + +``` bash +$ composer require php-http/promise +``` + + +## Documentation + +Please see the [official documentation](http://docs.php-http.org). + + +## Testing + +``` bash +$ composer test +``` + + +## Contributing + +Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html). + + +## Security + +If you discover any security related issues, please contact us at [security@httplug.io](mailto:security@httplug.io) +or [security@php-http.org](mailto:security@php-http.org). + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/vendor/php-http/promise/composer.json b/vendor/php-http/promise/composer.json new file mode 100644 index 00000000..ff1d2cee --- /dev/null +++ b/vendor/php-http/promise/composer.json @@ -0,0 +1,35 @@ +{ + "name": "php-http/promise", + "description": "Promise used for asynchronous HTTP requests", + "license": "MIT", + "keywords": ["promise"], + "homepage": "http://httplug.io", + "authors": [ + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "require-dev": { + "phpspec/phpspec": "^2.4", + "henrikbjorn/phpspec-code-coverage" : "^1.0" + }, + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "scripts": { + "test": "vendor/bin/phpspec run", + "test-ci": "vendor/bin/phpspec run -c phpspec.yml.ci" + }, + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + } +} diff --git a/vendor/php-http/promise/src/FulfilledPromise.php b/vendor/php-http/promise/src/FulfilledPromise.php new file mode 100644 index 00000000..f60f686a --- /dev/null +++ b/vendor/php-http/promise/src/FulfilledPromise.php @@ -0,0 +1,58 @@ + + */ +final class FulfilledPromise implements Promise +{ + /** + * @var mixed + */ + private $result; + + /** + * @param $result + */ + public function __construct($result) + { + $this->result = $result; + } + + /** + * {@inheritdoc} + */ + public function then(callable $onFulfilled = null, callable $onRejected = null) + { + if (null === $onFulfilled) { + return $this; + } + + try { + return new self($onFulfilled($this->result)); + } catch (\Exception $e) { + return new RejectedPromise($e); + } + } + + /** + * {@inheritdoc} + */ + public function getState() + { + return Promise::FULFILLED; + } + + /** + * {@inheritdoc} + */ + public function wait($unwrap = true) + { + if ($unwrap) { + return $this->result; + } + } +} diff --git a/vendor/php-http/promise/src/Promise.php b/vendor/php-http/promise/src/Promise.php new file mode 100644 index 00000000..e2cf5f89 --- /dev/null +++ b/vendor/php-http/promise/src/Promise.php @@ -0,0 +1,69 @@ + + * @author Márk Sági-Kazár + */ +interface Promise +{ + /** + * Promise has not been fulfilled or rejected. + */ + const PENDING = 'pending'; + + /** + * Promise has been fulfilled. + */ + const FULFILLED = 'fulfilled'; + + /** + * Promise has been rejected. + */ + const REJECTED = 'rejected'; + + /** + * Adds behavior for when the promise is resolved or rejected (response will be available, or error happens). + * + * If you do not care about one of the cases, you can set the corresponding callable to null + * The callback will be called when the value arrived and never more than once. + * + * @param callable $onFulfilled Called when a response will be available. + * @param callable $onRejected Called when an exception occurs. + * + * @return Promise A new resolved promise with value of the executed callback (onFulfilled / onRejected). + */ + public function then(callable $onFulfilled = null, callable $onRejected = null); + + /** + * Returns the state of the promise, one of PENDING, FULFILLED or REJECTED. + * + * @return string + */ + public function getState(); + + /** + * Wait for the promise to be fulfilled or rejected. + * + * When this method returns, the request has been resolved and if callables have been + * specified, the appropriate one has terminated. + * + * When $unwrap is true (the default), the response is returned, or the exception thrown + * on failure. Otherwise, nothing is returned or thrown. + * + * @param bool $unwrap Whether to return resolved value / throw reason or not + * + * @return mixed Resolved value, null if $unwrap is set to false + * + * @throws \Exception The rejection reason if $unwrap is set to true and the request failed. + */ + public function wait($unwrap = true); +} diff --git a/vendor/php-http/promise/src/RejectedPromise.php b/vendor/php-http/promise/src/RejectedPromise.php new file mode 100644 index 00000000..e396a40f --- /dev/null +++ b/vendor/php-http/promise/src/RejectedPromise.php @@ -0,0 +1,58 @@ + + */ +final class RejectedPromise implements Promise +{ + /** + * @var \Exception + */ + private $exception; + + /** + * @param \Exception $exception + */ + public function __construct(\Exception $exception) + { + $this->exception = $exception; + } + + /** + * {@inheritdoc} + */ + public function then(callable $onFulfilled = null, callable $onRejected = null) + { + if (null === $onRejected) { + return $this; + } + + try { + return new FulfilledPromise($onRejected($this->exception)); + } catch (\Exception $e) { + return new self($e); + } + } + + /** + * {@inheritdoc} + */ + public function getState() + { + return Promise::REJECTED; + } + + /** + * {@inheritdoc} + */ + public function wait($unwrap = true) + { + if ($unwrap) { + throw $this->exception; + } + } +} diff --git a/vendor/psr/cache/CHANGELOG.md b/vendor/psr/cache/CHANGELOG.md new file mode 100644 index 00000000..58ddab05 --- /dev/null +++ b/vendor/psr/cache/CHANGELOG.md @@ -0,0 +1,16 @@ +# 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 + +### Fixed + +- Make spacing consistent in phpdoc annotations php-fig/cache#9 - chalasr +- Fix grammar in phpdoc annotations php-fig/cache#10 - chalasr +- Be more specific in docblocks that `getItems()` and `deleteItems()` take an array of strings (`string[]`) compared to just `array` php-fig/cache#8 - GrahamCampbell +- For `expiresAt()` and `expiresAfter()` in CacheItemInterface fix docblock to specify null as a valid parameters as well as an implementation of DateTimeInterface php-fig/cache#7 - GrahamCampbell + +## 1.0.0 - 2015-12-11 + +Initial stable release; reflects accepted PSR-6 specification diff --git a/vendor/psr/cache/LICENSE.txt b/vendor/psr/cache/LICENSE.txt new file mode 100644 index 00000000..b1c2c97b --- /dev/null +++ b/vendor/psr/cache/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2015 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. diff --git a/vendor/psr/cache/README.md b/vendor/psr/cache/README.md new file mode 100644 index 00000000..c8706cee --- /dev/null +++ b/vendor/psr/cache/README.md @@ -0,0 +1,9 @@ +PSR Cache +========= + +This repository holds all interfaces defined by +[PSR-6](http://www.php-fig.org/psr/psr-6/). + +Note that this is not a Cache implementation of its own. It is merely an +interface that describes a Cache implementation. See the specification for more +details. diff --git a/vendor/psr/cache/composer.json b/vendor/psr/cache/composer.json new file mode 100644 index 00000000..e828fec9 --- /dev/null +++ b/vendor/psr/cache/composer.json @@ -0,0 +1,25 @@ +{ + "name": "psr/cache", + "description": "Common interface for caching libraries", + "keywords": ["psr", "psr-6", "cache"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/cache/src/CacheException.php b/vendor/psr/cache/src/CacheException.php new file mode 100644 index 00000000..e27f22f8 --- /dev/null +++ b/vendor/psr/cache/src/CacheException.php @@ -0,0 +1,10 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Debug; + +use Symfony\Component\OptionsResolver\Exception\NoConfigurationException; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Maxime Steinhausser + * + * @final + */ +class OptionsResolverIntrospector +{ + private $get; + + public function __construct(OptionsResolver $optionsResolver) + { + $this->get = \Closure::bind(function ($property, $option, $message) { + /** @var OptionsResolver $this */ + if (!$this->isDefined($option)) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist.', $option)); + } + + if (!array_key_exists($option, $this->{$property})) { + throw new NoConfigurationException($message); + } + + return $this->{$property}[$option]; + }, $optionsResolver, $optionsResolver); + } + + /** + * @return mixed + * + * @throws NoConfigurationException on no configured value + */ + public function getDefault(string $option) + { + return ($this->get)('defaults', $option, sprintf('No default value was set for the "%s" option.', $option)); + } + + /** + * @return \Closure[] + * + * @throws NoConfigurationException on no configured closures + */ + public function getLazyClosures(string $option): array + { + return ($this->get)('lazy', $option, sprintf('No lazy closures were set for the "%s" option.', $option)); + } + + /** + * @return string[] + * + * @throws NoConfigurationException on no configured types + */ + public function getAllowedTypes(string $option): array + { + return ($this->get)('allowedTypes', $option, sprintf('No allowed types were set for the "%s" option.', $option)); + } + + /** + * @return mixed[] + * + * @throws NoConfigurationException on no configured values + */ + public function getAllowedValues(string $option): array + { + return ($this->get)('allowedValues', $option, sprintf('No allowed values were set for the "%s" option.', $option)); + } + + /** + * @throws NoConfigurationException on no configured normalizer + */ + public function getNormalizer(string $option): \Closure + { + return ($this->get)('normalizers', $option, sprintf('No normalizer was set for the "%s" option.', $option)); + } + + /** + * @return string|\Closure + * + * @throws NoConfigurationException on no configured deprecation + */ + public function getDeprecationMessage(string $option) + { + return ($this->get)('deprecated', $option, sprintf('No deprecation was set for the "%s" option.', $option)); + } +} diff --git a/vendor/symfony/options-resolver/Exception/AccessException.php b/vendor/symfony/options-resolver/Exception/AccessException.php new file mode 100644 index 00000000..c12b6806 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/AccessException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when trying to read an option outside of or write it inside of + * {@link \Symfony\Component\OptionsResolver\Options::resolve()}. + * + * @author Bernhard Schussek + */ +class AccessException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/ExceptionInterface.php b/vendor/symfony/options-resolver/Exception/ExceptionInterface.php new file mode 100644 index 00000000..ea99d050 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Marker interface for all exceptions thrown by the OptionsResolver component. + * + * @author Bernhard Schussek + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php b/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..6d421d68 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when an argument is invalid. + * + * @author Bernhard Schussek + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php b/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php new file mode 100644 index 00000000..6fd4f125 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when the value of an option does not match its validation rules. + * + * You should make sure a valid value is passed to the option. + * + * @author Bernhard Schussek + */ +class InvalidOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/Exception/MissingOptionsException.php b/vendor/symfony/options-resolver/Exception/MissingOptionsException.php new file mode 100644 index 00000000..faa487f1 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/MissingOptionsException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Exception thrown when a required option is missing. + * + * Add the option to the passed options array. + * + * @author Bernhard Schussek + */ +class MissingOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/Exception/NoConfigurationException.php b/vendor/symfony/options-resolver/Exception/NoConfigurationException.php new file mode 100644 index 00000000..6693ec14 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/NoConfigurationException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +use Symfony\Component\OptionsResolver\Debug\OptionsResolverIntrospector; + +/** + * Thrown when trying to introspect an option definition property + * for which no value was configured inside the OptionsResolver instance. + * + * @see OptionsResolverIntrospector + * + * @author Maxime Steinhausser + */ +class NoConfigurationException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php b/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php new file mode 100644 index 00000000..4c3280f4 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when trying to read an option that has no value set. + * + * When accessing optional options from within a lazy option or normalizer you should first + * check whether the optional option is set. You can do this with `isset($options['optional'])`. + * In contrast to the {@link UndefinedOptionsException}, this is a runtime exception that can + * occur when evaluating lazy options. + * + * @author Tobias Schultze + */ +class NoSuchOptionException extends \OutOfBoundsException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php b/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php new file mode 100644 index 00000000..e8e339d4 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when two lazy options have a cyclic dependency. + * + * @author Bernhard Schussek + */ +class OptionDefinitionException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php b/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php new file mode 100644 index 00000000..6ca3fce4 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Exception thrown when an undefined option is passed. + * + * You should remove the options in question from your code or define them + * beforehand. + * + * @author Bernhard Schussek + */ +class UndefinedOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/LICENSE b/vendor/symfony/options-resolver/LICENSE new file mode 100644 index 00000000..a677f437 --- /dev/null +++ b/vendor/symfony/options-resolver/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/options-resolver/Options.php b/vendor/symfony/options-resolver/Options.php new file mode 100644 index 00000000..d18374cb --- /dev/null +++ b/vendor/symfony/options-resolver/Options.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver; + +/** + * Contains resolved option values. + * + * @author Bernhard Schussek + * @author Tobias Schultze + * + * @method mixed offsetGet(string $option, bool $triggerDeprecation = true) + */ +interface Options extends \ArrayAccess, \Countable +{ +} diff --git a/vendor/symfony/options-resolver/OptionsResolver.php b/vendor/symfony/options-resolver/OptionsResolver.php new file mode 100644 index 00000000..5d238f50 --- /dev/null +++ b/vendor/symfony/options-resolver/OptionsResolver.php @@ -0,0 +1,1149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver; + +use Symfony\Component\OptionsResolver\Exception\AccessException; +use Symfony\Component\OptionsResolver\Exception\InvalidArgumentException; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; +use Symfony\Component\OptionsResolver\Exception\NoSuchOptionException; +use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; + +/** + * Validates options and merges them with default values. + * + * @author Bernhard Schussek + * @author Tobias Schultze + */ +class OptionsResolver implements Options +{ + /** + * The names of all defined options. + */ + private $defined = array(); + + /** + * The default option values. + */ + private $defaults = array(); + + /** + * A list of closure for nested options. + * + * @var \Closure[][] + */ + private $nested = array(); + + /** + * The names of required options. + */ + private $required = array(); + + /** + * The resolved option values. + */ + private $resolved = array(); + + /** + * A list of normalizer closures. + * + * @var \Closure[] + */ + private $normalizers = array(); + + /** + * A list of accepted values for each option. + */ + private $allowedValues = array(); + + /** + * A list of accepted types for each option. + */ + private $allowedTypes = array(); + + /** + * A list of closures for evaluating lazy options. + */ + private $lazy = array(); + + /** + * A list of lazy options whose closure is currently being called. + * + * This list helps detecting circular dependencies between lazy options. + */ + private $calling = array(); + + /** + * A list of deprecated options. + */ + private $deprecated = array(); + + /** + * The list of options provided by the user. + */ + private $given = array(); + + /** + * Whether the instance is locked for reading. + * + * Once locked, the options cannot be changed anymore. This is + * necessary in order to avoid inconsistencies during the resolving + * process. If any option is changed after being read, all evaluated + * lazy options that depend on this option would become invalid. + */ + private $locked = false; + + private static $typeAliases = array( + 'boolean' => 'bool', + 'integer' => 'int', + 'double' => 'float', + ); + + /** + * Sets the default value of a given option. + * + * If the default value should be set based on other options, you can pass + * a closure with the following signature: + * + * function (Options $options) { + * // ... + * } + * + * The closure will be evaluated when {@link resolve()} is called. The + * closure has access to the resolved values of other options through the + * passed {@link Options} instance: + * + * function (Options $options) { + * if (isset($options['port'])) { + * // ... + * } + * } + * + * If you want to access the previously set default value, add a second + * argument to the closure's signature: + * + * $options->setDefault('name', 'Default Name'); + * + * $options->setDefault('name', function (Options $options, $previousValue) { + * // 'Default Name' === $previousValue + * }); + * + * This is mostly useful if the configuration of the {@link Options} object + * is spread across different locations of your code, such as base and + * sub-classes. + * + * If you want to define nested options, you can pass a closure with the + * following signature: + * + * $options->setDefault('database', function (OptionsResolver $resolver) { + * $resolver->setDefined(array('dbname', 'host', 'port', 'user', 'pass')); + * } + * + * To get access to the parent options, add a second argument to the closure's + * signature: + * + * function (OptionsResolver $resolver, Options $parent) { + * // 'default' === $parent['connection'] + * } + * + * @param string $option The name of the option + * @param mixed $value The default value of the option + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setDefault($option, $value) + { + // Setting is not possible once resolving starts, because then lazy + // options could manipulate the state of the object, leading to + // inconsistent results. + if ($this->locked) { + throw new AccessException('Default values cannot be set from a lazy option or normalizer.'); + } + + // If an option is a closure that should be evaluated lazily, store it + // in the "lazy" property. + if ($value instanceof \Closure) { + $reflClosure = new \ReflectionFunction($value); + $params = $reflClosure->getParameters(); + + if (isset($params[0]) && null !== ($class = $params[0]->getClass()) && Options::class === $class->name) { + // Initialize the option if no previous value exists + if (!isset($this->defaults[$option])) { + $this->defaults[$option] = null; + } + + // Ignore previous lazy options if the closure has no second parameter + if (!isset($this->lazy[$option]) || !isset($params[1])) { + $this->lazy[$option] = array(); + } + + // Store closure for later evaluation + $this->lazy[$option][] = $value; + $this->defined[$option] = true; + + // Make sure the option is processed and is not nested anymore + unset($this->resolved[$option], $this->nested[$option]); + + return $this; + } + + if (isset($params[0]) && null !== ($class = $params[0]->getClass()) && self::class === $class->name && (!isset($params[1]) || (null !== ($class = $params[1]->getClass()) && Options::class === $class->name))) { + // Store closure for later evaluation + $this->nested[$option][] = $value; + $this->defaults[$option] = array(); + $this->defined[$option] = true; + + // Make sure the option is processed and is not lazy anymore + unset($this->resolved[$option], $this->lazy[$option]); + + return $this; + } + } + + // This option is not lazy nor nested anymore + unset($this->lazy[$option], $this->nested[$option]); + + // Yet undefined options can be marked as resolved, because we only need + // to resolve options with lazy closures, normalizers or validation + // rules, none of which can exist for undefined options + // If the option was resolved before, update the resolved value + if (!isset($this->defined[$option]) || array_key_exists($option, $this->resolved)) { + $this->resolved[$option] = $value; + } + + $this->defaults[$option] = $value; + $this->defined[$option] = true; + + return $this; + } + + /** + * Sets a list of default values. + * + * @param array $defaults The default values to set + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setDefaults(array $defaults) + { + foreach ($defaults as $option => $value) { + $this->setDefault($option, $value); + } + + return $this; + } + + /** + * Returns whether a default value is set for an option. + * + * Returns true if {@link setDefault()} was called for this option. + * An option is also considered set if it was set to null. + * + * @param string $option The option name + * + * @return bool Whether a default value is set + */ + public function hasDefault($option) + { + return array_key_exists($option, $this->defaults); + } + + /** + * Marks one or more options as required. + * + * @param string|string[] $optionNames One or more option names + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setRequired($optionNames) + { + if ($this->locked) { + throw new AccessException('Options cannot be made required from a lazy option or normalizer.'); + } + + foreach ((array) $optionNames as $option) { + $this->defined[$option] = true; + $this->required[$option] = true; + } + + return $this; + } + + /** + * Returns whether an option is required. + * + * An option is required if it was passed to {@link setRequired()}. + * + * @param string $option The name of the option + * + * @return bool Whether the option is required + */ + public function isRequired($option) + { + return isset($this->required[$option]); + } + + /** + * Returns the names of all required options. + * + * @return string[] The names of the required options + * + * @see isRequired() + */ + public function getRequiredOptions() + { + return array_keys($this->required); + } + + /** + * Returns whether an option is missing a default value. + * + * An option is missing if it was passed to {@link setRequired()}, but not + * to {@link setDefault()}. This option must be passed explicitly to + * {@link resolve()}, otherwise an exception will be thrown. + * + * @param string $option The name of the option + * + * @return bool Whether the option is missing + */ + public function isMissing($option) + { + return isset($this->required[$option]) && !array_key_exists($option, $this->defaults); + } + + /** + * Returns the names of all options missing a default value. + * + * @return string[] The names of the missing options + * + * @see isMissing() + */ + public function getMissingOptions() + { + return array_keys(array_diff_key($this->required, $this->defaults)); + } + + /** + * Defines a valid option name. + * + * Defines an option name without setting a default value. The option will + * be accepted when passed to {@link resolve()}. When not passed, the + * option will not be included in the resolved options. + * + * @param string|string[] $optionNames One or more option names + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setDefined($optionNames) + { + if ($this->locked) { + throw new AccessException('Options cannot be defined from a lazy option or normalizer.'); + } + + foreach ((array) $optionNames as $option) { + $this->defined[$option] = true; + } + + return $this; + } + + /** + * Returns whether an option is defined. + * + * Returns true for any option passed to {@link setDefault()}, + * {@link setRequired()} or {@link setDefined()}. + * + * @param string $option The option name + * + * @return bool Whether the option is defined + */ + public function isDefined($option) + { + return isset($this->defined[$option]); + } + + /** + * Returns the names of all defined options. + * + * @return string[] The names of the defined options + * + * @see isDefined() + */ + public function getDefinedOptions() + { + return array_keys($this->defined); + } + + public function isNested(string $option): bool + { + return isset($this->nested[$option]); + } + + /** + * Deprecates an option, allowed types or values. + * + * Instead of passing the message, you may also pass a closure with the + * following signature: + * + * function (Options $options, $value): string { + * // ... + * } + * + * The closure receives the value as argument and should return a string. + * Return an empty string to ignore the option deprecation. + * + * The closure is invoked when {@link resolve()} is called. The parameter + * passed to the closure is the value of the option after validating it + * and before normalizing it. + * + * @param string|\Closure $deprecationMessage + */ + public function setDeprecated(string $option, $deprecationMessage = 'The option "%name%" is deprecated.'): self + { + if ($this->locked) { + throw new AccessException('Options cannot be deprecated from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist, defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); + } + + if (!\is_string($deprecationMessage) && !$deprecationMessage instanceof \Closure) { + throw new InvalidArgumentException(sprintf('Invalid type for deprecation message argument, expected string or \Closure, but got "%s".', \gettype($deprecationMessage))); + } + + // ignore if empty string + if ('' === $deprecationMessage) { + return $this; + } + + $this->deprecated[$option] = $deprecationMessage; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + public function isDeprecated(string $option): bool + { + return isset($this->deprecated[$option]); + } + + /** + * Sets the normalizer for an option. + * + * The normalizer should be a closure with the following signature: + * + * function (Options $options, $value) { + * // ... + * } + * + * The closure is invoked when {@link resolve()} is called. The closure + * has access to the resolved values of other options through the passed + * {@link Options} instance. + * + * The second parameter passed to the closure is the value of + * the option. + * + * The resolved option value is set to the return value of the closure. + * + * @param string $option The option name + * @param \Closure $normalizer The normalizer + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function setNormalizer($option, \Closure $normalizer) + { + if ($this->locked) { + throw new AccessException('Normalizers cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); + } + + $this->normalizers[$option] = $normalizer; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Sets allowed values for an option. + * + * Instead of passing values, you may also pass a closures with the + * following signature: + * + * function ($value) { + * // return true or false + * } + * + * The closure receives the value as argument and should return true to + * accept the value and false to reject the value. + * + * @param string $option The option name + * @param mixed $allowedValues One or more acceptable values/closures + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function setAllowedValues($option, $allowedValues) + { + if ($this->locked) { + throw new AccessException('Allowed values cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); + } + + $this->allowedValues[$option] = \is_array($allowedValues) ? $allowedValues : array($allowedValues); + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Adds allowed values for an option. + * + * The values are merged with the allowed values defined previously. + * + * Instead of passing values, you may also pass a closures with the + * following signature: + * + * function ($value) { + * // return true or false + * } + * + * The closure receives the value as argument and should return true to + * accept the value and false to reject the value. + * + * @param string $option The option name + * @param mixed $allowedValues One or more acceptable values/closures + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function addAllowedValues($option, $allowedValues) + { + if ($this->locked) { + throw new AccessException('Allowed values cannot be added from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); + } + + if (!\is_array($allowedValues)) { + $allowedValues = array($allowedValues); + } + + if (!isset($this->allowedValues[$option])) { + $this->allowedValues[$option] = $allowedValues; + } else { + $this->allowedValues[$option] = array_merge($this->allowedValues[$option], $allowedValues); + } + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Sets allowed types for an option. + * + * Any type for which a corresponding is_() function exists is + * acceptable. Additionally, fully-qualified class or interface names may + * be passed. + * + * @param string $option The option name + * @param string|string[] $allowedTypes One or more accepted types + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function setAllowedTypes($option, $allowedTypes) + { + if ($this->locked) { + throw new AccessException('Allowed types cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); + } + + $this->allowedTypes[$option] = (array) $allowedTypes; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Adds allowed types for an option. + * + * The types are merged with the allowed types defined previously. + * + * Any type for which a corresponding is_() function exists is + * acceptable. Additionally, fully-qualified class or interface names may + * be passed. + * + * @param string $option The option name + * @param string|string[] $allowedTypes One or more accepted types + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function addAllowedTypes($option, $allowedTypes) + { + if ($this->locked) { + throw new AccessException('Allowed types cannot be added from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); + } + + if (!isset($this->allowedTypes[$option])) { + $this->allowedTypes[$option] = (array) $allowedTypes; + } else { + $this->allowedTypes[$option] = array_merge($this->allowedTypes[$option], (array) $allowedTypes); + } + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Removes the option with the given name. + * + * Undefined options are ignored. + * + * @param string|string[] $optionNames One or more option names + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function remove($optionNames) + { + if ($this->locked) { + throw new AccessException('Options cannot be removed from a lazy option or normalizer.'); + } + + foreach ((array) $optionNames as $option) { + unset($this->defined[$option], $this->defaults[$option], $this->required[$option], $this->resolved[$option]); + unset($this->lazy[$option], $this->normalizers[$option], $this->allowedTypes[$option], $this->allowedValues[$option]); + } + + return $this; + } + + /** + * Removes all options. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function clear() + { + if ($this->locked) { + throw new AccessException('Options cannot be cleared from a lazy option or normalizer.'); + } + + $this->defined = array(); + $this->defaults = array(); + $this->nested = array(); + $this->required = array(); + $this->resolved = array(); + $this->lazy = array(); + $this->normalizers = array(); + $this->allowedTypes = array(); + $this->allowedValues = array(); + $this->deprecated = array(); + + return $this; + } + + /** + * Merges options with the default values stored in the container and + * validates them. + * + * Exceptions are thrown if: + * + * - Undefined options are passed; + * - Required options are missing; + * - Options have invalid types; + * - Options have invalid values. + * + * @param array $options A map of option names to values + * + * @return array The merged and validated options + * + * @throws UndefinedOptionsException If an option name is undefined + * @throws InvalidOptionsException If an option doesn't fulfill the + * specified validation rules + * @throws MissingOptionsException If a required option is missing + * @throws OptionDefinitionException If there is a cyclic dependency between + * lazy options and/or normalizers + * @throws NoSuchOptionException If a lazy option reads an unavailable option + * @throws AccessException If called from a lazy option or normalizer + */ + public function resolve(array $options = array()) + { + if ($this->locked) { + throw new AccessException('Options cannot be resolved from a lazy option or normalizer.'); + } + + // Allow this method to be called multiple times + $clone = clone $this; + + // Make sure that no unknown options are passed + $diff = array_diff_key($options, $clone->defined); + + if (\count($diff) > 0) { + ksort($clone->defined); + ksort($diff); + + throw new UndefinedOptionsException(sprintf((\count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.').' Defined options are: "%s".', implode('", "', array_keys($diff)), implode('", "', array_keys($clone->defined)))); + } + + // Override options set by the user + foreach ($options as $option => $value) { + $clone->given[$option] = true; + $clone->defaults[$option] = $value; + unset($clone->resolved[$option], $clone->lazy[$option]); + } + + // Check whether any required option is missing + $diff = array_diff_key($clone->required, $clone->defaults); + + if (\count($diff) > 0) { + ksort($diff); + + throw new MissingOptionsException(sprintf(\count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', implode('", "', array_keys($diff)))); + } + + // Lock the container + $clone->locked = true; + + // Now process the individual options. Use offsetGet(), which resolves + // the option itself and any options that the option depends on + foreach ($clone->defaults as $option => $_) { + $clone->offsetGet($option); + } + + return $clone->resolved; + } + + /** + * Returns the resolved value of an option. + * + * @param string $option The option name + * @param bool $triggerDeprecation Whether to trigger the deprecation or not (true by default) + * + * @return mixed The option value + * + * @throws AccessException If accessing this method outside of + * {@link resolve()} + * @throws NoSuchOptionException If the option is not set + * @throws InvalidOptionsException If the option doesn't fulfill the + * specified validation rules + * @throws OptionDefinitionException If there is a cyclic dependency between + * lazy options and/or normalizers + */ + public function offsetGet($option/*, bool $triggerDeprecation = true*/) + { + if (!$this->locked) { + throw new AccessException('Array access is only supported within closures of lazy options and normalizers.'); + } + + $triggerDeprecation = 1 === \func_num_args() || \func_get_arg(1); + + // Shortcut for resolved options + if (isset($this->resolved[$option]) || array_key_exists($option, $this->resolved)) { + if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || $this->calling) && \is_string($this->deprecated[$option])) { + @trigger_error(strtr($this->deprecated[$option], array('%name%' => $option)), E_USER_DEPRECATED); + } + + return $this->resolved[$option]; + } + + // Check whether the option is set at all + if (!isset($this->defaults[$option]) && !array_key_exists($option, $this->defaults)) { + if (!isset($this->defined[$option])) { + throw new NoSuchOptionException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); + } + + throw new NoSuchOptionException(sprintf('The optional option "%s" has no value set. You should make sure it is set with "isset" before reading it.', $option)); + } + + $value = $this->defaults[$option]; + + // Resolve the option if it is a nested definition + if (isset($this->nested[$option])) { + // If the closure is already being called, we have a cyclic dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', array_keys($this->calling)))); + } + + if (!\is_array($value)) { + throw new InvalidOptionsException(sprintf('The nested option "%s" with value %s is expected to be of type array, but is of type "%s".', $option, $this->formatValue($value), $this->formatTypeOf($value))); + } + + // The following section must be protected from cyclic calls. + $this->calling[$option] = true; + try { + $resolver = new self(); + foreach ($this->nested[$option] as $closure) { + $closure($resolver, $this); + } + $value = $resolver->resolve($value); + } finally { + unset($this->calling[$option]); + } + } + + // Resolve the option if the default value is lazily evaluated + if (isset($this->lazy[$option])) { + // If the closure is already being called, we have a cyclic + // dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', array_keys($this->calling)))); + } + + // The following section must be protected from cyclic + // calls. Set $calling for the current $option to detect a cyclic + // dependency + // BEGIN + $this->calling[$option] = true; + try { + foreach ($this->lazy[$option] as $closure) { + $value = $closure($this, $value); + } + } finally { + unset($this->calling[$option]); + } + // END + } + + // Validate the type of the resolved option + if (isset($this->allowedTypes[$option])) { + $valid = false; + $invalidTypes = array(); + + foreach ($this->allowedTypes[$option] as $type) { + $type = self::$typeAliases[$type] ?? $type; + + if ($valid = $this->verifyTypes($type, $value, $invalidTypes)) { + break; + } + } + + if (!$valid) { + $keys = array_keys($invalidTypes); + + if (1 === \count($keys) && '[]' === substr($keys[0], -2)) { + throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $option, $this->formatValue($value), implode('" or "', $this->allowedTypes[$option]), $keys[0])); + } + + throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but is of type "%s".', $option, $this->formatValue($value), implode('" or "', $this->allowedTypes[$option]), implode('|', array_keys($invalidTypes)))); + } + } + + // Validate the value of the resolved option + if (isset($this->allowedValues[$option])) { + $success = false; + $printableAllowedValues = array(); + + foreach ($this->allowedValues[$option] as $allowedValue) { + if ($allowedValue instanceof \Closure) { + if ($allowedValue($value)) { + $success = true; + break; + } + + // Don't include closures in the exception message + continue; + } + + if ($value === $allowedValue) { + $success = true; + break; + } + + $printableAllowedValues[] = $allowedValue; + } + + if (!$success) { + $message = sprintf( + 'The option "%s" with value %s is invalid.', + $option, + $this->formatValue($value) + ); + + if (\count($printableAllowedValues) > 0) { + $message .= sprintf( + ' Accepted values are: %s.', + $this->formatValues($printableAllowedValues) + ); + } + + throw new InvalidOptionsException($message); + } + } + + // Check whether the option is deprecated + // and it is provided by the user or is being called from a lazy evaluation + if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || ($this->calling && \is_string($this->deprecated[$option])))) { + $deprecationMessage = $this->deprecated[$option]; + + if ($deprecationMessage instanceof \Closure) { + // If the closure is already being called, we have a cyclic dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', array_keys($this->calling)))); + } + + $this->calling[$option] = true; + try { + if (!\is_string($deprecationMessage = $deprecationMessage($this, $value))) { + throw new InvalidOptionsException(sprintf('Invalid type for deprecation message, expected string but got "%s", return an empty string to ignore.', \gettype($deprecationMessage))); + } + } finally { + unset($this->calling[$option]); + } + } + + if ('' !== $deprecationMessage) { + @trigger_error(strtr($deprecationMessage, array('%name%' => $option)), E_USER_DEPRECATED); + } + } + + // Normalize the validated option + if (isset($this->normalizers[$option])) { + // If the closure is already being called, we have a cyclic + // dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', array_keys($this->calling)))); + } + + $normalizer = $this->normalizers[$option]; + + // The following section must be protected from cyclic + // calls. Set $calling for the current $option to detect a cyclic + // dependency + // BEGIN + $this->calling[$option] = true; + try { + $value = $normalizer($this, $value); + } finally { + unset($this->calling[$option]); + } + // END + } + + // Mark as resolved + $this->resolved[$option] = $value; + + return $value; + } + + private function verifyTypes(string $type, $value, array &$invalidTypes, int $level = 0): bool + { + if (\is_array($value) && '[]' === substr($type, -2)) { + $type = substr($type, 0, -2); + + foreach ($value as $val) { + if (!$this->verifyTypes($type, $val, $invalidTypes, $level + 1)) { + return false; + } + } + + return true; + } + + if (('null' === $type && null === $value) || (\function_exists($func = 'is_'.$type) && $func($value)) || $value instanceof $type) { + return true; + } + + if (!$invalidTypes) { + $suffix = ''; + while (\strlen($suffix) < $level * 2) { + $suffix .= '[]'; + } + $invalidTypes[$this->formatTypeOf($value).$suffix] = true; + } + + return false; + } + + /** + * Returns whether a resolved option with the given name exists. + * + * @param string $option The option name + * + * @return bool Whether the option is set + * + * @throws AccessException If accessing this method outside of {@link resolve()} + * + * @see \ArrayAccess::offsetExists() + */ + public function offsetExists($option) + { + if (!$this->locked) { + throw new AccessException('Array access is only supported within closures of lazy options and normalizers.'); + } + + return array_key_exists($option, $this->defaults); + } + + /** + * Not supported. + * + * @throws AccessException + */ + public function offsetSet($option, $value) + { + throw new AccessException('Setting options via array access is not supported. Use setDefault() instead.'); + } + + /** + * Not supported. + * + * @throws AccessException + */ + public function offsetUnset($option) + { + throw new AccessException('Removing options via array access is not supported. Use remove() instead.'); + } + + /** + * Returns the number of set options. + * + * This may be only a subset of the defined options. + * + * @return int Number of options + * + * @throws AccessException If accessing this method outside of {@link resolve()} + * + * @see \Countable::count() + */ + public function count() + { + if (!$this->locked) { + throw new AccessException('Counting is only supported within closures of lazy options and normalizers.'); + } + + return \count($this->defaults); + } + + /** + * Returns a string representation of the type of the value. + * + * @param mixed $value The value to return the type of + * + * @return string The type of the value + */ + private function formatTypeOf($value): string + { + return \is_object($value) ? \get_class($value) : \gettype($value); + } + + /** + * Returns a string representation of the value. + * + * This method returns the equivalent PHP tokens for most scalar types + * (i.e. "false" for false, "1" for 1 etc.). Strings are always wrapped + * in double quotes ("). + * + * @param mixed $value The value to format as string + */ + private function formatValue($value): string + { + if (\is_object($value)) { + return \get_class($value); + } + + if (\is_array($value)) { + return 'array'; + } + + if (\is_string($value)) { + return '"'.$value.'"'; + } + + if (\is_resource($value)) { + return 'resource'; + } + + if (null === $value) { + return 'null'; + } + + if (false === $value) { + return 'false'; + } + + if (true === $value) { + return 'true'; + } + + return (string) $value; + } + + /** + * Returns a string representation of a list of values. + * + * Each of the values is converted to a string using + * {@link formatValue()}. The values are then concatenated with commas. + * + * @see formatValue() + */ + private function formatValues(array $values): string + { + foreach ($values as $key => $value) { + $values[$key] = $this->formatValue($value); + } + + return implode(', ', $values); + } +} diff --git a/vendor/symfony/options-resolver/README.md b/vendor/symfony/options-resolver/README.md new file mode 100644 index 00000000..245e69b5 --- /dev/null +++ b/vendor/symfony/options-resolver/README.md @@ -0,0 +1,15 @@ +OptionsResolver Component +========================= + +The OptionsResolver component is `array_replace` on steroids. It allows you to +create an options system with required options, defaults, validation (type, +value), normalization and more. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/options_resolver.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/options-resolver/Tests/Debug/OptionsResolverIntrospectorTest.php b/vendor/symfony/options-resolver/Tests/Debug/OptionsResolverIntrospectorTest.php new file mode 100644 index 00000000..4bdce6f8 --- /dev/null +++ b/vendor/symfony/options-resolver/Tests/Debug/OptionsResolverIntrospectorTest.php @@ -0,0 +1,248 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Tests\Debug; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\OptionsResolver\Debug\OptionsResolverIntrospector; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class OptionsResolverIntrospectorTest extends TestCase +{ + public function testGetDefault() + { + $resolver = new OptionsResolver(); + $resolver->setDefault($option = 'foo', 'bar'); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getDefault($option)); + } + + public function testGetDefaultNull() + { + $resolver = new OptionsResolver(); + $resolver->setDefault($option = 'foo', null); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertNull($debug->getDefault($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoConfigurationException + * @expectedExceptionMessage No default value was set for the "foo" option. + */ + public function testGetDefaultThrowsOnNoConfiguredValue() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getDefault($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The option "foo" does not exist. + */ + public function testGetDefaultThrowsOnNotDefinedOption() + { + $resolver = new OptionsResolver(); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getDefault('foo')); + } + + public function testGetLazyClosures() + { + $resolver = new OptionsResolver(); + $closures = array(); + $resolver->setDefault($option = 'foo', $closures[] = function (Options $options) {}); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame($closures, $debug->getLazyClosures($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoConfigurationException + * @expectedExceptionMessage No lazy closures were set for the "foo" option. + */ + public function testGetLazyClosuresThrowsOnNoConfiguredValue() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getLazyClosures($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The option "foo" does not exist. + */ + public function testGetLazyClosuresThrowsOnNotDefinedOption() + { + $resolver = new OptionsResolver(); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getLazyClosures('foo')); + } + + public function testGetAllowedTypes() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + $resolver->setAllowedTypes($option = 'foo', $allowedTypes = array('string', 'bool')); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame($allowedTypes, $debug->getAllowedTypes($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoConfigurationException + * @expectedExceptionMessage No allowed types were set for the "foo" option. + */ + public function testGetAllowedTypesThrowsOnNoConfiguredValue() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getAllowedTypes($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The option "foo" does not exist. + */ + public function testGetAllowedTypesThrowsOnNotDefinedOption() + { + $resolver = new OptionsResolver(); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getAllowedTypes('foo')); + } + + public function testGetAllowedValues() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + $resolver->setAllowedValues($option = 'foo', $allowedValues = array('bar', 'baz')); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame($allowedValues, $debug->getAllowedValues($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoConfigurationException + * @expectedExceptionMessage No allowed values were set for the "foo" option. + */ + public function testGetAllowedValuesThrowsOnNoConfiguredValue() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getAllowedValues($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The option "foo" does not exist. + */ + public function testGetAllowedValuesThrowsOnNotDefinedOption() + { + $resolver = new OptionsResolver(); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getAllowedValues('foo')); + } + + public function testGetNormalizer() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + $resolver->setNormalizer($option = 'foo', $normalizer = function () {}); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame($normalizer, $debug->getNormalizer($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoConfigurationException + * @expectedExceptionMessage No normalizer was set for the "foo" option. + */ + public function testGetNormalizerThrowsOnNoConfiguredValue() + { + $resolver = new OptionsResolver(); + $resolver->setDefined($option = 'foo'); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getNormalizer($option)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The option "foo" does not exist. + */ + public function testGetNormalizerThrowsOnNotDefinedOption() + { + $resolver = new OptionsResolver(); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getNormalizer('foo')); + } + + public function testGetDeprecationMessage() + { + $resolver = new OptionsResolver(); + $resolver->setDefined('foo'); + $resolver->setDeprecated('foo', 'The option "foo" is deprecated.'); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('The option "foo" is deprecated.', $debug->getDeprecationMessage('foo')); + } + + public function testGetClosureDeprecationMessage() + { + $resolver = new OptionsResolver(); + $resolver->setDefined('foo'); + $resolver->setDeprecated('foo', $closure = function (Options $options, $value) {}); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame($closure, $debug->getDeprecationMessage('foo')); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoConfigurationException + * @expectedExceptionMessage No deprecation was set for the "foo" option. + */ + public function testGetDeprecationMessageThrowsOnNoConfiguredValue() + { + $resolver = new OptionsResolver(); + $resolver->setDefined('foo'); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getDeprecationMessage('foo')); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The option "foo" does not exist. + */ + public function testGetDeprecationMessageThrowsOnNotDefinedOption() + { + $resolver = new OptionsResolver(); + + $debug = new OptionsResolverIntrospector($resolver); + $this->assertSame('bar', $debug->getDeprecationMessage('foo')); + } +} diff --git a/vendor/symfony/options-resolver/Tests/OptionsResolverTest.php b/vendor/symfony/options-resolver/Tests/OptionsResolverTest.php new file mode 100644 index 00000000..213c471a --- /dev/null +++ b/vendor/symfony/options-resolver/Tests/OptionsResolverTest.php @@ -0,0 +1,2466 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Tests; + +use PHPUnit\Framework\Assert; +use PHPUnit\Framework\TestCase; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class OptionsResolverTest extends TestCase +{ + /** + * @var OptionsResolver + */ + private $resolver; + + protected function setUp() + { + $this->resolver = new OptionsResolver(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The option "foo" does not exist. Defined options are: "a", "z". + */ + public function testResolveFailsIfNonExistingOption() + { + $this->resolver->setDefault('z', '1'); + $this->resolver->setDefault('a', '2'); + + $this->resolver->resolve(array('foo' => 'bar')); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The options "baz", "foo", "ping" do not exist. Defined options are: "a", "z". + */ + public function testResolveFailsIfMultipleNonExistingOptions() + { + $this->resolver->setDefault('z', '1'); + $this->resolver->setDefault('a', '2'); + + $this->resolver->resolve(array('ping' => 'pong', 'foo' => 'bar', 'baz' => 'bam')); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testResolveFailsFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->resolve(array()); + }); + + $this->resolver->resolve(); + } + + public function testSetDefaultReturnsThis() + { + $this->assertSame($this->resolver, $this->resolver->setDefault('foo', 'bar')); + } + + public function testSetDefault() + { + $this->resolver->setDefault('one', '1'); + $this->resolver->setDefault('two', '20'); + + $this->assertEquals(array( + 'one' => '1', + 'two' => '20', + ), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetDefaultFromLazyOption() + { + $this->resolver->setDefault('lazy', function (Options $options) { + $options->setDefault('default', 42); + }); + + $this->resolver->resolve(); + } + + public function testHasDefault() + { + $this->assertFalse($this->resolver->hasDefault('foo')); + $this->resolver->setDefault('foo', 42); + $this->assertTrue($this->resolver->hasDefault('foo')); + } + + public function testHasDefaultWithNullValue() + { + $this->assertFalse($this->resolver->hasDefault('foo')); + $this->resolver->setDefault('foo', null); + $this->assertTrue($this->resolver->hasDefault('foo')); + } + + public function testSetLazyReturnsThis() + { + $this->assertSame($this->resolver, $this->resolver->setDefault('foo', function (Options $options) {})); + } + + public function testSetLazyClosure() + { + $this->resolver->setDefault('foo', function (Options $options) { + return 'lazy'; + }); + + $this->assertEquals(array('foo' => 'lazy'), $this->resolver->resolve()); + } + + public function testClosureWithoutTypeHintNotInvoked() + { + $closure = function ($options) { + Assert::fail('Should not be called'); + }; + + $this->resolver->setDefault('foo', $closure); + + $this->assertSame(array('foo' => $closure), $this->resolver->resolve()); + } + + public function testClosureWithoutParametersNotInvoked() + { + $closure = function () { + Assert::fail('Should not be called'); + }; + + $this->resolver->setDefault('foo', $closure); + + $this->assertSame(array('foo' => $closure), $this->resolver->resolve()); + } + + public function testAccessPreviousDefaultValue() + { + // defined by superclass + $this->resolver->setDefault('foo', 'bar'); + + // defined by subclass + $this->resolver->setDefault('foo', function (Options $options, $previousValue) { + Assert::assertEquals('bar', $previousValue); + + return 'lazy'; + }); + + $this->assertEquals(array('foo' => 'lazy'), $this->resolver->resolve()); + } + + public function testAccessPreviousLazyDefaultValue() + { + // defined by superclass + $this->resolver->setDefault('foo', function (Options $options) { + return 'bar'; + }); + + // defined by subclass + $this->resolver->setDefault('foo', function (Options $options, $previousValue) { + Assert::assertEquals('bar', $previousValue); + + return 'lazy'; + }); + + $this->assertEquals(array('foo' => 'lazy'), $this->resolver->resolve()); + } + + public function testPreviousValueIsNotEvaluatedIfNoSecondArgument() + { + // defined by superclass + $this->resolver->setDefault('foo', function () { + Assert::fail('Should not be called'); + }); + + // defined by subclass, no $previousValue argument defined! + $this->resolver->setDefault('foo', function (Options $options) { + return 'lazy'; + }); + + $this->assertEquals(array('foo' => 'lazy'), $this->resolver->resolve()); + } + + public function testOverwrittenLazyOptionNotEvaluated() + { + $this->resolver->setDefault('foo', function (Options $options) { + Assert::fail('Should not be called'); + }); + + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testInvokeEachLazyOptionOnlyOnce() + { + $calls = 0; + + $this->resolver->setDefault('lazy1', function (Options $options) use (&$calls) { + Assert::assertSame(1, ++$calls); + + $options['lazy2']; + }); + + $this->resolver->setDefault('lazy2', function (Options $options) use (&$calls) { + Assert::assertSame(2, ++$calls); + }); + + $this->resolver->resolve(); + + $this->assertSame(2, $calls); + } + + public function testSetRequiredReturnsThis() + { + $this->assertSame($this->resolver, $this->resolver->setRequired('foo')); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetRequiredFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->setRequired('bar'); + }); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\MissingOptionsException + */ + public function testResolveFailsIfRequiredOptionMissing() + { + $this->resolver->setRequired('foo'); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfRequiredOptionSet() + { + $this->resolver->setRequired('foo'); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + public function testResolveSucceedsIfRequiredOptionPassed() + { + $this->resolver->setRequired('foo'); + + $this->assertNotEmpty($this->resolver->resolve(array('foo' => 'bar'))); + } + + public function testIsRequired() + { + $this->assertFalse($this->resolver->isRequired('foo')); + $this->resolver->setRequired('foo'); + $this->assertTrue($this->resolver->isRequired('foo')); + } + + public function testRequiredIfSetBefore() + { + $this->assertFalse($this->resolver->isRequired('foo')); + + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setRequired('foo'); + + $this->assertTrue($this->resolver->isRequired('foo')); + } + + public function testStillRequiredAfterSet() + { + $this->assertFalse($this->resolver->isRequired('foo')); + + $this->resolver->setRequired('foo'); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertTrue($this->resolver->isRequired('foo')); + } + + public function testIsNotRequiredAfterRemove() + { + $this->assertFalse($this->resolver->isRequired('foo')); + $this->resolver->setRequired('foo'); + $this->resolver->remove('foo'); + $this->assertFalse($this->resolver->isRequired('foo')); + } + + public function testIsNotRequiredAfterClear() + { + $this->assertFalse($this->resolver->isRequired('foo')); + $this->resolver->setRequired('foo'); + $this->resolver->clear(); + $this->assertFalse($this->resolver->isRequired('foo')); + } + + public function testGetRequiredOptions() + { + $this->resolver->setRequired(array('foo', 'bar')); + $this->resolver->setDefault('bam', 'baz'); + $this->resolver->setDefault('foo', 'boo'); + + $this->assertSame(array('foo', 'bar'), $this->resolver->getRequiredOptions()); + } + + public function testIsMissingIfNotSet() + { + $this->assertFalse($this->resolver->isMissing('foo')); + $this->resolver->setRequired('foo'); + $this->assertTrue($this->resolver->isMissing('foo')); + } + + public function testIsNotMissingIfSet() + { + $this->resolver->setDefault('foo', 'bar'); + + $this->assertFalse($this->resolver->isMissing('foo')); + $this->resolver->setRequired('foo'); + $this->assertFalse($this->resolver->isMissing('foo')); + } + + public function testIsNotMissingAfterRemove() + { + $this->resolver->setRequired('foo'); + $this->resolver->remove('foo'); + $this->assertFalse($this->resolver->isMissing('foo')); + } + + public function testIsNotMissingAfterClear() + { + $this->resolver->setRequired('foo'); + $this->resolver->clear(); + $this->assertFalse($this->resolver->isRequired('foo')); + } + + public function testGetMissingOptions() + { + $this->resolver->setRequired(array('foo', 'bar')); + $this->resolver->setDefault('bam', 'baz'); + $this->resolver->setDefault('foo', 'boo'); + + $this->assertSame(array('bar'), $this->resolver->getMissingOptions()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetDefinedFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->setDefined('bar'); + }); + + $this->resolver->resolve(); + } + + public function testDefinedOptionsNotIncludedInResolvedOptions() + { + $this->resolver->setDefined('foo'); + + $this->assertSame(array(), $this->resolver->resolve()); + } + + public function testDefinedOptionsIncludedIfDefaultSetBefore() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setDefined('foo'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testDefinedOptionsIncludedIfDefaultSetAfter() + { + $this->resolver->setDefined('foo'); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testDefinedOptionsIncludedIfPassedToResolve() + { + $this->resolver->setDefined('foo'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve(array('foo' => 'bar'))); + } + + public function testIsDefined() + { + $this->assertFalse($this->resolver->isDefined('foo')); + $this->resolver->setDefined('foo'); + $this->assertTrue($this->resolver->isDefined('foo')); + } + + public function testLazyOptionsAreDefined() + { + $this->assertFalse($this->resolver->isDefined('foo')); + $this->resolver->setDefault('foo', function (Options $options) {}); + $this->assertTrue($this->resolver->isDefined('foo')); + } + + public function testRequiredOptionsAreDefined() + { + $this->assertFalse($this->resolver->isDefined('foo')); + $this->resolver->setRequired('foo'); + $this->assertTrue($this->resolver->isDefined('foo')); + } + + public function testSetOptionsAreDefined() + { + $this->assertFalse($this->resolver->isDefined('foo')); + $this->resolver->setDefault('foo', 'bar'); + $this->assertTrue($this->resolver->isDefined('foo')); + } + + public function testGetDefinedOptions() + { + $this->resolver->setDefined(array('foo', 'bar')); + $this->resolver->setDefault('baz', 'bam'); + $this->resolver->setRequired('boo'); + + $this->assertSame(array('foo', 'bar', 'baz', 'boo'), $this->resolver->getDefinedOptions()); + } + + public function testRemovedOptionsAreNotDefined() + { + $this->assertFalse($this->resolver->isDefined('foo')); + $this->resolver->setDefined('foo'); + $this->assertTrue($this->resolver->isDefined('foo')); + $this->resolver->remove('foo'); + $this->assertFalse($this->resolver->isDefined('foo')); + } + + public function testClearedOptionsAreNotDefined() + { + $this->assertFalse($this->resolver->isDefined('foo')); + $this->resolver->setDefined('foo'); + $this->assertTrue($this->resolver->isDefined('foo')); + $this->resolver->clear(); + $this->assertFalse($this->resolver->isDefined('foo')); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetDeprecatedFromLazyOption() + { + $this->resolver + ->setDefault('bar', 'baz') + ->setDefault('foo', function (Options $options) { + $options->setDeprecated('bar'); + }) + ->resolve() + ; + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + */ + public function testSetDeprecatedFailsIfUnknownOption() + { + $this->resolver->setDeprecated('foo'); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid type for deprecation message argument, expected string or \Closure, but got "boolean". + */ + public function testSetDeprecatedFailsIfInvalidDeprecationMessageType() + { + $this->resolver + ->setDefined('foo') + ->setDeprecated('foo', true) + ; + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid type for deprecation message, expected string but got "boolean", return an empty string to ignore. + */ + public function testLazyDeprecationFailsIfInvalidDeprecationMessageType() + { + $this->resolver + ->setDefined('foo') + ->setDeprecated('foo', function (Options $options, $value) { + return false; + }) + ; + $this->resolver->resolve(array('foo' => null)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException + * @expectedExceptionMessage The options "foo", "bar" have a cyclic dependency. + */ + public function testFailsIfCyclicDependencyBetweenDeprecation() + { + $this->resolver + ->setDefined(array('foo', 'bar')) + ->setDeprecated('foo', function (Options $options, $value) { + $options['bar']; + }) + ->setDeprecated('bar', function (Options $options, $value) { + $options['foo']; + }) + ; + $this->resolver->resolve(array('foo' => null, 'bar' => null)); + } + + public function testIsDeprecated() + { + $this->resolver + ->setDefined('foo') + ->setDeprecated('foo') + ; + $this->assertTrue($this->resolver->isDeprecated('foo')); + } + + public function testIsNotDeprecatedIfEmptyString() + { + $this->resolver + ->setDefined('foo') + ->setDeprecated('foo', '') + ; + $this->assertFalse($this->resolver->isDeprecated('foo')); + } + + /** + * @dataProvider provideDeprecationData + */ + public function testDeprecationMessages(\Closure $configureOptions, array $options, ?array $expectedError, int $expectedCount) + { + $count = 0; + error_clear_last(); + set_error_handler(function () use (&$count) { + ++$count; + + return false; + }); + $e = error_reporting(0); + + $configureOptions($this->resolver); + $this->resolver->resolve($options); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $this->assertSame($expectedError, $lastError); + $this->assertSame($expectedCount, $count); + } + + public function provideDeprecationData() + { + yield 'It deprecates an option with default message' => array( + function (OptionsResolver $resolver) { + $resolver + ->setDefined(array('foo', 'bar')) + ->setDeprecated('foo') + ; + }, + array('foo' => 'baz'), + array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The option "foo" is deprecated.', + ), + 1, + ); + + yield 'It deprecates an option with custom message' => array( + function (OptionsResolver $resolver) { + $resolver + ->setDefined('foo') + ->setDefault('bar', function (Options $options) { + return $options['foo']; + }) + ->setDeprecated('foo', 'The option "foo" is deprecated, use "bar" option instead.') + ; + }, + array('foo' => 'baz'), + array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The option "foo" is deprecated, use "bar" option instead.', + ), + 2, + ); + + yield 'It deprecates an option evaluated in another definition' => array( + function (OptionsResolver $resolver) { + // defined by superclass + $resolver + ->setDefault('foo', null) + ->setDeprecated('foo') + ; + // defined by subclass + $resolver->setDefault('bar', function (Options $options) { + return $options['foo']; // It triggers a deprecation + }); + }, + array(), + array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The option "foo" is deprecated.', + ), + 1, + ); + + yield 'It deprecates allowed type and value' => array( + function (OptionsResolver $resolver) { + $resolver + ->setDefault('foo', null) + ->setAllowedTypes('foo', array('null', 'string', \stdClass::class)) + ->setDeprecated('foo', function (Options $options, $value) { + if ($value instanceof \stdClass) { + return sprintf('Passing an instance of "%s" to option "foo" is deprecated, pass its FQCN instead.', \stdClass::class); + } + + return ''; + }) + ; + }, + array('foo' => new \stdClass()), + array( + 'type' => E_USER_DEPRECATED, + 'message' => 'Passing an instance of "stdClass" to option "foo" is deprecated, pass its FQCN instead.', + ), + 1, + ); + + yield 'It triggers a deprecation based on the value only if option is provided by the user' => array( + function (OptionsResolver $resolver) { + $resolver + ->setDefined('foo') + ->setAllowedTypes('foo', array('null', 'bool')) + ->setDeprecated('foo', function (Options $options, $value) { + if (!\is_bool($value)) { + return 'Passing a value different than true or false is deprecated.'; + } + + return ''; + }) + ->setDefault('baz', null) + ->setAllowedTypes('baz', array('null', 'int')) + ->setDeprecated('baz', function (Options $options, $value) { + if (!\is_int($value)) { + return 'Not passing an integer is deprecated.'; + } + + return ''; + }) + ->setDefault('bar', function (Options $options) { + $options['baz']; // It does not triggers a deprecation + + return $options['foo']; // It does not triggers a deprecation + }) + ; + }, + array('foo' => null), // It triggers a deprecation + array( + 'type' => E_USER_DEPRECATED, + 'message' => 'Passing a value different than true or false is deprecated.', + ), + 1, + ); + + yield 'It ignores a deprecation if closure returns an empty string' => array( + function (OptionsResolver $resolver) { + $resolver + ->setDefault('foo', null) + ->setDeprecated('foo', function (Options $options, $value) { + return ''; + }) + ; + }, + array('foo' => Bar::class), + null, + 0, + ); + + yield 'It deprecates value depending on other option value' => array( + function (OptionsResolver $resolver) { + $resolver + ->setDefault('widget', null) + ->setDefault('date_format', null) + ->setDeprecated('date_format', function (Options $options, $dateFormat) { + if (null !== $dateFormat && 'single_text' === $options['widget']) { + return 'Using the "date_format" option when the "widget" option is set to "single_text" is deprecated.'; + } + + return ''; + }) + ; + }, + array('widget' => 'single_text', 'date_format' => 2), + array( + 'type' => E_USER_DEPRECATED, + 'message' => 'Using the "date_format" option when the "widget" option is set to "single_text" is deprecated.', + ), + 1, + ); + + yield 'It triggers a deprecation for each evaluation' => array( + function (OptionsResolver $resolver) { + $resolver + // defined by superclass + ->setDefined('foo') + ->setDeprecated('foo') + // defined by subclass + ->setDefault('bar', function (Options $options) { + return $options['foo']; // It triggers a deprecation + }) + ->setNormalizer('bar', function (Options $options, $value) { + $options['foo']; // It triggers a deprecation + $options['foo']; // It triggers a deprecation + + return $value; + }) + ; + }, + array('foo' => 'baz'), // It triggers a deprecation + array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The option "foo" is deprecated.', + ), + 4, + ); + + yield 'It ignores a deprecation if no option is provided by the user' => array( + function (OptionsResolver $resolver) { + $resolver + ->setDefined('foo') + ->setDefault('bar', null) + ->setDeprecated('foo') + ->setDeprecated('bar') + ; + }, + array(), + null, + 0, + ); + + yield 'It explicitly ignores a depreciation' => array( + function (OptionsResolver $resolver) { + $resolver + ->setDefault('baz', function (Options $options) { + return $options->offsetGet('foo', false); + }) + ->setDefault('foo', null) + ->setDeprecated('foo') + ->setDefault('bar', function (Options $options) { + return $options->offsetGet('foo', false); + }) + ; + }, + array(), + null, + 0, + ); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + */ + public function testSetAllowedTypesFailsIfUnknownOption() + { + $this->resolver->setAllowedTypes('foo', 'string'); + } + + public function testResolveTypedArray() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'string[]'); + $options = $this->resolver->resolve(array('foo' => array('bar', 'baz'))); + + $this->assertSame(array('foo' => array('bar', 'baz')), $options); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetAllowedTypesFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->setAllowedTypes('bar', 'string'); + }); + + $this->resolver->setDefault('bar', 'baz'); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "DateTime[]". + */ + public function testResolveFailsIfInvalidTypedArray() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'int[]'); + + $this->resolver->resolve(array('foo' => array(new \DateTime()))); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value "bar" is expected to be of type "int[]", but is of type "string". + */ + public function testResolveFailsWithNonArray() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'int[]'); + + $this->resolver->resolve(array('foo' => 'bar')); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "stdClass[]". + */ + public function testResolveFailsIfTypedArrayContainsInvalidTypes() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'int[]'); + $values = range(1, 5); + $values[] = new \stdClass(); + $values[] = array(); + $values[] = new \DateTime(); + $values[] = 123; + + $this->resolver->resolve(array('foo' => $values)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "double[][]". + */ + public function testResolveFailsWithCorrectLevelsButWrongScalar() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'int[][]'); + + $this->resolver->resolve( + array( + 'foo' => array( + array(1.2), + ), + ) + ); + } + + /** + * @dataProvider provideInvalidTypes + */ + public function testResolveFailsIfInvalidType($actualType, $allowedType, $exceptionMessage) + { + $this->resolver->setDefined('option'); + $this->resolver->setAllowedTypes('option', $allowedType); + + if (method_exists($this, 'expectException')) { + $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); + $this->expectExceptionMessage($exceptionMessage); + } else { + $this->setExpectedException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException', $exceptionMessage); + } + + $this->resolver->resolve(array('option' => $actualType)); + } + + public function provideInvalidTypes() + { + return array( + array(true, 'string', 'The option "option" with value true is expected to be of type "string", but is of type "boolean".'), + array(false, 'string', 'The option "option" with value false is expected to be of type "string", but is of type "boolean".'), + array(fopen(__FILE__, 'r'), 'string', 'The option "option" with value resource is expected to be of type "string", but is of type "resource".'), + array(array(), 'string', 'The option "option" with value array is expected to be of type "string", but is of type "array".'), + array(new OptionsResolver(), 'string', 'The option "option" with value Symfony\Component\OptionsResolver\OptionsResolver is expected to be of type "string", but is of type "Symfony\Component\OptionsResolver\OptionsResolver".'), + array(42, 'string', 'The option "option" with value 42 is expected to be of type "string", but is of type "integer".'), + array(null, 'string', 'The option "option" with value null is expected to be of type "string", but is of type "NULL".'), + array('bar', '\stdClass', 'The option "option" with value "bar" is expected to be of type "\stdClass", but is of type "string".'), + ); + } + + public function testResolveSucceedsIfValidType() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedTypes('foo', 'string'); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value 42 is expected to be of type "string" or "bool", but is of type "integer". + */ + public function testResolveFailsIfInvalidTypeMultiple() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->setAllowedTypes('foo', array('string', 'bool')); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfValidTypeMultiple() + { + $this->resolver->setDefault('foo', true); + $this->resolver->setAllowedTypes('foo', array('string', 'bool')); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + public function testResolveSucceedsIfInstanceOfClass() + { + $this->resolver->setDefault('foo', new \stdClass()); + $this->resolver->setAllowedTypes('foo', '\stdClass'); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + public function testResolveSucceedsIfTypedArray() + { + $this->resolver->setDefault('foo', null); + $this->resolver->setAllowedTypes('foo', array('null', 'DateTime[]')); + + $data = array( + 'foo' => array( + new \DateTime(), + new \DateTime(), + ), + ); + $result = $this->resolver->resolve($data); + $this->assertEquals($data, $result); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfNotInstanceOfClass() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedTypes('foo', '\stdClass'); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + */ + public function testAddAllowedTypesFailsIfUnknownOption() + { + $this->resolver->addAllowedTypes('foo', 'string'); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfAddAllowedTypesFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->addAllowedTypes('bar', 'string'); + }); + + $this->resolver->setDefault('bar', 'baz'); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfInvalidAddedType() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->addAllowedTypes('foo', 'string'); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfValidAddedType() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->addAllowedTypes('foo', 'string'); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfInvalidAddedTypeMultiple() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->addAllowedTypes('foo', array('string', 'bool')); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfValidAddedTypeMultiple() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->addAllowedTypes('foo', array('string', 'bool')); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + public function testAddAllowedTypesDoesNotOverwrite() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedTypes('foo', 'string'); + $this->resolver->addAllowedTypes('foo', 'bool'); + + $this->resolver->setDefault('foo', 'bar'); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + public function testAddAllowedTypesDoesNotOverwrite2() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedTypes('foo', 'string'); + $this->resolver->addAllowedTypes('foo', 'bool'); + + $this->resolver->setDefault('foo', false); + + $this->assertNotEmpty($this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + */ + public function testSetAllowedValuesFailsIfUnknownOption() + { + $this->resolver->setAllowedValues('foo', 'bar'); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetAllowedValuesFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->setAllowedValues('bar', 'baz'); + }); + + $this->resolver->setDefault('bar', 'baz'); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value 42 is invalid. Accepted values are: "bar". + */ + public function testResolveFailsIfInvalidValue() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedValues('foo', 'bar'); + + $this->resolver->resolve(array('foo' => 42)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value null is invalid. Accepted values are: "bar". + */ + public function testResolveFailsIfInvalidValueIsNull() + { + $this->resolver->setDefault('foo', null); + $this->resolver->setAllowedValues('foo', 'bar'); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfInvalidValueStrict() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->setAllowedValues('foo', '42'); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfValidValue() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', 'bar'); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testResolveSucceedsIfValidValueIsNull() + { + $this->resolver->setDefault('foo', null); + $this->resolver->setAllowedValues('foo', null); + + $this->assertEquals(array('foo' => null), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value 42 is invalid. Accepted values are: "bar", false, null. + */ + public function testResolveFailsIfInvalidValueMultiple() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->setAllowedValues('foo', array('bar', false, null)); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfValidValueMultiple() + { + $this->resolver->setDefault('foo', 'baz'); + $this->resolver->setAllowedValues('foo', array('bar', 'baz')); + + $this->assertEquals(array('foo' => 'baz'), $this->resolver->resolve()); + } + + public function testResolveFailsIfClosureReturnsFalse() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->setAllowedValues('foo', function ($value) use (&$passedValue) { + $passedValue = $value; + + return false; + }); + + try { + $this->resolver->resolve(); + $this->fail('Should fail'); + } catch (InvalidOptionsException $e) { + } + + $this->assertSame(42, $passedValue); + } + + public function testResolveSucceedsIfClosureReturnsTrue() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', function ($value) use (&$passedValue) { + $passedValue = $value; + + return true; + }); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + $this->assertSame('bar', $passedValue); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfAllClosuresReturnFalse() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->setAllowedValues('foo', array( + function () { return false; }, + function () { return false; }, + function () { return false; }, + )); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfAnyClosureReturnsTrue() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', array( + function () { return false; }, + function () { return true; }, + function () { return false; }, + )); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + */ + public function testAddAllowedValuesFailsIfUnknownOption() + { + $this->resolver->addAllowedValues('foo', 'bar'); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfAddAllowedValuesFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->addAllowedValues('bar', 'baz'); + }); + + $this->resolver->setDefault('bar', 'baz'); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfInvalidAddedValue() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->addAllowedValues('foo', 'bar'); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfValidAddedValue() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->addAllowedValues('foo', 'bar'); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testResolveSucceedsIfValidAddedValueIsNull() + { + $this->resolver->setDefault('foo', null); + $this->resolver->addAllowedValues('foo', null); + + $this->assertEquals(array('foo' => null), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfInvalidAddedValueMultiple() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->addAllowedValues('foo', array('bar', 'baz')); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfValidAddedValueMultiple() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->addAllowedValues('foo', array('bar', 'baz')); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testAddAllowedValuesDoesNotOverwrite() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', 'bar'); + $this->resolver->addAllowedValues('foo', 'baz'); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testAddAllowedValuesDoesNotOverwrite2() + { + $this->resolver->setDefault('foo', 'baz'); + $this->resolver->setAllowedValues('foo', 'bar'); + $this->resolver->addAllowedValues('foo', 'baz'); + + $this->assertEquals(array('foo' => 'baz'), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testResolveFailsIfAllAddedClosuresReturnFalse() + { + $this->resolver->setDefault('foo', 42); + $this->resolver->setAllowedValues('foo', function () { return false; }); + $this->resolver->addAllowedValues('foo', function () { return false; }); + + $this->resolver->resolve(); + } + + public function testResolveSucceedsIfAnyAddedClosureReturnsTrue() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', function () { return false; }); + $this->resolver->addAllowedValues('foo', function () { return true; }); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testResolveSucceedsIfAnyAddedClosureReturnsTrue2() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', function () { return true; }); + $this->resolver->addAllowedValues('foo', function () { return false; }); + + $this->assertEquals(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testSetNormalizerReturnsThis() + { + $this->resolver->setDefault('foo', 'bar'); + $this->assertSame($this->resolver, $this->resolver->setNormalizer('foo', function () {})); + } + + public function testSetNormalizerClosure() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setNormalizer('foo', function () { + return 'normalized'; + }); + + $this->assertEquals(array('foo' => 'normalized'), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + */ + public function testSetNormalizerFailsIfUnknownOption() + { + $this->resolver->setNormalizer('foo', function () {}); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetNormalizerFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->setNormalizer('foo', function () {}); + }); + + $this->resolver->setDefault('bar', 'baz'); + + $this->resolver->resolve(); + } + + public function testNormalizerReceivesSetOption() + { + $this->resolver->setDefault('foo', 'bar'); + + $this->resolver->setNormalizer('foo', function (Options $options, $value) { + return 'normalized['.$value.']'; + }); + + $this->assertEquals(array('foo' => 'normalized[bar]'), $this->resolver->resolve()); + } + + public function testNormalizerReceivesPassedOption() + { + $this->resolver->setDefault('foo', 'bar'); + + $this->resolver->setNormalizer('foo', function (Options $options, $value) { + return 'normalized['.$value.']'; + }); + + $resolved = $this->resolver->resolve(array('foo' => 'baz')); + + $this->assertEquals(array('foo' => 'normalized[baz]'), $resolved); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testValidateTypeBeforeNormalization() + { + $this->resolver->setDefault('foo', 'bar'); + + $this->resolver->setAllowedTypes('foo', 'int'); + + $this->resolver->setNormalizer('foo', function () { + Assert::fail('Should not be called.'); + }); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + */ + public function testValidateValueBeforeNormalization() + { + $this->resolver->setDefault('foo', 'bar'); + + $this->resolver->setAllowedValues('foo', 'baz'); + + $this->resolver->setNormalizer('foo', function () { + Assert::fail('Should not be called.'); + }); + + $this->resolver->resolve(); + } + + public function testNormalizerCanAccessOtherOptions() + { + $this->resolver->setDefault('default', 'bar'); + $this->resolver->setDefault('norm', 'baz'); + + $this->resolver->setNormalizer('norm', function (Options $options) { + /* @var TestCase $test */ + Assert::assertSame('bar', $options['default']); + + return 'normalized'; + }); + + $this->assertEquals(array( + 'default' => 'bar', + 'norm' => 'normalized', + ), $this->resolver->resolve()); + } + + public function testNormalizerCanAccessLazyOptions() + { + $this->resolver->setDefault('lazy', function (Options $options) { + return 'bar'; + }); + $this->resolver->setDefault('norm', 'baz'); + + $this->resolver->setNormalizer('norm', function (Options $options) { + /* @var TestCase $test */ + Assert::assertEquals('bar', $options['lazy']); + + return 'normalized'; + }); + + $this->assertEquals(array( + 'lazy' => 'bar', + 'norm' => 'normalized', + ), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException + */ + public function testFailIfCyclicDependencyBetweenNormalizers() + { + $this->resolver->setDefault('norm1', 'bar'); + $this->resolver->setDefault('norm2', 'baz'); + + $this->resolver->setNormalizer('norm1', function (Options $options) { + $options['norm2']; + }); + + $this->resolver->setNormalizer('norm2', function (Options $options) { + $options['norm1']; + }); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException + */ + public function testFailIfCyclicDependencyBetweenNormalizerAndLazyOption() + { + $this->resolver->setDefault('lazy', function (Options $options) { + $options['norm']; + }); + + $this->resolver->setDefault('norm', 'baz'); + + $this->resolver->setNormalizer('norm', function (Options $options) { + $options['lazy']; + }); + + $this->resolver->resolve(); + } + + public function testCaughtExceptionFromNormalizerDoesNotCrashOptionResolver() + { + $throw = true; + + $this->resolver->setDefaults(array('catcher' => null, 'thrower' => null)); + + $this->resolver->setNormalizer('catcher', function (Options $options) { + try { + return $options['thrower']; + } catch (\Exception $e) { + return false; + } + }); + + $this->resolver->setNormalizer('thrower', function () use (&$throw) { + if ($throw) { + $throw = false; + throw new \UnexpectedValueException('throwing'); + } + + return true; + }); + + $this->assertSame(array('catcher' => false, 'thrower' => true), $this->resolver->resolve()); + } + + public function testCaughtExceptionFromLazyDoesNotCrashOptionResolver() + { + $throw = true; + + $this->resolver->setDefault('catcher', function (Options $options) { + try { + return $options['thrower']; + } catch (\Exception $e) { + return false; + } + }); + + $this->resolver->setDefault('thrower', function (Options $options) use (&$throw) { + if ($throw) { + $throw = false; + throw new \UnexpectedValueException('throwing'); + } + + return true; + }); + + $this->assertSame(array('catcher' => false, 'thrower' => true), $this->resolver->resolve()); + } + + public function testInvokeEachNormalizerOnlyOnce() + { + $calls = 0; + + $this->resolver->setDefault('norm1', 'bar'); + $this->resolver->setDefault('norm2', 'baz'); + + $this->resolver->setNormalizer('norm1', function ($options) use (&$calls) { + Assert::assertSame(1, ++$calls); + + $options['norm2']; + }); + $this->resolver->setNormalizer('norm2', function () use (&$calls) { + Assert::assertSame(2, ++$calls); + }); + + $this->resolver->resolve(); + + $this->assertSame(2, $calls); + } + + public function testNormalizerNotCalledForUnsetOptions() + { + $this->resolver->setDefined('norm'); + + $this->resolver->setNormalizer('norm', function () { + Assert::fail('Should not be called.'); + }); + + $this->assertEmpty($this->resolver->resolve()); + } + + public function testSetDefaultsReturnsThis() + { + $this->assertSame($this->resolver, $this->resolver->setDefaults(array('foo', 'bar'))); + } + + public function testSetDefaults() + { + $this->resolver->setDefault('one', '1'); + $this->resolver->setDefault('two', 'bar'); + + $this->resolver->setDefaults(array( + 'two' => '2', + 'three' => '3', + )); + + $this->assertEquals(array( + 'one' => '1', + 'two' => '2', + 'three' => '3', + ), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfSetDefaultsFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->setDefaults(array('two' => '2')); + }); + + $this->resolver->resolve(); + } + + public function testRemoveReturnsThis() + { + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame($this->resolver, $this->resolver->remove('foo')); + } + + public function testRemoveSingleOption() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setDefault('baz', 'boo'); + $this->resolver->remove('foo'); + + $this->assertSame(array('baz' => 'boo'), $this->resolver->resolve()); + } + + public function testRemoveMultipleOptions() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setDefault('baz', 'boo'); + $this->resolver->setDefault('doo', 'dam'); + + $this->resolver->remove(array('foo', 'doo')); + + $this->assertSame(array('baz' => 'boo'), $this->resolver->resolve()); + } + + public function testRemoveLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + return 'lazy'; + }); + $this->resolver->remove('foo'); + + $this->assertSame(array(), $this->resolver->resolve()); + } + + public function testRemoveNormalizer() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setNormalizer('foo', function (Options $options, $value) { + return 'normalized'; + }); + $this->resolver->remove('foo'); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testRemoveAllowedTypes() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedTypes('foo', 'int'); + $this->resolver->remove('foo'); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testRemoveAllowedValues() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', array('baz', 'boo')); + $this->resolver->remove('foo'); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfRemoveFromLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->remove('bar'); + }); + + $this->resolver->setDefault('bar', 'baz'); + + $this->resolver->resolve(); + } + + public function testRemoveUnknownOptionIgnored() + { + $this->assertNotNull($this->resolver->remove('foo')); + } + + public function testClearReturnsThis() + { + $this->assertSame($this->resolver, $this->resolver->clear()); + } + + public function testClearRemovesAllOptions() + { + $this->resolver->setDefault('one', 1); + $this->resolver->setDefault('two', 2); + + $this->resolver->clear(); + + $this->assertEmpty($this->resolver->resolve()); + } + + public function testClearLazyOption() + { + $this->resolver->setDefault('foo', function (Options $options) { + return 'lazy'; + }); + $this->resolver->clear(); + + $this->assertSame(array(), $this->resolver->resolve()); + } + + public function testClearNormalizer() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setNormalizer('foo', function (Options $options, $value) { + return 'normalized'; + }); + $this->resolver->clear(); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testClearAllowedTypes() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedTypes('foo', 'int'); + $this->resolver->clear(); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testClearAllowedValues() + { + $this->resolver->setDefault('foo', 'bar'); + $this->resolver->setAllowedValues('foo', 'baz'); + $this->resolver->clear(); + $this->resolver->setDefault('foo', 'bar'); + + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testFailIfClearFromLazyption() + { + $this->resolver->setDefault('foo', function (Options $options) { + $options->clear(); + }); + + $this->resolver->setDefault('bar', 'baz'); + + $this->resolver->resolve(); + } + + public function testClearOptionAndNormalizer() + { + $this->resolver->setDefault('foo1', 'bar'); + $this->resolver->setNormalizer('foo1', function (Options $options) { + return ''; + }); + $this->resolver->setDefault('foo2', 'bar'); + $this->resolver->setNormalizer('foo2', function (Options $options) { + return ''; + }); + + $this->resolver->clear(); + $this->assertEmpty($this->resolver->resolve()); + } + + public function testArrayAccess() + { + $this->resolver->setDefault('default1', 0); + $this->resolver->setDefault('default2', 1); + $this->resolver->setRequired('required'); + $this->resolver->setDefined('defined'); + $this->resolver->setDefault('lazy1', function (Options $options) { + return 'lazy'; + }); + + $this->resolver->setDefault('lazy2', function (Options $options) { + Assert::assertArrayHasKey('default1', $options); + Assert::assertArrayHasKey('default2', $options); + Assert::assertArrayHasKey('required', $options); + Assert::assertArrayHasKey('lazy1', $options); + Assert::assertArrayHasKey('lazy2', $options); + Assert::assertArrayNotHasKey('defined', $options); + + Assert::assertSame(0, $options['default1']); + Assert::assertSame(42, $options['default2']); + Assert::assertSame('value', $options['required']); + Assert::assertSame('lazy', $options['lazy1']); + + // Obviously $options['lazy'] and $options['defined'] cannot be + // accessed + }); + + $this->resolver->resolve(array('default2' => 42, 'required' => 'value')); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testArrayAccessGetFailsOutsideResolve() + { + $this->resolver->setDefault('default', 0); + + $this->resolver['default']; + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testArrayAccessExistsFailsOutsideResolve() + { + $this->resolver->setDefault('default', 0); + + isset($this->resolver['default']); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testArrayAccessSetNotSupported() + { + $this->resolver['default'] = 0; + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testArrayAccessUnsetNotSupported() + { + $this->resolver->setDefault('default', 0); + + unset($this->resolver['default']); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoSuchOptionException + * @expectedExceptionMessage The option "undefined" does not exist. Defined options are: "foo", "lazy". + */ + public function testFailIfGetNonExisting() + { + $this->resolver->setDefault('foo', 'bar'); + + $this->resolver->setDefault('lazy', function (Options $options) { + $options['undefined']; + }); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\NoSuchOptionException + * @expectedExceptionMessage The optional option "defined" has no value set. You should make sure it is set with "isset" before reading it. + */ + public function testFailIfGetDefinedButUnset() + { + $this->resolver->setDefined('defined'); + + $this->resolver->setDefault('lazy', function (Options $options) { + $options['defined']; + }); + + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException + */ + public function testFailIfCyclicDependency() + { + $this->resolver->setDefault('lazy1', function (Options $options) { + $options['lazy2']; + }); + + $this->resolver->setDefault('lazy2', function (Options $options) { + $options['lazy1']; + }); + + $this->resolver->resolve(); + } + + public function testCount() + { + $this->resolver->setDefault('default', 0); + $this->resolver->setRequired('required'); + $this->resolver->setDefined('defined'); + $this->resolver->setDefault('lazy1', function () {}); + + $this->resolver->setDefault('lazy2', function (Options $options) { + Assert::assertCount(4, $options); + }); + + $this->assertCount(4, $this->resolver->resolve(array('required' => 'value'))); + } + + /** + * In resolve() we count the options that are actually set (which may be + * only a subset of the defined options). Outside of resolve(), it's not + * clear what is counted. + * + * @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException + */ + public function testCountFailsOutsideResolve() + { + $this->resolver->setDefault('foo', 0); + $this->resolver->setRequired('bar'); + $this->resolver->setDefined('bar'); + $this->resolver->setDefault('lazy1', function () {}); + + \count($this->resolver); + } + + public function testNestedArrays() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'int[][]'); + + $this->assertEquals(array( + 'foo' => array( + array( + 1, 2, + ), + ), + ), $this->resolver->resolve( + array( + 'foo' => array( + array(1, 2), + ), + ) + )); + } + + public function testNested2Arrays() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'int[][][][]'); + + $this->assertEquals(array( + 'foo' => array( + array( + array( + array( + 1, 2, + ), + ), + ), + ), + ), $this->resolver->resolve( + array( + 'foo' => array( + array( + array( + array(1, 2), + ), + ), + ), + ) + )); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value array is expected to be of type "float[][][][]", but one of the elements is of type "integer[][][][]". + */ + public function testNestedArraysException() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'float[][][][]'); + + $this->resolver->resolve( + array( + 'foo' => array( + array( + array( + array(1, 2), + ), + ), + ), + ) + ); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean[][]". + */ + public function testNestedArrayException1() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'int[][]'); + $this->resolver->resolve(array( + 'foo' => array( + array(1, true, 'str', array(2, 3)), + ), + )); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean[][]". + */ + public function testNestedArrayException2() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'int[][]'); + $this->resolver->resolve(array( + 'foo' => array( + array(true, 'str', array(2, 3)), + ), + )); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "string[][]". + */ + public function testNestedArrayException3() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'string[][][]'); + $this->resolver->resolve(array( + 'foo' => array( + array('str', array(1, 2)), + ), + )); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "integer[][][]". + */ + public function testNestedArrayException4() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'string[][][]'); + $this->resolver->resolve(array( + 'foo' => array( + array( + array('str'), array(1, 2), ), + ), + )); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[]", but one of the elements is of type "array[]". + */ + public function testNestedArrayException5() + { + $this->resolver->setDefined('foo'); + $this->resolver->setAllowedTypes('foo', 'string[]'); + $this->resolver->resolve(array( + 'foo' => array( + array( + array('str'), array(1, 2), ), + ), + )); + } + + public function testIsNestedOption() + { + $this->resolver->setDefaults(array( + 'database' => function (OptionsResolver $resolver) { + $resolver->setDefined(array('host', 'port')); + }, + )); + $this->assertTrue($this->resolver->isNested('database')); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @expectedExceptionMessage The option "foo" does not exist. Defined options are: "host", "port". + */ + public function testFailsIfUndefinedNestedOption() + { + $this->resolver->setDefaults(array( + 'name' => 'default', + 'database' => function (OptionsResolver $resolver) { + $resolver->setDefined(array('host', 'port')); + }, + )); + $this->resolver->resolve(array( + 'database' => array('foo' => 'bar'), + )); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\MissingOptionsException + * @expectedExceptionMessage The required option "host" is missing. + */ + public function testFailsIfMissingRequiredNestedOption() + { + $this->resolver->setDefaults(array( + 'name' => 'default', + 'database' => function (OptionsResolver $resolver) { + $resolver->setRequired('host'); + }, + )); + $this->resolver->resolve(array( + 'database' => array(), + )); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The option "logging" with value null is expected to be of type "bool", but is of type "NULL". + */ + public function testFailsIfInvalidTypeNestedOption() + { + $this->resolver->setDefaults(array( + 'name' => 'default', + 'database' => function (OptionsResolver $resolver) { + $resolver + ->setDefined('logging') + ->setAllowedTypes('logging', 'bool'); + }, + )); + $this->resolver->resolve(array( + 'database' => array('logging' => null), + )); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The nested option "database" with value null is expected to be of type array, but is of type "NULL". + */ + public function testFailsIfNotArrayIsGivenForNestedOptions() + { + $this->resolver->setDefaults(array( + 'name' => 'default', + 'database' => function (OptionsResolver $resolver) { + $resolver->setDefined('host'); + }, + )); + $this->resolver->resolve(array( + 'database' => null, + )); + } + + public function testResolveNestedOptionsWithoutDefault() + { + $this->resolver->setDefaults(array( + 'name' => 'default', + 'database' => function (OptionsResolver $resolver) { + $resolver->setDefined(array('host', 'port')); + }, + )); + $actualOptions = $this->resolver->resolve(); + $expectedOptions = array( + 'name' => 'default', + 'database' => array(), + ); + $this->assertSame($expectedOptions, $actualOptions); + } + + public function testResolveNestedOptionsWithDefault() + { + $this->resolver->setDefaults(array( + 'name' => 'default', + 'database' => function (OptionsResolver $resolver) { + $resolver->setDefaults(array( + 'host' => 'localhost', + 'port' => 3306, + )); + }, + )); + $actualOptions = $this->resolver->resolve(); + $expectedOptions = array( + 'name' => 'default', + 'database' => array( + 'host' => 'localhost', + 'port' => 3306, + ), + ); + $this->assertSame($expectedOptions, $actualOptions); + } + + public function testResolveMultipleNestedOptions() + { + $this->resolver->setDefaults(array( + 'name' => 'default', + 'database' => function (OptionsResolver $resolver) { + $resolver + ->setRequired(array('dbname', 'host')) + ->setDefaults(array( + 'port' => 3306, + 'replicas' => function (OptionsResolver $resolver) { + $resolver->setDefaults(array( + 'host' => 'replica1', + 'port' => 3306, + )); + }, + )); + }, + )); + $actualOptions = $this->resolver->resolve(array( + 'name' => 'custom', + 'database' => array( + 'dbname' => 'test', + 'host' => 'localhost', + 'port' => null, + 'replicas' => array('host' => 'replica2'), + ), + )); + $expectedOptions = array( + 'name' => 'custom', + 'database' => array( + 'port' => null, + 'replicas' => array('port' => 3306, 'host' => 'replica2'), + 'dbname' => 'test', + 'host' => 'localhost', + ), + ); + $this->assertSame($expectedOptions, $actualOptions); + } + + public function testResolveLazyOptionUsingNestedOption() + { + $this->resolver->setDefaults(array( + 'version' => function (Options $options) { + return $options['database']['server_version']; + }, + 'database' => function (OptionsResolver $resolver) { + $resolver->setDefault('server_version', '3.15'); + }, + )); + $actualOptions = $this->resolver->resolve(); + $expectedOptions = array( + 'database' => array('server_version' => '3.15'), + 'version' => '3.15', + ); + $this->assertSame($expectedOptions, $actualOptions); + } + + public function testNormalizeNestedOptionValue() + { + $this->resolver + ->setDefaults(array( + 'database' => function (OptionsResolver $resolver) { + $resolver->setDefaults(array( + 'port' => 3306, + 'host' => 'localhost', + 'dbname' => 'demo', + )); + }, + )) + ->setNormalizer('database', function (Options $options, $value) { + ksort($value); + + return $value; + }); + $actualOptions = $this->resolver->resolve(array( + 'database' => array('dbname' => 'test'), + )); + $expectedOptions = array( + 'database' => array('dbname' => 'test', 'host' => 'localhost', 'port' => 3306), + ); + $this->assertSame($expectedOptions, $actualOptions); + } + + public function testOverwrittenNestedOptionNotEvaluatedIfLazyDefault() + { + // defined by superclass + $this->resolver->setDefault('foo', function (OptionsResolver $resolver) { + Assert::fail('Should not be called'); + }); + // defined by subclass + $this->resolver->setDefault('foo', function (Options $options) { + return 'lazy'; + }); + $this->assertSame(array('foo' => 'lazy'), $this->resolver->resolve()); + } + + public function testOverwrittenNestedOptionNotEvaluatedIfScalarDefault() + { + // defined by superclass + $this->resolver->setDefault('foo', function (OptionsResolver $resolver) { + Assert::fail('Should not be called'); + }); + // defined by subclass + $this->resolver->setDefault('foo', 'bar'); + $this->assertSame(array('foo' => 'bar'), $this->resolver->resolve()); + } + + public function testOverwrittenLazyOptionNotEvaluatedIfNestedOption() + { + // defined by superclass + $this->resolver->setDefault('foo', function (Options $options) { + Assert::fail('Should not be called'); + }); + // defined by subclass + $this->resolver->setDefault('foo', function (OptionsResolver $resolver) { + $resolver->setDefault('bar', 'baz'); + }); + $this->assertSame(array('foo' => array('bar' => 'baz')), $this->resolver->resolve()); + } + + public function testResolveAllNestedOptionDefinitions() + { + // defined by superclass + $this->resolver->setDefault('foo', function (OptionsResolver $resolver) { + $resolver->setRequired('bar'); + }); + // defined by subclass + $this->resolver->setDefault('foo', function (OptionsResolver $resolver) { + $resolver->setDefault('bar', 'baz'); + }); + // defined by subclass + $this->resolver->setDefault('foo', function (OptionsResolver $resolver) { + $resolver->setDefault('ping', 'pong'); + }); + $this->assertSame(array('foo' => array('ping' => 'pong', 'bar' => 'baz')), $this->resolver->resolve()); + } + + public function testNormalizeNestedValue() + { + // defined by superclass + $this->resolver->setDefault('foo', function (OptionsResolver $resolver) { + $resolver->setDefault('bar', null); + }); + // defined by subclass + $this->resolver->setNormalizer('foo', function (Options $options, $resolvedValue) { + if (null === $resolvedValue['bar']) { + $resolvedValue['bar'] = 'baz'; + } + + return $resolvedValue; + }); + $this->assertSame(array('foo' => array('bar' => 'baz')), $this->resolver->resolve()); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException + */ + public function testFailsIfCyclicDependencyBetweenSameNestedOption() + { + $this->resolver->setDefault('database', function (OptionsResolver $resolver, Options $parent) { + $resolver->setDefault('replicas', $parent['database']); + }); + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException + */ + public function testFailsIfCyclicDependencyBetweenNestedOptionAndParentLazyOption() + { + $this->resolver->setDefaults(array( + 'version' => function (Options $options) { + return $options['database']['server_version']; + }, + 'database' => function (OptionsResolver $resolver, Options $parent) { + $resolver->setDefault('server_version', $parent['version']); + }, + )); + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException + */ + public function testFailsIfCyclicDependencyBetweenNormalizerAndNestedOption() + { + $this->resolver + ->setDefault('name', 'default') + ->setDefault('database', function (OptionsResolver $resolver, Options $parent) { + $resolver->setDefault('host', $parent['name']); + }) + ->setNormalizer('name', function (Options $options, $value) { + $options['database']; + }); + $this->resolver->resolve(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException + */ + public function testFailsIfCyclicDependencyBetweenNestedOptions() + { + $this->resolver->setDefault('database', function (OptionsResolver $resolver, Options $parent) { + $resolver->setDefault('host', $parent['replica']['host']); + }); + $this->resolver->setDefault('replica', function (OptionsResolver $resolver, Options $parent) { + $resolver->setDefault('host', $parent['database']['host']); + }); + $this->resolver->resolve(); + } + + public function testGetAccessToParentOptionFromNestedOption() + { + $this->resolver->setDefaults(array( + 'version' => 3.15, + 'database' => function (OptionsResolver $resolver, Options $parent) { + $resolver->setDefault('server_version', $parent['version']); + }, + )); + $this->assertSame(array('version' => 3.15, 'database' => array('server_version' => 3.15)), $this->resolver->resolve()); + } + + public function testNestedClosureWithoutTypeHintNotInvoked() + { + $closure = function ($resolver) { + Assert::fail('Should not be called'); + }; + $this->resolver->setDefault('foo', $closure); + $this->assertSame(array('foo' => $closure), $this->resolver->resolve()); + } + + public function testNestedClosureWithoutTypeHint2ndArgumentNotInvoked() + { + $closure = function (OptionsResolver $resolver, $parent) { + Assert::fail('Should not be called'); + }; + $this->resolver->setDefault('foo', $closure); + $this->assertSame(array('foo' => $closure), $this->resolver->resolve()); + } + + public function testResolveLazyOptionWithTransitiveDefaultDependency() + { + $this->resolver->setDefaults(array( + 'ip' => null, + 'database' => function (OptionsResolver $resolver, Options $parent) { + $resolver->setDefault('host', $parent['ip']); + $resolver->setDefault('primary_replica', function (OptionsResolver $resolver, Options $parent) { + $resolver->setDefault('host', $parent['host']); + }); + }, + 'secondary_replica' => function (Options $options) { + return $options['database']['primary_replica']['host']; + }, + )); + $actualOptions = $this->resolver->resolve(array('ip' => '127.0.0.1')); + $expectedOptions = array( + 'ip' => '127.0.0.1', + 'database' => array( + 'host' => '127.0.0.1', + 'primary_replica' => array('host' => '127.0.0.1'), + ), + 'secondary_replica' => '127.0.0.1', + ); + $this->assertSame($expectedOptions, $actualOptions); + } + + public function testAccessToParentOptionFromNestedNormalizerAndLazyOption() + { + $this->resolver->setDefaults(array( + 'debug' => true, + 'database' => function (OptionsResolver $resolver, Options $parent) { + $resolver + ->setDefined('logging') + ->setDefault('profiling', function (Options $options) use ($parent) { + return $parent['debug']; + }) + ->setNormalizer('logging', function (Options $options, $value) use ($parent) { + return false === $parent['debug'] ? true : $value; + }); + }, + )); + $actualOptions = $this->resolver->resolve(array( + 'debug' => false, + 'database' => array('logging' => false), + )); + $expectedOptions = array( + 'debug' => false, + 'database' => array('profiling' => false, 'logging' => true), + ); + $this->assertSame($expectedOptions, $actualOptions); + } +} diff --git a/vendor/symfony/options-resolver/composer.json b/vendor/symfony/options-resolver/composer.json new file mode 100644 index 00000000..1c819495 --- /dev/null +++ b/vendor/symfony/options-resolver/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/options-resolver", + "type": "library", + "description": "Symfony OptionsResolver Component", + "keywords": ["options", "config", "configuration"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^7.1.3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\OptionsResolver\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + } +} diff --git a/vendor/symfony/options-resolver/phpunit.xml.dist b/vendor/symfony/options-resolver/phpunit.xml.dist new file mode 100644 index 00000000..9a2ec111 --- /dev/null +++ b/vendor/symfony/options-resolver/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + +