Move port checker to Validation; create StationCloneForm, etc.

This commit is contained in:
Buster Neece 2019-04-09 04:06:44 -05:00
parent c735b07a67
commit 3554d65c75
No known key found for this signature in database
GPG Key ID: 6D9E12FF03411F4E
22 changed files with 390 additions and 297 deletions

8
composer.lock generated
View File

@ -157,12 +157,12 @@
"source": {
"type": "git",
"url": "https://github.com/AzuraCast/azuraforms.git",
"reference": "2731102fcf407ebc45c5efdd6278d5a1952f43bd"
"reference": "3cbaf8d771f217111f91a01f7082a39ed279190d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/AzuraCast/azuraforms/zipball/2731102fcf407ebc45c5efdd6278d5a1952f43bd",
"reference": "2731102fcf407ebc45c5efdd6278d5a1952f43bd",
"url": "https://api.github.com/repos/AzuraCast/azuraforms/zipball/3cbaf8d771f217111f91a01f7082a39ed279190d",
"reference": "3cbaf8d771f217111f91a01f7082a39ed279190d",
"shasum": ""
},
"require": {
@ -200,7 +200,7 @@
],
"description": "A modern, namespaced, configuration-driven forms engine for PHP.",
"homepage": "https://github.com/AzuraCast/azuraforms",
"time": "2019-03-04T19:59:02+00:00"
"time": "2019-04-09T08:54:02+00:00"
},
{
"name": "azuracast/nowplaying",

View File

@ -120,7 +120,6 @@ return [
'label_class' => 'advanced',
'description' => __('No other program can be using this port. Leave blank to automatically assign a port.'),
'belongsTo' => 'frontend_config',
'class' => 'input-port',
]
],
@ -292,7 +291,6 @@ return [
'label_class' => 'advanced',
'description' => __('No other program can be using this port. Leave blank to automatically assign a port.<br><b>Note:</b> The port after this one (n+1) will automatically be used for legacy connections.'),
'belongsTo' => 'backend_config',
'class' => 'input-port',
]
],
@ -343,7 +341,6 @@ return [
'label_class' => 'advanced',
'description' => __('This port is not used by any external process. Only modify this port if the assigned port is in use. Leave blank to automatically assign a port.'),
'belongsTo' => 'backend_config',
'class' => 'input-port',
]
],

View File

@ -106,7 +106,7 @@ return function(App $app)
$this->map(['GET', 'POST'], '/add', Controller\Admin\StationsController::class.':editAction')
->setName('admin:stations:add');
$this->map(['GET', 'POST'], '/clone/{id}', Controller\Admin\Stations\CloneController::class)
$this->map(['GET', 'POST'], '/clone/{id}', Controller\Admin\StationsController::class.':cloneAction')
->setName('admin:stations:clone');
$this->get('/delete/{id}/{csrf}', Controller\Admin\StationsController::class.':deleteAction')

View File

@ -64,7 +64,8 @@ return function (\Azura\Container $di)
$di[\App\Sync\Task\Media::class],
$di[\App\Radio\Adapters::class],
$di[\App\Radio\Configuration::class],
$di[\Azura\Cache::class]
$di[\Azura\Cache::class],
$di[\Symfony\Component\Validator\Validator\ValidatorInterface::class]
);
};
@ -280,6 +281,12 @@ return function (\Azura\Container $di)
);
};
$di[\App\Validator\Constraints\StationPortCheckerValidator::class] = function($di) {
return new \App\Validator\Constraints\StationPortCheckerValidator(
$di[\App\Radio\Configuration::class]
);
};
// Radio management
$di->register(new \App\Provider\RadioProvider);

View File

