includes/settings.php
<?php
declare(strict_types=1);
/*
|--------------------------------------------------------------------------
| Brivacia Settings
|--------------------------------------------------------------------------
|
| Defaults to /data/settings.json and /data/brivacia.key.
| settings.json is the source of truth for user configuration.
|
*/
function brivacia_default_settings(): array
{
return [
'installed' => false,
'version' => '1.0.0',
'sites' => [
'main' => '',
],
'dashboard' => [
'auto_refresh' => 1,
'instance_name' => 'Brivacia',
'light_theme' => false,
'show_external_icon_in_top_pages' => true,
],
'admin' => [
'ignore_cookie_years' => 5,
],
'privacy' => [
'country_provider' => 'none',
'ip_prefix_octets' => 2,
],
'referrers' => [
'auto_referrers' => true,
'auto_referrer_icons' => true,
'max_icon_bytes' => 102400,
'max_icon_size' => 96,
],
'trends' => [
'visitors' => true,
'visits' => true,
'pageviews' => true,
],
];
}
function brivacia_root_path(string $path = ''): string
{
$root = rtrim(__DIR__ . '/..', '/\\');
$path = trim($path, '/\\');
return $path === '' ? $root : $root . '/' . $path;
}
function brivacia_default_data_dir(): string
{
return brivacia_root_path('data');
}
function brivacia_settings_path(): string
{
return brivacia_default_data_dir() . '/settings.json';
}
function brivacia_key_path(): string
{
return brivacia_default_data_dir() . '/brivacia.key';
}
function brivacia_read_json_file(string $file): ?array
{
if (!is_file($file)) {
return null;
}
$json = file_get_contents($file);
if ($json === false || trim($json) === '') {
return null;
}
$data = json_decode($json, true);
return is_array($data) ? $data : null;
}
function brivacia_array_merge_recursive_distinct(array $base, array $override): array
{
foreach ($override as $key => $value) {
if (
isset($base[$key]) &&
is_array($base[$key]) &&
is_array($value) &&
array_is_list($base[$key]) === false &&
array_is_list($value) === false
) {
$base[$key] = brivacia_array_merge_recursive_distinct($base[$key], $value);
continue;
}
$base[$key] = $value;
}
return $base;
}
function brivacia_settings(): array
{
static $settings = null;
if ($settings !== null) {
return $settings;
}
$loaded = brivacia_read_json_file(brivacia_settings_path()) ?? [];
$settings = brivacia_array_merge_recursive_distinct(
brivacia_default_settings(),
$loaded
);
if (isset($loaded['sites']) && is_array($loaded['sites'])) {
$settings['sites'] = $loaded['sites'];
}
return $settings;
}
function brivacia_setting(string $key, mixed $default = null): mixed
{
$value = brivacia_settings();
foreach (explode('.', $key) as $part) {
if (!is_array($value) || !array_key_exists($part, $value)) {
return $default;
}
$value = $value[$part];
}
return $value;
}
function brivacia_sites(): array
{
$sites = brivacia_setting('sites', []);
return is_array($sites) ? $sites : [];
}
function brivacia_public_settings(): array
{
$settings = brivacia_settings();
unset($settings['secret'], $settings['key']);
return $settings;
}
function brivacia_secret_key(): string
{
static $key = null;
if ($key !== null) {
return $key;
}
$file = brivacia_key_path();
if (!is_file($file)) {
return $key = '';
}
return $key = trim((string)file_get_contents($file));
}
function brivacia_is_installed(): bool
{
$settings = brivacia_settings();
$key = brivacia_secret_key();
return
$key !== ''
&& $key !== 'CHANGE_ME_BEFORE_INSTALLATION'
&& (bool)($settings['installed'] ?? false);
}
function brivacia_write_file_atomic(string $file, string $content): void
{
$dir = dirname($file);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
$tmp = $file . '.tmp.' . bin2hex(random_bytes(6));
if (file_put_contents($tmp, $content, LOCK_EX) === false) {
throw new RuntimeException('Unable to write temporary file: ' . $tmp);
}
if (!rename($tmp, $file)) {
@unlink($tmp);
throw new RuntimeException('Unable to write file: ' . $file);
}
}
function brivacia_save_settings(array $settings): void
{
$settings = brivacia_sanitize_settings($settings, brivacia_settings());
$dataDir = brivacia_default_data_dir();
if (!is_dir($dataDir)) {
mkdir($dataDir, 0755, true);
}
$keyFile = brivacia_key_path();
if (!is_file($keyFile)) {
brivacia_write_file_atomic($keyFile, bin2hex(random_bytes(32)) . "\n");
}
$json = json_encode(
$settings,
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
);
if ($json === false) {
throw new RuntimeException('Unable to encode settings JSON.');
}
brivacia_write_file_atomic(brivacia_settings_path(), $json . "\n");
}
function brivacia_sanitize_bool(mixed $value): bool
{
if (is_bool($value)) {
return $value;
}
if (is_string($value)) {
return in_array(strtolower($value), ['1', 'true', 'yes', 'on'], true);
}
return (bool)$value;
}
function brivacia_sanitize_int(mixed $value, int $min, int $max, int $default): int
{
if (!is_numeric($value)) {
return $default;
}
return max($min, min($max, (int)$value));
}
function brivacia_sanitize_site_code(string $code): string
{
$code = strtolower(trim($code));
$code = preg_replace('/[^a-z0-9_-]/', '-', $code) ?? '';
$code = trim($code, '-_');
return $code !== '' ? $code : 'main';
}
function brivacia_sanitize_domain(string $domain): string
{
$domain = strtolower(trim($domain));
$domain = preg_replace('#^https?://#', '', $domain) ?? $domain;
$domain = explode('/', $domain)[0] ?? $domain;
$domain = preg_replace('/[^a-z0-9.-]/', '', $domain) ?? '';
return trim($domain, '.');
}
function brivacia_sanitize_sites(mixed $sites): array
{
$clean = [];
if (is_array($sites)) {
foreach ($sites as $code => $domain) {
if (is_array($domain)) {
$code = (string)($domain['code'] ?? $code);
$domain = (string)($domain['domain'] ?? '');
}
$code = brivacia_sanitize_site_code((string)$code);
$domain = brivacia_sanitize_domain((string)$domain);
if ($domain !== '') {
$clean[$code] = $domain;
}
}
}
return $clean !== [] ? $clean : ['main' => ''];
}
function brivacia_sanitize_settings(array $input, ?array $base = null): array
{
$base ??= brivacia_default_settings();
$merged = brivacia_array_merge_recursive_distinct($base, $input);
$provider = (string)($merged['privacy']['country_provider'] ?? 'none');
if (!in_array($provider, ['none', 'blurloc', 'cloudflare'], true)) {
$provider = 'none';
}
return [
'installed' => brivacia_sanitize_bool($merged['installed'] ?? false),
'version' => preg_match('/^\d+\.\d+\.\d+$/', (string)($merged['version'] ?? '1.0.0'))
? (string)$merged['version']
: '1.0.0',
'sites' => brivacia_sanitize_sites($input['sites'] ?? $merged['sites'] ?? []),
'dashboard' => [
'auto_refresh' => brivacia_sanitize_int($merged['dashboard']['auto_refresh'] ?? 1, 0, 1440, 1),
'instance_name' => trim((string)($merged['dashboard']['instance_name'] ?? 'Brivacia')) ?: 'Brivacia',
'light_theme' => brivacia_sanitize_bool($merged['dashboard']['light_theme'] ?? false),
'show_external_icon_in_top_pages' => brivacia_sanitize_bool($merged['dashboard']['show_external_icon_in_top_pages'] ?? true),
],
'admin' => [
'ignore_cookie_years' => brivacia_sanitize_int($merged['admin']['ignore_cookie_years'] ?? 5, 1, 20, 5),
],
'privacy' => [
'country_provider' => $provider,
'ip_prefix_octets' => brivacia_sanitize_int($merged['privacy']['ip_prefix_octets'] ?? 2, 1, 3, 2),
],
'referrers' => [
'auto_referrers' => brivacia_sanitize_bool($merged['referrers']['auto_referrers'] ?? true),
'auto_referrer_icons' => brivacia_sanitize_bool($merged['referrers']['auto_referrer_icons'] ?? true),
'max_icon_bytes' => brivacia_sanitize_int($merged['referrers']['max_icon_bytes'] ?? 102400, 1024, 1048576, 102400),
'max_icon_size' => brivacia_sanitize_int($merged['referrers']['max_icon_size'] ?? 96, 16, 512, 96),
],
'trends' => [
'visitors' => brivacia_sanitize_bool($merged['trends']['visitors'] ?? true),
'visits' => brivacia_sanitize_bool($merged['trends']['visits'] ?? true),
'pageviews' => brivacia_sanitize_bool($merged['trends']['pageviews'] ?? true),
],
];
}
function brivacia_install(array $input): array
{
$settings = brivacia_sanitize_settings($input, brivacia_default_settings());
$settings['installed'] = true;
brivacia_save_settings($settings);
return $settings;
}