Revert "Optimize imports and enforce PSR-1 and PSR-2 formatting standards."

This reverts commit 7cdba9a90b.
This commit is contained in:
Buster Silver 2017-01-23 18:17:50 -06:00
parent 7cdba9a90b
commit 5164d5184b
101 changed files with 3438 additions and 3606 deletions

View file

@ -5,43 +5,39 @@
// Security settings
define("APP_IS_COMMAND_LINE", (PHP_SAPI == "cli"));
define("APP_IS_SECURE",
(!APP_IS_COMMAND_LINE && (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == "on")) ? true : false);
define("APP_IS_SECURE", (!APP_IS_COMMAND_LINE && (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == "on")) ? TRUE : FALSE);
if (!defined('APP_TESTING_MODE')) {
if (!defined('APP_TESTING_MODE'))
define('APP_TESTING_MODE', false);
}
// General includes
define("APP_INCLUDE_BASE", dirname(__FILE__));
define("APP_INCLUDE_ROOT", realpath(APP_INCLUDE_BASE . '/..'));
define("APP_INCLUDE_WEB", APP_INCLUDE_ROOT . '/web');
define("APP_INCLUDE_STATIC", APP_INCLUDE_WEB . '/static');
define("APP_INCLUDE_ROOT", realpath(APP_INCLUDE_BASE.'/..'));
define("APP_INCLUDE_WEB", APP_INCLUDE_ROOT.'/web');
define("APP_INCLUDE_STATIC", APP_INCLUDE_WEB.'/static');
define("APP_INCLUDE_VENDOR", APP_INCLUDE_ROOT . '/vendor');
define("APP_INCLUDE_VENDOR", APP_INCLUDE_ROOT.'/vendor');
define("APP_INCLUDE_LIB", APP_INCLUDE_ROOT . '/src');
define("APP_INCLUDE_LIB", APP_INCLUDE_ROOT.'/src');
define("APP_INCLUDE_MODELS", APP_INCLUDE_LIB);
define("APP_INCLUDE_TEMP", APP_INCLUDE_ROOT . '/../www_tmp');
define("APP_INCLUDE_CACHE", APP_INCLUDE_TEMP . '/cache');
define("APP_INCLUDE_TEMP", APP_INCLUDE_ROOT.'/../www_tmp');
define("APP_INCLUDE_CACHE", APP_INCLUDE_TEMP.'/cache');
define("APP_UPLOAD_FOLDER", APP_INCLUDE_STATIC);
// Application environment.
if (isset($_SERVER['APP_APPLICATION_ENV'])) {
if (isset($_SERVER['APP_APPLICATION_ENV']))
define('APP_APPLICATION_ENV', $_SERVER['APP_APPLICATION_ENV']);
} elseif (file_exists(APP_INCLUDE_BASE . '/.env')) {
define('APP_APPLICATION_ENV', ($env = @file_get_contents(APP_INCLUDE_BASE . '/.env')) ? trim($env) : 'development');
} elseif (isset($_SERVER['X-App-Dev-Environment']) && $_SERVER['X-App-Dev-Environment']) {
elseif (file_exists(APP_INCLUDE_BASE.'/.env'))
define('APP_APPLICATION_ENV', ($env = @file_get_contents(APP_INCLUDE_BASE.'/.env')) ? trim($env) : 'development');
elseif (isset($_SERVER['X-App-Dev-Environment']) && $_SERVER['X-App-Dev-Environment'])
define('APP_APPLICATION_ENV', 'development');
} else {
else
define('APP_APPLICATION_ENV', 'development');
}
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']))
$_SERVER['HTTPS'] = (strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https');
}
// Composer autoload.
$autoloader = require(APP_INCLUDE_VENDOR . '/autoload.php');
@ -53,49 +49,45 @@ $app_settings = [
'addContentLengthHeader' => false,
];
if (APP_APPLICATION_ENV !== 'development') {
$app_settings['routerCacheFile'] = APP_INCLUDE_TEMP . '/app_routes.cache.php';
}
if (APP_APPLICATION_ENV !== 'development')
$app_settings['routerCacheFile'] = APP_INCLUDE_TEMP.'/app_routes.cache.php';
$di = new \Slim\Container(['settings' => $app_settings]);
// Save configuration object.
$config = new \App\Config(APP_INCLUDE_BASE . '/config', $di);
$config = new \App\Config(APP_INCLUDE_BASE.'/config', $di);
// Add application autoloaders to Composer's autoloader handler.
$autoload_classes = $config->application->autoload->toArray();
foreach ($autoload_classes['psr0'] as $class_key => $class_dir) {
foreach($autoload_classes['psr0'] as $class_key => $class_dir)
$autoloader->add($class_key, $class_dir);
}
foreach ($autoload_classes['psr4'] as $class_key => $class_dir) {
foreach($autoload_classes['psr4'] as $class_key => $class_dir)
$autoloader->addPsr4($class_key, $class_dir);
}
// Set URL constants from configuration.
$app_cfg = $config->application;
if ($app_cfg->base_url) {
if ($app_cfg->base_url)
define('APP_BASE_URL', $app_cfg->base_url);
}
// Apply PHP settings.
$php_settings = $config->application->phpSettings->toArray();
foreach ($php_settings as $setting_key => $setting_value) {
foreach($php_settings as $setting_key => $setting_value)
{
if (is_array($setting_value)) {
foreach ($setting_value as $setting_subkey => $setting_subval) {
ini_set($setting_key . '.' . $setting_subkey, $setting_subval);
}
foreach($setting_value as $setting_subkey => $setting_subval)
ini_set($setting_key.'.'.$setting_subkey, $setting_subval);
} else {
ini_set($setting_key, $setting_value);
}
}
// Override Slim handlers.
$di['callableResolver'] = function ($di) {
$di['callableResolver'] = function($di) {
return new \App\Mvc\Resolver($di);
};
$di['errorHandler'] = function ($di) {
$di['errorHandler'] = function($di) {
return function ($request, $response, $exception) use ($di) {
return \App\Mvc\ErrorHandler::handle($di, $request, $response, $exception);
};
@ -117,37 +109,41 @@ $di['notFoundHandler'] = function ($di) {
$di['config'] = $config;
// Database
$di['em'] = function ($di) {
try {
$di['em'] = function($di) {
try
{
$config = $di['config'];
$db_conf = $config->application->doctrine->toArray();
$db_conf['conn'] = $config->db->toArray();
return \App\Doctrine\EntityManagerFactory::create($di, $db_conf);
} catch (\Exception $e) {
}
catch(\Exception $e)
{
throw new \App\Exception\Bootstrap($e->getMessage());
}
};
$di['db'] = function ($di) {
$di['db'] = function($di) {
return $di['em']->getConnection();
};
// Auth and ACL
$di['auth'] = function ($di) {
$di['auth'] = function($di) {
return new \App\Auth($di['session'], $di['em']->getRepository('Entity\User'));
};
$di['acl'] = function ($di) {
$di['acl'] = function($di) {
return new \AzuraCast\Acl\StationAcl($di['em'], $di['auth']);
};
// Caching
$di['cache_driver'] = function ($di) {
$di['cache_driver'] = function($di) {
$config = $di['config'];
$cache_config = $config->cache->toArray();
switch ($cache_config['cache']) {
switch($cache_config['cache'])
{
case 'redis':
$cache_driver = new \Stash\Driver\Redis($cache_config['redis']);
break;
@ -168,7 +164,8 @@ $di['cache_driver'] = function ($di) {
}
// Register Stash as session handler if necessary.
if (!($cache_driver instanceof \Stash\Driver\Ephemeral)) {
if (!($cache_driver instanceof \Stash\Driver\Ephemeral))
{
$pool = new \Stash\Pool($cache_driver);
$pool->setNamespace(\App\Cache::getSitePrefix('session'));
@ -179,17 +176,17 @@ $di['cache_driver'] = function ($di) {
return $cache_driver;
};
$di['cache'] = function ($di) {
$di['cache'] = function($di) {
return new \App\Cache($di['cache_driver'], 'user');
};
// Register URL handler.
$di['url'] = function ($di) {
$di['url'] = function($di) {
return new \App\Url($di);
};
// Register session service.
$di['session'] = function ($di) {
$di['session'] = function($di) {
// Depends on cache driver.
$di->get('cache_driver');
@ -197,17 +194,17 @@ $di['session'] = function ($di) {
};
// Register CSRF prevention security token service.
$di['csrf'] = function ($di) {
$di['csrf'] = function($di) {
return new \App\Csrf($di['session']);
};
// Register Flash notification service.
$di['flash'] = function ($di) {
$di['flash'] = function($di) {
return new \App\Flash($di['session']);
};
// InfluxDB
$di['influx'] = function ($di) {
$di['influx'] = function($di) {
$config = $di['config'];
$opts = $config->influx->toArray();
@ -216,12 +213,12 @@ $di['influx'] = function ($di) {
};
// E-mail Messenger
$di['messenger'] = function ($di) {
$di['messenger'] = function($di) {
return new \App\Messenger($di);
};
// Supervisord Interaction
$di['supervisor'] = function ($di) {
$di['supervisor'] = function($di) {
$guzzle_client = new \GuzzleHttp\Client();
$client = new \fXmlRpc\Client(
'http://127.0.0.1:9001/RPC2',
@ -237,37 +234,36 @@ $di['supervisor'] = function ($di) {
};
// Scheduled synchronization manager
$di['sync'] = function ($di) {
$di['sync'] = function($di) {
return new \AzuraCast\Sync($di);
};
// Currently logged in user
$di['user'] = $di->factory(function ($di) {
$di['user'] = $di->factory(function($di) {
$auth = $di['auth'];
if ($auth->isLoggedIn()) {
if ($auth->isLoggedIn())
return $auth->getLoggedInUser();
} else {
return null;
}
else
return NULL;
});
$di['customization'] = $di->factory(function ($di) {
$di['customization'] = $di->factory(function($di) {
return new \AzuraCast\Customization($di);
});
$di['view'] = $di->factory(function ($di) {
$view = new \App\Mvc\View(APP_INCLUDE_BASE . '/templates');
$di['view'] = $di->factory(function($di) {
$view = new \App\Mvc\View(APP_INCLUDE_BASE.'/templates');
$view->setFileExtension('phtml');
$view->addAppCommands($di);
$view->addData([
'di' => $di,
'auth' => $di['auth'],
'acl' => $di['acl'],
'url' => $di['url'],
'config' => $di['config'],
'flash' => $di['flash'],
'di' => $di,
'auth' => $di['auth'],
'acl' => $di['acl'],
'url' => $di['url'],
'config' => $di['config'],
'flash' => $di['flash'],
'customization' => $di['customization'],
]);
@ -277,7 +273,8 @@ $di['view'] = $di->factory(function ($di) {
// Initialize cache.
$cache = $di->get('cache');
if (!APP_IS_COMMAND_LINE || APP_TESTING_MODE) {
if (!APP_IS_COMMAND_LINE || APP_TESTING_MODE)
{
/** @var \AzuraCast\Customization $customization */
$customization = $di->get('customization');
@ -286,30 +283,28 @@ if (!APP_IS_COMMAND_LINE || APP_TESTING_MODE) {
// Localization
$locale = $customization->getLocale();
putenv("LANG=" . $locale);
putenv("LANG=".$locale);
setlocale(LC_ALL, $locale);
$locale_domain = 'default';
bindtextdomain($locale_domain, APP_INCLUDE_BASE . '/locale');
bindtextdomain($locale_domain, APP_INCLUDE_BASE.'/locale');
bind_textdomain_codeset($locale_domain, 'UTF-8');
textdomain($locale_domain);
}
// Set up application and routing.
$di['app'] = function ($di) {
$di['app'] = function($di) {
$app = new \Slim\App($di);
// Remove trailing slash from all URLs when routing.
$app->add(function (
\Psr\Http\Message\RequestInterface $request,
\Psr\Http\Message\ResponseInterface $response,
callable $next
) {
$app->add(function (\Psr\Http\Message\RequestInterface $request, \Psr\Http\Message\ResponseInterface $response, callable $next)
{
$uri = $request->getUri();
$path = $uri->getPath();
if ($path != '/' && substr($path, -1) == '/') {
if ($path != '/' && substr($path, -1) == '/')
{
// permanently redirect paths with a trailing slash
// to their non-trailing counterpart
$uri = $uri->withPath(substr($path, 0, -1));
@ -319,7 +314,7 @@ $di['app'] = function ($di) {
return $next($request, $response);
});
include(dirname(__FILE__) . '/bootstrap/routes.php');
include(dirname(__FILE__).'/bootstrap/routes.php');
return $app;
};

View file

@ -5,7 +5,7 @@
* i.e. frontend:index:index -> \Modules\Frontend\Controllers\IndexController::indexAction
*/
$app->group('/admin', function () {
$app->group('/admin', function() {
$this->get('', 'admin:index:index')
->setName('admin:index:index');
@ -13,7 +13,7 @@ $app->group('/admin', function () {
$this->get('/sync/{type}', 'admin:index:sync')
->setName('admin:index:sync');
$this->group('/api', function () {
$this->group('/api', function() {
$this->get('', 'admin:api:index')
->setName('admin:api:index');
@ -26,7 +26,7 @@ $app->group('/admin', function () {
});
$this->group('/permissions', function () {
$this->group('/permissions', function() {
$this->get('', 'admin:permissions:index')
->setName('admin:permissions:index');
@ -45,7 +45,7 @@ $app->group('/admin', function () {
$this->map(['GET', 'POST'], '/settings', 'admin:settings:index')
->setName('admin:settings:index');
$this->group('/stations', function () {
$this->group('/stations', function() {
$this->get('', 'admin:stations:index')
->setName('admin:stations:index');
@ -58,7 +58,7 @@ $app->group('/admin', function () {
});
$this->group('/users', function () {
$this->group('/users', function() {
$this->get('', 'admin:users:index')
->setName('admin:users:index');
@ -76,7 +76,7 @@ $app->group('/admin', function () {
});
$app->group('/api', function () {
$app->group('/api', function() {
$this->map(['GET', 'POST'], '', 'api:index:index')
->setName('api:index:index');
@ -87,21 +87,21 @@ $app->group('/api', function () {
$this->map(['GET', 'POST'], '/time', 'api:index:time')
->setName('api:index:time');
$this->group('/internal', function () {
$this->group('/internal', function() {
$this->map(['GET', 'POST'], '/streamauth/{id}', 'api:internal:streamauth')
->setName('api:internal:streamauth');
});
$this->group('/nowplaying[/{id}]', function () {
$this->group('/nowplaying[/{id}]', function() {
$this->map(['GET', 'POST'], '', 'api:nowplaying:index')
->setName('api:nowplaying:index');
});
$this->group('/requests/{station}', function () {
$this->group('/requests/{station}', function() {
$this->map(['GET', 'POST'], '/list', 'api:requests:list')
->setName('api:requests:list');
@ -111,7 +111,7 @@ $app->group('/api', function () {
});
$this->group('/stations', function () {
$this->group('/stations', function() {
$this->map(['GET', 'POST'], '', 'api:stations:list')
->setName('api:stations:list');
@ -147,7 +147,7 @@ $app->map(['GET', 'POST'], '/profile/edit', 'frontend:profile:edit')
$app->map(['GET', 'POST'], '/profile/timezone', 'frontend:profile:timezone')
->setName('profile:timezone');
$app->group('/setup', function () {
$app->group('/setup', function() {
$this->map(['GET', 'POST'], '', 'frontend:setup:index')
->setName('setup:index');
@ -166,7 +166,7 @@ $app->group('/setup', function () {
});
$app->group('/public', function () {
$app->group('/public', function() {
$this->get('[/{station}]', 'frontend:public:index')
->setName('public:index');
@ -176,12 +176,12 @@ $app->group('/public', function () {
$app->get('/test', 'frontend:util:test')
->setName('util:test');
$app->group('/station/{station}', function () {
$app->group('/station/{station}', function() {
$this->get('', 'stations:index:index')
->setName('stations:index:index');
$this->group('/automation', function () {
$this->group('/automation', function() {
$this->map(['GET', 'POST'], '', 'stations:automation:index')
->setName('stations:automation:index');
@ -191,7 +191,7 @@ $app->group('/station/{station}', function () {
});
$this->group('/files', function () {
$this->group('/files', function() {
$this->get('', 'stations:files:index')
->setName('stations:files:index');
@ -216,7 +216,7 @@ $app->group('/station/{station}', function () {
});
$this->group('/playlists', function () {
$this->group('/playlists', function() {
$this->get('', 'stations:playlists:index')
->setName('stations:playlists:index');
@ -229,7 +229,7 @@ $app->group('/station/{station}', function () {
});
$this->group('/mounts', function () {
$this->group('/mounts', function() {
$this->get('', 'stations:mounts:index')
->setName('stations:mounts:index');
@ -242,7 +242,7 @@ $app->group('/station/{station}', function () {
});
$this->group('/profile', function () {
$this->group('/profile', function() {
$this->get('', 'stations:profile:index')
->setName('stations:profile:index');
@ -258,7 +258,7 @@ $app->group('/station/{station}', function () {
});
$this->group('/reports', function () {
$this->group('/reports', function() {
$this->get('/timeline[/format/{format}]', 'stations:index:timeline')
->setName('stations:index:timeline');
@ -274,7 +274,7 @@ $app->group('/station/{station}', function () {
});
$this->group('/streamers', function () {
$this->group('/streamers', function() {
$this->get('', 'stations:streamers:index')
->setName('stations:streamers:index');
@ -287,7 +287,7 @@ $app->group('/station/{station}', function () {
});
$this->group('/util', function () {
$this->group('/util', function() {
$this->get('/playlist[/{format}]', 'stations:util:playlist')
->setName('stations:util:playlist');

View file

@ -75,7 +75,8 @@ $config = [
* Development mode changes.
*/
if (APP_APPLICATION_ENV != 'production') {
if (APP_APPLICATION_ENV != 'production')
{
$config['phpSettings']['display_startup_errors'] = 1;
$config['phpSettings']['display_errors'] = 1;

View file

@ -3,7 +3,7 @@
* Backend cache configuration.
*/
return [
return array(
// Valid options are:
// ephemeral - Uses in-memory cache that expires at page request.
// memcached - Uses libmemcached and 'memcached' settings below.
@ -12,33 +12,33 @@ return [
'cache' => 'file',
// Flatfile configuration
'file' => [
'path' => APP_INCLUDE_CACHE . DIRECTORY_SEPARATOR,
],
'file' => array(
'path' => APP_INCLUDE_CACHE.DIRECTORY_SEPARATOR,
),
// Redis configuration
'redis' => [
'servers' => [
[
'server' => 'localhost',
'port' => 6379, // default: 6379
],
],
'redis' => array(
'servers' => array(
array(
'server' => 'localhost',
'port' => 6379, // default: 6379
),
),
// 'password' => '', // Must be commented out to have no authentication
'database' => 0,
],
'database' => 0,
),
// Memcached configuration
'memcached' => [
'servers' => [
[
'server' => 'localhost',
'port' => 11211, // default: 11211
'weight' => 1,
],
],
'memcached' => array(
'servers' => array(
array(
'server' => 'localhost',
'port' => 11211, // default: 11211
'weight' => 1,
),
),
'extension' => 'memcached', // Use libmemcached instead of memcache
],
),
];
);

View file

@ -3,27 +3,27 @@
* Database configuration and credentials.
*/
return [
return array(
// Backend driver to use with the database.
'driver' => 'pdo_mysql',
'driver' => 'pdo_mysql',
// Host or IP to connect to (default: localhost).
'host' => 'localhost',
'host' => 'localhost',
// Name of the primary application database.
'dbname' => 'azuracast',
'dbname' => 'azuracast',
// Username for the database user with read/write access to the above database.
'user' => 'root',
'user' => 'root',
// Password for the user account specified above.
'password' => 'password',
'password' => 'password',
// Character set.
'charset' => 'utf8',
'charset' => 'utf8',
// Other options to send to the PDO adapter for the database.
'driverOptions' => [
1002 => 'SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci',
],
];
'driverOptions' => array(
1002 => 'SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci',
),
);

View file

@ -4,23 +4,17 @@ return [
'method' => 'post',
'elements' => [
'owner' => [
'text',
[
'label' => _('API Key Owner'),
'class' => 'half-width',
'required' => true,
]
],
'owner' => ['text', [
'label' => _('API Key Owner'),
'class' => 'half-width',
'required' => true,
]],
'submit' => [
'submit',
[
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]
],
'submit' => ['submit', [
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]],
],
];

View file

@ -4,44 +4,35 @@ return [
'method' => 'post',
'elements' => [
'is_enabled' => [
'radio',
[
'label' => _('Enable Automated Assignment'),
'description' => _('Allow the system to periodically automatically assign songs to playlists based on their performance. This process will run in the background, and will only run if this option is set to "Enabled" and at least one playlist is set to "Include in Automated Assignment".'),
'default' => '0',
'options' => [
0 => 'Disabled',
1 => 'Enabled',
],
]
],
'is_enabled' => ['radio', [
'label' => _('Enable Automated Assignment'),
'description' => _('Allow the system to periodically automatically assign songs to playlists based on their performance. This process will run in the background, and will only run if this option is set to "Enabled" and at least one playlist is set to "Include in Automated Assignment".'),
'default' => '0',
'options' => [
0 => 'Disabled',
1 => 'Enabled',
],
]],
'threshold_days' => [
'radio',
[
'label' => _('Days Between Automated Assignments'),
'description' => _('Based on this setting, the system will automatically reassign songs every (this) days using data from the previous (this) days.'),
'class' => 'inline',
'default' => \AzuraCast\Sync\RadioAutomation::DEFAULT_THRESHOLD_DAYS,
'options' => [
7 => '7 days',
14 => '14 days',
30 => '30 days',
60 => '60 days',
],
]
],
'threshold_days' => ['radio', [
'label' => _('Days Between Automated Assignments'),
'description' => _('Based on this setting, the system will automatically reassign songs every (this) days using data from the previous (this) days.'),
'class' => 'inline',
'default' => \AzuraCast\Sync\RadioAutomation::DEFAULT_THRESHOLD_DAYS,
'options' => [
7 => '7 days',
14 => '14 days',
30 => '30 days',
60 => '60 days',
],
]],
'submit' => [
'submit',
[
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]
],
'submit' => ['submit', [
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]],
],
];

View file

@ -7,33 +7,24 @@ return [
'method' => 'post',
'elements' => [
'username' => [
'text',
[
'label' => _('E-mail Address'),
'class' => 'half-width',
'spellcheck' => 'false',
'required' => true,
]
],
'username' => ['text', [
'label' => _('E-mail Address'),
'class' => 'half-width',
'spellcheck' => 'false',
'required' => true,
]],
'password' => [
'password',
[
'label' => _('Password'),
'class' => 'half-width',
'required' => true,
]
],
'password' => ['password', [
'label' => _('Password'),
'class' => 'half-width',
'required' => true,
]],
'submit' => [
'submit',
[
'type' => 'submit',
'label' => _('Log in'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]
],
'submit' => ['submit', [
'type' => 'submit',
'label' => _('Log in'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]],
],
];

View file

@ -3,36 +3,24 @@ return [
'method' => 'post',
'elements' => [
'title' => [
'text',
[
'label' => _('Song Title'),
]
],
'title' => ['text', [
'label' => _('Song Title'),
]],
'artist' => [
'text',
[
'label' => _('Song Artist'),
]
],
'artist' => ['text', [
'label' => _('Song Artist'),
]],
'album' => [
'text',
[
'label' => _('Song Album'),
]
],
'album' => ['text', [
'label' => _('Song Album'),
]],
'submit' => [
'submit',
[
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'ui-button btn-lg btn-primary',
]
],
'submit' => ['submit', [
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'ui-button btn-lg btn-primary',
]],
],
];

View file

@ -8,88 +8,64 @@ return [
'basic_info' => [
'elements' => [
'name' => [
'text',
[
'label' => _('Mount Point Name/URL'),
'description' => _('This name should always begin with a slash (/), and must be a valid URL, such as /autodj.mp3'),
'required' => true,
]
],
'name' => ['text', [
'label' => _('Mount Point Name/URL'),
'description' => _('This name should always begin with a slash (/), and must be a valid URL, such as /autodj.mp3'),
'required' => true,
]],
'is_default' => [
'radio',
[
'label' => _('Is Default Mount'),
'description' => _('If this mount is the default, it will be played on the radio preview and the public radio page in this system.'),
'options' => [0 => _('No'), 1 => _('Yes')],
'default' => 0,
]
],
'is_default' => ['radio', [
'label' => _('Is Default Mount'),
'description' => _('If this mount is the default, it will be played on the radio preview and the public radio page in this system.'),
'options' => [0 => _('No'), 1 => _('Yes')],
'default' => 0,
]],
'fallback_mount' => [
'text',
[
'label' => _('Fallback Mount'),
'description' => _('If this mount point is not playing audio, listeners will automatically be redirected to this mount point. The default is /error.mp3, a repeating error message.'),
'default' => '/error.mp3',
]
],
'fallback_mount' => ['text', [
'label' => _('Fallback Mount'),
'description' => _('If this mount point is not playing audio, listeners will automatically be redirected to this mount point. The default is /error.mp3, a repeating error message.'),
'default' => '/error.mp3',
]],
'enable_streamers' => [
'radio',
[
'label' => _('Enable Streamers'),
'description' => _('If set to "Yes", streamers will be able to broadcast to this mount point.'),
'options' => [0 => _('No'), 1 => _('Yes')],
'default' => 0,
]
],
'enable_streamers' => ['radio', [
'label' => _('Enable Streamers'),
'description' => _('If set to "Yes", streamers will be able to broadcast to this mount point.'),
'options' => [0 => _('No'), 1 => _('Yes')],
'default' => 0,
]],
'enable_autodj' => [
'radio',
[
'label' => _('Enable AutoDJ'),
'description' => _('If set to "Yes", the AutoDJ will automatically play music to this mount point.'),
'options' => [0 => _('No'), 1 => _('Yes')],
'default' => 1,
]
],
'enable_autodj' => ['radio', [
'label' => _('Enable AutoDJ'),
'description' => _('If set to "Yes", the AutoDJ will automatically play music to this mount point.'),
'options' => [0 => _('No'), 1 => _('Yes')],
'default' => 1,
]],
'autodj_format' => [
'radio',
[
'label' => _('AutoDJ Format'),
'options' => [
'mp3' => 'MP3',
'ogg' => 'OGG Vorbis',
],
'default' => 'mp3',
]
],
'autodj_format' => ['radio', [
'label' => _('AutoDJ Format'),
'options' => [
'mp3' => 'MP3',
'ogg' => 'OGG Vorbis',
],
'default' => 'mp3',
]],
'autodj_bitrate' => [
'radio',
[
'label' => _('AutoDJ Bitrate (kbps)'),
'options' => [
64 => '64',
96 => '96',
128 => '128',
192 => '192',
256 => '256',
],
'default' => 128,
]
],
'autodj_bitrate' => ['radio', [
'label' => _('AutoDJ Bitrate (kbps)'),
'options' => [
64 => '64',
96 => '96',
128 => '128',
192 => '192',
256 => '256',
],
'default' => 128,
]],
'frontend_config' => [
'textarea',
[
'label' => _('Advanced Frontend Configuration'),
'description' => _('You can include any special mount point settings here, in either JSON { key: \'value\' } format or XML <key>value</key>'),
]
],
'frontend_config' => ['textarea', [
'label' => _('Advanced Frontend Configuration'),
'description' => _('You can include any special mount point settings here, in either JSON { key: \'value\' } format or XML <key>value</key>'),
]],
],
],
@ -97,15 +73,12 @@ return [
'grp_submit' => [
'elements' => [
'submit' => [
'submit',
[
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'ui-button btn-lg btn-primary',
]
],
'submit' => ['submit', [
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'ui-button btn-lg btn-primary',
]],
],
],

View file

@ -1,8 +1,10 @@
<?php
$hour_select = [];
for ($hr = 0; $hr <= 23; $hr++) {
foreach ([0, 15, 30, 45] as $min) {
$time_num = $hr * 100 + $min;
for($hr = 0; $hr <= 23; $hr++)
{
foreach([0, 15, 30, 45] as $min)
{
$time_num = $hr*100 + $min;
$hour_select[$time_num] = \Entity\StationPlaylist::formatTimeCode($time_num);
}
}
@ -16,43 +18,34 @@ return [
'basic_info' => [
'elements' => [
'name' => [
'text',
[
'label' => _('Playlist Name'),
'required' => true,
]
],
'name' => ['text', [
'label' => _('Playlist Name'),
'required' => true,
]],
'is_enabled' => [
'radio',
[
'label' => _('Enable Playlist'),
'required' => true,
'description' => _('If set to "No", the playlist will not be included in radio playback, but can still be managed.'),
'options' => [
1 => 'Yes',
0 => 'No',
],
'default' => 1,
]
],
'is_enabled' => ['radio', [
'label' => _('Enable Playlist'),
'required' => true,
'description' => _('If set to "No", the playlist will not be included in radio playback, but can still be managed.'),
'options' => [
1 => 'Yes',
0 => 'No',
],
'default' => 1,
]],
'type' => [
'radio',
[
'label' => _('Playlist Type'),
'options' => [
'default' => '<b>' . _('Standard Playlist') . ':</b> ' . _('Plays all day, shuffles with other standard playlists based on weight.'),
'scheduled' => '<b>' . _('Scheduled Playlist') . ':</b> ' . _('Play during a scheduled time range. Useful for mood-based time playlists.'),
'once_per_x_songs' => '<b>' . _('Once per x Songs Playlist') . ':</b> ' . _('Play exactly once every <i>x</i> songs. Useful for station ID/jingles.'),
'once_per_x_minutes' => '<b>' . _('Once Per x Minutes Playlist') . ':</b> ' . _('Play exactly once every <i>x</i> minutes. Useful for station ID/jingles.'),
'once_per_day' => '<b>' . _('Daily Playlist') . '</b>: ' . _('Play once per day at the specified time. Useful for timely reminders.'),
],
'default' => 'default',
'required' => true,
]
],
'type' => ['radio', [
'label' => _('Playlist Type'),
'options' => [
'default' => '<b>'._('Standard Playlist').':</b> '._('Plays all day, shuffles with other standard playlists based on weight.'),
'scheduled' => '<b>'._('Scheduled Playlist').':</b> '._('Play during a scheduled time range. Useful for mood-based time playlists.'),
'once_per_x_songs' => '<b>'._('Once per x Songs Playlist').':</b> '._('Play exactly once every <i>x</i> songs. Useful for station ID/jingles.'),
'once_per_x_minutes' => '<b>'._('Once Per x Minutes Playlist').':</b> '._('Play exactly once every <i>x</i> minutes. Useful for station ID/jingles.'),
'once_per_day' => '<b>'._('Daily Playlist').'</b>: '._('Play once per day at the specified time. Useful for timely reminders.'),
],
'default' => 'default',
'required' => true,
]],
],
],
@ -62,37 +55,31 @@ return [
'class' => 'type_fieldset',
'elements' => [
'weight' => [
'radio',
[
'label' => _('Playlist Weight'),
'description' => _('How often the playlist\'s songs will be played. 1 is the most infrequent, 5 is the most frequent.'),
'default' => 3,
'required' => true,
'class' => 'inline',
'options' => [
1 => '1 - Lowest',
2 => '2',
3 => '3 - Default',
4 => '4',
5 => '5 - Highest',
],
]
],
'weight' => ['radio', [
'label' => _('Playlist Weight'),
'description' => _('How often the playlist\'s songs will be played. 1 is the most infrequent, 5 is the most frequent.'),
'default' => 3,
'required' => true,
'class' => 'inline',
'options' => [
1 => '1 - Lowest',
2 => '2',
3 => '3 - Default',
4 => '4',
5 => '5 - Highest',
],
]],
'include_in_automation' => [
'radio',
[
'label' => _('Include in Automated Assignment'),
'description' => _('If auto-assignment is enabled, use this playlist as one of the targets for songs to be redistributed into. This will overwrite the existing contents of this playlist.'),
'required' => true,
'default' => '0',
'options' => [
0 => 'No',
1 => 'Yes',
],
]
],
'include_in_automation' => ['radio', [
'label' => _('Include in Automated Assignment'),
'description' => _('If auto-assignment is enabled, use this playlist as one of the targets for songs to be redistributed into. This will overwrite the existing contents of this playlist.'),
'required' => true,
'default' => '0',
'options' => [
0 => 'No',
1 => 'Yes',
],
]],
],
],
@ -102,23 +89,17 @@ return [
'class' => 'type_fieldset',
'elements' => [
'schedule_start_time' => [
'select',
[
'label' => _('Start Time'),
'description' => sprintf(_('Current server time is <b>%s</b>.'), date('g:ia')),
'options' => $hour_select,
]
],
'schedule_start_time' => ['select', [
'label' => _('Start Time'),
'description' => sprintf(_('Current server time is <b>%s</b>.'), date('g:ia')),
'options' => $hour_select,
]],
'schedule_end_time' => [
'select',
[
'label' => _('End Time'),
'description' => _('If the end time is before the start time, the playlist will play overnight until this time on the next day.'),
'options' => $hour_select,
]
],
'schedule_end_time' => ['select', [
'label' => _('End Time'),
'description' => _('If the end time is before the start time, the playlist will play overnight until this time on the next day.'),
'options' => $hour_select,
]],
],
],
@ -128,22 +109,19 @@ return [
'class' => 'type_fieldset',
'elements' => [
'play_per_songs' => [
'radio',
[
'label' => _('Number of Songs Between Plays'),
'description' => _('This playlist will play every $x songs, where $x is specified below.'),
'options' => \App\Utilities::pairs([
5,
10,
15,
20,
25,
50,
100
]),
]
],
'play_per_songs' => ['radio', [
'label' => _('Number of Songs Between Plays'),
'description' => _('This playlist will play every $x songs, where $x is specified below.'),
'options' => \App\Utilities::pairs([
5,
10,
15,
20,
25,
50,
100
]),
]],
],
],
@ -153,23 +131,20 @@ return [
'class' => 'type_fieldset',
'elements' => [
'play_per_minutes' => [
'radio',
[
'label' => _('Number of Minutes Between Plays'),
'description' => _('This playlist will play every $x minutes, where $x is specified below.'),
'options' => \App\Utilities::pairs([
5,
10,
15,
30,
45,
60,
120,
240,
]),
]
],
'play_per_minutes' => ['radio', [
'label' => _('Number of Minutes Between Plays'),
'description' => _('This playlist will play every $x minutes, where $x is specified below.'),
'options' => \App\Utilities::pairs([
5,
10,
15,
30,
45,
60,
120,
240,
]),
]],
],
],
@ -179,14 +154,11 @@ return [
'class' => 'type_fieldset',
'elements' => [
'play_once_time' => [
'select',
[
'label' => _('Scheduled Play Time'),
'description' => sprintf(_('Current server time is <b>%s</b>.'), date('g:ia')),
'options' => $hour_select,
]
],
'play_once_time' => ['select', [
'label' => _('Scheduled Play Time'),
'description' => sprintf(_('Current server time is <b>%s</b>.'), date('g:ia')),
'options' => $hour_select,
]],
],
],
@ -194,15 +166,12 @@ return [
'grp_submit' => [
'elements' => [
'submit' => [
'submit',
[
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'ui-button btn-lg btn-primary',
]
],
'submit' => ['submit', [
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'ui-button btn-lg btn-primary',
]],
],
],

View file

@ -17,32 +17,23 @@ return [
'legend' => _('Account Information'),
'elements' => [
'name' => [
'text',
[
'label' => _('Name'),
'class' => 'half-width',
]
],
'name' => array('text', array(
'label' => _('Name'),
'class' => 'half-width',
)),
'email' => [
'text',
[
'label' => _('E-mail Address'),
'class' => 'half-width',
'required' => true,
'autocomplete' => 'off',
]
],
'email' => ['text', [
'label' => _('E-mail Address'),
'class' => 'half-width',
'required' => true,
'autocomplete' => 'off',
]],
'auth_password' => [
'password',
[
'label' => _('Reset Password'),
'description' => _('To change your password, enter the new password in the field below.'),
'autocomplete' => 'off',
]
],
'auth_password' => ['password', [
'label' => _('Reset Password'),
'description' => _('To change your password, enter the new password in the field below.'),
'autocomplete' => 'off',
]],
],
],
@ -51,49 +42,36 @@ return [
'legend' => _('Customization'),
'elements' => [
'timezone' => [
'select',
[
'label' => _('Time Zone'),
'description' => _('All times displayed on the site will be based on this time zone.') . '<br>' . sprintf(_('Current server time is <b>%s</b>.'),
date('g:ia')),
'options' => \App\Timezone::fetchSelect(),
'default' => date_default_timezone_get(),
]
],
'timezone' => ['select', [
'label' => _('Time Zone'),
'description' => _('All times displayed on the site will be based on this time zone.').'<br>'.sprintf(_('Current server time is <b>%s</b>.'), date('g:ia')),
'options' => \App\Timezone::fetchSelect(),
'default' => date_default_timezone_get(),
]],
'locale' => [
'radio',
[
'label' => _('Language'),
'options' => $locale_select,
'default' => 'default',
]
],
'locale' => ['radio', [
'label' => _('Language'),
'options' => $locale_select,
'default' => 'default',
]],
'theme' => [
'radio',
[
'label' => _('Site Theme'),
'options' => $config->application->themes->available->toArray(),
'default' => $config->application->themes->default,
]
],
'theme' => ['radio', [
'label' => _('Site Theme'),
'options' => $config->application->themes->available->toArray(),
'default' => $config->application->themes->default,
]],
],
],
'submit' => [
'elements' => [
'submit' => [
'submit',
[
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]
],
'submit' => ['submit', [
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]],
],
],

View file

@ -7,38 +7,29 @@ return [
'legend' => _('Account Information'),
'elements' => [
'username' => [
'text',
[
'label' => _('E-mail Address'),
'class' => 'half-width',
'required' => true,
'validators' => ['EmailAddress'],
]
],
'username' => ['text', [
'label' => _('E-mail Address'),
'class' => 'half-width',
'required' => true,
'validators' => ['EmailAddress'],
]],
'password' => [
'password',
[
'label' => _('Password'),
'required' => true,
]
],
'password' => ['password', [
'label' => _('Password'),
'required' => true,
]],
],
],
'submit' => [
'elements' => [
'submit' => [
'submit',
[
'type' => 'submit',
'label' => _('Create Account'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]
],
'submit' => ['submit', [
'type' => 'submit',
'label' => _('Create Account'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]],
],
],

View file

@ -18,14 +18,11 @@ $form_config = [
'basic_info' => [
'elements' => [
'name' => [
'text',
[
'label' => _('Role Name'),
'class' => 'half-width',
'required' => true,
]
],
'name' => ['text', [
'label' => _('Role Name'),
'class' => 'half-width',
'required' => true,
]],
],
],
@ -34,13 +31,10 @@ $form_config = [
'legend' => _('System-Wide Permissions'),
'elements' => [
'actions_global' => [
'multiSelect',
[
'label' => _('Actions'),
'multiOptions' => $actions['global'],
]
],
'actions_global' => ['multiSelect', [
'label' => _('Actions'),
'multiOptions' => $actions['global'],
]],
],
],
@ -48,18 +42,16 @@ $form_config = [
],
];
foreach ($all_stations as $station) {
$form_config['groups']['grp_station_' . $station['id']] = [
foreach($all_stations as $station)
{
$form_config['groups']['grp_station_'.$station['id']] = [
'legend' => $station['name'],
'elements' => [
'actions_' . $station['id'] => [
'multiSelect',
[
'label' => _('Actions'),
'multiOptions' => $actions['station'],
]
],
'actions_'.$station['id'] => ['multiSelect', [
'label' => _('Actions'),
'multiOptions' => $actions['station'],
]],
],
];
@ -67,15 +59,12 @@ foreach ($all_stations as $station) {
$form_config['groups']['grp_submit'] = [
'elements' => [
'submit' => [
'submit',
[
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]
],
'submit' => ['submit', [
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]],
],
];

View file

@ -19,27 +19,21 @@ return [
'legend' => _('System Settings'),
'elements' => [
'base_url' => [
'text',
[
'label' => _('Site Base URL'),
'description' => _('The base URL where this service is located. For local testing, use "localhost". Otherwise, use either the external IP address or fully-qualified domain name pointing to the server.'),
'default' => $base_url_default,
]
],
'base_url' => ['text', [
'label' => _('Site Base URL'),
'description' => _('The base URL where this service is located. For local testing, use "localhost". Otherwise, use either the external IP address or fully-qualified domain name pointing to the server.'),
'default' => $base_url_default,
]],
'use_radio_proxy' => [
'radio',
[
'label' => _('Use Web Proxy for Radio'),
'description' => _('By default, radio stations broadcast on their own ports (i.e. 8000). If you\'re using a service like CloudFlare or accessing your radio station by SSL, you should enable this feature, which routes all radio through the web ports (80 and 443).'),
'options' => [
0 => 'No',
1 => 'Yes',
],
'default' => 0,
]
],
'use_radio_proxy' => ['radio', [
'label' => _('Use Web Proxy for Radio'),
'description' => _('By default, radio stations broadcast on their own ports (i.e. 8000). If you\'re using a service like CloudFlare or accessing your radio station by SSL, you should enable this feature, which routes all radio through the web ports (80 and 443).'),
'options' => [
0 => 'No',
1 => 'Yes',
],
'default' => 0,
]],
],
],
@ -47,15 +41,12 @@ return [
'submit' => [
'legend' => '',
'elements' => [
'submit' => [
'submit',
[
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]
],
'submit' => ['submit', [
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]],
],
],

View file

@ -8,47 +8,35 @@ return [
'legend' => _('Metadata'),
'elements' => [
'text' => [
'text',
[
'label' => _('Full Text'),
'description' => 'Typically in the form of "Artist - Title". Should not be edited.',
'class' => 'half-width',
'disabled' => 'disabled',
]
],
'text' => ['text', [
'label' => _('Full Text'),
'description' => 'Typically in the form of "Artist - Title". Should not be edited.',
'class' => 'half-width',
'disabled' => 'disabled',
]],
'artist' => [
'text',
[
'label' => _('Artist Name'),
'class' => 'half-width',
'description' => 'For multiple artists, format should be "Artist 1, Artist 2"',
]
],
'artist' => ['text', [
'label' => _('Artist Name'),
'class' => 'half-width',
'description' => 'For multiple artists, format should be "Artist 1, Artist 2"',
]],
'title' => [
'text',
[
'label' => _('Song Title'),
'class' => 'half-width',
]
],
'title' => ['text', [
'label' => _('Song Title'),
'class' => 'half-width',
]],
],
],
'submit_grp' => [
'elements' => [
'submit' => [
'submit',
[
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]
],
'submit' => ['submit', [
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]],
],
],

View file

@ -1,16 +1,14 @@
<?php
$frontends = \Entity\Station::getFrontendAdapters();
$frontend_types = [];
foreach ($frontends['adapters'] as $adapter_nickname => $adapter_info) {
foreach ($frontends['adapters'] as $adapter_nickname => $adapter_info)
$frontend_types[$adapter_nickname] = $adapter_info['name'];
}
$frontend_default = $frontends['default'];
$backends = \Entity\Station::getBackendAdapters();
$backend_types = [];
foreach ($backends['adapters'] as $adapter_nickname => $adapter_info) {
foreach ($backends['adapters'] as $adapter_nickname => $adapter_info)
$backend_types[$adapter_nickname] = $adapter_info['name'];
}
$backend_default = $backends['default'];
return [
@ -23,97 +21,70 @@ return [
'legend' => _('Station Details'),
'elements' => [
'name' => [
'text',
[
'label' => _('Station Name'),
'class' => 'half-width',
'required' => true,
]
],
'name' => ['text', [
'label' => _('Station Name'),
'class' => 'half-width',
'required' => true,
]],
'description' => [
'textarea',
[
'label' => _('Station Description'),
'class' => 'full-width full-height',
]
],
'description' => ['textarea', [
'label' => _('Station Description'),
'class' => 'full-width full-height',
]],
'frontend_type' => [
'radio',
[
'label' => _('Station Frontend Type'),
'description' => _('The type of software you use to deliver your broadcast to the audience.'),
'options' => $frontend_types,
'default' => $frontend_default,
]
],
'frontend_type' => ['radio', [
'label' => _('Station Frontend Type'),
'description' => _('The type of software you use to deliver your broadcast to the audience.'),
'options' => $frontend_types,
'default' => $frontend_default,
]],
'backend_type' => [
'radio',
[
'label' => _('Station Backend Type'),
'description' => _('The type of software you use to manage the station\'s playlists and media.'),
'options' => $backend_types,
'default' => $backend_default,
]
],
'backend_type' => ['radio', [
'label' => _('Station Backend Type'),
'description' => _('The type of software you use to manage the station\'s playlists and media.'),
'options' => $backend_types,
'default' => $backend_default,
]],
],
],
'frontend_local' => [
'legend' => _('Configure Radio Broadcasting'),
'class' => 'frontend_fieldset',
'class' => 'frontend_fieldset',
'elements' => [
'enable_streamers' => [
'radio',
[
'label' => _('Allow Streamers / DJs'),
'description' => _('If this setting is turned on, streamers (or DJs) will be able to connect directly to your stream and broadcast live music that interrupts the AutoDJ stream.'),
'default' => '0',
'options' => [0 => 'No', 1 => 'Yes'],
]
],
'enable_streamers' => ['radio', [
'label' => _('Allow Streamers / DJs'),
'description' => _('If this setting is turned on, streamers (or DJs) will be able to connect directly to your stream and broadcast live music that interrupts the AutoDJ stream.'),
'default' => '0',
'options' => [0 => 'No', 1 => 'Yes'],
]],
'port' => [
'text',
[
'label' => _('Broadcasting Port'),
'description' => _('No other program can be using this port. Leave blank to automatically assign a port.'),
'belongsTo' => 'frontend_config',
]
],
'port' => ['text', [
'label' => _('Broadcasting Port'),
'description' => _('No other program can be using this port. Leave blank to automatically assign a port.'),
'belongsTo' => 'frontend_config',
]],
'source_pw' => [
'text',
[
'label' => _('Source Password'),
'description' => _('Leave blank to automatically generate a new password.'),
'belongsTo' => 'frontend_config',
]
],
'source_pw' => ['text', [
'label' => _('Source Password'),
'description' => _('Leave blank to automatically generate a new password.'),
'belongsTo' => 'frontend_config',
]],
'admin_pw' => [
'text',
[
'label' => _('Admin Password'),
'description' => _('Leave blank to automatically generate a new password.'),
'belongsTo' => 'frontend_config',
]
],
'admin_pw' => ['text', [
'label' => _('Admin Password'),
'description' => _('Leave blank to automatically generate a new password.'),
'belongsTo' => 'frontend_config',
]],
'custom_config' => [
'textarea',
[
'label' => _('Custom Configuration'),
'belongsTo' => 'frontend_config',
'description' => _('This code will be included in the frontend configuration. You can use either JSON {"new_key": "new_value"} format or XML &lt;new_key&gt;new_value&lt;/new_key&gt;.'),
]
],
'custom_config' => ['textarea', [
'label' => _('Custom Configuration'),
'belongsTo' => 'frontend_config',
'description' => _('This code will be included in the frontend configuration. You can use either JSON {"new_key": "new_value"} format or XML &lt;new_key&gt;new_value&lt;/new_key&gt;.'),
]],
],
],
@ -124,77 +95,59 @@ return [
'elements' => [
'remote_type' => [
'radio',
[
'label' => _('Radio Station Type'),
'belongsTo' => 'frontend_config',
'options' => [
'shoutcast1' => _('ShoutCast v1'),
'shoutcast2' => _('ShoutCast v2'),
'icecast' => _('IceCast v2.4+'),
],
]
],
'remote_type' => ['radio', [
'label' => _('Radio Station Type'),
'belongsTo' => 'frontend_config',
'options' => [
'shoutcast1' => _('ShoutCast v1'),
'shoutcast2' => _('ShoutCast v2'),
'icecast' => _('IceCast v2.4+'),
],
]],
'remote_url' => [
'text',
[
'label' => _('Radio Station Base URL'),
'belongsTo' => 'frontend_config',
]
],
'remote_url' => ['text', [
'label' => _('Radio Station Base URL'),
'belongsTo' => 'frontend_config',
]],
]
],
'backend_liquidsoap' => [
'legend' => _('Configure LiquidSoap'),
'class' => 'backend_fieldset',
'class' => 'backend_fieldset',
'elements' => [
'enable_requests' => [
'radio',
[
'label' => _('Allow Song Requests'),
'description' => _('Setting this enables listeners to request a song for play on your station. Only songs that are already in your playlists are listed as requestable.'),
'default' => '0',
'options' => [0 => 'No', 1 => 'Yes'],
]
],
'enable_requests' => ['radio', [
'label' => _('Allow Song Requests'),
'description' => _('Setting this enables listeners to request a song for play on your station. Only songs that are already in your playlists are listed as requestable.'),
'default' => '0',
'options' => [0 => 'No', 1 => 'Yes'],
]],
'request_delay' => [
'text',
[
'label' => _('Request Minimum Delay (Minutes)'),
'description' => _('If requests are enabled, this specifies the minimum delay (in minutes) between a request being submitted and being played. If set to zero, no delay is applied.<br><b>Important:</b> Some stream licensing rules require a minimum delay for requests (in the US, this is currently 60 minutes). Check your local regulations for more information.'),
'default' => '5',
]
],
'request_delay' => ['text', [
'label' => _('Request Minimum Delay (Minutes)'),
'description' => _('If requests are enabled, this specifies the minimum delay (in minutes) between a request being submitted and being played. If set to zero, no delay is applied.<br><b>Important:</b> Some stream licensing rules require a minimum delay for requests (in the US, this is currently 60 minutes). Check your local regulations for more information.'),
'default' => '5',
]],
'custom_config' => [
'textarea',
[
'label' => _('Advanced: Custom Configuration'),
'belongsTo' => 'backend_config',
'description' => _('This code will be inserted into your station\'s LiquidSoap configuration, below the playlist configuration and just before the IceCast output. Only use valid LiquidSoap code for this section!'),
]
],
'custom_config' => ['textarea', [
'label' => _('Advanced: Custom Configuration'),
'belongsTo' => 'backend_config',
'description' => _('This code will be inserted into your station\'s LiquidSoap configuration, below the playlist configuration and just before the IceCast output. Only use valid LiquidSoap code for this section!'),
]],
],
],
'submit_grp' => [
'elements' => [
'submit' => [
'submit',
[
'type' => 'submit',
'label' => _('Save Changes'),
'class' => 'btn btn-lg btn-primary',
]
],
'submit' => ['submit', [
'type' => 'submit',
'label' => _('Save Changes'),
'class' => 'btn btn-lg btn-primary',
]],
],
],
],

View file

@ -3,55 +3,40 @@ return [
'method' => 'post',
'elements' => [
'streamer_username' => [
'text',
[
'label' => _('Streamer Username'),
'description' => _('The streamer will use this username to connect to the radio server.'),
'required' => true,
]
],
'streamer_username' => ['text', [
'label' => _('Streamer Username'),
'description' => _('The streamer will use this username to connect to the radio server.'),
'required' => true,
]],
'streamer_password' => [
'text',
[
'label' => _('Streamer Password'),
'description' => _('The streamer will use this password to connect to the radio server.'),
'required' => true,
]
],
'streamer_password' => ['text', [
'label' => _('Streamer Password'),
'description' => _('The streamer will use this password to connect to the radio server.'),
'required' => true,
]],
'comments' => [
'textarea',
[
'label' => _('Comments'),
'description' => _('Internal notes or comments about the user, visible only on this control panel.'),
]
],
'comments' => ['textarea', [
'label' => _('Comments'),
'description' => _('Internal notes or comments about the user, visible only on this control panel.'),
]],
'is_active' => [
'radio',
[
'label' => _('Account is Active'),
'description' => _('Set to "Yes" to allow this account to log in and stream.'),
'required' => true,
'default' => '1',
'options' => [
0 => 'No',
1 => 'Yes',
],
]
],
'is_active' => ['radio', [
'label' => _('Account is Active'),
'description' => _('Set to "Yes" to allow this account to log in and stream.'),
'required' => true,
'default' => '1',
'options' => [
0 => 'No',
1 => 'Yes',
],
]],
'submit' => [
'submit',
[
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'ui-button btn-lg btn-primary',
]
],
'submit' => ['submit', [
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'ui-button btn-lg btn-primary',
]],
],
];

View file

@ -14,42 +14,30 @@ return [
'method' => 'post',
'elements' => [
'email' => [
'email',
[
'label' => _('E-mail Address'),
'required' => true,
'autocomplete' => 'off',
]
],
'email' => ['email', [
'label' => _('E-mail Address'),
'required' => true,
'autocomplete' => 'off',
]],
'auth_password' => [
'password',
[
'label' => _('Reset Password'),
'description' => _('Leave blank to use the current password.'),
'autocomplete' => 'off',
'required' => false,
]
],
'auth_password' => ['password', [
'label' => _('Reset Password'),
'description' => _('Leave blank to use the current password.'),
'autocomplete' => 'off',
'required' => false,
]],
'roles' => [
'multiCheckbox',
[
'label' => _('Roles'),
'options' => $em->getRepository(\Entity\Role::class)->fetchSelect(),
]
],
'roles' => ['multiCheckbox', [
'label' => _('Roles'),
'options' => $em->getRepository(\Entity\Role::class)->fetchSelect(),
]],
'submit' => [
'submit',
[
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]
],
'submit' => ['submit', [
'type' => 'submit',
'label' => _('Save Changes'),
'helper' => 'formButton',
'class' => 'btn btn-lg btn-primary',
]],
],
],
];

View file

@ -3,10 +3,10 @@
* InfluxDB Configuration
*/
return [
'host' => 'localhost',
'port' => 8086,
'username' => 'root',
'password' => 'root',
'protocol' => 'http',
];
return array(
'host' => 'localhost',
'port' => 8086,
'username' => 'root',
'password' => 'root',
'protocol' => 'http',
);

View file

@ -5,50 +5,12 @@
namespace AzuraCast\Acl;
use Entity\User;
use \Entity\User;
use \Entity\Role;
use \Entity\Station;
class StationAcl extends \App\Acl
{
/**
* Pretty wrapper around the 'isAllowed' function that throws a UI-friendly exception upon failure.
*
* @param $action
* @throws \App\Exception\NotLoggedIn
* @throws \App\Exception\PermissionDenied
*/
public function checkPermission($action, $station_id = null)
{
if (!$this->isAllowed($action, $station_id)) {
if (!$this->_auth->isLoggedIn()) {
throw new \App\Exception\NotLoggedIn();
} else {
throw new \App\Exception\PermissionDenied();
}
}
}
/**
* Check if the currently logged-in user can perform a specified action.
*
* @param string $action
* @return bool|mixed
*/
public function isAllowed($action, $station_id = null)
{
$user = $this->_auth->getLoggedInUser();
$is_logged_in = ($user instanceof User);
if ($action == "is logged in") {
return ($is_logged_in);
} elseif ($action == "is not logged in") {
return (!$is_logged_in);
} elseif ($is_logged_in) {
return $this->userAllowed($action, $user, $station_id);
} else {
return false;
}
}
/**
* Check if a specified User entity is allowed to perform an action (or array of actions).
*
@ -63,31 +25,57 @@ class StationAcl extends \App\Acl
asort($action);
$memoize_text = serialize($action);
$memoize = ($station_id !== null) ? md5($memoize_text . '_' . $station_id) : md5($memoize_text);
$memoize = ($station_id !== null) ? md5($memoize_text.'_'.$station_id) : md5($memoize_text);
$user_id = ($user instanceof User) ? $user->id : 'anonymous';
if (!isset($this->_cache[$user_id][$memoize])) {
if ($user instanceof User) {
if (!isset($this->_roles[$user_id])) {
$this->_roles[$user_id] = [];
if( !isset($this->_cache[$user_id][$memoize]) )
{
if($user instanceof User)
{
if(!isset($this->_roles[$user_id]))
{
$this->_roles[$user_id] = array();
if (count($user->roles) > 0) {
foreach ($user->roles as $role) {
if (count($user->roles) > 0)
{
foreach($user->roles as $role)
$this->_roles[$user_id][] = $role->id;
}
}
}
$this->_cache[$user_id][$memoize] = $this->roleAllowed($this->_roles[$user_id], $action, $station_id);
} else {
$this->_cache[$user_id][$memoize] = $this->roleAllowed(['Unauthenticated'], $action, $station_id);
}
else
{
$this->_cache[$user_id][$memoize] = $this->roleAllowed(array('Unauthenticated'), $action, $station_id);
}
}
return $this->_cache[$user_id][$memoize];
}
/**
* Check if the currently logged-in user can perform a specified action.
*
* @param string $action
* @return bool|mixed
*/
public function isAllowed($action, $station_id = null)
{
$user = $this->_auth->getLoggedInUser();
$is_logged_in = ($user instanceof User);
if ($action == "is logged in")
return ($is_logged_in);
elseif ($action == "is not logged in")
return (!$is_logged_in);
elseif ($is_logged_in)
return $this->userAllowed($action, $user, $station_id);
else
return false;
}
/**
* Check if a role (or array of roles) is allowed to perform an action (or array of actions).
*
@ -99,45 +87,66 @@ class StationAcl extends \App\Acl
{
$this->init();
if (is_array($role_id)) {
foreach ($role_id as $r) {
if ($this->roleAllowed($r, $action, $station_id)) {
if(is_array($role_id))
{
foreach($role_id as $r)
{
if($this->roleAllowed($r, $action, $station_id))
return true;
}
}
return false;
} else {
if (is_array($action)) {
foreach ($action as $a) {
if ($this->roleAllowed($role_id, $a, $station_id)) {
return true;
}
}
return false;
} else {
if ($role_id == 1) // Default super-administrator role.
{
}
else if(is_array($action))
{
foreach($action as $a)
{
if($this->roleAllowed($role_id, $a, $station_id))
return true;
}
if (in_array('administer all', (array)$this->_actions[$role_id]['global'])) {
return true;
}
if (isset($this->_actions[$role_id])) {
if ($station_id !== null) {
if (in_array('administer stations', (array)$this->_actions[$role_id]['global'])) {
return true;
}
return in_array($action, (array)$this->_actions[$role_id]['stations'][$station_id]);
} else {
return in_array($action, (array)$this->_actions[$role_id]['global']);
}
}
return false;
}
return false;
}
else
{
if($role_id == 1) // Default super-administrator role.
return true;
if (in_array('administer all', (array)$this->_actions[$role_id]['global']))
return true;
if (isset($this->_actions[$role_id]))
{
if ($station_id !== null)
{
if (in_array('administer stations', (array)$this->_actions[$role_id]['global']))
return true;
return in_array($action, (array)$this->_actions[$role_id]['stations'][$station_id]);
}
else
{
return in_array($action, (array)$this->_actions[$role_id]['global']);
}
}
return false;
}
}
/**
* Pretty wrapper around the 'isAllowed' function that throws a UI-friendly exception upon failure.
*
* @param $action
* @throws \App\Exception\NotLoggedIn
* @throws \App\Exception\PermissionDenied
*/
public function checkPermission($action, $station_id = null)
{
if (!$this->isAllowed($action, $station_id))
{
if (!$this->_auth->isLoggedIn())
throw new \App\Exception\NotLoggedIn();
else
throw new \App\Exception\PermissionDenied();
}
}
}

View file

@ -22,9 +22,8 @@ class ClearCache extends \App\Console\Command\CommandAbstract
{
// Flush route cache.
$app_settings = $this->di->get('settings');
if (!empty($app_settings['routerCacheFile'])) {
if (!empty($app_settings['routerCacheFile']))
@unlink($app_settings['routerCacheFile']);
}
$output->writeln('Router cache file cleared.');

View file

@ -36,10 +36,11 @@ class RestartRadio extends \App\Console\Command\CommandAbstract
$supervisor->stopAllProcesses();
foreach ($stations as $station) {
foreach($stations as $station)
{
/** @var Station $station */
\App\Debug::log('Restarting station #' . $station->id . ': ' . $station->name);
\App\Debug::log('Restarting station #'.$station->id.': '.$station->name);
$station->writeConfiguration($this->di);

View file

@ -1,11 +1,14 @@
<?php
namespace AzuraCast\Console\Command;
use App\Sync\Manager;
use Entity\Station;
use Entity\StationStreamer;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use App\Sync\Manager;
class StreamerAuth extends \App\Console\Command\CommandAbstract
{
@ -42,26 +45,26 @@ class StreamerAuth extends \App\Console\Command\CommandAbstract
$station_id = (int)$input->getArgument('station_id');
$station = $this->di['em']->getRepository(Station::class)->find($station_id);
if (!($station instanceof Station)) {
if (!($station instanceof Station))
return $this->_return($output, 'false');
}
if ($input->getArgument('user') == 'shoutcast') {
if ($input->getArgument('user') == 'shoutcast')
{
list($user, $pass) = explode(':', $input->getArgument('pass'));
} else {
}
else
{
$user = $input->getArgument('user');
$pass = $input->getArgument('pass');
}
if (!$station->enable_streamers) {
if (!$station->enable_streamers)
return $this->_return($output, 'false');
}
if ($this->di['em']->getRepository(StationStreamer::class)->authenticate($station, $user, $pass)) {
if ($this->di['em']->getRepository(StationStreamer::class)->authenticate($station, $user, $pass))
return $this->_return($output, 'true');
} else {
else
return $this->_return($output, 'false');
}
}
protected function _return(OutputInterface $output, $result)

View file

@ -1,9 +1,11 @@
<?php
namespace AzuraCast\Console\Command;
use App\Sync\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use App\Sync\Manager;
class Sync extends \App\Console\Command\CommandAbstract
{
@ -30,24 +32,25 @@ class Sync extends \App\Console\Command\CommandAbstract
/** @var \App\Sync $sync */
$sync = $this->di['sync'];
switch ($input->getArgument('task')) {
switch($input->getArgument('task'))
{
case 'long':
$output->writeln('Running Long (1-hour) Sync...');
$sync->syncLong();
break;
break;
case 'medium':
$output->writeln('Running Medium (5-minutes) Sync...');
$sync->syncMedium();
break;
break;
case 'short':
$output->writeln('Running Short (1-minute) Sync...');
$sync->syncShort();
break;
break;
case 'nowplaying':
default:
@ -55,7 +58,7 @@ class Sync extends \App\Console\Command\CommandAbstract
define('NOWPLAYING_SEGMENT', 1);
$sync->syncNowplaying();
break;
break;
}
}
}

View file

@ -29,11 +29,10 @@ class Customization
*/
public function getTimeZone()
{
if ($this->user !== null && !empty($this->user->timezone)) {
if ($this->user !== null && !empty($this->user->timezone))
return $this->user->timezone;
} else {
else
return date_default_timezone_get();
}
}
/*
@ -55,18 +54,21 @@ class Customization
$supported_locales = $this->config->application->locale->supported->toArray();
// Prefer user-based profile locale.
if ($this->user !== null && !empty($this->user->locale) && $this->user->locale !== 'default') {
if (isset($supported_locales[$this->user->locale])) {
if ($this->user !== null && !empty($this->user->locale) && $this->user->locale !== 'default')
{
if (isset($supported_locales[$this->user->locale]))
$locale = $this->user->locale;
}
}
// Attempt to load from browser headers.
if (!$locale) {
if (!$locale)
{
$browser_locale = \Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']);
foreach ($supported_locales as $lang_code => $lang_name) {
if (strcmp(substr($browser_locale, 0, 2), substr($lang_code, 0, 2)) == 0) {
foreach($supported_locales as $lang_code => $lang_name)
{
if (strcmp(substr($browser_locale, 0, 2), substr($lang_code, 0, 2)) == 0)
{
$locale = $lang_code;
break;
}
@ -74,9 +76,8 @@ class Customization
}
// Default to system option.
if (!$locale) {
if (!$locale)
$locale = $this->config->application->locale->default;
}
return $locale;
}
@ -88,11 +89,11 @@ class Customization
*/
public function getTheme()
{
if ($this->user !== null && !empty($this->user->theme)) {
if ($this->user !== null && !empty($this->user->theme))
{
$available_themes = $this->config->application->themes->available->toArray();
if (isset($available_themes[$this->user->theme])) {
if (isset($available_themes[$this->user->theme]))
return $this->user->theme;
}
}
return $this->config->application->themes->default;

View file

@ -3,6 +3,8 @@ namespace AzuraCast\Mvc;
use AzuraCast\Acl\StationAcl;
use Entity\Settings;
class Controller extends \App\Mvc\Controller
{
/** @var StationAcl */
@ -10,9 +12,8 @@ class Controller extends \App\Mvc\Controller
public function init()
{
if ($this->em->getRepository('Entity\Settings')->getSetting('setup_complete', 0) == 0) {
if ($this->em->getRepository('Entity\Settings')->getSetting('setup_complete', 0) == 0)
return $this->redirectToRoute(['module' => 'frontend', 'controller' => 'setup']);
}
return parent::init();
}

View file

@ -41,22 +41,12 @@ abstract class AdapterAbstract
abstract public function write();
/**
* Check if the service is running.
*
* @return bool
* Return the shell command required to run the program.
* @return string|null
*/
public function isRunning()
public function getCommand()
{
if ($this->hasCommand()) {
$program_name = $this->getProgramName();
$process = $this->supervisor->getProcess($program_name);
if ($process instanceof Process) {
return $process->isRunning();
}
}
return false;
return NULL;
}
/**
@ -65,27 +55,78 @@ abstract class AdapterAbstract
*/
public function hasCommand()
{
if (APP_TESTING_MODE) {
if (APP_TESTING_MODE)
return false;
}
return ($this->getCommand() !== null);
}
/**
* Return the shell command required to run the program.
* @return string|null
* Check if the service is running.
*
* @return bool
*/
public function getCommand()
public function isRunning()
{
return null;
if ($this->hasCommand())
{
$program_name = $this->getProgramName();
$process = $this->supervisor->getProcess($program_name);
if ($process instanceof Process)
return $process->isRunning();
}
return false;
}
/**
* Return the program's fully qualified supervisord name.
* @return bool
* Stop the executable service.
*/
abstract public function getProgramName();
public function stop()
{
if ($this->hasCommand())
{
try
{
$program_name = $this->getProgramName();
$this->supervisor->stopProcess($program_name);
$this->log(_('Process stopped.'), 'green');
}
catch(FaultException $e)
{
if (stristr($e->getMessage(), 'NOT_RUNNING') !== false)
$this->log(_('Process was not running!'), 'red');
else
throw $e;
}
}
}
/**
* Start the executable service.
*/
public function start()
{
if ($this->hasCommand())
{
try
{
$program_name = $this->getProgramName();
$this->supervisor->startProcess($program_name);
$this->log(_('Process started.'), 'green');
}
catch(FaultException $e)
{
if (stristr($e->getMessage(), 'ALREADY_STARTED') !== false)
$this->log(_('Process is already running!'), 'red');
else
throw $e;
}
}
}
/**
* Restart the executable service.
@ -97,25 +138,10 @@ abstract class AdapterAbstract
}
/**
* Stop the executable service.
* Return the program's fully qualified supervisord name.
* @return bool
*/
public function stop()
{
if ($this->hasCommand()) {
try {
$program_name = $this->getProgramName();
$this->supervisor->stopProcess($program_name);
$this->log(_('Process stopped.'), 'green');
} catch (FaultException $e) {
if (stristr($e->getMessage(), 'NOT_RUNNING') !== false) {
$this->log(_('Process was not running!'), 'red');
} else {
throw $e;
}
}
}
}
abstract public function getProgramName();
/**
* Log a message to console or to flash (if interactive session).
@ -124,43 +150,21 @@ abstract class AdapterAbstract
*/
public function log($message, $class = 'info')
{
if (empty($message)) {
if (empty($message))
return;
}
if (!APP_IS_COMMAND_LINE) {
if (!APP_IS_COMMAND_LINE)
{
$flash = $this->di['flash'];
$flash->addMessage($message, $class, true);
}
$log_file = APP_INCLUDE_TEMP . '/radio_adapter_log.txt';
$log_message = str_pad(date('Y-m-d g:ia'), 20, ' ', STR_PAD_RIGHT) . $message . "\n";
$log_file = APP_INCLUDE_TEMP.'/radio_adapter_log.txt';
$log_message = str_pad(date('Y-m-d g:ia'), 20, ' ', STR_PAD_RIGHT).$message."\n";
file_put_contents($log_file, $log_message, FILE_APPEND);
if (!APP_TESTING_MODE) {
\App\Debug::log('[' . strtoupper($class) . '] ' . $message);
}
}
/**
* Start the executable service.
*/
public function start()
{
if ($this->hasCommand()) {
try {
$program_name = $this->getProgramName();
$this->supervisor->startProcess($program_name);
$this->log(_('Process started.'), 'green');
} catch (FaultException $e) {
if (stristr($e->getMessage(), 'ALREADY_STARTED') !== false) {
$this->log(_('Process is already running!'), 'red');
} else {
throw $e;
}
}
}
if (!APP_TESTING_MODE)
\App\Debug::log('['.strtoupper($class).'] '.$message);
}
}

View file

@ -5,13 +5,13 @@ abstract class BackendAbstract extends \AzuraCast\Radio\AdapterAbstract
{
protected $supports_media = true;
protected $supports_requests = true;
public function supportsMedia()
{
return $this->supports_media;
}
protected $supports_requests = true;
public function supportsRequests()
{
return $this->supports_requests;
@ -19,13 +19,12 @@ abstract class BackendAbstract extends \AzuraCast\Radio\AdapterAbstract
public function log($message, $class = 'info')
{
if (!empty(trim($message))) {
parent::log(str_pad('Radio Backend: ', 20, ' ', STR_PAD_RIGHT) . $message, $class);
}
if (!empty(trim($message)))
parent::log(str_pad('Radio Backend: ', 20, ' ', STR_PAD_RIGHT).$message, $class);
}
public function getProgramName()
{
return 'station_' . $this->station->id . ':station_' . $this->station->id . '_backend';
return 'station_'.$this->station->id.':station_'.$this->station->id.'_backend';
}
}

View file

@ -1,11 +1,11 @@
<?php
namespace AzuraCast\Radio\Backend;
use Entity\Settings;
class LiquidSoap extends BackendAbstract
{
public function read()
{
}
public function read() {}
/**
* Write configuration from Station object to the external service.
@ -18,19 +18,19 @@ class LiquidSoap extends BackendAbstract
$playlist_path = $this->station->getRadioPlaylistsDir();
$media_path = $this->station->getRadioMediaDir();
$config_path = $this->station->getRadioConfigDir();
$ls_config = [];
$ls_config = array();
$ls_config[] = '# WARNING! This file is automatically generated by AzuraCast.';
$ls_config[] = '# Do not update it directly!';
$ls_config[] = '';
$ls_config[] = 'set("init.daemon", false)';
$ls_config[] = 'set("init.daemon.pidfile.path","' . $config_path . '/liquidsoap.pid")';
$ls_config[] = 'set("log.file.path","' . $config_path . '/liquidsoap.log")';
$ls_config[] = 'set("init.daemon.pidfile.path","'.$config_path.'/liquidsoap.pid")';
$ls_config[] = 'set("log.file.path","'.$config_path.'/liquidsoap.log")';
$ls_config[] = 'set("server.telnet",true)';
$ls_config[] = 'set("server.telnet.bind_addr","127.0.0.1")';
$ls_config[] = 'set("server.telnet.port", ' . $this->_getTelnetPort() . ')';
$ls_config[] = 'set("server.telnet.port", '.$this->_getTelnetPort().')';
$ls_config[] = 'set("server.telnet.reverse_dns",false)';
$ls_config[] = '';
@ -47,25 +47,25 @@ class LiquidSoap extends BackendAbstract
*/
// Clear out existing playlists directory.
$current_playlists = array_diff(scandir($playlist_path), ['..', '.']);
foreach ($current_playlists as $list) {
@unlink($playlist_path . '/' . $list);
}
$current_playlists = array_diff(scandir($playlist_path), array('..', '.'));
foreach($current_playlists as $list)
@unlink($playlist_path.'/'.$list);
// Write new playlists.
$playlists_by_type = [];
$playlists = [];
$playlists_by_type = array();
$playlists = array();
$ls_config[] = '# Playlists';
foreach ($this->station->playlists as $playlist_raw) {
if (!$playlist_raw->is_enabled) {
foreach($this->station->playlists as $playlist_raw)
{
if (!$playlist_raw->is_enabled)
continue;
}
$playlist_file = [];
foreach ($playlist_raw->media as $media_file) {
$media_file_path = $media_path . '/' . $media_file->path;
$playlist_file = array();
foreach($playlist_raw->media as $media_file)
{
$media_file_path = $media_path.'/'.$media_file->path;
$playlist_file[] = $media_file_path;
}
@ -75,12 +75,12 @@ class LiquidSoap extends BackendAbstract
$playlist = $playlist_raw->toArray($this->di['em']);
$playlist['var_name'] = 'playlist_' . $playlist_raw->getShortName();
$playlist['file_path'] = $playlist_path . '/' . $playlist['var_name'] . '.pls';
$playlist['var_name'] = 'playlist_'.$playlist_raw->getShortName();
$playlist['file_path'] = $playlist_path.'/'.$playlist['var_name'].'.pls';
file_put_contents($playlist['file_path'], $playlist_file_contents);
$ls_config[] = $playlist['var_name'] . ' = playlist(reload_mode="watch","' . $playlist['file_path'] . '")';
$ls_config[] = $playlist['var_name'].' = playlist(reload_mode="watch","'.$playlist['file_path'].'")';
$playlist_type = $playlist['type'] ?: 'default';
$playlists_by_type[$playlist_type][] = $playlist;
@ -92,33 +92,34 @@ class LiquidSoap extends BackendAbstract
$ls_config[] = '';
// Cannot build a LiquidSoap playlist with
if (count($playlists_by_type['default']) == 0) {
if (count($playlists) > 0) {
$this->log('LiquidSoap will not start until at least one playlist is set as the "Default" type.',
'error');
}
if (count($playlists_by_type['default']) == 0)
{
if (count($playlists) > 0)
$this->log('LiquidSoap will not start until at least one playlist is set as the "Default" type.', 'error');
return false;
}
// Build "default" type playlists.
$playlist_weights = [];
$playlist_vars = [];
$playlist_weights = array();
$playlist_vars = array();
foreach ($playlists_by_type['default'] as $playlist) {
foreach($playlists_by_type['default'] as $playlist)
{
$playlist_weights[] = $playlist['weight'];
$playlist_vars[] = $playlist['var_name'];
}
$ls_config[] = '# Standard Playlists';
$ls_config[] = 'radio = random(weights=[' . implode(', ', $playlist_weights) . '], [' . implode(', ',
$playlist_vars) . ']);';
$ls_config[] = 'radio = random(weights=['.implode(', ', $playlist_weights).'], ['.implode(', ', $playlist_vars).']);';
$ls_config[] = '';
// Once per X songs playlists
if (count($playlists_by_type['once_per_x_songs']) > 0) {
if (count($playlists_by_type['once_per_x_songs']) > 0)
{
$ls_config[] = '# Once per x Songs Playlists';
foreach ($playlists_by_type['once_per_x_songs'] as $playlist) {
foreach($playlists_by_type['once_per_x_songs'] as $playlist)
{
$ls_config[] = 'radio = rotate(weights=[1,' . $playlist['play_per_songs'] . '], [' . $playlist['var_name'] . ', radio])';
}
@ -126,13 +127,15 @@ class LiquidSoap extends BackendAbstract
}
// Once per X minutes playlists
if (count($playlists_by_type['once_per_x_minutes']) > 0) {
if (count($playlists_by_type['once_per_x_minutes']) > 0)
{
$ls_config[] = '# Once per x Minutes Playlists';
foreach ($playlists_by_type['once_per_x_minutes'] as $playlist) {
$delay_seconds = $playlist['play_per_minutes'] * 60;
$ls_config[] = 'delay_' . $playlist['var_name'] . ' = delay(' . $delay_seconds . '., ' . $playlist['var_name'] . ')';
$ls_config[] = 'radio = fallback([delay_' . $playlist['var_name'] . ', radio])';
foreach($playlists_by_type['once_per_x_minutes'] as $playlist)
{
$delay_seconds = $playlist['play_per_minutes']*60;
$ls_config[] = 'delay_'.$playlist['var_name'].' = delay('.$delay_seconds.'., '.$playlist['var_name'].')';
$ls_config[] = 'radio = fallback([delay_'.$playlist['var_name'].', radio])';
}
$ls_config[] = '';
@ -142,16 +145,20 @@ class LiquidSoap extends BackendAbstract
$switches = [];
// Scheduled playlists
if (count($playlists_by_type['scheduled']) > 0) {
foreach ($playlists_by_type['scheduled'] as $playlist) {
$play_time = $this->_getTime($playlist['schedule_start_time']) . '-' . $this->_getTime($playlist['schedule_end_time']);
if (count($playlists_by_type['scheduled']) > 0)
{
foreach($playlists_by_type['scheduled'] as $playlist)
{
$play_time = $this->_getTime($playlist['schedule_start_time']).'-'.$this->_getTime($playlist['schedule_end_time']);
$switches[] = '({ ' . $play_time . ' }, ' . $playlist['var_name'] . ')';
}
}
// Once per day playlists
if (count($playlists_by_type['once_per_day']) > 0) {
foreach ($playlists_by_type['once_per_day'] as $playlist) {
if (count($playlists_by_type['once_per_day']) > 0)
{
foreach($playlists_by_type['once_per_day'] as $playlist)
{
$play_time = $this->_getTime($playlist['play_once_time']);
$switches[] = '({ ' . $play_time . ' }, ' . $playlist['var_name'] . ')';
}
@ -176,17 +183,18 @@ class LiquidSoap extends BackendAbstract
$fallbacks[] = 'requests';
$switches[] = '({ true }, radio)';
$fallbacks[] = 'switch([ ' . implode(', ', $switches) . ' ])';
$fallbacks[] = 'switch([ '.implode(', ', $switches).' ])';
$fallbacks[] = 'blank(duration=2.)';
// $ls_config[] = 'radio = fallback(track_sensitive = true, [playlists, security])';
$ls_config[] = 'radio = fallback(track_sensitive = true, [' . implode(', ', $fallbacks) . '])';
$ls_config[] = 'radio = fallback(track_sensitive = true, ['.implode(', ', $fallbacks).'])';
$ls_config[] = '';
$ls_config[] = '# Crossfading';
$ls_config[] = 'radio = crossfade(start_next=3.,fade_out=2.,fade_in=2.,radio)';
if (!empty($settings['custom_config'])) {
if (!empty($settings['custom_config']))
{
$ls_config[] = '';
$ls_config[] = '# Custom Configuration (Specified in Station Profile)';
$ls_config[] = $settings['custom_config'];
@ -201,10 +209,10 @@ class LiquidSoap extends BackendAbstract
$broadcast_port = $fe_settings['port'];
$broadcast_source_pw = $fe_settings['source_pw'];
switch ($this->station->frontend_type) {
switch($this->station->frontend_type)
{
case 'remote':
$this->log(_('You cannot use an AutoDJ with a remote frontend. Please change the frontend type or update the backend to be "Disabled".'),
'error');
$this->log(_('You cannot use an AutoDJ with a remote frontend. Please change the frontend type or update the backend to be "Disabled".'), 'error');
return false;
break;
@ -212,38 +220,38 @@ class LiquidSoap extends BackendAbstract
$format = 'mp3';
$bitrate = 128;
$output_format = '%mp3.cbr(samplerate=44100,stereo=true,bitrate=' . (int)$bitrate . ')';
$output_format = '%mp3.cbr(samplerate=44100,stereo=true,bitrate='.(int)$bitrate.')';
$output_params = [
'id="radio_out"',
'host = "localhost"',
'port = ' . ($broadcast_port),
'password = "' . $broadcast_source_pw . '"',
'port = '.($broadcast_port),
'password = "'.$broadcast_source_pw.'"',
'name = "' . $this->_cleanUpString($this->station->name) . '"',
'public = false',
$output_format, // Required output format (%mp3 etc)
'radio', // Required
];
$ls_config[] = 'output.shoutcast(' . implode(', ', $output_params) . ')';
break;
$ls_config[] = 'output.shoutcast('.implode(', ', $output_params).')';
break;
case 'icecast':
default:
foreach ($this->station->mounts as $mount_row) {
if (!$mount_row->enable_autodj) {
foreach($this->station->mounts as $mount_row)
{
if (!$mount_row->enable_autodj)
continue;
}
$format = strtolower($mount_row->autodj_format ?: 'mp3');
$bitrate = $mount_row->autodj_bitrate ?: 128;
if ($format == 'ogg') {
$output_format = '%vorbis.cbr(samplerate=44100, channels=2, bitrate=' . (int)$bitrate . ')';
} else {
$output_format = '%mp3.cbr(samplerate=44100,stereo=true,bitrate=' . (int)$bitrate . ')';
}
if ($format == 'ogg')
$output_format = '%vorbis.cbr(samplerate=44100, channels=2, bitrate='.(int)$bitrate.')';
else
$output_format = '%mp3.cbr(samplerate=44100,stereo=true,bitrate='.(int)$bitrate.')';
if (!empty($output_format)) {
if (!empty($output_format))
{
$output_params = [
$output_format, // Required output format (%mp3 or %ogg)
'id="radio_out_' . $mount_row->id . '"',
@ -258,19 +266,19 @@ class LiquidSoap extends BackendAbstract
$ls_config[] = 'output.icecast(' . implode(', ', $output_params) . ')';
}
}
break;
break;
}
$ls_config_contents = implode("\n", $ls_config);
$ls_config_path = $config_path . '/liquidsoap.liq';
$ls_config_path = $config_path.'/liquidsoap.liq';
file_put_contents($ls_config_path, $ls_config_contents);
return true;
}
protected function _getTelnetPort()
protected function _cleanUpString($string)
{
return (8500 + (($this->station->id - 1) * 10));
return str_replace(['"', "\n", "\r"], ['\'', '', ''], $string);
}
protected function _getTime($time_code)
@ -293,50 +301,47 @@ class LiquidSoap extends BackendAbstract
$hours += $offset_hours;
$hours = $hours % 24;
if ($hours < 0) {
if ($hours < 0)
$hours += 24;
}
return $hours . 'h' . $mins . 'm';
}
protected function _cleanUpString($string)
{
return str_replace(['"', "\n", "\r"], ['\'', '', ''], $string);
return $hours.'h'.$mins.'m';
}
public function getCommand()
{
$config_path = $this->station->getRadioConfigDir() . '/liquidsoap.liq';
return 'liquidsoap ' . $config_path;
$config_path = $this->station->getRadioConfigDir().'/liquidsoap.liq';
return 'liquidsoap '.$config_path;
}
public function request($music_file)
{
return $this->command('requests.push ' . $music_file);
}
public function command($command_str)
{
$fp = stream_socket_client('tcp://localhost:' . $this->_getTelnetPort(), $errno, $errstr, 20);
if (!$fp) {
throw new \App\Exception('Telnet failure: ' . $errstr . ' (' . $errno . ')');
}
fwrite($fp, str_replace(["\\'", '&amp;'], ["'", '&'], urldecode($command_str)) . "\nquit\n");
$eat = '';
while (!feof($fp)) {
$eat .= fgets($fp, 1024);
}
fclose($fp);
return true;
return $this->command('requests.push '.$music_file);
}
public function skip()
{
return $this->command('radio_out.skip');
}
public function command($command_str)
{
$fp = stream_socket_client('tcp://localhost:'.$this->_getTelnetPort(), $errno, $errstr, 20);
if (!$fp)
throw new \App\Exception('Telnet failure: '.$errstr.' ('.$errno.')');
fwrite($fp, str_replace(array("\\'", '&amp;'), array("'",'&'),urldecode($command_str))."\nquit\n");
$eat = '';
while (!feof($fp))
$eat .= fgets($fp, 1024);
fclose($fp);
return true;
}
protected function _getTelnetPort()
{
return (8500 + (($this->station->id - 1) * 10));
}
}

View file

@ -1,19 +1,18 @@
<?php
namespace AzuraCast\Radio\Backend;
use Entity\Settings;
class None extends BackendAbstract
{
protected $supports_media = false;
protected $supports_requests = false;
public function read()
{
}
{}
public function write()
{
}
{}
public function isRunning()
{

View file

@ -2,21 +2,21 @@
namespace AzuraCast\Radio\Frontend;
use App\Service\Curl;
use Entity\Station;
use Interop\Container\ContainerInterface;
abstract class FrontendAbstract extends \AzuraCast\Radio\AdapterAbstract
{
protected $supports_mounts = true;
protected $supports_streamers = true;
public function supportsMounts()
{
return $this->supports_mounts;
}
public function getDefaultMounts()
{
}
public function getDefaultMounts() {}
protected $supports_streamers = true;
public function supportsStreamers()
{
@ -25,7 +25,7 @@ abstract class FrontendAbstract extends \AzuraCast\Radio\AdapterAbstract
public function getProgramName()
{
return 'station_' . $this->station->id . ':station_' . $this->station->id . '_frontend';
return 'station_'.$this->station->id.':station_'.$this->station->id.'_frontend';
}
abstract public function getStreamUrl();
@ -43,92 +43,69 @@ abstract class FrontendAbstract extends \AzuraCast\Radio\AdapterAbstract
$use_radio_proxy = $settings_repo->getSetting('use_radio_proxy', 0);
// Web proxy support.
if (APP_APPLICATION_ENV == 'development' || $use_radio_proxy) {
return '/radio/' . $radio_port;
} else {
return 'http://' . $base_url . ':' . $radio_port;
}
if (APP_APPLICATION_ENV == 'development' || $use_radio_proxy)
return '/radio/'.$radio_port;
else
return 'http://'.$base_url.':'.$radio_port;
}
/* Fetch a remote URL. */
protected function getUrl($url, $c_opts = null)
{
if ($c_opts === null)
$c_opts = array();
if (!isset($c_opts['url']))
$c_opts['url'] = $url;
if (!isset($c_opts['timeout']))
$c_opts['timeout'] = 4;
return Curl::request($c_opts);
}
public function getNowPlaying()
{
// Now Playing defaults.
$np = [
'current_song' => [
'text' => 'Stream Offline',
'title' => '',
'artist' => '',
],
'listeners' => [
'current' => 0,
'unique' => null,
'total' => null,
],
'meta' => [
'status' => 'offline',
'bitrate' => 0,
'format' => '',
],
];
$np = array(
'current_song' => array(
'text' => 'Stream Offline',
'title' => '',
'artist' => '',
),
'listeners' => array(
'current' => 0,
'unique' => null,
'total' => null,
),
'meta' => array(
'status' => 'offline',
'bitrate' => 0,
'format' => '',
),
);
// Merge station-specific info into defaults.
$this->_getNowPlaying($np);
// Update status code for offline stations, clean up song info for online ones.
if ($np['current_song']['text'] == 'Stream Offline') {
if ($np['current_song']['text'] == 'Stream Offline')
$np['meta']['status'] = 'offline';
} else {
array_walk($np['current_song'], [$this, '_cleanUpString']);
}
else
array_walk($np['current_song'], array($this, '_cleanUpString'));
// Fill in any missing listener info.
if ($np['listeners']['unique'] === null) {
if ($np['listeners']['unique'] === null)
$np['listeners']['unique'] = $np['listeners']['current'];
}
if ($np['listeners']['total'] === null) {
if ($np['listeners']['total'] === null)
$np['listeners']['total'] = $np['listeners']['current'];
}
return $np;
}
abstract protected function _getNowPlaying(&$np);
/* Stub function for the process internal handler. */
/**
* Log a message to console or to flash (if interactive session).
*
* @param $message
*/
public function log($message, $class = 'info')
{
if (!empty(trim($message))) {
parent::log(str_pad('Radio Frontend: ', 20, ' ', STR_PAD_RIGHT) . $message, $class);
}
}
protected function getUrl($url, $c_opts = null)
{
if ($c_opts === null) {
$c_opts = [];
}
if (!isset($c_opts['url'])) {
$c_opts['url'] = $url;
}
if (!isset($c_opts['timeout'])) {
$c_opts['timeout'] = 4;
}
return Curl::request($c_opts);
}
/* Calculate listener count from unique and current totals. */
abstract protected function _getNowPlaying(&$np);
protected function _cleanUpString(&$value)
{
@ -136,20 +113,19 @@ abstract class FrontendAbstract extends \AzuraCast\Radio\AdapterAbstract
$value = trim($value);
}
/* Return the artist and title from a string in the format "Artist - Title" */
/* Calculate listener count from unique and current totals. */
protected function getListenerCount($unique_listeners = 0, $current_listeners = 0)
{
$unique_listeners = (int)$unique_listeners;
$current_listeners = (int)$current_listeners;
if ($unique_listeners == 0 || $current_listeners == 0) {
if ($unique_listeners == 0 || $current_listeners == 0)
return max($unique_listeners, $current_listeners);
} else {
else
return min($unique_listeners, $current_listeners);
}
}
/* Return the artist and title from a string in the format "Artist - Title" */
protected function getSongFromString($song_string, $delimiter = '-')
{
// Filter for CR AutoDJ
@ -164,30 +140,43 @@ abstract class FrontendAbstract extends \AzuraCast\Radio\AdapterAbstract
$string_parts = explode($delimiter, $song_string);
// If not normally delimited, return "text" only.
if (count($string_parts) == 1) {
return ['text' => $song_string, 'artist' => '', 'title' => $song_string];
}
if (count($string_parts) == 1)
return array('text' => $song_string, 'artist' => '', 'title' => $song_string);
// Title is the last element, artist is all other elements (artists are far more likely to have hyphens).
$title = trim(array_pop($string_parts));
$artist = trim(implode($delimiter, $string_parts));
return [
return array(
'text' => $song_string,
'artist' => $artist,
'title' => $title,
];
);
}
/**
* Log a message to console or to flash (if interactive session).
*
* @param $message
*/
public function log($message, $class = 'info')
{
if (!empty(trim($message)))
parent::log(str_pad('Radio Frontend: ', 20, ' ', STR_PAD_RIGHT).$message, $class);
}
protected function _processCustomConfig($custom_config_raw)
{
$custom_config = [];
if (substr($custom_config_raw, 0, 1) == '{') {
if (substr($custom_config_raw, 0, 1) == '{')
{
$custom_config = @json_decode($custom_config_raw, true);
} elseif (substr($custom_config_raw, 0, 1) == '<') {
}
elseif (substr($custom_config_raw, 0, 1) == '<')
{
$reader = new \App\Xml\Reader;
$custom_config = $reader->fromString('<icecast>' . $custom_config_raw . '</icecast>');
$custom_config = $reader->fromString('<icecast>'.$custom_config_raw.'</icecast>');
}
return $custom_config;

View file

@ -3,11 +3,86 @@ namespace AzuraCast\Radio\Frontend;
use App\Utilities;
use Doctrine\ORM\EntityManager;
use Entity\Station;
use Entity\Settings;
use Entity\StationMount;
class IceCast extends FrontendAbstract
{
/* Process a nowplaying record. */
protected function _getNowPlaying(&$np)
{
$fe_config = (array)$this->station->frontend_config;
$radio_port = $fe_config['port'];
$np_url = 'http://localhost:'.$radio_port.'/status-json.xsl';
\App\Debug::log($np_url);
$return_raw = $this->getUrl($np_url);
if (!$return_raw)
return false;
$return = @json_decode($return_raw, true);
\App\Debug::print_r($return);
if (!$return || !isset($return['icestats']['source']))
return false;
$sources = $return['icestats']['source'];
if (empty($sources))
return false;
if (key($sources) === 0)
$mounts = $sources;
else
$mounts = array($sources);
if (count($mounts) == 0)
return false;
$mounts = array_filter($mounts, function($mount) {
return (!empty($mount['title']) || !empty($mount['artist']));
});
// Sort in descending order of listeners.
usort($mounts, function($a, $b) {
$a_list = (int)$a['listeners'];
$b_list = (int)$b['listeners'];
if ($a_list == $b_list)
return 0;
else
return ($a_list > $b_list) ? -1 : 1;
});
$temp_array = $mounts[0];
if (isset($temp_array['artist']))
{
$np['current_song'] = array(
'artist' => $temp_array['artist'],
'title' => $temp_array['title'],
'text' => $temp_array['artist'].' - '.$temp_array['title'],
);
}
else
{
$np['current_song'] = $this->getSongFromString($temp_array['title'], ' - ');
}
$np['meta']['status'] = 'online';
$np['meta']['bitrate'] = $temp_array['bitrate'];
$np['meta']['format'] = $temp_array['server_type'];
$np['listeners']['current'] = (int)$temp_array['listeners'];
return true;
}
public function read()
{
$config = $this->_getConfig();
@ -16,14 +91,110 @@ class IceCast extends FrontendAbstract
return true;
}
public function write()
{
$config = $this->_getDefaults();
$frontend_config = (array)$this->station->frontend_config;
if (!empty($frontend_config['port']))
$config['listen-socket']['port'] = $frontend_config['port'];
if (!empty($frontend_config['source_pw']))
$config['authentication']['source-password'] = $frontend_config['source_pw'];
if (!empty($frontend_config['admin_pw']))
$config['authentication']['admin-password'] = $frontend_config['admin_pw'];
if (!empty($frontend_config['streamer_pw']))
{
foreach($config['mount'] as &$mount)
{
if (!empty($mount['password']))
$mount['password'] = $frontend_config['streamer_pw'];
}
}
if (!empty($frontend_config['custom_config']))
{
$custom_conf = $this->_processCustomConfig($frontend_config['custom_config']);
if (!empty($custom_conf))
$config = \App\Utilities::array_merge_recursive_distinct($config, $custom_conf);
}
// Set any unset values back to the DB config.
$this->station->frontend_config = $this->_loadFromConfig($config);
$em = $this->di['em'];
$em->persist($this->station);
$em->flush();
$config_path = $this->station->getRadioConfigDir();
$icecast_path = $config_path.'/icecast.xml';
$writer = new \App\Xml\Writer;
$icecast_config_str = $writer->toString($config, 'icecast');
// Strip the first line (the XML charset)
$icecast_config_str = substr( $icecast_config_str, strpos($icecast_config_str, "\n")+1 );
file_put_contents($icecast_path, $icecast_config_str);
}
/*
* Process Management
*/
public function getCommand()
{
$config_path = $this->station->getRadioConfigDir().'/icecast.xml';
return 'icecast2 -c '.$config_path;
}
public function getStreamUrl()
{
/** @var EntityManager */
$em = $this->di->get('em');
$mount_repo = $em->getRepository(StationMount::class);
$default_mount = $mount_repo->getDefaultMount($this->station);
$mount_name = ($default_mount instanceof StationMount) ? $default_mount->name : '/radio.mp3';
return $this->getUrlForMount($mount_name);
}
public function getStreamUrls()
{
$urls = [];
foreach($this->station->mounts as $mount)
$urls[] = $this->getUrlForMount($mount->name);
return $urls;
}
public function getUrlForMount($mount_name)
{
return $this->getPublicUrl().$mount_name.'?'.time();
}
public function getAdminUrl()
{
return $this->getPublicUrl().'/admin/';
}
/*
* Configuration
*/
protected function _getConfig()
{
$config_path = $this->station->getRadioConfigDir();
$icecast_path = $config_path . '/icecast.xml';
$icecast_path = $config_path.'/icecast.xml';
$defaults = $this->_getDefaults();
if (file_exists($icecast_path)) {
if (file_exists($icecast_path))
{
$reader = new \App\Xml\Reader;
$data = $reader->fromFile($icecast_path);
@ -33,6 +204,19 @@ class IceCast extends FrontendAbstract
return $defaults;
}
protected function _loadFromConfig($config)
{
$frontend_config = (array)$this->station->frontend_config;
return [
'custom_config' => $frontend_config['custom_config'],
'port' => $config['listen-socket']['port'],
'source_pw' => $config['authentication']['source-password'],
'admin_pw' => $config['authentication']['admin-password'],
'streamer_pw' => $config['mount'][0]['password'],
];
}
protected function _getDefaults()
{
$config_dir = $this->station->getRadioConfigDir();
@ -70,7 +254,7 @@ class IceCast extends FrontendAbstract
'logdir' => $config_dir,
'webroot' => '/usr/share/icecast2/web',
'adminroot' => '/usr/share/icecast2/admin',
'pidfile' => $config_dir . '/icecast.pid',
'pidfile' => $config_dir.'/icecast.pid',
'alias' => [
'@source' => '/',
'@destination' => '/status.xsl',
@ -89,43 +273,41 @@ class IceCast extends FrontendAbstract
$url = $this->di['url'];
foreach ($this->station->mounts as $mount_row) {
foreach($this->station->mounts as $mount_row)
{
$mount = [
'@type' => 'normal',
'@type' => 'normal',
'mount-name' => $mount_row->name,
];
if (!empty($mount_row->fallback_mount)) {
if (!empty($mount_row->fallback_mount))
{
$mount['fallback-mount'] = $mount_row->fallback_mount;
$mount['fallback-override'] = 1;
}
if ($mount_row->enable_streamers) {
$mount['username'] = 'shoutcast';
if ($mount_row->enable_streamers)
{
$mount['username'] ='shoutcast';
$mount['password'] = Utilities::generatePassword();
$mount['authentication'] = [
$mount['authentication'] = array(
'@type' => 'url',
'option' => [
[
'@name' => 'stream_auth',
'@value' => $url->route([
'module' => 'api',
'controller' => 'internal',
'action' => 'streamauth',
'id' => $this->station->id
], true)
'@value' => $url->route(['module' => 'api', 'controller' => 'internal', 'action' => 'streamauth', 'id' => $this->station->id], true)
],
],
];
);
$defaults['listen-socket']['shoutcast-mount'] = $mount_row->name;
}
if ($mount_row->frontend_config) {
if ($mount_row->frontend_config)
{
$mount_conf = $this->_processCustomConfig($mount_row->frontend_config);
if (!empty($mount_conf)) {
if (!empty($mount_conf))
$mount = \App\Utilities::array_merge_recursive_distinct($mount, $mount_conf);
}
}
$defaults['mount'][] = $mount;
@ -134,130 +316,19 @@ class IceCast extends FrontendAbstract
return $defaults;
}
/*
* Process Management
*/
protected function _loadFromConfig($config)
{
$frontend_config = (array)$this->station->frontend_config;
return [
'custom_config' => $frontend_config['custom_config'],
'port' => $config['listen-socket']['port'],
'source_pw' => $config['authentication']['source-password'],
'admin_pw' => $config['authentication']['admin-password'],
'streamer_pw' => $config['mount'][0]['password'],
];
}
public function write()
{
$config = $this->_getDefaults();
$frontend_config = (array)$this->station->frontend_config;
if (!empty($frontend_config['port'])) {
$config['listen-socket']['port'] = $frontend_config['port'];
}
if (!empty($frontend_config['source_pw'])) {
$config['authentication']['source-password'] = $frontend_config['source_pw'];
}
if (!empty($frontend_config['admin_pw'])) {
$config['authentication']['admin-password'] = $frontend_config['admin_pw'];
}
if (!empty($frontend_config['streamer_pw'])) {
foreach ($config['mount'] as &$mount) {
if (!empty($mount['password'])) {
$mount['password'] = $frontend_config['streamer_pw'];
}
}
}
if (!empty($frontend_config['custom_config'])) {
$custom_conf = $this->_processCustomConfig($frontend_config['custom_config']);
if (!empty($custom_conf)) {
$config = \App\Utilities::array_merge_recursive_distinct($config, $custom_conf);
}
}
// Set any unset values back to the DB config.
$this->station->frontend_config = $this->_loadFromConfig($config);
$em = $this->di['em'];
$em->persist($this->station);
$em->flush();
$config_path = $this->station->getRadioConfigDir();
$icecast_path = $config_path . '/icecast.xml';
$writer = new \App\Xml\Writer;
$icecast_config_str = $writer->toString($config, 'icecast');
// Strip the first line (the XML charset)
$icecast_config_str = substr($icecast_config_str, strpos($icecast_config_str, "\n") + 1);
file_put_contents($icecast_path, $icecast_config_str);
}
public function getCommand()
{
$config_path = $this->station->getRadioConfigDir() . '/icecast.xml';
return 'icecast2 -c ' . $config_path;
}
public function getStreamUrl()
{
/** @var EntityManager */
$em = $this->di->get('em');
$mount_repo = $em->getRepository(StationMount::class);
$default_mount = $mount_repo->getDefaultMount($this->station);
$mount_name = ($default_mount instanceof StationMount) ? $default_mount->name : '/radio.mp3';
return $this->getUrlForMount($mount_name);
}
public function getUrlForMount($mount_name)
{
return $this->getPublicUrl() . $mount_name . '?' . time();
}
/*
* Configuration
*/
public function getStreamUrls()
{
$urls = [];
foreach ($this->station->mounts as $mount) {
$urls[] = $this->getUrlForMount($mount->name);
}
return $urls;
}
public function getAdminUrl()
{
return $this->getPublicUrl() . '/admin/';
}
public function getDefaultMounts()
{
return [
[
'name' => '/radio.mp3',
'is_default' => 1,
'name' => '/radio.mp3',
'is_default' => 1,
'fallback_mount' => '/autodj.mp3',
'enable_streamers' => 1,
'enable_autodj' => 0,
],
[
'name' => '/autodj.mp3',
'is_default' => 0,
'name' => '/autodj.mp3',
'is_default' => 0,
'fallback_mount' => '/error.mp3',
'enable_streamers' => 0,
'enable_autodj' => 1,
@ -266,80 +337,4 @@ class IceCast extends FrontendAbstract
]
];
}
protected function _getNowPlaying(&$np)
{
$fe_config = (array)$this->station->frontend_config;
$radio_port = $fe_config['port'];
$np_url = 'http://localhost:' . $radio_port . '/status-json.xsl';
\App\Debug::log($np_url);
$return_raw = $this->getUrl($np_url);
if (!$return_raw) {
return false;
}
$return = @json_decode($return_raw, true);
\App\Debug::print_r($return);
if (!$return || !isset($return['icestats']['source'])) {
return false;
}
$sources = $return['icestats']['source'];
if (empty($sources)) {
return false;
}
if (key($sources) === 0) {
$mounts = $sources;
} else {
$mounts = [$sources];
}
if (count($mounts) == 0) {
return false;
}
$mounts = array_filter($mounts, function ($mount) {
return (!empty($mount['title']) || !empty($mount['artist']));
});
// Sort in descending order of listeners.
usort($mounts, function ($a, $b) {
$a_list = (int)$a['listeners'];
$b_list = (int)$b['listeners'];
if ($a_list == $b_list) {
return 0;
} else {
return ($a_list > $b_list) ? -1 : 1;
}
});
$temp_array = $mounts[0];
if (isset($temp_array['artist'])) {
$np['current_song'] = [
'artist' => $temp_array['artist'],
'title' => $temp_array['title'],
'text' => $temp_array['artist'] . ' - ' . $temp_array['title'],
];
} else {
$np['current_song'] = $this->getSongFromString($temp_array['title'], ' - ');
}
$np['meta']['status'] = 'online';
$np['meta']['bitrate'] = $temp_array['bitrate'];
$np['meta']['format'] = $temp_array['server_type'];
$np['listeners']['current'] = (int)$temp_array['listeners'];
return true;
}
}

View file

@ -6,18 +6,148 @@ use App\Debug;
class Remote extends FrontendAbstract
{
protected $supports_mounts = false;
protected $supports_streamers = false;
/* Process a nowplaying record. */
protected function _getNowPlaying(&$np)
{
$mounts = $this->_getMounts();
if (empty($mounts))
return false;
$default_mount = $mounts[0];
if (isset($default_mount['artist']))
{
$np['current_song'] = array(
'artist' => $default_mount['artist'],
'title' => $default_mount['title'],
'text' => $default_mount['artist'].' - '.$default_mount['title'],
);
}
else
{
$np['current_song'] = $this->getSongFromString($default_mount['title'], ' - ');
}
$np['meta']['status'] = 'online';
$np['meta']['bitrate'] = $default_mount['bitrate'];
$np['meta']['format'] = $default_mount['server_type'];
$np['listeners']['current'] = (int)$default_mount['listeners'];
return false;
}
protected function _getMounts()
{
$settings = (array)$this->station->frontend_config;
$remote_url = $this->getPublicUrl();
Debug::print_r($settings);
switch($settings['remote_type'])
{
case 'icecast':
$remote_stats_url = $remote_url.'/status-json.xsl';
$return_raw = $this->getUrl($remote_stats_url);
if (!$return_raw)
return false;
$return = @json_decode($return_raw, true);
Debug::print_r($return);
if (!$return || !isset($return['icestats']['source']))
return false;
$sources = $return['icestats']['source'];
if (empty($sources))
return false;
if (key($sources) === 0)
$mounts = $sources;
else
$mounts = array($sources);
if (count($mounts) == 0)
return false;
$mounts = array_filter($mounts, function($mount) {
return (!empty($mount['title']) || !empty($mount['artist']));
});
// Sort in descending order of listeners.
usort($mounts, function($a, $b) {
$a_list = (int)$a['listeners'];
$b_list = (int)$b['listeners'];
if ($a_list == $b_list)
return 0;
else
return ($a_list > $b_list) ? -1 : 1;
});
return $mounts;
break;
case 'shoutcast1':
$remote_stats_url = $remote_url.'/7.html';
$return_raw = $this->getUrl($remote_stats_url);
if (empty($return_raw))
return false;
preg_match("/<body.*>(.*)<\/body>/smU", $return_raw, $return);
$parts = explode(",", $return[1], 7);
Debug::print_r($parts);
return [[
'title' => $parts[6],
'bitrate' => $parts[5],
'listenurl' => $remote_url.'/;stream.nsv',
'server_type' => 'audio/mpeg',
'listeners' => $this->getListenerCount((int)$parts[4], (int)$parts[0]),
]];
break;
case 'shoutcast2':
$remote_stats_url = $remote_url.'/stats';
$return_raw = $this->getUrl($remote_stats_url);
if (empty($return_raw))
return false;
$current_data = \App\Export::xml_to_array($return_raw);
$song_data = $current_data['SHOUTCASTSERVER'];
Debug::print_r($song_data);
return [[
'title' => $song_data['SONGTITLE'],
'bitrate' => $song_data['BITRATE'],
'listenurl' => $remote_url.$song_data['STREAMPATH'],
'server_type' => $song_data['CONTENT'],
'listeners' => $this->getListenerCount((int)$song_data['UNIQUELISTENERS'], (int)$song_data['CURRENTLISTENERS']),
]];
break;
}
return false;
}
public function read()
{
}
{}
public function write()
{
}
{}
/*
* Process Management
*/
public function isRunning()
{
@ -27,125 +157,41 @@ class Remote extends FrontendAbstract
public function getStreamUrl()
{
$mounts = $this->_getMounts();
if (empty($mounts)) {
if (empty($mounts))
return false;
}
$default_mount = $mounts[0];
return $default_mount['listenurl'];
}
/*
* Process Management
*/
public function getStreamUrls()
{
$mounts = $this->_getMounts();
if (empty($mounts))
return false;
protected function _getMounts()
return \Packaged\Helpers\Arrays::ipull($mounts, 'listenurl');
}
public function getUrlForMount($mount_name)
{
return $this->getPublicUrl().$mount_name;
}
public function getAdminUrl()
{
$settings = (array)$this->station->frontend_config;
$remote_url = $this->getPublicUrl();
Debug::print_r($settings);
switch ($settings['remote_type']) {
switch($settings['remote_type'])
{
case 'icecast':
$remote_stats_url = $remote_url . '/status-json.xsl';
$return_raw = $this->getUrl($remote_stats_url);
if (!$return_raw) {
return false;
}
$return = @json_decode($return_raw, true);
Debug::print_r($return);
if (!$return || !isset($return['icestats']['source'])) {
return false;
}
$sources = $return['icestats']['source'];
if (empty($sources)) {
return false;
}
if (key($sources) === 0) {
$mounts = $sources;
} else {
$mounts = [$sources];
}
if (count($mounts) == 0) {
return false;
}
$mounts = array_filter($mounts, function ($mount) {
return (!empty($mount['title']) || !empty($mount['artist']));
});
// Sort in descending order of listeners.
usort($mounts, function ($a, $b) {
$a_list = (int)$a['listeners'];
$b_list = (int)$b['listeners'];
if ($a_list == $b_list) {
return 0;
} else {
return ($a_list > $b_list) ? -1 : 1;
}
});
return $mounts;
break;
return $this->getPublicUrl().'/admin/';
break;
case 'shoutcast1':
$remote_stats_url = $remote_url . '/7.html';
$return_raw = $this->getUrl($remote_stats_url);
if (empty($return_raw)) {
return false;
}
preg_match("/<body.*>(.*)<\/body>/smU", $return_raw, $return);
$parts = explode(",", $return[1], 7);
Debug::print_r($parts);
return [
[
'title' => $parts[6],
'bitrate' => $parts[5],
'listenurl' => $remote_url . '/;stream.nsv',
'server_type' => 'audio/mpeg',
'listeners' => $this->getListenerCount((int)$parts[4], (int)$parts[0]),
]
];
break;
case 'shoutcast2':
$remote_stats_url = $remote_url . '/stats';
$return_raw = $this->getUrl($remote_stats_url);
if (empty($return_raw)) {
return false;
}
$current_data = \App\Export::xml_to_array($return_raw);
$song_data = $current_data['SHOUTCASTSERVER'];
Debug::print_r($song_data);
return [
[
'title' => $song_data['SONGTITLE'],
'bitrate' => $song_data['BITRATE'],
'listenurl' => $remote_url . $song_data['STREAMPATH'],
'server_type' => $song_data['CONTENT'],
'listeners' => $this->getListenerCount((int)$song_data['UNIQUELISTENERS'],
(int)$song_data['CURRENTLISTENERS']),
]
];
break;
return $this->getPublicUrl().'/admin.cgi';
break;
}
return false;
@ -157,81 +203,21 @@ class Remote extends FrontendAbstract
$remote_url = rtrim($settings['remote_url'], '/');
switch ($settings['remote_type']) {
switch($settings['remote_type'])
{
case 'icecast':
return str_replace('/status-json.xsl', '', $remote_url);
break;
break;
case 'shoutcast1':
return str_replace('/7.html', '', $remote_url);
break;
break;
case 'shoutcast2':
return str_replace('/stats', '', $remote_url);
break;
break;
}
return $remote_url;
}
public function getStreamUrls()
{
$mounts = $this->_getMounts();
if (empty($mounts)) {
return false;
}
return \Packaged\Helpers\Arrays::ipull($mounts, 'listenurl');
}
public function getUrlForMount($mount_name)
{
return $this->getPublicUrl() . $mount_name;
}
public function getAdminUrl()
{
$settings = (array)$this->station->frontend_config;
switch ($settings['remote_type']) {
case 'icecast':
return $this->getPublicUrl() . '/admin/';
break;
case 'shoutcast1':
case 'shoutcast2':
return $this->getPublicUrl() . '/admin.cgi';
break;
}
return false;
}
protected function _getNowPlaying(&$np)
{
$mounts = $this->_getMounts();
if (empty($mounts)) {
return false;
}
$default_mount = $mounts[0];
if (isset($default_mount['artist'])) {
$np['current_song'] = [
'artist' => $default_mount['artist'],
'title' => $default_mount['title'],
'text' => $default_mount['artist'] . ' - ' . $default_mount['title'],
];
} else {
$np['current_song'] = $this->getSongFromString($default_mount['title'], ' - ');
}
$np['meta']['status'] = 'online';
$np['meta']['bitrate'] = $default_mount['bitrate'];
$np['meta']['format'] = $default_mount['server_type'];
$np['listeners']['current'] = (int)$default_mount['listeners'];
return false;
}
}

View file

@ -3,149 +3,24 @@ namespace AzuraCast\Radio\Frontend;
use App\Debug;
use App\Utilities;
use Entity\Station;
use Doctrine\ORM\EntityManager;
class ShoutCast2 extends FrontendAbstract
{
protected $supports_mounts = false;
/* Process a nowplaying record. */
public function read()
{
$config = $this->_getConfig();
$this->station->frontend_config = $this->_loadFromConfig($config);
return true;
}
protected function _getConfig()
{
$config_dir = $this->station->getRadioConfigDir();
$config = @parse_ini_file($config_dir . '/sc_serv.conf', false, INI_SCANNER_RAW);
return $config;
}
protected function _loadFromConfig($config)
{
return [
'port' => $config['portbase'],
'source_pw' => $config['password'],
'admin_pw' => $config['adminpassword'],
];
}
/*
* Process Management
*/
public function write()
{
$config = $this->_getDefaults();
$frontend_config = (array)$this->station->frontend_config;
if (!empty($frontend_config['port'])) {
$config['portbase'] = $frontend_config['port'];
}
if (!empty($frontend_config['source_pw'])) {
$config['password'] = $frontend_config['source_pw'];
}
if (!empty($frontend_config['admin_pw'])) {
$config['adminpassword'] = $frontend_config['admin_pw'];
}
if (!empty($frontend_config['custom_config'])) {
$custom_conf = $this->_processCustomConfig($frontend_config['custom_config']);
if (!empty($custom_conf)) {
$config = array_merge($config, $custom_conf);
}
}
// Set any unset values back to the DB config.
$this->station->frontend_config = $this->_loadFromConfig($config);
$em = $this->di['em'];
$em->persist($this->station);
$em->flush();
$config_path = $this->station->getRadioConfigDir();
$sc_path = $config_path . '/sc_serv.conf';
$sc_file = '';
foreach ($config as $config_key => $config_value) {
$sc_file .= $config_key . '=' . str_replace("\n", "", $config_value) . "\n";
}
file_put_contents($sc_path, $sc_file);
}
protected function _getDefaults()
{
$config_path = $this->station->getRadioConfigDir();
$defaults = [
'password' => Utilities::generatePassword(),
'adminpassword' => Utilities::generatePassword(),
'logfile' => $config_path . '/sc_serv.log',
'w3clog' => $config_path . '/sc_w3c.log',
'publicserver' => 'never',
'banfile' => $config_path . '/sc_serv.ban',
'ripfile' => $config_path . '/sc_serv.rip',
'maxuser' => 500,
'portbase' => $this->_getRadioPort(),
];
return $defaults;
}
public function getCommand()
{
$config_path = $this->station->getRadioConfigDir();
$sc_binary = realpath(APP_INCLUDE_ROOT . '/..') . '/servers/sc_serv';
$sc_config = $config_path . '/sc_serv.conf';
return $sc_binary . ' ' . $sc_config;
}
public function getStreamUrl()
{
return $this->getUrlForMount('/stream/1/');
}
public function getUrlForMount($mount_name)
{
return $this->getPublicUrl() . $mount_name . '?' . time();
}
/*
* Configuration
*/
public function getStreamUrls()
{
return [$this->getUrlForMount('/stream/1/')];
}
public function getAdminUrl()
{
return $this->getPublicUrl() . '/admin.cgi';
}
protected function _getNowPlaying(&$np)
{
$fe_config = (array)$this->station->frontend_config;
$radio_port = $fe_config['port'];
$np_url = 'http://localhost:' . $radio_port . '/stats';
$np_url = 'http://localhost:'.$radio_port.'/stats';
$return_raw = $this->getUrl($np_url);
if (empty($return_raw)) {
if (empty($return_raw))
return false;
}
$current_data = \App\Export::xml_to_array($return_raw);
@ -163,12 +38,133 @@ class ShoutCast2 extends FrontendAbstract
$u_list = (int)$song_data['UNIQUELISTENERS'];
$t_list = (int)$song_data['CURRENTLISTENERS'];
$np['listeners'] = [
'current' => $this->getListenerCount($u_list, $t_list),
'unique' => $u_list,
'total' => $t_list,
];
$np['listeners'] = array(
'current' => $this->getListenerCount($u_list, $t_list),
'unique' => $u_list,
'total' => $t_list,
);
return true;
}
public function read()
{
$config = $this->_getConfig();
$this->station->frontend_config = $this->_loadFromConfig($config);
return true;
}
public function write()
{
$config = $this->_getDefaults();
$frontend_config = (array)$this->station->frontend_config;
if (!empty($frontend_config['port']))
$config['portbase'] = $frontend_config['port'];
if (!empty($frontend_config['source_pw']))
$config['password'] = $frontend_config['source_pw'];
if (!empty($frontend_config['admin_pw']))
$config['adminpassword'] = $frontend_config['admin_pw'];
if (!empty($frontend_config['custom_config']))
{
$custom_conf = $this->_processCustomConfig($frontend_config['custom_config']);
if (!empty($custom_conf))
$config = array_merge($config, $custom_conf);
}
// Set any unset values back to the DB config.
$this->station->frontend_config = $this->_loadFromConfig($config);
$em = $this->di['em'];
$em->persist($this->station);
$em->flush();
$config_path = $this->station->getRadioConfigDir();
$sc_path = $config_path.'/sc_serv.conf';
$sc_file = '';
foreach($config as $config_key => $config_value)
$sc_file .= $config_key.'='.str_replace("\n", "", $config_value)."\n";
file_put_contents($sc_path, $sc_file);
}
/*
* Process Management
*/
public function getCommand()
{
$config_path = $this->station->getRadioConfigDir();
$sc_binary = realpath(APP_INCLUDE_ROOT.'/..').'/servers/sc_serv';
$sc_config = $config_path.'/sc_serv.conf';
return $sc_binary.' '.$sc_config;
}
public function getStreamUrl()
{
return $this->getUrlForMount('/stream/1/');
}
public function getStreamUrls()
{
return [$this->getUrlForMount('/stream/1/')];
}
public function getUrlForMount($mount_name)
{
return $this->getPublicUrl().$mount_name.'?'.time();
}
public function getAdminUrl()
{
return $this->getPublicUrl().'/admin.cgi';
}
/*
* Configuration
*/
protected function _getConfig()
{
$config_dir = $this->station->getRadioConfigDir();
$config = @parse_ini_file($config_dir.'/sc_serv.conf', false, INI_SCANNER_RAW);
return $config;
}
protected function _loadFromConfig($config)
{
return [
'port' => $config['portbase'],
'source_pw' => $config['password'],
'admin_pw' => $config['adminpassword'],
];
}
protected function _getDefaults()
{
$config_path = $this->station->getRadioConfigDir();
$defaults = [
'password' => Utilities::generatePassword(),
'adminpassword' => Utilities::generatePassword(),
'logfile' => $config_path.'/sc_serv.log',
'w3clog' => $config_path.'/sc_w3c.log',
'publicserver' => 'never',
'banfile' => $config_path.'/sc_serv.ban',
'ripfile' => $config_path.'/sc_serv.rip',
'maxuser' => 500,
'portbase' => $this->_getRadioPort(),
];
return $defaults;
}
}

View file

@ -3,8 +3,8 @@ namespace AzuraCast;
use App\Debug;
use Entity\Settings;
use Entity\SettingsRepository;
use Interop\Container\ContainerInterface;
use Entity\SettingsRepository;
/**
* The runner of scheduled synchronization tasks.
@ -30,6 +30,23 @@ class Sync
$this->settings = $di['em']->getRepository(Settings::class);
}
protected function _initSync($script_timeout = 60)
{
// Immediately halt if setup is not complete.
if ($this->settings->getSetting('setup_complete', 0) == 0)
die('Setup not complete; halting synchronized task.');
set_time_limit($script_timeout);
ini_set('memory_limit', '256M');
if (APP_IS_COMMAND_LINE)
{
error_reporting(E_ALL & ~E_STRICT & ~E_NOTICE);
ini_set('display_errors', 1);
ini_set('log_errors', 1);
}
}
/**
* Now-Playing Synchronization
* The most frequent sync process, which must be optimized for speed,
@ -45,15 +62,14 @@ class Sync
$last_start = $this->settings->getSetting('nowplaying_last_started', 0);
$last_end = $this->settings->getSetting('nowplaying_last_run', 0);
if ($last_start > $last_end && $last_start >= (time() - 10) && !$force) {
if ($last_start > $last_end && $last_start >= (time() - 10) && !$force)
return;
}
// Sync schedules.
$this->settings->setSetting('nowplaying_last_started', time());
// Run Now Playing data for radio streams.
Debug::runTimer('Run NowPlaying update', function () {
Debug::runTimer('Run NowPlaying update', function() {
$task = new Sync\NowPlaying($this->di);
$task->run();
});
@ -61,23 +77,6 @@ class Sync
$this->settings->setSetting('nowplaying_last_run', time());
}
protected function _initSync($script_timeout = 60)
{
// Immediately halt if setup is not complete.
if ($this->settings->getSetting('setup_complete', 0) == 0) {
die('Setup not complete; halting synchronized task.');
}
set_time_limit($script_timeout);
ini_set('memory_limit', '256M');
if (APP_IS_COMMAND_LINE) {
error_reporting(E_ALL & ~E_STRICT & ~E_NOTICE);
ini_set('display_errors', 1);
ini_set('log_errors', 1);
}
}
/**
* Short Synchronization
* This task runs automatically every minute.
@ -88,7 +87,7 @@ class Sync
{
$this->_initSync(60);
Debug::runTimer('Handle pending song requests', function () {
Debug::runTimer('Handle pending song requests', function() {
$task = new Sync\RadioRequests($this->di);
$task->run();
});
@ -107,7 +106,7 @@ class Sync
$this->_initSync(300);
// Sync uploaded media.
Debug::runTimer('Run radio station track sync', function () {
Debug::runTimer('Run radio station track sync', function() {
$task = new Sync\Media($this->di);
$task->run();
});
@ -134,19 +133,19 @@ class Sync
$this->_initSync(1800);
// Sync analytical and statistical data (long running).
Debug::runTimer('Run analytics manager', function () {
Debug::runTimer('Run analytics manager', function() {
$task = new Sync\Analytics($this->di);
$task->run();
});
// Run automated playlist assignment.
Debug::runTimer('Run automated playlist assignment', function () {
Debug::runTimer('Run automated playlist assignment', function() {
$task = new Sync\RadioAutomation($this->di);
$task->run();
});
// Clean up old song history entries.
Debug::runTimer('Run song history cleanup', function () {
Debug::runTimer('Run song history cleanup', function() {
$task = new Sync\HistoryCleanup($this->di);
$task->run();
});
@ -190,10 +189,11 @@ class Sync
],
];
foreach ($syncs as $sync_key => $sync_info) {
foreach ($syncs as $sync_key => $sync_info)
{
$sync_latest = $sync_info['latest'];
$syncs[$sync_key]['diff'] = time() - $sync_latest;
$syncs[$sync_key]['diff'] = time()-$sync_latest;
$syncs[$sync_key]['diff_text'] = \App\Utilities::timeDifferenceText($sync_latest, time());
}

View file

@ -23,36 +23,37 @@ class Analytics extends SyncAbstract
]);
$results_raw = $resultset->getSeries();
$results = [];
foreach ($results_raw as $serie) {
$results = array();
foreach($results_raw as $serie)
{
$points = [];
foreach ($serie['values'] as $point) {
foreach ($serie['values'] as $point)
$points[] = array_combine($serie['columns'], $point);
}
$results[$serie['name']] = $points;
}
$new_records = [];
$new_records = array();
$earliest_timestamp = time();
foreach ($results as $stat_series => $stat_rows) {
foreach($results as $stat_series => $stat_rows)
{
$series_split = explode('.', $stat_series);
$station_id = ($series_split[1] == 'all') ? null : $series_split[1];
$station_id = ($series_split[1] == 'all') ? NULL : $series_split[1];
foreach ($stat_rows as $stat_row) {
if ($stat_row['time'] < $earliest_timestamp) {
foreach($stat_rows as $stat_row)
{
if ($stat_row['time'] < $earliest_timestamp)
$earliest_timestamp = $stat_row['time'];
}
$new_records[] = [
$new_records[] = array(
'station_id' => $station_id,
'type' => 'day',
'timestamp' => $stat_row['time'],
'number_min' => (int)$stat_row['min'],
'number_max' => (int)$stat_row['max'],
'number_avg' => round($stat_row['value']),
];
);
}
}
@ -60,7 +61,8 @@ class Analytics extends SyncAbstract
->setParameter('earliest', $earliest_timestamp)
->execute();
foreach ($new_records as $new_record) {
foreach($new_records as $new_record)
{
$row = new \Entity\Analytics;
$row->fromArray($em, $new_record);

View file

@ -1,11 +1,11 @@
<?php
namespace AzuraCast\Sync;
use Doctrine\ORM\EntityManager;
use Entity\Song;
use Entity\Station;
use Entity\StationMedia;
use Entity\StationPlaylist;
use Doctrine\ORM\EntityManager;
class Media extends SyncAbstract
{
@ -15,28 +15,26 @@ class Media extends SyncAbstract
$em = $this->di['em'];
$stations = $em->getRepository(Station::class)->findAll();
foreach ($stations as $station) {
foreach($stations as $station)
$this->importMusic($station);
}
}
public function importMusic(Station $station)
{
$base_dir = $station->getRadioMediaDir();
if (empty($base_dir)) {
if (empty($base_dir))
return;
}
$glob_formats = implode(',', StationMedia::getSupportedFormats());
$music_files_raw = $this->globDirectory($base_dir . '/*.{' . $glob_formats . '}', \GLOB_BRACE);
$music_files = [];
$music_files_raw = $this->globDirectory($base_dir.'/*.{'.$glob_formats.'}', \GLOB_BRACE);
$music_files = array();
foreach ($music_files_raw as $music_file_path) {
$path_short = str_replace($base_dir . '/', '', $music_file_path);
foreach($music_files_raw as $music_file_path)
{
$path_short = str_replace($base_dir.'/', '', $music_file_path);
if (substr($path_short, 0, strlen('not-processed')) == 'not-processed') {
if (substr($path_short, 0, strlen('not-processed')) == 'not-processed')
continue;
}
$path_hash = md5($path_short);
$music_files[$path_hash] = $path_short;
@ -45,20 +43,24 @@ class Media extends SyncAbstract
$em = $this->di['em'];
$existing_media = $station->media;
foreach ($existing_media as $media_row) {
foreach($existing_media as $media_row)
{
// Check if media file still exists.
$full_path = $base_dir . '/' . $media_row->path;
if (file_exists($full_path)) {
$full_path = $base_dir.'/'.$media_row->path;
if (file_exists($full_path))
{
// Check for modifications.
try {
try
{
$song_info = $media_row->loadFromFile();
if (!empty($song_info)) {
if (!empty($song_info))
$media_row->song = $em->getRepository(Song::class)->getOrCreate($song_info);
}
$em->persist($media_row);
} catch (\App\Exception $e) {
}
catch(\App\Exception $e)
{
$media_row->moveToNotProcessed();
$em->remove($media_row);
@ -66,26 +68,31 @@ class Media extends SyncAbstract
$path_hash = md5($media_row->path);
unset($music_files[$path_hash]);
} else {
}
else
{
// Delete the now-nonexistent media item.
$em->remove($media_row);
}
}
// Create files that do not currently exist.
foreach ($music_files as $new_file_path) {
foreach($music_files as $new_file_path)
{
$media_row = new StationMedia;
$media_row->station = $station;
$media_row->path = $new_file_path;
try {
try
{
$song_info = $media_row->loadFromFile();
if (!empty($song_info)) {
if (!empty($song_info))
$media_row->song = $em->getRepository(Song::class)->getOrCreate($song_info);
}
$em->persist($media_row);
} catch (\Exception $e) {
}
catch(\Exception $e)
{
$media_row->moveToNotProcessed();
}
}
@ -93,26 +100,16 @@ class Media extends SyncAbstract
$em->flush();
}
public function globDirectory($pattern, $flags = 0)
{
$files = (array)glob($pattern, $flags);
foreach (glob(dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
$files = array_merge($files, $this->globDirectory($dir . '/' . basename($pattern), $flags));
}
return $files;
}
public function importPlaylists(Station $station)
{
$base_dir = $station->getRadioPlaylistsDir();
if (empty($base_dir)) {
if (empty($base_dir))
return;
}
// Create a lookup cache of all valid imported media.
$media_lookup = [];
foreach ($station->media as $media) {
$media_lookup = array();
foreach($station->media as $media)
{
$media_path = $media->getFullPath();
$media_hash = md5($media_path);
@ -122,9 +119,10 @@ class Media extends SyncAbstract
// Iterate through playlists.
$em = $this->di['em'];
$playlist_files_raw = $this->globDirectory($base_dir . '/*.{m3u,pls}', \GLOB_BRACE);
$playlist_files_raw = $this->globDirectory($base_dir.'/*.{m3u,pls}', \GLOB_BRACE);
foreach ($playlist_files_raw as $playlist_file_path) {
foreach($playlist_files_raw as $playlist_file_path)
{
// Create new StationPlaylist record.
$record = new StationPlaylist;
$record->station = $station;
@ -137,15 +135,17 @@ class Media extends SyncAbstract
$playlist_lines = explode("\n", $playlist_file);
$em->persist($record);
foreach ($playlist_lines as $line_raw) {
foreach($playlist_lines as $line_raw)
{
$line = trim($line_raw);
if (substr($line, 0, 1) == '#' || empty($line)) {
if (substr($line, 0, 1) == '#' || empty($line))
continue;
}
if (file_exists($line)) {
if (file_exists($line))
{
$line_hash = md5($line);
if (isset($media_lookup[$line_hash])) {
if (isset($media_lookup[$line_hash]))
{
$media_record = $media_lookup[$line_hash];
$media_record->playlists->add($record);
@ -161,4 +161,13 @@ class Media extends SyncAbstract
$em->flush();
}
public function globDirectory($pattern, $flags = 0)
{
$files = (array)glob($pattern, $flags);
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir)
$files = array_merge($files, $this->globDirectory($dir.'/'.basename($pattern), $flags));
return $files;
}
}

View file

@ -1,11 +1,12 @@
<?php
namespace AzuraCast\Sync;
use App\Debug;
use Doctrine\ORM\EntityManager;
use Entity\Station;
use Entity\Song;
use Entity\SongHistory;
use Entity\Station;
use Entity\Settings;
use App\Debug;
class NowPlaying extends SyncAbstract
{
@ -17,18 +18,19 @@ class NowPlaying extends SyncAbstract
// Post statistics to InfluxDB.
$influx = $this->di->get('influx');
$influx_points = [];
$influx_points = array();
$total_overall = 0;
foreach ($nowplaying as $short_code => $info) {
foreach($nowplaying as $short_code => $info)
{
$listeners = (int)$info['listeners']['current'];
$total_overall += $listeners;
$station_id = $info['station']['id'];
$influx_points[] = new \InfluxDB\Point(
'station.' . $station_id . '.listeners',
'station.'.$station_id.'.listeners',
$listeners,
[],
['station' => $station_id],
@ -47,16 +49,14 @@ class NowPlaying extends SyncAbstract
$influx->writePoints($influx_points, \InfluxDB\Database::PRECISION_SECONDS);
// Generate PVL API cache.
foreach ($nowplaying as $station => $np_info) {
foreach($nowplaying as $station => $np_info)
$nowplaying[$station]['cache'] = 'hit';
}
$cache = $this->di->get('cache');
$cache->save($nowplaying, 'api_nowplaying_data', ['nowplaying'], 60);
$cache->save($nowplaying, 'api_nowplaying_data', array('nowplaying'), 60);
foreach ($nowplaying as $station => $np_info) {
foreach($nowplaying as $station => $np_info)
$nowplaying[$station]['cache'] = 'database';
}
$this->di['em']->getRepository('Entity\Settings')->setSetting('nowplaying', $nowplaying);
}
@ -69,9 +69,10 @@ class NowPlaying extends SyncAbstract
$em = $this->di['em'];
$stations = $em->getRepository(Station::class)->findAll();
$nowplaying = [];
$nowplaying = array();
foreach ($stations as $station) {
foreach($stations as $station)
{
Debug::startTimer($station->name);
// $name = $station->short_name;
@ -99,27 +100,33 @@ class NowPlaying extends SyncAbstract
$np_old = (array)$station->nowplaying_data;
$np = [];
$np = array();
$np['station'] = Station::api($station, $this->di);
$frontend_adapter = $station->getFrontendAdapter($this->di);
$np_new = $frontend_adapter->getNowPlaying();
$np = array_merge($np, $np_new);
$np['listeners'] = $np_new['listeners'];
// Pull from current NP data if song details haven't changed.
$current_song_hash = Song::getSongHash($np_new['current_song']);
if (empty($np['current_song']['text'])) {
$np['current_song'] = [];
if (empty($np['current_song']['text']))
{
$np['current_song'] = array();
$np['song_history'] = $em->getRepository(SongHistory::class)->getHistoryForStation($station);
} else {
if (strcmp($current_song_hash, $np_old['current_song']['id']) == 0) {
}
else
{
if (strcmp($current_song_hash, $np_old['current_song']['id']) == 0)
{
$np['song_history'] = $np_old['song_history'];
$song_obj = $em->getRepository(Song::class)->find($current_song_hash);
} else {
}
else
{
$np['song_history'] = $em->getRepository(SongHistory::class)->getHistoryForStation($station);
$song_obj = $em->getRepository(Song::class)->getOrCreate($np_new['current_song'], true);

View file

@ -2,7 +2,9 @@
namespace AzuraCast\Sync;
use App\Exception;
use App\Utilities;
use Doctrine\ORM\EntityManager;
use Entity\Settings;
use Entity\Station;
class RadioAutomation extends SyncAbstract
@ -20,15 +22,18 @@ class RadioAutomation extends SyncAbstract
// Check all stations for automation settings.
$stations = $em->getRepository(Station::class)->findAll();
$automation_log = $this->di['em']->getRepository('Entity\Settings')->getSetting('automation_log', []);
$automation_log = $this->di['em']->getRepository('Entity\Settings')->getSetting('automation_log', array());
foreach ($stations as $station) {
try {
if ($this->runStation($station)) {
$automation_log[$station->id] = $station->name . ': SUCCESS';
}
} catch (Exception $e) {
$automation_log[$station->id] = $station->name . ': ERROR - ' . $e->getMessage();
foreach($stations as $station)
{
try
{
if ($this->runStation($station))
$automation_log[$station->id] = $station->name.': SUCCESS';
}
catch(Exception $e)
{
$automation_log[$station->id] = $station->name.': ERROR - '.$e->getMessage();
}
}
@ -50,35 +55,34 @@ class RadioAutomation extends SyncAbstract
$settings = (array)$station->automation_settings;
if (empty($settings)) {
if (empty($settings))
throw new Exception('Automation has not been configured for this station yet.');
}
if (!$settings['is_enabled']) {
if (!$settings['is_enabled'])
throw new Exception('Automation is not enabled for this station.');
}
// Check whether assignment needs to be run.
$threshold_days = (int)$settings['threshold_days'];
$threshold = time() - (86400 * $threshold_days);
$threshold = time()-(86400 * $threshold_days);
if (!$force && $station->automation_timestamp >= $threshold) {
return false;
} // No error, but no need to run assignment.
if (!$force && $station->automation_timestamp >= $threshold)
return false; // No error, but no need to run assignment.
$playlists = [];
$original_playlists = [];
$playlists = array();
$original_playlists = array();
// Related playlists are already automatically sorted by weight.
$i = 0;
foreach ($station->playlists as $playlist) {
foreach($station->playlists as $playlist)
{
if ($playlist->is_enabled &&
$playlist->type == 'default' &&
$playlist->include_in_automation == true
) {
$playlist->include_in_automation == true)
{
// Clear all related media.
foreach ($playlist->media as $media) {
foreach($playlist->media as $media)
{
$original_playlists[$media->song_id][] = $i;
$media->playlists->removeElement($playlist);
@ -91,36 +95,34 @@ class RadioAutomation extends SyncAbstract
}
}
if (count($playlists) == 0) {
if (count($playlists) == 0)
throw new Exception('No playlists have automation enabled.');
}
$em->flush();
$media_report = $this->generateReport($station, $threshold_days);
$media_report = array_filter($media_report, function ($media) use ($original_playlists) {
$media_report = array_filter($media_report, function($media) use ($original_playlists) {
// Remove songs that are already in non-auto-assigned playlists.
if (!empty($media['playlists'])) {
if (!empty($media['playlists']))
return false;
}
// Remove songs that weren't already in auto-assigned playlists.
if (!isset($original_playlists[$media['song_id']])) {
if (!isset($original_playlists[$media['song_id']]))
return false;
}
return true;
});
// Place all songs with 0 plays back in their original playlists.
foreach ($media_report as $song_id => $media) {
if ($media['num_plays'] == 0 && isset($original_playlists[$song_id])) {
foreach($media_report as $song_id => $media)
{
if ($media['num_plays'] == 0 && isset($original_playlists[$song_id]))
{
$media_row = $media['record'];
foreach ($original_playlists[$song_id] as $playlist_key) {
foreach($original_playlists[$song_id] as $playlist_key)
$media_row->playlists->add($playlists[$playlist_key]);
}
$em->persist($media_row);
@ -131,7 +133,7 @@ class RadioAutomation extends SyncAbstract
$em->flush();
// Sort songs by ratio descending.
uasort($media_report, function ($a_media, $b_media) {
uasort($media_report, function($a_media, $b_media) {
$a = (int)$a_media['ratio'];
$b = (int)$b_media['ratio'];
@ -145,15 +147,16 @@ class RadioAutomation extends SyncAbstract
$i = 0;
foreach ($playlists as $playlist) {
if ($i == 0) {
foreach($playlists as $playlist)
{
if ($i == 0)
$playlist_num_songs = $songs_per_playlist + ($num_songs % $num_playlists);
} else {
else
$playlist_num_songs = $songs_per_playlist;
}
$media_in_playlist = array_slice($media_report, $i, $playlist_num_songs);
foreach ($media_in_playlist as $media) {
foreach($media_in_playlist as $media)
{
$media_row = $media['record'];
$media_row->playlists->add($playlist);
@ -184,7 +187,7 @@ class RadioAutomation extends SyncAbstract
/** @var EntityManager $em */
$em = $this->di['em'];
$threshold = strtotime('-' . (int)$threshold_days . ' days');
$threshold = strtotime('-'.(int)$threshold_days.' days');
// Pull all SongHistory data points.
$data_points_raw = $em->createQuery('SELECT sh.song_id, sh.timestamp_start, sh.delta_positive, sh.delta_negative, sh.listeners_start FROM Entity\SongHistory sh WHERE sh.station_id = :station_id AND sh.timestamp_end != 0 AND sh.timestamp_start >= :threshold')
@ -193,21 +196,20 @@ class RadioAutomation extends SyncAbstract
->getArrayResult();
$total_plays = 0;
$data_points = [];
$data_points_by_hour = [];
$data_points = array();
$data_points_by_hour = array();
foreach ($data_points_raw as $row) {
$total_plays++;
foreach($data_points_raw as $row)
{
$total_plays ++;
if (!isset($data_points[$row['song_id']])) {
if (!isset($data_points[$row['song_id']]))
$data_points[$row['song_id']] = [];
}
$row['hour'] = date('H', $row['timestamp_start']);
if (!isset($totals_by_hour[$row['hour']])) {
$data_points_by_hour[$row['hour']] = [];
}
if (!isset($totals_by_hour[$row['hour']]))
$data_points_by_hour[$row['hour']] = array();
$data_points_by_hour[$row['hour']][] = $row;
@ -237,21 +239,22 @@ class RadioAutomation extends SyncAbstract
->setParameter('station_id', $station->id)
->execute();
$report = [];
$report = array();
foreach ($media_raw as $row) {
$media = [
'song_id' => $row['song_id'],
'record' => $row,
foreach($media_raw as $row)
{
$media = array(
'song_id' => $row['song_id'],
'record' => $row,
'title' => $row['title'],
'artist' => $row['artist'],
'title' => $row['title'],
'artist' => $row['artist'],
'length_raw' => $row['length'],
'length' => $row['length_text'],
'path' => $row['path'],
'length' => $row['length_text'],
'path' => $row['path'],
'playlists' => [],
'data_points' => [],
'playlists' => array(),
'data_points' => array(),
'num_plays' => 0,
'percent_plays' => 0,
@ -261,19 +264,21 @@ class RadioAutomation extends SyncAbstract
'delta_total' => 0,
'ratio' => 0,
];
);
if (!empty($row['playlists'])) {
foreach ($row['playlists'] as $playlist) {
if (!empty($row['playlists']))
{
foreach($row['playlists'] as $playlist)
$media['playlists'][] = $playlist['name'];
}
}
if (isset($data_points[$row['song_id']])) {
$ratio_points = [];
if (isset($data_points[$row['song_id']]))
{
$ratio_points = array();
foreach ($data_points[$row['song_id']] as $data_row) {
$media['num_plays']++;
foreach($data_points[$row['song_id']] as $data_row)
{
$media['num_plays'] ++;
$media['delta_positive'] += $data_row['delta_positive'];
$media['delta_negative'] -= $data_row['delta_negative'];
@ -288,7 +293,7 @@ class RadioAutomation extends SyncAbstract
}
$media['delta_total'] = $media['delta_positive'] + $media['delta_negative'];
$media['percent_plays'] = round(($media['num_plays'] / $total_plays) * 100, 2);
$media['percent_plays'] = round(($media['num_plays'] / $total_plays)*100, 2);
$media['ratio'] = round(array_sum($ratio_points) / count($ratio_points), 3);
}

View file

@ -13,12 +13,12 @@ class RadioRequests extends SyncAbstract
$stations = $em->getRepository(Station::class)->findAll();
foreach ($stations as $station) {
foreach($stations as $station)
{
/** @var $station Station */
if (!$station->enable_requests) {
if (!$station->enable_requests)
continue;
}
$min_minutes = (int)$station->request_delay;
$threshold_minutes = $min_minutes + mt_rand(0, $min_minutes);
@ -35,7 +35,8 @@ class RadioRequests extends SyncAbstract
->setParameter('threshold', $threshold)
->execute();
foreach ($requests as $request) {
foreach ($requests as $request)
{
\App\Debug::log($station->name . ': Request to play ' . $request->track->artist . ' - ' . $request->track->title);
// Log the request as played.
@ -47,9 +48,8 @@ class RadioRequests extends SyncAbstract
// Send request to the station to play the request.
$backend = $station->getBackendAdapter($this->di);
if (method_exists($backend, 'request')) {
if (method_exists($backend, 'request'))
$backend->request($request->track->getFullPath());
}
}
}
}

View file

@ -17,7 +17,7 @@ class Version
public static function getVersionText()
{
return 'v' . APP_CORE_VERSION . ' ' . APP_CORE_RELEASE;
return 'v'.APP_CORE_VERSION.' '.APP_CORE_RELEASE;
}
}

View file

@ -19,18 +19,19 @@ class ApiController extends BaseController
{
$form = new \App\Form($this->config->forms->api_key);
if ($this->hasParam('id')) {
if ($this->hasParam('id'))
{
$id = $this->getParam('id');
$record = $this->em->getRepository(Record::class)->find($id);
$form->setDefaults($record->toArray($this->em, true, true));
$form->setDefaults($record->toArray($this->em, TRUE, TRUE));
}
if ($_POST && $form->isValid($_POST)) {
if($_POST && $form->isValid($_POST) )
{
$data = $form->getValues();
if (!($record instanceof Record)) {
if (!($record instanceof Record))
$record = new Record;
}
$record->fromArray($this->em, $data);
@ -39,7 +40,7 @@ class ApiController extends BaseController
$this->alert(_('Changes saved.'), 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => null]);
return $this->redirectFromHere(array('action' => 'index', 'id' => NULL));
}
return $this->renderForm($form, 'edit', _('Edit Record'));
@ -49,13 +50,12 @@ class ApiController extends BaseController
{
$record = $this->em->getRepository(Record::class)->find($this->getParam('id'));
if ($record instanceof Record) {
if ($record instanceof Record)
$this->em->remove($record);
}
$this->em->flush();
$this->alert(_('Record deleted.'), 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => null, 'csrf' => null]);
return $this->redirectFromHere(array('action' => 'index', 'id' => NULL, 'csrf' => NULL));
}
}

View file

@ -10,28 +10,26 @@ class BaseController extends \AzuraCast\Mvc\Controller
// Load dashboard.
$panels = $this->config->admin->dashboard->toArray();
foreach ($panels as $sidebar_category => &$sidebar_info) {
foreach ($sidebar_info['items'] as $item_name => $item_params) {
foreach($panels as $sidebar_category => &$sidebar_info)
{
foreach($sidebar_info['items'] as $item_name => $item_params)
{
$permission = $item_params['permission'];
if (!is_bool($permission)) {
if (!is_bool($permission))
$permission = $this->di['acl']->isAllowed($permission);
}
if (!$permission) {
if (!$permission)
unset($sidebar_info['items'][$item_name]);
}
}
if (empty($sidebar_info['items'])) {
if (empty($sidebar_info['items']))
unset($panels[$sidebar_category]);
}
}
$this->view->admin_panels = $panels;
if (!($this->controller == 'index' && $this->action == 'index')) {
if (!($this->controller == 'index' && $this->action == 'index'))
$this->view->sidebar = $this->view->fetch('common::sidebar');
}
return true;
}

View file

@ -9,7 +9,8 @@ class IndexController extends BaseController
public function indexAction()
{
// Synchronization statuses
if ($this->acl->isAllowed('administer all')) {
if ($this->acl->isAllowed('administer all'))
{
/** @var \AzuraCast\Sync $sync */
$sync = $this->di['sync'];
$this->view->sync_times = $sync->getSyncTimes();
@ -24,7 +25,7 @@ class IndexController extends BaseController
ob_start();
\App\Debug::setEchoMode(true);
\App\Debug::setEchoMode(TRUE);
\App\Debug::startTimer('sync_task');
$type = $this->getParam('type', 'nowplaying');
@ -32,18 +33,19 @@ class IndexController extends BaseController
/** @var \AzuraCast\Sync $sync */
$sync = $this->di['sync'];
switch ($type) {
switch($type)
{
case "long":
$sync->syncLong();
break;
break;
case "medium":
$sync->syncMedium();
break;
break;
case "short":
$sync->syncShort();
break;
break;
case "nowplaying":
default:
@ -51,7 +53,7 @@ class IndexController extends BaseController
define('NOWPLAYING_SEGMENT', $segment);
$sync->syncNowplaying(true);
break;
break;
}
\App\Debug::endTimer('sync_task');

View file

@ -10,7 +10,7 @@ class PermissionsController extends BaseController
{
return $this->acl->isAllowed('administer permissions');
}
public function indexAction()
{
$all_roles = $this->em->createQuery('SELECT r, rp, s FROM Entity\Role r LEFT JOIN r.users u LEFT JOIN r.permissions rp LEFT JOIN rp.station s ORDER BY r.id ASC')
@ -18,16 +18,17 @@ class PermissionsController extends BaseController
$roles = [];
foreach ($all_roles as $role) {
foreach($all_roles as $role)
{
$role['permissions_global'] = [];
$role['permissions_station'] = [];
foreach ($role['permissions'] as $permission) {
if ($permission['station']) {
foreach($role['permissions'] as $permission)
{
if ($permission['station'])
$role['permissions_station'][$permission['station']['name']][] = $permission['action_name'];
} else {
else
$role['permissions_global'][] = $permission['action_name'];
}
}
$roles[] = $role;
@ -47,8 +48,9 @@ class PermissionsController extends BaseController
public function editAction()
{
$form = new \App\Form($this->config->forms->role->toArray());
if ($this->hasParam('id')) {
if ($this->hasParam('id'))
{
$record = $this->em->getRepository(Role::class)->find($this->getParam('id'));
$record_info = $record->toArray($this->em, true, true);
@ -57,12 +59,12 @@ class PermissionsController extends BaseController
$form->setDefaults(array_merge($record_info, $actions));
}
if (!empty($_POST) && $form->isValid($_POST)) {
if( !empty($_POST) && $form->isValid($_POST) )
{
$data = $form->getValues();
if (!($record instanceof Role)) {
if (!($record instanceof Role))
$record = new Role;
}
$record->fromArray($this->em, $data);
@ -71,8 +73,8 @@ class PermissionsController extends BaseController
$this->em->getRepository(RolePermission::class)->setActionsForRole($record, $data);
$this->alert('<b>' . _('Record updated.') . '</b>', 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => null, 'csrf' => null]);
$this->alert('<b>'._('Record updated.').'</b>', 'green');
return $this->redirectFromHere(array('action' => 'index', 'id' => NULL, 'csrf' => NULL));
}
return $this->renderForm($form, 'edit', _('Edit Record'));
@ -81,13 +83,12 @@ class PermissionsController extends BaseController
public function deleteAction()
{
$record = $this->em->getRepository(Role::class)->find($this->getParam('id'));
if ($record instanceof Role) {
if ($record instanceof Role)
$this->em->remove($record);
}
$this->em->flush();
$this->alert('<b>' . _('Record deleted.') . '</b>', 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => null, 'csrf' => null]);
$this->alert('<b>'._('Record deleted.').'</b>', 'green');
return $this->redirectFromHere(array('action' => 'index', 'id' => NULL, 'csrf' => NULL));
}
}

View file

@ -1,8 +1,8 @@
<?php
namespace Controller\Admin;
use Entity\Repository;
use Entity\Settings;
use Entity\Repository;
class SettingsController extends BaseController
{
@ -10,7 +10,7 @@ class SettingsController extends BaseController
{
return $this->acl->isAllowed('administer settings');
}
public function indexAction()
{
/** @var Repository\SettingsRepository $settings_repo */
@ -18,10 +18,11 @@ class SettingsController extends BaseController
$form = new \App\Form($this->config->forms->settings->form);
$existing_settings = $settings_repo->fetchArray(false);
$existing_settings = $settings_repo->fetchArray(FALSE);
$form->setDefaults($existing_settings);
if (!empty($_POST) && $form->isValid($_POST)) {
if (!empty($_POST) && $form->isValid($_POST))
{
$data = $form->getValues();
$settings_repo->setSettings($data);

View file

@ -1,6 +1,7 @@
<?php
namespace Controller\Admin;
use Entity\Station;
use Entity\Station as Record;
class StationsController extends BaseController
@ -9,30 +10,35 @@ class StationsController extends BaseController
{
return $this->acl->isAllowed('administer stations');
}
public function indexAction()
{
$this->view->stations = $this->em->createQuery('SELECT s FROM Entity\Station s ORDER BY s.name ASC')
->getArrayResult();
}
public function editAction()
{
$form = new \App\Form($this->config->forms->station);
if ($this->hasParam('id')) {
if ($this->hasParam('id'))
{
$id = (int)$this->getParam('id');
$record = $this->em->getRepository(Record::class)->find($id);
$form->setDefaults($record->toArray($this->em, false, true));
$form->setDefaults($record->toArray($this->em, FALSE, TRUE));
}
if ($_POST && $form->isValid($_POST)) {
if($_POST && $form->isValid($_POST) )
{
$data = $form->getValues();
if (!($record instanceof Record)) {
if (!($record instanceof Record))
{
$station_repo = $this->em->getRepository(Record::class);
$station_repo->create($data, $this->di);
} else {
}
else
{
$record->fromArray($this->em, $data);
$this->em->persist($record);
@ -44,16 +50,17 @@ class StationsController extends BaseController
$cache->remove('stations');
$this->alert(_('Changes saved.'), 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => null]);
return $this->redirectFromHere(array('action' => 'index', 'id' => NULL));
}
$this->view->form = $form;
}
public function deleteAction()
{
$record = $this->em->getRepository(Record::class)->find($this->getParam('id'));
if ($record) {
if ($record)
{
$ba = $record->getBackendAdapter($this->di);
$fa = $record->getFrontendAdapter($this->di);
@ -63,8 +70,8 @@ class StationsController extends BaseController
$this->em->remove($record);
$this->em->flush();
}
$this->alert(_('Record deleted.'), 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => null, 'csrf' => null]);
return $this->redirectFromHere(array('action' => 'index', 'id' => NULL, 'csrf' => NULL));
}
}

View file

@ -1,7 +1,8 @@
<?php
namespace Controller\Admin;
use Entity\User;
use Entity\Role;
use \Entity\User;
class UsersController extends BaseController
{
@ -12,16 +13,18 @@ class UsersController extends BaseController
public function indexAction()
{
if ($_GET) {
if ($_GET)
$this->redirectFromHere($_GET);
}
if ($this->hasParam('q')) {
if ($this->hasParam('q'))
{
$this->view->q = $q = trim($this->getParam('q'));
$query = $this->em->createQuery('SELECT u, r FROM Entity\User u LEFT JOIN u.roles r WHERE (u.name LIKE :query OR u.email LIKE :query) ORDER BY u.name ASC')
->setParameter('query', '%' . $q . '%');
} else {
->setParameter('query', '%'.$q.'%');
}
else
{
$query = $this->em->createQuery('SELECT u, r FROM Entity\User u LEFT JOIN u.roles r ORDER BY u.name ASC');
}
@ -32,30 +35,31 @@ class UsersController extends BaseController
{
$form_config = $this->config->forms->user->form->toArray();
$form = new \App\Form($form_config);
if ($this->hasParam('id')) {
if ($this->hasParam('id'))
{
$record = $this->em->getRepository(User::class)->find($this->getParam('id'));
$record_defaults = $record->toArray($this->em, true, true);
$record_defaults = $record->toArray($this->em, TRUE, TRUE);
unset($record_defaults['auth_password']);
$form->setDefaults($record_defaults);
}
if (!empty($_POST) && $form->isValid($_POST)) {
if(!empty($_POST) && $form->isValid($_POST))
{
$data = $form->getValues();
if (!($record instanceof User)) {
if (!($record instanceof User))
$record = new User;
}
$record->fromArray($this->em, $data);
$this->em->persist($record);
$this->em->flush();
$this->alert(_('Record updated.'), 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => null]);
return $this->redirectFromHere(array('action' => 'index', 'id' => NULL));
}
return $this->renderForm($form, 'edit', _('Edit Record'));
@ -66,14 +70,13 @@ class UsersController extends BaseController
$id = (int)$this->getParam('id');
$user = $this->em->getRepository(User::class)->find($id);
if ($user instanceof User) {
if ($user instanceof User)
$this->em->remove($user);
}
$this->em->flush();
$this->alert('<b>' . _('Record deleted.') . '</b>', 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => null]);
$this->alert('<b>'._('Record deleted.').'</b>', 'green');
return $this->redirectFromHere(array('action' => 'index', 'id' => NULL));
}
public function impersonateAction()
@ -81,14 +84,13 @@ class UsersController extends BaseController
$id = (int)$this->getParam('id');
$user = $this->em->getRepository(User::class)->find($id);
if (!($user instanceof User)) {
if (!($user instanceof User))
throw new \App\Exception(_('Record not found!'));
}
// Set new identity in Zend_Auth
$this->auth->masqueradeAsUser($user);
$this->alert('<b>' . _('Logged in successfully.') . '</b><br>' . $user->email, 'green');
$this->alert('<b>'._('Logged in successfully.').'</b><br>'.$user->email, 'green');
return $this->redirectHome();
}
}

View file

@ -5,13 +5,13 @@ use Entity\ApiKey;
class BaseController extends \AzuraCast\Mvc\Controller
{
protected $_time_start;
public function permissions()
{
return true;
}
protected $_time_start;
public function preDispatch()
{
parent::preDispatch();
@ -41,11 +41,10 @@ class BaseController extends \AzuraCast\Mvc\Controller
$request_time = $end_time - $this->_time_start;
// Log request using a raw SQL query for higher performance.
if (isset($_SERVER['CF-Connecting-IP'])) {
if (isset($_SERVER['CF-Connecting-IP']))
$remote_ip = $_SERVER['CF-Connecting-IP'];
} else {
else
$remote_ip = $_SERVER['REMOTE_ADDR'];
}
$params = array_merge((array)$this->dispatcher->getParams(), (array)$this->request->getQuery());
@ -79,9 +78,8 @@ class BaseController extends \AzuraCast\Mvc\Controller
*/
public function requireKey()
{
if (!$this->authenticate()) {
if (!$this->authenticate())
throw new \App\Exception\PermissionDenied('No valid API key specified.');
}
}
/**
@ -91,22 +89,21 @@ class BaseController extends \AzuraCast\Mvc\Controller
*/
public function authenticate()
{
if (isset($_SERVER['X-API-Key'])) {
if (isset($_SERVER['X-API-Key']))
$key = $_SERVER['X-API-Key'];
} elseif ($this->hasParam('key')) {
elseif ($this->hasParam('key'))
$key = $this->getParam('key');
} else {
else
return false;
}
if (empty($key)) {
if (empty($key))
return false;
}
$record = $this->em->getRepository(ApiKey::class)->find($key);
// $record = self::find($key);
if ($record instanceof ApiKey) {
if ($record instanceof ApiKey)
{
$record->calls_made++;
$this->em->persist($record);
@ -123,21 +120,30 @@ class BaseController extends \AzuraCast\Mvc\Controller
public function returnSuccess($data)
{
return $this->returnToScreen([
'status' => 'success',
'result' => $data,
]);
return $this->returnToScreen(array(
'status' => 'success',
'result' => $data,
));
}
public function returnError($message, $error_code = 400)
{
$this->response = $this->response->withStatus($error_code);
return $this->returnToScreen(array(
'status' => 'error',
'error' => $message,
));
}
public function returnToScreen($obj)
{
$format = strtolower($this->getParam('format', 'json'));
if ($format == 'xml') {
if ($format == 'xml')
return $this->returnRaw(\App\Export::array_to_xml($obj), 'xml');
} else {
else
return $this->returnRaw(json_encode($obj, \JSON_UNESCAPED_SLASHES), 'json');
}
}
public function returnRaw($message, $format = 'json')
@ -148,14 +154,4 @@ class BaseController extends \AzuraCast\Mvc\Controller
$this->response->getBody()->write($message);
return $this->response;
}
public function returnError($message, $error_code = 400)
{
$this->response = $this->response->withStatus($error_code);
return $this->returnToScreen([
'status' => 'error',
'error' => $message,
]);
}
}

View file

@ -8,7 +8,7 @@ class IndexController extends BaseController
*/
public function indexAction()
{
return $this->returnSuccess('The ' . $this->config->application->name . ' API is online and functioning.');
return $this->returnSuccess('The '.$this->config->application->name.' API is online and functioning.');
}
/**
@ -16,10 +16,10 @@ class IndexController extends BaseController
*/
public function statusAction()
{
return $this->returnSuccess([
return $this->returnSuccess(array(
'online' => 'true',
'timestamp' => time(),
]);
));
}
/**
@ -31,20 +31,20 @@ class IndexController extends BaseController
$tz_info = \App\Timezone::getInfo();
return $this->returnSuccess([
'timestamp' => time(),
return $this->returnSuccess(array(
'timestamp' => time(),
'gmt_datetime' => $tz_info['now_utc']->format('Y-m-d g:i:s'),
'gmt_date' => $tz_info['now_utc']->format('F j, Y'),
'gmt_time' => $tz_info['now_utc']->format('g:ia'),
'gmt_timezone' => 'GMT',
'gmt_timezone_abbr' => 'GMT',
'gmt_datetime' => $tz_info['now_utc']->format('Y-m-d g:i:s'),
'gmt_date' => $tz_info['now_utc']->format('F j, Y'),
'gmt_time' => $tz_info['now_utc']->format('g:ia'),
'gmt_timezone' => 'GMT',
'gmt_timezone_abbr' => 'GMT',
'local_datetime' => $tz_info['now']->format('Y-m-d g:i:s'),
'local_date' => $tz_info['now']->format('F j, Y'),
'local_time' => $tz_info['now']->format('g:ia'),
'local_timezone' => $tz_info['code'],
'local_timezone_abbr' => $tz_info['abbr'],
]);
'local_datetime' => $tz_info['now']->format('Y-m-d g:i:s'),
'local_date' => $tz_info['now']->format('F j, Y'),
'local_time' => $tz_info['now']->format('g:ia'),
'local_timezone' => $tz_info['code'],
'local_timezone_abbr' => $tz_info['abbr'],
));
}
}

View file

@ -1,28 +1,25 @@
<?php
namespace Controller\Api;
use Entity\Station;
use \Entity\Station;
use Entity\StationStreamer;
class InternalController extends BaseController
{
public function streamauthAction()
{
if (!$this->hasParam('id')) {
if (!$this->hasParam('id'))
return $this->_authFail('No station specified!');
}
$id = (int)$this->getParam('id');
$station = $this->em->getRepository(Station::class)->find($id);
if (!($station instanceof Station)) {
if (!($station instanceof Station))
return $this->_authFail('Invalid station specified');
}
// Log requests to a temp file for debugging.
$request_vars = "-------\n" . date('F j, Y g:i:s') . "\n" . print_r($_REQUEST,
true) . "\n" . print_r($this->params, true);
$log_path = APP_INCLUDE_TEMP . '/icecast_stream_auth.txt';
$request_vars = "-------\n".date('F j, Y g:i:s')."\n".print_r($_REQUEST, true)."\n".print_r($this->params, true);
$log_path = APP_INCLUDE_TEMP.'/icecast_stream_auth.txt';
file_put_contents($log_path, $request_vars, \FILE_APPEND);
/* Passed via POST from IceCast
@ -35,17 +32,13 @@ class InternalController extends BaseController
* [pass] => testpass
*/
if (!$station->enable_streamers) {
if (!$station->enable_streamers)
return $this->_authFail('Support for streamers/DJs on this station is disabled.');
}
if ($this->em->getRepository(StationStreamer::class)->authenticate($station, $_REQUEST['user'],
$_REQUEST['pass'])
) {
if ($this->em->getRepository(StationStreamer::class)->authenticate($station, $_REQUEST['user'], $_REQUEST['pass']))
return $this->_authSuccess();
} else {
else
return $this->_authFail('Could not authenticate streamer account.');
}
}
protected function _authFail($message)
@ -54,7 +47,7 @@ class InternalController extends BaseController
->withHeader('icecast-auth-user', '0')
->withHeader('Icecast-Auth-Message', $message);
$this->response->getBody()->write('Authentication failure: ' . $message);
$this->response->getBody()->write('Authentication failure: '.$message);
return $this->response;
}

View file

@ -11,28 +11,31 @@ class NowplayingController extends BaseController
// Pull from cache, or load from flatfile otherwise.
$cache = $this->di->get('cache');
$np = $cache->get('api_nowplaying_data', function () {
$np = $cache->get('api_nowplaying_data', function() {
return $this->di['em']->getRepository(Settings::class)->getSetting('nowplaying');
});
// Sanity check for now playing data.
if (empty($np)) {
if (empty($np))
return $this->returnError('Now Playing data has not loaded into the cache. Wait for file reload.');
}
if ($this->hasParam('id') || $this->hasParam('station')) {
if ($this->hasParam('id')) {
if ($this->hasParam('id') || $this->hasParam('station'))
{
if ($this->hasParam('id'))
{
$id = (int)$this->getParam('id');
foreach ($np as $key => $np_row) {
if ($np_row['station']['id'] == $id) {
foreach($np as $key => $np_row)
{
if ($np_row['station']['id'] == $id)
return $this->returnSuccess($np_row);
}
}
return $this->returnError('Station not found.');
}
} else {
}
else
{
return $this->returnSuccess($np);
}
}

View file

@ -1,8 +1,8 @@
<?php
namespace Controller\Api;
use Entity\Station;
use Entity\StationRequest;
use \Entity\Station;
use \Entity\StationRequest;
class RequestsController extends BaseController
{
@ -10,14 +10,12 @@ class RequestsController extends BaseController
{
$station = $this->_getStation();
if (!$station) {
if (!$station)
return $this->returnError('Station not found!');
}
$ba = $station->getBackendAdapter($this->di);
if (!$ba->supportsRequests()) {
if (!$ba->supportsRequests())
return $this->returnError('This station does not support requests.');
}
$requestable_media = $this->em->createQuery('SELECT sm, s, sp
FROM Entity\StationMedia sm JOIN sm.song s LEFT JOIN sm.playlists sp
@ -25,24 +23,26 @@ class RequestsController extends BaseController
->setParameter('station_id', $station->id)
->getArrayResult();
$result = [];
$result = array();
foreach ($requestable_media as $media_row) {
$result_row = [
foreach($requestable_media as $media_row)
{
$result_row = array(
'song' => \Entity\Song::api($media_row['song']),
'request_song_id' => $media_row['id'],
'request_url' => $this->url->routeFromHere(['action' => 'submit', 'song_id' => $media_row['id']]),
];
);
$result[] = $result_row;
}
// Handle Bootgrid-style iteration through result
if (!empty($_REQUEST['current'])) {
if (!empty($_REQUEST['current']))
{
// Flatten the results array for bootgrid.
foreach ($result as &$row) {
foreach ($row['song'] as $song_key => $song_val) {
$row['song_' . $song_key] = $song_val;
}
foreach($result as &$row)
{
foreach($row['song'] as $song_key => $song_val)
$row['song_'.$song_key] = $song_val;
}
// Example from bootgrid docs:
@ -51,28 +51,33 @@ class RequestsController extends BaseController
// Apply sorting, limiting and searching.
$search_phrase = trim($_REQUEST['searchPhrase']);
if (!empty($search_phrase)) {
$result = array_filter($result, function ($row) use ($search_phrase) {
$search_fields = ['song_title', 'song_artist'];
if (!empty($search_phrase))
{
$result = array_filter($result, function($row) use($search_phrase) {
$search_fields = array('song_title', 'song_artist');
foreach ($search_fields as $field_name) {
if (stripos($row[$field_name], $search_phrase) !== false) {
foreach($search_fields as $field_name)
{
if (stripos($row[$field_name], $search_phrase) !== false)
return true;
}
}
return false;
});
}
if (!empty($_REQUEST['sort'])) {
if (!empty($_REQUEST['sort']))
{
$sort_by = [];
foreach ($_REQUEST['sort'] as $sort_key => $sort_direction) {
foreach ($_REQUEST['sort'] as $sort_key => $sort_direction)
{
$sort_dir = (strtolower($sort_direction) == 'desc') ? \SORT_DESC : \SORT_ASC;
$sort_by[] = $sort_key;
$sort_by[] = $sort_dir;
}
} else {
}
else
{
$sort_by = ['song_artist', \SORT_ASC, 'song_title', \SORT_ASC];
}
@ -86,17 +91,42 @@ class RequestsController extends BaseController
$offset_start = ($page - 1) * $row_count;
$return_result = array_slice($result, $offset_start, $row_count);
return $this->renderJson([
return $this->renderJson(array(
'current' => $page,
'rowCount' => $row_count,
'total' => $num_results,
'rows' => $return_result,
]);
));
}
return $this->returnSuccess($result);
}
public function submitAction()
{
$station = $this->_getStation();
if (!$station)
return $this->returnError('Station not found!');
$ba = $station->getBackendAdapter($this->di);
if (!$ba->supportsRequests())
return $this->returnError('This station does not support requests.');
$song = $this->getParam('song_id');
try
{
$this->em->getRepository(StationRequest::class)->submit($station, $song, $this->authenticate());
return $this->returnSuccess('Request submitted successfully.');
}
catch(\App\Exception $e)
{
return $this->returnError($e->getMessage());
}
}
/**
* @return Station|null
*/
@ -104,41 +134,19 @@ class RequestsController extends BaseController
{
$station = $this->getParam('station');
if (is_numeric($station)) {
if (is_numeric($station))
{
$id = (int)$station;
$record = $this->em->getRepository(Station::class)->find($id);
} else {
}
else
{
$record = $this->em->getRepository(Station::class)->findByShortCode($this->getParam('station'));
}
if (!($record instanceof Station) || $record->deleted_at) {
if (!($record instanceof Station) || $record->deleted_at)
return null;
}
return $record;
}
public function submitAction()
{
$station = $this->_getStation();
if (!$station) {
return $this->returnError('Station not found!');
}
$ba = $station->getBackendAdapter($this->di);
if (!$ba->supportsRequests()) {
return $this->returnError('This station does not support requests.');
}
$song = $this->getParam('song_id');
try {
$this->em->getRepository(StationRequest::class)->submit($station, $song, $this->authenticate());
return $this->returnSuccess('Request submitted successfully.');
} catch (\App\Exception $e) {
return $this->returnError($e->getMessage());
}
}
}

View file

@ -1,45 +1,48 @@
<?php
namespace Controller\Api;
use Entity\Station;
use \Entity\Station;
class StationsController extends BaseController
{
public function viewAction()
{
return $this->indexAction();
}
public function indexAction()
{
if ($this->hasParam('station')) {
if ($this->hasParam('station'))
{
$record = $this->em->getRepository(Station::class)->findByShortCode($this->getParam('station'));
} elseif ($this->hasParam('id')) {
}
elseif ($this->hasParam('id'))
{
$id = (int)$this->getParam('id');
$record = $this->em->getRepository(Station::class)->find($id);
} else {
$this->dispatcher->forward([
}
else
{
$this->dispatcher->forward(array(
'controller' => 'station',
'action' => 'list',
]);
));
return false;
}
if (!($record instanceof Station) || $record->deleted_at) {
if (!($record instanceof Station) || $record->deleted_at)
return $this->returnError('Station not found.');
}
return $this->returnSuccess(Station::api($record, $this->di));
}
public function viewAction()
{
return $this->indexAction();
}
public function listAction()
{
$stations_raw = $this->em->getRepository(Station::class)->findAll();
$stations = [];
foreach ($stations_raw as $row) {
$stations = array();
foreach($stations_raw as $row)
$stations[] = Station::api($row, $this->di);
}
return $this->returnSuccess($stations);
}

View file

@ -2,17 +2,18 @@
namespace Controller\Frontend;
use Entity\Settings;
use Entity\User;
class AccountController extends BaseController
{
public function init()
{
if ($this->em->getRepository(Settings::class)->getSetting('setup_complete', 0) == 0) {
if ($this->em->getRepository(Settings::class)->getSetting('setup_complete', 0) == 0)
{
$num_users = $this->em->createQuery('SELECT COUNT(u.id) FROM Entity\User u')->getSingleScalarResult();
if ($num_users == 0) {
if ($num_users == 0)
return $this->redirectToRoute(['module' => 'frontend', 'controller' => 'setup']);
}
}
return null;
@ -20,38 +21,38 @@ class AccountController extends BaseController
public function indexAction()
{
if ($this->auth->isLoggedIn()) {
if ($this->auth->isLoggedIn())
return $this->redirectHome();
} else {
return $this->redirectFromHere(['action' => 'login']);
}
else
return $this->redirectFromHere(array('action' => 'login'));
}
public function loginAction()
{
if ($this->auth->isLoggedIn()) {
if ($this->auth->isLoggedIn())
return $this->redirectHome();
}
if (!$_POST) {
if (!$_POST)
$this->storeReferrer('login', false);
}
if (!empty($_POST['username']) && !empty($_POST['password'])) {
if (!empty($_POST['username']) && !empty($_POST['password']))
{
$login_success = $this->auth->authenticate($_POST['username'], $_POST['password']);
if ($login_success) {
if($login_success)
{
$this->acl->reload();
$user = $this->auth->getLoggedInUser();
$this->alert('<b>' . _('Logged in successfully.') . '</b><br>' . $user->email, 'green');
$this->alert('<b>'._('Logged in successfully.').'</b><br>'.$user->email, 'green');
$default_url = $this->url->named('home');
return $this->redirectToStoredReferrer('login', $default_url);
} else {
$this->alert('<b>' . _('Login unsuccessful') . '</b><br>' . _('Your credentials could not be verified.'),
'red');
}
else
{
$this->alert('<b>'._('Login unsuccessful').'</b><br>'._('Your credentials could not be verified.'), 'red');
return $this->redirectHere();
}
}

View file

@ -2,6 +2,7 @@
namespace Controller\Frontend;
use Entity\Station;
use Entity\Settings;
class IndexController extends BaseController
{
@ -15,15 +16,16 @@ class IndexController extends BaseController
$cache = $this->di->get('cache');
$metrics = $cache->get('admin_metrics');
if (!$metrics) {
if (!$metrics)
{
// Statistics by day.
$station_averages = [];
$network_data = [
'All Stations' => [
'ranges' => [],
'averages' => [],
],
];
$station_averages = array();
$network_data = array(
'All Stations' => array(
'ranges' => array(),
'averages' => array(),
),
);
// Query InfluxDB database.
$influx = $this->di->get('influx');
@ -32,50 +34,46 @@ class IndexController extends BaseController
]);
$results_raw = $resultset->getSeries();
$results = [];
foreach ($results_raw as $serie) {
$results = array();
foreach($results_raw as $serie)
{
$points = [];
foreach ($serie['values'] as $point) {
foreach ($serie['values'] as $point)
$points[] = array_combine($serie['columns'], $point);
}
$results[$serie['name']] = $points;
}
foreach ($results as $stat_series => $stat_rows) {
foreach($results as $stat_series => $stat_rows)
{
$series_split = explode('.', $stat_series);
if ($series_split[1] == 'all') {
if ($series_split[1] == 'all')
{
$network_name = 'All Stations';
foreach ($stat_rows as $stat_row) {
foreach($stat_rows as $stat_row)
{
// Add 12 hours to statistics so they always land inside the day they represent.
$stat_row['time'] = $stat_row['time'] + (60 * 60 * 12 * 1000);
$stat_row['time'] = $stat_row['time'] + (60*60*12*1000);
$network_data[$network_name]['ranges'][$stat_row['time']] = [
$stat_row['time'],
$stat_row['min'],
$stat_row['max']
];
$network_data[$network_name]['averages'][$stat_row['time']] = [
$stat_row['time'],
round($stat_row['value'], 2)
];
$network_data[$network_name]['ranges'][$stat_row['time']] = array($stat_row['time'], $stat_row['min'], $stat_row['max']);
$network_data[$network_name]['averages'][$stat_row['time']] = array($stat_row['time'], round($stat_row['value'], 2));
}
} else {
}
else
{
$station_id = $series_split[1];
foreach ($stat_rows as $stat_row) {
foreach($stat_rows as $stat_row)
{
// Add 12 hours to statistics so they always land inside the day they represent.
$stat_row['time'] = $stat_row['time'] + (60 * 60 * 12 * 1000);
$stat_row['time'] = $stat_row['time'] + (60*60*12*1000);
$station_averages[$station_id][$stat_row['time']] = [
$stat_row['time'],
round($stat_row['value'], 2)
];
$station_averages[$station_id][$stat_row['time']] = array($stat_row['time'], round($stat_row['value'], 2));
}
}
}
$network_metrics = [];
$network_metrics = array();
foreach ($network_data as $network_name => $data_charts) {
if (isset($data_charts['ranges'])) {
$metric_row = new \stdClass;
@ -100,7 +98,7 @@ class IndexController extends BaseController
}
}
$station_metrics = [];
$station_metrics = array();
foreach ($stations as $station) {
$station_id = $station['id'];
@ -116,12 +114,12 @@ class IndexController extends BaseController
}
}
$metrics = [
'network' => json_encode($network_metrics),
'station' => json_encode($station_metrics),
];
$metrics = array(
'network' => json_encode($network_metrics),
'station' => json_encode($station_metrics),
);
$cache->save($metrics, 'admin_metrics', [], 600);
$cache->save($metrics, 'admin_metrics', array(), 600);
}
$this->view->metrics = $metrics;

View file

@ -1,7 +1,8 @@
<?php
namespace Controller\Frontend;
use Entity\UserExternal;
use \Entity\User;
use \Entity\UserExternal;
class ProfileController extends BaseController
{
@ -31,7 +32,8 @@ class ProfileController extends BaseController
$form->setDefaults($user_profile);
if ($_POST && $form->isValid($_POST)) {
if($_POST && $form->isValid($_POST))
{
$data = $form->getValues();
$user->fromArray($this->em, $data);
@ -39,7 +41,7 @@ class ProfileController extends BaseController
$this->em->flush();
$this->alert(_('Profile saved!'), 'green');
return $this->redirectFromHere(['action' => 'index']);
return $this->redirectFromHere(array('action' => 'index'));
}
return $this->renderForm($form, 'edit', _('Edit Profile'));

View file

@ -2,6 +2,7 @@
namespace Controller\Frontend;
use Entity\Station;
use Entity\Settings;
class PublicController extends BaseController
{
@ -16,7 +17,8 @@ class PublicController extends BaseController
$stations = $this->em->getRepository(Station::class)->findAll();
$this->view->stations = $stations;
if (!$this->hasParam('station')) {
if (!$this->hasParam('station'))
{
$station = reset($stations);
return $this->redirectFromHere(['station' => $station->id]);
}
@ -24,9 +26,8 @@ class PublicController extends BaseController
$station_id = (int)$this->getParam('station');
$station = $this->em->getRepository(Station::class)->find($station_id);
if (!($station instanceof Station)) {
if (!($station instanceof Station))
throw new \App\Exception(_('Station not found!'));
}
$this->view->station = $station;
}

View file

@ -2,14 +2,14 @@
namespace Controller\Frontend;
use Entity\Settings;
use Entity\SettingsRepository;
use Entity\Station;
use Entity\SettingsRepository;
class SetupController extends BaseController
{
public function init()
{
return null;
return NULL;
}
/**
@ -21,49 +21,16 @@ class SetupController extends BaseController
return $this->redirectFromHere(['action' => $current_step]);
}
/**
* Determine which step of setup is currently active.
*
* @return string
* @throws \App\Exception\NotLoggedIn
*/
protected function _getSetupStep()
{
if ($this->em->getRepository('Entity\Settings')->getSetting('setup_complete', 0) != 0) {
return 'complete';
}
// Step 1: Register
$num_users = $this->em->createQuery('SELECT COUNT(u.id) FROM Entity\User u')->getSingleScalarResult();
if ($num_users == 0) {
return 'register';
}
// If past "register" step, require login.
if (!$this->auth->isLoggedIn()) {
throw new \App\Exception\NotLoggedIn;
}
// Step 2: Set up Station
$num_stations = $this->em->createQuery('SELECT COUNT(s.id) FROM Entity\Station s')->getSingleScalarResult();
if ($num_stations == 0) {
return 'station';
}
// Step 3: System Settings
return 'settings';
}
/**
* Placeholder function for "setup complete" redirection.
*/
public function completeAction()
{
$this->alert('<b>' . _('Setup has already been completed!') . '</b>', 'red');
$this->alert('<b>'._('Setup has already been completed!').'</b>', 'red');
return $this->redirectHome();
}
/**
* Setup Step 1:
* Create Super Administrator Account
@ -72,12 +39,12 @@ class SetupController extends BaseController
{
// Verify current step.
$current_step = $this->_getSetupStep();
if ($current_step != 'register') {
if ($current_step != 'register')
return $this->redirectFromHere(['action' => $current_step]);
}
// Create first account form.
if (!empty($_POST['username']) && !empty($_POST['password'])) {
if (!empty($_POST['username']) && !empty($_POST['password']))
{
$data = $_POST;
// Create actions and roles supporting Super Admninistrator.
@ -119,9 +86,8 @@ class SetupController extends BaseController
{
// Verify current step.
$current_step = $this->_getSetupStep();
if ($current_step != 'station') {
if ($current_step != 'station')
return $this->redirectFromHere(['action' => $current_step]);
}
// Set up station form.
$form_config = $this->config->forms->station->toArray();
@ -130,7 +96,8 @@ class SetupController extends BaseController
$form = new \App\Form($form_config);
if (!empty($_POST) && $form->isValid($_POST)) {
if (!empty($_POST) && $form->isValid($_POST))
{
$data = $form->getValues();
$station_repo = $this->em->getRepository(Station::class);
@ -151,19 +118,19 @@ class SetupController extends BaseController
// Verify current step.
$current_step = $this->_getSetupStep();
if ($current_step != 'settings') {
if ($current_step != 'settings')
return $this->redirectFromHere(['action' => $current_step]);
}
$form = new \App\Form($this->config->forms->settings->form);
/** @var SettingsRepository $settings_repo */
$settings_repo = $this->em->getRepository(Settings::class);
$existing_settings = $settings_repo->fetchArray(false);
$existing_settings = $settings_repo->fetchArray(FALSE);
$form->setDefaults($existing_settings);
if ($this->request->getMethod() == 'POST' && $form->isValid($this->request->getQueryParams())) {
if ($this->request->getMethod() == 'POST' && $form->isValid($this->request->getQueryParams()))
{
$data = $form->getValues();
// Mark setup as complete along with other settings changes.
@ -172,11 +139,39 @@ class SetupController extends BaseController
$settings_repo->setSettings($data);
// Notify the user and redirect to homepage.
$this->alert('<b>' . _('Setup is now complete!') . '</b><br>' . _('Continue setting up your station in the main AzuraCast app.'),
'green');
$this->alert('<b>'._('Setup is now complete!').'</b><br>'._('Continue setting up your station in the main AzuraCast app.'), 'green');
return $this->redirectHome();
}
return $this->renderForm($form, 'edit', _('Site Settings'));
}
/**
* Determine which step of setup is currently active.
*
* @return string
* @throws \App\Exception\NotLoggedIn
*/
protected function _getSetupStep()
{
if ($this->em->getRepository('Entity\Settings')->getSetting('setup_complete', 0) != 0)
return 'complete';
// Step 1: Register
$num_users = $this->em->createQuery('SELECT COUNT(u.id) FROM Entity\User u')->getSingleScalarResult();
if ($num_users == 0)
return 'register';
// If past "register" step, require login.
if (!$this->auth->isLoggedIn())
throw new \App\Exception\NotLoggedIn;
// Step 2: Set up Station
$num_stations = $this->em->createQuery('SELECT COUNT(s.id) FROM Entity\Station s')->getSingleScalarResult();
if ($num_stations == 0)
return 'station';
// Step 3: System Settings
return 'settings';
}
}

View file

@ -1,8 +1,17 @@
<?php
namespace Controller\Stations;
use Entity\Station;
use Entity\StationMedia;
use Entity\StationPlaylist;
class AutomationController extends BaseController
{
protected function permissions()
{
return $this->acl->isAllowed('manage station automation', $this->station->id);
}
public function indexAction()
{
$automation_settings = (array)$this->station->automation_settings;
@ -10,7 +19,8 @@ class AutomationController extends BaseController
$form = new \App\Form($this->config->forms->automation);
$form->setDefaults($automation_settings);
if (!empty($_POST) && $form->isValid($_POST)) {
if (!empty($_POST) && $form->isValid($_POST))
{
$data = $form->getValues();
$this->station->automation_settings = $data;
@ -27,21 +37,18 @@ class AutomationController extends BaseController
public function runAction()
{
try {
try
{
$automation = new \AzuraCast\Sync\RadioAutomation($this->di);
if ($automation->runStation($this->station, true)) {
$this->alert('<b>' . _('Automated assignment complete!') . '</b>', 'green');
}
} catch (\Exception $e) {
$this->alert('<b>' . _('Automated assignment error') . ':</b><br>' . $e->getMessage(), 'red');
if ($automation->runStation($this->station, true))
$this->alert('<b>'._('Automated assignment complete!').'</b>', 'green');
}
catch(\Exception $e)
{
$this->alert('<b>'._('Automated assignment error').':</b><br>'.$e->getMessage(), 'red');
}
return $this->redirectFromHere(['action' => 'index']);
}
protected function permissions()
{
return $this->acl->isAllowed('manage station automation', $this->station->id);
}
}

View file

@ -27,9 +27,8 @@ class BaseController extends \AzuraCast\Mvc\Controller
$station_id = (int)$this->getParam('station');
$this->station = $this->view->station = $this->em->getRepository(Station::class)->find($station_id);
if (!($this->station instanceof Station)) {
if (!($this->station instanceof Station))
throw new \App\Exception\PermissionDenied;
}
$this->frontend = $this->view->frontend = $this->station->getFrontendAdapter($this->di);
$this->backend = $this->view->backend = $this->station->getBackendAdapter($this->di);

View file

@ -2,6 +2,7 @@
namespace Controller\Stations;
use App\Utilities;
use Entity\Station;
use Entity\StationMedia;
use Entity\StationPlaylist;
use Slim\Http\UploadedFile;
@ -15,59 +16,51 @@ use Slim\Http\UploadedFile;
*/
class FilesController extends BaseController
{
protected $base_dir = null;
protected $base_dir = NULL;
protected $file = '';
protected $file_path = NULL;
protected $file_path = null;
protected function permissions()
{
return $this->acl->isAllowed('manage station media', $this->station->id);
}
public function preDispatch()
{
parent::preDispatch();
if (!$this->backend->supportsMedia()) {
if (!$this->backend->supportsMedia())
throw new \App\Exception(_('This feature is not currently supported on this station.'));
}
$this->base_dir = realpath($this->station->radio_base_dir . '/media');
$this->base_dir = realpath($this->station->radio_base_dir.'/media');
$this->view->base_dir = $this->base_dir;
if (!empty($_REQUEST['file'])) {
if (!empty($_REQUEST['file']))
$this->file = $_REQUEST['file'];
}
$this->file_path = realpath($this->base_dir . '/' . $this->file);
$this->file_path = realpath($this->base_dir.'/'.$this->file);
if ($this->file_path === false) {
return $this->_err(404, 'File or Directory Not Found');
}
if (substr($this->file_path, 0, strlen($this->base_dir)) !== $this->base_dir) {
return $this->_err(403, "Forbidden");
}
if ($this->file_path === false)
return $this->_err(404,'File or Directory Not Found');
if(substr($this->file_path, 0, strlen($this->base_dir)) !== $this->base_dir)
return $this->_err(403,"Forbidden");
$csrf = $this->di->get('csrf');
$this->view->CSRF = $csrf->generate('files');
if (!empty($_POST)) {
if (!$csrf->verify($_POST['xsrf'], 'files')) {
if (!empty($_POST))
{
if (!$csrf->verify($_POST['xsrf'], 'files'))
return $this->_err(403, 'XSRF Failure');
}
}
$this->view->MAX_UPLOAD_SIZE = min($this->_asBytes(ini_get('post_max_size')),
$this->_asBytes(ini_get('upload_max_filesize')));
$this->view->MAX_UPLOAD_SIZE = min($this->_asBytes(ini_get('post_max_size')), $this->_asBytes(ini_get('upload_max_filesize')));
}
protected function _err($code, $msg)
{
return $this->renderJson(['error' => ['code' => intval($code), 'msg' => $msg]]);
}
protected function _asBytes($ini_v)
{
protected function _asBytes($ini_v) {
$ini_v = trim($ini_v);
$s = ['g' => 1 << 30, 'm' => 1 << 20, 'k' => 1 << 10];
return intval($ini_v) * ($s[strtolower(substr($ini_v, -1))] ?: 1);
$s = array('g'=> 1<<30, 'm' => 1<<20, 'k' => 1<<10);
return intval($ini_v) * ($s[strtolower(substr($ini_v,-1))] ?: 1);
}
public function indexAction()
@ -76,10 +69,9 @@ class FilesController extends BaseController
->setParameter('station_id', $this->station->id)
->getArrayResult();
$playlists = [];
foreach ($playlists_raw as $row) {
$playlists = array();
foreach($playlists_raw as $row)
$playlists[$row['id']] = $row['name'];
}
$this->view->playlists = $playlists;
@ -98,25 +90,21 @@ class FilesController extends BaseController
public function editAction()
{
$media_id = (int)$this->getParam('id');
$media = $this->em->getRepository(StationMedia::class)->findOneBy([
'station_id' => $this->station->id,
'id' => $media_id
]);
$media = $this->em->getRepository(StationMedia::class)->findOneBy(['station_id' => $this->station->id, 'id' => $media_id]);
if (!($media instanceof StationMedia)) {
if (!($media instanceof StationMedia))
throw new \Exception('Media not found.');
}
if (empty($_POST)) {
if (empty($_POST))
$this->storeReferrer('media_edit');
}
$form_config = $this->config->forms->media->toArray();
$form = new \App\Form($form_config);
$form->populate($media->toArray($this->em));
if (!empty($_POST) && $form->isValid()) {
if (!empty($_POST) && $form->isValid())
{
$data = $form->getValues();
$media->fromArray($this->em, $data);
@ -125,7 +113,7 @@ class FilesController extends BaseController
$this->em->persist($media);
$this->em->flush();
$this->alert('<b>' . _('Media metadata updated!') . '</b>', 'green');
$this->alert('<b>'._('Media metadata updated!').'</b>', 'green');
$default_url = $this->url->routeFromHere(['action' => 'index']);
return $this->redirectToStoredReferrer('media_edit', $default_url);
@ -136,69 +124,68 @@ class FilesController extends BaseController
public function listAction()
{
$result = [];
$result = array();
if (is_dir($this->file_path)) {
if (is_dir($this->file_path))
{
$media_in_dir_raw = $this->em->createQuery('SELECT sm, sp FROM Entity\StationMedia sm LEFT JOIN sm.playlists sp WHERE sm.station_id = :station_id AND sm.path LIKE :path')
->setParameter('station_id', $this->station->id)
->setParameter('path', $this->file . '%')
->setParameter('path', $this->file.'%')
->getArrayResult();
$media_in_dir = [];
foreach ($media_in_dir_raw as $media_row) {
$playlists = [];
foreach ($media_row['playlists'] as $playlist_row) {
$media_in_dir = array();
foreach($media_in_dir_raw as $media_row)
{
$playlists = array();
foreach($media_row['playlists'] as $playlist_row)
$playlists[] = $playlist_row['name'];
}
$media_in_dir[$media_row['path']] = [
$media_in_dir[$media_row['path']] = array(
'is_playable' => true,
'length' => $media_row['length'],
'length_text' => $media_row['length_text'],
'artist' => $media_row['artist'],
'title' => $media_row['title'],
'name' => $media_row['artist'] . ' - ' . $media_row['title'],
'name' => $media_row['artist'].' - '.$media_row['title'],
'edit_url' => $this->url->routeFromHere(['action' => 'edit', 'id' => $media_row['id']]),
'play_url' => $this->url->routeFromHere(['action' => 'download']) . '?file=' . urlencode($media_row['path']),
'play_url' => $this->url->routeFromHere(['action' => 'download']).'?file='.urlencode($media_row['path']),
'playlists' => implode('<br>', $playlists),
];
);
}
$directory = $this->file_path;
$files = array_diff(scandir($directory), ['.', '..']);
foreach ($files as $entry) {
$files = array_diff(scandir($directory), array('.', '..'));
foreach ($files as $entry)
{
$i = $directory . '/' . $entry;
$short = ltrim(str_replace($this->base_dir, '', $i), '/');
if (is_dir($i)) {
if (is_dir($i))
$media = ['name' => _('Directory'), 'playlists' => '', 'is_playable' => false];
} elseif (isset($media_in_dir[$short])) {
elseif (isset($media_in_dir[$short]))
$media = $media_in_dir[$short];
} else {
else
$media = ['name' => _('File Not Processed'), 'playlists' => '', 'is_playable' => false];
}
$stat = stat($i);
$max_length = 60;
$shortname = basename($i);
if (mb_strlen($shortname) > $max_length) {
$shortname = mb_substr($shortname, 0, $max_length - 15) . '...' . mb_substr($shortname, -12);
}
if (mb_strlen($shortname) > $max_length)
$shortname = mb_substr($shortname, 0, $max_length-15).'...'.mb_substr($shortname, -12);
$result_row = [
$result_row = array(
'mtime' => $stat['mtime'],
'size' => $stat['size'],
'name' => basename($i),
'text' => $shortname,
'path' => $short,
'is_dir' => is_dir($i),
];
);
foreach ($media as $media_key => $media_val) {
$result_row['media_' . $media_key] = $media_val;
}
foreach($media as $media_key => $media_val)
$result_row['media_'.$media_key] = $media_val;
$result[] = $result_row;
}
@ -210,30 +197,35 @@ class FilesController extends BaseController
// Apply sorting, limiting and searching.
$search_phrase = trim($_REQUEST['searchPhrase']);
if (!empty($search_phrase)) {
$result = array_filter($result, function ($row) use ($search_phrase) {
$search_fields = ['media_name', 'text'];
if (!empty($search_phrase))
{
$result = array_filter($result, function($row) use($search_phrase) {
$search_fields = array('media_name', 'text');
foreach ($search_fields as $field_name) {
if (stripos($row[$field_name], $search_phrase) !== false) {
foreach($search_fields as $field_name)
{
if (stripos($row[$field_name], $search_phrase) !== false)
return true;
}
}
return false;
});
}
$sort_by = ['is_dir', \SORT_DESC];
$sort_by = array('is_dir', \SORT_DESC);
if (!empty($_REQUEST['sort'])) {
foreach ($_REQUEST['sort'] as $sort_key => $sort_direction) {
if (!empty($_REQUEST['sort']))
{
foreach ($_REQUEST['sort'] as $sort_key => $sort_direction)
{
$sort_dir = (strtolower($sort_direction) == 'desc') ? \SORT_DESC : \SORT_ASC;
$sort_by[] = $sort_key;
$sort_by[] = $sort_dir;
}
} else {
}
else
{
$sort_by[] = 'name';
$sort_by[] = \SORT_ASC;
}
@ -248,24 +240,24 @@ class FilesController extends BaseController
$offset_start = ($page - 1) * $row_count;
$return_result = array_slice($result, $offset_start, $row_count);
return $this->renderJson([
return $this->renderJson(array(
'current' => $page,
'rowCount' => $row_count,
'total' => $num_results,
'rows' => $return_result,
]);
));
}
public function batchAction()
{
$files_raw = explode('|', $_POST['files']);
$files = [];
$files = array();
foreach ($files_raw as $file) {
$file_path = $this->file_path . '/' . $file;
if (file_exists($file_path)) {
foreach($files_raw as $file)
{
$file_path = $this->file_path.'/'.$file;
if (file_exists($file_path))
$files[] = $file_path;
}
}
$files_found = 0;
@ -273,17 +265,22 @@ class FilesController extends BaseController
list($action, $action_id) = explode('_', $_POST['do']);
switch ($action) {
switch($action)
{
case 'delete':
// Remove the database entries of any music being removed.
$music_files = $this->_getMusicFiles($files);
$files_found = count($music_files);
foreach ($music_files as $i => $file) {
try {
foreach($music_files as $i => $file)
{
try
{
$media = $this->em->getRepository(StationMedia::class)->getOrCreate($this->station, $file);
$this->em->remove($media);
} catch (\Exception $e) {
}
catch(\Exception $e)
{
@unlink($file);
}
@ -293,23 +290,25 @@ class FilesController extends BaseController
$this->em->flush();
// Delete all selected files.
foreach ($files as $file) {
foreach($files as $file)
\App\Utilities::rmdir_recursive($file);
}
break;
break;
case 'clear':
// Clear all assigned playlists from the selected files.
$music_files = $this->_getMusicFiles($files);
$files_found = count($music_files);
foreach ($music_files as $file) {
try {
foreach($music_files as $file)
{
try
{
$media = $this->em->getRepository(StationMedia::class)->getOrCreate($this->station, $file);
$media->playlists->clear();
$this->em->persist($media);
} catch (\Exception $e) {
}
catch(\Exception $e)
{}
$files_affected++;
@ -319,34 +318,32 @@ class FilesController extends BaseController
// Write new PLS playlist configuration.
$this->backend->write();
break;
break;
// Add all selected files to a playlist.
case 'playlist':
$playlist_id = (int)$action_id;
$playlist = $this->em->getRepository(StationPlaylist::class)->findOneBy([
'station_id' => $this->station->id,
'id' => $playlist_id
]);
$playlist = $this->em->getRepository(StationPlaylist::class)->findOneBy(['station_id' => $this->station->id, 'id' => $playlist_id]);
if (!($playlist instanceof StationPlaylist)) {
if (!($playlist instanceof StationPlaylist))
return $this->_err(500, 'Playlist Not Found');
}
$music_files = $this->_getMusicFiles($files);
$files_found = count($music_files);
foreach ($music_files as $file) {
try {
foreach($music_files as $file)
{
try
{
$media = $this->em->getRepository(StationMedia::class)->getOrCreate($this->station, $file);
if (!$media->playlists->contains($playlist)) {
if (!$media->playlists->contains($playlist))
$media->playlists->add($playlist);
}
$this->em->persist($media);
} catch (\Exception $e) {
}
catch(\Exception $e)
{}
$files_affected++;
}
@ -355,45 +352,44 @@ class FilesController extends BaseController
// Write new PLS playlist configuration.
$this->backend->write();
break;
break;
}
return $this->renderJson([
'success' => true,
'files_found' => $files_found,
'files_affected' => $files_affected
]);
return $this->renderJson(['success' => true, 'files_found' => $files_found, 'files_affected' => $files_affected]);
}
protected function _getMusicFiles($path)
{
if (is_array($path)) {
$music_files = [];
foreach ($path as $dir_file) {
if (is_array($path))
{
$music_files = array();
foreach($path as $dir_file)
$music_files = array_merge($music_files, $this->_getMusicFiles($dir_file));
}
return $music_files;
}
$supported = StationMedia::getSupportedFormats();
if (is_dir($path)) {
$music_files = [];
if (is_dir($path))
{
$music_files = array();
$files = array_diff(scandir($path), ['.', '..']);
foreach ($files as $file) {
$files = array_diff(scandir($path), array('.','..'));
foreach ($files as $file)
{
$file_ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
$file_path = $path . '/' . $file;
if (is_dir($file_path)) {
if (is_dir($file_path))
$music_files = array_merge($music_files, $this->_getMusicFiles($file_path));
} elseif (in_array($file_ext, $supported)) {
elseif (in_array($file_ext, $supported))
$music_files[] = $file_path;
}
}
return $music_files;
} else {
}
else
{
$file_ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
return (in_array($file_ext, $supported)) ? [$path] : [];
}
@ -404,11 +400,10 @@ class FilesController extends BaseController
// don't allow actions outside root. we also filter out slashes to catch args like './../outside'
$dir = $_POST['name'];
$dir = str_replace('/', '', $dir);
if (substr($dir, 0, 2) === '..') {
if(substr($dir, 0, 2) === '..')
return $this->_err(403, 'Cannot create directory: ..');
}
@mkdir($this->file_path . '/' . $dir);
@mkdir($this->file_path.'/'.$dir);
return $this->renderJson(['success' => true]);
}
@ -417,10 +412,12 @@ class FilesController extends BaseController
{
$this->doNotRender();
try {
try
{
$files = $this->request->getUploadedFiles();
if (isset($files['file_data'])) {
if (isset($files['file_data']))
{
/** @var UploadedFile $uploaded_file */
$uploaded_file = $files['file_data'];
@ -429,15 +426,19 @@ class FilesController extends BaseController
$upload_file_path = $file->getPath();
}
} catch (\Exception $e) {
}
catch(\Exception $e)
{
return $this->_err(500, $e->getMessage());
}
try {
$station_media = $this->em->getRepository(StationMedia::class)->getOrCreate($this->station,
$upload_file_path);
try
{
$station_media = $this->em->getRepository(StationMedia::class)->getOrCreate($this->station, $upload_file_path);
$this->em->persist($station_media);
} catch (\Exception $e) {
}
catch(\Exception $e)
{
return $this->_err(500, $e->getMessage());
}
@ -453,34 +454,31 @@ class FilesController extends BaseController
$filename = basename($this->file_path);
header('Content-Type: ' . mime_content_type($this->file_path));
header('Content-Length: ' . filesize($this->file_path));
header('Content-Length: '. filesize($this->file_path));
header(sprintf('Content-Disposition: attachment; filename=%s',
strpos('MSIE', $_SERVER['HTTP_REFERER']) ? rawurlencode($filename) : "\"$filename\""));
strpos('MSIE',$_SERVER['HTTP_REFERER']) ? rawurlencode($filename) : "\"$filename\"" ));
ob_flush();
readfile($this->file_path);
}
protected function permissions()
{
return $this->acl->isAllowed('manage station media', $this->station->id);
}
protected function _is_recursively_deleteable($d)
{
$stack = [$d];
while ($dir = array_pop($stack)) {
if (!is_readable($dir) || !is_writable($dir)) {
$stack = array($d);
while($dir = array_pop($stack)) {
if(!is_readable($dir) || !is_writable($dir))
return false;
}
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
if (is_dir($file)) {
$stack[] = "$dir/$file";
}
$files = array_diff(scandir($dir), array('.','..'));
foreach($files as $file) if(is_dir($file)) {
$stack[] = "$dir/$file";
}
}
return true;
}
protected function _err($code, $msg)
{
return $this->renderJson(array('error' => array('code'=>intval($code), 'msg' => $msg)));
}
}

View file

@ -1,7 +1,12 @@
<?php
namespace Controller\Stations;
use Entity\Station;
use Entity\Song;
use Entity\SongHistory;
use Zend\Paginator\Adapter\ArrayAdapter;
use Zend\Paginator\Paginator;
class IndexController extends BaseController
{
@ -16,32 +21,31 @@ class IndexController extends BaseController
// Statistics by day.
$influx = $this->di->get('influx');
$resultset = $influx->query('SELECT * FROM "1d"."station.' . $this->station->id . '.listeners" WHERE time > now() - 30d',
[
'epoch' => 'ms',
]);
$resultset = $influx->query('SELECT * FROM "1d"."station.'.$this->station->id.'.listeners" WHERE time > now() - 30d', [
'epoch' => 'ms',
]);
$daily_stats = $resultset->getPoints();
$daily_ranges = [];
$daily_averages = [];
$days_of_week = [];
$daily_ranges = array();
$daily_averages = array();
$days_of_week = array();
foreach ($daily_stats as $stat) {
foreach($daily_stats as $stat)
{
// Add 12 hours to statistics so they always land inside the day they represent.
$stat['time'] = $stat['time'] + (60 * 60 * 12 * 1000);
$stat['time'] = $stat['time'] + (60*60*12*1000);
$daily_ranges[] = [$stat['time'], $stat['min'], $stat['max']];
$daily_averages[] = [$stat['time'], round($stat['value'], 2)];
$daily_ranges[] = array($stat['time'], $stat['min'], $stat['max']);
$daily_averages[] = array($stat['time'], round($stat['value'], 2));
$day_of_week = date('l', round($stat['time'] / 1000));
$day_of_week = date('l', round($stat['time']/1000));
$days_of_week[$day_of_week][] = $stat['value'];
}
$day_of_week_stats = [];
foreach ($days_of_week as $day_name => $day_totals) {
$day_of_week_stats[] = [$day_name, round(array_sum($day_totals) / count($day_totals), 2)];
}
$day_of_week_stats = array();
foreach($days_of_week as $day_name => $day_totals)
$day_of_week_stats[] = array($day_name, round(array_sum($day_totals) / count($day_totals), 2));
$this->view->day_of_week_stats = json_encode($day_of_week_stats);
@ -50,31 +54,33 @@ class IndexController extends BaseController
// Statistics by hour.
$influx = $this->di->get('influx');
$resultset = $influx->query('SELECT * FROM "1h"."station.' . $this->station->id . '.listeners"', [
$resultset = $influx->query('SELECT * FROM "1h"."station.'.$this->station->id.'.listeners"', [
'epoch' => 'ms',
]);
$hourly_stats = $resultset->getPoints();
$hourly_averages = [];
$hourly_ranges = [];
$totals_by_hour = [];
$hourly_averages = array();
$hourly_ranges = array();
$totals_by_hour = array();
foreach ($hourly_stats as $stat) {
$hourly_ranges[] = [$stat['time'], $stat['min'], $stat['max']];
$hourly_averages[] = [$stat['time'], round($stat['value'], 2)];
foreach($hourly_stats as $stat)
{
$hourly_ranges[] = array($stat['time'], $stat['min'], $stat['max']);
$hourly_averages[] = array($stat['time'], round($stat['value'], 2));
$hour = date('G', round($stat['time'] / 1000));
$hour = date('G', round($stat['time']/1000));
$totals_by_hour[$hour][] = $stat['value'];
}
$this->view->hourly_ranges = json_encode($hourly_ranges);
$this->view->hourly_averages = json_encode($hourly_averages);
$averages_by_hour = [];
for ($i = 0; $i < 24; $i++) {
$totals = $totals_by_hour[$i] ?: [0];
$averages_by_hour[] = [$i . ':00', round(array_sum($totals) / count($totals), 2)];
$averages_by_hour = array();
for($i = 0; $i < 24; $i++)
{
$totals = $totals_by_hour[$i] ?: array(0);
$averages_by_hour[] = array($i.':00', round(array_sum($totals) / count($totals), 2));
}
$this->view->averages_by_hour = json_encode($averages_by_hour);
@ -83,7 +89,7 @@ class IndexController extends BaseController
* Play Count Statistics
*/
$song_totals_raw = [];
$song_totals_raw = array();
$song_totals_raw['played'] = $this->em->createQuery('SELECT sh.song_id, COUNT(sh.id) AS records
FROM Entity\SongHistory sh
WHERE sh.station_id = :station_id AND sh.timestamp_start >= :timestamp
@ -95,21 +101,24 @@ class IndexController extends BaseController
->getArrayResult();
$ignored_songs = $this->_getIgnoredSongs();
$song_totals_raw['played'] = array_filter($song_totals_raw['played'], function ($value) use ($ignored_songs) {
$song_totals_raw['played'] = array_filter($song_totals_raw['played'], function($value) use ($ignored_songs)
{
return !(isset($ignored_songs[$value['song_id']]));
});
// Compile the above data.
$song_totals = [];
foreach ($song_totals_raw as $total_type => $total_records) {
foreach ($total_records as $total_record) {
$song_totals = array();
foreach($song_totals_raw as $total_type => $total_records)
{
foreach($total_records as $total_record)
{
$song = $this->em->getRepository(Song::class)->find($total_record['song_id']);
$total_record['song'] = $song;
$song_totals[$total_type][] = $total_record;
}
$song_totals[$total_type] = array_slice((array)$song_totals[$total_type], 0, 10, true);
$song_totals[$total_type] = array_slice((array)$song_totals[$total_type], 0, 10, TRUE);
}
$this->view->song_totals = $song_totals;
@ -119,13 +128,13 @@ class IndexController extends BaseController
*/
$songs_played_raw = $this->_getEligibleHistory();
$songs = [];
$songs = array();
foreach ($songs_played_raw as $i => $song_row) {
foreach($songs_played_raw as $i => $song_row)
{
// Song has no recorded ending.
if ($song_row['timestamp_end'] == 0) {
if ($song_row['timestamp_end'] == 0)
continue;
}
$song_row['stat_start'] = $song_row['listeners_start'];
$song_row['stat_end'] = $song_row['listeners_end'];
@ -134,13 +143,11 @@ class IndexController extends BaseController
$songs[] = $song_row;
}
usort($songs, function ($a_arr, $b_arr) {
usort($songs, function($a_arr, $b_arr) {
$a = $a_arr['stat_delta'];
$b = $b_arr['stat_delta'];
if ($a == $b) {
return 0;
}
if ($a == $b) return 0;
return ($a > $b) ? 1 : -1;
});
@ -148,33 +155,78 @@ class IndexController extends BaseController
$this->view->worst_performing_songs = array_slice((array)$songs, 0, 5);
}
protected function _getIgnoredSongs()
public function timelineAction()
{
$cache = $this->di->get('cache');
$song_hashes = $cache->get('station_center_ignored_songs');
$songs_played_raw = $this->_getEligibleHistory();
if (!$song_hashes) {
$ignored_phrases = ['Offline', 'Sweeper', 'Bumper', 'Unknown'];
$station_media_raw = $this->em->createQuery('SELECT sm, sp FROM Entity\StationMedia sm LEFT JOIN sm.playlists sp WHERE sm.station_id = :station_id')
->setParameter('station_id', $this->station->id)
->getArrayResult();
$qb = $this->em->createQueryBuilder();
$qb->select('s.id')->from('Entity\Song', 's');
$station_media = array();
foreach($station_media_raw as $media)
$station_media[$media['song_id']] = $media;
foreach ($ignored_phrases as $i => $phrase) {
$qb->orWhere('s.text LIKE ?' . ($i + 1));
$qb->setParameter($i + 1, '%' . $phrase . '%');
$songs = array();
foreach ($songs_played_raw as $song_row)
{
// Song has no recorded ending.
if ($song_row['timestamp_end'] == 0)
continue;
$song_row['stat_start'] = $song_row['listeners_start'];
$song_row['stat_end'] = $song_row['listeners_end'];
$song_row['stat_delta'] = $song_row['delta_total'];
if (isset($station_media[$song_row['song']['id']]))
{
$media = $station_media[$song_row['song']['id']];
$song_row['playlists'] = \Packaged\Helpers\Arrays::ipull($media['playlists'], 'name', 'id');
}
else
{
$song_row['playlists'] = array();
}
$song_hashes_raw = $qb->getQuery()->getArrayResult();
$song_hashes = [];
foreach ($song_hashes_raw as $row) {
$song_hashes[$row['id']] = $row['id'];
}
$cache->save($song_hashes, 'station_center_ignored_songs', [], 86400);
$songs[] = $song_row;
}
return $song_hashes;
$format = $this->getParam('format', 'html');
if ($format == 'csv')
{
$this->doNotRender();
$export_all = array();
$export_all[] = array('Date', 'Time', 'Listeners', 'Delta', 'Likes', 'Dislikes', 'Track', 'Artist', 'Playlist(s)');
foreach ($songs as $song_row)
{
$export_row = array(
date('Y-m-d', $song_row['timestamp_start']),
date('g:ia', $song_row['timestamp_start']),
$song_row['stat_start'],
$song_row['stat_delta'],
$song_row['score_likes'],
$song_row['score_dislikes'],
($song_row['song']['title']) ? $song_row['song']['title'] : $song_row['song']['text'],
$song_row['song']['artist'],
implode(', ', $song_row['playlists']),
);
$export_all[] = $export_row;
}
$csv_file = \App\Export::csv($export_all);
$csv_filename = $this->station->getShortName() . '_timeline_' . date('Ymd').'.csv';
return $this->renderStringAsFile($csv_file, 'text/csv', $csv_filename);
}
else
{
$songs = array_reverse($songs);
$this->view->songs = $songs;
}
}
/**
@ -184,19 +236,23 @@ class IndexController extends BaseController
protected function _getEligibleHistory()
{
$cache = $this->di->get('cache');
$cache_name = 'station_center_history_' . $this->station->id;
$cache_name = 'station_center_history_'.$this->station->id;
$songs_played_raw = $cache->get($cache_name);
if (!$songs_played_raw) {
try {
if (!$songs_played_raw)
{
try
{
$first_song = $this->em->createQuery('SELECT sh.timestamp_start FROM Entity\SongHistory sh
WHERE sh.station_id = :station_id AND sh.listeners_start IS NOT NULL
ORDER BY sh.timestamp_start ASC')
->setParameter('station_id', $this->station->id)
->setMaxResults(1)
->getSingleScalarResult();
} catch (\Exception $e) {
}
catch(\Exception $e)
{
$first_song = strtotime('Yesterday 00:00:00');
}
@ -214,93 +270,46 @@ class IndexController extends BaseController
->getArrayResult();
$ignored_songs = $this->_getIgnoredSongs();
$songs_played_raw = array_filter($songs_played_raw, function ($value) use ($ignored_songs) {
$songs_played_raw = array_filter($songs_played_raw, function($value) use ($ignored_songs)
{
return !(isset($ignored_songs[$value['song_id']]));
});
$songs_played_raw = array_values($songs_played_raw);
$cache->save($songs_played_raw, $cache_name, [], 60 * 5);
$cache->save($songs_played_raw, $cache_name, array(), 60*5);
}
return $songs_played_raw;
}
public function timelineAction()
protected function _getIgnoredSongs()
{
$songs_played_raw = $this->_getEligibleHistory();
$cache = $this->di->get('cache');
$song_hashes = $cache->get('station_center_ignored_songs');
$station_media_raw = $this->em->createQuery('SELECT sm, sp FROM Entity\StationMedia sm LEFT JOIN sm.playlists sp WHERE sm.station_id = :station_id')
->setParameter('station_id', $this->station->id)
->getArrayResult();
if (!$song_hashes)
{
$ignored_phrases = array('Offline', 'Sweeper', 'Bumper', 'Unknown');
$station_media = [];
foreach ($station_media_raw as $media) {
$station_media[$media['song_id']] = $media;
}
$qb = $this->em->createQueryBuilder();
$qb->select('s.id')->from('Entity\Song', 's');
$songs = [];
foreach ($songs_played_raw as $song_row) {
// Song has no recorded ending.
if ($song_row['timestamp_end'] == 0) {
continue;
foreach($ignored_phrases as $i => $phrase)
{
$qb->orWhere('s.text LIKE ?'.($i+1));
$qb->setParameter($i+1, '%'.$phrase.'%');
}
$song_row['stat_start'] = $song_row['listeners_start'];
$song_row['stat_end'] = $song_row['listeners_end'];
$song_row['stat_delta'] = $song_row['delta_total'];
$song_hashes_raw = $qb->getQuery()->getArrayResult();
$song_hashes = array();
if (isset($station_media[$song_row['song']['id']])) {
$media = $station_media[$song_row['song']['id']];
foreach($song_hashes_raw as $row)
$song_hashes[$row['id']] = $row['id'];
$song_row['playlists'] = \Packaged\Helpers\Arrays::ipull($media['playlists'], 'name', 'id');
} else {
$song_row['playlists'] = [];
}
$songs[] = $song_row;
$cache->save($song_hashes, 'station_center_ignored_songs', array(), 86400);
}
$format = $this->getParam('format', 'html');
if ($format == 'csv') {
$this->doNotRender();
$export_all = [];
$export_all[] = [
'Date',
'Time',
'Listeners',
'Delta',
'Likes',
'Dislikes',
'Track',
'Artist',
'Playlist(s)'
];
foreach ($songs as $song_row) {
$export_row = [
date('Y-m-d', $song_row['timestamp_start']),
date('g:ia', $song_row['timestamp_start']),
$song_row['stat_start'],
$song_row['stat_delta'],
$song_row['score_likes'],
$song_row['score_dislikes'],
($song_row['song']['title']) ? $song_row['song']['title'] : $song_row['song']['text'],
$song_row['song']['artist'],
implode(', ', $song_row['playlists']),
];
$export_all[] = $export_row;
}
$csv_file = \App\Export::csv($export_all);
$csv_filename = $this->station->getShortName() . '_timeline_' . date('Ymd') . '.csv';
return $this->renderStringAsFile($csv_file, 'text/csv', $csv_filename);
} else {
$songs = array_reverse($songs);
$this->view->songs = $songs;
}
return $song_hashes;
}
}

View file

@ -1,10 +1,23 @@
<?php
namespace Controller\Stations;
use Entity\Station;
use Entity\StationMount;
class MountsController extends BaseController
{
protected function preDispatch()
{
if (!$this->frontend->supportsMounts())
throw new \App\Exception(_('This feature is not currently supported on this station.'));
return parent::preDispatch();
}
protected function permissions()
{
return $this->acl->isAllowed('manage station mounts', $this->station->id);
}
public function indexAction()
{
$this->view->mounts = $this->station->mounts;
@ -15,18 +28,21 @@ class MountsController extends BaseController
$form_config = $this->config->forms->mount;
$form = new \App\Form($form_config);
if ($this->hasParam('id')) {
$record = $this->em->getRepository(StationMount::class)->findOneBy([
if ($this->hasParam('id'))
{
$record = $this->em->getRepository(StationMount::class)->findOneBy(array(
'id' => $this->getParam('id'),
'station_id' => $this->station->id,
]);
));
$form->setDefaults($record->toArray($this->em));
}
if (!empty($_POST) && $form->isValid($_POST)) {
if(!empty($_POST) && $form->isValid($_POST))
{
$data = $form->getValues();
if (!($record instanceof StationMount)) {
if (!($record instanceof StationMount))
{
$record = new StationMount;
$record->station = $this->station;
}
@ -37,7 +53,8 @@ class MountsController extends BaseController
$uow = $this->em->getUnitOfWork();
$uow->computeChangeSets();
if ($uow->isEntityScheduled($record)) {
if ($uow->isEntityScheduled($record))
{
$this->station->needs_restart = true;
$this->em->persist($this->station);
}
@ -45,9 +62,9 @@ class MountsController extends BaseController
$this->em->flush();
$this->em->refresh($this->station);
$this->alert('<b>' . _('Record updated.') . '</b>', 'green');
$this->alert('<b>'._('Record updated.').'</b>', 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => null]);
return $this->redirectFromHere(['action' => 'index', 'id' => NULL]);
}
$title = ($this->hasParam('id')) ? _('Edit Record') : _('Add Record');
@ -58,14 +75,13 @@ class MountsController extends BaseController
{
$id = (int)$this->getParam('id');
$record = $this->em->getRepository(StationMount::class)->findOneBy([
$record = $this->em->getRepository(StationMount::class)->findOneBy(array(
'id' => $id,
'station_id' => $this->station->id
]);
));
if ($record instanceof StationMount) {
if ($record instanceof StationMount)
$this->em->remove($record);
}
$this->station->needs_restart = true;
$this->em->persist($this->station);
@ -73,20 +89,7 @@ class MountsController extends BaseController
$this->em->refresh($this->station);
$this->alert('<b>' . _('Record deleted.') . '</b>', 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => null]);
}
protected function preDispatch()
{
if (!$this->frontend->supportsMounts()) {
throw new \App\Exception(_('This feature is not currently supported on this station.'));
}
return parent::preDispatch();
}
protected function permissions()
{
return $this->acl->isAllowed('manage station mounts', $this->station->id);
$this->alert('<b>'._('Record deleted.').'</b>', 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => NULL]);
}
}

View file

@ -1,28 +1,41 @@
<?php
namespace Controller\Stations;
use Entity\Station;
use Entity\StationPlaylist;
class PlaylistsController extends BaseController
{
protected function preDispatch()
{
if (!$this->backend->supportsMedia())
throw new \App\Exception(_('This feature is not currently supported on this station.'));
return parent::preDispatch();
}
protected function permissions()
{
return $this->acl->isAllowed('manage station media', $this->station->id);
}
public function indexAction()
{
$all_playlists = $this->station->playlists;
$total_weights = 0;
foreach ($all_playlists as $playlist) {
if ($playlist->is_enabled && $playlist->type == 'default') {
foreach($all_playlists as $playlist)
{
if ($playlist->is_enabled && $playlist->type == 'default')
$total_weights += $playlist->weight;
}
}
$playlists = [];
foreach ($all_playlists as $playlist) {
$playlists = array();
foreach($all_playlists as $playlist)
{
$playlist_row = $playlist->toArray($this->em);
if ($playlist->is_enabled && $playlist->type == 'default') {
$playlist_row['probability'] = round(($playlist->weight / $total_weights) * 100, 1) . '%';
}
if ($playlist->is_enabled && $playlist->type == 'default')
$playlist_row['probability'] = round(($playlist->weight / $total_weights) * 100, 1).'%';
$playlist_row['num_songs'] = count($playlist->media);
@ -37,18 +50,21 @@ class PlaylistsController extends BaseController
$form_config = $this->config->forms->playlist;
$form = new \App\Form($form_config);
if ($this->hasParam('id')) {
$record = $this->em->getRepository(StationPlaylist::class)->findOneBy([
if ($this->hasParam('id'))
{
$record = $this->em->getRepository(StationPlaylist::class)->findOneBy(array(
'id' => $this->getParam('id'),
'station_id' => $this->station->id
]);
));
$form->setDefaults($record->toArray($this->em));
}
if (!empty($_POST) && $form->isValid($_POST)) {
if(!empty($_POST) && $form->isValid($_POST))
{
$data = $form->getValues();
if (!($record instanceof StationPlaylist)) {
if (!($record instanceof StationPlaylist))
{
$record = new StationPlaylist;
$record->station = $this->station;
}
@ -59,7 +75,8 @@ class PlaylistsController extends BaseController
$uow = $this->em->getUnitOfWork();
$uow->computeChangeSets();
if ($uow->isEntityScheduled($record)) {
if ($uow->isEntityScheduled($record))
{
$this->station->needs_restart = true;
$this->em->persist($this->station);
}
@ -67,9 +84,9 @@ class PlaylistsController extends BaseController
$this->em->flush();
$this->em->refresh($this->station);
$this->alert('<b>' . _('Record updated.') . '</b>', 'green');
$this->alert('<b>'._('Record updated.').'</b>', 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => null]);
return $this->redirectFromHere(['action' => 'index', 'id' => NULL]);
}
$this->view->form = $form;
@ -80,32 +97,18 @@ class PlaylistsController extends BaseController
{
$id = (int)$this->getParam('id');
$record = $this->em->getRepository(StationPlaylist::class)->findOneBy([
$record = $this->em->getRepository(StationPlaylist::class)->findOneBy(array(
'id' => $id,
'station_id' => $this->station->id
]);
));
if ($record instanceof StationPlaylist) {
if ($record instanceof StationPlaylist)
$this->em->remove($record);
}
$this->em->flush();
$this->em->refresh($this->station);
$this->alert('<b>' . _('Record deleted.') . '</b>', 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => null]);
}
protected function preDispatch()
{
if (!$this->backend->supportsMedia()) {
throw new \App\Exception(_('This feature is not currently supported on this station.'));
}
return parent::preDispatch();
}
protected function permissions()
{
return $this->acl->isAllowed('manage station media', $this->station->id);
$this->alert('<b>'._('Record deleted.').'</b>', 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => NULL]);
}
}

View file

@ -1,6 +1,9 @@
<?php
namespace Controller\Stations;
use \Entity\Station;
use \Entity\Settings;
class ProfileController extends BaseController
{
public function indexAction()
@ -38,7 +41,8 @@ class ProfileController extends BaseController
$form->setDefaults($this->station->toArray($this->em));
if (!empty($_POST) && $form->isValid($_POST)) {
if(!empty($_POST) && $form->isValid($_POST))
{
$data = $form->getValues();
/*
@ -57,7 +61,7 @@ class ProfileController extends BaseController
$cache = $this->di->get('cache');
$cache->remove('stations');
return $this->redirectFromHere(['action' => 'index']);
return $this->redirectFromHere(array('action' => 'index'));
}
$this->view->form = $form;
@ -67,29 +71,29 @@ class ProfileController extends BaseController
{
$this->acl->checkPermission('manage station broadcasting', $this->station->id);
switch ($this->getParam('do', 'restart')) {
switch($this->getParam('do', 'restart'))
{
case "skip":
if (method_exists($this->backend, 'skip')) {
if (method_exists($this->backend, 'skip'))
$this->backend->skip();
}
$this->alert('<b>' . _('Song skipped.') . '</b>', 'green');
break;
$this->alert('<b>'._('Song skipped.').'</b>', 'green');
break;
case "stop":
$this->backend->stop();
break;
break;
case "start":
$this->backend->start();
break;
break;
case "restart":
default:
$this->backend->stop();
$this->backend->write();
$this->backend->start();
break;
break;
}
return $this->redirectFromHere(['action' => 'index']);
@ -99,21 +103,22 @@ class ProfileController extends BaseController
{
$this->acl->checkPermission('manage station broadcasting', $this->station->id);
switch ($this->getParam('do', 'restart')) {
switch($this->getParam('do', 'restart'))
{
case "stop":
$this->frontend->stop();
break;
break;
case "start":
$this->frontend->start();
break;
break;
case "restart":
default:
$this->frontend->stop();
$this->frontend->write();
$this->frontend->start();
break;
break;
}
return $this->redirectFromHere(['action' => 'index']);

View file

@ -1,53 +1,58 @@
<?php
namespace Controller\Stations;
use Entity\Station;
use Entity\StationMedia;
use Entity\StationPlaylist;
class ReportsController extends BaseController
{
protected function permissions()
{
return $this->acl->isAllowed('view station reports', $this->station->id);
}
public function performanceAction()
{
$automation_config = (array)$this->station->automation_settings;
if (isset($automation_config['threshold_days'])) {
if (isset($automation_config['threshold_days']))
$threshold_days = (int)$automation_config['threshold_days'];
} else {
else
$threshold_days = \AzuraCast\Sync\RadioAutomation::DEFAULT_THRESHOLD_DAYS;
}
$automation = new \AzuraCast\Sync\RadioAutomation($this->di);
$report_data = $automation->generateReport($this->station, $threshold_days);
// Do not show songs that are not in playlists.
$report_data = array_filter($report_data, function ($media) {
if (empty($media['playlists'])) {
$report_data = array_filter($report_data, function($media) {
if (empty($media['playlists']))
return false;
}
return true;
});
switch (strtolower($this->getParam('format'))) {
switch(strtolower($this->getParam('format')))
{
case 'csv':
$this->doNotRender();
$export_csv = [
[
'Song Title',
'Song Artist',
'Filename',
'Length',
'Current Playlist',
'Delta Joins',
'Delta Losses',
'Delta Total',
'Play Count',
'Play Percentage',
'Weighted Ratio',
]
];
$export_csv = [[
'Song Title',
'Song Artist',
'Filename',
'Length',
'Current Playlist',
'Delta Joins',
'Delta Losses',
'Delta Total',
'Play Count',
'Play Percentage',
'Weighted Ratio',
]];
foreach ($report_data as $row) {
foreach($report_data as $row)
{
$export_csv[] = [
$row['title'],
$row['artist'],
@ -60,26 +65,26 @@ class ReportsController extends BaseController
$row['delta_total'],
$row['num_plays'],
$row['percent_plays'] . '%',
$row['percent_plays'].'%',
$row['ratio'],
];
}
$csv_file = \App\Export::csv($export_csv);
$csv_filename = $this->station->getShortName() . '_media_' . date('Ymd') . '.csv';
$csv_filename = $this->station->getShortName().'_media_'.date('Ymd').'.csv';
return $this->renderStringAsFile($csv_file, 'text/csv', $csv_filename);
break;
break;
case 'json':
$this->response->getBody()->write(json_encode($report_data));
return $this->response;
break;
break;
case 'html':
default:
$this->view->report_data = $report_data;
break;
break;
}
return true;
@ -91,34 +96,36 @@ class ReportsController extends BaseController
->setParameter('station_id', $this->station->id)
->getArrayResult();
$dupes = [];
$songs_to_compare = [];
$dupes = array();
$songs_to_compare = array();
// Find exact duplicates and sort other songs into a searchable array.
foreach ($media_raw as $media_row) {
if (isset($songs_to_compare[$media_row['song_id']])) {
foreach($media_raw as $media_row)
{
if (isset($songs_to_compare[$media_row['song_id']]))
$dupes[] = [$songs_to_compare[$media_row['song_id']], $media_row];
} else {
else
$songs_to_compare[$media_row['song_id']] = $media_row;
}
}
foreach ($songs_to_compare as $song_id => $media_row) {
foreach($songs_to_compare as $song_id => $media_row)
{
unset($songs_to_compare[$song_id]);
$media_text = strtolower(preg_replace("/[^A-Za-z0-9]/", '', $media_row['song']['text']));
$song_dupes = [];
foreach ($songs_to_compare as $search_song_id => $search_media_row) {
$song_dupes = array();
foreach($songs_to_compare as $search_song_id => $search_media_row)
{
$search_media_text = strtolower(preg_replace("/[^A-Za-z0-9]/", '', $search_media_row['song']['text']));
$similarity = levenshtein($media_text, $search_media_text);
if ($similarity <= 5) {
if ($similarity <= 5)
$song_dupes[] = $search_media_row;
}
}
if (count($song_dupes) > 0) {
if (count($song_dupes) > 0)
{
$song_dupes[] = $media_row;
$dupes[] = $song_dupes;
}
@ -136,7 +143,8 @@ class ReportsController extends BaseController
'station_id' => $this->station->id
]);
if ($media instanceof StationMedia) {
if ($media instanceof StationMedia)
{
$path = $media->getFullPath();
@unlink($path);
@ -148,9 +156,4 @@ class ReportsController extends BaseController
return $this->redirectFromHere(['action' => 'duplicates', 'media_id' => null]);
}
protected function permissions()
{
return $this->acl->isAllowed('view station reports', $this->station->id);
}
}

View file

@ -1,24 +1,43 @@
<?php
namespace Controller\Stations;
use Entity\Settings;
use Entity\Station;
use Entity\StationStreamer;
use Entity\StationStreamer as Record;
class StreamersController extends BaseController
{
protected function preDispatch()
{
if (!$this->frontend->supportsStreamers())
throw new \App\Exception(_('This feature is not currently supported on this station.'));
return parent::preDispatch();
}
protected function permissions()
{
return $this->acl->isAllowed('manage station streamers', $this->station->id);
}
public function indexAction()
{
if (!$this->station->enable_streamers) {
if ($this->hasParam('enable')) {
if (!$this->station->enable_streamers)
{
if ($this->hasParam('enable'))
{
$this->station->enable_streamers = true;
$this->em->persist($this->station);
$this->em->flush();
$this->alert('<b>' . _('Streamers enabled!') . '</b><br>' . _('You can now set up streamer (DJ) accounts.'),
'green');
$this->alert('<b>'._('Streamers enabled!').'</b><br>'._('You can now set up streamer (DJ) accounts.'), 'green');
return $this->redirectFromHere(['enable' => null]);
} else {
}
else
{
return $this->render('controller::disabled');
}
}
@ -32,18 +51,21 @@ class StreamersController extends BaseController
$form_config = $this->config->forms->streamer;
$form = new \App\Form($form_config);
if ($this->hasParam('id')) {
$record = $this->em->getRepository(Record::class)->findOneBy([
if ($this->hasParam('id'))
{
$record = $this->em->getRepository(Record::class)->findOneBy(array(
'id' => $this->getParam('id'),
'station_id' => $this->station->id
]);
));
$form->setDefaults($record->toArray($this->em));
}
if (!empty($_POST) && $form->isValid($_POST)) {
if(!empty($_POST) && $form->isValid($_POST))
{
$data = $form->getValues();
if (!($record instanceof Record)) {
if (!($record instanceof Record))
{
$record = new Record;
$record->station = $this->station;
}
@ -54,9 +76,9 @@ class StreamersController extends BaseController
$this->em->refresh($this->station);
$this->alert('<b>' . _('Streamer account updated!') . '</b>', 'green');
$this->alert('<b>'._('Streamer account updated!').'</b>', 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => null]);
return $this->redirectFromHere(['action' => 'index', 'id' => NULL]);
}
$title = (($this->hasParam('id')) ? _('Edit Streamer') : _('Add Streamer'));
@ -67,34 +89,19 @@ class StreamersController extends BaseController
{
$id = (int)$this->getParam('id');
$record = $this->em->getRepository(Record::class)->findOneBy([
$record = $this->em->getRepository(Record::class)->findOneBy(array(
'id' => $id,
'station_id' => $this->station->id
]);
));
if ($record instanceof Record) {
if ($record instanceof Record)
$this->em->remove($record);
}
$this->em->flush();
$this->em->refresh($this->station);
$this->alert('<b>' . _('Record deleted.') . '</b>', 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => null]);
}
protected function preDispatch()
{
if (!$this->frontend->supportsStreamers()) {
throw new \App\Exception(_('This feature is not currently supported on this station.'));
}
return parent::preDispatch();
}
protected function permissions()
{
return $this->acl->isAllowed('manage station streamers', $this->station->id);
$this->alert('<b>'._('Record deleted.').'</b>', 'green');
return $this->redirectFromHere(['action' => 'index', 'id' => NULL]);
}
}

View file

@ -1,6 +1,8 @@
<?php
namespace Controller\Stations;
use Entity\Station;
class UtilController extends BaseController
{
/**
@ -8,18 +10,20 @@ class UtilController extends BaseController
*/
public function playlistAction()
{
$stations = [$this->station];
$stations = array($this->station);
$this->doNotRender();
$format = strtolower($this->getParam('format', 'pls'));
switch ($format) {
switch($format)
{
// M3U Playlist Format
case "m3u":
$m3u_lines = [];
$m3u_lines = array();
$m3u_lines[] = '#EXTM3U';
$i = 0;
foreach ($stations as $station) {
foreach($stations as $station)
{
$fa = $station->getFrontendAdapter($this->di);
$stream_url = $fa->getStreamUrl();
@ -31,54 +35,56 @@ class UtilController extends BaseController
$m3u_file = implode("\r\n", $m3u_lines);
header('Content-Type: audio/x-mpegurl');
header('Content-Disposition: attachment; filename="' . $this->station->getShortName() . '.m3u"');
header('Content-Disposition: attachment; filename="'.$this->station->getShortName().'.m3u"');
echo $m3u_file;
break;
break;
// Euro Truck Simulator 2
case "ets":
$ets_lines = [];
$ets_lines = array();
$ets_i = 0;
foreach ($stations as $station) {
foreach ($station['streams'] as $stream) {
if (!$stream['is_active'] || !$stream['is_default']) {
foreach($stations as $station)
{
foreach($station['streams'] as $stream)
{
if (!$stream['is_active'] || !$stream['is_default'])
continue;
}
$ets_line = [
$ets_line = array(
str_replace('|', '', $stream['stream_url']),
str_replace('|', '', $station['name']),
str_replace('|', '', $station['genre']),
'EN',
128,
1,
];
);
$ets_lines[] = ' stream_data[' . $ets_i . ']: "' . implode('|', $ets_line) . '"';
$ets_lines[] = ' stream_data['.$ets_i.']: "'.implode('|', $ets_line).'"';
$ets_i++;
}
}
$ets_file = "SiiNunit\n{\nlive_stream_def : _nameless.0662.83F8 {\n";
$ets_file .= " stream_data: " . count($ets_lines) . "\n";
$ets_file = "SiiNunit\n{\nlive_stream_def : _nameless.0662.83F8 {\n";
$ets_file .= " stream_data: ".count($ets_lines)."\n";
$ets_file .= implode("\n", $ets_lines);
$ets_file .= "\n}\n\n}";
header('Content-Type: text/plain');
header('Content-Disposition: attachment; filename="live_streams.sii"');
echo $ets_file;
break;
break;
// PLS Playlist Format
case "pls":
default:
$output = [];
$output = array();
$output[] = '[playlist]';
$output[] = 'NumberOfEntries=' . count($stations);
$output[] = 'NumberOfEntries='.count($stations);
$i = 1;
foreach ($stations as $station) {
foreach($stations as $station)
{
$fa = $station->getFrontendAdapter($this->di);
$stream_url = $fa->getStreamUrl();
@ -91,9 +97,9 @@ class UtilController extends BaseController
}
header('Content-Type: audio/x-scpls');
header('Content-Disposition: attachment; filename="' . $this->station->getShortName() . '.pls"');
header('Content-Disposition: attachment; filename="'.$this->station->getShortName().'.pls"');
echo implode("\r\n", $output);
break;
break;
}
}

View file

@ -1,6 +1,8 @@
<?php
namespace Entity;
use \Doctrine\Common\Collections\ArrayCollection;
/**
* @Table(name="analytics", indexes={
* @index(name="search_idx", columns={"type", "timestamp"})
@ -9,13 +11,21 @@ namespace Entity;
*/
class Analytics extends \App\Doctrine\Entity
{
public function __construct()
{
$this->timestamp = time();
$this->number_min = 0;
$this->number_max = 0;
$this->number_avg = 0;
}
/**
* @Column(name="id", type="integer")
* @Id
* @GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/** @Column(name="station_id", type="integer", nullable=true) */
protected $station_id;
@ -24,13 +34,13 @@ class Analytics extends \App\Doctrine\Entity
/** @Column(name="timestamp", type="integer") */
protected $timestamp;
/** @Column(name="number_min", type="integer") */
protected $number_min;
/** @Column(name="number_max", type="integer") */
protected $number_max;
/** @Column(name="number_avg", type="integer") */
protected $number_avg;
@ -41,19 +51,11 @@ class Analytics extends \App\Doctrine\Entity
* })
*/
protected $station;
public function __construct()
{
$this->timestamp = time();
$this->number_min = 0;
$this->number_max = 0;
$this->number_avg = 0;
}
public function calculateFromArray($number_set)
{
$number_set = (array)$number_set;
$this->number_min = (int)min($number_set);
$this->number_max = (int)max($number_set);
$this->number_avg = (int)(array_sum($number_set) / count($number_set));

View file

@ -1,6 +1,8 @@
<?php
namespace Entity;
use \Doctrine\Common\Collections\ArrayCollection;
/**
* @Table(name="api_keys")
* @Entity
@ -8,6 +10,19 @@ namespace Entity;
*/
class ApiKey extends \App\Doctrine\Entity
{
public function __construct()
{
$this->calls_made = 0;
$this->created = time();
}
/** @PrePersist */
public function preSave()
{
if (!$this->id)
$this->id = sha1(mt_rand(0, microtime(true)));
}
/**
* @Column(name="id", type="string", length=50)
* @Id
@ -22,18 +37,4 @@ class ApiKey extends \App\Doctrine\Entity
/** @Column(name="created", type="integer") */
protected $created;
public function __construct()
{
$this->calls_made = 0;
$this->created = time();
}
/** @PrePersist */
public function preSave()
{
if (!$this->id) {
$this->id = sha1(mt_rand(0, microtime(true)));
}
}
}

View file

@ -10,12 +10,12 @@ class RolePermissionRepository extends \App\Doctrine\Repository
$all_permissions = $this->fetchArray();
$roles = [];
foreach ($all_permissions as $row) {
if ($row['station_id']) {
foreach($all_permissions as $row)
{
if ($row['station_id'])
$roles[$row['role_id']]['stations'][$row['station_id']][] = $row['action_name'];
} else {
else
$roles[$row['role_id']]['global'][] = $row['action_name'];
}
}
return $roles;
@ -26,12 +26,12 @@ class RolePermissionRepository extends \App\Doctrine\Repository
$role_has_action = $this->findBy(['role_id' => $role->id]);
$result = [];
foreach ($role_has_action as $row) {
if ($row['station_id']) {
$result['actions_' . $row['station_id']][] = $row['action_name'];
} else {
foreach($role_has_action as $row)
{
if ($row['station_id'])
$result['actions_'.$row['station_id']][] = $row['action_name'];
else
$result['actions_global'][] = $row['action_name'];
}
}
return $result;
@ -39,26 +39,26 @@ class RolePermissionRepository extends \App\Doctrine\Repository
public function setActionsForRole(Entity\Role $role, $post_values)
{
$this->_em->createQuery('DELETE FROM ' . $this->_entityName . ' rp WHERE rp.role_id = :role_id')
$this->_em->createQuery('DELETE FROM '.$this->_entityName.' rp WHERE rp.role_id = :role_id')
->setParameter('role_id', $role->id)
->execute();
foreach ($post_values as $post_key => $post_value) {
foreach($post_values as $post_key => $post_value)
{
list($post_key_action, $post_key_id) = explode('_', $post_key);
if ($post_key_action !== 'actions' || empty($post_value)) {
if ($post_key_action !== 'actions' || empty($post_value))
continue;
}
foreach ((array)$post_value as $action_name) {
foreach((array)$post_value as $action_name)
{
$record_info = [
'role_id' => $role->id,
'action_name' => $action_name,
];
if ($post_key_id !== 'global') {
if ($post_key_id !== 'global')
$record_info['station_id'] = $post_key_id;
}
$record = new Entity\RolePermission;
$record->fromArray($this->_em, $record_info);

View file

@ -10,9 +10,8 @@ class SettingsRepository extends \App\Doctrine\Repository
*/
public function setSettings($settings)
{
foreach ($settings as $setting_key => $setting_value) {
foreach($settings as $setting_key => $setting_value)
$this->setSetting($setting_key, $setting_value, false);
}
$this->clearCache();
}
@ -26,7 +25,8 @@ class SettingsRepository extends \App\Doctrine\Repository
{
$record = $this->findOneBy(['setting_key' => $key]);
if (!($record instanceof Entity\Settings)) {
if (!($record instanceof Entity\Settings))
{
$record = new Entity\Settings;
$record->setting_key = $key;
}
@ -36,9 +36,61 @@ class SettingsRepository extends \App\Doctrine\Repository
$this->_em->persist($record);
$this->_em->flush();
if ($flush_cache) {
if ($flush_cache)
$this->clearCache();
}
/**
* @param $key
* @param null $default_value
* @param bool $cached
* @return mixed|null
*/
public function getSetting($key, $default_value = NULL, $cached = TRUE)
{
$settings = $this->fetchArray($cached);
if (isset($settings[$key]))
return $settings[$key];
else
return $default_value;
}
/**
* @return array
*/
public function fetchAll()
{
$all_records_raw = $this->findAll();
$all_records = array();
foreach($all_records_raw as $record)
$all_records[$record['setting_key']] = $record['setting_value'];
return $all_records;
}
/**
* @param bool $cached
* @param null $order_by
* @param string $order_dir
* @return array
*/
public function fetchArray($cached = true, $order_by = NULL, $order_dir = 'ASC')
{
static $settings;
if (!$settings || !$cached)
{
$settings_raw = $this->_em->createQuery('SELECT s FROM '.$this->_entityName.' s ORDER BY s.setting_key ASC')
->getArrayResult();
$settings = array();
foreach((array)$settings_raw as $setting)
$settings[$setting['setting_key']] = $setting['setting_value'];
}
return $settings;
}
/**
@ -49,59 +101,4 @@ class SettingsRepository extends \App\Doctrine\Repository
// Regenerate cache and flush static value.
$this->fetchArray(false);
}
/**
* @param bool $cached
* @param null $order_by
* @param string $order_dir
* @return array
*/
public function fetchArray($cached = true, $order_by = null, $order_dir = 'ASC')
{
static $settings;
if (!$settings || !$cached) {
$settings_raw = $this->_em->createQuery('SELECT s FROM ' . $this->_entityName . ' s ORDER BY s.setting_key ASC')
->getArrayResult();
$settings = [];
foreach ((array)$settings_raw as $setting) {
$settings[$setting['setting_key']] = $setting['setting_value'];
}
}
return $settings;
}
/**
* @param $key
* @param null $default_value
* @param bool $cached
* @return mixed|null
*/
public function getSetting($key, $default_value = null, $cached = true)
{
$settings = $this->fetchArray($cached);
if (isset($settings[$key])) {
return $settings[$key];
} else {
return $default_value;
}
}
/**
* @return array
*/
public function fetchAll()
{
$all_records_raw = $this->findAll();
$all_records = [];
foreach ($all_records_raw as $record) {
$all_records[$record['setting_key']] = $record['setting_value'];
}
return $all_records;
}
}

View file

@ -11,17 +11,18 @@ class SongHistoryRepository extends \App\Doctrine\Repository
*/
public function getHistoryForStation(Entity\Station $station, $num_entries = 5)
{
$history = $this->_em->createQuery('SELECT sh, s FROM ' . $this->_entityName . ' sh JOIN sh.song s WHERE sh.station_id = :station_id ORDER BY sh.id DESC')
$history = $this->_em->createQuery('SELECT sh, s FROM '.$this->_entityName.' sh JOIN sh.song s WHERE sh.station_id = :station_id ORDER BY sh.id DESC')
->setParameter('station_id', $station->id)
->setMaxResults($num_entries)
->getArrayResult();
$return = [];
foreach ($history as $sh) {
$history = [
'played_at' => $sh['timestamp_start'],
'song' => Entity\Song::api($sh['song']),
];
$return = array();
foreach($history as $sh)
{
$history = array(
'played_at' => $sh['timestamp_start'],
'song' => Entity\Song::api($sh['song']),
);
$return[] = $history;
}
@ -46,7 +47,8 @@ class SongHistoryRepository extends \App\Doctrine\Repository
$listeners = (int)$np['listeners']['current'];
if ($last_sh->song_id == $song->id) {
if ($last_sh->song_id == $song->id)
{
// Updating the existing SongHistory item with a new data point.
$delta_points = (array)$last_sh->delta_points;
$delta_points[] = $listeners;
@ -56,9 +58,12 @@ class SongHistoryRepository extends \App\Doctrine\Repository
$this->_em->persist($last_sh);
$this->_em->flush();
return null;
} else {
}
else
{
// Wrapping up processing on the previous SongHistory item (if present).
if ($last_sh instanceof Entity\SongHistory) {
if ($last_sh instanceof Entity\SongHistory)
{
$last_sh->timestamp_end = time();
$last_sh->listeners_end = $listeners;
@ -71,18 +76,18 @@ class SongHistoryRepository extends \App\Doctrine\Repository
$delta_negative = 0;
$delta_total = 0;
for ($i = 1; $i < count($delta_points); $i++) {
for ($i = 1; $i < count($delta_points); $i++)
{
$current_delta = $delta_points[$i];
$previous_delta = $delta_points[$i - 1];
$delta_delta = $current_delta - $previous_delta;
$delta_total += $delta_delta;
if ($delta_delta > 0) {
if ($delta_delta > 0)
$delta_positive += $delta_delta;
} elseif ($delta_delta < 0) {
elseif ($delta_delta < 0)
$delta_negative += abs($delta_delta);
}
}
$last_sh->delta_positive = $delta_positive;

View file

@ -5,6 +5,27 @@ use Entity;
class SongRepository extends \App\Doctrine\Repository
{
/**
* Return a song by its ID, including resolving merged song IDs.
*
* @param $song_hash
* @return null|object
*/
public function getById($song_hash)
{
$record = $this->find($song_hash);
if ($record instanceof Entity\Song)
{
if (!empty($record->merge_song_id))
return $this->getById($record->merge_song_id);
else
return $record;
}
return null;
}
/**
* Get a list of all song IDs.
*
@ -12,7 +33,7 @@ class SongRepository extends \App\Doctrine\Repository
*/
public function getIds()
{
$ids_raw = $this->_em->createQuery('SELECT s.id FROM ' . $this->_entityName . ' s')
$ids_raw = $this->_em->createQuery('SELECT s.id FROM '.$this->_entityName.' s')
->getArrayResult();
return \Packaged\Helpers\Arrays::ipull($ids_raw, 'id');
@ -24,8 +45,10 @@ class SongRepository extends \App\Doctrine\Repository
$obj = $this->getById($song_hash);
if ($obj instanceof Entity\Song) {
if ($is_radio_play) {
if ($obj instanceof Entity\Song)
{
if ($is_radio_play)
{
$obj->last_played = time();
$obj->play_count += 1;
}
@ -34,27 +57,27 @@ class SongRepository extends \App\Doctrine\Repository
$this->_em->flush();
return $obj;
} else {
if (!is_array($song_info)) {
$song_info = ['text' => $song_info];
}
}
else
{
if (!is_array($song_info))
$song_info = array('text' => $song_info);
$obj = new Entity\Song;
$obj->id = $song_hash;
if (empty($song_info['text'])) {
$song_info['text'] = $song_info['artist'] . ' - ' . $song_info['title'];
}
if (empty($song_info['text']))
$song_info['text'] = $song_info['artist'].' - '.$song_info['title'];
$obj->text = $song_info['text'];
$obj->title = $song_info['title'];
$obj->artist = $song_info['artist'];
if (isset($song_info['image_url'])) {
if (isset($song_info['image_url']))
$obj->image_url = $song_info['image_url'];
}
if ($is_radio_play) {
if ($is_radio_play)
{
$obj->last_played = time();
$obj->play_count = 1;
}
@ -65,25 +88,4 @@ class SongRepository extends \App\Doctrine\Repository
return $obj;
}
}
/**
* Return a song by its ID, including resolving merged song IDs.
*
* @param $song_hash
* @return null|object
*/
public function getById($song_hash)
{
$record = $this->find($song_hash);
if ($record instanceof Entity\Song) {
if (!empty($record->merge_song_id)) {
return $this->getById($record->merge_song_id);
} else {
return $record;
}
}
return null;
}
}

View file

@ -11,7 +11,7 @@ class StationMediaRepository extends \App\Doctrine\Repository
*/
public function getRequestable(Entity\Station $station)
{
return $this->_em->createQuery('SELECT sm FROM ' . $this->_entityName . ' sm WHERE sm.station_id = :station_id ORDER BY sm.artist ASC, sm.title ASC')
return $this->_em->createQuery('SELECT sm FROM '.$this->_entityName.' sm WHERE sm.station_id = :station_id ORDER BY sm.artist ASC, sm.title ASC')
->setParameter('station_id', $station->id)
->getArrayResult();
}
@ -23,7 +23,7 @@ class StationMediaRepository extends \App\Doctrine\Repository
*/
public function getByArtist(Entity\Station $station, $artist_name)
{
return $this->_em->createQuery('SELECT sm FROM ' . $this->_entityName . ' sm WHERE sm.station_id = :station_id AND sm.artist LIKE :artist ORDER BY sm.title ASC')
return $this->_em->createQuery('SELECT sm FROM '.$this->_entityName.' sm WHERE sm.station_id = :station_id AND sm.artist LIKE :artist ORDER BY sm.title ASC')
->setParameter('station_id', $station->id)
->setParameter('artist', $artist_name)
->getArrayResult();
@ -39,8 +39,7 @@ class StationMediaRepository extends \App\Doctrine\Repository
$db = $this->_em->getConnection();
$table_name = $this->_em->getClassMetadata(__CLASS__)->getTableName();
$stmt = $db->executeQuery('SELECT sm.* FROM ' . $db->quoteIdentifier($table_name) . ' AS sm WHERE sm.station_id = ? AND CONCAT(sm.title, \' \', sm.artist, \' \', sm.album) LIKE ?',
[$station->id, '%' . addcslashes($query, "%_") . '%']);
$stmt = $db->executeQuery('SELECT sm.* FROM '.$db->quoteIdentifier($table_name).' AS sm WHERE sm.station_id = ? AND CONCAT(sm.title, \' \', sm.artist, \' \', sm.album) LIKE ?', array($station->id, '%'.addcslashes($query, "%_").'%'));
$results = $stmt->fetchAll();
return $results;
}
@ -56,18 +55,21 @@ class StationMediaRepository extends \App\Doctrine\Repository
$record = $this->findOneBy(['station_id' => $station->id, 'path' => $short_path]);
if (!($record instanceof Entity\StationMedia)) {
if (!($record instanceof Entity\StationMedia))
{
$record = new Entity\StationMedia;
$record->station = $station;
$record->path = $short_path;
}
try {
try
{
$song_info = $record->loadFromFile();
if (!empty($song_info)) {
if (!empty($song_info))
$record->song = $this->_em->getRepository(Entity\Song::class)->getOrCreate($song_info);
}
} catch (\Exception $e) {
}
catch(\Exception $e)
{
$record->moveToNotProcessed();
throw $e;
}

View file

@ -11,10 +11,25 @@ class StationRepository extends \App\Doctrine\Repository
*/
public function fetchAll()
{
return $this->_em->createQuery('SELECT s FROM ' . $this->_entityName . ' s ORDER BY s.name ASC')
return $this->_em->createQuery('SELECT s FROM '.$this->_entityName.' s ORDER BY s.name ASC')
->execute();
}
/**
* @param bool $cached
* @param null $order_by
* @param string $order_dir
* @return array
*/
public function fetchArray($cached = true, $order_by = NULL, $order_dir = 'ASC')
{
$stations = parent::fetchArray($cached, $order_by, $order_dir);
foreach($stations as &$station)
$station['short_name'] = Entity\Station::getStationShortName($station['name']);
return $stations;
}
/**
* @param bool $add_blank
* @param \Closure|NULL $display
@ -22,60 +37,28 @@ class StationRepository extends \App\Doctrine\Repository
* @param string $order_by
* @return array
*/
public function fetchSelect($add_blank = false, \Closure $display = null, $pk = 'id', $order_by = 'name')
public function fetchSelect($add_blank = FALSE, \Closure $display = NULL, $pk = 'id', $order_by = 'name')
{
$select = [];
$select = array();
// Specify custom text in the $add_blank parameter to override.
if ($add_blank !== false) {
$select[''] = ($add_blank === true) ? 'Select...' : $add_blank;
}
if ($add_blank !== FALSE)
$select[''] = ($add_blank === TRUE) ? 'Select...' : $add_blank;
// Build query for records.
$results = $this->fetchArray();
// Assemble select values and, if necessary, call $display callback.
foreach ((array)$results as $result) {
foreach((array)$results as $result)
{
$key = $result[$pk];
$value = ($display === null) ? $result['name'] : $display($result);
$value = ($display === NULL) ? $result['name'] : $display($result);
$select[$key] = $value;
}
return $select;
}
/**
* @param bool $cached
* @param null $order_by
* @param string $order_dir
* @return array
*/
public function fetchArray($cached = true, $order_by = null, $order_dir = 'ASC')
{
$stations = parent::fetchArray($cached, $order_by, $order_dir);
foreach ($stations as &$station) {
$station['short_name'] = Entity\Station::getStationShortName($station['name']);
}
return $stations;
}
/**
* @param $short_code
* @return null|object
*/
public function findByShortCode($short_code)
{
$short_names = $this->getShortNameLookup();
if (isset($short_names[$short_code])) {
$id = $short_names[$short_code]['id'];
return $this->find($id);
}
return null;
}
/**
* @param bool $cached
* @return array
@ -84,14 +67,30 @@ class StationRepository extends \App\Doctrine\Repository
{
$stations = $this->fetchArray($cached);
$lookup = [];
foreach ($stations as $station) {
$lookup = array();
foreach ($stations as $station)
$lookup[$station['short_name']] = $station;
}
return $lookup;
}
/**
* @param $short_code
* @return null|object
*/
public function findByShortCode($short_code)
{
$short_names = $this->getShortNameLookup();
if (isset($short_names[$short_code]))
{
$id = $short_names[$short_code]['id'];
return $this->find($id);
}
return NULL;
}
/**
* Create a station based on the specified data.
*
@ -105,9 +104,9 @@ class StationRepository extends \App\Doctrine\Repository
$station->fromArray($this->_em, $data);
// Create path for station.
$station_base_dir = realpath(APP_INCLUDE_ROOT . '/..') . '/stations';
$station_base_dir = realpath(APP_INCLUDE_ROOT.'/..').'/stations';
$station_dir = $station_base_dir . '/' . $station->getShortName();
$station_dir = $station_base_dir.'/'.$station->getShortName();
$station->setRadioBaseDir($station_dir);
$this->_em->persist($station);
@ -130,11 +129,13 @@ class StationRepository extends \App\Doctrine\Repository
$backend_adapter = $station->getBackendAdapter($di);
// Create default mountpoints if station supports them.
if ($frontend_adapter->supportsMounts()) {
if ($frontend_adapter->supportsMounts())
{
// Create default mount points.
$mount_points = $frontend_adapter->getDefaultMounts();
foreach ($mount_points as $mount_point) {
foreach($mount_points as $mount_point)
{
$mount_point['station'] = $station;
$mount_record = new Entity\StationMount;
$mount_record->fromArray($this->_em, $mount_point);
@ -168,7 +169,8 @@ class StationRepository extends \App\Doctrine\Repository
$frontend = $station->getFrontendAdapter($di);
$backend = $station->getBackendAdapter($di);
if ($frontend->hasCommand() || $backend->hasCommand()) {
if ($frontend->hasCommand() || $backend->hasCommand())
{
/** @var \Supervisor\Supervisor */
$supervisor = $di['supervisor'];

View file

@ -17,50 +17,49 @@ class StationRequestRepository extends \App\Doctrine\Repository
public function submit(Entity\Station $station, $track_id, $is_authenticated = false)
{
// Forbid web crawlers from using this feature.
if (\App\Utilities::is_crawler()) {
if (\App\Utilities::is_crawler())
throw new \App\Exception('Search engine crawlers are not permitted to use this feature.');
}
// Verify that the station supports requests.
if (!$station->enable_requests) {
if (!$station->enable_requests)
throw new \App\Exception('This station does not accept requests currently.');
}
// Verify that Track ID exists with station.
$media_repo = $this->_em->getRepository(Entity\StationMedia::class);
$media_item = $media_repo->findOneBy(['id' => $track_id, 'station_id' => $station->id]);
$media_item = $media_repo->findOneBy(array('id' => $track_id, 'station_id' => $station->id));
if (!($media_item instanceof Entity\StationMedia)) {
if (!($media_item instanceof Entity\StationMedia))
throw new \App\Exception('The song ID you specified could not be found in the station.');
}
// Check if the song is already enqueued as a request.
$pending_request = $this->_em->createQuery('SELECT sr FROM ' . $this->_entityName . ' sr WHERE sr.track_id = :track_id AND sr.station_id = :station_id AND sr.played_at = 0')
$pending_request = $this->_em->createQuery('SELECT sr FROM '.$this->_entityName.' sr WHERE sr.track_id = :track_id AND sr.station_id = :station_id AND sr.played_at = 0')
->setParameter('track_id', $track_id)
->setParameter('station_id', $station->id)
->setMaxResults(1)
->getOneOrNullResult();
if ($pending_request) {
if ($pending_request)
throw new \App\Exception('Duplicate request: this song is already a pending request on this station.');
}
// Check the most recent song history.
try {
try
{
$last_play_time = $this->_em->createQuery('SELECT sh.timestamp_start FROM Entity\SongHistory sh WHERE sh.song_id = :song_id AND sh.station_id = :station_id ORDER BY sh.timestamp_start DESC')
->setParameter('song_id', $media_item->song_id)
->setParameter('station_id', $station->id)
->setMaxResults(1)
->getSingleScalarResult();
} catch (\Exception $e) {
}
catch(\Exception $e)
{
$last_play_time = 0;
}
if ($last_play_time && $last_play_time > (time() - 60 * 30)) {
if ($last_play_time && $last_play_time > (time() - 60*30))
throw new \App\Exception('This song has been played too recently on the station.');
}
if (!$is_authenticated) {
if (!$is_authenticated)
{
// Check for an existing request from this user.
$user_ip = $_SERVER['REMOTE_ADDR'];
@ -72,9 +71,8 @@ class StationRequestRepository extends \App\Doctrine\Repository
->setParameter('threshold', time() - $threshold_seconds)
->getArrayResult();
if (count($recent_requests) > 0) {
if (count($recent_requests) > 0)
throw new \App\Exception('You have submitted a request too recently! Please wait a while before submitting another one.');
}
}
// Save request locally.

View file

@ -16,9 +16,8 @@ class StationStreamerRepository extends \App\Doctrine\Repository
public function authenticate(Entity\Station $station, $username, $password)
{
// Extra safety check for the station's streamer status.
if (!$station->enable_streamers) {
if (!$station->enable_streamers)
return false;
}
$streamer = $this->findOneBy([
'station_id' => $station->id,
@ -26,9 +25,8 @@ class StationStreamerRepository extends \App\Doctrine\Repository
'is_active' => 1
]);
if (!($streamer instanceof Entity\StationStreamer)) {
if (!($streamer instanceof Entity\StationStreamer))
return false;
}
return (strcmp($streamer->streamer_password, $password) === 0);
}

View file

@ -14,15 +14,13 @@ class UserRepository extends \App\Doctrine\Repository
{
$login_info = $this->findOneBy(['email' => $username]);
if (!($login_info instanceof Entity\User)) {
return false;
}
if (!($login_info instanceof Entity\User))
return FALSE;
if ($login_info->verifyPassword($password)) {
if ($login_info->verifyPassword($password))
return $login_info;
} else {
return false;
}
else
return FALSE;
}
/**
@ -35,7 +33,8 @@ class UserRepository extends \App\Doctrine\Repository
{
$user = $this->findOneBy(['email' => $email]);
if (!($user instanceof Entity\User)) {
if (!($user instanceof Entity\User))
{
$user = new Entity\User;
$user->email = $email;
$user->name = $email;

View file

@ -1,7 +1,7 @@
<?php
namespace Entity;
use Doctrine\Common\Collections\ArrayCollection;
use \Doctrine\Common\Collections\ArrayCollection;
/**
* @Table(name="role")
@ -9,6 +9,12 @@ use Doctrine\Common\Collections\ArrayCollection;
*/
class Role extends \App\Doctrine\Entity
{
public function __construct()
{
$this->users = new ArrayCollection;
$this->permissions = new ArrayCollection;
}
/**
* @Column(name="id", type="integer")
* @Id
@ -19,15 +25,9 @@ class Role extends \App\Doctrine\Entity
/** @Column(name="name", type="string", length=100) */
protected $name;
/** @ManyToMany(targetEntity="User", mappedBy="roles") */
/** @ManyToMany(targetEntity="User", mappedBy="roles")*/
protected $users;
/** @OneToMany(targetEntity="Entity\RolePermission", mappedBy="role") */
protected $permissions;
public function __construct()
{
$this->users = new ArrayCollection;
$this->permissions = new ArrayCollection;
}
}

View file

@ -1,6 +1,8 @@
<?php
namespace Entity;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Table(name="role_permissions", uniqueConstraints={
* @UniqueConstraint(name="role_permission_unique_idx", columns={"role_id","action_name","station_id"})

View file

@ -1,7 +1,7 @@
<?php
namespace Entity;
use Doctrine\ORM\Mapping as ORM;
use \Doctrine\ORM\Mapping as ORM;
/**
* @Table(name="settings")

View file

@ -1,7 +1,7 @@
<?php
namespace Entity;
use Doctrine\Common\Collections\ArrayCollection;
use \Doctrine\Common\Collections\ArrayCollection;
/**
* @Table(name="songs", indexes={
@ -14,6 +14,22 @@ class Song extends \App\Doctrine\Entity
{
const SYNC_THRESHOLD = 604800; // 604800 = 1 week
public function __construct()
{
$this->created = time();
$this->play_count = 0;
$this->last_played = 0;
$this->history = new ArrayCollection;
}
/** @PrePersist */
public function preSave()
{
if (empty($this->id))
$this->id = self::getSongHash($this);
}
/**
* @Column(name="id", type="string", length=50)
* @Id
@ -38,19 +54,48 @@ class Song extends \App\Doctrine\Entity
/** @Column(name="last_played", type="integer") */
protected $last_played;
/**
/**
* @OneToMany(targetEntity="SongHistory", mappedBy="song")
* @OrderBy({"timestamp" = "DESC"})
*/
protected $history;
public function __construct()
{
$this->created = time();
$this->play_count = 0;
$this->last_played = 0;
/**
* Static Functions
*/
$this->history = new ArrayCollection;
/**
* @param $song_info
* @return string
*/
public static function getSongHash($song_info)
{
// Handle various input types.
if ($song_info instanceof self)
{
$song_info = array(
'text' => $song_info->text,
'artist' => $song_info->artist,
'title' => $song_info->title,
);
}
elseif (!is_array($song_info))
{
$song_info = array(
'text' => $song_info,
);
}
// Generate hash.
if (!empty($song_info['text']))
$song_text = $song_info['text'];
else if (!empty($song_info['artist']))
$song_text = $song_info['artist'].' - '.$song_info['title'];
else
$song_text = $song_info['title'];
$hash_base = strtolower(preg_replace("/[^A-Za-z0-9]/", '', $song_text));
return md5($hash_base);
}
/**
@ -61,61 +106,15 @@ class Song extends \App\Doctrine\Entity
*/
public static function api($row)
{
return [
'id' => $row['id'],
'text' => $row['text'],
'artist' => $row['artist'],
'title' => $row['title'],
return array(
'id' => $row['id'],
'text' => $row['text'],
'artist' => $row['artist'],
'title' => $row['title'],
'created' => (int)$row['created'],
'created' => (int)$row['created'],
'play_count' => (int)$row['play_count'],
'last_played' => (int)$row['last_played'],
];
}
/**
* Static Functions
*/
/** @PrePersist */
public function preSave()
{
if (empty($this->id)) {
$this->id = self::getSongHash($this);
}
}
/**
* @param $song_info
* @return string
*/
public static function getSongHash($song_info)
{
// Handle various input types.
if ($song_info instanceof self) {
$song_info = [
'text' => $song_info->text,
'artist' => $song_info->artist,
'title' => $song_info->title,
];
} elseif (!is_array($song_info)) {
$song_info = [
'text' => $song_info,
];
}
// Generate hash.
if (!empty($song_info['text'])) {
$song_text = $song_info['text'];
} else {
if (!empty($song_info['artist'])) {
$song_text = $song_info['artist'] . ' - ' . $song_info['title'];
} else {
$song_text = $song_info['title'];
}
}
$hash_base = strtolower(preg_replace("/[^A-Za-z0-9]/", '', $song_text));
return md5($hash_base);
);
}
}

View file

@ -1,6 +1,8 @@
<?php
namespace Entity;
use \Doctrine\Common\Collections\ArrayCollection;
/**
* @Table(name="song_history", indexes={
* @index(name="sort_idx", columns={"timestamp_start"}),
@ -9,6 +11,19 @@ namespace Entity;
*/
class SongHistory extends \App\Doctrine\Entity
{
public function __construct()
{
$this->timestamp_start = time();
$this->listeners_start = 0;
$this->timestamp_end = 0;
$this->listeners_end = 0;
$this->delta_total = 0;
$this->delta_negative = 0;
$this->delta_positive = 0;
}
/**
* @Column(name="id", type="integer")
* @Id
@ -25,9 +40,19 @@ class SongHistory extends \App\Doctrine\Entity
/** @Column(name="timestamp_start", type="integer") */
protected $timestamp_start;
public function getTimestamp()
{
return $this->timestamp_start;
}
/** @Column(name="listeners_start", type="integer", nullable=true) */
protected $listeners_start;
public function getListeners()
{
return $this->listeners_start;
}
/** @Column(name="timestamp_end", type="integer") */
protected $timestamp_end;
@ -61,27 +86,4 @@ class SongHistory extends \App\Doctrine\Entity
* })
*/
protected $station;
public function __construct()
{
$this->timestamp_start = time();
$this->listeners_start = 0;
$this->timestamp_end = 0;
$this->listeners_end = 0;
$this->delta_total = 0;
$this->delta_negative = 0;
$this->delta_positive = 0;
}
public function getTimestamp()
{
return $this->timestamp_start;
}
public function getListeners()
{
return $this->listeners_start;
}
}

View file

@ -1,7 +1,7 @@
<?php
namespace Entity;
use Doctrine\Common\Collections\ArrayCollection;
use \Doctrine\Common\Collections\ArrayCollection;
use Interop\Container\ContainerInterface;
/**
@ -11,6 +11,25 @@ use Interop\Container\ContainerInterface;
*/
class Station extends \App\Doctrine\Entity
{
public function __construct()
{
$this->automation_timestamp = 0;
$this->enable_streamers = false;
$this->enable_requests = false;
$this->request_delay = 5;
$this->needs_restart = false;
$this->history = new ArrayCollection;
$this->managers = new ArrayCollection;
$this->media = new ArrayCollection;
$this->playlists = new ArrayCollection;
$this->mounts = new ArrayCollection;
$this->streamers = new ArrayCollection;
}
/**
* @Column(name="id", type="integer")
* @Id
@ -21,24 +40,89 @@ class Station extends \App\Doctrine\Entity
/** @Column(name="name", type="string", length=100, nullable=true) */
protected $name;
public function getShortName()
{
return self::getStationShortName($this->name);
}
/** @Column(name="frontend_type", type="string", length=100, nullable=true) */
protected $frontend_type;
/** @Column(name="frontend_config", type="json", nullable=true) */
protected $frontend_config;
/**
* @return \AzuraCast\Radio\Frontend\FrontendAbstract
* @throws \Exception
*/
public function getFrontendAdapter(ContainerInterface $di)
{
$adapters = self::getFrontendAdapters();
if (!isset($adapters['adapters'][$this->frontend_type]))
throw new \Exception('Adapter not found: '.$this->frontend_type);
$class_name = $adapters['adapters'][$this->frontend_type]['class'];
return new $class_name($di, $this);
}
/** @Column(name="backend_type", type="string", length=100, nullable=true) */
protected $backend_type;
/** @Column(name="backend_config", type="json", nullable=true) */
protected $backend_config;
/**
* @return \AzuraCast\Radio\Backend\BackendAbstract
* @throws \Exception
*/
public function getBackendAdapter(ContainerInterface $di)
{
$adapters = self::getBackendAdapters();
if (!isset($adapters['adapters'][$this->backend_type]))
throw new \Exception('Adapter not found: '.$this->backend_type);
$class_name = $adapters['adapters'][$this->backend_type]['class'];
return new $class_name($di, $this);
}
/** @Column(name="description", type="text", nullable=true) */
protected $description;
/** @Column(name="radio_base_dir", type="string", length=255, nullable=true) */
protected $radio_base_dir;
public function setRadioBaseDir($new_dir)
{
if (strcmp($this->radio_base_dir, $new_dir) !== 0)
{
$this->radio_base_dir = $new_dir;
$radio_dirs = [$this->radio_base_dir, $this->getRadioMediaDir(), $this->getRadioPlaylistsDir(), $this->getRadioConfigDir()];
foreach($radio_dirs as $radio_dir)
{
if (!file_exists($radio_dir))
mkdir($radio_dir, 0777);
}
}
}
public function getRadioMediaDir()
{
return $this->radio_base_dir.'/media';
}
public function getRadioPlaylistsDir()
{
return $this->radio_base_dir.'/playlists';
}
public function getRadioConfigDir()
{
return $this->radio_base_dir.'/config';
}
/** @Column(name="nowplaying_data", type="json", nullable=true) */
protected $nowplaying_data;
@ -87,23 +171,118 @@ class Station extends \App\Doctrine\Entity
*/
protected $mounts;
public function __construct()
/**
* Write all configuration changes to the filesystem and reload supervisord.
*
* @param ContainerInterface $di
*/
public function writeConfiguration(ContainerInterface $di)
{
$this->automation_timestamp = 0;
$this->enable_streamers = false;
$this->enable_requests = false;
$this->request_delay = 5;
if (APP_TESTING_MODE)
return;
$this->needs_restart = false;
/** @var \Supervisor\Supervisor */
$supervisor = $di['supervisor'];
$this->history = new ArrayCollection;
$this->managers = new ArrayCollection;
$config_path = $this->getRadioConfigDir();
$supervisor_config = [];
$supervisor_config_path = $config_path.'/supervisord.conf';
$this->media = new ArrayCollection;
$this->playlists = new ArrayCollection;
$this->mounts = new ArrayCollection;
$frontend = $this->getFrontendAdapter($di);
$backend = $this->getBackendAdapter($di);
$this->streamers = new ArrayCollection;
// If no processes need to be managed, remove any existing config.
if (!$frontend->hasCommand() && !$backend->hasCommand())
{
@unlink($supervisor_config_path);
return;
}
// Write config files for both backend and frontend.
$frontend->write();
$backend->write();
// Get group information
$backend_name = $backend->getProgramName();
list($backend_group, $backend_program) = explode(':', $backend_name);
$frontend_name = $frontend->getProgramName();
list($frontend_group, $frontend_program) = explode(':', $frontend_name);
// Write group section of config
$programs = [];
if ($backend->hasCommand())
$programs[] = $backend_program;
if ($frontend->hasCommand())
$programs[] = $frontend_program;
$supervisor_config[] = '[group:'.$backend_group.']';
$supervisor_config[] = 'programs='.implode(',', $programs);
$supervisor_config[] = '';
// Write frontend
if ($frontend->hasCommand())
{
$supervisor_config[] = '[program:'.$frontend_program.']';
$supervisor_config[] = 'directory='.$config_path;
$supervisor_config[] = 'command='.$frontend->getCommand();
$supervisor_config[] = 'user=azuracast';
$supervisor_config[] = 'priority=90';
$supervisor_config[] = '';
}
// Write backend
if ($backend->hasCommand())
{
$supervisor_config[] = '[program:'.$backend_program.']';
$supervisor_config[] = 'directory='.$config_path;
$supervisor_config[] = 'command='.$backend->getCommand();
$supervisor_config[] = 'user=azuracast';
$supervisor_config[] = 'priority=100';
$supervisor_config[] = '';
}
// Write config contents
$supervisor_config_data = implode("\n", $supervisor_config);
file_put_contents($supervisor_config_path, $supervisor_config_data);
// Trigger a supervisord reload and restart all relevant services.
$reload_result = $supervisor->reloadConfig();
$reload_added = $reload_result[0][0];
$reload_changed = $reload_result[0][1];
$reload_removed = $reload_result[0][2];
foreach($reload_removed as $group)
{
$supervisor->stopProcessGroup($group);
$supervisor->removeProcessGroup($group);
}
foreach($reload_changed as $group)
{
$supervisor->stopProcessGroup($group);
$supervisor->removeProcessGroup($group);
$supervisor->addProcessGroup($group);
}
foreach($reload_added as $group)
{
$supervisor->addProcessGroup($group);
}
}
/**
* Static Functions
*/
/**
* @param $name
* @return string
*/
public static function getStationShortName($name)
{
return strtolower(preg_replace("/[^A-Za-z0-9_]/", '', str_replace(' ', '_', trim($name))));
}
/**
@ -118,58 +297,6 @@ class Station extends \App\Doctrine\Entity
return $name;
}
/**
* Retrieve the API version of the object/array.
*
* @param $row
* @return array
*/
public static function api(Station $row, ContainerInterface $di)
{
$fa = $row->getFrontendAdapter($di);
$api = [
'id' => (int)$row->id,
'name' => $row->name,
'shortcode' => self::getStationShortName($row['name']),
'description' => $row->description,
'frontend' => $row->frontend_type,
'backend' => $row->backend_type,
'listen_url' => $fa->getStreamUrl(),
'mounts' => [],
];
if ($row->mounts->count() > 0) {
if ($fa->supportsMounts()) {
foreach ($row->mounts as $mount_row) {
$api['mounts'][] = [
'name' => $mount_row->name,
'is_default' => (bool)$mount_row->is_default,
'url' => $fa->getUrlForMount($mount_row->name),
];
}
}
}
return $api;
}
/**
* @return \AzuraCast\Radio\Frontend\FrontendAbstract
* @throws \Exception
*/
public function getFrontendAdapter(ContainerInterface $di)
{
$adapters = self::getFrontendAdapters();
if (!isset($adapters['adapters'][$this->frontend_type])) {
throw new \Exception('Adapter not found: ' . $this->frontend_type);
}
$class_name = $adapters['adapters'][$this->frontend_type]['class'];
return new $class_name($di, $this);
}
/**
* @return array
*/
@ -194,172 +321,6 @@ class Station extends \App\Doctrine\Entity
];
}
/**
* @param $name
* @return string
*/
public static function getStationShortName($name)
{
return strtolower(preg_replace("/[^A-Za-z0-9_]/", '', str_replace(' ', '_', trim($name))));
}
public function getShortName()
{
return self::getStationShortName($this->name);
}
public function setRadioBaseDir($new_dir)
{
if (strcmp($this->radio_base_dir, $new_dir) !== 0) {
$this->radio_base_dir = $new_dir;
$radio_dirs = [
$this->radio_base_dir,
$this->getRadioMediaDir(),
$this->getRadioPlaylistsDir(),
$this->getRadioConfigDir()
];
foreach ($radio_dirs as $radio_dir) {
if (!file_exists($radio_dir)) {
mkdir($radio_dir, 0777);
}
}
}
}
public function getRadioMediaDir()
{
return $this->radio_base_dir . '/media';
}
/**
* Static Functions
*/
public function getRadioPlaylistsDir()
{
return $this->radio_base_dir . '/playlists';
}
public function getRadioConfigDir()
{
return $this->radio_base_dir . '/config';
}
/**
* Write all configuration changes to the filesystem and reload supervisord.
*
* @param ContainerInterface $di
*/
public function writeConfiguration(ContainerInterface $di)
{
if (APP_TESTING_MODE) {
return;
}
/** @var \Supervisor\Supervisor */
$supervisor = $di['supervisor'];
$config_path = $this->getRadioConfigDir();
$supervisor_config = [];
$supervisor_config_path = $config_path . '/supervisord.conf';
$frontend = $this->getFrontendAdapter($di);
$backend = $this->getBackendAdapter($di);
// If no processes need to be managed, remove any existing config.
if (!$frontend->hasCommand() && !$backend->hasCommand()) {
@unlink($supervisor_config_path);
return;
}
// Write config files for both backend and frontend.
$frontend->write();
$backend->write();
// Get group information
$backend_name = $backend->getProgramName();
list($backend_group, $backend_program) = explode(':', $backend_name);
$frontend_name = $frontend->getProgramName();
list($frontend_group, $frontend_program) = explode(':', $frontend_name);
// Write group section of config
$programs = [];
if ($backend->hasCommand()) {
$programs[] = $backend_program;
}
if ($frontend->hasCommand()) {
$programs[] = $frontend_program;
}
$supervisor_config[] = '[group:' . $backend_group . ']';
$supervisor_config[] = 'programs=' . implode(',', $programs);
$supervisor_config[] = '';
// Write frontend
if ($frontend->hasCommand()) {
$supervisor_config[] = '[program:' . $frontend_program . ']';
$supervisor_config[] = 'directory=' . $config_path;
$supervisor_config[] = 'command=' . $frontend->getCommand();
$supervisor_config[] = 'user=azuracast';
$supervisor_config[] = 'priority=90';
$supervisor_config[] = '';
}
// Write backend
if ($backend->hasCommand()) {
$supervisor_config[] = '[program:' . $backend_program . ']';
$supervisor_config[] = 'directory=' . $config_path;
$supervisor_config[] = 'command=' . $backend->getCommand();
$supervisor_config[] = 'user=azuracast';
$supervisor_config[] = 'priority=100';
$supervisor_config[] = '';
}
// Write config contents
$supervisor_config_data = implode("\n", $supervisor_config);
file_put_contents($supervisor_config_path, $supervisor_config_data);
// Trigger a supervisord reload and restart all relevant services.
$reload_result = $supervisor->reloadConfig();
$reload_added = $reload_result[0][0];
$reload_changed = $reload_result[0][1];
$reload_removed = $reload_result[0][2];
foreach ($reload_removed as $group) {
$supervisor->stopProcessGroup($group);
$supervisor->removeProcessGroup($group);
}
foreach ($reload_changed as $group) {
$supervisor->stopProcessGroup($group);
$supervisor->removeProcessGroup($group);
$supervisor->addProcessGroup($group);
}
foreach ($reload_added as $group) {
$supervisor->addProcessGroup($group);
}
}
/**
* @return \AzuraCast\Radio\Backend\BackendAbstract
* @throws \Exception
*/
public function getBackendAdapter(ContainerInterface $di)
{
$adapters = self::getBackendAdapters();
if (!isset($adapters['adapters'][$this->backend_type])) {
throw new \Exception('Adapter not found: ' . $this->backend_type);
}
$class_name = $adapters['adapters'][$this->backend_type]['class'];
return new $class_name($di, $this);
}
/**
* @return array
*/
@ -379,4 +340,43 @@ class Station extends \App\Doctrine\Entity
],
];
}
/**
* Retrieve the API version of the object/array.
*
* @param $row
* @return array
*/
public static function api(Station $row, ContainerInterface $di)
{
$fa = $row->getFrontendAdapter($di);
$api = [
'id' => (int)$row->id,
'name' => $row->name,
'shortcode' => self::getStationShortName($row['name']),
'description' => $row->description,
'frontend' => $row->frontend_type,
'backend' => $row->backend_type,
'listen_url' => $fa->getStreamUrl(),
'mounts' => [],
];
if ($row->mounts->count() > 0)
{
if ($fa->supportsMounts())
{
foreach($row->mounts as $mount_row)
{
$api['mounts'][] = [
'name' => $mount_row->name,
'is_default' => (bool)$mount_row->is_default,
'url' => $fa->getUrlForMount($mount_row->name),
];
}
}
}
return $api;
}
}

View file

@ -1,7 +1,8 @@
<?php
namespace Entity;
use Doctrine\Common\Collections\ArrayCollection;
use App\Exception;
use \Doctrine\Common\Collections\ArrayCollection;
/**
* @Table(name="station_media", indexes={
@ -14,6 +15,16 @@ use Doctrine\Common\Collections\ArrayCollection;
*/
class StationMedia extends \App\Doctrine\Entity
{
public function __construct()
{
$this->length = 0;
$this->length_text = '0:00';
$this->mtime = 0;
$this->playlists = new ArrayCollection();
}
/**
* @Column(name="id", type="integer")
* @Id
@ -39,12 +50,27 @@ class StationMedia extends \App\Doctrine\Entity
/** @Column(name="length", type="smallint") */
protected $length;
public function setLength($length)
{
$length_min = floor($length / 60);
$length_sec = $length % 60;
$this->length = $length;
$this->length_text = $length_min.':'.str_pad($length_sec, 2, '0', STR_PAD_LEFT);
}
/** @Column(name="length_text", type="string", length=10, nullable=true) */
protected $length_text;
/** @Column(name="path", type="string", length=255, nullable=true) */
protected $path;
public function getFullPath()
{
$media_base_dir = $this->station->getRadioMediaDir();
return $media_base_dir.'/'.$this->path;
}
/** @Column(name="mtime", type="integer", nullable=true) */
protected $mtime;
@ -73,14 +99,114 @@ class StationMedia extends \App\Doctrine\Entity
*/
protected $playlists;
public function __construct()
/**
* Process metadata information from media file.
*/
public function loadFromFile()
{
$this->length = 0;
$this->length_text = '0:00';
if (empty($this->path))
return false;
$media_base_dir = $this->station->getRadioMediaDir();
$media_path = $media_base_dir.'/'.$this->path;
$this->mtime = 0;
// Only update metadata if the file has been updated.
$media_mtime = filemtime($media_path);
if ($media_mtime > $this->mtime || !$this->song)
{
// Load metadata from MP3 file.
$id3 = new \getID3();
$this->playlists = new ArrayCollection();
$id3->option_md5_data = true;
$id3->option_md5_data_source = true;
$id3->encoding = 'UTF-8';
$file_info = $id3->analyze($media_path);
if (isset($file_info['error']))
throw new \App\Exception($file_info['error'][0]);
$this->setLength($file_info['playtime_seconds']);
$tags_to_set = ['title', 'artist', 'album'];
if (!empty($file_info['tags']))
{
foreach($file_info['tags'] as $tag_type => $tag_data)
{
foreach($tags_to_set as $tag)
{
if (!empty($tag_data[$tag][0]))
$this->{$tag} = $tag_data[$tag][0];
}
}
}
if (empty($this->title))
{
$path_parts = pathinfo($media_path);
$this->title = $path_parts['filename'];
}
$this->mtime = $media_mtime;
return array(
'artist' => $this->artist,
'title' => $this->title,
);
}
return false;
}
/**
* Write modified metadata directly to the file as ID3 information.
*/
public function writeToFile()
{
$getID3 = new \getID3;
$getID3->setOption(array('encoding'=> 'UTF8'));
require_once(APP_INCLUDE_VENDOR.'/james-heinrich/getid3/getid3/write.php');
$tagwriter = new \getid3_writetags;
$tagwriter->filename = $this->getFullPath();
$tagwriter->tagformats = array('id3v1', 'id3v2.3');
$tagwriter->overwrite_tags = true;
$tagwriter->tag_encoding = 'UTF8';
$tagwriter->remove_other_tags = true;
$tag_data = array(
'title' => array($this->title),
'artist' => array($this->artist),
'album' => array($this->album),
);
$tagwriter->tag_data = $tag_data;
// write tags
if ($tagwriter->WriteTags())
{
$this->mtime = time();
return true;
}
}
/**
* Move this media file to the "not-processed" directory.
*/
public function moveToNotProcessed()
{
$old_path = $this->getFullPath();
$media_base_dir = $this->station->getRadioMediaDir();
$unprocessed_dir = $media_base_dir.'/not-processed';
@mkdir($unprocessed_dir);
$new_path = $unprocessed_dir.'/'.basename($this->path);
@rename($old_path, $new_path);
}
/**
@ -92,126 +218,4 @@ class StationMedia extends \App\Doctrine\Entity
{
return ['mp3', 'ogg', 'm4a', 'flac'];
}
/**
* Process metadata information from media file.
*/
public function loadFromFile()
{
if (empty($this->path)) {
return false;
}
$media_base_dir = $this->station->getRadioMediaDir();
$media_path = $media_base_dir . '/' . $this->path;
// Only update metadata if the file has been updated.
$media_mtime = filemtime($media_path);
if ($media_mtime > $this->mtime || !$this->song) {
// Load metadata from MP3 file.
$id3 = new \getID3();
$id3->option_md5_data = true;
$id3->option_md5_data_source = true;
$id3->encoding = 'UTF-8';
$file_info = $id3->analyze($media_path);
if (isset($file_info['error'])) {
throw new \App\Exception($file_info['error'][0]);
}
$this->setLength($file_info['playtime_seconds']);
$tags_to_set = ['title', 'artist', 'album'];
if (!empty($file_info['tags'])) {
foreach ($file_info['tags'] as $tag_type => $tag_data) {
foreach ($tags_to_set as $tag) {
if (!empty($tag_data[$tag][0])) {
$this->{$tag} = $tag_data[$tag][0];
}
}
}
}
if (empty($this->title)) {
$path_parts = pathinfo($media_path);
$this->title = $path_parts['filename'];
}
$this->mtime = $media_mtime;
return [
'artist' => $this->artist,
'title' => $this->title,
];
}
return false;
}
public function setLength($length)
{
$length_min = floor($length / 60);
$length_sec = $length % 60;
$this->length = $length;
$this->length_text = $length_min . ':' . str_pad($length_sec, 2, '0', STR_PAD_LEFT);
}
/**
* Write modified metadata directly to the file as ID3 information.
*/
public function writeToFile()
{
$getID3 = new \getID3;
$getID3->setOption(['encoding' => 'UTF8']);
require_once(APP_INCLUDE_VENDOR . '/james-heinrich/getid3/getid3/write.php');
$tagwriter = new \getid3_writetags;
$tagwriter->filename = $this->getFullPath();
$tagwriter->tagformats = ['id3v1', 'id3v2.3'];
$tagwriter->overwrite_tags = true;
$tagwriter->tag_encoding = 'UTF8';
$tagwriter->remove_other_tags = true;
$tag_data = [
'title' => [$this->title],
'artist' => [$this->artist],
'album' => [$this->album],
];
$tagwriter->tag_data = $tag_data;
// write tags
if ($tagwriter->WriteTags()) {
$this->mtime = time();
return true;
}
}
public function getFullPath()
{
$media_base_dir = $this->station->getRadioMediaDir();
return $media_base_dir . '/' . $this->path;
}
/**
* Move this media file to the "not-processed" directory.
*/
public function moveToNotProcessed()
{
$old_path = $this->getFullPath();
$media_base_dir = $this->station->getRadioMediaDir();
$unprocessed_dir = $media_base_dir . '/not-processed';
@mkdir($unprocessed_dir);
$new_path = $unprocessed_dir . '/' . basename($this->path);
@rename($old_path, $new_path);
}
}

View file

@ -1,6 +1,8 @@
<?php
namespace Entity;
use \Doctrine\Common\Collections\ArrayCollection;
/**
* @Table(name="station_mounts")
* @Entity(repositoryClass="Entity\Repository\StationMountRepository")
@ -8,6 +10,16 @@ namespace Entity;
*/
class StationMount extends \App\Doctrine\Entity
{
public function __construct()
{
$this->is_default = false;
$this->enable_autodj = true;
$this->enable_streamers = false;
$this->autodj_format = 'mp3';
$this->autodj_bitrate = 128;
}
/**
* @Column(name="id", type="integer")
* @Id
@ -21,6 +33,15 @@ class StationMount extends \App\Doctrine\Entity
/** @Column(name="name", type="string", length=100) */
protected $name;
/**
* Ensure all mountpoint names start with a leading slash.
* @param $new_name
*/
public function setName($new_name)
{
$this->name = '/'.ltrim($new_name, '/');
}
/** @Column(name="is_default", type="boolean", nullable=false) */
protected $is_default;
@ -49,23 +70,4 @@ class StationMount extends \App\Doctrine\Entity
* })
*/
protected $station;
public function __construct()
{
$this->is_default = false;
$this->enable_autodj = true;
$this->enable_streamers = false;
$this->autodj_format = 'mp3';
$this->autodj_bitrate = 128;
}
/**
* Ensure all mountpoint names start with a leading slash.
* @param $new_name
*/
public function setName($new_name)
{
$this->name = '/' . ltrim($new_name, '/');
}
}

View file

@ -1,7 +1,7 @@
<?php
namespace Entity;
use Doctrine\Common\Collections\ArrayCollection;
use \Doctrine\Common\Collections\ArrayCollection;
/**
* @Table(name="station_playlists")
@ -10,6 +10,22 @@ use Doctrine\Common\Collections\ArrayCollection;
*/
class StationPlaylist extends \App\Doctrine\Entity
{
public function __construct()
{
$this->type = 'default';
$this->is_enabled = 1;
$this->weight = 3;
$this->include_in_automation = false;
$this->play_once_time = 0;
$this->play_per_minutes = 0;
$this->play_per_songs = 0;
$this->schedule_start_time = 0;
$this->schedule_end_time = 0;
$this->media = new ArrayCollection;
}
/**
* @Column(name="id", type="integer")
* @Id
@ -23,6 +39,11 @@ class StationPlaylist extends \App\Doctrine\Entity
/** @Column(name="name", type="string", length=200) */
protected $name;
public function getShortName()
{
return Station::getStationShortName($this->name);
}
/** @Column(name="type", type="string", length=50) */
protected $type;
@ -38,12 +59,27 @@ class StationPlaylist extends \App\Doctrine\Entity
/** @Column(name="schedule_start_time", type="smallint") */
protected $schedule_start_time;
public function getScheduleStartTimeText()
{
return self::formatTimeCode($this->schedule_start_time);
}
/** @Column(name="schedule_end_time", type="smallint") */
protected $schedule_end_time;
public function getScheduleEndTimeText()
{
return self::formatTimeCode($this->schedule_end_time);
}
/** @Column(name="play_once_time", type="smallint") */
protected $play_once_time;
public function getPlayOnceTimeText()
{
return self::formatTimeCode($this->play_once_time);
}
/** @Column(name="weight", type="smallint") */
protected $weight;
@ -63,32 +99,6 @@ class StationPlaylist extends \App\Doctrine\Entity
*/
protected $media;
public function __construct()
{
$this->type = 'default';
$this->is_enabled = 1;
$this->weight = 3;
$this->include_in_automation = false;
$this->play_once_time = 0;
$this->play_per_minutes = 0;
$this->play_per_songs = 0;
$this->schedule_start_time = 0;
$this->schedule_end_time = 0;
$this->media = new ArrayCollection;
}
public function getShortName()
{
return Station::getStationShortName($this->name);
}
public function getScheduleStartTimeText()
{
return self::formatTimeCode($this->schedule_start_time);
}
/**
* Given a time code i.e. "2300", return a time i.e. "11:00 PM"
* @param $time_code
@ -101,24 +111,13 @@ class StationPlaylist extends \App\Doctrine\Entity
$ampm = ($hours < 12) ? 'AM' : 'PM';
if ($hours == 0) {
if ($hours == 0)
$hours_text = '12';
} elseif ($hours > 12) {
$hours_text = $hours - 12;
} else {
elseif ($hours > 12)
$hours_text = $hours-12;
else
$hours_text = $hours;
}
return $hours_text . ':' . str_pad($mins, 2, '0', STR_PAD_LEFT) . ' ' . $ampm;
}
public function getScheduleEndTimeText()
{
return self::formatTimeCode($this->schedule_end_time);
}
public function getPlayOnceTimeText()
{
return self::formatTimeCode($this->play_once_time);
return $hours_text.':'.str_pad($mins, 2, '0', STR_PAD_LEFT).' '.$ampm;
}
}

View file

@ -1,12 +1,22 @@
<?php
namespace Entity;
use \Doctrine\Common\Collections\ArrayCollection;
/**
* @Table(name="station_requests")
* @Entity(repositoryClass="Entity\Repository\StationRequestRepository")
*/
class StationRequest extends \App\Doctrine\Entity
{
public function __construct()
{
$this->timestamp = time();
$this->played_at = 0;
$this->ip = $_SERVER['REMOTE_ADDR'];
}
/**
* @Column(name="id", type="integer")
* @Id
@ -44,12 +54,4 @@ class StationRequest extends \App\Doctrine\Entity
* })
*/
protected $track;
public function __construct()
{
$this->timestamp = time();
$this->played_at = 0;
$this->ip = $_SERVER['REMOTE_ADDR'];
}
}

View file

@ -1,6 +1,8 @@
<?php
namespace Entity;
use \Doctrine\Common\Collections\ArrayCollection;
/**
* Station streamers (DJ accounts) allowed to broadcast to a station.
*
@ -10,6 +12,11 @@ namespace Entity;
*/
class StationStreamer extends \App\Doctrine\Entity
{
public function __construct()
{
$this->is_active = true;
}
/**
* @Column(name="id", type="integer")
* @Id
@ -39,9 +46,4 @@ class StationStreamer extends \App\Doctrine\Entity
* })
*/
protected $station;
public function __construct()
{
$this->is_active = true;
}
}

View file

@ -1,8 +1,8 @@
<?php
namespace Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use \Doctrine\ORM\Mapping as ORM;
use \Doctrine\Common\Collections\ArrayCollection;
/**
* @Table(name="users")
@ -11,6 +11,22 @@ use Doctrine\ORM\Mapping as ORM;
*/
class User extends \App\Doctrine\Entity
{
public function __construct()
{
$this->roles = new ArrayCollection;
$this->created_at = time();
$this->updated_at = time();
}
/**
* @PrePersist
*/
public function preSave()
{
$this->updated_at = time();
}
/**
* @Column(name="uid", type="integer")
* @Id
@ -21,9 +37,37 @@ class User extends \App\Doctrine\Entity
/** @Column(name="email", type="string", length=100, nullable=true) */
protected $email;
public function getAvatar($size = 50)
{
return \App\Service\Gravatar::get($this->email, $size, 'identicon');
}
/** @Column(name="auth_password", type="string", length=255, nullable=true) */
protected $auth_password;
public function verifyPassword($password)
{
return password_verify($password, $this->auth_password);
}
public function getAuthPassword()
{
return '';
}
public function setAuthPassword($password)
{
if (trim($password))
$this->auth_password = password_hash($password, \PASSWORD_DEFAULT);
return $this;
}
public function generateRandomPassword()
{
$this->setAuthPassword(md5('APP_EXTERNAL_'.mt_rand()));
}
/** @Column(name="name", type="string", length=100, nullable=true) */
protected $name;
@ -50,49 +94,4 @@ class User extends \App\Doctrine\Entity
* )
*/
protected $roles;
public function __construct()
{
$this->roles = new ArrayCollection;
$this->created_at = time();
$this->updated_at = time();
}
/**
* @PrePersist
*/
public function preSave()
{
$this->updated_at = time();
}
public function getAvatar($size = 50)
{
return \App\Service\Gravatar::get($this->email, $size, 'identicon');
}
public function verifyPassword($password)
{
return password_verify($password, $this->auth_password);
}
public function getAuthPassword()
{
return '';
}
public function setAuthPassword($password)
{
if (trim($password)) {
$this->auth_password = password_hash($password, \PASSWORD_DEFAULT);
}
return $this;
}
public function generateRandomPassword()
{
$this->setAuthPassword(md5('APP_EXTERNAL_' . mt_rand()));
}
}

View file

@ -16,8 +16,7 @@ class Version20161003041904 extends AbstractMigration
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE role_has_actions (id INT AUTO_INCREMENT NOT NULL, station_id INT DEFAULT NULL, role_id INT NOT NULL, action_id INT NOT NULL, INDEX IDX_50EEC1BDD60322AC (role_id), INDEX IDX_50EEC1BD21BDB235 (station_id), UNIQUE INDEX role_action_unique_idx (role_id, action_id, station_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
$this->addSql('ALTER TABLE role_has_actions ADD CONSTRAINT FK_50EEC1BD21BDB235 FOREIGN KEY (station_id) REFERENCES station (id) ON DELETE CASCADE');
@ -45,7 +44,8 @@ class Version20161003041904 extends AbstractMigration
['manage station automation', 0],
];
foreach ($actions as $action) {
foreach($actions as $action)
{
$this->addSql('DELETE FROM action WHERE name = :name', [
'name' => $action[0],
], [
@ -68,8 +68,7 @@ class Version20161003041904 extends AbstractMigration
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE role_has_action (role_id INT NOT NULL, action_id INT NOT NULL, INDEX IDX_E4DAF125D60322AC (role_id), INDEX IDX_E4DAF1259D32F035 (action_id), PRIMARY KEY(role_id, action_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
$this->addSql('ALTER TABLE role_has_action ADD CONSTRAINT FK_E4DAF1259D32F035 FOREIGN KEY (action_id) REFERENCES action (id) ON DELETE CASCADE');

View file

@ -16,8 +16,7 @@ class Version20161006030903 extends AbstractMigration
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('DROP TABLE IF EXISTS user_manages_station');
@ -37,8 +36,7 @@ class Version20161006030903 extends AbstractMigration
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE user_manages_station (user_id INT NOT NULL, station_id INT NOT NULL, INDEX IDX_2453B56BA76ED395 (user_id), INDEX IDX_2453B56B21BDB235 (station_id), PRIMARY KEY(user_id, station_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
$this->addSql('ALTER TABLE user_manages_station ADD CONSTRAINT FK_2453B56B21BDB235 FOREIGN KEY (station_id) REFERENCES station (id) ON DELETE CASCADE');

View file

@ -16,8 +16,7 @@ class Version20161007021719 extends AbstractMigration
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE users ADD theme VARCHAR(25) DEFAULT NULL');
}
@ -28,8 +27,7 @@ class Version20161007021719 extends AbstractMigration
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE users DROP theme');
}

View file

@ -16,8 +16,7 @@ class Version20161007195027 extends AbstractMigration
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE role_permissions (id INT AUTO_INCREMENT NOT NULL, role_id INT NOT NULL, station_id INT DEFAULT NULL, action_name VARCHAR(50) NOT NULL, INDEX IDX_1FBA94E6D60322AC (role_id), INDEX IDX_1FBA94E621BDB235 (station_id), UNIQUE INDEX role_permission_unique_idx (role_id, action_name, station_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
$this->addSql('ALTER TABLE role_permissions ADD CONSTRAINT FK_1FBA94E6D60322AC FOREIGN KEY (role_id) REFERENCES role (id) ON DELETE CASCADE');
@ -36,8 +35,7 @@ class Version20161007195027 extends AbstractMigration
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE action (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(100) DEFAULT NULL COLLATE utf8_unicode_ci, is_global TINYINT(1) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE role_has_actions (id INT AUTO_INCREMENT NOT NULL, station_id INT DEFAULT NULL, action_id INT NOT NULL, role_id INT NOT NULL, UNIQUE INDEX role_action_unique_idx (role_id, action_id, station_id), INDEX IDX_50EEC1BDD60322AC (role_id), INDEX IDX_50EEC1BD21BDB235 (station_id), INDEX IDX_50EEC1BD9D32F035 (action_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');

View file

@ -16,8 +16,7 @@ class Version20161117000718 extends AbstractMigration
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE station_mounts (id INT AUTO_INCREMENT NOT NULL, station_id INT NOT NULL, name VARCHAR(100) NOT NULL, is_default TINYINT(1) NOT NULL, fallback_mount VARCHAR(100) DEFAULT NULL, enable_streamers TINYINT(1) NOT NULL, enable_autodj TINYINT(1) NOT NULL, autodj_format VARCHAR(10) DEFAULT NULL, autodj_bitrate SMALLINT DEFAULT NULL, INDEX IDX_4DDF64AD21BDB235 (station_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
$this->addSql('ALTER TABLE station_mounts ADD CONSTRAINT FK_4DDF64AD21BDB235 FOREIGN KEY (station_id) REFERENCES station (id) ON DELETE CASCADE');
@ -27,20 +26,21 @@ class Version20161117000718 extends AbstractMigration
{
$all_stations = $this->connection->fetchAll('SELECT * FROM station');
foreach ($all_stations as $station) {
foreach($all_stations as $station)
{
$this->connection->insert('station_mounts', [
'station_id' => $station['id'],
'name' => '/radio.mp3',
'is_default' => 1,
'station_id' => $station['id'],
'name' => '/radio.mp3',
'is_default' => 1,
'fallback_mount' => '/autodj.mp3',
'enable_streamers' => 1,
'enable_autodj' => 0,
]);
$this->connection->insert('station_mounts', [
'station_id' => $station['id'],
'name' => '/autodj.mp3',
'is_default' => 0,
'station_id' => $station['id'],
'name' => '/autodj.mp3',
'is_default' => 0,
'fallback_mount' => '/error.mp3',
'enable_streamers' => 0,
'enable_autodj' => 1,
@ -56,8 +56,7 @@ class Version20161117000718 extends AbstractMigration
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('DROP TABLE station_mounts');
}

View file

@ -16,8 +16,7 @@ class Version20161117161959 extends AbstractMigration
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE station_mounts ADD frontend_config LONGTEXT DEFAULT NULL');
}
@ -28,8 +27,7 @@ class Version20161117161959 extends AbstractMigration
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE station_mounts DROP frontend_config');
}

View file

@ -16,8 +16,7 @@ class Version20161120032434 extends AbstractMigration
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE station ADD needs_restart TINYINT(1) NOT NULL');
}
@ -28,8 +27,7 @@ class Version20161120032434 extends AbstractMigration
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE station DROP needs_restart');
}

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