@ -1,152 +0,0 @@
<?php
namespace App\Controller\Admin\Stations;
use Azura\Cache;
use App\Radio\Configuration;
use Doctrine\ORM\EntityManager;
use App\Entity;
use App\Http\Request;
use App\Http\Response;
use Psr\Http\Message\ResponseInterface;
class CloneController
{
/** @var EntityManager */
protected $em;
/** @var Cache */
protected $cache;
/** @var Configuration */
protected $configuration;
/** @var array */
protected $form_config;
/** @var Entity\Repository\StationRepository */
protected $record_repo;
/**
* @param EntityManager $em
* @param Cache $cache
* @param Configuration $configuration
* @param array $form_config
*
* @see \App\Provider\AdminProvider
*/
public function __construct(EntityManager $em, Cache $cache, Configuration $configuration, array $form_config)
{
$this->em = $em;
$this->cache = $cache;
$this->configuration = $configuration;
$this->form_config = $form_config;
$this->record_repo = $em->getRepository(Entity\Station::class);
}
public function __invoke(Request $request, Response $response, $id): ResponseInterface
{
$record = $this->record_repo->find((int)$id);
if (!($record instanceof Entity\Station)) {
throw new \App\Exception\NotFound(__('%s not found.', __('Station')));
}
$form = new \AzuraForms\Form($this->form_config);
$form->populate([
'name' => $record->getName().' - Copy',
'description' => $record->getDescription(),
]);
if ($_POST && $form->isValid($_POST)) {
$data = $form->getValues();
// Assemble new station from old station based on form parameters.
$new_record_data = $this->record_repo->toArray($record);
$new_record_data['name'] = $data['name'];
$new_record_data['description'] = $data['description'];
$unset_values = [
'short_name',
'radio_base_dir',
'nowplaying',
'nowplaying_timestamp',
'is_streamer_live',
'needs_restart',
'has_started',
];
foreach($unset_values as $unset_value) {
unset($new_record_data[$unset_value]);
}
if ($data['clone_media'] === 'share') {
$new_record_data['radio_media_dir'] = $record->getRadioMediaDir();
} else {
unset($new_record_data['radio_media_dir'], $new_record_data['storage_used']);
}
// Trigger normal creation process of station.
$new_record = $this->record_repo->create($new_record_data);
// Force port reassignment
$this->configuration->assignRadioPorts($new_record, true);
$this->configuration->writeConfiguration($new_record);
// Copy associated records if applicable.
if ($data['clone_media'] === 'copy') {
copy($record->getRadioMediaDir(), $new_record->getRadioMediaDir());
}
if ($data['clone_playlists'] == 1) {
foreach ($record->getPlaylists() as $source_record) {
$dest_record_data = $this->record_repo->toArray($source_record);
unset($dest_record_data['id'], $dest_record_data['station_id']);
$dest_record = new Entity\StationPlaylist($new_record);
$this->record_repo->fromArray($dest_record, $dest_record_data);
$this->em->persist($dest_record);
}
}
if ($data['clone_streamers'] == 1) {
foreach ($record->getStreamers() as $source_record) {
$dest_record_data = $this->record_repo->toArray($source_record);
unset($dest_record_data['id'], $dest_record_data['station_id']);
$dest_record = new Entity\StationStreamer($new_record);
$this->record_repo->fromArray($dest_record, $dest_record_data);
$this->em->persist($dest_record);
}
}
if ($data['clone_permissions'] == 1) {
foreach ($record->getPermissions() as $source_record) {
$dest_record_data = $this->record_repo->toArray($source_record);
unset($dest_record_data['id'], $dest_record_data['station_id']);
$dest_record = new Entity\RolePermission($source_record->getRole(), $new_record);
$this->record_repo->fromArray($dest_record, $dest_record_data);
$this->em->persist($dest_record);
}
}
$this->em->flush();
// Clear station cache.
$this->cache->remove('stations');
$request->getSession()->flash(__('Changes saved.'), 'green');
return $response->withRedirect($request->getRouter()->named('admin:stations:index'));
}
return $request->getView()->renderToResponse($response, 'system/form_page', [
'form' => $form,
'render_mode' => 'edit',
'title' => __('Clone Station: %s', $record->getName())
]);
}
}

View File

