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 #!/usr/bin/env php
<?php <?php
declare(strict_types=1);
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT); 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'; $autoloader = require dirname(__DIR__) . '/vendor/autoload.php';

View File

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

View File

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

12
composer.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,8 @@
parameters: parameters:
level: 3 level: 8
checkGenericClassInNonGenericObjectType: false
checkMissingIterableValueType: false
paths: paths:
- src - src
@ -15,6 +18,13 @@ parameters:
# Caused by Symfony Validator (perhaps wrongly) returning the interface. # Caused by Symfony Validator (perhaps wrongly) returning the interface.
- '#Cannot cast Symfony\\Component\\Validator\\ConstraintViolationListInterface to string.#' - '#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: parallel:
maximumNumberOfProcesses: 1 maximumNumberOfProcesses: 1

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace App; namespace App;
use App\Entity\Repository\UserRepository; use App\Entity\Repository\UserRepository;
@ -52,7 +54,7 @@ class Auth
* *
* @throws Exception * @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()) { if (!$real_user_only && $this->isMasqueraded()) {
return $this->getMasquerade(); return $this->getMasquerade();
@ -193,13 +195,16 @@ class Auth
*/ */
public function getMasquerade(): ?User 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. * 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 public function masqueradeAsUser(User|array $user_info): void
{ {

View File

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

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace App\Console; namespace App\Console;
use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\ArrayInput;
@ -22,6 +24,10 @@ class Application extends \Silly\Edition\PhpDi\Application
$input->setInteractive(false); $input->setInteractive(false);
$temp_stream = fopen($outputFile, 'wb+'); $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); $output = new StreamOutput($temp_stream);
$command = $this->find($command); $command = $this->find($command);
@ -31,7 +37,7 @@ class Application extends \Silly\Edition\PhpDi\Application
$result_output = stream_get_contents($temp_stream); $result_output = stream_get_contents($temp_stream);
fclose($temp_stream); fclose($temp_stream);
$result_output = trim($result_output); $result_output = trim((string)$result_output);
return [ return [
$result_code, $result_code,

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace App\Console\Command; namespace App\Console\Command;
use App\Console\Application; use App\Console\Application;
@ -20,7 +22,7 @@ abstract class CommandAbstract
return $this->application; 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); $command = $this->getApplication()->find($command_name);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace App\Controller; namespace App\Controller;
use App\Entity; use App\Entity;
@ -16,8 +18,8 @@ abstract class AbstractLogViewerController
protected function view( protected function view(
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
$log_path, string $log_path,
$tail_file = true bool $tail_file = true
): ResponseInterface { ): ResponseInterface {
clearstatcache(); clearstatcache();
@ -26,7 +28,7 @@ abstract class AbstractLogViewerController
} }
if (!$tail_file) { if (!$tail_file) {
$log = file_get_contents($log_path); $log = file_get_contents($log_path) ?: '';
$log_contents = $this->processLog($request, $log); $log_contents = $this->processLog($request, $log);
return $response->withJson([ return $response->withJson([
@ -55,8 +57,12 @@ abstract class AbstractLogViewerController
if ($log_visible_size > 0) { if ($log_visible_size > 0) {
$fp = fopen($log_path, 'rb'); $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); 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); fclose($fp);
$log_contents = $this->processLog($request, $log_contents_raw, $cut_first_line, true); $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 protected function getStationLogs(Entity\Station $station): array
{ {

View File

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

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Form\ApiKeyForm; 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)) { if (false !== $this->doEdit($request, $id)) {
$request->getFlash()->addMessage(__('API Key updated.'), Flash::SUCCESS); $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', [ return $request->getView()->renderToResponse(
'form' => $this->form, $response,
'render_mode' => 'edit', 'system/form_page',
'title' => __('Edit API Key'), [
]); '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); $this->doDelete($request, $id, $csrf);
$request->getFlash()->addMessage(__('API Key deleted.'), Flash::SUCCESS); $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 <?php
declare(strict_types=1);
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Http\Response; use App\Http\Response;

View File

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

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Entity; 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)) { if (false !== $this->doEdit($request, $id)) {
$request->getFlash()->addMessage( $request->getFlash()->addMessage(
($id ? __('Custom Field updated.') : __('Custom Field added.')), ($id ? __('Custom Field updated.') : __('Custom Field added.')),
Flash::SUCCESS 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', [ return $request->getView()->renderToResponse(
'form' => $this->form, $response,
'render_mode' => 'edit', 'system/form_page',
'title' => $id ? __('Edit Custom Field') : __('Add Custom Field'), [
]); '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); $this->doDelete($request, $id, $csrf);
$request->getFlash()->addMessage('<b>' . __('Custom Field deleted.') . '</b>', Flash::SUCCESS); $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 <?php
declare(strict_types=1);
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Console\Application; use App\Console\Application;
@ -169,7 +171,7 @@ class DebugController extends AbstractLogViewerController
// Flash an update to ensure the session is recreated. // Flash an update to ensure the session is recreated.
$request->getFlash()->addMessage($resultOutput, Flash::SUCCESS); $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( public function clearQueueAction(
@ -187,6 +189,6 @@ class DebugController extends AbstractLogViewerController
// Flash an update to ensure the session is recreated. // Flash an update to ensure the session is recreated.
$request->getFlash()->addMessage($resultOutput, Flash::SUCCESS); $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 <?php
declare(strict_types=1);
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Acl; use App\Acl;
@ -50,7 +52,7 @@ class IndexController
$spaceUsed = $spaceTotal->minus($spaceFree); $spaceUsed = $spaceTotal->minus($spaceFree);
// Get memory info. // Get memory info.
$meminfoRaw = file("/proc/meminfo", FILE_IGNORE_NEW_LINES); $meminfoRaw = file("/proc/meminfo", FILE_IGNORE_NEW_LINES) ?: [];
$meminfo = []; $meminfo = [];
foreach ($meminfoRaw as $line) { foreach ($meminfoRaw as $line) {
if (str_contains($line, ':')) { if (str_contains($line, ':')) {

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Entity\Repository\SettingsRepository; use App\Entity\Repository\SettingsRepository;
@ -63,7 +65,7 @@ class InstallGeoLiteController
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
SettingsRepository $settingsRepo, SettingsRepository $settingsRepo,
$csrf string $csrf
): ResponseInterface { ): ResponseInterface {
$request->getCsrf()->verify($csrf, $this->csrf_namespace); $request->getCsrf()->verify($csrf, $this->csrf_namespace);
@ -74,6 +76,6 @@ class InstallGeoLiteController
@unlink(GeoLite::getDatabasePath()); @unlink(GeoLite::getDatabasePath());
$request->getFlash()->addMessage(__('GeoLite database uninstalled.'), Flash::SUCCESS); $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 <?php
declare(strict_types=1);
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Config; use App\Config;
@ -13,8 +15,6 @@ use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UploadedFileInterface;
use Symfony\Component\Process\Process; use Symfony\Component\Process\Process;
use const UPLOAD_ERR_OK;
class InstallShoutcastController class InstallShoutcastController
{ {
protected array $form_config; protected array $form_config;
@ -44,15 +44,14 @@ class InstallShoutcastController
$form = new Form($form_config, []); $form = new Form($form_config, []);
if ($request->isPost() && $form->isValid($request->getParsedBody())) { if ($form->isValid($request)) {
try { try {
$sc_base_dir = $environment->getParentDirectory() . '/servers/shoutcast2'; $sc_base_dir = $environment->getParentDirectory() . '/servers/shoutcast2';
$files = $request->getUploadedFiles(); $values = $form->getValues();
/** @var UploadedFileInterface $import_file */
$import_file = $files['binary'];
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'; $sc_tgz_path = $sc_base_dir . '/sc_serv.tar.gz';
if (is_file($sc_tgz_path)) { if (is_file($sc_tgz_path)) {
unlink($sc_tgz_path); unlink($sc_tgz_path);

View File

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

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Form\PermissionsForm; 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)) { if (false !== $this->doEdit($request, $id)) {
$request->getFlash()->addMessage( $request->getFlash()->addMessage(
'<b>' . ($id ? __('Permission updated.') : __('Permission added.')) . '</b>', '<b>' . ($id ? __('Permission updated.') : __('Permission added.')) . '</b>',
Flash::SUCCESS 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', [ return $request->getView()->renderToResponse(
'form' => $this->form, $response,
'render_mode' => 'edit', 'system/form_page',
'title' => $id ? __('Edit Permission') : __('Add Permission'), [
]); '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); $this->doDelete($request, $id, $csrf);
$request->getFlash()->addMessage('<b>' . __('Permission deleted.') . '</b>', Flash::SUCCESS); $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 <?php
declare(strict_types=1);
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Entity; use App\Entity;

View File

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

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Entity; 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)) { if (false !== $this->doEdit($request, $id)) {
$request->getFlash()->addMessage(($id ? __('Station updated.') : __('Station added.')), Flash::SUCCESS); $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', [ return $request->getView()->renderToResponse(
'form' => $this->form, $response,
'title' => $id ? __('Edit Station') : 'Add Station', '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); $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) { if ($record instanceof Entity\Station) {
$this->stationRepo->destroy($record); $this->stationRepo->destroy($record);
} }
$request->getFlash()->addMessage(__('Station deleted.'), Flash::SUCCESS); $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); $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)) { if (!($record instanceof Entity\Station)) {
throw new NotFoundException(__('Station not found.')); throw new NotFoundException(__('Station not found.'));
} }
if (false !== $cloneForm->process($request, $record)) { if (false !== $cloneForm->process($request, $record)) {
$request->getFlash()->addMessage(__('Changes saved.'), Flash::SUCCESS); $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( return $request->getView()->renderToResponse(
$response, $response,
'system/form_page', 'system/form_page',
[ [
'form' => $cloneForm, 'form' => $cloneForm,
'render_mode' => 'edit', 'render_mode' => 'edit',
'title' => __('Clone Station: %s', $record->getName()), 'title' => __('Clone Station: %s', $record->getName()),
] ]
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace App\Controller\Api; namespace App\Controller\Api;
use App\Environment; use App\Environment;
@ -20,7 +22,7 @@ class OpenApiController
public function __invoke(ServerRequest $request, Response $response): ResponseInterface 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); $api_base_url = str_replace('/openapi.yml', '', $api_base_url);
define('AZURACAST_API_URL', $api_base_url); define('AZURACAST_API_URL', $api_base_url);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace App\Controller\Api\Stations\Art; namespace App\Controller\Api\Stations\Art;
use App\Entity; use App\Entity;
@ -11,6 +13,16 @@ use Psr\Http\Message\ResponseInterface;
class PostArtAction 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( public function __invoke(
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
@ -23,7 +35,7 @@ class PostArtAction
$media = $mediaRepo->find($media_id, $station); $media = $mediaRepo->find($media_id, $station);
if (!($media instanceof Entity\StationMedia)) { if (!($media instanceof Entity\StationMedia)) {
return $response->withStatus(404) 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()); $flowResponse = Flow::process($request, $response, $station->getRadioTempDir());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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