diff --git a/.travis.yml b/.travis.yml index 6518bd64..38328d45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ install: - composer install --ignore-platform-reqs php: - - 7 - 7.1 - 7.2 - hhvm diff --git a/README.md b/README.md index 681ec383..e6d9501a 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Update your anime/manga list on Kitsu.io and MyAnimeList.net ### Requirements -* PHP 7.0+ +* PHP 7.1+ * PDO SQLite or PDO PostgreSQL (For collection tab) * GD * Redis or Memcached for caching diff --git a/app/appConf/base_config.php b/app/appConf/base_config.php index b8b645ad..c58684aa 100644 --- a/app/appConf/base_config.php +++ b/app/appConf/base_config.php @@ -26,7 +26,7 @@ $ROOT_DIR = realpath("{$APP_DIR}/../"); $tomlConfig = loadToml(__DIR__); -$base_config = array_merge($tomlConfig, [ +return array_merge($tomlConfig, [ 'asset_dir' => "{$ROOT_DIR}/public", // Template file path diff --git a/app/appConf/routes.php b/app/appConf/routes.php index 08e32bd0..387c74c8 100644 --- a/app/appConf/routes.php +++ b/app/appConf/routes.php @@ -16,17 +16,14 @@ use const Aviat\AnimeClient\{ - DEFAULT_CONTROLLER_NAMESPACE, DEFAULT_CONTROLLER_METHOD, DEFAULT_CONTROLLER }; -use Aviat\AnimeClient\AnimeClient; - // ------------------------------------------------------------------------- // Routing Config // -// Maps paths to controlers and methods +// Maps paths to controllers and methods // ------------------------------------------------------------------------- return [ // --------------------------------------------------------------------- diff --git a/app/bootstrap.php b/app/bootstrap.php index 507f3a3b..1ee84cb5 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -36,7 +36,7 @@ use Zend\Diactoros\{Response, ServerRequestFactory}; // ----------------------------------------------------------------------------- // Setup DI container // ----------------------------------------------------------------------------- -return function(array $configArray = []) { +return function (array $configArray = []) { $container = new Container(); // ------------------------------------------------------------------------- @@ -49,7 +49,7 @@ return function(array $configArray = []) { $kitsuRequestLogger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/kitsu_request.log', Logger::NOTICE)); $malRequestLogger = new Logger('mal-request'); $malRequestLogger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/mal_request.log', Logger::NOTICE)); - $container->setLogger($appLogger, 'default'); + $container->setLogger($appLogger); $container->setLogger($kitsuRequestLogger, 'kitsu-request'); $container->setLogger($malRequestLogger, 'mal-request'); diff --git a/app/views/character.php b/app/views/character.php index 8b0dfb3c..e28b1795 100644 --- a/app/views/character.php +++ b/app/views/character.php @@ -1,5 +1,5 @@ -
+
" alt="" /> @@ -40,6 +40,8 @@
+ +

Manga

