feat: Add comprehensive documentation suite and reorganize project structure
- Created complete documentation in docs/ directory - Added PROJECT_OVERVIEW.md with feature highlights and getting started guide - Added ARCHITECTURE.md with system design and technical details - Added SECURITY.md with comprehensive security implementation guide - Added DEVELOPMENT.md with development workflows and best practices - Added DEPLOYMENT.md with production deployment instructions - Added API.md with complete REST API documentation - Added CONTRIBUTING.md with contribution guidelines - Added CHANGELOG.md with version history and migration notes - Reorganized all documentation files into docs/ directory for better organization - Updated README.md with proper documentation links and quick navigation - Enhanced project structure with professional documentation standards
This commit is contained in:
864
f_core/f_classes/class.settings.php
Normal file
864
f_core/f_classes/class.settings.php
Normal file
@@ -0,0 +1,864 @@
|
||||
<?php
|
||||
/*******************************************************************************************************************
|
||||
| Software Name : EasyStream
|
||||
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
|
||||
| Software Author : (c) Sami Ahmed
|
||||
|*******************************************************************************************************************
|
||||
|
|
||||
|*******************************************************************************************************************
|
||||
| This source file is subject to the EasyStream Proprietary License Agreement.
|
||||
|
|
||||
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|
||||
|*******************************************************************************************************************
|
||||
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|
||||
|*******************************************************************************************************************/
|
||||
|
||||
defined('_ISVALID') or header('Location: /error');
|
||||
|
||||
/**
|
||||
* Settings Management Class
|
||||
* Provides unified interface for all system settings stored in db_settings table
|
||||
*/
|
||||
class Settings
|
||||
{
|
||||
private $pdo;
|
||||
private $redis;
|
||||
private $cache = [];
|
||||
private $cacheLoaded = false;
|
||||
private $redisKey = 'easystream:settings:all';
|
||||
private $redisTTL = 3600; // 1 hour cache
|
||||
|
||||
// Sensitive settings that should be encrypted
|
||||
private $encryptedKeys = [
|
||||
'paypal_secret',
|
||||
'paypal_client_id',
|
||||
'stripe_secret_key',
|
||||
'stripe_webhook_secret',
|
||||
'mail_smtp_password',
|
||||
'google_client_secret',
|
||||
'fb_app_secret',
|
||||
'twitter_api_secret',
|
||||
];
|
||||
|
||||
// Validation rules for settings
|
||||
private $validationRules = [
|
||||
// Email settings
|
||||
'backend_email' => ['type' => 'email', 'required' => true, 'label' => 'Admin Email'],
|
||||
'mail_smtp_username' => ['type' => 'email', 'required' => false, 'label' => 'SMTP Username'],
|
||||
'paypal_email' => ['type' => 'email', 'required' => false, 'label' => 'PayPal Email'],
|
||||
|
||||
// URL settings
|
||||
'branding_logo_url' => ['type' => 'url', 'required' => false, 'label' => 'Logo URL'],
|
||||
'branding_favicon_url' => ['type' => 'url', 'required' => false, 'label' => 'Favicon URL'],
|
||||
'main_url' => ['type' => 'url', 'required' => true, 'label' => 'Main URL'],
|
||||
|
||||
// Color settings
|
||||
'branding_primary_color' => ['type' => 'color', 'pattern' => '/^#[0-9A-F]{6}$/i', 'required' => true, 'label' => 'Primary Color'],
|
||||
'branding_secondary_color' => ['type' => 'color', 'pattern' => '/^#[0-9A-F]{6}$/i', 'required' => true, 'label' => 'Secondary Color'],
|
||||
'token_color' => ['type' => 'color', 'pattern' => '/^#[0-9A-F]{6}$/i', 'required' => false, 'label' => 'Token Color'],
|
||||
|
||||
// Integer settings
|
||||
'mail_smtp_port' => ['type' => 'int', 'min' => 1, 'max' => 65535, 'required' => true, 'label' => 'SMTP Port'],
|
||||
'creator_payout_percentage' => ['type' => 'int', 'min' => 0, 'max' => 100, 'required' => true, 'label' => 'Payout Percentage'],
|
||||
'signup_min_age' => ['type' => 'int', 'min' => 13, 'max' => 100, 'required' => true, 'label' => 'Minimum Age'],
|
||||
'signup_max_age' => ['type' => 'int', 'min' => 13, 'max' => 150, 'required' => true, 'label' => 'Maximum Age'],
|
||||
'signup_min_password' => ['type' => 'int', 'min' => 4, 'max' => 50, 'required' => true, 'label' => 'Min Password Length'],
|
||||
'signup_max_password' => ['type' => 'int', 'min' => 8, 'max' => 100, 'required' => true, 'label' => 'Max Password Length'],
|
||||
'signup_min_username' => ['type' => 'int', 'min' => 3, 'max' => 20, 'required' => true, 'label' => 'Min Username Length'],
|
||||
'signup_max_username' => ['type' => 'int', 'min' => 5, 'max' => 50, 'required' => true, 'label' => 'Max Username Length'],
|
||||
|
||||
// Float settings
|
||||
'minimum_payout_amount' => ['type' => 'float', 'min' => 0, 'required' => true, 'label' => 'Minimum Payout'],
|
||||
|
||||
// Boolean settings
|
||||
'video_module' => ['type' => 'bool', 'label' => 'Video Module'],
|
||||
'live_module' => ['type' => 'bool', 'label' => 'Live Module'],
|
||||
'short_module' => ['type' => 'bool', 'label' => 'Short Module'],
|
||||
'image_module' => ['type' => 'bool', 'label' => 'Image Module'],
|
||||
'audio_module' => ['type' => 'bool', 'label' => 'Audio Module'],
|
||||
'document_module' => ['type' => 'bool', 'label' => 'Document Module'],
|
||||
'blog_module' => ['type' => 'bool', 'label' => 'Blog Module'],
|
||||
'stripe_enabled' => ['type' => 'bool', 'label' => 'Stripe Enabled'],
|
||||
'paypal_test' => ['type' => 'bool', 'label' => 'PayPal Test Mode'],
|
||||
'creator_payout_enabled' => ['type' => 'bool', 'label' => 'Creator Payouts Enabled'],
|
||||
'login_remember' => ['type' => 'bool', 'label' => 'Remember Me Feature'],
|
||||
'mail_smtp_auth' => ['type' => 'bool_string', 'label' => 'SMTP Authentication'],
|
||||
|
||||
// String with pattern
|
||||
'stripe_publishable_key' => ['type' => 'string', 'pattern' => '/^pk_(test|live)_/', 'required' => false, 'label' => 'Stripe Publishable Key'],
|
||||
'stripe_secret_key' => ['type' => 'string', 'pattern' => '/^sk_(test|live)_/', 'required' => false, 'label' => 'Stripe Secret Key'],
|
||||
|
||||
// String with length
|
||||
'website_shortname' => ['type' => 'string', 'min_length' => 1, 'max_length' => 50, 'required' => true, 'label' => 'Website Name'],
|
||||
'head_title' => ['type' => 'string', 'min_length' => 1, 'max_length' => 100, 'required' => true, 'label' => 'Site Title'],
|
||||
'backend_email_fromname' => ['type' => 'string', 'min_length' => 1, 'max_length' => 50, 'required' => true, 'label' => 'Email From Name'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Initialize settings manager
|
||||
* @param PDO $pdo Database connection
|
||||
*/
|
||||
public function __construct($pdo = null)
|
||||
{
|
||||
if ($pdo) {
|
||||
$this->pdo = $pdo;
|
||||
} else {
|
||||
// Use global PDO if available
|
||||
global $db_pdo;
|
||||
$this->pdo = $db_pdo ?? null;
|
||||
}
|
||||
|
||||
// Initialize Redis if available
|
||||
try {
|
||||
if (class_exists('VRedis')) {
|
||||
$this->redis = VRedis::getInstance();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->redis = null;
|
||||
error_log("Settings: Redis not available - " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all settings into cache
|
||||
* @return bool Success status
|
||||
*/
|
||||
private function loadCache()
|
||||
{
|
||||
if ($this->cacheLoaded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try Redis first
|
||||
if ($this->redis) {
|
||||
try {
|
||||
$cached = $this->redis->get($this->redisKey);
|
||||
if ($cached) {
|
||||
$this->cache = json_decode($cached, true);
|
||||
$this->cacheLoaded = true;
|
||||
return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Settings: Redis cache read error - " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to database
|
||||
if (!$this->pdo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $this->pdo->query("SELECT cfg_name, cfg_data, cfg_info FROM db_settings");
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$this->cache[$row['cfg_name']] = [
|
||||
'value' => $row['cfg_data'],
|
||||
'info' => $row['cfg_info']
|
||||
];
|
||||
}
|
||||
$this->cacheLoaded = true;
|
||||
|
||||
// Store in Redis for next time
|
||||
if ($this->redis) {
|
||||
try {
|
||||
$this->redis->set(
|
||||
$this->redisKey,
|
||||
json_encode($this->cache),
|
||||
$this->redisTTL
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
error_log("Settings: Redis cache write error - " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (PDOException $e) {
|
||||
error_log("Settings cache load error: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a setting value
|
||||
* @param string $key Setting name
|
||||
* @param mixed $default Default value if not found
|
||||
* @return mixed Setting value or default
|
||||
*/
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
$this->loadCache();
|
||||
|
||||
if (isset($this->cache[$key])) {
|
||||
$value = $this->cache[$key]['value'];
|
||||
|
||||
// Decrypt if needed
|
||||
if ($this->shouldEncrypt($key)) {
|
||||
return $this->decrypt($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple settings at once
|
||||
* @param array $keys Array of setting names
|
||||
* @return array Associative array of settings
|
||||
*/
|
||||
public function getMultiple(array $keys)
|
||||
{
|
||||
$this->loadCache();
|
||||
$results = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$results[$key] = $this->get($key);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all settings in a category
|
||||
* @param string $prefix Category prefix (e.g., 'backend_', 'mail_', 'token_')
|
||||
* @return array Associative array of settings
|
||||
*/
|
||||
public function getByPrefix($prefix)
|
||||
{
|
||||
$this->loadCache();
|
||||
$results = [];
|
||||
|
||||
foreach ($this->cache as $key => $data) {
|
||||
if (strpos($key, $prefix) === 0) {
|
||||
$results[$key] = $data['value'];
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a setting should be encrypted
|
||||
* @param string $key Setting name
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldEncrypt($key)
|
||||
{
|
||||
return in_array($key, $this->encryptedKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get encryption key from config
|
||||
* @return string
|
||||
*/
|
||||
private function getEncryptionKey()
|
||||
{
|
||||
return defined('ENC_FIRSTKEY') ? base64_decode(ENC_FIRSTKEY) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get IV for encryption
|
||||
* @return string
|
||||
*/
|
||||
private function getIV()
|
||||
{
|
||||
return defined('ENC_SECONDKEY') ? substr(base64_decode(ENC_SECONDKEY), 0, 16) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a value
|
||||
* @param string $value Value to encrypt
|
||||
* @return string Encrypted value
|
||||
*/
|
||||
private function encrypt($value)
|
||||
{
|
||||
if (empty($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
try {
|
||||
$encrypted = openssl_encrypt(
|
||||
$value,
|
||||
'AES-256-CBC',
|
||||
$this->getEncryptionKey(),
|
||||
0,
|
||||
$this->getIV()
|
||||
);
|
||||
return base64_encode($encrypted);
|
||||
} catch (Exception $e) {
|
||||
error_log("Settings encryption error: " . $e->getMessage());
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a value
|
||||
* @param string $value Value to decrypt
|
||||
* @return string Decrypted value
|
||||
*/
|
||||
private function decrypt($value)
|
||||
{
|
||||
if (empty($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
try {
|
||||
$decrypted = openssl_decrypt(
|
||||
base64_decode($value),
|
||||
'AES-256-CBC',
|
||||
$this->getEncryptionKey(),
|
||||
0,
|
||||
$this->getIV()
|
||||
);
|
||||
return $decrypted ?: $value;
|
||||
} catch (Exception $e) {
|
||||
error_log("Settings decryption error: " . $e->getMessage());
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a setting value
|
||||
* @param string $key Setting name
|
||||
* @param mixed $value Value to validate
|
||||
* @return array ['valid' => bool, 'error' => string]
|
||||
*/
|
||||
public function validate($key, $value)
|
||||
{
|
||||
// No rules = allow
|
||||
if (!isset($this->validationRules[$key])) {
|
||||
return ['valid' => true];
|
||||
}
|
||||
|
||||
$rule = $this->validationRules[$key];
|
||||
|
||||
// Check required
|
||||
if (isset($rule['required']) && $rule['required'] && ($value === '' || $value === null)) {
|
||||
return [
|
||||
'valid' => false,
|
||||
'error' => "{$rule['label']} is required"
|
||||
];
|
||||
}
|
||||
|
||||
// Skip validation if empty and not required
|
||||
if (($value === '' || $value === null) && (!isset($rule['required']) || !$rule['required'])) {
|
||||
return ['valid' => true];
|
||||
}
|
||||
|
||||
// Type-specific validation
|
||||
switch ($rule['type']) {
|
||||
case 'email':
|
||||
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
|
||||
return ['valid' => false, 'error' => "{$rule['label']} must be a valid email address"];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
if (!filter_var($value, FILTER_VALIDATE_URL)) {
|
||||
return ['valid' => false, 'error' => "{$rule['label']} must be a valid URL"];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'color':
|
||||
if (isset($rule['pattern']) && !preg_match($rule['pattern'], $value)) {
|
||||
return ['valid' => false, 'error' => "{$rule['label']} must be a valid hex color (e.g., #FF0000)"];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'int':
|
||||
if (!is_numeric($value) || (int)$value != $value) {
|
||||
return ['valid' => false, 'error' => "{$rule['label']} must be a whole number"];
|
||||
}
|
||||
$intVal = (int)$value;
|
||||
if (isset($rule['min']) && $intVal < $rule['min']) {
|
||||
return ['valid' => false, 'error' => "{$rule['label']} must be at least {$rule['min']}"];
|
||||
}
|
||||
if (isset($rule['max']) && $intVal > $rule['max']) {
|
||||
return ['valid' => false, 'error' => "{$rule['label']} must be at most {$rule['max']}"];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'float':
|
||||
if (!is_numeric($value)) {
|
||||
return ['valid' => false, 'error' => "{$rule['label']} must be a number"];
|
||||
}
|
||||
$floatVal = (float)$value;
|
||||
if (isset($rule['min']) && $floatVal < $rule['min']) {
|
||||
return ['valid' => false, 'error' => "{$rule['label']} must be at least {$rule['min']}"];
|
||||
}
|
||||
if (isset($rule['max']) && $floatVal > $rule['max']) {
|
||||
return ['valid' => false, 'error' => "{$rule['label']} must be at most {$rule['max']}"];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'bool':
|
||||
if (!in_array($value, ['0', '1', 0, 1, true, false], true)) {
|
||||
return ['valid' => false, 'error' => "{$rule['label']} must be 0 or 1"];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'bool_string':
|
||||
if (!in_array($value, ['true', 'false'], true)) {
|
||||
return ['valid' => false, 'error' => "{$rule['label']} must be 'true' or 'false'"];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
if (isset($rule['min_length']) && strlen($value) < $rule['min_length']) {
|
||||
return ['valid' => false, 'error' => "{$rule['label']} must be at least {$rule['min_length']} characters"];
|
||||
}
|
||||
if (isset($rule['max_length']) && strlen($value) > $rule['max_length']) {
|
||||
return ['valid' => false, 'error' => "{$rule['label']} must be at most {$rule['max_length']} characters"];
|
||||
}
|
||||
if (isset($rule['pattern']) && !preg_match($rule['pattern'], $value)) {
|
||||
return ['valid' => false, 'error' => "{$rule['label']} has an invalid format"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return ['valid' => true];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a setting value
|
||||
* @param string $key Setting name
|
||||
* @param mixed $value Setting value
|
||||
* @param string $info Optional description
|
||||
* @return bool Success status
|
||||
*/
|
||||
public function set($key, $value, $info = '')
|
||||
{
|
||||
if (!$this->pdo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate before saving
|
||||
$validation = $this->validate($key, $value);
|
||||
if (!$validation['valid']) {
|
||||
throw new InvalidArgumentException($validation['error']);
|
||||
}
|
||||
|
||||
try {
|
||||
// Get old value for audit trail
|
||||
$oldValue = $this->get($key);
|
||||
|
||||
// Encrypt sensitive values before saving
|
||||
$saveValue = $this->shouldEncrypt($key) ? $this->encrypt($value) : $value;
|
||||
|
||||
// Check if setting exists
|
||||
$stmt = $this->pdo->prepare("SELECT id FROM db_settings WHERE cfg_name = ?");
|
||||
$stmt->execute([$key]);
|
||||
$exists = $stmt->fetch();
|
||||
|
||||
if ($exists) {
|
||||
// Update existing setting
|
||||
$stmt = $this->pdo->prepare("UPDATE db_settings SET cfg_data = ?, cfg_info = ? WHERE cfg_name = ?");
|
||||
$result = $stmt->execute([$saveValue, $info ?: $this->cache[$key]['info'] ?? '', $key]);
|
||||
} else {
|
||||
// Insert new setting
|
||||
$stmt = $this->pdo->prepare("INSERT INTO db_settings (cfg_name, cfg_data, cfg_info) VALUES (?, ?, ?)");
|
||||
$result = $stmt->execute([$key, $saveValue, $info]);
|
||||
}
|
||||
|
||||
// Update cache
|
||||
$this->cache[$key] = [
|
||||
'value' => $value,
|
||||
'info' => $info ?: ($this->cache[$key]['info'] ?? '')
|
||||
];
|
||||
|
||||
// Log the change to audit trail
|
||||
$this->logChange($key, $oldValue, $value);
|
||||
|
||||
// Invalidate Redis cache
|
||||
if ($this->redis) {
|
||||
try {
|
||||
$this->redis->delete($this->redisKey);
|
||||
} catch (Exception $e) {
|
||||
error_log("Settings: Redis cache invalidation error - " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (PDOException $e) {
|
||||
error_log("Settings set error: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple settings at once
|
||||
* @param array $settings Associative array of key => value pairs
|
||||
* @return bool Success status
|
||||
*/
|
||||
public function setMultiple(array $settings)
|
||||
{
|
||||
if (!$this->pdo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
foreach ($settings as $key => $value) {
|
||||
$this->set($key, $value);
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
error_log("Settings setMultiple error: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a setting
|
||||
* @param string $key Setting name
|
||||
* @return bool Success status
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
if (!$this->pdo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("DELETE FROM db_settings WHERE cfg_name = ?");
|
||||
$result = $stmt->execute([$key]);
|
||||
|
||||
unset($this->cache[$key]);
|
||||
|
||||
return $result;
|
||||
} catch (PDOException $e) {
|
||||
error_log("Settings delete error: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all settings grouped by category
|
||||
* @return array Multi-dimensional array of settings by category
|
||||
*/
|
||||
public function getAllGrouped()
|
||||
{
|
||||
$this->loadCache();
|
||||
|
||||
$grouped = [
|
||||
'general' => [],
|
||||
'backend' => [],
|
||||
'frontend' => [],
|
||||
'mail' => [],
|
||||
'payment' => [],
|
||||
'token' => [],
|
||||
'branding' => [],
|
||||
'security' => [],
|
||||
'seo' => [],
|
||||
'modules' => [],
|
||||
'conversion' => [],
|
||||
'social' => [],
|
||||
'other' => []
|
||||
];
|
||||
|
||||
foreach ($this->cache as $key => $data) {
|
||||
$category = 'other';
|
||||
|
||||
if (strpos($key, 'backend_') === 0) {
|
||||
$category = 'backend';
|
||||
} elseif (strpos($key, 'mail_') === 0) {
|
||||
$category = 'mail';
|
||||
} elseif (strpos($key, 'paypal_') === 0 || strpos($key, 'stripe_') === 0 || strpos($key, 'payment_') === 0) {
|
||||
$category = 'payment';
|
||||
} elseif (strpos($key, 'token_') === 0) {
|
||||
$category = 'token';
|
||||
} elseif (strpos($key, 'branding_') === 0 || strpos($key, 'color_') === 0 || strpos($key, 'logo_') === 0) {
|
||||
$category = 'branding';
|
||||
} elseif (strpos($key, 'signup_') === 0 || strpos($key, 'login_') === 0 || strpos($key, 'username_') === 0) {
|
||||
$category = 'security';
|
||||
} elseif (strpos($key, 'head_') === 0 || strpos($key, 'metaname_') === 0 || strpos($key, 'website_') === 0) {
|
||||
$category = 'seo';
|
||||
} elseif (strpos($key, '_module') !== false || $key === 'paid_memberships') {
|
||||
$category = 'modules';
|
||||
} elseif (strpos($key, 'conversion_') === 0) {
|
||||
$category = 'conversion';
|
||||
} elseif (strpos($key, 'fb_') === 0 || strpos($key, 'google_') === 0 || strpos($key, 'twitter_') === 0) {
|
||||
$category = 'social';
|
||||
} else {
|
||||
$category = 'general';
|
||||
}
|
||||
|
||||
$grouped[$category][$key] = [
|
||||
'value' => $data['value'],
|
||||
'info' => $data['info']
|
||||
];
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all settings as JSON
|
||||
* @return string JSON string
|
||||
*/
|
||||
public function exportJSON()
|
||||
{
|
||||
$this->loadCache();
|
||||
return json_encode($this->cache, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import settings from JSON
|
||||
* @param string $json JSON string
|
||||
* @return bool Success status
|
||||
*/
|
||||
public function importJSON($json)
|
||||
{
|
||||
$data = json_decode($json, true);
|
||||
|
||||
if (!$data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
foreach ($data as $key => $item) {
|
||||
$this->set($key, $item['value'], $item['info'] ?? '');
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
error_log("Settings import error: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a module is enabled
|
||||
* @param string $module Module name (video, live, blog, etc.)
|
||||
* @return bool Enabled status
|
||||
*/
|
||||
public function isModuleEnabled($module)
|
||||
{
|
||||
return $this->get($module . '_module', '0') === '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable a module
|
||||
* @param string $module Module name
|
||||
* @param bool $enabled Enable/disable
|
||||
* @return bool Success status
|
||||
*/
|
||||
public function setModuleEnabled($module, $enabled)
|
||||
{
|
||||
return $this->set($module . '_module', $enabled ? '1' : '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site branding information
|
||||
* @return array Branding settings
|
||||
*/
|
||||
public function getBranding()
|
||||
{
|
||||
return [
|
||||
'site_name' => $this->get('website_shortname', 'EasyStream'),
|
||||
'site_title' => $this->get('head_title', 'EasyStream'),
|
||||
'primary_color' => $this->get('branding_primary_color', '#1a73e8'),
|
||||
'secondary_color' => $this->get('branding_secondary_color', '#34a853'),
|
||||
'logo_url' => $this->get('branding_logo_url', ''),
|
||||
'favicon_url' => $this->get('branding_favicon_url', ''),
|
||||
'meta_description' => $this->get('metaname_description', ''),
|
||||
'meta_keywords' => $this->get('metaname_keywords', '')
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get payment gateway configuration
|
||||
* @return array Payment settings
|
||||
*/
|
||||
public function getPaymentConfig()
|
||||
{
|
||||
return [
|
||||
'enabled_gateways' => explode(',', $this->get('payment_methods', 'Paypal')),
|
||||
'paypal' => [
|
||||
'email' => $this->get('paypal_email', ''),
|
||||
'test_mode' => $this->get('paypal_test', '1') === '1',
|
||||
'client_id' => $this->get('paypal_client_id', ''),
|
||||
'secret' => $this->get('paypal_secret', '')
|
||||
],
|
||||
'stripe' => [
|
||||
'enabled' => $this->get('stripe_enabled', '0') === '1',
|
||||
'publishable_key' => $this->get('stripe_publishable_key', ''),
|
||||
'secret_key' => $this->get('stripe_secret_key', ''),
|
||||
'webhook_secret' => $this->get('stripe_webhook_secret', '')
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get email/SMTP configuration
|
||||
* @return array Email settings
|
||||
*/
|
||||
public function getEmailConfig()
|
||||
{
|
||||
return [
|
||||
'type' => $this->get('mail_type', 'smtp'),
|
||||
'from_email' => $this->get('backend_email', ''),
|
||||
'from_name' => $this->get('backend_email_fromname', 'Webmaster'),
|
||||
'smtp' => [
|
||||
'host' => $this->get('mail_smtp_host', 'smtp.gmail.com'),
|
||||
'port' => (int)$this->get('mail_smtp_port', '587'),
|
||||
'username' => $this->get('mail_smtp_username', ''),
|
||||
'password' => $this->get('mail_smtp_password', ''),
|
||||
'auth' => $this->get('mail_smtp_auth', 'true') === 'true',
|
||||
'prefix' => $this->get('mail_smtp_prefix', 'tls')
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token economy configuration
|
||||
* @return array Token settings
|
||||
*/
|
||||
public function getTokenConfig()
|
||||
{
|
||||
return [
|
||||
'enabled' => $this->get('token_system_enabled', '1') === '1',
|
||||
'name' => $this->get('token_name', 'Tokens'),
|
||||
'symbol' => $this->get('token_symbol', 'TKN'),
|
||||
'color' => $this->get('token_color', '#FFD700'),
|
||||
'icon_url' => $this->get('token_icon_url', ''),
|
||||
'creator_payout_enabled' => $this->get('creator_payout_enabled', '0') === '1',
|
||||
'creator_payout_percentage' => (int)$this->get('creator_payout_percentage', '70'),
|
||||
'minimum_payout' => (float)$this->get('minimum_payout_amount', '50.00')
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a settings change to audit trail
|
||||
* @param string $key Setting name
|
||||
* @param mixed $oldValue Old value
|
||||
* @param mixed $newValue New value
|
||||
* @param string $reason Change reason
|
||||
* @return bool Success status
|
||||
*/
|
||||
private function logChange($key, $oldValue, $newValue, $reason = '')
|
||||
{
|
||||
if (!$this->pdo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
global $usr;
|
||||
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT INTO db_settings_history
|
||||
(cfg_name, old_value, new_value, changed_by, ip_address, user_agent, change_reason)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
|
||||
return $stmt->execute([
|
||||
$key,
|
||||
$oldValue,
|
||||
$newValue,
|
||||
$usr['id'] ?? $_SESSION['admin_user_id'] ?? 0,
|
||||
$_SERVER['REMOTE_ADDR'] ?? '',
|
||||
substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255),
|
||||
$reason
|
||||
]);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Settings audit log error: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get change history for a specific setting
|
||||
* @param string $key Setting name
|
||||
* @param int $limit Number of records to return
|
||||
* @return array History records
|
||||
*/
|
||||
public function getHistory($key, $limit = 20)
|
||||
{
|
||||
if (!$this->pdo) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT h.*, u.usr_user as username
|
||||
FROM db_settings_history h
|
||||
LEFT JOIN db_accountuser u ON h.changed_by = u.usr_id
|
||||
WHERE h.cfg_name = ?
|
||||
ORDER BY h.changed_at DESC
|
||||
LIMIT ?
|
||||
");
|
||||
$stmt->execute([$key, $limit]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Settings history error: " . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all recent settings changes
|
||||
* @param int $limit Number of records to return
|
||||
* @return array History records
|
||||
*/
|
||||
public function getAllRecentChanges($limit = 50)
|
||||
{
|
||||
if (!$this->pdo) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT h.*, u.usr_user as username, s.cfg_info
|
||||
FROM db_settings_history h
|
||||
LEFT JOIN db_accountuser u ON h.changed_by = u.usr_id
|
||||
LEFT JOIN db_settings s ON h.cfg_name = s.cfg_name
|
||||
ORDER BY h.changed_at DESC
|
||||
LIMIT ?
|
||||
");
|
||||
$stmt->execute([$limit]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Settings recent changes error: " . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback a setting to a previous value from history
|
||||
* @param string $key Setting name
|
||||
* @param int $historyId History record ID to rollback to
|
||||
* @return bool Success status
|
||||
*/
|
||||
public function rollback($key, $historyId)
|
||||
{
|
||||
if (!$this->pdo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the historical value
|
||||
$stmt = $this->pdo->prepare("SELECT old_value FROM db_settings_history WHERE id = ? AND cfg_name = ?");
|
||||
$stmt->execute([$historyId, $key]);
|
||||
$history = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$history) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the old value back
|
||||
return $this->set($key, $history['old_value'], '', 'Rollback from history #' . $historyId);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Settings rollback error: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user