@ -15,6 +15,9 @@ class StationsController
/** @var Form\StationForm */
protected $station_form;
/** @var Form\StationCloneForm */
protected $clone_form;
/** @var string */
protected $csrf_namespace = 'admin_stations';
@ -23,9 +26,13 @@ class StationsController
*
* @see \App\Provider\AdminProvider
*/
public function __construct(Form\StationForm $station_form)
{
public function __construct(
Form\StationForm $station_form,
Form\StationCloneForm $clone_form
) {
$this->station_form = $station_form;
$this->clone_form = $clone_form;
$this->record_repo = $station_form->getEntityRepository();
}
@ -69,4 +76,23 @@ class StationsController
$request->getSession()->flash(__('%s deleted.', __('Station')), 'green');
return $response->withRedirect($request->getRouter()->named('admin:stations:index'));
}
public function cloneAction(Request $request, Response $response, $id): ResponseInterface
{
$record = $this->record_repo->find((int)$id);
if (!($record instanceof Entity\Station)) {
throw new \App\Exception\NotFound(__('%s not found.', __('Station')));
}
if (false !== $this->clone_form->process($request, $record)) {
$request->getSession()->flash(__('Changes saved.'), 'green');
return $response->withRedirect($request->getRouter()->named('admin:stations:index'));
}
return $request->getView()->renderToResponse($response, 'system/form_page', [
'form' => $this->clone_form,
'render_mode' => 'edit',
'title' => __('Clone Station: %s', $record->getName())
]);
}
}

View File

@ -121,16 +121,33 @@ class StationsController extends AbstractGenericCrudController
]);
}
/** @inheritDoc */
protected function _createRecord($data): object
{
return $this->station_repo->create($data);
}
/** @inheritDoc */
protected function _editRecord($data, $record = null, array $context = []): object
{
return $this->station_repo->edit($data, $record);
$create_mode = (null === $record);
if (null === $data) {
throw new \InvalidArgumentException('Could not parse input data.');
}
$record = $this->_denormalizeToRecord($data, $record, $context);
$errors = $this->validator->validate($record);
if (count($errors) > 0) {
$e = new \App\Exception\Validation((string)$errors);
$e->setDetailedErrors($errors);
throw $e;
}
if ($create_mode) {
$this->station_repo->create($record);
} else {
$this->station_repo->edit($record);
}
$this->em->persist($record);
$this->em->flush($record);
return $record;
}
/** @inheritDoc */

View File

@ -3,6 +3,7 @@ namespace App\Controller\Frontend;
use App\Acl;
use App\Auth;
use App\Form\StationForm;
use App\Radio\Adapters;
use App\Radio\Configuration;
use App\Radio\Frontend\SHOUTcast;
@ -23,8 +24,8 @@ class SetupController
/** @var Acl */
protected $acl;
/** @var array */
protected $station_form_config;
/** @var StationForm */
protected $station_form;
/** @var array */
protected $settings_form_config;
@ -33,7 +34,7 @@ class SetupController
* @param EntityManager $em
* @param Auth $auth
* @param Acl $acl
* @param array $station_form_config
* @param StationForm $station_form
* @param array $settings_form_config
*
* @see \App\Provider\FrontendProvider
@ -42,14 +43,14 @@ class SetupController
EntityManager $em,
Auth $auth,
Acl $acl,
array $station_form_config,
StationForm $station_form,
array $settings_form_config
)
{
$this->em = $em;
$this->auth = $auth;
$this->acl = $acl;
$this->station_form_config = $station_form_config;
$this->station_form = $station_form;
$this->settings_form_config = $settings_form_config;
}
@ -149,28 +150,12 @@ class SetupController
return $response->withRedirect($request->getRouter()->named('setup:'.$current_step));
}
// Set up station form.
$form_config = $this->station_form_config;
unset($form_config['groups']['profile']['legend']);
if (!SHOUTcast::isInstalled()) {
$form_config['groups']['select_frontend_type']['elements']['frontend_type'][1]['description'] = __('Want to use SHOUTcast 2? <a href="%s" target="_blank">Install it here</a>, then reload this page.', $request->getRouter()->named('admin:install:shoutcast'));
}
$form = new \AzuraForms\Form($form_config);
if (!empty($_POST) && $form->isValid($_POST)) {
$data = $form->getValues();
/** @var Entity\Repository\StationRepository $station_repo */
$station_repo = $this->em->getRepository(Entity\Station::class);
$station_repo->create($data);
if (false !== $this->station_form->process($request)) {
return $response->withRedirect($request->getRouter()->named('setup:settings'));
}
return $request->getView()->renderToResponse($response, 'frontend/setup/station', [
'form' => $form,
'form' => $this->station_form,
]);
}