diff --git a/build/header_comment.txt b/build/header_comment.txt index b6e10e3d..5ca5be22 100644 --- a/build/header_comment.txt +++ b/build/header_comment.txt @@ -7,7 +7,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/composer.json b/composer.json index 4192c657..82279651 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,7 @@ "license": "MIT", "autoload": { "files": [ + "src/constants.php", "src/AnimeClient.php" ], "psr-4": { @@ -22,7 +23,7 @@ "aura/router": "^3.0", "aura/session": "^2.0", "aviat/banker": "^1.0.0", - "aviat/ion": "^2.2.0", + "aviat/ion": "^2.3.0", "maximebf/consolekit": "^1.0", "monolog/monolog": "^1.0", "psr/http-message": "~1.0", @@ -51,9 +52,16 @@ "build:css": "cd public && npm run build && cd ..", "clean": "vendor/bin/robo clean", "coverage": "phpdbg -qrr -- vendor/bin/phpunit -c build", - "docs": "vendor/bin/phpdox", - "phpstan": "phpstan analyse src tests", + "phpstan": "phpstan analyse -l 4 -c phpstan.neon src tests ./console index.php", "watch:css": "cd public && npm run watch", "test": "vendor/bin/phpunit" + }, + "scripts-descriptions": { + "build": "Generate the api docs", + "build:css": "Generate browser css", + "clean": "Remove documentation generation files and folders", + "coverage": "Generate a test coverage report", + "phpstan": "Run PHP Static analysis", + "test": "Run the unit tests" } } diff --git a/console b/console index 674a96e5..6869e202 100755 --- a/console +++ b/console @@ -2,30 +2,29 @@ Command\CachePrime::class, - 'cache-clear' => Command\CacheClear::class, - 'clear-cache' => Command\CacheClear::class, - 'sync-lists' => Command\SyncKitsuWithMal::class, -]); +try +{ + (new \ConsoleKit\Console([ + 'cache:clear' => Command\CacheClear::class, + 'cache:prime' => Command\CachePrime::class, + 'lists:sync' => Command\SyncKitsuWithMal::class, + 'cache-prime' => Command\CachePrime::class, + 'cache-clear' => Command\CacheClear::class, + 'clear-cache' => Command\CacheClear::class, + 'sync-lists' => Command\SyncKitsuWithMal::class, + ]))->run(); +} +catch (\Exception $e) +{ + +} -$console->run(); \ No newline at end of file diff --git a/index.php b/index.php index d59043cf..b13fc9ac 100644 --- a/index.php +++ b/index.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -40,7 +40,7 @@ $CONF_DIR = _dir($APP_DIR, 'config'); // ----------------------------------------------------------------------------- // Dependency Injection setup // ----------------------------------------------------------------------------- -require_once $APPCONF_DIR . '/base_config.php'; // $base_config +$base_config = require $APPCONF_DIR . '/base_config.php'; $di = require $APP_DIR . '/bootstrap.php'; $config = loadToml($CONF_DIR); diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..964eb301 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,9 @@ +parameters: + autoload_files: + - %rootDir%/../../../tests/mocks.php + ignoreErrors: + - '#Access to an undefined property Aviat\\\Ion\\\Friend::\$[a-zA-Z0-9_]+#' + - '#Call to an undefined method Aviat\\\Ion\\\Friend::[a-zA-Z0-9_]+\(\)#' + - '#Call to an undefined method Aura\\\Html\\\HelperLocator::[a-zA-Z0-9_]+\(\)#' + - '#Undefined variable: \$var#' + - '#Property Amp\\Artax\\Internal\\RequestCycle::\$[a-zA-Z0-9_]+#' diff --git a/src/API/APIRequestBuilder.php b/src/API/APIRequestBuilder.php index fa975b0c..3fb2a7b5 100644 --- a/src/API/APIRequestBuilder.php +++ b/src/API/APIRequestBuilder.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -55,7 +55,7 @@ class APIRequestBuilder { protected $defaultHeaders = []; /** - * Valid HTTP request methos + * Valid HTTP request methods * @var array */ protected $validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']; diff --git a/src/API/AbstractListItem.php b/src/API/AbstractListItem.php index 4dbc59d8..c4739e8c 100644 --- a/src/API/AbstractListItem.php +++ b/src/API/AbstractListItem.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/CacheTrait.php b/src/API/CacheTrait.php index 58933809..de3ff71a 100644 --- a/src/API/CacheTrait.php +++ b/src/API/CacheTrait.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -17,18 +17,17 @@ namespace Aviat\AnimeClient\API; use Aviat\Banker\Pool; -use Aviat\Ion\Di\ContainerAware; /** * Helper methods for dealing with the Cache */ trait CacheTrait { - + /** - * @var Aviat\Banker\Pool + * @var Pool */ protected $cache; - + /** * Inject the cache object * @@ -40,34 +39,32 @@ trait CacheTrait { $this->cache = $cache; return $this; } - + /** * Get the cache object if it exists * * @return Pool */ - public function getCache() + public function getCache(): Pool { return $this->cache; } - + /** * Generate a hash as a cache key from the current method call * - * @param object $object + * @param mixed $object * @param string $method * @param array $args * @return string */ public function getHashForMethodCall($object, string $method, array $args = []): string { - $classname = get_class($object); $keyObj = [ - 'class' => $classname, + 'class' => \get_class($object), 'method' => $method, 'args' => $args, ]; - $hash = sha1(json_encode($keyObj)); - return $hash; + return sha1(json_encode($keyObj)); } } \ No newline at end of file diff --git a/src/API/Enum/AnimeWatchingStatus/Kitsu.php b/src/API/Enum/AnimeWatchingStatus/Kitsu.php index b3447c29..db3afcca 100644 --- a/src/API/Enum/AnimeWatchingStatus/Kitsu.php +++ b/src/API/Enum/AnimeWatchingStatus/Kitsu.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/AnimeWatchingStatus/MAL.php b/src/API/Enum/AnimeWatchingStatus/MAL.php index 35209eaf..b9d17c53 100644 --- a/src/API/Enum/AnimeWatchingStatus/MAL.php +++ b/src/API/Enum/AnimeWatchingStatus/MAL.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/AnimeWatchingStatus/Route.php b/src/API/Enum/AnimeWatchingStatus/Route.php index 0c2b4a38..4f834c55 100644 --- a/src/API/Enum/AnimeWatchingStatus/Route.php +++ b/src/API/Enum/AnimeWatchingStatus/Route.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/AnimeWatchingStatus/Title.php b/src/API/Enum/AnimeWatchingStatus/Title.php index 5efdf4fb..ef3241cb 100644 --- a/src/API/Enum/AnimeWatchingStatus/Title.php +++ b/src/API/Enum/AnimeWatchingStatus/Title.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/MangaReadingStatus/Kitsu.php b/src/API/Enum/MangaReadingStatus/Kitsu.php index d8b7ccd0..2c5f3d74 100644 --- a/src/API/Enum/MangaReadingStatus/Kitsu.php +++ b/src/API/Enum/MangaReadingStatus/Kitsu.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/MangaReadingStatus/MAL.php b/src/API/Enum/MangaReadingStatus/MAL.php index 4da8b04a..9fe1f767 100644 --- a/src/API/Enum/MangaReadingStatus/MAL.php +++ b/src/API/Enum/MangaReadingStatus/MAL.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/MangaReadingStatus/Route.php b/src/API/Enum/MangaReadingStatus/Route.php index cc31f426..f33f7ad1 100644 --- a/src/API/Enum/MangaReadingStatus/Route.php +++ b/src/API/Enum/MangaReadingStatus/Route.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Enum/MangaReadingStatus/Title.php b/src/API/Enum/MangaReadingStatus/Title.php index c623c9c4..292da0da 100644 --- a/src/API/Enum/MangaReadingStatus/Title.php +++ b/src/API/Enum/MangaReadingStatus/Title.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/FailedResponseException.php b/src/API/FailedResponseException.php index 79047e84..b64ed202 100644 --- a/src/API/FailedResponseException.php +++ b/src/API/FailedResponseException.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/HummingbirdClient.php b/src/API/HummingbirdClient.php index 01b8b8f1..6f72e536 100644 --- a/src/API/HummingbirdClient.php +++ b/src/API/HummingbirdClient.php @@ -1,1155 +1,1184 @@ - true, - self::OP_TRANSFER_TIMEOUT => 15000, - self::OP_MAX_REDIRECTS => 5, - self::OP_AUTO_REFERER => true, - self::OP_DISCARD_BODY => false, - self::OP_DEFAULT_HEADERS => [], - self::OP_MAX_HEADER_BYTES => Parser::DEFAULT_MAX_HEADER_BYTES, - self::OP_MAX_BODY_BYTES => Parser::DEFAULT_MAX_BODY_BYTES, - ]; - - public function __construct( - CookieJar $cookieJar = null, - HttpSocketPool $socketPool = null, - ClientTlsContext $tlsContext = null - ) - { - $this->cookieJar = $cookieJar ?? new NullCookieJar; - $this->tlsContext = $tlsContext ?? new ClientTlsContext; - $this->socketPool = $socketPool ?? new HttpSocketPool; - $this->hasZlib = extension_loaded('zlib'); - } - - /** @inheritdoc */ - public function request($uriOrRequest, array $options = [], CancellationToken $cancellation = null): Promise - { - return call(function () use ($uriOrRequest, $options, $cancellation) { - $cancellation = $cancellation ?? new NullCancellationToken; - - foreach ($options as $option => $value) { - $this->validateOption($option, $value); - } - - /** @var Request $request */ - list($request, $uri) = $this->generateRequestFromUri($uriOrRequest); - $options = $options ? array_merge($this->options, $options) : $this->options; - - foreach ($this->options[self::OP_DEFAULT_HEADERS] as $name => $header) { - if (!$request->hasHeader($name)) { - $request = $request->withHeaders([$name => $header]); - } - } - - /** @var array $headers */ - $headers = yield $request->getBody()->getHeaders(); - foreach ($headers as $name => $header) { - if (!$request->hasHeader($name)) { - $request = $request->withHeaders([$name => $header]); - } - } - - $originalUri = $uri; - $previousResponse = null; - - $maxRedirects = $options[self::OP_MAX_REDIRECTS]; - $requestNr = 1; - - do { - /** @var Request $request */ - $request = yield from $this->normalizeRequestBodyHeaders($request); - $request = $this->normalizeRequestHeaders($request, $uri, $options); - - // Always normalize this as last item, because we need to strip sensitive headers - $request = $this->normalizeTraceRequest($request); - - /** @var Response $response */ - $response = yield $this->doRequest($request, $uri, $options, $previousResponse, $cancellation); - - // Explicit $maxRedirects !== 0 check to not consume redirect bodies if redirect following is disabled - if ($maxRedirects !== 0 && $redirectUri = $this->getRedirectUri($response)) { - // Discard response body of redirect responses - $body = $response->getBody(); - while (null !== yield $body->read()) ; - - /** - * If this is a 302/303 we need to follow the location with a GET if the original request wasn't - * GET. Otherwise we need to send the body again. - * - * We won't resend the body nor any headers on redirects to other hosts for security reasons. - * - * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.3 - */ - $method = $request->getMethod(); - $status = $response->getStatus(); - $isSameHost = $redirectUri->getAuthority(false) === $originalUri->getAuthority(false); - - if ($isSameHost) { - $request = $request->withUri($redirectUri); - - if ($status >= 300 && $status <= 303 && $method !== 'GET') { - $request = $request->withMethod('GET'); - $request = $request->withoutHeader('Transfer-Encoding'); - $request = $request->withoutHeader('Content-Length'); - $request = $request->withoutHeader('Content-Type'); - $request = $request->withBody(null); - } - } else { - // We ALWAYS follow with a GET and without any set headers or body for redirects to other hosts. - $optionsWithoutHeaders = $options; - unset($optionsWithoutHeaders[self::OP_DEFAULT_HEADERS]); - - $request = new Request((string)$redirectUri); - $request = $this->normalizeRequestHeaders($request, $redirectUri, $optionsWithoutHeaders); - } - - if ($options[self::OP_AUTO_REFERER]) { - $request = $this->assignRedirectRefererHeader($request, $originalUri, $redirectUri); - } - - $previousResponse = $response; - $originalUri = $redirectUri; - $uri = $redirectUri; - } else { - break; - } - } while (++$requestNr <= $maxRedirects + 1); - - if ($maxRedirects !== 0 && $redirectUri = $this->getRedirectUri($response)) { - throw new TooManyRedirectsException($response); - } - - return $response; - }); - } - - private function validateOption(string $option, $value) - { - switch ($option) { - case self::OP_AUTO_ENCODING: - if (!\is_bool($value)) { - throw new \TypeError("Invalid value for OP_AUTO_ENCODING, bool expected"); - } - - break; - - case self::OP_TRANSFER_TIMEOUT: - if (!\is_int($value) || $value < 0) { - throw new \Error("Invalid value for OP_TRANSFER_TIMEOUT, int >= 0 expected"); - } - - break; - - case self::OP_MAX_REDIRECTS: - if (!\is_int($value) || $value < 0) { - throw new \Error("Invalid value for OP_MAX_REDIRECTS, int >= 0 expected"); - } - - break; - - case self::OP_AUTO_REFERER: - if (!\is_bool($value)) { - throw new \TypeError("Invalid value for OP_AUTO_REFERER, bool expected"); - } - - break; - - case self::OP_DISCARD_BODY: - if (!\is_bool($value)) { - throw new \TypeError("Invalid value for OP_DISCARD_BODY, bool expected"); - } - - break; - - case self::OP_DEFAULT_HEADERS: - // We attempt to set the headers here, because they're automatically validated then. - (new Request("https://example.com/"))->withHeaders($value); - - break; - - case self::OP_MAX_HEADER_BYTES: - if (!\is_int($value) || $value < 0) { - throw new \Error("Invalid value for OP_MAX_HEADER_BYTES, int >= 0 expected"); - } - - break; - - case self::OP_MAX_BODY_BYTES: - if (!\is_int($value) || $value < 0) { - throw new \Error("Invalid value for OP_MAX_BODY_BYTES, int >= 0 expected"); - } - - break; - - default: - throw new \Error( - sprintf("Unknown option: %s", $option) - ); - } - } - - private function generateRequestFromUri($uriOrRequest) - { - if (is_string($uriOrRequest)) { - $uri = $this->buildUriFromString($uriOrRequest); - $request = new Request($uri); - } elseif ($uriOrRequest instanceof Request) { - $uri = $this->buildUriFromString($uriOrRequest->getUri()); - $request = $uriOrRequest; - } else { - throw new HttpException( - 'Request must be a valid HTTP URI or Amp\Artax\Request instance' - ); - } - - return [$request, $uri]; - } - - private function buildUriFromString($str): Uri - { - try { - $uri = new Uri($str); - $scheme = $uri->getScheme(); - - if (($scheme === "http" || $scheme === "https") && $uri->getHost()) { - return $uri; - } - - throw new HttpException("Request must specify a valid HTTP URI"); - } catch (InvalidUriException $e) { - throw new HttpException("Request must specify a valid HTTP URI", 0, $e); - } - } - - private function normalizeRequestBodyHeaders(Request $request): \Generator - { - if ($request->hasHeader("Transfer-Encoding")) { - return $request->withoutHeader("Content-Length"); - } - - if ($request->hasHeader("Content-Length")) { - return $request; - } - - /** @var RequestBody $body */ - $body = $request->getBody(); - $bodyLength = yield $body->getBodyLength(); - - if ($bodyLength === 0) { - $request = $request->withHeader('Content-Length', '0'); - $request = $request->withoutHeader('Transfer-Encoding'); - } else { - if ($bodyLength > 0) { - $request = $request->withHeader("Content-Length", $bodyLength); - $request = $request->withoutHeader("Transfer-Encoding"); - } else { - $request = $request->withHeader("Transfer-Encoding", "chunked"); - } - } - - return $request; - } - - private function normalizeRequestHeaders($request, $uri, $options) - { - $request = $this->normalizeRequestEncodingHeaderForZlib($request, $options); - $request = $this->normalizeRequestHostHeader($request, $uri); - $request = $this->normalizeRequestUserAgent($request); - $request = $this->normalizeRequestAcceptHeader($request); - $request = $this->assignApplicableRequestCookies($request); - - return $request; - } - - private function normalizeRequestEncodingHeaderForZlib(Request $request, array $options): Request - { - $autoEncoding = $options[self::OP_AUTO_ENCODING]; - - if (!$autoEncoding) { - return $request; - } - - if ($this->hasZlib) { - return $request->withHeader('Accept-Encoding', 'gzip, deflate, identity'); - } - - return $request->withoutHeader('Accept-Encoding'); - } - - private function normalizeRequestHostHeader(Request $request, Uri $uri): Request - { - if ($request->hasHeader('Host')) { - return $request; - } - - $authority = $this->generateAuthorityFromUri($uri); - $request = $request->withHeader('Host', $this->normalizeHostHeader($authority)); - - return $request; - } - - private function generateAuthorityFromUri(Uri $uri): string - { - $host = $uri->getHost(); - $port = $uri->getPort(); - - return "{$host}:{$port}"; - } - - private function normalizeHostHeader(string $host): string - { - // Though servers are supposed to be able to handle standard port names on the end of the - // Host header some fail to do this correctly. As a result, we strip the port from the end - // if it's a standard 80 or 443 - if (strpos($host, ':80') === strlen($host) - 3) { - return substr($host, 0, -3); - } elseif (strpos($host, ':443') === strlen($host) - 4) { - return substr($host, 0, -4); - } - - return $host; - } - - private function normalizeRequestUserAgent(Request $request): Request - { - if ($request->hasHeader('User-Agent')) { - return $request; - } - - return $request->withHeader('User-Agent', self::DEFAULT_USER_AGENT); - } - - private function normalizeRequestAcceptHeader(Request $request): Request - { - if ($request->hasHeader('Accept')) { - return $request; - } - - return $request->withHeader('Accept', '*/*'); - } - - private function assignApplicableRequestCookies(Request $request): Request - { - $uri = new Uri($request->getUri()); - - $domain = $uri->getHost(); - $path = $uri->getPath(); - - if (!$applicableCookies = $this->cookieJar->get($domain, $path)) { - // No cookies matched our request; we're finished. - return $request->withoutHeader("Cookie"); - } - - $isRequestSecure = strcasecmp($uri->getScheme(), "https") === 0; - $cookiePairs = []; - - /** @var Cookie $cookie */ - foreach ($applicableCookies as $cookie) { - if (!$cookie->isSecure() || $isRequestSecure) { - $cookiePairs[] = $cookie->getName() . "=" . $cookie->getValue(); - } - } - - if ($cookiePairs) { - return $request->withHeader("Cookie", \implode("; ", $cookiePairs)); - } - - return $request->withoutHeader("Cookie"); - } - - private function normalizeTraceRequest(Request $request): Request - { - $method = $request->getMethod(); - - if ($method !== 'TRACE') { - return $request; - } - - // https://tools.ietf.org/html/rfc7231#section-4.3.8 - /** @var Request $request */ - $request = $request->withBody(null); - - // Remove all body and sensitive headers - $request = $request->withHeaders([ - "Transfer-Encoding" => [], - "Content-Length" => [], - "Authorization" => [], - "Proxy-Authorization" => [], - "Cookie" => [], - ]); - - return $request; - } - - private function doRequest(Request $request, Uri $uri, array $options, Response $previousResponse = null, CancellationToken $cancellation): Promise - { - $deferred = new Deferred; - - $requestCycle = new RequestCycle; - $requestCycle->request = $request; - $requestCycle->uri = $uri; - $requestCycle->options = $options; - $requestCycle->previousResponse = $previousResponse; - $requestCycle->deferred = $deferred; - $requestCycle->bodyDeferred = new Deferred; - $requestCycle->body = new Emitter; - $requestCycle->cancellation = $cancellation; - - $protocolVersions = $request->getProtocolVersions(); - - if (\in_array("1.1", $protocolVersions, true)) { - $requestCycle->protocolVersion = "1.1"; - } elseif (\in_array("1.0", $protocolVersions, true)) { - $requestCycle->protocolVersion = "1.0"; - } else { - return new Failure(new HttpException( - "None of the requested protocol versions are supported: " . \implode(", ", $protocolVersions) - )); - } - - asyncCall(function () use ($requestCycle) { - try { - yield from $this->doWrite($requestCycle); - } catch (\Throwable $e) { - $this->fail($requestCycle, $e); - } - }); - - return $deferred->promise(); - } - - private function doWrite(RequestCycle $requestCycle) - { - $timeout = $requestCycle->options[self::OP_TRANSFER_TIMEOUT]; - $timeoutToken = new NullCancellationToken; - - if ($timeout > 0) { - $transferTimeoutWatcher = Loop::delay($timeout, function () use ($requestCycle, $timeout) { - $this->fail($requestCycle, new TimeoutException( - sprintf('Allowed transfer timeout exceeded: %d ms', $timeout) - )); - }); - - $requestCycle->bodyDeferred->promise()->onResolve(static function () use ($transferTimeoutWatcher) { - Loop::cancel($transferTimeoutWatcher); - }); - - $timeoutToken = new TimeoutCancellationToken($timeout); - } - - $authority = $this->generateAuthorityFromUri($requestCycle->uri); - $socketCheckoutUri = $requestCycle->uri->getScheme() . "://{$authority}"; - $connectTimeoutToken = new CombinedCancellationToken($requestCycle->cancellation, $timeoutToken); - - try { - /** @var ClientSocket $socket */ - $socket = yield $this->socketPool->checkout($socketCheckoutUri, $connectTimeoutToken); - $requestCycle->socket = $socket; - } catch (ResolutionException $dnsException) { - throw new DnsException(\sprintf("Resolving the specified domain failed: '%s'", $requestCycle->uri->getHost()), 0, $dnsException); - } catch (ConnectException $e) { - throw new SocketException(\sprintf("Connection to '%s' failed", $authority), 0, $e); - } catch (CancelledException $e) { - // In case of a user cancellation request, throw the expected exception - $requestCycle->cancellation->throwIfRequested(); - - // Otherwise we ran into a timeout of our TimeoutCancellationToken - throw new SocketException(\sprintf("Connection to '%s' timed out", $authority), 0, $e); - } - - $cancellation = $requestCycle->cancellation->subscribe(function ($error) use ($requestCycle) { - $this->fail($requestCycle, $error); - }); - - try { - if ($requestCycle->uri->getScheme() === 'https') { - $tlsContext = $this->tlsContext - ->withPeerName($requestCycle->uri->getHost()) - ->withPeerCapturing(); - - yield $socket->enableCrypto($tlsContext); - } - - // Collect this here, because it fails in case the remote closes the connection directly. - $connectionInfo = $this->collectConnectionInfo($socket); - - $rawHeaders = $this->generateRawRequestHeaders($requestCycle->request, $requestCycle->protocolVersion); - yield $socket->write($rawHeaders); - - $body = $requestCycle->request->getBody()->createBodyStream(); - $chunking = $requestCycle->request->getHeader("transfer-encoding") === "chunked"; - $remainingBytes = $requestCycle->request->getHeader("content-length"); - - if ($chunking && $requestCycle->protocolVersion === "1.0") { - throw new HttpException("Can't send chunked bodies over HTTP/1.0"); - } - - // We always buffer the last chunk to make sure we don't write $contentLength bytes if the body is too long. - $buffer = ""; - - while (null !== $chunk = yield $body->read()) { - $requestCycle->cancellation->throwIfRequested(); - - if ($chunk === "") { - continue; - } - - if ($chunking) { - $chunk = \dechex(\strlen($chunk)) . "\r\n" . $chunk . "\r\n"; - }/* elseif ($remainingBytes !== null) { - $remainingBytes -= \strlen($chunk); - - if ($remainingBytes < 0) { - throw new HttpException("Body contained more bytes than specified in Content-Length, aborting request"); - } - }*/ - - yield $socket->write($buffer); - $buffer = $chunk; - } - - // Flush last buffered chunk. - yield $socket->write($buffer); - - if ($chunking) { - yield $socket->write("0\r\n\r\n"); - }/* elseif ($remainingBytes !== null && $remainingBytes > 0) { - throw new HttpException("Body contained fewer bytes than specified in Content-Length, aborting request"); - }*/ - - yield from $this->doRead($requestCycle, $socket, $connectionInfo); - } finally { - $requestCycle->cancellation->unsubscribe($cancellation); - } - } - - private function fail(RequestCycle $requestCycle, \Throwable $error) - { - $toFails = []; - $socket = null; - - if ($requestCycle->deferred) { - $toFails[] = $requestCycle->deferred; - $requestCycle->deferred = null; - } - - if ($requestCycle->body) { - $toFails[] = $requestCycle->body; - $requestCycle->body = null; - } - - if ($requestCycle->bodyDeferred) { - $toFails[] = $requestCycle->bodyDeferred; - $requestCycle->bodyDeferred = null; - } - - if ($requestCycle->socket) { - $this->socketPool->clear($requestCycle->socket); - $socket = $requestCycle->socket; - $requestCycle->socket = null; - $socket->close(); - } - - foreach ($toFails as $toFail) { - $toFail->fail($error); - } - } - - private function collectConnectionInfo(ClientSocket $socket): ConnectionInfo - { - $crypto = \stream_get_meta_data($socket->getResource())["crypto"] ?? null; - - return new ConnectionInfo( - $socket->getLocalAddress(), - $socket->getRemoteAddress(), - $crypto ? TlsInfo::fromMetaData($crypto, \stream_context_get_options($socket->getResource())["ssl"]) : null - ); - } - - /** - * @param Request $request - * @param string $protocolVersion - * - * @return string - * - * @TODO Send absolute URIs in the request line when using a proxy server - * Right now this doesn't matter because all proxy requests use a CONNECT - * tunnel but this likely will not always be the case. - */ - private function generateRawRequestHeaders(Request $request, string $protocolVersion): string - { - $uri = $request->getUri(); - $uri = new Uri($uri); - - $requestUri = $uri->getPath() ?: '/'; - - if ($query = $uri->getQuery()) { - $requestUri .= '?' . $query; - } - - $head = $request->getMethod() . ' ' . $requestUri . ' HTTP/' . $protocolVersion . "\r\n"; - - $headers = $request->getHeaders(true); - /*$newHeaders = []; - - foreach($headers as $key => $val) - { - if ($key !== 'Content-Length') - { - $newHeaders[$key] = $val; - } - }*/ - - // Curse you Kitsu, for this stupid work-around because the login API endpoint doesn't allow for a Content-Length header! - //unset($headers['Content-Length']); - - foreach ($headers as $field => $values) { - if (\strcspn($field, "\r\n") !== \strlen($field)) { - throw new HttpException("Blocked header injection attempt for header '{$field}'"); - } - - foreach ($values as $value) { - if (\strcspn($value, "\r\n") !== \strlen($value)) { - throw new HttpException("Blocked header injection attempt for header '{$field}' with value '{$value}'"); - } - - $head .= "{$field}: {$value}\r\n"; - } - } - - $head .= "\r\n"; - - return $head; - } - - private function doRead(RequestCycle $requestCycle, ClientSocket $socket, ConnectionInfo $connectionInfo): \Generator - { - try { - $backpressure = new Success; - $bodyCallback = $requestCycle->options[self::OP_DISCARD_BODY] - ? null - : static function ($data) use ($requestCycle, &$backpressure) { - $backpressure = $requestCycle->body->emit($data); - }; - - $parser = new Parser($bodyCallback); - - $parser->enqueueResponseMethodMatch($requestCycle->request->getMethod()); - $parser->setAllOptions([ - Parser::OP_MAX_HEADER_BYTES => $requestCycle->options[self::OP_MAX_HEADER_BYTES], - Parser::OP_MAX_BODY_BYTES => $requestCycle->options[self::OP_MAX_BODY_BYTES], - ]); - - while (null !== $chunk = yield $socket->read()) { - $requestCycle->cancellation->throwIfRequested(); - - $parseResult = $parser->parse($chunk); - - if (!$parseResult) { - continue; - } - - $parseResult["headers"] = \array_change_key_case($parseResult["headers"], \CASE_LOWER); - - $response = $this->finalizeResponse($requestCycle, $parseResult, $connectionInfo); - $shouldCloseSocketAfterResponse = $this->shouldCloseSocketAfterResponse($response); - $ignoreIncompleteBodyCheck = false; - $responseHeaders = $response->getHeaders(); - - if ($requestCycle->deferred) { - $deferred = $requestCycle->deferred; - $requestCycle->deferred = null; - $deferred->resolve($response); - $response = null; // clear references - $deferred = null; // there's also a reference in the deferred - } else { - return; - } - - // Required, otherwise responses without body hang - if ($parseResult["headersOnly"]) { - // Directly parse again in case we already have the full body but aborted parsing - // to resolve promise with headers. - $chunk = null; - - do { - try { - $parseResult = $parser->parse($chunk); - } catch (ParseException $e) { - $this->fail($requestCycle, $e); - throw $e; - } - - if ($parseResult) { - break; - } - - if (!$backpressure instanceof Success) { - yield $this->withCancellation($backpressure, $requestCycle->cancellation); - } - - if ($requestCycle->bodyTooLarge) { - throw new HttpException("Response body exceeded the specified size limit"); - } - } while (null !== $chunk = yield $socket->read()); - - $parserState = $parser->getState(); - if ($parserState !== Parser::AWAITING_HEADERS) { - // Ignore check if neither content-length nor chunked encoding are given. - $ignoreIncompleteBodyCheck = $parserState === Parser::BODY_IDENTITY_EOF && - !isset($responseHeaders["content-length"]) && - strcasecmp('identity', $responseHeaders['transfer-encoding'][0] ?? ""); - - if (!$ignoreIncompleteBodyCheck) { - throw new SocketException(sprintf( - 'Socket disconnected prior to response completion (Parser state: %s)', - $parserState - )); - } - } - } - - if ($shouldCloseSocketAfterResponse || $ignoreIncompleteBodyCheck) { - $this->socketPool->clear($socket); - $socket->close(); - } else { - $this->socketPool->checkin($socket); - } - - $requestCycle->socket = null; - - // Complete body AFTER socket checkin, so the socket can be reused for a potential redirect - $body = $requestCycle->body; - $requestCycle->body = null; - - $bodyDeferred = $requestCycle->bodyDeferred; - $requestCycle->bodyDeferred = null; - - $body->complete(); - $bodyDeferred->resolve(); - - return; - } - } catch (\Throwable $e) { - $this->fail($requestCycle, $e); - - return; - } - - if ($socket->getResource() !== null) { - $requestCycle->socket = null; - $this->socketPool->clear($socket); - $socket->close(); - } - - // Required, because if the write fails, the read() call immediately resolves. - yield new Delayed(0); - - if ($requestCycle->deferred === null) { - return; - } - - $parserState = $parser->getState(); - - if ($parserState === Parser::AWAITING_HEADERS && $requestCycle->retryCount < 1) { - $requestCycle->retryCount++; - yield from $this->doWrite($requestCycle); - } else { - $this->fail($requestCycle, new SocketException(sprintf( - 'Socket disconnected prior to response completion (Parser state: %s)', - $parserState - ))); - } - } - - private function finalizeResponse(RequestCycle $requestCycle, array $parserResult, ConnectionInfo $connectionInfo) - { - $body = new IteratorStream($requestCycle->body->iterate()); - - if ($encoding = $this->determineCompressionEncoding($parserResult["headers"])) { - $body = new ZlibInputStream($body, $encoding); - } - - // Wrap the input stream so we can discard the body in case it's destructed but hasn't been consumed. - // This allows reusing the connection for further requests. It's important to have __destruct in InputStream and - // not in Message, because an InputStream might be pulled out of Message and used separately. - $body = new class($body, $requestCycle, $this->socketPool) implements InputStream - { - private $body; - private $bodySize = 0; - private $requestCycle; - private $socketPool; - private $successfulEnd = false; - - public function __construct(InputStream $body, RequestCycle $requestCycle, HttpSocketPool $socketPool) - { - $this->body = $body; - $this->requestCycle = $requestCycle; - $this->socketPool = $socketPool; - } - - public function read(): Promise - { - $promise = $this->body->read(); - $promise->onResolve(function ($error, $value) { - if ($value !== null) { - $this->bodySize += \strlen($value); - $maxBytes = $this->requestCycle->options[Client::OP_MAX_BODY_BYTES]; - if ($maxBytes !== 0 && $this->bodySize >= $maxBytes) { - $this->requestCycle->bodyTooLarge = true; - } - } elseif ($error === null) { - $this->successfulEnd = true; - } - }); - - return $promise; - } - - public function __destruct() - { - if (!$this->successfulEnd && $this->requestCycle->socket) { - $this->socketPool->clear($this->requestCycle->socket); - $socket = $this->requestCycle->socket; - $this->requestCycle->socket = null; - $socket->close(); - } - } - }; - - $response = new class($parserResult["protocol"], $parserResult["status"], $parserResult["reason"], $parserResult["headers"], $body, $requestCycle->request, $requestCycle->previousResponse, new MetaInfo($connectionInfo)) implements Response - { - private $protocolVersion; - private $status; - private $reason; - private $request; - private $previousResponse; - private $headers; - private $body; - private $metaInfo; - - public function __construct( - string $protocolVersion, - int $status, - string $reason, - array $headers, - InputStream $body, - Request $request, - Response $previousResponse = null, - MetaInfo $metaInfo - ) - { - $this->protocolVersion = $protocolVersion; - $this->status = $status; - $this->reason = $reason; - $this->headers = $headers; - $this->body = new Message($body); - $this->request = $request; - $this->previousResponse = $previousResponse; - $this->metaInfo = $metaInfo; - } - - public function getProtocolVersion(): string - { - return $this->protocolVersion; - } - - public function getStatus(): int - { - return $this->status; - } - - public function getReason(): string - { - return $this->reason; - } - - public function getRequest(): Request - { - return $this->request; - } - - public function getOriginalRequest(): Request - { - if (empty($this->previousResponse)) { - return $this->request; - } - - return $this->previousResponse->getOriginalRequest(); - } - - public function getPreviousResponse() - { - return $this->previousResponse; - } - - public function hasHeader(string $field): bool - { - return isset($this->headers[\strtolower($field)]); - } - - public function getHeader(string $field) - { - return $this->headers[\strtolower($field)][0] ?? null; - } - - public function getHeaderArray(string $field): array - { - return $this->headers[\strtolower($field)] ?? []; - } - - public function getHeaders(): array - { - return $this->headers; - } - - public function getBody(): Message - { - return $this->body; - } - - public function getMetaInfo(): MetaInfo - { - return $this->metaInfo; - } - }; - - if ($response->hasHeader('Set-Cookie')) { - $requestDomain = $requestCycle->uri->getHost(); - $cookies = $response->getHeaderArray('Set-Cookie'); - - foreach ($cookies as $rawCookieStr) { - $this->storeResponseCookie($requestDomain, $rawCookieStr); - } - } - - return $response; - } - - private function determineCompressionEncoding(array $responseHeaders): int - { - if (!$this->hasZlib) { - return 0; - } - - if (!isset($responseHeaders["content-encoding"])) { - return 0; - } - - $contentEncodingHeader = \trim(\current($responseHeaders["content-encoding"])); - - if (strcasecmp($contentEncodingHeader, 'gzip') === 0) { - return \ZLIB_ENCODING_GZIP; - } - - if (strcasecmp($contentEncodingHeader, 'deflate') === 0) { - return \ZLIB_ENCODING_DEFLATE; - } - - return 0; - } - - private function storeResponseCookie(string $requestDomain, string $rawCookieStr) - { - try { - $cookie = Cookie::fromString($rawCookieStr); - - if (!$cookie->getDomain()) { - $cookie = $cookie->withDomain($requestDomain); - } else { - // https://tools.ietf.org/html/rfc6265#section-4.1.2.3 - $cookieDomain = $cookie->getDomain(); - - // If a domain is set, left dots are ignored and it's always a wildcard - $cookieDomain = \ltrim($cookieDomain, "."); - - if ($cookieDomain !== $requestDomain) { - // ignore cookies on domains that are public suffixes - if (PublicSuffixList::isPublicSuffix($cookieDomain)) { - return; - } - - // cookie origin would not be included when sending the cookie - if (\substr($requestDomain, 0, -\strlen($cookieDomain) - 1) . "." . $cookieDomain !== $requestDomain) { - return; - } - } - - // always add the dot, it's used internally for wildcard matching when an explicit domain is sent - $cookie = $cookie->withDomain("." . $cookieDomain); - } - - $this->cookieJar->store($cookie); - } catch (CookieFormatException $e) { - // Ignore malformed Set-Cookie headers - } - } - - private function shouldCloseSocketAfterResponse(Response $response) - { - $request = $response->getRequest(); - - $requestConnHeader = $request->getHeader('Connection'); - $responseConnHeader = $response->getHeader('Connection'); - - if ($requestConnHeader && !strcasecmp($requestConnHeader, 'close')) { - return true; - } elseif ($responseConnHeader && !strcasecmp($responseConnHeader, 'close')) { - return true; - } elseif ($response->getProtocolVersion() === '1.0' && !$responseConnHeader) { - return true; - } - - return false; - } - - private function withCancellation(Promise $promise, CancellationToken $cancellationToken): Promise - { - $deferred = new Deferred; - $newPromise = $deferred->promise(); - - $promise->onResolve(function ($error, $value) use (&$deferred) { - if ($deferred) { - if ($error) { - $deferred->fail($error); - $deferred = null; - } else { - $deferred->resolve($value); - $deferred = null; - } - } - }); - - $cancellationSubscription = $cancellationToken->subscribe(function ($e) use (&$deferred) { - if ($deferred) { - $deferred->fail($e); - $deferred = null; - } - }); - - $newPromise->onResolve(function () use ($cancellationToken, $cancellationSubscription) { - $cancellationToken->unsubscribe($cancellationSubscription); - }); - - return $newPromise; - } - - private function getRedirectUri(Response $response) - { - if (!$response->hasHeader('Location')) { - return null; - } - - $request = $response->getRequest(); - - $status = $response->getStatus(); - $method = $request->getMethod(); - - if ($status < 300 || $status > 399 || $method === 'HEAD') { - return null; - } - - $requestUri = new Uri($request->getUri()); - $redirectLocation = $response->getHeader('Location'); - - try { - return $requestUri->resolve($redirectLocation); - } catch (InvalidUriException $e) { - return null; - } - } - - /** - * Clients must not add a Referer header when leaving an unencrypted resource and redirecting to an encrypted - * resource. - * - * @param Request $request - * @param string $refererUri - * @param string $newUri - * - * @return Request - * - * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3 - */ - private function assignRedirectRefererHeader(Request $request, string $refererUri, string $newUri): Request - { - $refererIsEncrypted = (\stripos($refererUri, 'https') === 0); - $destinationIsEncrypted = (\stripos($newUri, 'https') === 0); - - if (!$refererIsEncrypted || $destinationIsEncrypted) { - return $request->withHeader('Referer', $refererUri); - } - - return $request->withoutHeader('Referer'); - } - - /** - * Set multiple options at once. - * - * @param array $options An array of the form [OP_CONSTANT => $value] - * - * @throws \Error On unknown option key or invalid value. - */ - public function setOptions(array $options) - { - foreach ($options as $option => $value) { - $this->setOption($option, $value); - } - } - - /** - * Set an option. - * - * @param string $option A Client option constant - * @param mixed $value The option value to assign - * - * @throws \Error On unknown option key or invalid value. - */ - public function setOption(string $option, $value) - { - $this->validateOption($option, $value); - $this->options[$option] = $value; - } -} + + * @copyright 2015 - 2018 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 4.0 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\API; + +use Amp\{ + CancellationToken, + CancelledException, + Deferred, + Delayed, + Emitter, + Failure, + Loop, + NullCancellationToken, + Promise, + Success, + TimeoutCancellationToken +}; +use Amp\Artax\{ + ConnectionInfo, Client, DnsException, HttpException, HttpSocketPool, MetaInfo, ParseException, RequestBody, Response, Request, SocketException, TimeoutException, TlsInfo, TooManyRedirectsException +}; +use Amp\Artax\Cookie\{ + Cookie, + CookieFormatException, + CookieJar, + NullCookieJar +}; +use Amp\Artax\Internal\{ + CombinedCancellationToken, Parser, PublicSuffixList, RequestCycle +}; +use Amp\ByteStream\{ + InputStream, IteratorStream, Message, ZlibInputStream +}; +use Amp\Dns\ResolutionException; +use Amp\Socket\{ + ClientSocket, ClientTlsContext, ConnectException +}; +use Amp\Uri\{ + InvalidUriException, Uri +}; +use function Amp\{ + asyncCall, call +}; + +/** + * Standard client implementation. + * + * Use the `Client` interface for your type declarations so people can use composition to add layers like caching. + * + * @see Client + */ +final class HummingbirdClient implements Client { + const DEFAULT_USER_AGENT = 'Hummingbird Anime Client/5.0'; + + private $cookieJar; + private $socketPool; + private $tlsContext; + private $hasZlib; + private $options = [ + self::OP_AUTO_ENCODING => true, + self::OP_TRANSFER_TIMEOUT => 60000, + self::OP_MAX_REDIRECTS => 5, + self::OP_AUTO_REFERER => true, + self::OP_DISCARD_BODY => false, + self::OP_DEFAULT_HEADERS => [], + self::OP_MAX_HEADER_BYTES => Parser::DEFAULT_MAX_HEADER_BYTES, + self::OP_MAX_BODY_BYTES => Parser::DEFAULT_MAX_BODY_BYTES, + ]; + + public function __construct( + CookieJar $cookieJar = null, + HttpSocketPool $socketPool = null, + ClientTlsContext $tlsContext = null + ) + { + $this->cookieJar = $cookieJar ?? new NullCookieJar; + $this->tlsContext = $tlsContext ?? new ClientTlsContext; + $this->socketPool = $socketPool ?? new HttpSocketPool; + $this->hasZlib = \extension_loaded('zlib'); + } + + /** @inheritdoc */ + public function request($uriOrRequest, array $options = [], CancellationToken $cancellation = null): Promise + { + return call(function () use ($uriOrRequest, $options, $cancellation) { + $cancellation = $cancellation ?? new NullCancellationToken; + + foreach ($options as $option => $value) { + $this->validateOption($option, $value); + } + + /** @var Request $request */ + list($request, $uri) = $this->generateRequestFromUri($uriOrRequest); + $options = $options ? array_merge($this->options, $options) : $this->options; + + foreach ($this->options[self::OP_DEFAULT_HEADERS] as $name => $header) { + if (!$request->hasHeader($name)) { + $request = $request->withHeaders([$name => $header]); + } + } + + /** @var array $headers */ + $headers = yield $request->getBody()->getHeaders(); + foreach ($headers as $name => $header) { + if (!$request->hasHeader($name)) { + $request = $request->withHeaders([$name => $header]); + } + } + + $originalUri = $uri; + $previousResponse = null; + + $maxRedirects = $options[self::OP_MAX_REDIRECTS]; + $requestNr = 1; + + do { + /** @var Request $request */ + $request = yield from $this->normalizeRequestBodyHeaders($request); + $request = $this->normalizeRequestHeaders($request, $uri, $options); + + // Always normalize this as last item, because we need to strip sensitive headers + $request = $this->normalizeTraceRequest($request); + + /** @var Response $response */ + $response = yield $this->doRequest($request, $uri, $options, $previousResponse, $cancellation); + + // Explicit $maxRedirects !== 0 check to not consume redirect bodies if redirect following is disabled + if ($maxRedirects !== 0 && $redirectUri = $this->getRedirectUri($response)) { + // Discard response body of redirect responses + $body = $response->getBody(); + while (null !== yield $body->read()) ; + + /** + * If this is a 302/303 we need to follow the location with a GET if the original request wasn't + * GET. Otherwise we need to send the body again. + * + * We won't resend the body nor any headers on redirects to other hosts for security reasons. + * + * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.3 + */ + $method = $request->getMethod(); + $status = $response->getStatus(); + $isSameHost = $redirectUri->getAuthority(false) === $originalUri->getAuthority(false); + + if ($isSameHost) { + $request = $request->withUri($redirectUri); + + if ($status >= 300 && $status <= 303 && $method !== 'GET') { + $request = $request->withMethod('GET'); + $request = $request->withoutHeader('Transfer-Encoding'); + $request = $request->withoutHeader('Content-Length'); + $request = $request->withoutHeader('Content-Type'); + $request = $request->withBody(null); + } + } else { + // We ALWAYS follow with a GET and without any set headers or body for redirects to other hosts. + $optionsWithoutHeaders = $options; + unset($optionsWithoutHeaders[self::OP_DEFAULT_HEADERS]); + + $request = new Request((string)$redirectUri); + $request = $this->normalizeRequestHeaders($request, $redirectUri, $optionsWithoutHeaders); + } + + if ($options[self::OP_AUTO_REFERER]) { + $request = $this->assignRedirectRefererHeader($request, $originalUri, $redirectUri); + } + + $previousResponse = $response; + $originalUri = $redirectUri; + $uri = $redirectUri; + } else { + break; + } + } while (++$requestNr <= $maxRedirects + 1); + + if ($maxRedirects !== 0 && $redirectUri = $this->getRedirectUri($response)) { + throw new TooManyRedirectsException($response); + } + + return $response; + }); + } + + private function validateOption(string $option, $value) + { + switch ($option) { + case self::OP_AUTO_ENCODING: + if (!\is_bool($value)) { + throw new \TypeError("Invalid value for OP_AUTO_ENCODING, bool expected"); + } + + break; + + case self::OP_TRANSFER_TIMEOUT: + if (!\is_int($value) || $value < 0) { + throw new \Error("Invalid value for OP_TRANSFER_TIMEOUT, int >= 0 expected"); + } + + break; + + case self::OP_MAX_REDIRECTS: + if (!\is_int($value) || $value < 0) { + throw new \Error("Invalid value for OP_MAX_REDIRECTS, int >= 0 expected"); + } + + break; + + case self::OP_AUTO_REFERER: + if (!\is_bool($value)) { + throw new \TypeError("Invalid value for OP_AUTO_REFERER, bool expected"); + } + + break; + + case self::OP_DISCARD_BODY: + if (!\is_bool($value)) { + throw new \TypeError("Invalid value for OP_DISCARD_BODY, bool expected"); + } + + break; + + case self::OP_DEFAULT_HEADERS: + // We attempt to set the headers here, because they're automatically validated then. + (new Request("https://example.com/"))->withHeaders($value); + + break; + + case self::OP_MAX_HEADER_BYTES: + if (!\is_int($value) || $value < 0) { + throw new \Error("Invalid value for OP_MAX_HEADER_BYTES, int >= 0 expected"); + } + + break; + + case self::OP_MAX_BODY_BYTES: + if (!\is_int($value) || $value < 0) { + throw new \Error("Invalid value for OP_MAX_BODY_BYTES, int >= 0 expected"); + } + + break; + + default: + throw new \Error( + sprintf("Unknown option: %s", $option) + ); + } + } + + private function generateRequestFromUri($uriOrRequest) + { + if (is_string($uriOrRequest)) { + $uri = $this->buildUriFromString($uriOrRequest); + $request = new Request($uri); + } elseif ($uriOrRequest instanceof Request) { + $uri = $this->buildUriFromString($uriOrRequest->getUri()); + $request = $uriOrRequest; + } else { + throw new HttpException( + 'Request must be a valid HTTP URI or Amp\Artax\Request instance' + ); + } + + return [$request, $uri]; + } + + private function buildUriFromString($str): Uri + { + try { + $uri = new Uri($str); + $scheme = $uri->getScheme(); + + if (($scheme === "http" || $scheme === "https") && $uri->getHost()) { + return $uri; + } + + throw new HttpException("Request must specify a valid HTTP URI"); + } catch (InvalidUriException $e) { + throw new HttpException("Request must specify a valid HTTP URI", 0, $e); + } + } + + private function normalizeRequestBodyHeaders(Request $request): \Generator + { + if ($request->hasHeader("Transfer-Encoding")) { + return $request->withoutHeader("Content-Length"); + } + + if ($request->hasHeader("Content-Length")) { + return $request; + } + + /** @var RequestBody $body */ + $body = $request->getBody(); + $bodyLength = yield $body->getBodyLength(); + + if ($bodyLength === 0) { + $request = $request->withHeader('Content-Length', '0'); + $request = $request->withoutHeader('Transfer-Encoding'); + } else { + if ($bodyLength > 0) { + $request = $request->withHeader("Content-Length", $bodyLength); + $request = $request->withoutHeader("Transfer-Encoding"); + } else { + $request = $request->withHeader("Transfer-Encoding", "chunked"); + } + } + + return $request; + } + + private function normalizeRequestHeaders($request, $uri, $options) + { + $request = $this->normalizeRequestEncodingHeaderForZlib($request, $options); + $request = $this->normalizeRequestHostHeader($request, $uri); + $request = $this->normalizeRequestUserAgent($request); + $request = $this->normalizeRequestAcceptHeader($request); + $request = $this->assignApplicableRequestCookies($request); + + return $request; + } + + private function normalizeRequestEncodingHeaderForZlib(Request $request, array $options): Request + { + $autoEncoding = $options[self::OP_AUTO_ENCODING]; + + if (!$autoEncoding) { + return $request; + } + + if ($this->hasZlib) { + return $request->withHeader('Accept-Encoding', 'gzip, deflate, identity'); + } + + return $request->withoutHeader('Accept-Encoding'); + } + + private function normalizeRequestHostHeader(Request $request, Uri $uri): Request + { + if ($request->hasHeader('Host')) { + return $request; + } + + $authority = $this->generateAuthorityFromUri($uri); + $request = $request->withHeader('Host', $this->normalizeHostHeader($authority)); + + return $request; + } + + private function generateAuthorityFromUri(Uri $uri): string + { + $host = $uri->getHost(); + $port = $uri->getPort(); + + return "{$host}:{$port}"; + } + + private function normalizeHostHeader(string $host): string + { + // Though servers are supposed to be able to handle standard port names on the end of the + // Host header some fail to do this correctly. As a result, we strip the port from the end + // if it's a standard 80 or 443 + if (strpos($host, ':80') === strlen($host) - 3) { + return substr($host, 0, -3); + } elseif (strpos($host, ':443') === strlen($host) - 4) { + return substr($host, 0, -4); + } + + return $host; + } + + private function normalizeRequestUserAgent(Request $request): Request + { + if ($request->hasHeader('User-Agent')) { + return $request; + } + + return $request->withHeader('User-Agent', self::DEFAULT_USER_AGENT); + } + + private function normalizeRequestAcceptHeader(Request $request): Request + { + if ($request->hasHeader('Accept')) { + return $request; + } + + return $request->withHeader('Accept', '*/*'); + } + + private function assignApplicableRequestCookies(Request $request): Request + { + $uri = new Uri($request->getUri()); + + $domain = $uri->getHost(); + $path = $uri->getPath(); + + if (!$applicableCookies = $this->cookieJar->get($domain, $path)) { + // No cookies matched our request; we're finished. + return $request->withoutHeader("Cookie"); + } + + $isRequestSecure = strcasecmp($uri->getScheme(), "https") === 0; + $cookiePairs = []; + + /** @var Cookie $cookie */ + foreach ($applicableCookies as $cookie) { + if (!$cookie->isSecure() || $isRequestSecure) { + $cookiePairs[] = $cookie->getName() . "=" . $cookie->getValue(); + } + } + + if ($cookiePairs) { + return $request->withHeader("Cookie", \implode("; ", $cookiePairs)); + } + + return $request->withoutHeader("Cookie"); + } + + private function normalizeTraceRequest(Request $request): Request + { + $method = $request->getMethod(); + + if ($method !== 'TRACE') { + return $request; + } + + // https://tools.ietf.org/html/rfc7231#section-4.3.8 + /** @var Request $request */ + $request = $request->withBody(null); + + // Remove all body and sensitive headers + $request = $request->withHeaders([ + "Transfer-Encoding" => [], + "Content-Length" => [], + "Authorization" => [], + "Proxy-Authorization" => [], + "Cookie" => [], + ]); + + return $request; + } + + private function doRequest(Request $request, Uri $uri, array $options, Response $previousResponse = null, CancellationToken $cancellation): Promise + { + $deferred = new Deferred; + + $requestCycle = new RequestCycle; + $requestCycle->request = $request; + $requestCycle->uri = $uri; + $requestCycle->options = $options; + $requestCycle->previousResponse = $previousResponse; + $requestCycle->deferred = $deferred; + $requestCycle->bodyDeferred = new Deferred; + $requestCycle->body = new Emitter; + $requestCycle->cancellation = $cancellation; + + $protocolVersions = $request->getProtocolVersions(); + + if (\in_array("1.1", $protocolVersions, true)) { + $requestCycle->protocolVersion = "1.1"; + } elseif (\in_array("1.0", $protocolVersions, true)) { + $requestCycle->protocolVersion = "1.0"; + } else { + return new Failure(new HttpException( + "None of the requested protocol versions are supported: " . \implode(", ", $protocolVersions) + )); + } + + asyncCall(function () use ($requestCycle) { + try { + yield from $this->doWrite($requestCycle); + } catch (\Throwable $e) { + $this->fail($requestCycle, $e); + } + }); + + return $deferred->promise(); + } + + private function doWrite(RequestCycle $requestCycle) + { + $timeout = $requestCycle->options[self::OP_TRANSFER_TIMEOUT]; + $timeoutToken = new NullCancellationToken; + + if ($timeout > 0) { + $transferTimeoutWatcher = Loop::delay($timeout, function () use ($requestCycle, $timeout) { + $this->fail($requestCycle, new TimeoutException( + sprintf('Allowed transfer timeout exceeded: %d ms', $timeout) + )); + }); + + $requestCycle->bodyDeferred->promise()->onResolve(static function () use ($transferTimeoutWatcher) { + Loop::cancel($transferTimeoutWatcher); + }); + + $timeoutToken = new TimeoutCancellationToken($timeout); + } + + $authority = $this->generateAuthorityFromUri($requestCycle->uri); + $socketCheckoutUri = $requestCycle->uri->getScheme() . "://{$authority}"; + $connectTimeoutToken = new CombinedCancellationToken($requestCycle->cancellation, $timeoutToken); + + try { + /** @var ClientSocket $socket */ + $socket = yield $this->socketPool->checkout($socketCheckoutUri, $connectTimeoutToken); + $requestCycle->socket = $socket; + } catch (ResolutionException $dnsException) { + throw new DnsException(\sprintf("Resolving the specified domain failed: '%s'", $requestCycle->uri->getHost()), 0, $dnsException); + } catch (ConnectException $e) { + throw new SocketException(\sprintf("Connection to '%s' failed", $authority), 0, $e); + } catch (CancelledException $e) { + // In case of a user cancellation request, throw the expected exception + $requestCycle->cancellation->throwIfRequested(); + + // Otherwise we ran into a timeout of our TimeoutCancellationToken + throw new SocketException(\sprintf("Connection to '%s' timed out", $authority), 0, $e); + } + + $cancellation = $requestCycle->cancellation->subscribe(function ($error) use ($requestCycle) { + $this->fail($requestCycle, $error); + }); + + try { + if ($requestCycle->uri->getScheme() === 'https') { + $tlsContext = $this->tlsContext + ->withPeerName($requestCycle->uri->getHost()) + ->withPeerCapturing(); + + yield $socket->enableCrypto($tlsContext); + } + + // Collect this here, because it fails in case the remote closes the connection directly. + $connectionInfo = $this->collectConnectionInfo($socket); + + $rawHeaders = $this->generateRawRequestHeaders($requestCycle->request, $requestCycle->protocolVersion); + yield $socket->write($rawHeaders); + + $body = $requestCycle->request->getBody()->createBodyStream(); + $chunking = $requestCycle->request->getHeader("transfer-encoding") === "chunked"; + $remainingBytes = $requestCycle->request->getHeader("content-length"); + + if ($chunking && $requestCycle->protocolVersion === "1.0") { + throw new HttpException("Can't send chunked bodies over HTTP/1.0"); + } + + // We always buffer the last chunk to make sure we don't write $contentLength bytes if the body is too long. + $buffer = ""; + + while (null !== $chunk = yield $body->read()) { + $requestCycle->cancellation->throwIfRequested(); + + if ($chunk === "") { + continue; + } + + if ($chunking) { + $chunk = \dechex(\strlen($chunk)) . "\r\n" . $chunk . "\r\n"; + }/* elseif ($remainingBytes !== null) { + $remainingBytes -= \strlen($chunk); + + if ($remainingBytes < 0) { + throw new HttpException("Body contained more bytes than specified in Content-Length, aborting request"); + } + }*/ + + yield $socket->write($buffer); + $buffer = $chunk; + } + + // Flush last buffered chunk. + yield $socket->write($buffer); + + if ($chunking) { + yield $socket->write("0\r\n\r\n"); + }/* elseif ($remainingBytes !== null && $remainingBytes > 0) { + throw new HttpException("Body contained fewer bytes than specified in Content-Length, aborting request"); + }*/ + + yield from $this->doRead($requestCycle, $socket, $connectionInfo); + } finally { + $requestCycle->cancellation->unsubscribe($cancellation); + } + } + + private function fail(RequestCycle $requestCycle, \Throwable $error) + { + $toFails = []; + $socket = null; + + if ($requestCycle->deferred) { + $toFails[] = $requestCycle->deferred; + $requestCycle->deferred = null; + } + + if ($requestCycle->body) { + $toFails[] = $requestCycle->body; + $requestCycle->body = null; + } + + if ($requestCycle->bodyDeferred) { + $toFails[] = $requestCycle->bodyDeferred; + $requestCycle->bodyDeferred = null; + } + + if ($requestCycle->socket) { + $this->socketPool->clear($requestCycle->socket); + $socket = $requestCycle->socket; + $requestCycle->socket = null; + $socket->close(); + } + + foreach ($toFails as $toFail) { + $toFail->fail($error); + } + } + + private function collectConnectionInfo(ClientSocket $socket): ConnectionInfo + { + $crypto = \stream_get_meta_data($socket->getResource())["crypto"] ?? null; + + return new ConnectionInfo( + $socket->getLocalAddress(), + $socket->getRemoteAddress(), + $crypto ? TlsInfo::fromMetaData($crypto, \stream_context_get_options($socket->getResource())["ssl"]) : null + ); + } + + /** + * @param Request $request + * @param string $protocolVersion + * + * @return string + * + * @TODO Send absolute URIs in the request line when using a proxy server + * Right now this doesn't matter because all proxy requests use a CONNECT + * tunnel but this likely will not always be the case. + */ + private function generateRawRequestHeaders(Request $request, string $protocolVersion): string + { + $uri = $request->getUri(); + $uri = new Uri($uri); + + $requestUri = $uri->getPath() ?: '/'; + + if ($query = $uri->getQuery()) { + $requestUri .= '?' . $query; + } + + $head = $request->getMethod() . ' ' . $requestUri . ' HTTP/' . $protocolVersion . "\r\n"; + + $headers = $request->getHeaders(true); + /*$newHeaders = []; + + foreach($headers as $key => $val) + { + if ($key !== 'Content-Length') + { + $newHeaders[$key] = $val; + } + }*/ + + // Curse you Kitsu, for this stupid work-around because the login API endpoint doesn't allow for a Content-Length header! + //unset($headers['Content-Length']); + + foreach ($headers as $field => $values) { + if (\strcspn($field, "\r\n") !== \strlen($field)) { + throw new HttpException("Blocked header injection attempt for header '{$field}'"); + } + + foreach ($values as $value) { + if (\strcspn($value, "\r\n") !== \strlen($value)) { + throw new HttpException("Blocked header injection attempt for header '{$field}' with value '{$value}'"); + } + + $head .= "{$field}: {$value}\r\n"; + } + } + + $head .= "\r\n"; + + return $head; + } + + private function doRead(RequestCycle $requestCycle, ClientSocket $socket, ConnectionInfo $connectionInfo): \Generator + { + try { + $backpressure = new Success; + $bodyCallback = $requestCycle->options[self::OP_DISCARD_BODY] + ? null + : static function ($data) use ($requestCycle, &$backpressure) { + $backpressure = $requestCycle->body->emit($data); + }; + + $parser = new Parser($bodyCallback); + + $parser->enqueueResponseMethodMatch($requestCycle->request->getMethod()); + $parser->setAllOptions([ + Parser::OP_MAX_HEADER_BYTES => $requestCycle->options[self::OP_MAX_HEADER_BYTES], + Parser::OP_MAX_BODY_BYTES => $requestCycle->options[self::OP_MAX_BODY_BYTES], + ]); + + while (null !== $chunk = yield $socket->read()) { + $requestCycle->cancellation->throwIfRequested(); + + $parseResult = $parser->parse($chunk); + + if (!$parseResult) { + continue; + } + + $parseResult["headers"] = \array_change_key_case($parseResult["headers"], \CASE_LOWER); + + $response = $this->finalizeResponse($requestCycle, $parseResult, $connectionInfo); + $shouldCloseSocketAfterResponse = $this->shouldCloseSocketAfterResponse($response); + $ignoreIncompleteBodyCheck = false; + $responseHeaders = $response->getHeaders(); + + if ($requestCycle->deferred) { + $deferred = $requestCycle->deferred; + $requestCycle->deferred = null; + $deferred->resolve($response); + $response = null; // clear references + $deferred = null; // there's also a reference in the deferred + } else { + return; + } + + // Required, otherwise responses without body hang + if ($parseResult["headersOnly"]) { + // Directly parse again in case we already have the full body but aborted parsing + // to resolve promise with headers. + $chunk = null; + + do { + try { + $parseResult = $parser->parse($chunk); + } catch (ParseException $e) { + $this->fail($requestCycle, $e); + throw $e; + } + + if ($parseResult) { + break; + } + + if (!$backpressure instanceof Success) { + yield $this->withCancellation($backpressure, $requestCycle->cancellation); + } + + if ($requestCycle->bodyTooLarge) { + throw new HttpException("Response body exceeded the specified size limit"); + } + } while (null !== $chunk = yield $socket->read()); + + $parserState = $parser->getState(); + if ($parserState !== Parser::AWAITING_HEADERS) { + // Ignore check if neither content-length nor chunked encoding are given. + $ignoreIncompleteBodyCheck = $parserState === Parser::BODY_IDENTITY_EOF && + !isset($responseHeaders["content-length"]) && + strcasecmp('identity', $responseHeaders['transfer-encoding'][0] ?? ""); + + if (!$ignoreIncompleteBodyCheck) { + throw new SocketException(sprintf( + 'Socket disconnected prior to response completion (Parser state: %s)', + $parserState + )); + } + } + } + + if ($shouldCloseSocketAfterResponse || $ignoreIncompleteBodyCheck) { + $this->socketPool->clear($socket); + $socket->close(); + } else { + $this->socketPool->checkin($socket); + } + + $requestCycle->socket = null; + + // Complete body AFTER socket checkin, so the socket can be reused for a potential redirect + $body = $requestCycle->body; + $requestCycle->body = null; + + $bodyDeferred = $requestCycle->bodyDeferred; + $requestCycle->bodyDeferred = null; + + $body->complete(); + $bodyDeferred->resolve(); + + return; + } + } catch (\Throwable $e) { + $this->fail($requestCycle, $e); + + return; + } + + if ($socket->getResource() !== null) { + $requestCycle->socket = null; + $this->socketPool->clear($socket); + $socket->close(); + } + + // Required, because if the write fails, the read() call immediately resolves. + yield new Delayed(0); + + if ($requestCycle->deferred === null) { + return; + } + + $parserState = $parser->getState(); + + if ($parserState === Parser::AWAITING_HEADERS && $requestCycle->retryCount < 1) { + $requestCycle->retryCount++; + yield from $this->doWrite($requestCycle); + } else { + $this->fail($requestCycle, new SocketException(sprintf( + 'Socket disconnected prior to response completion (Parser state: %s)', + $parserState + ))); + } + } + + private function finalizeResponse(RequestCycle $requestCycle, array $parserResult, ConnectionInfo $connectionInfo) + { + $body = new IteratorStream($requestCycle->body->iterate()); + + if ($encoding = $this->determineCompressionEncoding($parserResult["headers"])) { + $body = new ZlibInputStream($body, $encoding); + } + + // Wrap the input stream so we can discard the body in case it's destructed but hasn't been consumed. + // This allows reusing the connection for further requests. It's important to have __destruct in InputStream and + // not in Message, because an InputStream might be pulled out of Message and used separately. + $body = new class($body, $requestCycle, $this->socketPool) implements InputStream + { + private $body; + private $bodySize = 0; + private $requestCycle; + private $socketPool; + private $successfulEnd = false; + + public function __construct(InputStream $body, RequestCycle $requestCycle, HttpSocketPool $socketPool) + { + $this->body = $body; + $this->requestCycle = $requestCycle; + $this->socketPool = $socketPool; + } + + public function read(): Promise + { + $promise = $this->body->read(); + $promise->onResolve(function ($error, $value) { + if ($value !== null) { + $this->bodySize += \strlen($value); + $maxBytes = $this->requestCycle->options[Client::OP_MAX_BODY_BYTES]; + if ($maxBytes !== 0 && $this->bodySize >= $maxBytes) { + $this->requestCycle->bodyTooLarge = true; + } + } elseif ($error === null) { + $this->successfulEnd = true; + } + }); + + return $promise; + } + + public function __destruct() + { + if (!$this->successfulEnd && $this->requestCycle->socket) { + $this->socketPool->clear($this->requestCycle->socket); + $socket = $this->requestCycle->socket; + $this->requestCycle->socket = null; + $socket->close(); + } + } + }; + + $response = new class($parserResult["protocol"], $parserResult["status"], $parserResult["reason"], $parserResult["headers"], $body, $requestCycle->request, $requestCycle->previousResponse, new MetaInfo($connectionInfo)) implements Response + { + private $protocolVersion; + private $status; + private $reason; + private $request; + private $previousResponse; + private $headers; + private $body; + private $metaInfo; + + public function __construct( + string $protocolVersion, + int $status, + string $reason, + array $headers, + InputStream $body, + Request $request, + Response $previousResponse = null, + MetaInfo $metaInfo + ) + { + $this->protocolVersion = $protocolVersion; + $this->status = $status; + $this->reason = $reason; + $this->headers = $headers; + $this->body = new Message($body); + $this->request = $request; + $this->previousResponse = $previousResponse; + $this->metaInfo = $metaInfo; + } + + public function getProtocolVersion(): string + { + return $this->protocolVersion; + } + + public function getStatus(): int + { + return $this->status; + } + + public function getReason(): string + { + return $this->reason; + } + + public function getRequest(): Request + { + return $this->request; + } + + public function getOriginalRequest(): Request + { + if (empty($this->previousResponse)) { + return $this->request; + } + + return $this->previousResponse->getOriginalRequest(); + } + + public function getPreviousResponse() + { + return $this->previousResponse; + } + + public function hasHeader(string $field): bool + { + return isset($this->headers[\strtolower($field)]); + } + + public function getHeader(string $field) + { + return $this->headers[\strtolower($field)][0] ?? null; + } + + public function getHeaderArray(string $field): array + { + return $this->headers[\strtolower($field)] ?? []; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function getBody(): Message + { + return $this->body; + } + + public function getMetaInfo(): MetaInfo + { + return $this->metaInfo; + } + }; + + if ($response->hasHeader('Set-Cookie')) { + $requestDomain = $requestCycle->uri->getHost(); + $cookies = $response->getHeaderArray('Set-Cookie'); + + foreach ($cookies as $rawCookieStr) { + $this->storeResponseCookie($requestDomain, $rawCookieStr); + } + } + + return $response; + } + + private function determineCompressionEncoding(array $responseHeaders): int + { + if (!$this->hasZlib) { + return 0; + } + + if (!isset($responseHeaders["content-encoding"])) { + return 0; + } + + $contentEncodingHeader = \trim(\current($responseHeaders["content-encoding"])); + + if (strcasecmp($contentEncodingHeader, 'gzip') === 0) { + return \ZLIB_ENCODING_GZIP; + } + + if (strcasecmp($contentEncodingHeader, 'deflate') === 0) { + return \ZLIB_ENCODING_DEFLATE; + } + + return 0; + } + + private function storeResponseCookie(string $requestDomain, string $rawCookieStr) + { + try { + $cookie = Cookie::fromString($rawCookieStr); + + if (!$cookie->getDomain()) { + $cookie = $cookie->withDomain($requestDomain); + } else { + // https://tools.ietf.org/html/rfc6265#section-4.1.2.3 + $cookieDomain = $cookie->getDomain(); + + // If a domain is set, left dots are ignored and it's always a wildcard + $cookieDomain = \ltrim($cookieDomain, "."); + + if ($cookieDomain !== $requestDomain) { + // ignore cookies on domains that are public suffixes + if (PublicSuffixList::isPublicSuffix($cookieDomain)) { + return; + } + + // cookie origin would not be included when sending the cookie + if (\substr($requestDomain, 0, -\strlen($cookieDomain) - 1) . "." . $cookieDomain !== $requestDomain) { + return; + } + } + + // always add the dot, it's used internally for wildcard matching when an explicit domain is sent + $cookie = $cookie->withDomain("." . $cookieDomain); + } + + $this->cookieJar->store($cookie); + } catch (CookieFormatException $e) { + // Ignore malformed Set-Cookie headers + } + } + + private function shouldCloseSocketAfterResponse(Response $response) + { + $request = $response->getRequest(); + + $requestConnHeader = $request->getHeader('Connection'); + $responseConnHeader = $response->getHeader('Connection'); + + if ($requestConnHeader && !strcasecmp($requestConnHeader, 'close')) { + return true; + } elseif ($responseConnHeader && !strcasecmp($responseConnHeader, 'close')) { + return true; + } elseif ($response->getProtocolVersion() === '1.0' && !$responseConnHeader) { + return true; + } + + return false; + } + + private function withCancellation(Promise $promise, CancellationToken $cancellationToken): Promise + { + $deferred = new Deferred; + $newPromise = $deferred->promise(); + + $promise->onResolve(function ($error, $value) use (&$deferred) { + if ($deferred) { + if ($error) { + $deferred->fail($error); + $deferred = null; + } else { + $deferred->resolve($value); + $deferred = null; + } + } + }); + + $cancellationSubscription = $cancellationToken->subscribe(function ($e) use (&$deferred) { + if ($deferred) { + $deferred->fail($e); + $deferred = null; + } + }); + + $newPromise->onResolve(function () use ($cancellationToken, $cancellationSubscription) { + $cancellationToken->unsubscribe($cancellationSubscription); + }); + + return $newPromise; + } + + private function getRedirectUri(Response $response) + { + if (!$response->hasHeader('Location')) { + return null; + } + + $request = $response->getRequest(); + + $status = $response->getStatus(); + $method = $request->getMethod(); + + if ($status < 300 || $status > 399 || $method === 'HEAD') { + return null; + } + + $requestUri = new Uri($request->getUri()); + $redirectLocation = $response->getHeader('Location'); + + try { + return $requestUri->resolve($redirectLocation); + } catch (InvalidUriException $e) { + return null; + } + } + + /** + * Clients must not add a Referer header when leaving an unencrypted resource and redirecting to an encrypted + * resource. + * + * @param Request $request + * @param string $refererUri + * @param string $newUri + * + * @return Request + * + * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3 + */ + private function assignRedirectRefererHeader(Request $request, string $refererUri, string $newUri): Request + { + $refererIsEncrypted = (\stripos($refererUri, 'https') === 0); + $destinationIsEncrypted = (\stripos($newUri, 'https') === 0); + + if (!$refererIsEncrypted || $destinationIsEncrypted) { + return $request->withHeader('Referer', $refererUri); + } + + return $request->withoutHeader('Referer'); + } + + /** + * Set multiple options at once. + * + * @param array $options An array of the form [OP_CONSTANT => $value] + * + * @throws \Error On unknown option key or invalid value. + */ + public function setOptions(array $options) + { + foreach ($options as $option => $value) { + $this->setOption($option, $value); + } + } + + /** + * Set an option. + * + * @param string $option A Client option constant + * @param mixed $value The option value to assign + * + * @throws \Error On unknown option key or invalid value. + */ + public function setOption(string $option, $value) + { + $this->validateOption($option, $value); + $this->options[$option] = $value; + } +} diff --git a/src/API/JsonAPI.php b/src/API/JsonAPI.php index 0bd1fe75..c90c4b85 100644 --- a/src/API/JsonAPI.php +++ b/src/API/JsonAPI.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -44,7 +44,7 @@ class JsonAPI { * Inline all included data * * @param array $data - The raw JsonAPI response data - * @return data + * @return array */ public static function organizeData(array $data): array { @@ -54,7 +54,7 @@ class JsonAPI { ]; // Reorganize included data - $included = (array_key_exists('included', $data)) + $included = array_key_exists('included', $data) ? static::organizeIncluded($data['included']) : []; @@ -313,7 +313,7 @@ class JsonAPI { foreach ($data['data'] as $item) { - if (is_array($item) && array_key_exists('id', $item)) + if (\is_array($item) && array_key_exists('id', $item)) { $organized[$key][] = $item['id']; } diff --git a/src/API/Kitsu.php b/src/API/Kitsu.php index bbebf196..9443fbe6 100644 --- a/src/API/Kitsu.php +++ b/src/API/Kitsu.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu/Auth.php b/src/API/Kitsu/Auth.php index 7ff53ad3..39a52362 100644 --- a/src/API/Kitsu/Auth.php +++ b/src/API/Kitsu/Auth.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -18,7 +18,6 @@ namespace Aviat\AnimeClient\API\Kitsu; use const Aviat\AnimeClient\SESSION_SEGMENT; -use Aviat\AnimeClient\AnimeClient; use Aviat\AnimeClient\API\{ CacheTrait, Kitsu as K diff --git a/src/API/Kitsu/Enum/AnimeAiringStatus.php b/src/API/Kitsu/Enum/AnimeAiringStatus.php index b658d7d2..93577a5d 100644 --- a/src/API/Kitsu/Enum/AnimeAiringStatus.php +++ b/src/API/Kitsu/Enum/AnimeAiringStatus.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu/KitsuRequestBuilder.php b/src/API/Kitsu/KitsuRequestBuilder.php index 46e7bf97..e36ee6f3 100644 --- a/src/API/Kitsu/KitsuRequestBuilder.php +++ b/src/API/Kitsu/KitsuRequestBuilder.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu/KitsuTrait.php b/src/API/Kitsu/KitsuTrait.php index 215c9bcc..efe3a930 100644 --- a/src/API/Kitsu/KitsuTrait.php +++ b/src/API/Kitsu/KitsuTrait.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -177,7 +177,7 @@ trait KitsuTrait { try { - return Json::decode(wait($response->getBody()), TRUE); + return Json::decode(wait($response->getBody())); } catch (JsonException $e) { @@ -226,7 +226,7 @@ trait KitsuTrait { $response = $this->getResponse('POST', ...$args); $validResponseCodes = [200, 201]; - if ( ! in_array((int) $response->getStatus(), $validResponseCodes)) + if ( ! \in_array((int) $response->getStatus(), $validResponseCodes, TRUE)) { if ($logger) { diff --git a/src/API/Kitsu/ListItem.php b/src/API/Kitsu/ListItem.php index 71bef41a..2f01be8a 100644 --- a/src/API/Kitsu/ListItem.php +++ b/src/API/Kitsu/ListItem.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu/Model.php b/src/API/Kitsu/Model.php index f25e9be2..3f43a782 100644 --- a/src/API/Kitsu/Model.php +++ b/src/API/Kitsu/Model.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -116,7 +116,7 @@ class Model { ] ]); $data = Json::decode(wait($response->getBody())); - + //dump($response); if (array_key_exists('access_token', $data)) @@ -763,7 +763,7 @@ class Model { * Get the mal id for the manga represented by the kitsu id * to enable updating MyAnimeList * - * @param string $kitsuAnimeId The id of the anime on Kitsu + * @param string $kitsuMangaId The id of the manga on Kitsu * @return string|null Returns the mal id if it exists, otherwise null */ public function getMalIdForManga(string $kitsuMangaId) diff --git a/src/API/Kitsu/Transformer/AnimeListTransformer.php b/src/API/Kitsu/Transformer/AnimeListTransformer.php index 5cd72d6e..cd692caf 100644 --- a/src/API/Kitsu/Transformer/AnimeListTransformer.php +++ b/src/API/Kitsu/Transformer/AnimeListTransformer.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -31,7 +31,7 @@ class AnimeListTransformer extends AbstractTransformer { * @param array $item API library item * @return array */ - public function transform($item) + public function transform($item): array { $included = $item['included']; $animeId = $item['relationships']['media']['data']['id']; @@ -41,7 +41,7 @@ class AnimeListTransformer extends AbstractTransformer { sort($genres); $rating = (int) $item['attributes']['rating'] !== 0 - ? (int) 2 * $item['attributes']['rating'] + ? 2 * $item['attributes']['rating'] : '-'; $total_episodes = array_key_exists('episodeCount', $anime) && (int) $anime['episodeCount'] !== 0 @@ -97,7 +97,7 @@ class AnimeListTransformer extends AbstractTransformer { 'rewatching' => (bool) $item['attributes']['reconsuming'], 'rewatched' => (int) $item['attributes']['reconsumeCount'], 'user_rating' => $rating, - 'private' => (bool) $item['attributes']['private'] ?? FALSE, + 'private' => $item['attributes']['private'] ?? FALSE, ]; } @@ -108,7 +108,7 @@ class AnimeListTransformer extends AbstractTransformer { * @param array $item Transformed library item * @return array API library item */ - public function untransform($item) + public function untransform($item): array { $privacy = (array_key_exists('private', $item) && $item['private']); $rewatching = (array_key_exists('rewatching', $item) && $item['rewatching']); diff --git a/src/API/Kitsu/Transformer/AnimeTransformer.php b/src/API/Kitsu/Transformer/AnimeTransformer.php index f50ac656..0b850f83 100644 --- a/src/API/Kitsu/Transformer/AnimeTransformer.php +++ b/src/API/Kitsu/Transformer/AnimeTransformer.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Kitsu/Transformer/MangaListTransformer.php b/src/API/Kitsu/Transformer/MangaListTransformer.php index 54106f63..b243bcac 100644 --- a/src/API/Kitsu/Transformer/MangaListTransformer.php +++ b/src/API/Kitsu/Transformer/MangaListTransformer.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -33,7 +33,7 @@ class MangaListTransformer extends AbstractTransformer { * @param array $item manga entry item * @return array */ - public function transform($item) + public function transform($item): array { $included = $item['included']; $mangaId = $item['relationships']['media']['data']['id']; @@ -43,7 +43,7 @@ class MangaListTransformer extends AbstractTransformer { sort($genres); $rating = (int) $item['attributes']['rating'] !== 0 - ? (int) 2 * $item['attributes']['rating'] + ? 2 * $item['attributes']['rating'] : '-'; $totalChapters = ((int) $manga['chapterCount'] !== 0) @@ -109,9 +109,9 @@ class MangaListTransformer extends AbstractTransformer { * @param array $item * @return array */ - public function untransform($item) + public function untransform($item): array { - $rereading = (array_key_exists('rereading', $item)) && (bool)$item['rereading']; + $rereading = array_key_exists('rereading', $item) && (bool)$item['rereading']; $map = [ 'id' => $item['id'], diff --git a/src/API/Kitsu/Transformer/MangaTransformer.php b/src/API/Kitsu/Transformer/MangaTransformer.php index 2b466c1b..53465603 100644 --- a/src/API/Kitsu/Transformer/MangaTransformer.php +++ b/src/API/Kitsu/Transformer/MangaTransformer.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/ListItemInterface.php b/src/API/ListItemInterface.php index 1ed294e2..bd252417 100644 --- a/src/API/ListItemInterface.php +++ b/src/API/ListItemInterface.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -27,7 +27,7 @@ interface ListItemInterface { * Create a list item * * @param array $data - - * @return bool + * @return Request */ public function create(array $data): Request; @@ -44,7 +44,7 @@ interface ListItemInterface { * * @param string $id - The id of the list item to update * @param array $data - The data with which to update the list item - * @return Response + * @return Request */ public function update(string $id, array $data): Request; @@ -52,7 +52,7 @@ interface ListItemInterface { * Delete a list item * * @param string $id - The id of the list item to delete - * @return bool + * @return Request */ public function delete(string $id): Request; } \ No newline at end of file diff --git a/src/API/MAL.php b/src/API/MAL.php index e477ab24..12343a28 100644 --- a/src/API/MAL.php +++ b/src/API/MAL.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -16,11 +16,14 @@ namespace Aviat\AnimeClient\API; -use Aviat\AnimeClient\API\Kitsu\Enum\{ - AnimeWatchingStatus as KAWS, - MangaReadingStatus as KMRS +use Aviat\AnimeClient\API\Enum\{ + AnimeWatchingStatus\Kitsu as KAWS, + MangaReadingStatus\Kitsu as KMRS +}; +use Aviat\AnimeClient\API\Enum\{ + AnimeWatchingStatus\MAL as AnimeWatchingStatus, + MangaReadingStatus\MAL as MangaReadingStatus }; -use Aviat\AnimeClient\API\MAL\Enum\{AnimeWatchingStatus, MangaReadingStatus}; /** * Constants and mappings for the My Anime List API @@ -36,7 +39,7 @@ class MAL { KAWS::DROPPED => AnimeWatchingStatus::DROPPED, KAWS::PLAN_TO_WATCH => AnimeWatchingStatus::PLAN_TO_WATCH ]; - + const MAL_KITSU_WATCHING_STATUS_MAP = [ 1 => KAWS::WATCHING, 2 => KAWS::COMPLETED, diff --git a/src/API/MAL/ListItem.php b/src/API/MAL/ListItem.php index 27de85b3..0c81844a 100644 --- a/src/API/MAL/ListItem.php +++ b/src/API/MAL/ListItem.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/MAL/MALRequestBuilder.php b/src/API/MAL/MALRequestBuilder.php index 4b92fcba..cc8899a5 100644 --- a/src/API/MAL/MALRequestBuilder.php +++ b/src/API/MAL/MALRequestBuilder.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/MAL/MALTrait.php b/src/API/MAL/MALTrait.php index b82ffa2c..c10775bd 100644 --- a/src/API/MAL/MALTrait.php +++ b/src/API/MAL/MALTrait.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/MAL/Model.php b/src/API/MAL/Model.php index 35c849ef..7c807f31 100644 --- a/src/API/MAL/Model.php +++ b/src/API/MAL/Model.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -38,6 +38,11 @@ class Model { */ protected $animeListTransformer; + /** + * @var MangaListTransformer + */ + protected $mangaListTransformer; + /** * @var ListItem */ @@ -76,6 +81,8 @@ class Model { */ public function createListItem(array $data, string $type = 'anime'): Request { + $createData = []; + if ($type === 'anime') { $createData = [ @@ -119,7 +126,7 @@ class Model { ] ]); - return (array_key_exists($type, $list['myanimelist'])) + return array_key_exists($type, $list['myanimelist']) ? $list['myanimelist'][$type] : []; } @@ -146,6 +153,8 @@ class Model { */ public function updateListItem(array $data, string $type = 'anime'): Request { + $updateData = []; + if ($type === 'anime') { $updateData = $this->animeListTransformer->untransform($data); diff --git a/src/API/MAL/Transformer/AnimeListTransformer.php b/src/API/MAL/Transformer/AnimeListTransformer.php index 2e74dceb..25d18c2c 100644 --- a/src/API/MAL/Transformer/AnimeListTransformer.php +++ b/src/API/MAL/Transformer/AnimeListTransformer.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/MAL/Transformer/MangaListTransformer.php b/src/API/MAL/Transformer/MangaListTransformer.php index 04896ec7..2c04db77 100644 --- a/src/API/MAL/Transformer/MangaListTransformer.php +++ b/src/API/MAL/Transformer/MangaListTransformer.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Mapping/AnimeWatchingStatus.php b/src/API/Mapping/AnimeWatchingStatus.php index 0017775c..8bc37712 100644 --- a/src/API/Mapping/AnimeWatchingStatus.php +++ b/src/API/Mapping/AnimeWatchingStatus.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/Mapping/MangaReadingStatus.php b/src/API/Mapping/MangaReadingStatus.php index c509a0bb..77d76609 100644 --- a/src/API/Mapping/MangaReadingStatus.php +++ b/src/API/Mapping/MangaReadingStatus.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/API/ParallelAPIRequest.php b/src/API/ParallelAPIRequest.php index b7c1dc95..a446fc62 100644 --- a/src/API/ParallelAPIRequest.php +++ b/src/API/ParallelAPIRequest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -34,7 +34,7 @@ class ParallelAPIRequest { /** * Add a request * - * @param string|Request $request + * @param string|\Amp\Artax\Request $request * @param string|number $key * @return self */ @@ -53,7 +53,7 @@ class ParallelAPIRequest { /** * Add multiple requests * - * @param string[]|Request[] $requests + * @param string[]|\Amp\Artax\Request[] $requests * @return self */ public function addRequests(array $requests): self diff --git a/src/API/XML.php b/src/API/XML.php index ff9fe997..09e5fd4f 100644 --- a/src/API/XML.php +++ b/src/API/XML.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -16,7 +16,7 @@ namespace Aviat\AnimeClient\API; -use DOMDocument, DOMNode, DOMNodelist; +use DOMDocument, DOMNode, DOMNodeList; /** * XML <=> PHP Array codec @@ -180,7 +180,7 @@ class XML { // except for the xml declaration tag, Which looks // something like: /* */ - + return preg_replace('/([^\?])>\s+<', $xml); } diff --git a/src/AnimeClient.php b/src/AnimeClient.php index 226de822..5383dabb 100644 --- a/src/AnimeClient.php +++ b/src/AnimeClient.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -18,21 +18,6 @@ namespace Aviat\AnimeClient; use Yosymfony\Toml\Toml; -if ( ! defined('SRC_DIR')) -{ - \define('SRC_DIR', \realpath(__DIR__)); -} - -const SESSION_SEGMENT = 'Aviat\AnimeClient\Auth'; -const DEFAULT_CONTROLLER = 'Aviat\AnimeClient\Controller\Index'; -const DEFAULT_CONTROLLER_NAMESPACE = 'Aviat\AnimeClient\Controller'; -const DEFAULT_LIST_CONTROLLER = 'Aviat\AnimeClient\Controller\Anime'; -const DEFAULT_CONTROLLER_METHOD = 'index'; -const NOT_FOUND_METHOD = 'notFound'; -const ERROR_MESSAGE_METHOD = 'errorPage'; -const SRC_DIR = SRC_DIR; - - if ( ! \function_exists('Aviat\AnimeClient\loadToml')) { /** diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php index 5327ffb5..7a870197 100644 --- a/src/Command/BaseCommand.php +++ b/src/Command/BaseCommand.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -19,11 +19,7 @@ namespace Aviat\AnimeClient\Command; use function Aviat\AnimeClient\loadToml; use Aura\Session\SessionFactory; -use Aviat\AnimeClient\{ - AnimeClient, - Model, - Util -}; +use Aviat\AnimeClient\Util; use Aviat\AnimeClient\API\CacheTrait; use Aviat\AnimeClient\API\{Kitsu, MAL}; use Aviat\AnimeClient\API\Kitsu\KitsuRequestBuilder; @@ -67,7 +63,7 @@ class BaseCommand extends Command { $APP_DIR = realpath(__DIR__ . '/../../app'); $APPCONF_DIR = realpath("{$APP_DIR}/appConf/"); $CONF_DIR = realpath("{$APP_DIR}/config/"); - require_once $APPCONF_DIR . '/base_config.php'; // $base_config + $base_config = require_once $APPCONF_DIR . '/base_config.php'; $config = loadToml($CONF_DIR); $config_array = array_merge($base_config, $config); @@ -85,7 +81,7 @@ class BaseCommand extends Command { $kitsu_request_logger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/kitsu_request-cli.log', Logger::NOTICE)); $mal_request_logger = new Logger('mal-request'); $mal_request_logger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/mal_request-cli.log', Logger::NOTICE)); - $container->setLogger($app_logger, 'default'); + $container->setLogger($app_logger); $container->setLogger($kitsu_request_logger, 'kitsu-request'); $container->setLogger($mal_request_logger, 'mal-request'); diff --git a/src/Command/CacheClear.php b/src/Command/CacheClear.php index 6029e72c..77754da2 100644 --- a/src/Command/CacheClear.php +++ b/src/Command/CacheClear.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Command/CachePrime.php b/src/Command/CachePrime.php index 3f06c229..660f43f8 100644 --- a/src/Command/CachePrime.php +++ b/src/Command/CachePrime.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Command/SyncKitsuWithMal.php b/src/Command/SyncKitsuWithMal.php index 966aef52..4fb248e3 100644 --- a/src/Command/SyncKitsuWithMal.php +++ b/src/Command/SyncKitsuWithMal.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -51,7 +51,6 @@ class SyncKitsuWithMal extends BaseCommand { * @param array $args * @param array $options * @return void - * @throws \ConsoleKit\ConsoleException */ public function execute(array $args, array $options = []) { @@ -80,6 +79,7 @@ class SyncKitsuWithMal extends BaseCommand { ); } + $kitsuCount = 0; try { $kitsuCount = $this->kitsuModel->{"get{$uType}ListCount"}(); diff --git a/src/Controller.php b/src/Controller.php index 24324a71..c6bb8e57 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -18,15 +18,20 @@ namespace Aviat\AnimeClient; use function Aviat\Ion\_dir; -use Aviat\Ion\Di\{ContainerAware, ContainerInterface}; +use Aviat\Ion\Di\{ + ContainerAware, + ContainerInterface +}; +use Aviat\Ion\Di\Exception\{ + ContainerException, + NotFoundException +}; use Aviat\Ion\Exception\DoubleRenderException; use Aviat\Ion\View\{HtmlView, HttpView, JsonView}; use InvalidArgumentException; /** * Controller base, defines output methods - * - * @property $response Response object */ class Controller { @@ -46,22 +51,16 @@ class Controller { /** * Request object - * @var object $request + * @var \Psr\Http\Message\ServerRequestInterface $request */ protected $request; /** * Response object - * @var object $response + * @var \Psr\Http\Message\ResponseInterface $response */ public $response; - /** - * The api model for the current controller - * @var object - */ - protected $model; - /** * Url generation class * @var UrlGenerator @@ -94,8 +93,8 @@ class Controller { * Constructor * * @param ContainerInterface $container - * @throws \Aviat\Ion\Di\ContainerException - * @throws \Aviat\Ion\Di\NotFoundException + * @throws ContainerException + * @throws NotFoundException */ public function __construct(ContainerInterface $container) { @@ -107,7 +106,7 @@ class Controller { $this->request = $container->get('request'); $this->response = $container->get('response'); - $this->baseData = array_merge((array)$this->baseData, [ + $this->baseData = array_merge($this->baseData, [ 'url' => $auraUrlGenerator, 'urlGenerator' => $urlGenerator, 'auth' => $container->get('auth'), @@ -146,8 +145,8 @@ class Controller { * Set the current url in the session as the target of a future redirect * * @param string|null $url - * @throws \Aviat\Ion\Di\ContainerException - * @throws \Aviat\Ion\Di\NotFoundException + * @throws ContainerException + * @throws NotFoundException * @return void */ public function setSessionRedirect(string $url = NULL) @@ -174,7 +173,7 @@ class Controller { if (null === $url) { $url = $util->isViewPage() - ? $this->request->url->get() + ? $this->request->getUri()->__toString() : $serverParams['HTTP_REFERER']; } diff --git a/src/Controller/Anime.php b/src/Controller/Anime.php index 90cbac2c..478f7bc3 100644 --- a/src/Controller/Anime.php +++ b/src/Controller/Anime.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -17,10 +17,8 @@ namespace Aviat\AnimeClient\Controller; use Aviat\AnimeClient\Controller as BaseController; -use Aviat\AnimeClient\API\Kitsu\{ - Enum\AnimeWatchingStatus as KitsuWatchingStatus, - Transformer\AnimeListTransformer -}; +use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeListTransformer; +use Aviat\AnimeClient\API\Enum\AnimeWatchingStatus\Kitsu as KitsuWatchingStatus; use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus; use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Json; @@ -35,22 +33,10 @@ class Anime extends BaseController { /** * The anime list model - * @var object $model + * @var \Aviat\AnimeClient\Model\Anime $model */ protected $model; - /** - * Data to be sent to all routes in this controller - * @var array $baseData - */ - protected $baseData; - - /** - * Data cache - * @var \Psr\Cache\CachePoolInterface - */ - protected $cache; - /** * Constructor * @@ -158,7 +144,7 @@ class Anime extends BaseController { */ public function edit($id, $status = "all") { - $item = $this->model->getLibraryItem($id, $status); + $item = $this->model->getLibraryItem($id); $this->setSessionRedirect(); $this->outputHTML('anime/edit', [ @@ -203,7 +189,7 @@ class Anime extends BaseController { if ($fullResult['statusCode'] === 200) { - $this->setFlashMessage("Successfully updated.", 'success'); + $this->setFlashMessage('Successfully updated.', 'success'); $this->cache->clear(); } else @@ -230,7 +216,7 @@ class Anime extends BaseController { $data = $this->request->getParsedBody(); } - $response = $this->model->updateLibraryItem($data, $data); + $response = $this->model->updateLibraryItem($data); $this->cache->clear(); $this->outputJSON($response['body'], $response['statusCode']); @@ -246,9 +232,9 @@ class Anime extends BaseController { $body = $this->request->getParsedBody(); $response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']); - if ((bool)$response === TRUE) + if ($response === TRUE) { - $this->setFlashMessage("Successfully deleted anime.", 'success'); + $this->setFlashMessage('Successfully deleted anime.', 'success'); $this->cache->clear(); } else @@ -272,12 +258,14 @@ class Anime extends BaseController { if (empty($data)) { - return $this->notFound( + $this->notFound( $this->config->get('whose_list') . "'s Anime List · Anime · " . 'Anime not found', 'Anime Not Found' ); + + return; } if (array_key_exists('characters', $data['included'])) diff --git a/src/Controller/AnimeCollection.php b/src/Controller/AnimeCollection.php index 0bb096a8..e83ce631 100644 --- a/src/Controller/AnimeCollection.php +++ b/src/Controller/AnimeCollection.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Controller/Character.php b/src/Controller/Character.php index 25977ade..4381cd5e 100644 --- a/src/Controller/Character.php +++ b/src/Controller/Character.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -35,13 +35,15 @@ class Character extends BaseController { if (( ! array_key_exists('data', $rawData)) || empty($rawData['data'])) { - return $this->notFound( + $this->notFound( $this->formatTitle( 'Characters', 'Character not found' ), 'Character Not Found' ); + + return; } $data = JsonAPI::organizeData($rawData); @@ -110,22 +112,22 @@ class Character extends BaseController { return $output; } - + private function getCastCount(array $cast): int { $count = 0; - + foreach($cast as $role) { if ( - array_key_exists('attributes', $role) && + array_key_exists('attributes', $role) && array_key_exists('role', $role['attributes']) && ( ! is_null($role['attributes']['role'])) ) { $count++; } } - + return $count; } diff --git a/src/Controller/Index.php b/src/Controller/Index.php index 439522c5..358c6d60 100644 --- a/src/Controller/Index.php +++ b/src/Controller/Index.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Controller/Manga.php b/src/Controller/Manga.php index 84c4a55e..78d30fe6 100644 --- a/src/Controller/Manga.php +++ b/src/Controller/Manga.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -36,12 +36,6 @@ class Manga extends Controller { */ protected $model; - /** - * Data to ve sent to all routes in this controller - * @var array $baseData - */ - protected $baseData; - /** * Constructor * @@ -265,12 +259,13 @@ class Manga extends Controller { if (empty($data)) { - return $this->notFound( + $this->notFound( $this->config->get('whose_list') . "'s Manga List · Manga · " . 'Manga not found', 'Manga Not Found' ); + return; } foreach($data['included'] as $included) diff --git a/src/Controller/MangaCollection.php b/src/Controller/MangaCollection.php index b5bfcc94..b03f1970 100644 --- a/src/Controller/MangaCollection.php +++ b/src/Controller/MangaCollection.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -18,8 +18,8 @@ namespace Aviat\AnimeClient\Controller; use Aviat\AnimeClient\Controller as BaseController; use Aviat\AnimeClient\Model\{ - manga as mangaModel, - mangaCollection as mangaCollectionModel + Manga as MangaModel, + MangaCollection as MangaCollectionModel }; use Aviat\AnimeClient\UrlGenerator; use Aviat\Ion\Di\ContainerInterface; @@ -31,13 +31,13 @@ class MangaCollection extends BaseController { /** * The manga collection model - * @var mangaCollectionModel $mangaCollectionModel + * @var MangaCollectionModel $mangaCollectionModel */ private $mangaCollectionModel; /** * The manga API model - * @var mangaModel $mangaModel + * @var MangaModel $mangaModel */ private $mangaModel; diff --git a/src/Dispatcher.php b/src/Dispatcher.php index 92e31d9a..19e17b35 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -16,16 +16,10 @@ namespace Aviat\AnimeClient; -use const Aviat\AnimeClient\{ - DEFAULT_CONTROLLER, - DEFAULT_CONTROLLER_NAMESPACE, - ERROR_MESSAGE_METHOD, - NOT_FOUND_METHOD, - SRC_DIR -}; - use function Aviat\Ion\_dir; +use Aura\Router\Matcher; + use Aviat\AnimeClient\API\FailedResponseException; use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Friend; @@ -46,7 +40,7 @@ class Dispatcher extends RoutingBase { /** * The route matcher - * @var object $matcher + * @var Matcher $matcher */ protected $matcher; @@ -80,19 +74,22 @@ class Dispatcher extends RoutingBase { /** * Get the current route object, if one matches * - * @return object + * @return \Aura\Router\Route|false */ public function getRoute() { - $logger = $this->container->getLogger('default'); + $logger = $this->container->getLogger(); $rawRoute = $this->request->getUri()->getPath(); - $routePath = "/" . trim($rawRoute, '/'); + $routePath = '/' . trim($rawRoute, '/'); - $logger->info('Dispatcher - Routing data from get_route method'); - $logger->info(print_r([ - 'route_path' => $routePath - ], TRUE)); + if ($logger !== NULL) + { + $logger->info('Dispatcher - Routing data from get_route method'); + $logger->info(print_r([ + 'route_path' => $routePath + ], TRUE)); + } return $this->matcher->match($this->request); } @@ -113,16 +110,19 @@ class Dispatcher extends RoutingBase { * @param object|null $route * @return void */ - public function __invoke($route = NULL) + public function __invoke($route = NULL): void { - $logger = $this->container->getLogger('default'); + $logger = $this->container->getLogger(); - if (is_null($route)) + if ($route === NULL) { $route = $this->getRoute(); - $logger->info('Dispatcher - Route invoke arguments'); - $logger->info(print_r($route, TRUE)); + if ($logger !== NULL) + { + $logger->info('Dispatcher - Route invoke arguments'); + $logger->info(print_r($route, TRUE)); + } } if ($route) @@ -153,7 +153,7 @@ class Dispatcher extends RoutingBase { * @throws \LogicException * @return array */ - protected function processRoute($route) + protected function processRoute($route): array { if (array_key_exists('controller', $route->attributes)) { @@ -161,7 +161,7 @@ class Dispatcher extends RoutingBase { } else { - throw new \LogicException("Missing controller"); + throw new \LogicException('Missing controller'); } // Get the full namespace for a controller if a short name is given @@ -187,8 +187,11 @@ class Dispatcher extends RoutingBase { } } } - $logger = $this->container->getLogger('default'); - $logger->info(json_encode($params)); + $logger = $this->container->getLogger(); + if ($logger !== NULL) + { + $logger->info(json_encode($params)); + } return [ 'controller_name' => $controllerName, @@ -211,8 +214,11 @@ class Dispatcher extends RoutingBase { $segments = explode('/', $path); $controller = reset($segments); - $logger = $this->container->getLogger('default'); - $logger->info('Controller: ' . $controller); + $logger = $this->container->getLogger(); + if ($logger !== NULL) + { + $logger->info('Controller: ' . $controller); + } if (empty($controller)) { @@ -240,7 +246,7 @@ class Dispatcher extends RoutingBase { foreach ($classFiles as $file) { - $rawClassName = basename(str_replace(".php", "", $file)); + $rawClassName = basename(str_replace('.php', '', $file)); $path = $this->string($rawClassName)->dasherize()->__toString(); $className = trim($defaultNamespace . '\\' . $rawClassName, '\\'); @@ -268,9 +274,12 @@ class Dispatcher extends RoutingBase { $controller = new $controllerName($this->container); // Run the appropriate controller method - $logger->debug('Dispatcher - controller arguments', $params); + if ($logger !== NULL) + { + $logger->debug('Dispatcher - controller arguments', $params); + } - call_user_func_array([$controller, $method], $params); + \call_user_func_array([$controller, $method], $params); } catch (FailedResponseException $e) { @@ -291,11 +300,14 @@ class Dispatcher extends RoutingBase { */ protected function getErrorParams() { - $logger = $this->container->getLogger('default'); + $logger = $this->container->getLogger(); $failure = $this->matcher->getFailedRoute(); - $logger->info('Dispatcher - failed route'); - $logger->info(print_r($failure, TRUE)); + if ($logger !== NULL) + { + $logger->info('Dispatcher - failed route'); + $logger->info(print_r($failure, TRUE)); + } $actionMethod = ERROR_MESSAGE_METHOD; @@ -360,9 +372,9 @@ class Dispatcher extends RoutingBase { $route['controller'] = $controllerClass; // Select the appropriate router method based on the http verb - $add = (array_key_exists('verb', $route)) + $add = array_key_exists('verb', $route) ? strtolower($route['verb']) - : "get"; + : 'get'; // Add the route to the router object if ( ! array_key_exists('tokens', $route)) diff --git a/src/Helper/Menu.php b/src/Helper/Menu.php index b726538b..17a5e380 100644 --- a/src/Helper/Menu.php +++ b/src/Helper/Menu.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/MenuGenerator.php b/src/MenuGenerator.php index 89199ddd..b7c92da5 100644 --- a/src/MenuGenerator.php +++ b/src/MenuGenerator.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -16,8 +16,11 @@ namespace Aviat\AnimeClient; -use Aviat\Ion\{ArrayWrapper, StringWrapper}; +use Aviat\Ion\{ + ArrayWrapper, StringWrapper +}; use Aviat\Ion\Di\ContainerInterface; +use Aviat\Ion\Exception\ConfigException; /** * Helper object to manage menu creation and selection @@ -80,6 +83,7 @@ class MenuGenerator extends UrlGenerator { * Generate the html structure of the menu selected * * @param string $menu + * @throws ConfigException * @return string */ public function generate($menu) diff --git a/src/Model/API.php b/src/Model/API.php index e13e0ee4..e4501c90 100644 --- a/src/Model/API.php +++ b/src/Model/API.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -16,10 +16,13 @@ namespace Aviat\AnimeClient\Model; +use Aviat\Ion\StringWrapper; + /** * Base model for api interaction */ -class API extends AbstractModel { +class API { + use StringWrapper; /** * Whether to use the MAL api diff --git a/src/Model/Anime.php b/src/Model/Anime.php index 43c18d35..e56522c5 100644 --- a/src/Model/Anime.php +++ b/src/Model/Anime.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Model/AnimeCollection.php b/src/Model/AnimeCollection.php index cf2ce5fb..2af9de2c 100644 --- a/src/Model/AnimeCollection.php +++ b/src/Model/AnimeCollection.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -16,9 +16,7 @@ namespace Aviat\AnimeClient\Model; -use Aviat\AnimeClient\API\Kitsu; use Aviat\Ion\Di\ContainerInterface; -use Aviat\Ion\Json; use PDO; /** @@ -28,7 +26,7 @@ class AnimeCollection extends Collection { /** * Anime API Model - * @var object $animeModel + * @var Anime $animeModel */ protected $animeModel; @@ -48,7 +46,7 @@ class AnimeCollection extends Collection { * * @return array */ - public function getCollection() + public function getCollection(): array { $rawCollection = $this->getCollectionFromDatabase(); @@ -74,7 +72,7 @@ class AnimeCollection extends Collection { * * @return array */ - public function getMediaTypeList() + public function getMediaTypeList(): array { $output = []; @@ -93,13 +91,13 @@ class AnimeCollection extends Collection { /** * Get item from collection for editing * - * @param int $id + * @param string $id * @return array */ - public function getCollectionEntry($id) + public function getCollectionEntry($id): array { $query = $this->db->from('anime_set') - ->where('hummingbird_id', (int)$id) + ->where('hummingbird_id', $id) ->get(); return $query->fetch(PDO::FETCH_ASSOC); @@ -110,7 +108,7 @@ class AnimeCollection extends Collection { * * @return array */ - private function getCollectionFromDatabase() + private function getCollectionFromDatabase(): array { if ( ! $this->validDatabase) { @@ -134,7 +132,7 @@ class AnimeCollection extends Collection { * @param array $data * @return void */ - public function add($data) + public function add($data): void { $anime = (object)$this->animeModel->getAnimeById($data['id']); $this->db->set([ @@ -160,7 +158,7 @@ class AnimeCollection extends Collection { * @param array $data * @return void */ - public function update($data) + public function update($data): void { // If there's no id to update, don't update if ( ! array_key_exists('hummingbird_id', $data)) @@ -182,7 +180,7 @@ class AnimeCollection extends Collection { * @param array $data * @return void */ - public function delete($data) + public function delete($data): void { // If there's no id to update, don't delete if ( ! array_key_exists('hummingbird_id', $data)) @@ -203,7 +201,7 @@ class AnimeCollection extends Collection { * @param int $kitsuId * @return array */ - public function get($kitsuId) + public function get($kitsuId): array { $query = $this->db->from('anime_set') ->where('hummingbird_id', $kitsuId) @@ -215,13 +213,14 @@ class AnimeCollection extends Collection { /** * Update genre information for selected anime * - * @param int $animeId The current anime + * @param string $animeId The current anime * @return void */ - private function updateGenre($animeId) + private function updateGenre($animeId): void { $genreInfo = $this->getGenreData(); - extract($genreInfo); + $genres = $genreInfo['genres']; + $links = $genreInfo['links']; // Get api information $anime = $this->animeModel->getAnimeById($animeId); @@ -229,7 +228,7 @@ class AnimeCollection extends Collection { foreach ($anime['genres'] as $genre) { // Add genres that don't currently exist - if ( ! in_array($genre, $genres)) + if ( ! \in_array($genre, $genres, TRUE)) { $this->db->set('genre', $genre) ->insert('genres'); @@ -248,7 +247,7 @@ class AnimeCollection extends Collection { if (array_key_exists($animeId, $links)) { - if ( ! in_array($flippedGenres[$genre], $links[$animeId])) + if ( ! \in_array($flippedGenres[$genre], $links[$animeId], TRUE)) { $this->db->set($insertArray)->insert('genre_anime_set_link'); } @@ -265,7 +264,7 @@ class AnimeCollection extends Collection { * * @return array */ - private function getGenreData() + private function getGenreData(): array { $genres = []; $links = []; diff --git a/src/Model/Collection.php b/src/Model/Collection.php index 50003973..117227bf 100644 --- a/src/Model/Collection.php +++ b/src/Model/Collection.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -16,8 +16,7 @@ namespace Aviat\AnimeClient\Model; -use Aviat\AnimeClient\Model\DB; -use Aviat\Ion\Di\{ContainerAware, ContainerInterface}; +use Aviat\Ion\Di\ContainerInterface; use PDO; use PDOException; @@ -26,8 +25,6 @@ use PDOException; */ class Collection extends DB { - use ContainerAware; - /** * Whether the database is valid for querying * @var boolean @@ -47,11 +44,7 @@ class Collection extends DB { { $this->db = \Query($this->dbConfig['collection']); } - catch (PDOException $e) - { - //$this->validDatabase = FALSE; - //return FALSE; - } + catch (PDOException $e) {} // Is database valid? If not, set a flag so the // app can be run without a valid database @@ -81,7 +74,7 @@ class Collection extends DB { * @param array $filter * @return array */ - public function getGenreList($filter = []) + public function getGenreList(array $filter = []): array { $this->db->select('hummingbird_id, genre') ->from('genre_anime_set_link gl') @@ -112,7 +105,7 @@ class Collection extends DB { if (array_key_exists($id, $output)) { - array_push($output[$id], $genre); + $output[$id][] = $genre; } else { diff --git a/src/Model/DB.php b/src/Model/DB.php index f047c030..03ee3ef4 100644 --- a/src/Model/DB.php +++ b/src/Model/DB.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -17,16 +17,19 @@ namespace Aviat\AnimeClient\Model; use Aviat\Ion\Di\{ContainerAware, ContainerInterface}; +use Aviat\Ion\{ArrayWrapper, StringWrapper}; /** * Base model for database interaction */ -class DB extends AbstractModel { +class DB { + use ArrayWrapper; use ContainerAware; + use StringWrapper; /** * The query builder object - * @var object $db + * @var \Query\Query_Builder_Interface */ protected $db; diff --git a/src/Model/Manga.php b/src/Model/Manga.php index dbd5bc89..0ca1a732 100644 --- a/src/Model/Manga.php +++ b/src/Model/Manga.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Model/MangaCollection.php b/src/Model/MangaCollection.php index 37ae42e6..a41b6618 100644 --- a/src/Model/MangaCollection.php +++ b/src/Model/MangaCollection.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -16,9 +16,7 @@ namespace Aviat\AnimeClient\Model; -use Aviat\AnimeClient\API\Kitsu; use Aviat\Ion\Di\ContainerInterface; -use Aviat\Ion\Json; use PDO; /** @@ -28,7 +26,7 @@ class MangaCollection extends Collection { /** * Manga API Model - * @var object $mangaModel + * @var Manga $mangaModel */ protected $mangaModel; @@ -48,7 +46,7 @@ class MangaCollection extends Collection { * * @return array */ - public function getCollection() + public function getCollection(): array { $rawCollection = $this->getCollectionFromDatabase(); @@ -74,7 +72,7 @@ class MangaCollection extends Collection { * * @return array */ - public function getMediaTypeList() + public function getMediaTypeList(): array { $output = []; @@ -96,10 +94,10 @@ class MangaCollection extends Collection { * @param int $id * @return array */ - public function getCollectionEntry($id) + public function getCollectionEntry($id): array { $query = $this->db->from('anime_set') - ->where('hummingbird_id', (int)$id) + ->where('hummingbird_id', $id) ->get(); return $query->fetch(PDO::FETCH_ASSOC); @@ -110,7 +108,7 @@ class MangaCollection extends Collection { * * @return array */ - private function getCollectionFromDatabase() + private function getCollectionFromDatabase(): array { if ( ! $this->validDatabase) { @@ -134,7 +132,7 @@ class MangaCollection extends Collection { * @param array $data * @return void */ - public function add($data) + public function add($data): void { $anime = (object)$this->mangaModel->getMangaById($data['id']); $this->db->set([ @@ -160,7 +158,7 @@ class MangaCollection extends Collection { * @param array $data * @return void */ - public function update($data) + public function update($data): void { // If there's no id to update, don't update if ( ! array_key_exists('hummingbird_id', $data)) @@ -182,7 +180,7 @@ class MangaCollection extends Collection { * @param array $data * @return void */ - public function delete($data) + public function delete($data): void { // If there's no id to update, don't delete if ( ! array_key_exists('hummingbird_id', $data)) @@ -200,10 +198,10 @@ class MangaCollection extends Collection { /** * Get the details of a collection item * - * @param int $kitsuId + * @param string $kitsuId * @return array */ - public function get($kitsuId) + public function get($kitsuId): array { $query = $this->db->from('manga_set') ->where('hummingbird_id', $kitsuId) @@ -215,21 +213,22 @@ class MangaCollection extends Collection { /** * Update genre information for selected manga * - * @param int $mangaId The current manga + * @param string $mangaId The current manga * @return void */ - private function updateGenre($mangaId) + private function updateGenre($mangaId): void { $genreInfo = $this->getGenreData(); - extract($genreInfo); + $genres = $genreInfo['genres']; + $links = $genreInfo['links']; // Get api information $manga = $this->mangaModel->getMangaById($mangaId); - foreach ($anime['genres'] as $genre) + foreach ($manga['genres'] as $genre) { // Add genres that don't currently exist - if ( ! in_array($genre, $genres)) + if ( ! \in_array($genre, $genres, TRUE)) { $this->db->set('genre', $genre) ->insert('genres'); @@ -248,7 +247,7 @@ class MangaCollection extends Collection { if (array_key_exists($mangaId, $links)) { - if ( ! in_array($flippedGenres[$genre], $links[$mangaId])) + if ( ! \in_array($flippedGenres[$genre], $links[$mangaId], TRUE)) { $this->db->set($insertArray)->insert('genre_manga_set_link'); } @@ -265,7 +264,7 @@ class MangaCollection extends Collection { * * @return array */ - private function getGenreData() + private function getGenreData(): array { $genres = []; $links = []; diff --git a/src/RoutingBase.php b/src/RoutingBase.php index eaf247ed..d4269cc3 100644 --- a/src/RoutingBase.php +++ b/src/RoutingBase.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/UrlGenerator.php b/src/UrlGenerator.php index b26399b7..39862ca6 100644 --- a/src/UrlGenerator.php +++ b/src/UrlGenerator.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Util.php b/src/Util.php index 1dca7425..dd87758f 100644 --- a/src/Util.php +++ b/src/Util.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/src/Model/AbstractModel.php b/src/constants.php similarity index 51% rename from src/Model/AbstractModel.php rename to src/constants.php index 8a2e92d7..3a606455 100644 --- a/src/Model/AbstractModel.php +++ b/src/constants.php @@ -8,19 +8,19 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient */ -namespace Aviat\AnimeClient\Model; +namespace Aviat\AnimeClient; -use Aviat\Ion\StringWrapper; - -/** - * Base class for Models - */ -abstract class AbstractModel { - use StringWrapper; -} \ No newline at end of file +const DEFAULT_CONTROLLER = Controller\Index::class; +const DEFAULT_CONTROLLER_METHOD = 'index'; +const DEFAULT_CONTROLLER_NAMESPACE = Controller::class; +const DEFAULT_LIST_CONTROLLER = Controller\Anime::class; +const ERROR_MESSAGE_METHOD = 'errorPage'; +const NOT_FOUND_METHOD = 'notFound'; +const SESSION_SEGMENT = 'Aviat\AnimeClient\Auth'; +const SRC_DIR = __DIR__; \ No newline at end of file diff --git a/tests/API/APIRequestBuilderTest.php b/tests/API/APIRequestBuilderTest.php index 1f0020dc..a3afc8cc 100644 --- a/tests/API/APIRequestBuilderTest.php +++ b/tests/API/APIRequestBuilderTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -116,7 +116,7 @@ class APIRequestBuilderTest extends TestCase { 'foo' => [ 'bar' => 1, 'baz' => [2, 3, 4], - 'bar' => [ + 'bazbar' => [ 'a' => 1, 'b' => 2 ] diff --git a/tests/API/CacheTraitTest.php b/tests/API/CacheTraitTest.php index 6817448d..7fc03e8f 100644 --- a/tests/API/CacheTraitTest.php +++ b/tests/API/CacheTraitTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -20,21 +20,24 @@ use Aviat\AnimeClient\API\CacheTrait; use Aviat\AnimeClient\Tests\AnimeClientTestCase; class CacheTraitTest extends AnimeClientTestCase { + + protected $testClass; + public function setUp() { parent::setUp(); - $this->testClass = new class { - use CacheTrait; + $this->testClass = new class { + use CacheTrait; }; } - + public function testSetGet() { $cachePool = $this->container->get('cache'); $this->testClass->setCache($cachePool); $this->assertEquals($cachePool, $this->testClass->getCache()); } - + public function testGetHashForMethodCall() { $hash = $this->testClass->getHashForMethodCall($this, __METHOD__, []); diff --git a/tests/API/JsonAPITest.php b/tests/API/JsonAPITest.php index 22e65696..e72dd09f 100644 --- a/tests/API/JsonAPITest.php +++ b/tests/API/JsonAPITest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/Kitsu/Transformer/AnimeListTransformerTest.php b/tests/API/Kitsu/Transformer/AnimeListTransformerTest.php index e8a20e63..2f062339 100644 --- a/tests/API/Kitsu/Transformer/AnimeListTransformerTest.php +++ b/tests/API/Kitsu/Transformer/AnimeListTransformerTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/Kitsu/Transformer/AnimeTransformerTest.php b/tests/API/Kitsu/Transformer/AnimeTransformerTest.php index 04b74fda..fc418842 100644 --- a/tests/API/Kitsu/Transformer/AnimeTransformerTest.php +++ b/tests/API/Kitsu/Transformer/AnimeTransformerTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/Kitsu/Transformer/MangaListTransformerTest.php b/tests/API/Kitsu/Transformer/MangaListTransformerTest.php index dd1cc8ca..b68db9d7 100644 --- a/tests/API/Kitsu/Transformer/MangaListTransformerTest.php +++ b/tests/API/Kitsu/Transformer/MangaListTransformerTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/Kitsu/Transformer/MangaTransformerTest.php b/tests/API/Kitsu/Transformer/MangaTransformerTest.php index 0c31fcc8..6185d6e4 100644 --- a/tests/API/Kitsu/Transformer/MangaTransformerTest.php +++ b/tests/API/Kitsu/Transformer/MangaTransformerTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/KitsuTest.php b/tests/API/KitsuTest.php index ebbca7da..b78e439d 100644 --- a/tests/API/KitsuTest.php +++ b/tests/API/KitsuTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/MAL/ListItemTest.php b/tests/API/MAL/ListItemTest.php index 510010fb..6586ecf6 100644 --- a/tests/API/MAL/ListItemTest.php +++ b/tests/API/MAL/ListItemTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/MAL/MALTraitTest.php b/tests/API/MAL/MALTraitTest.php index 5c239eb9..132cca59 100644 --- a/tests/API/MAL/MALTraitTest.php +++ b/tests/API/MAL/MALTraitTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/MAL/ModelTest.php b/tests/API/MAL/ModelTest.php index b626bb07..0d430b5a 100644 --- a/tests/API/MAL/ModelTest.php +++ b/tests/API/MAL/ModelTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/MAL/Transformer/AnimeListTransformerTest.php b/tests/API/MAL/Transformer/AnimeListTransformerTest.php index bc5007ea..5b03dc2a 100644 --- a/tests/API/MAL/Transformer/AnimeListTransformerTest.php +++ b/tests/API/MAL/Transformer/AnimeListTransformerTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/MAL/Transformer/MangaListTransformerTest.php b/tests/API/MAL/Transformer/MangaListTransformerTest.php index 6a6a6196..35327fca 100644 --- a/tests/API/MAL/Transformer/MangaListTransformerTest.php +++ b/tests/API/MAL/Transformer/MangaListTransformerTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/API/ParallelAPIRequestTest.php b/tests/API/ParallelAPIRequestTest.php index c3555e52..6a529dda 100644 --- a/tests/API/ParallelAPIRequestTest.php +++ b/tests/API/ParallelAPIRequestTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -16,32 +16,34 @@ namespace Aviat\AnimeClient\Tests\API; -use Aviat\AnimeClient\API\{APIRequestBuilder, ParallelAPIRequest}; +use Aviat\AnimeClient\API\ParallelAPIRequest; use Aviat\Ion\Friend; use PHPUnit\Framework\TestCase; class ParallelAPIRequestTest extends TestCase { - + public function testAddStringUrlRequest() { $requester = new ParallelAPIRequest(); + $requester->addRequest('https://httpbin.org'); + $friend = new Friend($requester); - $friend->addRequest('https://httpbin.org'); - + $this->assertEquals($friend->requests, ['https://httpbin.org']); } - + public function testAddStringUrlRequests() { $requests = [ 'foo' => 'http://example.com', 'bar' => 'https://example.com' ]; - + $requester = new ParallelAPIRequest(); + $requester->addRequests($requests); + $friend = new Friend($requester); - $friend->addRequests($requests); - + $this->assertEquals($friend->requests, $requests); } } \ No newline at end of file diff --git a/tests/API/XMLTest.php b/tests/API/XMLTest.php index c51b6cda..db17f53b 100644 --- a/tests/API/XMLTest.php +++ b/tests/API/XMLTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/AnimeClientTestCase.php b/tests/AnimeClientTestCase.php index 4418e787..bbe323aa 100644 --- a/tests/AnimeClientTestCase.php +++ b/tests/AnimeClientTestCase.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -29,9 +29,9 @@ use Zend\Diactoros\{ ServerRequestFactory }; -define('ROOT_DIR', __DIR__ . '/../'); -define('TEST_DATA_DIR', __DIR__ . '/test_data'); -define('TEST_VIEW_DIR', __DIR__ . '/test_views'); +\define('ROOT_DIR', __DIR__ . '/../'); +\define('TEST_DATA_DIR', __DIR__ . '/test_data'); +\define('TEST_VIEW_DIR', __DIR__ . '/test_views'); /** * Base class for TestCases @@ -141,8 +141,8 @@ class AnimeClientTestCase extends TestCase { '_FILES' => $_FILES ]; - $request = call_user_func_array( - ['Zend\Diactoros\ServerRequestFactory', 'fromGlobals'], + $request = \call_user_func_array( + [ServerRequestFactory::class, 'fromGlobals'], array_merge($default, $supers) ); $this->container->setInstance('request', $request); diff --git a/tests/Command/BaseCommandTest.php b/tests/Command/BaseCommandTest.php index 22252a62..c019035b 100644 --- a/tests/Command/BaseCommandTest.php +++ b/tests/Command/BaseCommandTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/ControllerTest.php b/tests/ControllerTest.php index 8333cc62..34fefa73 100644 --- a/tests/ControllerTest.php +++ b/tests/ControllerTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -59,30 +59,30 @@ class ControllerTest extends AnimeClientTestCase { $this->container->setInstance('config', $config); $this->assertInstanceOf( - 'Aviat\AnimeClient\Controller', + Controller::class, new AnimeController($this->container) ); $this->assertInstanceOf( - 'Aviat\AnimeClient\Controller', + Controller::class, new MangaController($this->container) ); $this->assertInstanceOf( - 'Aviat\AnimeClient\Controller', + Controller::class, new CharacterController($this->container) ); $this->assertInstanceOf( - 'Aviat\AnimeClient\Controller', + Controller::class, new AnimeCollectionController($this->container) ); $this->assertInstanceOf( - 'Aviat\AnimeClient\Controller', + Controller::class, new MangaCollectionController($this->container) ); } public function testBaseControllerSanity() { - $this->assertTrue(is_object($this->BaseController)); + $this->assertTrue(\is_object($this->BaseController)); } public function testFormatTitle() diff --git a/tests/DispatcherTest.php b/tests/DispatcherTest.php index c64ec0f6..c67df57b 100644 --- a/tests/DispatcherTest.php +++ b/tests/DispatcherTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -180,14 +180,6 @@ class DispatcherTest extends AnimeClientTestCase { 'action' => ['login'], 'verb' => 'get' ], - 'index' => [ - 'path' => '/', - 'action' => ['redirect'], - 'params' => [ - 'url' => '', // Determined by config - 'code' => '301' - ] - ], 'index' => [ 'path' => '/', 'action' => ['redirect'], diff --git a/tests/Helper/MenuHelperTest.php b/tests/Helper/MenuHelperTest.php index cf0471cf..352aa607 100644 --- a/tests/Helper/MenuHelperTest.php +++ b/tests/Helper/MenuHelperTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/MenuGeneratorTest.php b/tests/MenuGeneratorTest.php index d6c2a806..a1e5a82d 100644 --- a/tests/MenuGeneratorTest.php +++ b/tests/MenuGeneratorTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient @@ -33,7 +33,7 @@ class MenuGeneratorTest extends AnimeClientTestCase { public function testSanity() { $generator = new MenuGenerator($this->container); - $this->assertInstanceOf('Aviat\AnimeClient\MenuGenerator', $generator); + $this->assertInstanceOf(MenuGenerator::class, $generator); } public function testParseConfig() diff --git a/tests/RequirementsTest.php b/tests/RequirementsTest.php index ec5c65cd..45416d4a 100644 --- a/tests/RequirementsTest.php +++ b/tests/RequirementsTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/RoutingBaseTest.php b/tests/RoutingBaseTest.php index 6e3e5893..9b6934fe 100644 --- a/tests/RoutingBaseTest.php +++ b/tests/RoutingBaseTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/TestSessionHandler.php b/tests/TestSessionHandler.php index 1f4c3262..ee7d235a 100644 --- a/tests/TestSessionHandler.php +++ b/tests/TestSessionHandler.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/UrlGeneratorTest.php b/tests/UrlGeneratorTest.php index 48d91cfd..a7fedf21 100644 --- a/tests/UrlGeneratorTest.php +++ b/tests/UrlGeneratorTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/UtilTest.php b/tests/UtilTest.php index d5a9470a..226cdd13 100644 --- a/tests/UtilTest.php +++ b/tests/UtilTest.php @@ -8,7 +8,7 @@ * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2018 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 4.0 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 7073357f..27878b14 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -14,8 +14,8 @@ if ($timezone === '' || $timezone === FALSE) // Autoloading // ----------------------------------------------------------------------------- -require __DIR__ . '/AnimeClientTestCase.php'; -require __DIR__ . '/../vendor/autoload.php'; +require_once __DIR__ . '/AnimeClientTestCase.php'; +require_once __DIR__ . '/../vendor/autoload.php'; // ----------------------------------------------------------------------------- // Ini Settings @@ -35,7 +35,7 @@ $_SESSION = []; $_COOKIE = []; // Request base test case and mocks -require __DIR__ . '/TestSessionHandler.php'; -require __DIR__ . '/mocks.php'; +require_once __DIR__ . '/TestSessionHandler.php'; +require_once __DIR__ . '/mocks.php'; // End of bootstrap.php \ No newline at end of file diff --git a/tests/mocks.php b/tests/mocks.php index eb37c55b..2584a1dd 100644 --- a/tests/mocks.php +++ b/tests/mocks.php @@ -72,7 +72,7 @@ class TestTransformer extends AbstractTransformer { } trait MockViewOutputTrait { - protected function output() { + protected function output(): void { $reflect = new ReflectionClass($this); $properties = $reflect->getProperties(); $props = []; @@ -102,8 +102,8 @@ class MockUtil { } class TestView extends View { - public function send() {} - protected function output() + public function send(): void {} + protected function output(): void { /*$content =& $this->response->content; $content->set($this->output);