Refector code
This commit is contained in:
1 parent
6ab627ba60
commit
2fbbf4b0fc
23 files changed
+1518
-1069
No files matched your search
+253
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
/**
|
||||
* config.php
|
||||
* -----------
|
||||
* Single source of truth for the whole site. Reads a local `.env` file and
|
||||
* merges it over sensible defaults so the project works out of the box and can
|
||||
* be fully re-skinned for any station without touching PHP.
|
||||
*
|
||||
* Nothing secret should live in this file — put secrets (Genius token, etc.)
|
||||
* in `.env`, which is gitignored. See `.env.example` for every supported key.
|
||||
*/
|
||||
|
||||
if (!defined('RADIO_CONFIG_LOADED')) {
|
||||
define('RADIO_CONFIG_LOADED', true);
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* Load .env */
|
||||
/* -------------------------------------------------------------------- */
|
||||
/**
|
||||
* Minimal, predictable .env parser. We deliberately avoid parse_ini_file:
|
||||
* its RAW scanner keeps the surrounding quotes, and its NORMAL scanner
|
||||
* rewrites bare words like true/false/none — which would silently undo a
|
||||
* user setting FEATURE=false. This parser just does: skip comments/blank
|
||||
* lines and an optional [section] header, split on the first "=", strip one
|
||||
* layer of matching quotes, and otherwise keep the value verbatim.
|
||||
*/
|
||||
function radio_parse_env($path) {
|
||||
$out = [];
|
||||
$lines = @file($path, FILE_IGNORE_NEW_LINES);
|
||||
if ($lines === false) return $out;
|
||||
foreach ($lines as $line) {
|
||||
$trim = trim($line);
|
||||
if ($trim === '' || $trim[0] === '#' || $trim[0] === ';') continue;
|
||||
if ($trim[0] === '[' && substr($trim, -1) === ']') continue; // [section]
|
||||
$eq = strpos($line, '=');
|
||||
if ($eq === false) continue;
|
||||
$key = trim(substr($line, 0, $eq));
|
||||
if ($key === '') continue;
|
||||
$val = ltrim(substr($line, $eq + 1));
|
||||
if ($val !== '' && ($val[0] === '"' || $val[0] === "'")) {
|
||||
$q = $val[0];
|
||||
$end = strpos($val, $q, 1);
|
||||
$val = ($end === false) ? substr($val, 1) : substr($val, 1, $end - 1);
|
||||
} else {
|
||||
// Strip an inline comment introduced by " #" or " ;".
|
||||
foreach ([' #', ' ;'] as $c) {
|
||||
$p = strpos($val, $c);
|
||||
if ($p !== false) $val = substr($val, 0, $p);
|
||||
}
|
||||
$val = rtrim($val);
|
||||
}
|
||||
$out[$key] = $val;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
$GLOBALS['__RADIO_ENV'] = [];
|
||||
$envPath = __DIR__ . '/.env';
|
||||
if (is_file($envPath)) {
|
||||
$GLOBALS['__RADIO_ENV'] = radio_parse_env($envPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a raw string value from the environment / .env.
|
||||
*/
|
||||
function radio_env_raw($key, $default = null) {
|
||||
if (array_key_exists($key, $GLOBALS['__RADIO_ENV'])) {
|
||||
return $GLOBALS['__RADIO_ENV'][$key];
|
||||
}
|
||||
$v = getenv($key);
|
||||
return ($v === false) ? $default : $v;
|
||||
}
|
||||
|
||||
/** Read a string, trimming surrounding whitespace. */
|
||||
function radio_env($key, $default = '') {
|
||||
$v = radio_env_raw($key, null);
|
||||
if ($v === null || $v === '') return $default;
|
||||
return trim($v);
|
||||
}
|
||||
|
||||
/** Read a boolean. Accepts 1/true/yes/on (case-insensitive). */
|
||||
function radio_env_bool($key, $default = false) {
|
||||
$v = radio_env_raw($key, null);
|
||||
if ($v === null || $v === '') return $default;
|
||||
return in_array(strtolower(trim($v)), ['1', 'true', 'yes', 'on'], true);
|
||||
}
|
||||
|
||||
/** Read an integer with a floor. */
|
||||
function radio_env_int($key, $default = 0, $min = null) {
|
||||
$v = radio_env_raw($key, null);
|
||||
$n = ($v === null || $v === '') ? $default : (int) $v;
|
||||
if ($min !== null && $n < $min) $n = $min;
|
||||
return $n;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* Build the config array */
|
||||
/* -------------------------------------------------------------------- */
|
||||
$config = [
|
||||
|
||||
// ---- Branding & metadata --------------------------------------
|
||||
'site' => [
|
||||
'name' => radio_env('SITE_NAME', 'My Radio'),
|
||||
'short_name' => radio_env('SITE_SHORT_NAME', radio_env('SITE_NAME', 'My Radio')),
|
||||
'tagline' => radio_env('SITE_TAGLINE', 'Streaming 24/7'),
|
||||
'description' => radio_env('SITE_DESCRIPTION', 'Listen to my internet radio station 24/7.'),
|
||||
'keywords' => radio_env('SITE_KEYWORDS', 'radio, internet radio, music, live stream'),
|
||||
'url' => rtrim(radio_env('SITE_URL', ''), '/') . '/',
|
||||
'locale' => radio_env('SITE_LOCALE', 'en_US'),
|
||||
'lang' => radio_env('SITE_LANG', 'en'),
|
||||
'author' => radio_env('SITE_AUTHOR', ''),
|
||||
],
|
||||
|
||||
// ---- SEO / icons ----------------------------------------------
|
||||
'seo' => [
|
||||
'og_image' => radio_env('OG_IMAGE', ''), // absolute URL or path
|
||||
'icons_dir' => rtrim(radio_env('ICONS_DIR', 'assets/icons'), '/'),
|
||||
'show_icons' => radio_env_bool('SHOW_ICONS', false),
|
||||
'theme_color' => radio_env('THEME_COLOR', radio_env('THEME_ACCENT', '#e21d31')),
|
||||
'tile_color' => radio_env('TILE_COLOR', radio_env('THEME_COLOR', '#5e656e')),
|
||||
],
|
||||
|
||||
// ---- Stream & now-playing -------------------------------------
|
||||
'stream' => [
|
||||
'url' => radio_env('STREAM_URL', ''), // <audio> source
|
||||
'type' => radio_env('STREAM_TYPE', 'audio/mpeg'),
|
||||
],
|
||||
'nowplaying' => [
|
||||
// provider: icecast | azuracast | shoutcast | custom
|
||||
'provider' => strtolower(radio_env('NOWPLAYING_PROVIDER', 'icecast')),
|
||||
// URL the *server* fetches (may be an internal address).
|
||||
'url' => radio_env('NOWPLAYING_URL', ''),
|
||||
'mount' => radio_env('ICECAST_MOUNT', ''), // optional, picks a source
|
||||
'delimiter' => radio_env_raw('TITLE_DELIMITER', ' - '), // splits "Artist<delim>Title"
|
||||
'poll' => radio_env_int('NOWPLAYING_POLL_SECONDS', 10, 3),
|
||||
'timeout' => radio_env_int('NOWPLAYING_TIMEOUT', 5, 1),
|
||||
// custom-provider JSON field paths (dot notation, arrays allowed: a.b.0.c)
|
||||
'custom' => [
|
||||
'artist' => radio_env('CUSTOM_ARTIST_PATH', ''),
|
||||
'title' => radio_env('CUSTOM_TITLE_PATH', ''),
|
||||
'album' => radio_env('CUSTOM_ALBUM_PATH', ''),
|
||||
'art' => radio_env('CUSTOM_ART_PATH', ''),
|
||||
'listeners' => radio_env('CUSTOM_LISTENERS_PATH', ''),
|
||||
// If you only have a combined "Artist - Title" field, set this
|
||||
// instead of artist/title and it will be split on the delimiter.
|
||||
'title_combined' => radio_env('CUSTOM_TITLE_COMBINED_PATH', ''),
|
||||
],
|
||||
],
|
||||
|
||||
// ---- Artwork & lyrics -----------------------------------------
|
||||
'artwork' => [
|
||||
// source: auto | provider | genius | none
|
||||
// auto = provider art if present, else Genius (if configured), else fallback
|
||||
// provider = only use art the stream provider gives us
|
||||
// genius = always look art up on Genius
|
||||
// none = always use the fallback image
|
||||
'source' => strtolower(radio_env('ARTWORK_SOURCE', 'auto')),
|
||||
'fallback' => radio_env('FALLBACK_ART', 'assets/img/placeholder.svg'),
|
||||
],
|
||||
'lyrics' => [
|
||||
'enabled' => radio_env_bool('LYRICS_ENABLED', true),
|
||||
],
|
||||
'genius' => [
|
||||
// Base64-encoded Genius bearer token (kept for compatibility with
|
||||
// the original project). You can also set GENIUS_TOKEN as plain text.
|
||||
'token' => (function () {
|
||||
$b64 = radio_env('GENIUS_API_BASE64', '');
|
||||
if ($b64 !== '') {
|
||||
$decoded = base64_decode($b64, true);
|
||||
if ($decoded !== false) return trim($decoded);
|
||||
}
|
||||
return radio_env('GENIUS_TOKEN', '');
|
||||
})(),
|
||||
],
|
||||
|
||||
// ---- Theme & colors -------------------------------------------
|
||||
'theme' => [
|
||||
'bg' => radio_env('THEME_BG', '#000000'),
|
||||
'bg_mobile' => radio_env('THEME_BG_MOBILE', '#262626'),
|
||||
'surface' => radio_env('THEME_SURFACE', '#1a1a1a'),
|
||||
'text' => radio_env('THEME_TEXT', '#ffffff'),
|
||||
'muted' => radio_env('THEME_MUTED', '#bfbfbf'),
|
||||
'accent' => radio_env('THEME_ACCENT', '#e21d31'),
|
||||
'accent_hover' => radio_env('THEME_ACCENT_HOVER', '#cf1b2d'),
|
||||
'accent_text' => radio_env('THEME_ACCENT_TEXT', '#000000'),
|
||||
'radius' => radio_env('THEME_RADIUS', '15px'),
|
||||
'font' => radio_env('THEME_FONT', 'Helvetica, Calibri, Tahoma, Verdana, Arial, sans-serif'),
|
||||
],
|
||||
|
||||
// ---- Listeners / extras ---------------------------------------
|
||||
'features' => [
|
||||
'show_listeners' => radio_env_bool('SHOW_LISTENERS', true),
|
||||
'listeners_link' => radio_env('LISTENERS_LINK', ''), // optional external URL
|
||||
'show_history' => radio_env_bool('SHOW_HISTORY', true),
|
||||
],
|
||||
|
||||
// ---- Analytics (optional) -------------------------------------
|
||||
'analytics' => [
|
||||
'matomo_url' => radio_env('MATOMO_URL', ''),
|
||||
'matomo_site_id' => radio_env('MATOMO_SITE_ID', ''),
|
||||
],
|
||||
|
||||
// ---- Runtime --------------------------------------------------
|
||||
'runtime' => [
|
||||
'timezone' => radio_env('TIMEZONE', 'UTC'),
|
||||
'debug' => radio_env_bool('DEBUG', false),
|
||||
'show_errors' => radio_env_bool('SHOW_ERRORS', false),
|
||||
],
|
||||
];
|
||||
|
||||
$GLOBALS['__RADIO_CONFIG'] = $config;
|
||||
date_default_timezone_set($config['runtime']['timezone']);
|
||||
}
|
||||
|
||||
/** Return the whole config array. */
|
||||
function config() {
|
||||
return $GLOBALS['__RADIO_CONFIG'];
|
||||
}
|
||||
|
||||
/** Dot-path getter into the config array: cfg('theme.accent'). */
|
||||
function cfg($path, $default = null) {
|
||||
$node = $GLOBALS['__RADIO_CONFIG'];
|
||||
foreach (explode('.', $path) as $key) {
|
||||
if (is_array($node) && array_key_exists($key, $node)) {
|
||||
$node = $node[$key];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
|
||||
/** HTML-escape helper. */
|
||||
function e($str) {
|
||||
return htmlspecialchars((string) $str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* The subset of config that is safe and useful to expose to the browser.
|
||||
* Emitted as `window.RADIO_CONFIG` by the page.
|
||||
*/
|
||||
function radio_client_config() {
|
||||
return [
|
||||
'pollSeconds' => cfg('nowplaying.poll'),
|
||||
'artworkSource' => cfg('artwork.source'),
|
||||
'fallbackArt' => cfg('artwork.fallback'),
|
||||
'lyricsEnabled' => cfg('lyrics.enabled') && cfg('genius.token') !== '',
|
||||
'showListeners' => cfg('features.show_listeners'),
|
||||
'showHistory' => cfg('features.show_history'),
|
||||
'listenersLink' => cfg('features.listeners_link'),
|
||||
'siteName' => cfg('site.name'),
|
||||
];
|
||||
}
|
||||
Reference in new issue
Block a user