- 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
1042 lines
32 KiB
PHP
1042 lines
32 KiB
PHP
<?php
|
|
/**
|
|
* Data providers for the lightweight admin panel.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/bootstrap.php';
|
|
|
|
function admin_fetch_user_stats(PDO $pdo): array
|
|
{
|
|
$defaults = ['total' => 0, 'today' => 0, 'active' => 0, 'verified' => 0];
|
|
|
|
try {
|
|
$stmt = $pdo->query("
|
|
SELECT
|
|
COUNT(*) AS total,
|
|
COUNT(CASE WHEN DATE(usr_joindate) = CURDATE() THEN 1 END) AS today,
|
|
COUNT(CASE WHEN usr_active = 1 AND usr_deleted = 0 THEN 1 END) AS active,
|
|
COUNT(CASE WHEN usr_verified = 1 THEN 1 END) AS verified
|
|
FROM db_accountuser
|
|
");
|
|
$row = $stmt->fetch() ?: [];
|
|
return array_merge($defaults, [
|
|
'total' => (int) ($row['total'] ?? 0),
|
|
'today' => (int) ($row['today'] ?? 0),
|
|
'active' => (int) ($row['active'] ?? 0),
|
|
'verified' => (int) ($row['verified'] ?? 0),
|
|
]);
|
|
} catch (Throwable) {
|
|
return $defaults;
|
|
}
|
|
}
|
|
|
|
function admin_fetch_video_stats(PDO $pdo): array
|
|
{
|
|
$defaults = ['total' => 0, 'today' => 0, 'approved' => 0, 'pending' => 0, 'processing' => 0];
|
|
|
|
try {
|
|
$stmt = $pdo->query("
|
|
SELECT
|
|
COUNT(*) AS total,
|
|
COUNT(CASE WHEN DATE(upload_date) = CURDATE() THEN 1 END) AS today,
|
|
COUNT(CASE WHEN approved = '1' THEN 1 END) AS approved,
|
|
COUNT(CASE WHEN approved = '0' THEN 1 END) AS pending,
|
|
COUNT(CASE WHEN processing_status IN ('queued','processing') THEN 1 END) AS processing
|
|
FROM db_videofiles
|
|
WHERE deleted = '0'
|
|
");
|
|
$row = $stmt->fetch() ?: [];
|
|
return array_merge($defaults, [
|
|
'total' => (int) ($row['total'] ?? 0),
|
|
'today' => (int) ($row['today'] ?? 0),
|
|
'approved' => (int) ($row['approved'] ?? 0),
|
|
'pending' => (int) ($row['pending'] ?? 0),
|
|
'processing' => (int) ($row['processing'] ?? 0),
|
|
]);
|
|
} catch (Throwable) {
|
|
return $defaults;
|
|
}
|
|
}
|
|
|
|
function admin_fetch_stream_stats(PDO $pdo): array
|
|
{
|
|
$defaults = ['total' => 0, 'active' => 0, 'today' => 0];
|
|
|
|
try {
|
|
$stmt = $pdo->query("
|
|
SELECT
|
|
COUNT(*) AS total,
|
|
COUNT(CASE WHEN live_status = 'live' THEN 1 END) AS active,
|
|
COUNT(CASE WHEN DATE(date_created) = CURDATE() THEN 1 END) AS today
|
|
FROM db_livefiles
|
|
WHERE deleted = '0'
|
|
");
|
|
$row = $stmt->fetch() ?: [];
|
|
return array_merge($defaults, [
|
|
'total' => (int) ($row['total'] ?? 0),
|
|
'active' => (int) ($row['active'] ?? 0),
|
|
'today' => (int) ($row['today'] ?? 0),
|
|
]);
|
|
} catch (Throwable) {
|
|
return $defaults;
|
|
}
|
|
}
|
|
|
|
function admin_fetch_total_views(PDO $pdo): int
|
|
{
|
|
try {
|
|
$stmt = $pdo->query("SELECT COALESCE(SUM(file_views), 0) AS total_views FROM db_videofiles WHERE deleted = '0'");
|
|
return (int) ($stmt->fetchColumn() ?: 0);
|
|
} catch (Throwable) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
function admin_fetch_system_health(PDO $pdo): array
|
|
{
|
|
$health = [
|
|
'database' => ['status' => 'unknown', 'details' => null],
|
|
'storage' => ['status' => 'unknown', 'details' => null],
|
|
];
|
|
|
|
try {
|
|
$stmt = $pdo->query("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE()");
|
|
$count = (int) $stmt->fetchColumn();
|
|
$health['database'] = [
|
|
'status' => 'healthy',
|
|
'details' => sprintf('%d tables accessible', $count),
|
|
];
|
|
} catch (Throwable $e) {
|
|
$health['database'] = [
|
|
'status' => 'error',
|
|
'details' => $e->getMessage(),
|
|
];
|
|
}
|
|
|
|
$storagePath = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'f_data' . DIRECTORY_SEPARATOR . 'data_userfiles';
|
|
if (is_dir($storagePath) && is_writable($storagePath)) {
|
|
$health['storage'] = [
|
|
'status' => 'healthy',
|
|
'details' => 'Storage writable',
|
|
];
|
|
} elseif (is_dir($storagePath)) {
|
|
$health['storage'] = [
|
|
'status' => 'warning',
|
|
'details' => 'Directory exists but is not writable',
|
|
];
|
|
} else {
|
|
$health['storage'] = [
|
|
'status' => 'error',
|
|
'details' => 'Storage directory missing',
|
|
];
|
|
}
|
|
|
|
return $health;
|
|
}
|
|
|
|
function admin_fetch_recent_activity(PDO $pdo, int $limit = 10): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("
|
|
SELECT ip_address, user_agent, timestamp, request_uri
|
|
FROM db_ip_tracking
|
|
ORDER BY timestamp DESC
|
|
LIMIT :limit
|
|
");
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function admin_fetch_pending_videos(PDO $pdo, int $limit = 5): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("
|
|
SELECT file_key, file_title, usr_id, upload_date, processing_status
|
|
FROM db_videofiles
|
|
WHERE approved = '0' AND deleted = '0'
|
|
ORDER BY upload_date DESC
|
|
LIMIT :limit
|
|
");
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function admin_fetch_recent_users(PDO $pdo, int $limit = 15): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("
|
|
SELECT usr_id, usr_user, usr_email, usr_joindate, usr_active, usr_verified, token_balance
|
|
FROM db_accountuser
|
|
ORDER BY usr_joindate DESC
|
|
LIMIT :limit
|
|
");
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function admin_fetch_role_distribution(PDO $pdo): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->query("
|
|
SELECT usr_role AS role, COUNT(*) AS total
|
|
FROM db_accountuser
|
|
GROUP BY usr_role
|
|
ORDER BY total DESC
|
|
");
|
|
return $stmt->fetchAll();
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function admin_fetch_user_growth(PDO $pdo, int $days = 7): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("
|
|
SELECT DATE(usr_joindate) AS date, COUNT(*) AS total
|
|
FROM db_accountuser
|
|
WHERE usr_joindate >= DATE_SUB(CURDATE(), INTERVAL :days DAY)
|
|
GROUP BY DATE(usr_joindate)
|
|
ORDER BY DATE(usr_joindate) ASC
|
|
");
|
|
$stmt->bindValue(':days', $days, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function admin_fetch_top_creators(PDO $pdo, int $limit = 10): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("
|
|
SELECT usr_id, usr_user, usr_dname, usr_v_count, token_balance, total_tokens_earned
|
|
FROM db_accountuser
|
|
ORDER BY usr_v_count DESC
|
|
LIMIT :limit
|
|
");
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function admin_fetch_content_overview(PDO $pdo): array
|
|
{
|
|
$overview = [
|
|
'videos' => admin_fetch_video_stats($pdo),
|
|
'live' => admin_fetch_stream_stats($pdo),
|
|
'shorts' => 0,
|
|
'images' => 0,
|
|
'audio' => 0,
|
|
'documents' => 0,
|
|
'blogs' => 0,
|
|
];
|
|
|
|
$tables = [
|
|
'shorts' => 'db_shortfiles',
|
|
'images' => 'db_imagefiles',
|
|
'audio' => 'db_audiofiles',
|
|
'documents' => 'db_documentfiles',
|
|
'blogs' => 'db_blogfiles',
|
|
];
|
|
|
|
foreach ($tables as $key => $table) {
|
|
try {
|
|
$stmt = $pdo->query("SELECT COUNT(*) FROM {$table}");
|
|
$overview[$key] = (int) $stmt->fetchColumn();
|
|
} catch (Throwable) {
|
|
$overview[$key] = 0;
|
|
}
|
|
}
|
|
|
|
return $overview;
|
|
}
|
|
|
|
function admin_fetch_recent_uploads(PDO $pdo, int $limit = 10): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("
|
|
SELECT file_key, file_title, usr_id, upload_date, approved, processing_status, file_views
|
|
FROM db_videofiles
|
|
WHERE deleted = '0'
|
|
ORDER BY upload_date DESC
|
|
LIMIT :limit
|
|
");
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function admin_fetch_processing_queue(PDO $pdo, int $limit = 10): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("
|
|
SELECT file_key, file_title, usr_id, processing_status, updated_at
|
|
FROM db_videofiles
|
|
WHERE processing_status IN ('queued','processing')
|
|
ORDER BY updated_at DESC
|
|
LIMIT :limit
|
|
");
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function admin_fetch_live_stream_list(PDO $pdo, int $limit = 10): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("
|
|
SELECT stream_key, usr_id, stream_title, live_status, viewers, updated_at
|
|
FROM db_livefiles
|
|
ORDER BY updated_at DESC
|
|
LIMIT :limit
|
|
");
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function admin_fetch_token_summary(PDO $pdo): array
|
|
{
|
|
$summary = [
|
|
'total_balance' => 0,
|
|
'total_earned' => 0,
|
|
'total_spent' => 0,
|
|
'purchase_count' => 0,
|
|
'purchase_volume' => 0,
|
|
'transactions_today' => 0,
|
|
];
|
|
|
|
try {
|
|
$stmt = $pdo->query("
|
|
SELECT
|
|
SUM(token_balance) AS total_balance,
|
|
SUM(total_tokens_earned) AS total_earned,
|
|
SUM(total_tokens_spent) AS total_spent
|
|
FROM db_accountuser
|
|
");
|
|
$row = $stmt->fetch() ?: [];
|
|
$summary['total_balance'] = (float) ($row['total_balance'] ?? 0);
|
|
$summary['total_earned'] = (float) ($row['total_earned'] ?? 0);
|
|
$summary['total_spent'] = (float) ($row['total_spent'] ?? 0);
|
|
} catch (Throwable) {
|
|
// ignore
|
|
}
|
|
|
|
try {
|
|
$stmt = $pdo->query("
|
|
SELECT
|
|
COUNT(*) AS purchase_count,
|
|
SUM(usd_amount) AS purchase_volume
|
|
FROM token_purchases
|
|
WHERE status = 'completed'
|
|
");
|
|
$row = $stmt->fetch() ?: [];
|
|
$summary['purchase_count'] = (int) ($row['purchase_count'] ?? 0);
|
|
$summary['purchase_volume'] = (float) ($row['purchase_volume'] ?? 0);
|
|
} catch (Throwable) {
|
|
// ignore
|
|
}
|
|
|
|
try {
|
|
$stmt = $pdo->query("
|
|
SELECT COUNT(*)
|
|
FROM token_transactions
|
|
WHERE DATE(created_at) = CURDATE()
|
|
");
|
|
$summary['transactions_today'] = (int) ($stmt->fetchColumn() ?: 0);
|
|
} catch (Throwable) {
|
|
// ignore
|
|
}
|
|
|
|
return $summary;
|
|
}
|
|
|
|
function admin_fetch_top_token_users(PDO $pdo, int $limit = 10): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("SELECT * FROM token_user_summary ORDER BY total_tokens_earned DESC LIMIT :limit");
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
} catch (Throwable) {
|
|
// Fallback if view not available
|
|
try {
|
|
$stmt = $pdo->prepare("
|
|
SELECT usr_id AS user_id, usr_user AS username, token_balance, total_tokens_earned, total_tokens_spent
|
|
FROM db_accountuser
|
|
ORDER BY token_balance DESC
|
|
LIMIT :limit
|
|
");
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
}
|
|
|
|
function admin_fetch_token_daily_stats(PDO $pdo, int $limit = 14): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("SELECT * FROM token_daily_stats ORDER BY date DESC LIMIT :limit");
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
} catch (Throwable) {
|
|
try {
|
|
$stmt = $pdo->prepare("
|
|
SELECT
|
|
DATE(created_at) AS date,
|
|
COUNT(*) AS total_transactions,
|
|
SUM(amount) AS tokens_purchased
|
|
FROM token_transactions
|
|
WHERE status = 'completed'
|
|
GROUP BY DATE(created_at)
|
|
ORDER BY DATE(created_at) DESC
|
|
LIMIT :limit
|
|
");
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
}
|
|
|
|
function admin_fetch_recent_token_activity(PDO $pdo, int $limit = 12): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("
|
|
SELECT id, user_id, amount, transaction_type, status, created_at
|
|
FROM token_transactions
|
|
ORDER BY created_at DESC
|
|
LIMIT :limit
|
|
");
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function admin_fetch_token_packages(PDO $pdo): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("
|
|
SELECT cfg_name, cfg_data
|
|
FROM db_settings
|
|
WHERE cfg_name LIKE 'token_package_%'
|
|
ORDER BY cfg_name ASC
|
|
");
|
|
$stmt->execute();
|
|
$packages = [];
|
|
foreach ($stmt->fetchAll() as $row) {
|
|
$decoded = json_decode($row['cfg_data'], true);
|
|
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
|
|
$packages[] = array_merge(['key' => $row['cfg_name']], $decoded);
|
|
}
|
|
}
|
|
return $packages;
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function admin_get_token_settings(PDO $pdo): array
|
|
{
|
|
$defaults = [
|
|
'name' => 'EasyCoins',
|
|
'symbol' => 'EC',
|
|
'plural' => 'EasyCoins',
|
|
'description' => 'Platform currency for tips and donations',
|
|
'icon' => '/f_templates/tpl_frontend/img/default-token.png',
|
|
'color_primary' => '#FFD700',
|
|
'color_secondary' => '#FFA500',
|
|
];
|
|
|
|
try {
|
|
$stmt = $pdo->query("SELECT cfg_name, cfg_data FROM db_settings WHERE cfg_name LIKE 'token_%'");
|
|
$settings = $defaults;
|
|
foreach ($stmt->fetchAll() as $row) {
|
|
$key = str_replace('token_', '', $row['cfg_name']);
|
|
if (array_key_exists($key, $defaults)) {
|
|
$settings[$key] = $row['cfg_data'];
|
|
}
|
|
}
|
|
return $settings;
|
|
} catch (Throwable) {
|
|
return $defaults;
|
|
}
|
|
}
|
|
|
|
function admin_update_token_settings(PDO $pdo, array $settings): void
|
|
{
|
|
$stmt = $pdo->prepare("
|
|
INSERT INTO db_settings (cfg_name, cfg_data, cfg_info)
|
|
VALUES (:name, :value, :info)
|
|
ON DUPLICATE KEY UPDATE cfg_data = :value, cfg_info = :info
|
|
");
|
|
|
|
foreach ($settings as $key => $value) {
|
|
$cfgName = str_starts_with($key, 'token_') ? $key : "token_{$key}";
|
|
$info = ucfirst(str_replace('_', ' ', str_replace('token_', '', $cfgName)));
|
|
$stmt->execute([
|
|
':name' => $cfgName,
|
|
':value' => $value,
|
|
':info' => $info,
|
|
]);
|
|
}
|
|
}
|
|
|
|
function admin_handle_token_icon_upload(PDO $pdo, array $file): array
|
|
{
|
|
if (!isset($file['tmp_name']) || $file['error'] !== UPLOAD_ERR_OK) {
|
|
return ['success' => false, 'message' => 'No file uploaded or upload error'];
|
|
}
|
|
|
|
$allowedMime = [
|
|
'image/png' => 'png',
|
|
'image/jpeg' => 'jpg',
|
|
'image/gif' => 'gif',
|
|
'image/svg+xml' => 'svg',
|
|
];
|
|
|
|
$mime = $file['type'] ?? '';
|
|
if (!isset($allowedMime[$mime])) {
|
|
return ['success' => false, 'message' => 'Invalid file type. Allowed: PNG, JPG, GIF, SVG'];
|
|
}
|
|
|
|
if (($file['size'] ?? 0) > 2 * 1024 * 1024) {
|
|
return ['success' => false, 'message' => 'File too large. Maximum size is 2MB'];
|
|
}
|
|
|
|
$extension = $allowedMime[$mime];
|
|
$uploadDir = dirname(__DIR__, 2) . '/f_templates/tpl_frontend/img/tokens/';
|
|
|
|
if (!is_dir($uploadDir) && !mkdir($uploadDir, 0755, true) && !is_dir($uploadDir)) {
|
|
return ['success' => false, 'message' => 'Failed to create upload directory'];
|
|
}
|
|
|
|
$filename = 'token_icon_' . time() . '_' . bin2hex(random_bytes(4)) . '.' . $extension;
|
|
$destination = $uploadDir . $filename;
|
|
|
|
if (!move_uploaded_file($file['tmp_name'], $destination)) {
|
|
return ['success' => false, 'message' => 'Failed to store uploaded file'];
|
|
}
|
|
|
|
$publicPath = '/f_templates/tpl_frontend/img/tokens/' . $filename;
|
|
admin_update_token_settings($pdo, ['icon' => $publicPath]);
|
|
|
|
return ['success' => true, 'message' => 'Token icon uploaded successfully', 'path' => $publicPath];
|
|
}
|
|
|
|
function admin_fetch_all_roles(PDO $pdo): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->query("
|
|
SELECT role_name, display_name, description, status
|
|
FROM db_roles
|
|
ORDER BY display_name ASC
|
|
");
|
|
return $stmt->fetchAll() ?: [];
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function admin_prepare_user_filters(array $filters, array &$params): array
|
|
{
|
|
$wheres = ['u.usr_deleted = 0'];
|
|
|
|
if (!empty($filters['q'])) {
|
|
$params['search'] = '%' . $filters['q'] . '%';
|
|
$wheres[] = '(u.usr_user LIKE :search OR u.usr_email LIKE :search)';
|
|
}
|
|
|
|
if (!empty($filters['status'])) {
|
|
if ($filters['status'] === 'active') {
|
|
$wheres[] = 'u.usr_active = 1';
|
|
} elseif ($filters['status'] === 'inactive') {
|
|
$wheres[] = 'u.usr_active = 0';
|
|
}
|
|
}
|
|
|
|
if (!empty($filters['verified'])) {
|
|
if ($filters['verified'] === 'yes') {
|
|
$wheres[] = 'u.usr_verified = 1';
|
|
} elseif ($filters['verified'] === 'no') {
|
|
$wheres[] = 'u.usr_verified = 0';
|
|
}
|
|
}
|
|
|
|
if (!empty($filters['streaming'])) {
|
|
if ($filters['streaming'] === 'enabled') {
|
|
$wheres[] = 'u.streaming_enabled = 1';
|
|
} elseif ($filters['streaming'] === 'disabled') {
|
|
$wheres[] = '(u.streaming_enabled IS NULL OR u.streaming_enabled = 0)';
|
|
}
|
|
}
|
|
|
|
if (!empty($filters['role'])) {
|
|
$params['filter_role'] = $filters['role'];
|
|
$wheres[] = "EXISTS (
|
|
SELECT 1 FROM db_user_roles ur
|
|
WHERE ur.user_id = u.usr_id
|
|
AND ur.role_name = :filter_role
|
|
AND ur.status = 'active'
|
|
)";
|
|
}
|
|
|
|
return $wheres;
|
|
}
|
|
|
|
function admin_search_users(PDO $pdo, array $filters, int $limit = 25, int $offset = 0): array
|
|
{
|
|
$params = [];
|
|
$wheres = admin_prepare_user_filters($filters, $params);
|
|
|
|
$sql = "
|
|
SELECT
|
|
u.usr_id,
|
|
u.usr_user,
|
|
u.usr_email,
|
|
u.usr_joindate,
|
|
u.usr_lastlogin,
|
|
u.usr_active,
|
|
u.usr_verified,
|
|
u.streaming_enabled,
|
|
u.live_key,
|
|
u.stream_key_regenerated_at,
|
|
u.usr_tokencount,
|
|
u.usr_logins,
|
|
COALESCE(active_streams.active_count, 0) AS active_streams,
|
|
COALESCE(total_streams.total_count, 0) AS total_streams,
|
|
GROUP_CONCAT(DISTINCT roles.role_name ORDER BY roles.role_name SEPARATOR ',') AS roles
|
|
FROM db_accountuser u
|
|
LEFT JOIN db_user_roles roles
|
|
ON roles.user_id = u.usr_id AND roles.status = 'active'
|
|
LEFT JOIN (
|
|
SELECT user_id, COUNT(*) AS active_count
|
|
FROM db_live_streams
|
|
WHERE status = 'live'
|
|
GROUP BY user_id
|
|
) active_streams ON active_streams.user_id = u.usr_id
|
|
LEFT JOIN (
|
|
SELECT user_id, COUNT(*) AS total_count
|
|
FROM db_live_streams
|
|
GROUP BY user_id
|
|
) total_streams ON total_streams.user_id = u.usr_id
|
|
";
|
|
|
|
if (!empty($wheres)) {
|
|
$sql .= ' WHERE ' . implode(' AND ', $wheres);
|
|
}
|
|
|
|
$sql .= '
|
|
GROUP BY u.usr_id
|
|
ORDER BY u.usr_joindate DESC
|
|
LIMIT :limit OFFSET :offset
|
|
';
|
|
|
|
try {
|
|
$stmt = $pdo->prepare($sql);
|
|
foreach ($params as $key => $value) {
|
|
$stmt->bindValue(':' . $key, $value);
|
|
}
|
|
$stmt->bindValue(':limit', max(1, $limit), PDO::PARAM_INT);
|
|
$stmt->bindValue(':offset', max(0, $offset), PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll() ?: [];
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function admin_count_users(PDO $pdo, array $filters): int
|
|
{
|
|
$params = [];
|
|
$wheres = admin_prepare_user_filters($filters, $params);
|
|
|
|
$sql = '
|
|
SELECT COUNT(DISTINCT u.usr_id)
|
|
FROM db_accountuser u
|
|
';
|
|
|
|
if (!empty($wheres)) {
|
|
$sql .= ' WHERE ' . implode(' AND ', $wheres);
|
|
}
|
|
|
|
try {
|
|
$stmt = $pdo->prepare($sql);
|
|
foreach ($params as $key => $value) {
|
|
$stmt->bindValue(':' . $key, $value);
|
|
}
|
|
$stmt->execute();
|
|
return (int) ($stmt->fetchColumn() ?: 0);
|
|
} catch (Throwable) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
function admin_set_user_active(PDO $pdo, int $userId, bool $active): bool
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("
|
|
UPDATE db_accountuser
|
|
SET usr_active = :active
|
|
WHERE usr_id = :user_id
|
|
LIMIT 1
|
|
");
|
|
$stmt->bindValue(':active', $active ? 1 : 0, PDO::PARAM_INT);
|
|
$stmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->rowCount() > 0;
|
|
} catch (Throwable) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function admin_get_user_roles(PDO $pdo, int $userId): array
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("
|
|
SELECT role_name
|
|
FROM db_user_roles
|
|
WHERE user_id = :user_id AND status = 'active'
|
|
ORDER BY role_name ASC
|
|
");
|
|
$stmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return array_column($stmt->fetchAll(), 'role_name');
|
|
} catch (Throwable) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function admin_user_exists(PDO $pdo, int $userId): bool
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("SELECT 1 FROM db_accountuser WHERE usr_id = :user_id LIMIT 1");
|
|
$stmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return (bool) $stmt->fetchColumn();
|
|
} catch (Throwable) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// SETTINGS MANAGEMENT DATA PROVIDERS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Fetch all settings grouped by category
|
|
* @param PDO $pdo Database connection
|
|
* @return array Settings grouped by category
|
|
*/
|
|
function admin_fetch_all_settings(PDO $pdo): array
|
|
{
|
|
$defaults = [
|
|
'general' => [],
|
|
'modules' => [],
|
|
'branding' => [],
|
|
'payment' => [],
|
|
'email' => [],
|
|
'token' => [],
|
|
'security' => [],
|
|
'seo' => [],
|
|
'social' => [],
|
|
'other' => []
|
|
];
|
|
|
|
try {
|
|
$stmt = $pdo->query("SELECT cfg_name, cfg_data, cfg_info FROM db_settings ORDER BY cfg_name");
|
|
$settings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
foreach ($settings as $setting) {
|
|
$key = $setting['cfg_name'];
|
|
$category = 'other';
|
|
|
|
// Categorize settings based on prefix
|
|
if (strpos($key, 'backend_') === 0) {
|
|
$category = 'general';
|
|
} elseif (strpos($key, 'mail_') === 0) {
|
|
$category = 'email';
|
|
} 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 || $key === 'website_shortname' || $key === 'head_title') {
|
|
$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) {
|
|
$category = 'seo';
|
|
} elseif (strpos($key, '_module') !== false || $key === 'paid_memberships') {
|
|
$category = 'modules';
|
|
} elseif (strpos($key, 'fb_') === 0 || strpos($key, 'google_') === 0 || strpos($key, 'twitter_') === 0) {
|
|
$category = 'social';
|
|
}
|
|
|
|
$defaults[$category][] = [
|
|
'name' => $key,
|
|
'value' => $setting['cfg_data'],
|
|
'info' => $setting['cfg_info']
|
|
];
|
|
}
|
|
|
|
return $defaults;
|
|
} catch (Throwable $e) {
|
|
error_log("Error fetching settings: " . $e->getMessage());
|
|
return $defaults;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a specific setting value
|
|
* @param PDO $pdo Database connection
|
|
* @param string $key Setting name
|
|
* @return string|null Setting value or null
|
|
*/
|
|
function admin_get_setting(PDO $pdo, string $key): ?string
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("SELECT cfg_data FROM db_settings WHERE cfg_name = :key LIMIT 1");
|
|
$stmt->bindValue(':key', $key, PDO::PARAM_STR);
|
|
$stmt->execute();
|
|
$result = $stmt->fetchColumn();
|
|
return $result !== false ? (string)$result : null;
|
|
} catch (Throwable $e) {
|
|
error_log("Error getting setting: " . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update or insert a setting
|
|
* @param PDO $pdo Database connection
|
|
* @param string $key Setting name
|
|
* @param string $value Setting value
|
|
* @param string $info Setting description
|
|
* @return bool Success status
|
|
*/
|
|
function admin_save_setting(PDO $pdo, string $key, string $value, string $info = ''): bool
|
|
{
|
|
try {
|
|
// Check if exists
|
|
$stmt = $pdo->prepare("SELECT id FROM db_settings WHERE cfg_name = :key LIMIT 1");
|
|
$stmt->bindValue(':key', $key, PDO::PARAM_STR);
|
|
$stmt->execute();
|
|
$exists = $stmt->fetchColumn();
|
|
|
|
if ($exists) {
|
|
// Update existing
|
|
$stmt = $pdo->prepare("UPDATE db_settings SET cfg_data = :value, cfg_info = :info WHERE cfg_name = :key");
|
|
} else {
|
|
// Insert new
|
|
$stmt = $pdo->prepare("INSERT INTO db_settings (cfg_name, cfg_data, cfg_info) VALUES (:key, :value, :info)");
|
|
}
|
|
|
|
$stmt->bindValue(':key', $key, PDO::PARAM_STR);
|
|
$stmt->bindValue(':value', $value, PDO::PARAM_STR);
|
|
$stmt->bindValue(':info', $info, PDO::PARAM_STR);
|
|
|
|
return $stmt->execute();
|
|
} catch (Throwable $e) {
|
|
error_log("Error saving setting: " . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save multiple settings at once
|
|
* @param PDO $pdo Database connection
|
|
* @param array $settings Array of ['key' => 'value'] pairs
|
|
* @return bool Success status
|
|
*/
|
|
function admin_save_multiple_settings(PDO $pdo, array $settings): bool
|
|
{
|
|
try {
|
|
$pdo->beginTransaction();
|
|
|
|
foreach ($settings as $key => $value) {
|
|
admin_save_setting($pdo, $key, (string)$value);
|
|
}
|
|
|
|
$pdo->commit();
|
|
return true;
|
|
} catch (Throwable $e) {
|
|
$pdo->rollBack();
|
|
error_log("Error saving multiple settings: " . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete a setting
|
|
* @param PDO $pdo Database connection
|
|
* @param string $key Setting name
|
|
* @return bool Success status
|
|
*/
|
|
function admin_delete_setting(PDO $pdo, string $key): bool
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare("DELETE FROM db_settings WHERE cfg_name = :key");
|
|
$stmt->bindValue(':key', $key, PDO::PARAM_STR);
|
|
return $stmt->execute();
|
|
} catch (Throwable $e) {
|
|
error_log("Error deleting setting: " . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get module status
|
|
* @param PDO $pdo Database connection
|
|
* @return array Module enabled/disabled status
|
|
*/
|
|
function admin_fetch_module_status(PDO $pdo): array
|
|
{
|
|
$modules = [
|
|
'video_module' => 'Videos',
|
|
'live_module' => 'Live Streaming',
|
|
'short_module' => 'Shorts',
|
|
'image_module' => 'Images',
|
|
'audio_module' => 'Audio',
|
|
'document_module' => 'Documents',
|
|
'blog_module' => 'Blogs',
|
|
'paid_memberships' => 'Paid Memberships',
|
|
'token_system_enabled' => 'Token Economy'
|
|
];
|
|
|
|
$result = [];
|
|
|
|
foreach ($modules as $key => $label) {
|
|
$value = admin_get_setting($pdo, $key);
|
|
$result[] = [
|
|
'key' => $key,
|
|
'label' => $label,
|
|
'enabled' => $value === '1'
|
|
];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Toggle module status
|
|
* @param PDO $pdo Database connection
|
|
* @param string $module Module key
|
|
* @param bool $enabled New status
|
|
* @return bool Success status
|
|
*/
|
|
function admin_toggle_module(PDO $pdo, string $module, bool $enabled): bool
|
|
{
|
|
return admin_save_setting($pdo, $module, $enabled ? '1' : '0');
|
|
}
|
|
|
|
/**
|
|
* Get branding settings
|
|
* @param PDO $pdo Database connection
|
|
* @return array Branding configuration
|
|
*/
|
|
function admin_fetch_branding_settings(PDO $pdo): array
|
|
{
|
|
return [
|
|
'site_name' => admin_get_setting($pdo, 'website_shortname') ?? 'EasyStream',
|
|
'site_title' => admin_get_setting($pdo, 'head_title') ?? 'EasyStream',
|
|
'primary_color' => admin_get_setting($pdo, 'branding_primary_color') ?? '#1a73e8',
|
|
'secondary_color' => admin_get_setting($pdo, 'branding_secondary_color') ?? '#34a853',
|
|
'logo_url' => admin_get_setting($pdo, 'branding_logo_url') ?? '',
|
|
'favicon_url' => admin_get_setting($pdo, 'branding_favicon_url') ?? '',
|
|
'footer_text' => admin_get_setting($pdo, 'branding_footer_text') ?? ''
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get payment gateway settings
|
|
* @param PDO $pdo Database connection
|
|
* @return array Payment configuration
|
|
*/
|
|
function admin_fetch_payment_settings(PDO $pdo): array
|
|
{
|
|
return [
|
|
'payment_methods' => admin_get_setting($pdo, 'payment_methods') ?? 'Paypal',
|
|
'paypal_email' => admin_get_setting($pdo, 'paypal_email') ?? '',
|
|
'paypal_test' => admin_get_setting($pdo, 'paypal_test') === '1',
|
|
'paypal_client_id' => admin_get_setting($pdo, 'paypal_client_id') ?? '',
|
|
'paypal_secret' => admin_get_setting($pdo, 'paypal_secret') ?? '',
|
|
'stripe_enabled' => admin_get_setting($pdo, 'stripe_enabled') === '1',
|
|
'stripe_publishable_key' => admin_get_setting($pdo, 'stripe_publishable_key') ?? '',
|
|
'stripe_secret_key' => admin_get_setting($pdo, 'stripe_secret_key') ?? '',
|
|
'stripe_webhook_secret' => admin_get_setting($pdo, 'stripe_webhook_secret') ?? ''
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get email/SMTP settings
|
|
* @param PDO $pdo Database connection
|
|
* @return array Email configuration
|
|
*/
|
|
function admin_fetch_email_settings(PDO $pdo): array
|
|
{
|
|
return [
|
|
'mail_type' => admin_get_setting($pdo, 'mail_type') ?? 'smtp',
|
|
'backend_email' => admin_get_setting($pdo, 'backend_email') ?? '',
|
|
'backend_email_fromname' => admin_get_setting($pdo, 'backend_email_fromname') ?? 'Webmaster',
|
|
'mail_smtp_host' => admin_get_setting($pdo, 'mail_smtp_host') ?? 'smtp.gmail.com',
|
|
'mail_smtp_port' => admin_get_setting($pdo, 'mail_smtp_port') ?? '587',
|
|
'mail_smtp_username' => admin_get_setting($pdo, 'mail_smtp_username') ?? '',
|
|
'mail_smtp_password' => admin_get_setting($pdo, 'mail_smtp_password') ?? '',
|
|
'mail_smtp_auth' => admin_get_setting($pdo, 'mail_smtp_auth') === 'true',
|
|
'mail_smtp_prefix' => admin_get_setting($pdo, 'mail_smtp_prefix') ?? 'tls'
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get creator payout settings
|
|
* @param PDO $pdo Database connection
|
|
* @return array Payout configuration
|
|
*/
|
|
function admin_fetch_payout_settings(PDO $pdo): array
|
|
{
|
|
return [
|
|
'creator_payout_enabled' => admin_get_setting($pdo, 'creator_payout_enabled') === '1',
|
|
'creator_payout_percentage' => (int)(admin_get_setting($pdo, 'creator_payout_percentage') ?? '70'),
|
|
'minimum_payout_amount' => (float)(admin_get_setting($pdo, 'minimum_payout_amount') ?? '50.00'),
|
|
'payout_schedule' => admin_get_setting($pdo, 'payout_schedule') ?? 'monthly',
|
|
'payout_method' => admin_get_setting($pdo, 'payout_method') ?? 'paypal'
|
|
];
|
|
}
|