System-Wide Strict Types (#4405)

This commit is contained in:
Buster "Silver Eagle" Neece 2021-07-19 00:53:45 -05:00 committed by GitHub
parent 64b7d83258
commit 5cbacd5df6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
613 changed files with 3144 additions and 1291 deletions

View File

@ -1,7 +1,10 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
ini_set('display_errors', 1);
ini_set('display_errors', '1');
$autoloader = require dirname(__DIR__) . '/vendor/autoload.php';

View File

@ -1,7 +1,10 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
ini_set('display_errors', 1);
ini_set('display_errors', '1');
$autoloader = require dirname(__DIR__) . '/vendor/autoload.php';

View File

@ -1,7 +1,10 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
ini_set('display_errors', 1);
ini_set('display_errors', '1');
class Spinner
{

12
composer.lock generated
View File

@ -102,17 +102,19 @@
"source": {
"type": "git",
"url": "https://github.com/AzuraCast/azuraforms.git",
"reference": "6fb50189f7286bdf6bca63047bc145867051c0a1"
"reference": "8a32a97e4d633eed0dca09ea1107d88256281aed"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/AzuraCast/azuraforms/zipball/6fb50189f7286bdf6bca63047bc145867051c0a1",
"reference": "6fb50189f7286bdf6bca63047bc145867051c0a1",
"url": "https://api.github.com/repos/AzuraCast/azuraforms/zipball/8a32a97e4d633eed0dca09ea1107d88256281aed",
"reference": "8a32a97e4d633eed0dca09ea1107d88256281aed",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": ">=7.4"
"league/mime-type-detection": "^1.7",
"php": ">=7.4",
"psr/http-message": ">1.0"
},
"require-dev": {
"php-parallel-lint/php-console-highlighter": "^0.5.0",
@ -165,7 +167,7 @@
"type": "patreon"
}
],
"time": "2021-04-26T10:41:32+00:00"
"time": "2021-07-18T17:02:47+00:00"
},
{
"name": "azuracast/flysystem-v2-extensions",

View File

@ -148,12 +148,12 @@ return [
return '$(function () { ' . implode('', $notifies) . ' });';
},
function (Request $request) {
/** @var Locale|null $locale */
/** @var App\Locale|null $locale */
$localeObj = $request->getAttribute(ServerRequest::ATTR_LOCALE);
$locale = ($localeObj instanceof App\Locale)
? (string)$localeObj
: Locale::DEFAULT_LOCALE;
: App\Locale::DEFAULT_LOCALE;
$locale = explode('.', $locale, 2)[0];
$localeShort = substr($locale, 0, 2);

View File

@ -17,32 +17,32 @@ return function (App\Event\BuildAdminMenu $e) {
'items' => [
'settings' => [
'label' => __('System Settings'),
'url' => $router->named('admin:settings:index'),
'url' => (string)$router->named('admin:settings:index'),
'permission' => Acl::GLOBAL_SETTINGS,
],
'branding' => [
'label' => __('Custom Branding'),
'url' => $router->named('admin:branding:index'),
'url' => (string)$router->named('admin:branding:index'),
'permission' => Acl::GLOBAL_SETTINGS,
],
'logs' => [
'label' => __('System Logs'),
'url' => $router->named('admin:logs:index'),
'url' => (string)$router->named('admin:logs:index'),
'permission' => Acl::GLOBAL_LOGS,
],
'storage_locations' => [
'label' => __('Storage Locations'),
'url' => $router->named('admin:storage_locations:index'),
'url' => (string)$router->named('admin:storage_locations:index'),
'permission' => Acl::GLOBAL_STORAGE_LOCATIONS,
],
'backups' => [
'label' => __('Backups'),
'url' => $router->named('admin:backups:index'),
'url' => (string)$router->named('admin:backups:index'),
'permission' => Acl::GLOBAL_BACKUPS,
],
'debug' => [
'label' => __('System Debugger'),
'url' => $router->named('admin:debug:index'),
'url' => (string)$router->named('admin:debug:index'),
'permission' => Acl::GLOBAL_ALL,
],
],
@ -53,22 +53,22 @@ return function (App\Event\BuildAdminMenu $e) {
'items' => [
'manage_users' => [
'label' => __('User Accounts'),
'url' => $router->named('admin:users:index'),
'url' => (string)$router->named('admin:users:index'),
'permission' => Acl::GLOBAL_ALL,
],
'permissions' => [
'label' => __('Permissions'),
'url' => $router->named('admin:permissions:index'),
'url' => (string)$router->named('admin:permissions:index'),
'permission' => Acl::GLOBAL_ALL,
],
'auditlog' => [
'label' => __('Audit Log'),
'url' => $router->named('admin:auditlog:index'),
'url' => (string)$router->named('admin:auditlog:index'),
'permission' => Acl::GLOBAL_LOGS,
],
'api_keys' => [
'label' => __('API Keys'),
'url' => $router->named('admin:api:index'),
'url' => (string)$router->named('admin:api:index'),
'permission' => Acl::GLOBAL_API_KEYS,
],
],
@ -79,27 +79,27 @@ return function (App\Event\BuildAdminMenu $e) {
'items' => [
'manage_stations' => [
'label' => __('Stations'),
'url' => $router->named('admin:stations:index'),
'url' => (string)$router->named('admin:stations:index'),
'permission' => Acl::GLOBAL_STATIONS,
],
'custom_fields' => [
'label' => __('Custom Fields'),
'url' => $router->named('admin:custom_fields:index'),
'url' => (string)$router->named('admin:custom_fields:index'),
'permission' => Acl::GLOBAL_CUSTOM_FIELDS,
],
'relays' => [
'label' => __('Connected AzuraRelays'),
'url' => $router->named('admin:relays:index'),
'url' => (string)$router->named('admin:relays:index'),
'permission' => Acl::GLOBAL_STATIONS,
],
'shoutcast' => [
'label' => __('Install SHOUTcast'),
'url' => $router->named('admin:install_shoutcast:index'),
'url' => (string)$router->named('admin:install_shoutcast:index'),
'permission' => Acl::GLOBAL_ALL,
],
'geolite' => [
'label' => __('Install GeoLite IP Database'),
'url' => $router->named('admin:install_geolite:index'),
'url' => (string)$router->named('admin:install_geolite:index'),
'permission' => Acl::GLOBAL_ALL,
],
],

View File

@ -21,7 +21,7 @@ return function (App\Event\BuildStationMenu $e) {
'label' => __('Start Station'),
'title' => __('Ready to start broadcasting? Click to start your station.'),
'icon' => 'refresh',
'url' => $router->fromHere('api:stations:restart'),
'url' => (string)$router->fromHere('api:stations:restart'),
'class' => 'api-call text-success',
'confirm' => __('Restart broadcasting? This will disconnect any current listeners.'),
'visible' => !$station->getHasStarted(),
@ -31,7 +31,7 @@ return function (App\Event\BuildStationMenu $e) {
'label' => __('Restart to Apply Changes'),
'title' => __('Click to restart your station and apply configuration changes.'),
'icon' => 'refresh',
'url' => $router->fromHere('api:stations:restart'),
'url' => (string)$router->fromHere('api:stations:restart'),
'class' => 'api-call text-warning',
'confirm' => __('Restart broadcasting? This will disconnect any current listeners.'),
'visible' => $station->getHasStarted() && $station->getNeedsRestart(),
@ -40,53 +40,53 @@ return function (App\Event\BuildStationMenu $e) {
'profile' => [
'label' => __('Profile'),
'icon' => 'image',
'url' => $router->fromHere('stations:profile:index'),
'url' => (string)$router->fromHere('stations:profile:index'),
],
'public' => [
'label' => __('Public Page'),
'icon' => 'public',
'url' => $router->named('public:index', ['station_id' => $station->getShortName()]),
'url' => (string)$router->named('public:index', ['station_id' => $station->getShortName()]),
'external' => true,
'visible' => $station->getEnablePublicPage(),
],
'ondemand' => [
'label' => __('On-Demand Media'),
'icon' => 'cloud_download',
'url' => $router->named('public:ondemand', ['station_id' => $station->getShortName()]),
'url' => (string)$router->named('public:ondemand', ['station_id' => $station->getShortName()]),
'external' => true,
'visible' => $station->getEnableOnDemand(),
],
'files' => [
'label' => __('Music Files'),
'icon' => 'library_music',
'url' => $router->fromHere('stations:files:index'),
'url' => (string)$router->fromHere('stations:files:index'),
'visible' => $backend->supportsMedia(),
'permission' => Acl::STATION_MEDIA,
],
'playlists' => [
'label' => __('Playlists'),
'icon' => 'queue_music',
'url' => $router->fromHere('stations:playlists:index'),
'url' => (string)$router->fromHere('stations:playlists:index'),
'visible' => $backend->supportsMedia(),
'permission' => Acl::STATION_MEDIA,
],
'podcasts' => [
'label' => __('Podcasts (Beta)'),
'icon' => 'cast',
'url' => $router->fromHere('stations:podcasts:index'),
'url' => (string)$router->fromHere('stations:podcasts:index'),
'permission' => Acl::STATION_PODCASTS,
],
'streamers' => [
'label' => __('Streamer/DJ Accounts'),
'icon' => 'mic',
'url' => $router->fromHere('stations:streamers:index'),
'url' => (string)$router->fromHere('stations:streamers:index'),
'visible' => $backend->supportsStreamers(),
'permission' => Acl::STATION_STREAMERS,
],
'web_dj' => [
'label' => __('Web DJ'),
'icon' => 'surround_sound',
'url' => $router->named('public:dj', ['station_id' => $station->getShortName()], [], true)
'url' => (string)$router->named('public:dj', ['station_id' => $station->getShortName()], [], true)
->withScheme('https'),
'visible' => $station->getEnablePublicPage() && $station->getEnableStreamers(),
'external' => true,
@ -94,20 +94,20 @@ return function (App\Event\BuildStationMenu $e) {
'mounts' => [
'label' => __('Mount Points'),
'icon' => 'wifi_tethering',
'url' => $router->fromHere('stations:mounts:index'),
'url' => (string)$router->fromHere('stations:mounts:index'),
'visible' => $frontend->supportsMounts(),
'permission' => Acl::STATION_MOUNTS,
],
'remotes' => [
'label' => __('Remote Relays'),
'icon' => 'router',
'url' => $router->fromHere('stations:remotes:index'),
'url' => (string)$router->fromHere('stations:remotes:index'),
'permission' => Acl::STATION_REMOTES,
],
'webhooks' => [
'label' => __('Web Hooks'),
'icon' => 'code',
'url' => $router->fromHere('stations:webhooks:index'),
'url' => (string)$router->fromHere('stations:webhooks:index'),
'permission' => Acl::STATION_WEB_HOOKS,
],
'reports' => [
@ -117,40 +117,40 @@ return function (App\Event\BuildStationMenu $e) {
'items' => [
'reports_overview' => [
'label' => __('Statistics Overview'),
'url' => $router->fromHere('stations:reports:overview'),
'url' => (string)$router->fromHere('stations:reports:overview'),
],
'reports_listeners' => [
'label' => __('Listeners'),
'url' => $router->fromHere('stations:reports:listeners'),
'url' => (string)$router->fromHere('stations:reports:listeners'),
'visible' => $frontend->supportsListenerDetail(),
],
'reports_requests' => [
'label' => __('Song Requests'),
'url' => $router->fromHere('stations:reports:requests'),
'url' => (string)$router->fromHere('stations:reports:requests'),
'visible' => $station->getEnableRequests(),
],
'reports_timeline' => [
'label' => __('Song Playback Timeline'),
'url' => $router->fromHere('stations:reports:timeline'),
'url' => (string)$router->fromHere('stations:reports:timeline'),
],
'reports_performance' => [
'label' => __('Song Listener Impact'),
'url' => $router->fromHere('stations:reports:performance'),
'url' => (string)$router->fromHere('stations:reports:performance'),
'visible' => $backend->supportsMedia(),
],
'reports_duplicates' => [
'label' => __('Duplicate Songs'),
'url' => $router->fromHere('stations:files:index') . '#special:duplicates',
'url' => (string)$router->fromHere('stations:files:index') . '#special:duplicates',
'visible' => $backend->supportsMedia(),
],
'reports_unprocessable' => [
'label' => __('Unprocessable Files'),
'url' => $router->fromHere('stations:files:index') . '#special:unprocessable',
'url' => (string)$router->fromHere('stations:files:index') . '#special:unprocessable',
'visible' => $backend->supportsMedia(),
],
'reports_soundexchange' => [
'label' => __('SoundExchange Royalties'),
'url' => $router->fromHere('stations:reports:soundexchange'),
'url' => (string)$router->fromHere('stations:reports:soundexchange'),
'visible' => $frontend->supportsListenerDetail(),
],
],
@ -161,36 +161,36 @@ return function (App\Event\BuildStationMenu $e) {
'items' => [
'sftp_users' => [
'label' => __('SFTP Users'),
'url' => $router->fromHere('stations:sftp_users:index'),
'url' => (string)$router->fromHere('stations:sftp_users:index'),
'visible' => App\Service\SftpGo::isSupportedForStation($station),
'permission' => Acl::STATION_MEDIA,
],
'automation' => [
'label' => __('Automated Assignment'),
'url' => $router->fromHere('stations:automation:index'),
'url' => (string)$router->fromHere('stations:automation:index'),
'visible' => $backend->supportsMedia(),
'permission' => Acl::STATION_AUTOMATION,
],
'ls_config' => [
'label' => __('Edit Liquidsoap Configuration'),
'url' => $router->fromHere('stations:util:ls_config'),
'url' => (string)$router->fromHere('stations:util:ls_config'),
'visible' => $settings->getEnableAdvancedFeatures()
&& $backend instanceof App\Radio\Backend\Liquidsoap,
'permission' => Acl::STATION_BROADCASTING,
],
'logs' => [
'label' => __('Log Viewer'),
'url' => $router->fromHere('stations:logs:index'),
'url' => (string)$router->fromHere('stations:logs:index'),
'permission' => Acl::STATION_LOGS,
],
'queue' => [
'label' => __('Upcoming Song Queue'),
'url' => $router->fromHere('stations:queue:index'),
'url' => (string)$router->fromHere('stations:queue:index'),
'permission' => Acl::STATION_BROADCASTING,
],
'restart' => [
'label' => __('Restart Broadcasting'),
'url' => $router->fromHere('api:stations:restart'),
'url' => (string)$router->fromHere('api:stations:restart'),
'class' => 'api-call',
'confirm' => __('Restart broadcasting? This will disconnect any current listeners.'),
'permission' => Acl::STATION_BROADCASTING,

View File

@ -30,6 +30,10 @@
<exclude-pattern>src/Installer/EnvFiles/*.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification">
<exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification"/>
</rule>
<rule ref="SlevomatCodingStandard.Arrays.TrailingArrayComma"/>
<rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHint">

View File

@ -1,5 +1,8 @@
parameters:
level: 3
level: 8
checkGenericClassInNonGenericObjectType: false
checkMissingIterableValueType: false
paths:
- src
@ -15,6 +18,13 @@ parameters:
# Caused by Symfony Validator (perhaps wrongly) returning the interface.
- '#Cannot cast Symfony\\Component\\Validator\\ConstraintViolationListInterface to string.#'
# Some doctrine migrations fail because of these
- '#Parameter \#3 \$criteria of method Doctrine\\DBAL\\Connection::update\(\) expects array<string, mixed>, array<int, int> given.#'
- '#Parameter \#2 \$criteria of method Doctrine\\DBAL\\Connection::delete\(\) expects array<string, mixed>, array<int, int> given.#'
# Known upstream issue with Plates template engine
- '#Parameter \#2 \$callback of method League\\Plates\\Engine::registerFunction\(\) expects League\\Plates\\callback, Closure given.#'
parallel:
maximumNumberOfProcesses: 1

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App;
use App\Entity;
@ -74,6 +76,7 @@ class Acl
public function listPermissions(): array
{
if (!isset($this->permissions)) {
/** @var array<string,array> $permissions */
$permissions = [
'global' => [
self::GLOBAL_ALL => __('All Permissions'),
@ -118,7 +121,7 @@ class Acl
* @param array|string $action
* @param int|Entity\Station|null $stationId
*/
public function isAllowed(array|string $action, $stationId = null): bool
public function isAllowed(array|string $action, Entity\Station|int $stationId = null): bool
{
if ($this->request instanceof ServerRequestInterface) {
$user = $this->request->getAttribute(ServerRequest::ATTR_USER);
@ -135,8 +138,11 @@ class Acl
* @param array|string $action
* @param int|Entity\Station|null $stationId
*/
public function userAllowed(?Entity\User $user = null, array|string $action, $stationId = null): bool
{
public function userAllowed(
?Entity\User $user = null,
array|string $action,
Entity\Station|int $stationId = null
): bool {
if (null === $user) {
return false;
}
@ -172,7 +178,7 @@ class Acl
* @param array|string $action
* @param int|Entity\Station|null $station_id
*/
public function roleAllowed(array|int $role_id, array|string $action, $station_id = null): bool
public function roleAllowed(array|int $role_id, array|string $action, Entity\Station|int $station_id = null): bool
{
if ($station_id instanceof Entity\Station) {
$station_id = $station_id->getId();

View File

@ -1,10 +1,13 @@
<?php
declare(strict_types=1);
namespace App;
use App\Console\Application;
use App\Http\Factory\ResponseFactory;
use App\Http\Factory\ServerRequestFactory;
use Composer\Autoload\ClassLoader;
use DI;
use DI\Bridge\Slim\ControllerInvoker;
use Doctrine\Common\Annotations\AnnotationRegistry;
@ -26,14 +29,32 @@ use const E_USER_ERROR;
class AppFactory
{
public static function createApp($autoloader = null, $appEnvironment = [], $diDefinitions = []): App
{
/**
* @param ClassLoader|null $autoloader
* @param array<string, mixed> $appEnvironment
* @param array<string, mixed> $diDefinitions
*
*/
public static function createApp(
?ClassLoader $autoloader = null,
array $appEnvironment = [],
array $diDefinitions = []
): App {
$di = self::buildContainer($autoloader, $appEnvironment, $diDefinitions);
return self::buildAppFromContainer($di);
}
public static function createCli($autoloader = null, $appEnvironment = [], $diDefinitions = []): Application
{
/**
* @param ClassLoader|null $autoloader
* @param array<string, mixed> $appEnvironment
* @param array<string, mixed> $diDefinitions
*
*/
public static function createCli(
?ClassLoader $autoloader = null,
array $appEnvironment = [],
array $diDefinitions = []
): Application {
$di = self::buildContainer($autoloader, $appEnvironment, $diDefinitions);
self::buildAppFromContainer($di);
@ -83,11 +104,18 @@ class AppFactory
return $app;
}
/** @noinspection SummerTimeUnsafeTimeManipulationInspection */
/**
* @param ClassLoader|null $autoloader
* @param array<string, mixed> $appEnvironment
* @param array<string, mixed> $diDefinitions
*
* @noinspection SummerTimeUnsafeTimeManipulationInspection
*
*/
public static function buildContainer(
$autoloader = null,
$appEnvironment = [],
$diDefinitions = []
?ClassLoader $autoloader = null,
array $appEnvironment = [],
array $diDefinitions = []
): DI\Container {
// Register Annotation autoloader
if (null !== $autoloader) {
@ -165,7 +193,10 @@ class AppFactory
return $di;
}
public static function buildEnvironment(array $environment): Environment
/**
* @param array<string, mixed> $environment
*/
public static function buildEnvironment(array $environment = []): Environment
{
if (!isset($environment[Environment::BASE_DIR])) {
throw new Exception\BootstrapException('No base directory specified!');
@ -180,7 +211,10 @@ class AppFactory
$environment[Environment::VIEWS_DIR] ??= $environment[Environment::BASE_DIR] . '/templates';
if (file_exists($environment[Environment::BASE_DIR] . '/env.ini')) {
$_ENV = array_merge($_ENV, parse_ini_file($environment[Environment::BASE_DIR] . '/env.ini'));
$envIni = parse_ini_file($environment[Environment::BASE_DIR] . '/env.ini');
if (false !== $envIni) {
$_ENV = array_merge($_ENV, $envIni);
}
} else {
$_ENV = getenv();
}

View File

@ -1,11 +1,14 @@
<?php
declare(strict_types=1);
namespace App;
use App\Traits\RequestAwareTrait;
use App\Utilities\Json;
use GuzzleHttp\Psr7\Uri;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use function base64_encode;
use function is_array;
@ -22,13 +25,13 @@ class Assets
{
use RequestAwareTrait;
/** @var array Known libraries loaded in initialization. */
/** @var array<string, array> Known libraries loaded in initialization. */
protected array $libraries = [];
/** @var array An optional array lookup for versioned files. */
/** @var array<string, string> An optional array lookup for versioned files. */
protected array $versioned_files = [];
/** @var array Loaded libraries. */
/** @var array<string, array> Loaded libraries. */
protected array $loaded = [];
/** @var bool Whether the current loaded libraries have been sorted by order. */
@ -40,9 +43,6 @@ class Assets
/** @var array The loaded domains that should be included in the CSP header. */
protected array $csp_domains;
/** @var ServerRequestInterface|null The current request (if it's available) */
protected ?ServerRequestInterface $request = null;
public function __construct(
protected Environment $environment,
Config $config
@ -51,18 +51,10 @@ class Assets
$this->addLibrary($library, $library_name);
}
$versioned_files = [];
$assets_file = $environment->getBaseDirectory() . '/web/static/assets.json';
if (is_file($assets_file)) {
$versioned_files = json_decode(file_get_contents($assets_file), true, 512, JSON_THROW_ON_ERROR);
}
$versioned_files = Json::loadFromFile($environment->getBaseDirectory() . '/web/static/assets.json');
$this->versioned_files = $versioned_files;
$vueComponents = [];
$assets_file = $environment->getBaseDirectory() . '/web/static/webpack.json';
if (is_file($assets_file)) {
$vueComponents = json_decode(file_get_contents($assets_file), true, 512, JSON_THROW_ON_ERROR);
}
$vueComponents = Json::loadFromFile($environment->getBaseDirectory() . '/web/static/webpack.json');
$this->addVueComponents($vueComponents);
$this->csp_nonce = (string)preg_replace('/[^A-Za-z0-9\+\/=]/', '', base64_encode(random_bytes(18)));
@ -538,10 +530,9 @@ class Assets
*/
protected function addDomainToCsp(string $src): void
{
$src_parts = parse_url($src);
$domain = $src_parts['scheme'] . '://' . $src_parts['host'];
$uri = new Uri($src);
$domain = $uri->getScheme() . '://' . $uri->getHost();
if (!isset($this->csp_domains[$domain])) {
$this->csp_domains[$domain] = $domain;
}
@ -550,7 +541,7 @@ class Assets
public function writeCsp(ResponseInterface $response): ResponseInterface
{
$csp = [];
if ('https' === $this->request->getUri()->getScheme()) {
if (null !== $this->request && 'https' === $this->request->getUri()->getScheme()) {
$csp[] = 'upgrade-insecure-requests';
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App;
use App\Entity\Repository\UserRepository;
@ -52,7 +54,7 @@ class Auth
*
* @throws Exception
*/
public function getLoggedInUser($real_user_only = false): ?User
public function getLoggedInUser(bool $real_user_only = false): ?User
{
if (!$real_user_only && $this->isMasqueraded()) {
return $this->getMasquerade();
@ -193,13 +195,16 @@ class Auth
*/
public function getMasquerade(): ?User
{
return $this->masqueraded_user;
if ($this->masqueraded_user instanceof User) {
return $this->masqueraded_user;
}
return null;
}
/**
* Become a different user across the application.
*
* @param array|User $user_info
* @param array<string, mixed>|User $user_info
*/
public function masqueradeAsUser(User|array $user_info): void
{

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App;
use const EXTR_OVERWRITE;
@ -17,7 +19,7 @@ class Config
* @param string $name
* @param array $inject_vars Variables to pass into the scope of the configuration.
*
* @return mixed[]
* @return array<mixed>
* @noinspection PhpIncludeInspection
* @noinspection UselessUnsetInspection
*/

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console;
use Symfony\Component\Console\Input\ArrayInput;
@ -22,6 +24,10 @@ class Application extends \Silly\Edition\PhpDi\Application
$input->setInteractive(false);
$temp_stream = fopen($outputFile, 'wb+');
if (false === $temp_stream) {
throw new \RuntimeException(sprintf('Could not open output file: "%s"', $outputFile));
}
$output = new StreamOutput($temp_stream);
$command = $this->find($command);
@ -31,7 +37,7 @@ class Application extends \Silly\Edition\PhpDi\Application
$result_output = stream_get_contents($temp_stream);
fclose($temp_stream);
$result_output = trim($result_output);
$result_output = trim((string)$result_output);
return [
$result_code,

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Backup;
use App\Console\Command\CommandAbstract;
@ -118,7 +120,7 @@ class BackupCommand extends CommandAbstract
// Strip leading slashes from backup paths.
$files_to_backup = array_map(
static function ($val) {
static function (string $val) {
if (str_starts_with($val, '/')) {
return substr($val, 1);
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Backup;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command;
use App\Entity\Repository\SettingsRepository;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command;
use App\Console\Application;
@ -20,7 +22,7 @@ abstract class CommandAbstract
return $this->application;
}
protected function runCommand(OutputInterface $output, $command_name, $command_args = []): void
protected function runCommand(OutputInterface $output, string $command_name, array $command_args = []): void
{
$command = $this->getApplication()->find($command_name);

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Debug;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command;
use App\Environment;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command;
use App\Entity;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Internal;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Internal;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Internal;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Internal;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Internal;
use App\Console\Command\CommandAbstract;
@ -12,7 +14,7 @@ class GetIpCommand extends CommandAbstract
SymfonyStyle $io,
AzuraCastCentral $acCentral
): int {
$io->write($acCentral->getIp());
$io->write($acCentral->getIp() ?? 'Unknown');
return 0;
}
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Internal;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Internal;
use App\Console\Command\CommandAbstract;
@ -18,9 +20,9 @@ class SftpAuthCommand extends CommandAbstract
SymfonyStyle $io,
EntityManagerInterface $em
): int {
$username = getenv('SFTPGO_AUTHD_USERNAME');
$password = getenv('SFTPGO_AUTHD_PASSWORD');
$pubKey = getenv('SFTPGO_AUTHD_PUBLIC_KEY');
$username = getenv('SFTPGO_AUTHD_USERNAME') ?: null;
$password = getenv('SFTPGO_AUTHD_PASSWORD') ?: null;
$pubKey = getenv('SFTPGO_AUTHD_PUBLIC_KEY') ?: null;
$sftpUser = $em->getRepository(SftpUser::class)->findOneBy(['username' => $username]);

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Internal;
use App\Console\Application;
@ -63,6 +65,11 @@ class SftpEventCommand extends CommandAbstract
return 1;
}
if (null === $path) {
$this->logger->error('No path specified for action.');
return 1;
}
return match ($action) {
'upload' => $this->handleNewUpload($storageLocation, $path),
'pre-delete' => $this->handleDelete($storageLocation, $path),
@ -99,7 +106,7 @@ class SftpEventCommand extends CommandAbstract
);
$message = new Message\AddNewMediaMessage();
$message->storage_location_id = $storageLocation->getId();
$message->storage_location_id = $storageLocation->getIdRequired();
$message->path = $relativePath;
$this->messageBus->dispatch($message);
@ -148,8 +155,13 @@ class SftpEventCommand extends CommandAbstract
protected function handleRename(
Entity\StorageLocation $storageLocation,
string $path,
string $newPath
?string $newPath
): int {
if (null === $newPath) {
$this->logger->error('No new path specified for rename.');
return 1;
}
$pathPrefixer = new PathPrefixer($storageLocation->getPath(), DIRECTORY_SEPARATOR);
$from = $pathPrefixer->stripPrefix($path);

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Locale;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Locale;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\MessageQueue;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\MessageQueue;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command;
use App\Environment;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command;
use App\Entity;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command;
use App\Entity\Repository\StationRepository;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Settings;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Settings;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command;
use App\Entity;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command;
use App\Environment;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command;
use App;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Traits;
use Symfony\Component\Console\Style\SymfonyStyle;
@ -9,8 +11,8 @@ trait PassThruProcess
{
protected function passThruProcess(
SymfonyStyle $io,
$cmd,
$cwd = null,
string|array $cmd,
?string $cwd = null,
array $env = [],
int $timeout = 14400
): Process {

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Users;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Users;
use App\Console\Command\CommandAbstract;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Command\Users;
use App\Acl;
@ -25,11 +27,19 @@ class SetAdministratorCommand extends CommandAbstract
$admin_role = $em->getRepository(Entity\Role::class)
->find(Entity\Role::SUPER_ADMINISTRATOR_ROLE_ID);
$perms_repo->setActionsForRole($admin_role, [
'actions_global' => [
Acl::GLOBAL_ALL,
],
]);
if (null === $admin_role) {
$io->error('Administrator role not found.');
return 1;
}
$perms_repo->setActionsForRole(
$admin_role,
[
'actions_global' => [
Acl::GLOBAL_ALL,
],
]
);
$user_roles = $user->getRoles();

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console;
use Psr\Log\LoggerInterface;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Entity;
@ -16,8 +18,8 @@ abstract class AbstractLogViewerController
protected function view(
ServerRequest $request,
Response $response,
$log_path,
$tail_file = true
string $log_path,
bool $tail_file = true
): ResponseInterface {
clearstatcache();
@ -26,7 +28,7 @@ abstract class AbstractLogViewerController
}
if (!$tail_file) {
$log = file_get_contents($log_path);
$log = file_get_contents($log_path) ?: '';
$log_contents = $this->processLog($request, $log);
return $response->withJson([
@ -55,8 +57,12 @@ abstract class AbstractLogViewerController
if ($log_visible_size > 0) {
$fp = fopen($log_path, 'rb');
if (false === $fp) {
throw new \RuntimeException(sprintf('Could not open file at path "%s".', $log_path));
}
fseek($fp, -$log_visible_size, SEEK_END);
$log_contents_raw = fread($fp, $log_visible_size);
$log_contents_raw = fread($fp, $log_visible_size) ?: '';
fclose($fp);
$log_contents = $this->processLog($request, $log_contents_raw, $cut_first_line, true);
@ -91,7 +97,7 @@ abstract class AbstractLogViewerController
}
/**
* @return mixed[]
* @return array<string, array>
*/
protected function getStationLogs(Entity\Station $station): array
{

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Exception\NotFoundException;
@ -28,19 +30,19 @@ abstract class AbstractAdminCrudController
/**
* @param ServerRequest $request
* @param string|int|null $id
* @param int|string|null $id
*
*/
protected function doEdit(ServerRequest $request, $id = null): object|bool|null
protected function doEdit(ServerRequest $request, int|string $id = null): object|bool|null
{
$record = $this->getRecord($id);
return $this->form->process($request, $record);
}
/**
* @param string|int|null $id
* @param int|string|null $id
*/
protected function getRecord($id = null): ?object
protected function getRecord(int|string $id = null): ?object
{
if (null === $id) {
return null;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Form\ApiKeyForm;
@ -33,25 +35,33 @@ class ApiController extends AbstractAdminCrudController
]);
}
public function editAction(ServerRequest $request, Response $response, $id): ResponseInterface
public function editAction(ServerRequest $request, Response $response, string $id): ResponseInterface
{
if (false !== $this->doEdit($request, $id)) {
$request->getFlash()->addMessage(__('API Key updated.'), Flash::SUCCESS);
return $response->withRedirect($request->getRouter()->named('admin:api:index'));
return $response->withRedirect((string)$request->getRouter()->named('admin:api:index'));
}
return $request->getView()->renderToResponse($response, 'system/form_page', [
'form' => $this->form,
'render_mode' => 'edit',
'title' => __('Edit API Key'),
]);
return $request->getView()->renderToResponse(
$response,
'system/form_page',
[
'form' => $this->form,
'render_mode' => 'edit',
'title' => __('Edit API Key'),
]
);
}
public function deleteAction(ServerRequest $request, Response $response, $id, $csrf): ResponseInterface
{
public function deleteAction(
ServerRequest $request,
Response $response,
string $id,
string $csrf
): ResponseInterface {
$this->doDelete($request, $id, $csrf);
$request->getFlash()->addMessage(__('API Key deleted.'), Flash::SUCCESS);
return $response->withRedirect($request->getRouter()->named('admin:api:index'));
return $response->withRedirect((string)$request->getRouter()->named('admin:api:index'));
}
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Http\Response;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Config;
@ -91,7 +93,7 @@ class BackupsController extends AbstractLogViewerController
if (false !== $settingsForm->process($request)) {
$request->getFlash()->addMessage(__('Changes saved.'), Flash::SUCCESS);
return $response->withRedirect($request->getRouter()->fromHere('admin:backups:index'));
return $response->withRedirect((string)$request->getRouter()->fromHere('admin:backups:index'));
}
return $request->getView()->renderToResponse(
@ -123,7 +125,7 @@ class BackupsController extends AbstractLogViewerController
);
// Handle submission.
if ($request->isPost() && $runForm->isValid($request->getParsedBody())) {
if ($runForm->isValid($request)) {
$data = $runForm->getValues();
$tempFile = File::generateTempPath('backup.log');
@ -176,7 +178,7 @@ class BackupsController extends AbstractLogViewerController
public function downloadAction(
ServerRequest $request,
Response $response,
$path
string $path
): ResponseInterface {
[$path, $fs] = $this->getFile($path);
@ -186,8 +188,12 @@ class BackupsController extends AbstractLogViewerController
->streamFilesystemFile($fs, $path);
}
public function deleteAction(ServerRequest $request, Response $response, $path, $csrf): ResponseInterface
{
public function deleteAction(
ServerRequest $request,
Response $response,
string $path,
string $csrf
): ResponseInterface {
$request->getCsrf()->verify($csrf, $this->csrfNamespace);
[$path, $fs] = $this->getFile($path);
@ -196,7 +202,7 @@ class BackupsController extends AbstractLogViewerController
$fs->delete($path);
$request->getFlash()->addMessage('<b>' . __('Backup deleted.') . '</b>', Flash::SUCCESS);
return $response->withRedirect($request->getRouter()->named('admin:backups:index'));
return $response->withRedirect((string)$request->getRouter()->named('admin:backups:index'));
}
/**

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Form\BrandingSettingsForm;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity;
@ -33,29 +35,37 @@ class CustomFieldsController extends AbstractAdminCrudController
]);
}
public function editAction(ServerRequest $request, Response $response, $id = null): ResponseInterface
public function editAction(ServerRequest $request, Response $response, int $id = null): ResponseInterface
{
if (false !== $this->doEdit($request, $id)) {
$request->getFlash()->addMessage(
($id ? __('Custom Field updated.') : __('Custom Field added.')),
Flash::SUCCESS
);
return $response->withRedirect($request->getRouter()->named('admin:custom_fields:index'));
return $response->withRedirect((string)$request->getRouter()->named('admin:custom_fields:index'));
}
return $request->getView()->renderToResponse($response, 'system/form_page', [
'form' => $this->form,
'render_mode' => 'edit',
'title' => $id ? __('Edit Custom Field') : __('Add Custom Field'),
]);
return $request->getView()->renderToResponse(
$response,
'system/form_page',
[
'form' => $this->form,
'render_mode' => 'edit',
'title' => $id ? __('Edit Custom Field') : __('Add Custom Field'),
]
);
}
public function deleteAction(ServerRequest $request, Response $response, $id, $csrf): ResponseInterface
{
public function deleteAction(
ServerRequest $request,
Response $response,
int $id,
string $csrf
): ResponseInterface {
$this->doDelete($request, $id, $csrf);
$request->getFlash()->addMessage('<b>' . __('Custom Field deleted.') . '</b>', Flash::SUCCESS);
return $response->withRedirect($request->getRouter()->named('admin:custom_fields:index'));
return $response->withRedirect((string)$request->getRouter()->named('admin:custom_fields:index'));
}
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Console\Application;
@ -169,7 +171,7 @@ class DebugController extends AbstractLogViewerController
// Flash an update to ensure the session is recreated.
$request->getFlash()->addMessage($resultOutput, Flash::SUCCESS);
return $response->withRedirect($request->getRouter()->fromHere('admin:debug:index'));
return $response->withRedirect((string)$request->getRouter()->fromHere('admin:debug:index'));
}
public function clearQueueAction(
@ -187,6 +189,6 @@ class DebugController extends AbstractLogViewerController
// Flash an update to ensure the session is recreated.
$request->getFlash()->addMessage($resultOutput, Flash::SUCCESS);
return $response->withRedirect($request->getRouter()->fromHere('admin:debug:index'));
return $response->withRedirect((string)$request->getRouter()->fromHere('admin:debug:index'));
}
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Acl;
@ -50,7 +52,7 @@ class IndexController
$spaceUsed = $spaceTotal->minus($spaceFree);
// Get memory info.
$meminfoRaw = file("/proc/meminfo", FILE_IGNORE_NEW_LINES);
$meminfoRaw = file("/proc/meminfo", FILE_IGNORE_NEW_LINES) ?: [];
$meminfo = [];
foreach ($meminfoRaw as $line) {
if (str_contains($line, ':')) {

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\Repository\SettingsRepository;
@ -63,7 +65,7 @@ class InstallGeoLiteController
ServerRequest $request,
Response $response,
SettingsRepository $settingsRepo,
$csrf
string $csrf
): ResponseInterface {
$request->getCsrf()->verify($csrf, $this->csrf_namespace);
@ -74,6 +76,6 @@ class InstallGeoLiteController
@unlink(GeoLite::getDatabasePath());
$request->getFlash()->addMessage(__('GeoLite database uninstalled.'), Flash::SUCCESS);
return $response->withRedirect($request->getRouter()->fromHere('admin:install_geolite:index'));
return $response->withRedirect((string)$request->getRouter()->fromHere('admin:install_geolite:index'));
}
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Config;
@ -13,8 +15,6 @@ use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UploadedFileInterface;
use Symfony\Component\Process\Process;
use const UPLOAD_ERR_OK;
class InstallShoutcastController
{
protected array $form_config;
@ -44,15 +44,14 @@ class InstallShoutcastController
$form = new Form($form_config, []);
if ($request->isPost() && $form->isValid($request->getParsedBody())) {
if ($form->isValid($request)) {
try {
$sc_base_dir = $environment->getParentDirectory() . '/servers/shoutcast2';
$files = $request->getUploadedFiles();
/** @var UploadedFileInterface $import_file */
$import_file = $files['binary'];
$values = $form->getValues();
if (UPLOAD_ERR_OK === $import_file->getError()) {
$import_file = $values['binary'] ?? null;
if ($import_file instanceof UploadedFileInterface) {
$sc_tgz_path = $sc_base_dir . '/sc_serv.tar.gz';
if (is_file($sc_tgz_path)) {
unlink($sc_tgz_path);

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Controller\AbstractLogViewerController;
@ -43,7 +45,7 @@ class LogsController extends AbstractLogViewerController
}
/**
* @return mixed[]
* @return array<string, array>
*/
protected function getGlobalLogs(): array
{
@ -82,8 +84,12 @@ class LogsController extends AbstractLogViewerController
return $logPaths;
}
public function viewAction(ServerRequest $request, Response $response, $station_id, $log): ResponseInterface
{
public function viewAction(
ServerRequest $request,
Response $response,
string|int $station_id,
string $log
): ResponseInterface {
if ('global' === $station_id) {
$log_areas = $this->getGlobalLogs();
} else {
@ -94,7 +100,7 @@ class LogsController extends AbstractLogViewerController
throw new Exception('Invalid log file specified.');
}
$log = $log_areas[$log];
return $this->view($request, $response, $log['path'], $log['tail'] ?? true);
$logArea = $log_areas[$log];
return $this->view($request, $response, $logArea['path'], $logArea['tail'] ?? true);
}
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Form\PermissionsForm;
@ -59,28 +61,36 @@ class PermissionsController extends AbstractAdminCrudController
]);
}
public function editAction(ServerRequest $request, Response $response, $id = null): ResponseInterface
public function editAction(ServerRequest $request, Response $response, int $id = null): ResponseInterface
{
if (false !== $this->doEdit($request, $id)) {
$request->getFlash()->addMessage(
'<b>' . ($id ? __('Permission updated.') : __('Permission added.')) . '</b>',
Flash::SUCCESS
);
return $response->withRedirect($request->getRouter()->named('admin:permissions:index'));
return $response->withRedirect((string)$request->getRouter()->named('admin:permissions:index'));
}
return $request->getView()->renderToResponse($response, 'system/form_page', [
'form' => $this->form,
'render_mode' => 'edit',
'title' => $id ? __('Edit Permission') : __('Add Permission'),
]);
return $request->getView()->renderToResponse(
$response,
'system/form_page',
[
'form' => $this->form,
'render_mode' => 'edit',
'title' => $id ? __('Edit Permission') : __('Add Permission'),
]
);
}
public function deleteAction(ServerRequest $request, Response $response, $id, $csrf): ResponseInterface
{
public function deleteAction(
ServerRequest $request,
Response $response,
int $id,
string $csrf
): ResponseInterface {
$this->doDelete($request, $id, $csrf);
$request->getFlash()->addMessage('<b>' . __('Permission deleted.') . '</b>', Flash::SUCCESS);
return $response->withRedirect($request->getRouter()->named('admin:permissions:index'));
return $response->withRedirect((string)$request->getRouter()->named('admin:permissions:index'));
}
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Form\SettingsForm;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity;
@ -33,53 +35,61 @@ class StationsController extends AbstractAdminCrudController
]);
}
public function editAction(ServerRequest $request, Response $response, $id = null): ResponseInterface
public function editAction(ServerRequest $request, Response $response, int $id = null): ResponseInterface
{
if (false !== $this->doEdit($request, $id)) {
$request->getFlash()->addMessage(($id ? __('Station updated.') : __('Station added.')), Flash::SUCCESS);
return $response->withRedirect($request->getRouter()->named('admin:stations:index'));
return $response->withRedirect((string)$request->getRouter()->named('admin:stations:index'));
}
return $request->getView()->renderToResponse($response, 'admin/stations/edit', [
'form' => $this->form,
'title' => $id ? __('Edit Station') : 'Add Station',
]);
return $request->getView()->renderToResponse(
$response,
'admin/stations/edit',
[
'form' => $this->form,
'title' => $id ? __('Edit Station') : 'Add Station',
]
);
}
public function deleteAction(ServerRequest $request, Response $response, $id, $csrf): ResponseInterface
{
public function deleteAction(
ServerRequest $request,
Response $response,
int $id,
string $csrf
): ResponseInterface {
$request->getCsrf()->verify($csrf, $this->csrf_namespace);
$record = $this->record_repo->find((int)$id);
$record = $this->record_repo->find($id);
if ($record instanceof Entity\Station) {
$this->stationRepo->destroy($record);
}
$request->getFlash()->addMessage(__('Station deleted.'), Flash::SUCCESS);
return $response->withRedirect($request->getRouter()->named('admin:stations:index'));
return $response->withRedirect((string)$request->getRouter()->named('admin:stations:index'));
}
public function cloneAction(ServerRequest $request, Response $response, $id): ResponseInterface
public function cloneAction(ServerRequest $request, Response $response, int $id): ResponseInterface
{
$cloneForm = $this->factory->make(Form\StationCloneForm::class);
$record = $this->record_repo->find((int)$id);
$record = $this->record_repo->find($id);
if (!($record instanceof Entity\Station)) {
throw new NotFoundException(__('Station not found.'));
}
if (false !== $cloneForm->process($request, $record)) {
$request->getFlash()->addMessage(__('Changes saved.'), Flash::SUCCESS);
return $response->withRedirect($request->getRouter()->named('admin:stations:index'));
return $response->withRedirect((string)$request->getRouter()->named('admin:stations:index'));
}
return $request->getView()->renderToResponse(
$response,
'system/form_page',
[
'form' => $cloneForm,
'render_mode' => 'edit',
'title' => __('Clone Station: %s', $record->getName()),
'form' => $cloneForm,
'render_mode' => 'edit',
'title' => __('Clone Station: %s', $record->getName()),
]
);
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Http\Response;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity;
@ -39,13 +41,13 @@ class UsersController extends AbstractAdminCrudController
]);
}
public function editAction(ServerRequest $request, Response $response, $id = null): ResponseInterface
public function editAction(ServerRequest $request, Response $response, int $id = null): ResponseInterface
{
try {
if (false !== $this->doEdit($request, $id)) {
$request->getFlash()->addMessage(($id ? __('User updated.') : __('User added.')), Flash::SUCCESS);
return $response->withRedirect($request->getRouter()->named('admin:users:index'));
return $response->withRedirect((string)$request->getRouter()->named('admin:users:index'));
}
} catch (UniqueConstraintViolationException) {
$request->getFlash()->addMessage(
@ -54,18 +56,26 @@ class UsersController extends AbstractAdminCrudController
);
}
return $request->getView()->renderToResponse($response, 'system/form_page', [
'form' => $this->form,
'render_mode' => 'edit',
'title' => $id ? __('Edit User') : __('Add User'),
]);
return $request->getView()->renderToResponse(
$response,
'system/form_page',
[
'form' => $this->form,
'render_mode' => 'edit',
'title' => $id ? __('Edit User') : __('Add User'),
]
);
}
public function deleteAction(ServerRequest $request, Response $response, $id, $csrf): ResponseInterface
{
public function deleteAction(
ServerRequest $request,
Response $response,
int $id,
string $csrf
): ResponseInterface {
$request->getCsrf()->verify($csrf, $this->csrf_namespace);
$user = $this->record_repo->find((int)$id);
$user = $this->record_repo->find($id);
$current_user = $request->getUser();
@ -78,18 +88,18 @@ class UsersController extends AbstractAdminCrudController
$request->getFlash()->addMessage('<b>' . __('User deleted.') . '</b>', Flash::SUCCESS);
}
return $response->withRedirect($request->getRouter()->named('admin:users:index'));
return $response->withRedirect((string)$request->getRouter()->named('admin:users:index'));
}
public function impersonateAction(
ServerRequest $request,
Response $response,
$id,
$csrf
int $id,
string $csrf
): ResponseInterface {
$request->getCsrf()->verify($csrf, $this->csrf_namespace);
$user = $this->record_repo->find((int)$id);
$user = $this->record_repo->find($id);
if (!($user instanceof Entity\User)) {
throw new NotFoundException(__('User not found.'));
@ -103,6 +113,6 @@ class UsersController extends AbstractAdminCrudController
Flash::SUCCESS
);
return $response->withRedirect($request->getRouter()->named('dashboard'));
return $response->withRedirect((string)$request->getRouter()->named('dashboard'));
}
}

View File

@ -1,15 +1,16 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api;
use App\Entity\Interfaces\IdentifiableEntityInterface;
use App\Exception\ValidationException;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Paginator;
use App\Utilities;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Query;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
@ -17,9 +18,12 @@ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @template TEntity as object
*/
abstract class AbstractApiCrudController
{
/** @var string The fully-qualified (::class) class name of the entity being managed. */
/** @var class-string<TEntity> The fully-qualified (::class) class name of the entity being managed. */
protected string $entityClass;
/** @var string The route name used to generate the "self" links for each record. */
@ -59,7 +63,7 @@ abstract class AbstractApiCrudController
}
/**
* @param object $record
* @param TEntity $record
* @param ServerRequest $request
*
*/
@ -74,21 +78,28 @@ abstract class AbstractApiCrudController
$isInternal = ('true' === $request->getParam('internal', 'false'));
$router = $request->getRouter();
$return['links'] = [
'self' => $router->fromHere($this->resourceRouteName, ['id' => $record->getId()], [], !$isInternal),
];
if ($record instanceof IdentifiableEntityInterface) {
$return['links'] = [
'self' => (string)$router->fromHere(
$this->resourceRouteName,
['id' => $record->getIdRequired()],
[],
!$isInternal
),
];
}
return $return;
}
/**
* @param object $record
* @param array $context
* @param TEntity $record
* @param array<string, mixed> $context
*
* @return mixed[]
* @return array<mixed>
*/
protected function toArray(object $record, array $context = []): array
{
return $this->serializer->normalize(
return (array)$this->serializer->normalize(
$record,
null,
array_merge(
@ -126,15 +137,25 @@ abstract class AbstractApiCrudController
return $object->getName();
}
return $object->getId();
if ($object instanceof IdentifiableEntityInterface) {
return $object->getIdRequired();
}
if ($object instanceof \Stringable) {
return (string)$object;
}
return get_class($object) . ': ' . spl_object_hash($object);
}
/**
* @param array|null $data
* @param object|null $record
* @param array $context
* @param array<mixed>|null $data
* @param TEntity|null $record
* @param array<string, mixed> $context
*
* @return TEntity
*/
protected function editRecord(?array $data, $record = null, array $context = []): object
protected function editRecord(?array $data, ?object $record = null, array $context = []): object
{
if (null === $data) {
throw new InvalidArgumentException('Could not parse input data.');
@ -156,11 +177,13 @@ abstract class AbstractApiCrudController
}
/**
* @param array $data
* @param object|null $record
* @param array $context
* @param array<mixed> $data
* @param TEntity|null $record
* @param array<string, mixed> $context
*
* @return TEntity
*/
protected function fromArray(array $data, $record = null, array $context = []): object
protected function fromArray(array $data, ?object $record = null, array $context = []): object
{
if (null !== $record) {
$context[ObjectNormalizer::OBJECT_TO_POPULATE] = $record;
@ -170,10 +193,7 @@ abstract class AbstractApiCrudController
}
/**
* @param object $record
*
* @throws ORMException
* @throws OptimisticLockException
* @param TEntity $record
*/
protected function deleteRecord(object $record): void
{

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Admin;
use App\Controller\Api\AbstractApiCrudController;
@ -7,11 +9,12 @@ use App\Entity;
use App\Exception;
use App\Http\Response;
use App\Http\ServerRequest;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\TransactionRequiredException;
use Psr\Http\Message\ResponseInterface;
/**
* @template TEntity as object
* @extends AbstractApiCrudController<TEntity>
*/
abstract class AbstractAdminApiCrudController extends AbstractApiCrudController
{
public function listAction(ServerRequest $request, Response $response): ResponseInterface
@ -29,7 +32,7 @@ abstract class AbstractAdminApiCrudController extends AbstractApiCrudController
*/
public function createAction(ServerRequest $request, Response $response): ResponseInterface
{
$row = $this->createRecord($request->getParsedBody());
$row = $this->createRecord((array)$request->getParsedBody());
$return = $this->viewRecord($row, $request);
return $response->withJson($return);
@ -37,6 +40,8 @@ abstract class AbstractAdminApiCrudController extends AbstractApiCrudController
/**
* @param array $data
*
* @return TEntity
*/
protected function createRecord(array $data): object
{
@ -54,7 +59,7 @@ abstract class AbstractAdminApiCrudController extends AbstractApiCrudController
if (null === $record) {
return $response->withStatus(404)
->withJson(new Entity\Api\Error(404, __('Record not found!')));
->withJson(Entity\Api\Error::notFound());
}
$return = $this->viewRecord($record, $request);
@ -64,9 +69,7 @@ abstract class AbstractAdminApiCrudController extends AbstractApiCrudController
/**
* @param mixed $id
*
* @throws ORMException
* @throws OptimisticLockException
* @throws TransactionRequiredException
* @return TEntity|null
*/
protected function getRecord(mixed $id): ?object
{
@ -84,10 +87,10 @@ abstract class AbstractAdminApiCrudController extends AbstractApiCrudController
if (null === $record) {
return $response->withStatus(404)
->withJson(new Entity\Api\Error(404, __('Record not found!')));
->withJson(Entity\Api\Error::notFound());
}
$this->editRecord($request->getParsedBody(), $record);
$this->editRecord((array)$request->getParsedBody(), $record);
return $response->withJson(new Entity\Api\Status(true, __('Changes saved successfully.')));
}
@ -103,7 +106,7 @@ abstract class AbstractAdminApiCrudController extends AbstractApiCrudController
if (null === $record) {
return $response->withStatus(404)
->withJson(new Entity\Api\Error(404, __('Record not found!')));
->withJson(Entity\Api\Error::notFound());
}
$this->deleteRecord($record);

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Admin;
use App\Entity;

View File

@ -1,10 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Admin;
use App\Entity;
use OpenApi\Annotations as OA;
/**
* @extends AbstractAdminApiCrudController<Entity\CustomField>
*/
class CustomFieldsController extends AbstractAdminApiCrudController
{
protected string $entityClass = Entity\CustomField::class;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Admin;
use App\Acl;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Admin;
use App\Acl;
@ -45,7 +47,7 @@ class RelaysController
$fa = $this->adapters->getFrontendAdapter($station);
$row = new Entity\Api\Admin\Relay();
$row->id = $station->getId();
$row->id = $station->getIdRequired();
$row->name = $station->getName();
$row->shortcode = $station->getShortName();
$row->description = $station->getDescription();
@ -108,14 +110,13 @@ class RelaysController
{
$relay_repo = $this->em->getRepository(Entity\Relay::class);
$body = $request->getParsedBody();
$body = (array)$request->getParsedBody();
if (!empty($body['base_url'])) {
$base_url = $body['base_url'];
} else {
$serverParams = $request->getServerParams();
/** @noinspection HttpUrlsUsage */
$base_url = 'http://' . $serverParams('REMOTE_ADDR');
$base_url = 'http://' . $request->getIp();
}
$relay = $relay_repo->findOneBy(['base_url' => $base_url]);

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Admin;
use App\Acl;
@ -10,6 +12,9 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @extends AbstractAdminApiCrudController<Entity\Role>
*/
class RolesController extends AbstractAdminApiCrudController
{
protected string $entityClass = Entity\Role::class;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Admin;
use App\Controller\Api\AbstractApiCrudController;
@ -13,6 +15,9 @@ use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @extends AbstractApiCrudController<Entity\Settings>
*/
class SettingsController extends AbstractApiCrudController
{
public function __construct(
@ -66,7 +71,7 @@ class SettingsController extends AbstractApiCrudController
public function updateAction(ServerRequest $request, Response $response): ResponseInterface
{
$settings = $this->settingsRepo->readSettings();
$this->editRecord($request->getParsedBody(), $settings);
$this->editRecord((array)$request->getParsedBody(), $settings);
return $response->withJson(new Entity\Api\Status());
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Admin;
use App\Entity;
@ -11,6 +13,9 @@ use OpenApi\Annotations as OA;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @extends AbstractAdminApiCrudController<Entity\Station>
*/
class StationsController extends AbstractAdminApiCrudController
{
protected string $entityClass = Entity\Station::class;
@ -104,7 +109,12 @@ class StationsController extends AbstractAdminApiCrudController
* )
*/
/** @inheritDoc */
/**
* @param Entity\Station $record
* @param array<string, mixed> $context
*
* @return array<mixed>
*/
protected function toArray(object $record, array $context = []): array
{
return parent::toArray(
@ -122,8 +132,14 @@ class StationsController extends AbstractAdminApiCrudController
);
}
/** @inheritDoc */
protected function editRecord(?array $data, $record = null, array $context = []): object
/**
* @param array<mixed>|null $data
* @param Entity\Station|null $record
* @param array<string, mixed> $context
*
* @return Entity\Station
*/
protected function editRecord(?array $data, object $record = null, array $context = []): object
{
$create_mode = (null === $record);
@ -150,7 +166,9 @@ class StationsController extends AbstractAdminApiCrudController
return $this->station_repo->edit($record);
}
/** @inheritDoc */
/**
* @param Entity\Station $record
*/
protected function deleteRecord(object $record): void
{
$this->station_repo->destroy($record);

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Admin;
use App\Entity;
@ -13,6 +15,9 @@ use RuntimeException;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @extends AbstractAdminApiCrudController<Entity\StorageLocation>
*/
class StorageLocationsController extends AbstractAdminApiCrudController
{
protected string $entityClass = Entity\StorageLocation::class;
@ -125,9 +130,8 @@ class StorageLocationsController extends AbstractAdminApiCrudController
}
/** @inheritDoc */
protected function viewRecord(object $record, ServerRequest $request): Entity\Api\Admin\StorageLocation
protected function viewRecord(object $record, ServerRequest $request): object
{
/** @var Entity\StorageLocation $record */
$original = parent::viewRecord($record, $request);
$return = new Entity\Api\Admin\StorageLocation();

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Admin;
use App\Entity;
@ -8,6 +10,9 @@ use App\Http\ServerRequest;
use OpenApi\Annotations as OA;
use Psr\Http\Message\ResponseInterface;
/**
* @extends AbstractAdminApiCrudController<Entity\User>
*/
class UsersController extends AbstractAdminApiCrudController
{
protected string $entityClass = Entity\User::class;
@ -97,12 +102,11 @@ class UsersController extends AbstractAdminApiCrudController
*/
public function deleteAction(ServerRequest $request, Response $response, mixed $id): ResponseInterface
{
/** @var Entity\User|null $record */
$record = $this->getRecord($id);
if (null === $record) {
return $response->withStatus(404)
->withJson(new Entity\Api\Error(404, __('Record not found!')));
->withJson(Entity\Api\Error::notFound());
}
$current_user = $request->getUser();

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Frontend\Account;
use App\Controller\Api\Admin\UsersController;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Frontend\Account;
use App\Controller\Api\Admin\UsersController;
@ -17,7 +19,7 @@ class PutMeAction extends UsersController
public function __invoke(ServerRequest $request, Response $response): ResponseInterface
{
$user = $request->getUser();
$this->editRecord($request->getParsedBody(), $user);
$this->editRecord((array)$request->getParsedBody(), $user);
return $response->withJson(new Entity\Api\Status(true, __('Changes saved successfully.')));
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Frontend\Dashboard;
use App\Acl;
@ -83,7 +85,7 @@ class ChartsAction
$sortableKey = $moment->format('Y-m-d');
$jsTimestamp = $moment->getTimestamp() * 1000;
$average = round($row['number_avg'], 2);
$average = round((float)$row['number_avg'], 2);
$unique = $row['number_unique'];
$rawStats['average'][$stationId][$sortableKey] = [

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Frontend\Dashboard;
use App\Event;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Frontend\Dashboard;
use App\Acl;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api;
use App\Entity;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api;
use App\Acl;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api;
use App\Entity;
@ -80,8 +82,11 @@ class NowplayingController implements EventSubscriberInterface
* @param Response $response
* @param int|string|null $station_id
*/
public function __invoke(ServerRequest $request, Response $response, $station_id = null): ResponseInterface
{
public function __invoke(
ServerRequest $request,
Response $response,
$station_id = null
): ResponseInterface {
$router = $request->getRouter();
// Pull NP data from the fastest/first available source using the EventDispatcher.
@ -101,7 +106,7 @@ class NowplayingController implements EventSubscriberInterface
}
return $response->withStatus(404)
->withJson(new Entity\Api\Error(404, 'Station not found.'));
->withJson(Entity\Api\Error::notFound());
}
// If unauthenticated, hide non-public stations from full view.

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api;
use App\Environment;
@ -20,7 +22,7 @@ class OpenApiController
public function __invoke(ServerRequest $request, Response $response): ResponseInterface
{
$api_base_url = $request->getRouter()->fromHere(null, [], [], true);
$api_base_url = (string)$request->getRouter()->fromHere(absolute: true);
$api_base_url = str_replace('/openapi.yml', '', $api_base_url);
define('AZURACAST_API_URL', $api_base_url);

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations;
use App\Entity;
@ -14,6 +16,10 @@ use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @template TEntity as Entity\StationPlaylist|Entity\StationStreamer
* @extends AbstractStationApiCrudController<TEntity>
*/
abstract class AbstractScheduledEntityController extends AbstractStationApiCrudController
{
public function __construct(
@ -37,11 +43,21 @@ abstract class AbstractScheduledEntityController extends AbstractStationApiCrudC
$params = $request->getQueryParams();
$startDateStr = substr($params['start'], 0, 10);
$startDate = CarbonImmutable::createFromFormat('Y-m-d', $startDateStr, $tz)->subDay();
$startDate = CarbonImmutable::createFromFormat('Y-m-d', $startDateStr, $tz);
if (false === $startDate) {
throw new \InvalidArgumentException(sprintf('Could not parse start date: "%s"', $startDateStr));
}
$startDate = $startDate->subDay();
$endDateStr = substr($params['end'], 0, 10);
$endDate = CarbonImmutable::createFromFormat('Y-m-d', $endDateStr, $tz);
if (false === $endDate) {
throw new \InvalidArgumentException(sprintf('Could not parse end date: "%s"', $endDateStr));
}
$events = [];
foreach ($scheduleItems as $scheduleItem) {
@ -73,13 +89,13 @@ abstract class AbstractScheduledEntityController extends AbstractStationApiCrudC
return $response->withJson($events);
}
protected function editRecord(?array $data, $record = null, array $context = []): object
protected function editRecord(?array $data, object $record = null, array $context = []): object
{
if (null === $data) {
throw new InvalidArgumentException('Could not parse input data.');
}
$scheduleItems = $data['schedule_items'] ?? null;
$scheduleItems = $data['schedule_items'] ?? [];
unset($data['schedule_items']);
$record = $this->fromArray($data, $record, $context);

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations;
use App\Controller\Api\AbstractApiCrudController;
@ -10,6 +12,10 @@ use App\Http\ServerRequest;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
/**
* @template TEntity as object
* @extends AbstractApiCrudController<TEntity>
*/
abstract class AbstractStationApiCrudController extends AbstractApiCrudController
{
/**
@ -46,7 +52,7 @@ abstract class AbstractStationApiCrudController extends AbstractApiCrudControlle
public function createAction(ServerRequest $request, Response $response): ResponseInterface
{
$station = $this->getStation($request);
$row = $this->createRecord($request->getParsedBody(), $station);
$row = $this->createRecord((array)$request->getParsedBody(), $station);
$return = $this->viewRecord($row, $request);
@ -56,6 +62,8 @@ abstract class AbstractStationApiCrudController extends AbstractApiCrudControlle
/**
* @param array $data
* @param Entity\Station $station
*
* @return TEntity
*/
protected function createRecord(array $data, Entity\Station $station): object
{
@ -75,23 +83,23 @@ abstract class AbstractStationApiCrudController extends AbstractApiCrudControlle
/**
* @param ServerRequest $request
* @param Response $response
* @param int|string $station_id
* @param int|string $id
* @param int $station_id
* @param int $id
*
* @throws Exception
*/
public function getAction(
ServerRequest $request,
Response $response,
int|string $station_id,
int|string $id
int $station_id,
int $id
): ResponseInterface {
$station = $this->getStation($request);
$record = $this->getRecord($station, $id);
if (null === $record) {
return $response->withStatus(404)
->withJson(new Entity\Api\Error(404, __('Record not found!')));
->withJson(Entity\Api\Error::notFound());
}
$return = $this->viewRecord($record, $request);
@ -101,6 +109,8 @@ abstract class AbstractStationApiCrudController extends AbstractApiCrudControlle
/**
* @param Entity\Station $station
* @param int|string $id
*
* @return TEntity
*/
protected function getRecord(Entity\Station $station, int|string $id): ?object
{
@ -115,23 +125,23 @@ abstract class AbstractStationApiCrudController extends AbstractApiCrudControlle
/**
* @param ServerRequest $request
* @param Response $response
* @param int|string $station_id
* @param int|string $id
* @param int $station_id
* @param int $id
*/
public function editAction(
ServerRequest $request,
Response $response,
int|string $station_id,
int|string $id
int $station_id,
int $id
): ResponseInterface {
$record = $this->getRecord($this->getStation($request), $id);
if (null === $record) {
return $response->withStatus(404)
->withJson(new Entity\Api\Error(404, __('Record not found!')));
->withJson(Entity\Api\Error::notFound());
}
$this->editRecord($request->getParsedBody(), $record);
$this->editRecord((array)$request->getParsedBody(), $record);
return $response->withJson(new Entity\Api\Status(true, __('Changes saved successfully.')));
}
@ -139,20 +149,20 @@ abstract class AbstractStationApiCrudController extends AbstractApiCrudControlle
/**
* @param ServerRequest $request
* @param Response $response
* @param int|string $station_id
* @param int|string $id
* @param int $station_id
* @param int $id
*/
public function deleteAction(
ServerRequest $request,
Response $response,
int|string $station_id,
int|string $id
int $station_id,
int $id
): ResponseInterface {
$record = $this->getRecord($this->getStation($request), $id);
if (null === $record) {
return $response->withStatus(404)
->withJson(new Entity\Api\Error(404, __('Record not found!')));
->withJson(Entity\Api\Error::notFound());
}
$this->deleteRecord($record);

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations\Art;
use App\Entity;
@ -9,6 +11,14 @@ use Psr\Http\Message\ResponseInterface;
class DeleteArtAction
{
/**
* @param ServerRequest $request
* @param Response $response
* @param Entity\Repository\StationMediaRepository $mediaRepo
* @param int|string $media_id
*
* @return ResponseInterface
*/
public function __invoke(
ServerRequest $request,
Response $response,
@ -20,7 +30,7 @@ class DeleteArtAction
$media = $mediaRepo->find($media_id, $station);
if (!($media instanceof Entity\StationMedia)) {
return $response->withStatus(404)
->withJson(new Entity\Api\Error(404, __('Record not found.')));
->withJson(Entity\Api\Error::notFound());
}
$mediaRepo->removeAlbumArt($media);

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations\Art;
use App\Entity;
@ -46,7 +48,7 @@ class GetArtAction
$fsMedia = (new StationFilesystems($station))->getMediaFilesystem();
$defaultArtRedirect = $response->withRedirect($stationRepo->getDefaultAlbumArtUrl($station), 302);
$defaultArtRedirect = $response->withRedirect((string)$stationRepo->getDefaultAlbumArtUrl($station), 302);
// If a timestamp delimiter is added, strip it automatically.
$media_id = explode('-', $media_id, 2)[0];

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations\Art;
use App\Entity;
@ -11,6 +13,16 @@ use Psr\Http\Message\ResponseInterface;
class PostArtAction
{
/**
* @param ServerRequest $request
* @param Response $response
* @param Entity\Repository\StationMediaRepository $mediaRepo
* @param EntityManagerInterface $em
* @param int|string $media_id
*
* @return ResponseInterface
* @throws \App\Exception\NoFileUploadedException
*/
public function __invoke(
ServerRequest $request,
Response $response,
@ -23,7 +35,7 @@ class PostArtAction
$media = $mediaRepo->find($media_id, $station);
if (!($media instanceof Entity\StationMedia)) {
return $response->withStatus(404)
->withJson(new Entity\Api\Error(404, __('Record not found.')));
->withJson(Entity\Api\Error::notFound());
}
$flowResponse = Flow::process($request, $response, $station->getRadioTempDir());

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations\Files;
use App\Doctrine\ReloadableEntityManagerInterface;
@ -157,6 +159,7 @@ class BatchAction
$this->em->flush();
foreach ($playlists as $playlistRecord) {
/** @var Entity\StationPlaylist $playlist */
$playlist = $this->em->refetchAsReference($playlistRecord);
$playlistWeights[$playlist->getId()]++;
@ -170,6 +173,7 @@ class BatchAction
}
}
/** @var Entity\Station $station */
$station = $this->em->refetch($station);
foreach ($result->directories as $dir) {
@ -226,7 +230,7 @@ class BatchAction
$toMove = [
$this->batchUtilities->iterateMediaInDirectory($storageLocation, $dirPath),
$this->batchUtilities->iterateUnprocessableMediaInDirectory($storageLocation, $dirPath),
$this->batchUtilities->iteratePlaylistFoldersInDirectory($station, $dirPath),
$this->batchUtilities->iteratePlaylistFoldersInDirectory($storageLocation, $dirPath),
];
foreach ($toMove as $iterator) {

View File

@ -1,8 +1,10 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations\Files;
use App\Entity\Api\Error;
use App\Entity;
use App\Flysystem\StationFilesystems;
use App\Http\Response;
use App\Http\ServerRequest;
@ -23,7 +25,7 @@ class DownloadAction
if (!$fsMedia->fileExists($path)) {
return $response->withStatus(404)
->withJson(new Error(404, 'File not found.'));
->withJson(Entity\Api\Error::notFound());
}
return $response->streamFilesystemFile($fsMedia, $path);

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations\Files;
use App\Entity;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations\Files;
use App\Entity;
@ -129,7 +131,7 @@ class ListAction
if (!$playlist instanceof Entity\StationPlaylist) {
return $response->withStatus(400)
->withJson(new Entity\Api\Error('Playlist not found.'));
->withJson(new Entity\Api\Error(400, 'Playlist not found.'));
}
$mediaQueryBuilder->andWhere(
@ -177,7 +179,7 @@ class ListAction
$media->genre = (string)$row['genre'];
$media->is_playable = ($row['length'] !== 0);
$media->length = $row['length'];
$media->length = (int)$row['length'];
$media->length_text = $row['length_text'];
$media->media_id = $row['id'];
@ -315,7 +317,7 @@ class ListAction
$paginator = Paginator::fromArray($result, $request);
// Add processor-intensive data for just this page.
$stationId = $station->getId();
$stationId = $station->getIdRequired();
$isInternal = (bool)$request->getParam('internal', false);
$defaultAlbumArtUrl = (string)$stationRepo->getDefaultAlbumArtUrl($station);

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations\Files;
use App\Entity;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations\Files;
use App\Entity;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations\Files;
use App\Entity;
@ -24,7 +26,7 @@ class PlayAction
if (!$media instanceof Entity\StationMedia) {
return $response->withStatus(404)
->withJson(new Entity\Api\Error(404, 'Not Found'));
->withJson(Entity\Api\Error::notFound());
}
$fsMedia = (new StationFilesystems($station))->getMediaFilesystem();

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations\Files;
use App\Entity;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations;
use App\Entity;
@ -19,6 +21,9 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @extends AbstractStationApiCrudController<Entity\StationMedia>
*/
class FilesController extends AbstractStationApiCrudController
{
protected string $entityClass = Entity\StationMedia::class;
@ -179,19 +184,19 @@ class FilesController extends AbstractStationApiCrudController
public function editAction(
ServerRequest $request,
Response $response,
int|string $station_id,
int|string $id
int $station_id,
int $id
): ResponseInterface {
$station = $this->getStation($request);
$record = $this->getRecord($station, $id);
if (null === $record) {
return $response->withStatus(404)
->withJson(new Entity\Api\Error(404, __('Record not found!')));
->withJson(Entity\Api\Error::notFound());
}
$data = $request->getParsedBody();
if (null === $data) {
if (!is_array($data)) {
throw new InvalidArgumentException('Could not parse input data.');
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations;
use App;

Some files were not shown because too many files have changed in this diff Show More