View File

@ -10,6 +10,7 @@ use Azura\Cache;
use Azura\Doctrine\Repository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class StationRepository extends Repository
{
@ -22,6 +23,9 @@ class StationRepository extends Repository
/** @var Configuration */
protected $configuration;
/** @var ValidatorInterface */
protected $validator;
/** @var Cache */
protected $cache;
@ -31,7 +35,8 @@ class StationRepository extends Repository
Media $media_sync,
Adapters $adapters,
Configuration $configuration,
Cache $cache
Cache $cache,
ValidatorInterface $validator
) {
parent::__construct($em, $class);
@ -39,6 +44,7 @@ class StationRepository extends Repository
$this->adapters = $adapters;
$this->configuration = $configuration;
$this->cache = $cache;
$this->validator = $validator;
}
/**
@ -89,30 +95,16 @@ class StationRepository extends Repository
}
/**
* @param array $data
* @param Entity\Station|null $record
* @return Entity\Station
*/
public function editOrCreate($data, Entity\Station $record = null): Entity\Station
{
return (null === $record)
? $this->create($data)
: $this->edit($data, $record);
}
/**
* @param array $data
* @param Entity\Station $record
* @return Entity\Station
*/
public function edit($data, Entity\Station $record): Entity\Station
public function edit(Entity\Station $record): void
{
$old_frontend = $record->getFrontendType();
$old_backend = $record->getBackendType();
/** @var Entity\Station $original_record */
$original_record = $this->_em->getUnitOfWork()->getOriginalEntityData($record);
$this->fromArray($record, $data);
$this->_em->persist($record);
$this->_em->flush($record);
// Get the original values to check for changes.
$old_frontend = $original_record['frontend_type'];
$old_backend = $original_record['backend_type'];
$frontend_changed = ($old_frontend !== $record->getFrontendType());
$backend_changed = ($old_backend !== $record->getBackendType());
@ -126,22 +118,15 @@ class StationRepository extends Repository
$this->configuration->writeConfiguration($record, $adapter_changed);
$this->cache->remove('stations');
return $record;
}
/**
* Create a station based on the specified data.
* Handle tasks necessary to a station's creation.
*
* @param array $data Array of data to populate the station with.
* @return Entity\Station
* @throws \Exception
* @param Entity\Station $station
*/
public function create($data): Entity\Station
public function create(Entity\Station $station): void
{
$station = new Entity\Station;
$this->fromArray($station, $data);
// Create path for station.
$station_base_dir = dirname(APP_INCLUDE_ROOT) . '/stations';
@ -179,8 +164,6 @@ class StationRepository extends Repository
$this->_em->flush();
$this->cache->remove('stations');
return $station;
}
/**
@ -230,21 +213,4 @@ class StationRepository extends Repository
$this->cache->remove('stations');
}
/**
* @param mixed|null $port
* @param Entity\Station|null $except_record
* @return bool
*/
public function isPortUsed($port, Entity\Station $except_record = null): bool
{
if (!empty($port)) {
$port = (int)$port;
$used_ports = $this->configuration->getUsedPorts($except_record);
return isset($used_ports[$port]);
}
return false;
}
}

View File

@ -10,6 +10,7 @@ use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use OpenApi\Annotations as OA;
use Symfony\Component\Serializer\Annotation\Groups;
use App\Validator\Constraints as AppAssert;
use App\Radio\Frontend\AbstractFrontend;
use App\Radio\Remote\AdapterProxy;
@ -25,6 +26,7 @@ use Psr\Http\Message\UriInterface;
* @ORM\HasLifecycleCallbacks
*
* @OA\Schema(type="object", schema="Station")
* @AppAssert\StationPortChecker()
*/
class Station
{
@ -41,7 +43,7 @@ class Station
* @ORM\GeneratedValue(strategy="IDENTITY")
*
* @OA\Property(example=1)
* @var int
* @var int|null
*/
protected $id;
@ -343,9 +345,9 @@ class Station
}
/**
* @return int
* @return int|null
*/
public function getId(): int
public function getId(): ?int
{
return $this->id;
}

