diff --git a/composer.lock b/composer.lock index 9765824b..283cf5a5 100644 --- a/composer.lock +++ b/composer.lock @@ -2811,17 +2811,17 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "ed4318ac306a1a1d467d19c1a768ff17e2d454b1" + "reference": "2b23329e299c9a6cd98a82f5137ab4909c8e506d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ed4318ac306a1a1d467d19c1a768ff17e2d454b1", - "reference": "ed4318ac306a1a1d467d19c1a768ff17e2d454b1", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/2b23329e299c9a6cd98a82f5137ab4909c8e506d", + "reference": "2b23329e299c9a6cd98a82f5137ab4909c8e506d", "shasum": "" }, "conflict": { "3f/pygmentize": "<1.2", - "admidio/admidio": "<4.2.11", + "admidio/admidio": "<4.2.13", "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", "aheinze/cockpit": "<2.2", "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", @@ -2894,7 +2894,7 @@ "codeception/codeception": "<3.1.3|>=4,<4.1.22", "codeigniter/framework": "<3.1.9", "codeigniter4/framework": "<=4.4.2", - "codeigniter4/shield": "<1.0.0.0-beta4", + "codeigniter4/shield": "<1.0.0.0-beta8", "codiad/codiad": "<=2.8.4", "composer/composer": "<1.10.27|>=2,<2.2.22|>=2.3,<2.6.4", "concrete5/concrete5": "<9.2.2", @@ -3237,6 +3237,7 @@ "really-simple-plugins/complianz-gdpr": "<6.4.2", "remdex/livehelperchat": "<3.99", "reportico-web/reportico": "<=7.1.21", + "rhukster/dom-sanitizer": "<1.0.7", "rmccue/requests": ">=1.6,<1.8", "robrichards/xmlseclibs": "<3.0.4", "roots/soil": "<4.1", @@ -3296,7 +3297,7 @@ "spoonity/tcpdf": "<6.2.22", "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", "ssddanbrown/bookstack": "<22.02.3", - "statamic/cms": "<4.34", + "statamic/cms": "<4.36", "stormpath/sdk": "<9.9.99", "studio-42/elfinder": "<2.1.62", "subhh/libconnect": "<7.0.8|>=8,<8.1", @@ -3305,6 +3306,7 @@ "sumocoders/framework-user-bundle": "<1.4", "swag/paypal": "<5.4.4", "swiftmailer/swiftmailer": ">=4,<5.4.5", + "swiftyedit/swiftyedit": "<1.2", "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", "sylius/grid-bundle": "<1.10.1", @@ -3505,7 +3507,7 @@ "type": "tidelift" } ], - "time": "2023-11-17T23:04:10+00:00" + "time": "2023-11-23T04:04:32+00:00" }, { "name": "sebastian/cli-parser", diff --git a/src/Backends/Common/ClientInterface.php b/src/Backends/Common/ClientInterface.php index 6ce2531e..e70f5ca1 100644 --- a/src/Backends/Common/ClientInterface.php +++ b/src/Backends/Common/ClientInterface.php @@ -211,4 +211,23 @@ interface ClientInterface * @return string|bool return user token as string or bool(FALSE) if not supported. */ public function getUserToken(int|string $userId, string $username): string|bool; + + /** + * Get Backend Info. + * + * @param array $opts + * + * @return array + */ + public function getInfo(array $opts = []): array; + + /** + * Get Backend Version. + * + * @param array $opts + * + * @return string + */ + public function getVersion(array $opts = []): string; + } diff --git a/src/Backends/Emby/Action/GetInfo.php b/src/Backends/Emby/Action/GetInfo.php new file mode 100644 index 00000000..637097df --- /dev/null +++ b/src/Backends/Emby/Action/GetInfo.php @@ -0,0 +1,9 @@ +response; } + public function getInfo(array $opts = []): array + { + $response = Container::get(GetInfo::class)(context: $this->context, opts: $opts); + + if ($response->hasError()) { + $this->logger->log($response->error->level(), $response->error->message, $response->error->context); + } + + if (false === $response->isSuccessful()) { + throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format())); + } + + return $response->response; + } + + public function getVersion(array $opts = []): string + { + $response = Container::get(GetVersion::class)(context: $this->context, opts: $opts); + + if ($response->hasError()) { + $this->logger->log($response->error->level(), $response->error->message, $response->error->context); + } + + if (false === $response->isSuccessful()) { + throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format())); + } + + return $response->response; + } + public static function manage(array $backend, array $opts = []): array { return Container::get(EmbyManage::class)->manage(backend: $backend, opts: $opts); diff --git a/src/Backends/Jellyfin/Action/GetIdentifier.php b/src/Backends/Jellyfin/Action/GetIdentifier.php index cd800e12..a0036b25 100644 --- a/src/Backends/Jellyfin/Action/GetIdentifier.php +++ b/src/Backends/Jellyfin/Action/GetIdentifier.php @@ -6,8 +6,6 @@ namespace App\Backends\Jellyfin\Action; use App\Backends\Common\CommonTrait; use App\Backends\Common\Context; -use App\Backends\Common\Error; -use App\Backends\Common\Levels; use App\Backends\Common\Response; use Psr\Log\LoggerInterface; use Psr\SimpleCache\CacheInterface; @@ -39,59 +37,13 @@ class GetIdentifier return $this->tryResponse( context: $context, fn: function () use ($context, $opts) { - $url = $context->backendUrl->withPath('/system/Info'); + $info = (new GetInfo($this->http, $this->logger, $this->cache))(context: $context, opts: $opts); - $this->logger->debug('Requesting [{client}: {backend}] unique identifier.', [ - 'client' => $context->clientName, - 'backend' => $context->backendName, - 'url' => $url - ]); - - $response = $this->http->request( - 'GET', - (string)$url, - array_replace_recursive($context->backendHeaders, $opts['headers'] ?? []) - ); - - $content = $response->getContent(false); - - if (200 !== $response->getStatusCode()) { - return new Response( - status: false, - error: new Error( - message: 'Request for [{backend}] {action} returned with unexpected [{status_code}] status code.', - context: [ - 'action' => $this->action, - 'client' => $context->clientName, - 'backend' => $context->backendName, - 'status_code' => $response->getStatusCode(), - 'url' => (string)$url, - 'response' => $content, - ], - level: Levels::WARNING - ) - ); + if (false === $info->status) { + return $info; } - $item = json_decode( - json: $content, - associative: true, - flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE - ); - - if (true === $context->trace) { - $this->logger->debug('Processing [{client}: {backend}] {action} payload.', [ - 'action' => $this->action, - 'client' => $context->clientName, - 'backend' => $context->backendName, - 'trace' => $item, - ]); - } - - return new Response( - status: true, - response: ag($item, 'Id', null) - ); + return new Response(status: true, response: ag($info->response, 'identifier')); }, action: $this->action, ); diff --git a/src/Backends/Jellyfin/Action/GetInfo.php b/src/Backends/Jellyfin/Action/GetInfo.php new file mode 100644 index 00000000..22f8e395 --- /dev/null +++ b/src/Backends/Jellyfin/Action/GetInfo.php @@ -0,0 +1,109 @@ +tryResponse( + context: $context, + fn: function () use ($context, $opts) { + $url = $context->backendUrl->withPath('/system/Info'); + + $this->logger->debug('Requesting [{client}: {backend}] info.', [ + 'client' => $context->clientName, + 'backend' => $context->backendName, + 'url' => $url + ]); + + $response = $this->http->request( + 'GET', + (string)$url, + array_replace_recursive($context->backendHeaders, $opts['headers'] ?? []) + ); + + $content = $response->getContent(false); + + if (200 !== $response->getStatusCode()) { + return new Response( + status: false, + error: new Error( + message: 'Request for [{backend}] {action} returned with unexpected [{status_code}] status code.', + context: [ + 'action' => $this->action, + 'client' => $context->clientName, + 'backend' => $context->backendName, + 'status_code' => $response->getStatusCode(), + 'url' => (string)$url, + 'response' => $content, + ], + level: Levels::WARNING + ) + ); + } + + $item = json_decode( + json: $content, + associative: true, + flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE + ); + + if (true === $context->trace) { + $this->logger->debug('Processing [{client}: {backend}] {action} payload.', [ + 'action' => $this->action, + 'client' => $context->clientName, + 'backend' => $context->backendName, + 'trace' => $item, + ]); + } + + $ret = [ + 'type' => $context->clientName, + 'name' => ag($item, 'ServerName'), + 'version' => ag($item, 'Version'), + 'identifier' => ag($item, 'Id'), + 'platform' => ag($item, 'OperatingSystem'), + ]; + + if (true === ag_exists($opts, Options::RAW_RESPONSE)) { + $ret[Options::RAW_RESPONSE] = $item; + } + + return new Response(status: true, response: $ret); + }, + action: $this->action, + ); + } +} diff --git a/src/Backends/Jellyfin/Action/GetVersion.php b/src/Backends/Jellyfin/Action/GetVersion.php new file mode 100644 index 00000000..4f924548 --- /dev/null +++ b/src/Backends/Jellyfin/Action/GetVersion.php @@ -0,0 +1,51 @@ +tryResponse( + context: $context, + fn: function () use ($context, $opts) { + $info = (new GetInfo($this->http, $this->logger, $this->cache))(context: $context, opts: $opts); + + if (false === $info->status) { + return $info; + } + + return new Response(status: true, response: ag($info->response, 'version')); + }, + action: $this->action, + ); + } +} diff --git a/src/Backends/Jellyfin/JellyfinClient.php b/src/Backends/Jellyfin/JellyfinClient.php index 682512e1..d7f4f1a3 100644 --- a/src/Backends/Jellyfin/JellyfinClient.php +++ b/src/Backends/Jellyfin/JellyfinClient.php @@ -11,10 +11,12 @@ use App\Backends\Common\GuidInterface as iGuid; use App\Backends\Jellyfin\Action\Backup; use App\Backends\Jellyfin\Action\Export; use App\Backends\Jellyfin\Action\GetIdentifier; +use App\Backends\Jellyfin\Action\GetInfo; use App\Backends\Jellyfin\Action\GetLibrariesList; use App\Backends\Jellyfin\Action\GetLibrary; use App\Backends\Jellyfin\Action\GetMetaData; use App\Backends\Jellyfin\Action\GetUsersList; +use App\Backends\Jellyfin\Action\GetVersion; use App\Backends\Jellyfin\Action\Import; use App\Backends\Jellyfin\Action\InspectRequest; use App\Backends\Jellyfin\Action\ParseWebhook; @@ -410,6 +412,36 @@ class JellyfinClient implements iClient return $response->response; } + public function getInfo(array $opts = []): array + { + $response = Container::get(GetInfo::class)(context: $this->context, opts: $opts); + + if ($response->hasError()) { + $this->logger->log($response->error->level(), $response->error->message, $response->error->context); + } + + if (false === $response->isSuccessful()) { + throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format())); + } + + return $response->response; + } + + public function getVersion(array $opts = []): string + { + $response = Container::get(GetVersion::class)(context: $this->context, opts: $opts); + + if ($response->hasError()) { + $this->logger->log($response->error->level(), $response->error->message, $response->error->context); + } + + if (false === $response->isSuccessful()) { + throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format())); + } + + return $response->response; + } + public static function manage(array $backend, array $opts = []): array { return Container::get(JellyfinManage::class)->manage(backend: $backend, opts: $opts); diff --git a/src/Backends/Plex/Action/GetIdentifier.php b/src/Backends/Plex/Action/GetIdentifier.php index 1b4b79c2..629645bc 100644 --- a/src/Backends/Plex/Action/GetIdentifier.php +++ b/src/Backends/Plex/Action/GetIdentifier.php @@ -6,8 +6,6 @@ namespace App\Backends\Plex\Action; use App\Backends\Common\CommonTrait; use App\Backends\Common\Context; -use App\Backends\Common\Error; -use App\Backends\Common\Levels; use App\Backends\Common\Response; use Psr\Log\LoggerInterface; use Psr\SimpleCache\CacheInterface; @@ -39,59 +37,13 @@ final class GetIdentifier return $this->tryResponse( context: $context, fn: function () use ($context, $opts) { - $url = $context->backendUrl->withPath('/'); + $info = (new GetInfo($this->http, $this->logger, $this->cache))(context: $context, opts: $opts); - $this->logger->debug('Requesting [{client}: {backend}] unique identifier.', [ - 'client' => $context->clientName, - 'backend' => $context->backendName, - 'url' => $url - ]); - - $response = $this->http->request( - 'GET', - (string)$url, - array_replace_recursive($context->backendHeaders, $opts['headers'] ?? []) - ); - - $content = $response->getContent(false); - - if (200 !== $response->getStatusCode()) { - return new Response( - status: false, - error: new Error( - message: 'Request for [{backend}] {action} returned with unexpected [{status_code}] status code.', - context: [ - 'action' => $this->action, - 'client' => $context->clientName, - 'backend' => $context->backendName, - 'status_code' => $response->getStatusCode(), - 'url' => (string)$url, - 'response' => $content, - ], - level: Levels::WARNING - ) - ); + if (false === $info->status) { + return $info; } - $item = json_decode( - json: $content, - associative: true, - flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE - ); - - if (true === $context->trace) { - $this->logger->debug('Processing [{client}: {backend}] {action} payload.', [ - 'action' => $this->action, - 'client' => $context->clientName, - 'backend' => $context->backendName, - 'trace' => $item, - ]); - } - - return new Response( - status: true, - response: ag($item, 'MediaContainer.machineIdentifier', null) - ); + return new Response(status: true, response: ag($info->response, 'identifier')); }, action: $this->action ); diff --git a/src/Backends/Plex/Action/GetInfo.php b/src/Backends/Plex/Action/GetInfo.php new file mode 100644 index 00000000..90406742 --- /dev/null +++ b/src/Backends/Plex/Action/GetInfo.php @@ -0,0 +1,114 @@ +tryResponse( + context: $context, + fn: function () use ($context, $opts) { + $url = $context->backendUrl->withPath('/'); + + $this->logger->debug('Requesting [{client}: {backend}] unique identifier.', [ + 'client' => $context->clientName, + 'backend' => $context->backendName, + 'url' => $url + ]); + + $response = $this->http->request( + 'GET', + (string)$url, + array_replace_recursive($context->backendHeaders, $opts['headers'] ?? []) + ); + + $content = $response->getContent(false); + + if (200 !== $response->getStatusCode()) { + return new Response( + status: false, + error: new Error( + message: 'Request for [{backend}] {action} returned with unexpected [{status_code}] status code.', + context: [ + 'action' => $this->action, + 'client' => $context->clientName, + 'backend' => $context->backendName, + 'status_code' => $response->getStatusCode(), + 'url' => (string)$url, + 'response' => $content, + ], + level: Levels::WARNING + ) + ); + } + + $item = json_decode( + json: $content, + associative: true, + flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE + ); + + if (true === $context->trace) { + $this->logger->debug('Processing [{client}: {backend}] {action} payload.', [ + 'action' => $this->action, + 'client' => $context->clientName, + 'backend' => $context->backendName, + 'trace' => $item, + ]); + } + + $data = ag($item, 'MediaContainer', []); + + $ret = [ + 'type' => $context->clientName, + 'name' => ag($data, 'friendlyName', null), + 'version' => ag($data, 'version', null), + 'identifier' => ag($data, 'machineIdentifier', null), + 'platform' => ag($data, 'platform', null), + ]; + + if (true === ag_exists($opts, Options::RAW_RESPONSE)) { + $ret[Options::RAW_RESPONSE] = $data; + } + + return new Response( + status: true, + response: $ret, + ); + }, + action: $this->action + ); + } +} diff --git a/src/Backends/Plex/Action/GetVersion.php b/src/Backends/Plex/Action/GetVersion.php new file mode 100644 index 00000000..65dddde9 --- /dev/null +++ b/src/Backends/Plex/Action/GetVersion.php @@ -0,0 +1,51 @@ +tryResponse( + context: $context, + fn: function () use ($context, $opts) { + $info = (new GetInfo($this->http, $this->logger, $this->cache))(context: $context, opts: $opts); + + if (false === $info->status) { + return $info; + } + + return new Response(status: true, response: ag($info->response, 'version')); + }, + action: $this->action + ); + } +} diff --git a/src/Backends/Plex/PlexClient.php b/src/Backends/Plex/PlexClient.php index e392ab94..7f51e54b 100644 --- a/src/Backends/Plex/PlexClient.php +++ b/src/Backends/Plex/PlexClient.php @@ -11,11 +11,13 @@ use App\Backends\Common\GuidInterface as iGuid; use App\Backends\Plex\Action\Backup; use App\Backends\Plex\Action\Export; use App\Backends\Plex\Action\GetIdentifier; +use App\Backends\Plex\Action\GetInfo; use App\Backends\Plex\Action\GetLibrariesList; use App\Backends\Plex\Action\GetLibrary; use App\Backends\Plex\Action\GetMetaData; use App\Backends\Plex\Action\GetUsersList; use App\Backends\Plex\Action\GetUserToken; +use App\Backends\Plex\Action\GetVersion; use App\Backends\Plex\Action\Import; use App\Backends\Plex\Action\InspectRequest; use App\Backends\Plex\Action\ParseWebhook; @@ -406,6 +408,36 @@ class PlexClient implements iClient return $response->response; } + public function getInfo(array $opts = []): array + { + $response = Container::get(GetInfo::class)(context: $this->context, opts: $opts); + + if ($response->hasError()) { + $this->logger->log($response->error->level(), $response->error->message, $response->error->context); + } + + if (false === $response->isSuccessful()) { + throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format())); + } + + return $response->response; + } + + public function getVersion(array $opts = []): string + { + $response = Container::get(GetVersion::class)(context: $this->context, opts: $opts); + + if ($response->hasError()) { + $this->logger->log($response->error->level(), $response->error->message, $response->error->context); + } + + if (false === $response->isSuccessful()) { + throw new RuntimeException(ag($response->extra, 'message', fn() => $response->error->format())); + } + + return $response->response; + } + public static function manage(array $backend, array $opts = []): array { return Container::get(PlexManage::class)->manage(backend: $backend, opts: $opts); diff --git a/src/Commands/Backend/InfoCommand.php b/src/Commands/Backend/InfoCommand.php new file mode 100644 index 00000000..394b3311 --- /dev/null +++ b/src/Commands/Backend/InfoCommand.php @@ -0,0 +1,75 @@ +setName(self::ROUTE) + ->setDescription('Get backend product info.') + ->addOption('include-raw-response', null, InputOption::VALUE_NONE, 'Include unfiltered raw response.') + ->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.') + ->addArgument('backend', InputArgument::REQUIRED, 'Backend name to restore.'); + } + + protected function runCommand(InputInterface $input, OutputInterface $output): int + { + $name = $input->getArgument('backend'); + $mode = $input->getOption('output'); + $opts = []; + + if (($config = $input->getOption('config'))) { + try { + Config::save('servers', Yaml::parseFile($this->checkCustomBackendsFile($config))); + } catch (RuntimeException $e) { + $output->writeln(sprintf('%s', $e->getMessage())); + return self::FAILURE; + } + } + + if (null === ag(Config::get('servers', []), $name, null)) { + $output->writeln(sprintf('ERROR: Backend \'%s\' not found.', $name)); + return self::FAILURE; + } + + if ($input->getOption('include-raw-response')) { + $opts[Options::RAW_RESPONSE] = true; + } + + $backend = $this->getBackend($name); + + $info = $backend->getInfo($opts); + + if ('table' === $mode) { + foreach ($info as $_ => &$val) { + if (false === is_bool($val)) { + continue; + } + $val = $val ? 'Yes' : 'No'; + } + + $info = [$info]; + } + + $this->displayContent($info, $output, $mode); + + return self::SUCCESS; + } +} diff --git a/src/Commands/Backend/VersionCommand.php b/src/Commands/Backend/VersionCommand.php new file mode 100644 index 00000000..fc3293f1 --- /dev/null +++ b/src/Commands/Backend/VersionCommand.php @@ -0,0 +1,52 @@ +setName(self::ROUTE) + ->setDescription('Get backend product version.') + ->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.') + ->addArgument('backend', InputArgument::REQUIRED, 'Backend name to restore.'); + } + + protected function runCommand(InputInterface $input, OutputInterface $output): int + { + $name = $input->getArgument('backend'); + + if (($config = $input->getOption('config'))) { + try { + Config::save('servers', Yaml::parseFile($this->checkCustomBackendsFile($config))); + } catch (RuntimeException $e) { + $output->writeln(sprintf('%s', $e->getMessage())); + return self::FAILURE; + } + } + + if (null === ag(Config::get('servers', []), $name, null)) { + $output->writeln(sprintf('ERROR: Backend \'%s\' not found.', $name)); + return self::FAILURE; + } + + $output->writeln($this->getBackend($name)->getVersion()); + + return self::SUCCESS; + } +}