Move "Enable Advanced Settings" to DB and make it actually work.

This commit is contained in:
Buster "Silver Eagle" Neece 2021-02-02 22:17:57 -06:00
parent 829bfb7a67
commit cabeeb5cc0
No known key found for this signature in database
GPG Key ID: 6D9E12FF03411F4E
20 changed files with 327 additions and 160 deletions

View File

@ -13,18 +13,6 @@ APPLICATION_ENV=production
# Valid options: debug, info, notice, warning, error, critical, alert, emergency
# LOG_LEVEL=notice
# Enable certain advanced features inside the web interface, including
# advanced playlist coniguration, station port assignment, changing base
# media directory, and other functionality that should only be used by
# users who are comfortable with advanced functionality.
#
# Disclaimer: If this is enabled, you are responsible for fixing any errors
# that are caused by your misuse of advanced features! In many cases, the easiest
# fix is to revert your own changes.
#
# Valid options: true, false
ENABLE_ADVANCED_FEATURES=false
# Enable the composer "merge" functionality to combine the main application's
# composer.json file with any plugins' composer files.
# This can have performance implications, so you should only use it if

View File

@ -113,6 +113,20 @@ return [
],
],
'enableAdvancedFeatures' => [
'toggle',
[
'label' => __('Enable Advanced Features'),
'description' => __(
'Enable certain advanced features in the web interface, including advanced playlist configuration, station port assignment, changing base media directories and other functionality that should only be used by users who are comfortable with advanced functionality.'
),
'selected_text' => __('Yes'),
'deselected_text' => __('No'),
'default' => false,
'form_group_class' => 'col-md-12',
],
],
],
],

View File