View File

@ -116,7 +116,6 @@ class EntityForm extends \AzuraForms\Form
$errors = $this->validator->validate($record);
if (count($errors) > 0) {
$other_errors = [];
foreach($errors as $error) {
/** @var ConstraintViolation $error */
$field_name = $error->getPropertyPath();
@ -124,16 +123,9 @@ class EntityForm extends \AzuraForms\Form
if (isset($this->fields[$field_name])) {
$this->fields[$field_name]->addError($error->getMessage());
} else {
$other_errors[] = $error;
$this->addError($error->getMessage());
}
}
if (count($other_errors) > 0) {
$e = new \App\Exception\Validation((string)$errors);
$e->setDetailedErrors($errors);
throw $e;
}
return false;
}
@ -146,6 +138,8 @@ class EntityForm extends \AzuraForms\Form
}
/**
* The old ->toArray().
*
* @param object $record
* @param array $context
* @return array
@ -180,6 +174,8 @@ class EntityForm extends \AzuraForms\Form
}
/**
* The old ->fromArray().
*
* @param array $data
* @param object|null $record
* @param array $context

View File

@ -0,0 +1,138 @@
<?php
namespace App\Form;
use App\Acl;
use App\Entity;
use App\Http\Request;
use App\Radio\Configuration;
use App\Radio\Frontend\SHOUTcast;
use Azura\Doctrine\Repository;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class StationCloneForm extends StationForm
{
/** @var Configuration */
protected $configuration;
/**
* @param EntityManager $em
* @param Serializer $serializer
* @param ValidatorInterface $validator
* @param Acl $acl
* @param Configuration $configuration
* @param array $form_config
*
* @see \App\Provider\FormProvider
*/
public function __construct(
EntityManager $em,
Serializer $serializer,
ValidatorInterface $validator,
Acl $acl,
Configuration $configuration,
array $form_config
) {
parent::__construct($em, $serializer, $validator, $acl, $form_config);
$this->configuration = $configuration;
}
/**
* @inheritdoc
*/
public function process(Request $request, $record = null)
{
if (!$record instanceof Entity\Station) {
throw new \InvalidArgumentException('Record must be a station.');
}
$this->populate([
'name' => $record->getName().' - Copy',
'description' => $record->getDescription(),
]);
if ($request->isPost() && $this->isValid($request->getParsedBody())) {
$data = $this->getValues();
$new_record_data = $this->_normalizeRecord($record);
$new_record_data['name'] = $data['name'];
$new_record_data['description'] = $data['description'];
$unset_values = [
'short_name',
'radio_base_dir',
'nowplaying',
'nowplaying_timestamp',
'is_streamer_live',
'needs_restart',
'has_started',
];
foreach($unset_values as $unset_value) {
unset($new_record_data[$unset_value]);
}
if ('share' === $data['clone_media']) {
$new_record_data['radio_media_dir'] = $record->getRadioMediaDir();
} else {
unset($new_record_data['radio_media_dir'], $new_record_data['storage_used']);
}
$new_record = $this->_denormalizeToRecord($new_record_data);
$this->station_repo->create($new_record);
$this->configuration->assignRadioPorts($new_record, true);
$this->configuration->writeConfiguration($new_record);
// Copy associated records if applicable.
if ('copy' === $data['clone_media']) {
copy($record->getRadioMediaDir(), $new_record->getRadioMediaDir());
}
if (1 == $data['clone_playlists']) {
foreach ($record->getPlaylists() as $source_record) {
$dest_record_data = $this->_normalizeRecord($source_record);
unset($dest_record_data['id'], $dest_record_data['station_id']);
$dest_record = $this->serializer->denormalize($data, Entity\StationPlaylist::class, null, [
ObjectNormalizer::OBJECT_TO_POPULATE => new Entity\StationPlaylist($new_record),
]);
$this->em->persist($dest_record);
}
}
if (1 == $data['clone_streamers']) {
foreach ($record->getStreamers() as $source_record) {
$dest_record_data = $this->_normalizeRecord($source_record);
unset($dest_record_data['id'], $dest_record_data['station_id']);
$dest_record = $this->serializer->denormalize($data, Entity\StationStreamer::class, null, [
ObjectNormalizer::OBJECT_TO_POPULATE => new Entity\StationStreamer($new_record),
]);
$this->em->persist($dest_record);
}
}
if (1 == $data['clone_permissions']) {
foreach ($record->getPermissions() as $source_record) {
$dest_record_data = $this->_normalizeRecord($source_record);
unset($dest_record_data['id'], $dest_record_data['station_id']);
$dest_record = $this->serializer->denormalize($data, Entity\RolePermission::class, null, [
ObjectNormalizer::OBJECT_TO_POPULATE => new Entity\RolePermission($source_record->getRole(), $new_record),
]);
$this->em->persist($dest_record);
}
}
$this->em->flush();
return $new_record;
}
return false;
}
}

