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;
+ }
+}