@ -635,6 +635,24 @@ return [
'elements' => [
'media_storage_location_id' => [
'select',
[
'label' => __('Media Storage Location'),
'choices' => [],
'form_group_class' => 'col-md-6',
],
],
'recordings_storage_location_id' => [
'select',
[
'label' => __('Live Recordings Storage Location'),
'choices' => [],
'form_group_class' => 'col-md-6',
],
],
'is_enabled' => [
'toggle',
[
@ -659,26 +677,6 @@ return [
],
],
'media_storage_location_id' => [
'select',
[
'label' => __('Media Storage Location'),
'choices' => [],
'label_class' => 'advanced',
'form_group_class' => 'col-md-6',
],
],
'recordings_storage_location_id' => [
'select',
[
'label' => __('Live Recordings Storage Location'),
'choices' => [],
'label_class' => 'advanced',
'form_group_class' => 'col-md-6',
],
],
],
],

View File

@ -13,7 +13,7 @@ return function (App\Event\BuildStationMenu $e) {
$backend = $request->getStationBackend();
$frontend = $request->getStationFrontend();
$settings = $e->getEnvironment();
$settings = $e->getSettings();
$e->merge(
[
@ -168,8 +168,8 @@ return function (App\Event\BuildStationMenu $e) {
'ls_config' => [
'label' => __('Edit Liquidsoap Configuration'),
'url' => $router->fromHere('stations:util:ls_config'),
'visible' => $settings->enableAdvancedFeatures(
) && $backend instanceof App\Radio\Backend\Liquidsoap,
'visible' => $settings->getEnableAdvancedFeatures()
&& $backend instanceof App\Radio\Backend\Liquidsoap,
'permission' => Acl::STATION_BROADCASTING,
],
'logs' => [

View File

@ -35,6 +35,7 @@ class ErrorHandler implements EventSubscriberInterface
public function onTerminate(ConsoleTerminateEvent $event): void
{
$command = $event->getCommand();
$commandName = (null !== $command) ? $command->getName() : 'Unknown';
$exitCode = $event->getExitCode();
if (0 === $exitCode) {
@ -43,7 +44,7 @@ class ErrorHandler implements EventSubscriberInterface
$message = sprintf(
'Console command `%s` exited with error code %d.',
$command->getName(),
$commandName,
$exitCode
);
$this->logger->warning($message);
@ -52,6 +53,8 @@ class ErrorHandler implements EventSubscriberInterface
public function onError(ConsoleErrorEvent $event): void
{
$command = $event->getCommand();
$commandName = (null !== $command) ? $command->getName() : 'Unknown';
$exception = $event->getError();
$message = sprintf(
@ -60,7 +63,7 @@ class ErrorHandler implements EventSubscriberInterface
$exception->getMessage(),
$exception->getFile(),
$exception->getLine(),
$command->getName()
$commandName
);
$this->logger->error($message, ['exception' => $exception]);

View File

@ -2,7 +2,7 @@
namespace App\Controller\Stations;
use App\Environment;
use App\Entity\Repository\SettingsRepository;
use App\Exception\AdvancedFeatureException;
use App\Exception\StationUnsupportedException;
use App\Form\Form;
@ -19,12 +19,13 @@ class EditLiquidsoapConfigController
ServerRequest $request,
Response $response,
EntityManagerInterface $em,
Environment $settings
SettingsRepository $settingsRepo
): ResponseInterface {
$station = $request->getStation();
$backend = $request->getStationBackend();
if (!$settings->enableAdvancedFeatures()) {
$settings = $settingsRepo->readSettings();
if (!$settings->getEnableAdvancedFeatures()) {
throw new AdvancedFeatureException();
}
@ -90,17 +91,17 @@ class EditLiquidsoapConfigController
$tok = strtok($tokens);
}
$settings = $station->getBackendConfig();
$form = new Form($formConfig, ['backend_config' => $settings->toArray()]);
$backendConfig = $station->getBackendConfig();
$form = new Form($formConfig, ['backend_config' => $backendConfig->toArray()]);
if ($request->isPost() && $form->isValid($request->getParsedBody())) {
$data = $form->getValues();
foreach ($data['backend_config'] as $configKey => $configValue) {
$settings[$configKey] = $configValue;
$backendConfig[$configKey] = $configValue;
}
$station->setBackendConfig($settings);
$station->setBackendConfig($backendConfig);
$em->persist($station);
$em->flush();

View File

@ -19,11 +19,13 @@ class Settings extends AbstractFixture
'useRadioProxy' => true,
'checkForUpdates' => true,
'externalIp' => '127.0.0.1',
'enableAdvancedFeatures' => true,
];
$isDemoMode = (!empty(getenv('INIT_DEMO_API_KEY') ?? ''));
if ($isDemoMode) {
$settings['analytics'] = Entity\Analytics::LEVEL_NO_IP;
$settings['checkForUpdates'] = false;
$settings['publicCustomJs'] = <<<'JS'
$(function() {
if ($('body').hasClass('login-content')) {

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace App\Entity\Migration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20210203030115 extends AbstractMigration
{
public function getDescription(): string
{
return 'Migrate advanced features setting to database.';
}
public function up(Schema $schema): void
{
// No-op
$this->addSql('SELECT 1');
}
public function postUp(Schema $schema): void
{
$this->connection->delete(
'settings',
[
'setting_key' => 'enableAdvancedFeatures',
]
);
$this->connection->insert(
'settings',
[
'setting_key' => 'enableAdvancedFeatures',
'setting_value' => 'true',
]
);
}
public function down(Schema $schema): void
{
// No-op
$this->addSql('SELECT 1');
}
}

View File

@ -768,4 +768,20 @@ class Settings
{
$this->setGeoliteLastRun(time());
}
/**
* @OA\Property(example=false)
* @var bool Whether to enable "advanced" functionality in the system that is intended for power users.
*/
protected bool $enableAdvancedFeatures = false;
public function getEnableAdvancedFeatures(): bool
{
return $this->enableAdvancedFeatures;
}
public function setEnableAdvancedFeatures(bool $enableAdvancedFeatures): void
{
$this->enableAdvancedFeatures = $enableAdvancedFeatures;
}
}

View File

@ -5,7 +5,6 @@
namespace App\Entity;
use App\Annotations\AuditLog;
use App\Environment;
use App\Normalizer\Annotation\DeepNormalize;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
@ -580,11 +579,6 @@ class StationPlaylist
*/
public function getBackendOptions(): array
{
$environment = Environment::getInstance();
if (!$environment->enableAdvancedFeatures()) {
return [];
}
return explode(',', $this->backend_options);
}

View File

@ -33,8 +33,6 @@ class Environment
public const DOCKER_REVISION = 'AZURACAST_DC_REVISION';
public const ENABLE_ADVANCED_FEATURES = 'ENABLE_ADVANCED_FEATURES';
public const LANG = 'LANG';
public const SUPPORTED_LOCALES = 'SUPPORTED_LOCALES';
@ -194,15 +192,6 @@ class Environment
return ($compareVersion >= $version);
}
public function enableAdvancedFeatures(): bool
{
if (!$this->isDocker()) {
return true;
}
return (bool)($this->data[self::ENABLE_ADVANCED_FEATURES] ?? true);
}
public function getLang(): ?string
{
return $this->data[self::LANG];

View File

@ -2,7 +2,7 @@
namespace App\Event;
use App\Environment;
use App\Entity\Settings;
use App\Http\ServerRequest;
use Symfony\Contracts\EventDispatcher\Event;
@ -10,14 +10,14 @@ abstract class AbstractBuildMenu extends Event
{
protected ServerRequest $request;
protected Environment $environment;
protected Settings $settings;
protected array $menu = [];
public function __construct(ServerRequest $request, Environment $environment)
public function __construct(ServerRequest $request, Settings $settings)
{
$this->request = $request;
$this->environment = $environment;
$this->settings = $settings;
}
public function getRequest(): ServerRequest
@ -25,9 +25,9 @@ abstract class AbstractBuildMenu extends Event
return $this->request;
}
public function getEnvironment(): Environment
public function getSettings(): Settings
{
return $this->environment;
return $this->settings;
}
/**

View File

@ -2,17 +2,17 @@
namespace App\Event;
use App\Entity\Settings;
use App\Entity\Station;
use App\Environment;
use App\Http\ServerRequest;
class BuildStationMenu extends AbstractBuildMenu
{
protected Station $station;
public function __construct(ServerRequest $request, Environment $environment, Station $station)
public function __construct(ServerRequest $request, Settings $settings, Station $station)
{
parent::__construct($request, $environment);
parent::__construct($request, $settings);
$this->station = $station;
}

View File

@ -29,8 +29,9 @@ class StationCloneForm extends StationForm
EntityManagerInterface $em,
Serializer $serializer,
ValidatorInterface $validator,
Entity\Repository\StationRepository $station_repo,
Entity\Repository\StationRepository $stationRepo,
Entity\Repository\StorageLocationRepository $storageLocationRepo,
Entity\Repository\SettingsRepository $settingsRepo,
Config $config,
Environment $environment,
Adapters $adapters,
@ -42,8 +43,9 @@ class StationCloneForm extends StationForm
$em,
$serializer,
$validator,
$station_repo,
$stationRepo,
$storageLocationRepo,
$settingsRepo,
$config,
$environment,
$adapters

View File

@ -15,10 +15,12 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
class StationForm extends EntityForm
{
protected Entity\Repository\StationRepository $station_repo;
protected Entity\Repository\StationRepository $stationRepo;
protected Entity\Repository\StorageLocationRepository $storageLocationRepo;
protected Entity\Repository\SettingsRepository $settingsRepo;
protected Environment $environment;
protected Adapters $adapters;
@ -27,15 +29,18 @@ class StationForm extends EntityForm
EntityManagerInterface $em,
Serializer $serializer,
ValidatorInterface $validator,
Entity\Repository\StationRepository $station_repo,
Entity\Repository\StationRepository $stationRepo,
Entity\Repository\StorageLocationRepository $storageLocationRepo,
Entity\Repository\SettingsRepository $settingsRepo,
Config $config,
Environment $environment,
Adapters $adapters
) {
$this->entityClass = Entity\Station::class;
$this->station_repo = $station_repo;
$this->stationRepo = $stationRepo;
$this->storageLocationRepo = $storageLocationRepo;
$this->settingsRepo = $settingsRepo;
$this->environment = $environment;
$this->adapters = $adapters;
@ -51,7 +56,8 @@ class StationForm extends EntityForm
public function configure(array $options): void
{
// Hide "advanced" fields if advanced features are hidden on this installation.
if (!$this->environment->enableAdvancedFeatures()) {
$settings = $this->settingsRepo->readSettings();
if (!$settings->getEnableAdvancedFeatures()) {
foreach ($options['groups'] as $groupId => $group) {
foreach ($group['elements'] as $elementKey => $element) {
$elementOptions = (array)$element[1];
@ -107,34 +113,31 @@ class StationForm extends EntityForm
$request->getRouter()->named('admin:storage_locations:index')
);
$mediaStorageField = $this->getField('media_storage_location_id');
$mediaStorageField->setOption('description', $storageLocationsDesc);
$mediaStorageField->setOption(
'choices',
$this->storageLocationRepo->fetchSelectByType(
Entity\StorageLocation::TYPE_STATION_MEDIA,
$create_mode,
__('Create a new storage location based on the base directory.'),
)
);
$recordingsStorageField = $this->getField('recordings_storage_location_id');
$recordingsStorageField->setOption('description', $storageLocationsDesc);
$recordingsStorageField->setOption(
'choices',
$this->storageLocationRepo->fetchSelectByType(
Entity\StorageLocation::TYPE_STATION_RECORDINGS,
$create_mode,
__('Create a new storage location based on the base directory.'),
)
);
$this->options['groups']['admin']['elements']['recordings_storage_location_id'][1]['choices'] =
$this->storageLocationRepo->fetchSelectByType(
Entity\StorageLocation::TYPE_STATION_RECORDINGS,
$create_mode,
__('Create a new storage location based on the base directory.'),
if ($this->hasField('media_storage_location_id')) {
$mediaStorageField = $this->getField('media_storage_location_id');
$mediaStorageField->setOption('description', $storageLocationsDesc);
$mediaStorageField->setOption(
'choices',
$this->storageLocationRepo->fetchSelectByType(
Entity\StorageLocation::TYPE_STATION_MEDIA,
$create_mode,
__('Create a new storage location based on the base directory.'),
)
);
}
if ($this->hasField('recordings_storage_location_id')) {
$recordingsStorageField = $this->getField('recordings_storage_location_id');
$recordingsStorageField->setOption('description', $storageLocationsDesc);
$recordingsStorageField->setOption(
'choices',
$this->storageLocationRepo->fetchSelectByType(
Entity\StorageLocation::TYPE_STATION_RECORDINGS,
$create_mode,
__('Create a new storage location based on the base directory.'),
)
);
}
}
if ('POST' === $request->getMethod() && $this->isValid($request->getParsedBody())) {
@ -178,8 +181,8 @@ class StationForm extends EntityForm
}
return ($create_mode)
? $this->station_repo->create($record)
: $this->station_repo->edit($record);
? $this->stationRepo->create($record)
: $this->stationRepo->edit($record);
}
return false;

View File

@ -2,7 +2,7 @@
namespace App\Middleware\Module;
use App\Environment;
use App\Entity\Repository\SettingsRepository;
use App\Event;
use App\EventDispatcher;
use App\Http\ServerRequest;
@ -18,17 +18,19 @@ class Admin
{
protected EventDispatcher $dispatcher;
protected Environment $environment;
protected SettingsRepository $settingsRepo;
public function __construct(EventDispatcher $dispatcher, Environment $environment)
public function __construct(EventDispatcher $dispatcher, SettingsRepository $settingsRepo)
{
$this->dispatcher = $dispatcher;
$this->environment = $environment;
$this->settingsRepo = $settingsRepo;
}
public function __invoke(ServerRequest $request, RequestHandlerInterface $handler): ResponseInterface
{
$event = new Event\BuildAdminMenu($request, $this->environment);
$settings = $this->settingsRepo->readSettings();
$event = new Event\BuildAdminMenu($request, $settings);
$this->dispatcher->dispatch($event);
$view = $request->getView();

View File

@ -2,7 +2,7 @@
namespace App\Middleware\Module;
use App\Environment;
use App\Entity\Repository\SettingsRepository;
use App\Event;
use App\EventDispatcher;
use App\Http\ServerRequest;
@ -18,12 +18,12 @@ class Stations
{
protected EventDispatcher $dispatcher;
protected Environment $environment;
protected SettingsRepository $settingsRepo;
public function __construct(EventDispatcher $dispatcher, Environment $environment)
public function __construct(EventDispatcher $dispatcher, SettingsRepository $settingsRepo)
{
$this->dispatcher = $dispatcher;
$this->environment = $environment;
$this->settingsRepo = $settingsRepo;
}
public function __invoke(ServerRequest $request, RequestHandlerInterface $handler): ResponseInterface
@ -42,7 +42,9 @@ class Stations
]
);
$event = new Event\BuildStationMenu($request, $this->environment, $station);
$settings = $this->settingsRepo->readSettings();
$event = new Event\BuildStationMenu($request, $settings, $station);
$this->dispatcher->dispatch($event);
$active_tab = null;

View File

@ -29,6 +29,8 @@ class ConfigWriter implements EventSubscriberInterface
protected EntityManagerInterface $em;
protected Entity\Repository\SettingsRepository $settingsRepo;
protected Liquidsoap $liquidsoap;
protected FilesystemManager $filesystem;
@ -39,12 +41,14 @@ class ConfigWriter implements EventSubscriberInterface
public function __construct(
EntityManagerInterface $em,
Entity\Repository\SettingsRepository $settingsRepo,
Liquidsoap $liquidsoap,
FilesystemManager $filesystem,
Environment $environment,
LoggerInterface $logger
) {
$this->em = $em;
$this->settingsRepo = $settingsRepo;
$this->liquidsoap = $liquidsoap;
$this->filesystem = $filesystem;
$this->environment = $environment;
@ -98,7 +102,8 @@ class ConfigWriter implements EventSubscriberInterface
return;
}
if (!$this->environment->enableAdvancedFeatures()) {
$settings = $this->settingsRepo->readSettings();
if (!$settings->getEnableAdvancedFeatures()) {
return;
}

View File

@ -1,4 +1,4 @@
<?php //[STAMP] 7db5db0338068d6dd173afbcecda5964
<?php //[STAMP] 28939ab87a9833860b309fa80c19ba9c
namespace _generated;
// This class was automatically generated by build task
@ -1496,8 +1496,7 @@ trait FunctionalTesterActions
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Saves current page's HTML into a temprary file.
* Use this method in debug mode within an interactive pause to get a source code of current page.
* Use this method within an [interactive pause](https://codeception.com/docs/02-GettingStarted#Interactive-Pause) to save the HTML source code of the current page.
*
* ```php
* <?php
@ -2370,6 +2369,79 @@ trait FunctionalTesterActions
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Prevents automatic redirects to be followed by the client
*
* ```php
* <?php
* $I->stopFollowingRedirects();
* ```
*
* @part xml
* @part json
* @see \Codeception\Module\REST::stopFollowingRedirects()
*/
public function stopFollowingRedirects() {
return $this->getScenario()->runStep(new \Codeception\Step\Action('stopFollowingRedirects', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Enables automatic redirects to be followed by the client
*
* ```php
* <?php
* $I->startFollowingRedirects();
* ```
*
* @part xml
* @part json
* @see \Codeception\Module\REST::startFollowingRedirects()
*/
public function startFollowingRedirects() {
return $this->getScenario()->runStep(new \Codeception\Step\Action('startFollowingRedirects', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Follow pending redirect if there is one.
*
* ```php
* <?php
* $I->followRedirect();
* ```
*
* @see \Codeception\Lib\InnerBrowser::followRedirect()
*/
public function followRedirect() {
return $this->getScenario()->runStep(new \Codeception\Step\Action('followRedirect', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Sets the maximum number of redirects that the Client can follow.
*
* ```php
* <?php
* $I->setMaxRedirects(2);
* ```
*
* @param int $maxRedirects
* @see \Codeception\Lib\InnerBrowser::setMaxRedirects()
*/
public function setMaxRedirects($maxRedirects) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('setMaxRedirects', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
@ -4370,44 +4442,6 @@ trait FunctionalTesterActions
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Prevents automatic redirects to be followed by the client
*
* ```php
* <?php
* $I->stopFollowingRedirects();
* ```
*
* @part xml
* @part json
* @see \Codeception\Module\REST::stopFollowingRedirects()
*/
public function stopFollowingRedirects() {
return $this->getScenario()->runStep(new \Codeception\Step\Action('stopFollowingRedirects', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Enables automatic redirects to be followed by the client
*
* ```php
* <?php
* $I->startFollowingRedirects();
* ```
*
* @part xml
* @part json
* @see \Codeception\Module\REST::startFollowingRedirects()
*/
public function startFollowingRedirects() {
return $this->getScenario()->runStep(new \Codeception\Step\Action('startFollowingRedirects', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*

View File

@ -1,4 +1,4 @@
<?php //[STAMP] f6796b4429a3dc71f3b89daa9b43789c
<?php //[STAMP] 966c4bdffa33a64492075fb3684ff642
namespace _generated;
// This class was automatically generated by build task
@ -1504,8 +1504,7 @@ trait UnitTesterActions
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Saves current page's HTML into a temprary file.
* Use this method in debug mode within an interactive pause to get a source code of current page.
* Use this method within an [interactive pause](https://codeception.com/docs/02-GettingStarted#Interactive-Pause) to save the HTML source code of the current page.
*
* ```php
* <?php
@ -2345,4 +2344,73 @@ trait UnitTesterActions
public function haveServerParameter($name, $value) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('haveServerParameter', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Prevents automatic redirects to be followed by the client.
*
* ```php
* <?php
* $I->stopFollowingRedirects();
* ```
*
* @see \Codeception\Lib\InnerBrowser::stopFollowingRedirects()
*/
public function stopFollowingRedirects() {
return $this->getScenario()->runStep(new \Codeception\Step\Action('stopFollowingRedirects', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Enables automatic redirects to be followed by the client.
*
* ```php
* <?php
* $I->startFollowingRedirects();
* ```
*
* @see \Codeception\Lib\InnerBrowser::startFollowingRedirects()
*/
public function startFollowingRedirects() {
return $this->getScenario()->runStep(new \Codeception\Step\Action('startFollowingRedirects', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Follow pending redirect if there is one.
*
* ```php
* <?php
* $I->followRedirect();
* ```
*
* @see \Codeception\Lib\InnerBrowser::followRedirect()
*/
public function followRedirect() {
return $this->getScenario()->runStep(new \Codeception\Step\Action('followRedirect', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Sets the maximum number of redirects that the Client can follow.
*
* ```php
* <?php
* $I->setMaxRedirects(2);
* ```
*
* @param int $maxRedirects
* @see \Codeception\Lib\InnerBrowser::setMaxRedirects()
*/
public function setMaxRedirects($maxRedirects) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('setMaxRedirects', func_get_args()));
}
}