View File

@ -4,9 +4,11 @@ namespace App\Form;
use App\Acl;
use App\Entity;
use App\Http\Request;
use App\Radio\Frontend\SHOUTcast;
use Azura\Doctrine\Repository;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class StationForm extends EntityForm
@ -73,29 +75,43 @@ class StationForm extends EntityForm
unset($this->options['groups']['admin']);
}
// Add the port checker validator (which depends on the current record) to the appropriate fields.
$port_checker = function($value) use ($record) {
if ($this->station_repo->isPortUsed($value, $record)) {
return __('This port is currently in use by another station.');
}
return true;
};
foreach($this->fields as $field) {
$attrs = $field->getAttributes();
if (isset($attrs['class']) && strpos($attrs['class'], 'input-port') !== false) {
$field->addValidator($port_checker);
}
if (!SHOUTcast::isInstalled()) {
$this->options['groups']['select_frontend_type']['elements']['frontend_type'][1]['description'] = __('Want to use SHOUTcast 2? <a href="%s" target="_blank">Install it here</a>, then reload this page.', $request->getRouter()->named('admin:install:shoutcast'));
}
if (null !== $record) {
$create_mode = (null === $record);
if (!$create_mode) {
$this->populate($this->_normalizeRecord($record));
}
if ($request->isPost() && $this->isValid($request->getParsedBody())) {
$data = $this->getValues();
return $this->station_repo->editOrCreate($data, $record);
$record = $this->_denormalizeToRecord($data, $record);
$errors = $this->validator->validate($record);
if (count($errors) > 0) {
foreach($errors as $error) {
/** @var ConstraintViolation $error */
$field_name = $error->getPropertyPath();
if (isset($this->fields[$field_name])) {
$this->fields[$field_name]->addError($error->getMessage());
} else {
$this->addError($error->getMessage());
}
}
return false;
}
if ($create_mode) {
$this->station_repo->create($record);
} else {
$this->station_repo->edit($record);
}
$this->em->persist($record);
$this->em->flush($record);
return $record;
}
return false;

View File

@ -74,19 +74,8 @@ class AdminProvider implements ServiceProviderInterface
$di[Admin\StationsController::class] = function($di) {
return new Admin\StationsController(
$di[\App\Form\StationForm::class]
);
};
$di[Admin\Stations\CloneController::class] = function($di) {
/** @var \Azura\Config $config */
$config = $di[\Azura\Config::class];
return new Admin\Stations\CloneController(
$di[EntityManager::class],
$di[\Azura\Cache::class],
$di[\App\Radio\Configuration::class],
$config->get('forms/station_clone')
$di[\App\Form\StationForm::class],
$di[\App\Form\StationCloneForm::class]
);
};

View File

@ -46,6 +46,20 @@ class FormProvider implements ServiceProviderInterface
);
};
$di[Form\StationCloneForm::class] = function($di) {
/** @var \Azura\Config $config */
$config = $di[\Azura\Config::class];
return new Form\StationCloneForm(
$di[EntityManager::class],
$di[Serializer::class],
$di[ValidatorInterface::class],
$di[\App\Acl::class],
$di[\App\Radio\Configuration::class],
$config->get('forms/station_clone')
);
};
$di[Form\UserForm::class] = function($di) {
/** @var \Azura\Config $config */
$config = $di[\Azura\Config::class];

View File

@ -70,7 +70,7 @@ class FrontendProvider implements ServiceProviderInterface
$di[\Doctrine\ORM\EntityManager::class],
$di[\App\Auth::class],
$di[\App\Acl::class],
$config->get('forms/station'),
$di[\App\Form\StationForm::class],
$config->get('forms/settings')
);
};

View File

@ -346,7 +346,7 @@ class Configuration
}
}
if ($except_station !== null) {
if (null !== $except_station && null !== $except_station->getId()) {
return array_filter($used_ports, function($station_reference) use ($except_station) {
return ($station_reference['id'] !== $except_station->getId());
});

View File

@ -0,0 +1,24 @@
<?php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class StationPortChecker extends Constraint
{
public $message;
public function __construct($options = null)
{
$this->message = __('The port %s is in use by another station.', '{{ port }}');
parent::__construct($options);
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Validator\Constraints;
use App\Entity;
use App\Radio\Configuration;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
class StationPortCheckerValidator extends ConstraintValidator
{
/** @var Configuration */
protected $configuration;
/**
* StationPortCheckerValidator constructor.
* @param Configuration $configuration
*/
public function __construct(Configuration $configuration)
{
$this->configuration = $configuration;
}
public function validate($station, Constraint $constraint)
{
if (!$constraint instanceof StationPortChecker) {
throw new UnexpectedTypeException($constraint, StationPortChecker::class);
}
if (!$station instanceof Entity\Station) {
throw new UnexpectedTypeException($station, Entity\Station::class);
}
$frontend_config = (array)$station->getFrontendConfig();
$backend_config = (array)$station->getBackendConfig();
$ports_to_check = [
'frontend_config_port' => $frontend_config['port'] ?? '',
'backend_config_dj_port' => $backend_config['dj_port'] ?? '',
'backend_config_telnet_port' => $backend_config['telnet_port'] ?? '',
];
$used_ports = $this->configuration->getUsedPorts($station);
foreach($ports_to_check as $port_path => $value) {
if (null === $value || '' === $value) {
continue;
}
$port = (int)$value;
if (isset($used_ports[$port])) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ port }}', $port)
->addViolation();
}
}
}
}

View File

@ -8,5 +8,12 @@ use Symfony\Component\Validator\Constraint;
*/
class StreamerPassword extends Constraint
{
public $message = 'Password cannot contain the following characters: {{ chars }}';
public $message;
public function __construct($options = null)
{
$this->message = __('Password cannot contain the following characters: %s', '{{ chars }}');
parent::__construct($options);
}
}

View File

@ -3,6 +3,10 @@ $(function() {
if ($('.tab-content').length > 0) {
$('.tab-content').prependTo('form.form');
if ($('form .alert').length > 0) {
$('form .alert').appendTo('.card-header:first');
}
$('form fieldset#profile').appendTo('#tab-profile');
$('form fieldset#select_frontend_type').appendTo('#tab-frontend');

View File

@ -86,19 +86,18 @@ abstract class CestAbstract
$this->di[\App\Acl::class]->reload();
// Create station.
$station_info = [
'id' => 25,
'name' => 'Functional Test Radio',
'description' => 'Test radio station.',
'frontend_type' => \App\Radio\Adapters::DEFAULT_FRONTEND,
'backend_type' => \App\Radio\Adapters::DEFAULT_BACKEND,
];
/** @var Entity\Repository\StationRepository $station_repo */
$station_repo = $this->em->getRepository(Entity\Station::class);
$this->test_station = $station_repo->create($station_info);
$test_station = new Entity\Station();
$test_station->setName('Functional Test Radio');
$test_station->setDescription('Test radio station.');
$test_station->setFrontendType(\App\Radio\Adapters::DEFAULT_FRONTEND);
$test_station->setBackendType(\App\Radio\Adapters::DEFAULT_BACKEND);
$station_repo->create($test_station);
$this->test_station = $test_station;
// Set settings.