581 lines
23 KiB
PHP
581 lines
23 KiB
PHP
<?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');
|
|
|
|
class VDatabase
|
|
{
|
|
/* db connection init */
|
|
public function dbConnection()
|
|
{
|
|
require 'f_core/config.database.php';
|
|
|
|
$db = &ADONewConnection($cfg_dbtype);
|
|
if (!$db->Connect($cfg_dbhost, $cfg_dbuser, $cfg_dbpass, $cfg_dbname)) {
|
|
// Log database connection error
|
|
$logger = VLogger::getInstance();
|
|
$logger->logDatabaseError('Database connection failed', '', [
|
|
'host' => $cfg_dbhost,
|
|
'database' => $cfg_dbname,
|
|
'user' => $cfg_dbuser
|
|
]);
|
|
|
|
die('<b>Error: </b> A database connection could not be established.');
|
|
} else {
|
|
//$db->debug = true;
|
|
return $db;
|
|
}
|
|
}
|
|
/* retrieve value of one database field - SECURE VERSION */
|
|
public function singleFieldValue($db_table, $get_value, $where_field, $where_value, $cache_time = false)
|
|
{
|
|
global $db;
|
|
|
|
try {
|
|
// Validate table and field names (whitelist approach)
|
|
if (!$this->isValidTableName($db_table) || !$this->isValidFieldName($get_value) || !$this->isValidFieldName($where_field)) {
|
|
throw new InvalidArgumentException('Invalid table or field name');
|
|
}
|
|
|
|
$sql = "SELECT `{$get_value}` FROM `{$db_table}` WHERE `{$where_field}` = ? LIMIT 1";
|
|
$q = $cache_time > 0 ? $db->CacheExecute($cache_time, $sql, array($where_value)) : $db->Execute($sql, array($where_value));
|
|
|
|
if (!$q) {
|
|
$logger = VLogger::getInstance();
|
|
$logger->logDatabaseError($db->ErrorMsg(), $sql, [$where_value]);
|
|
return null;
|
|
}
|
|
|
|
return $q && !$q->EOF ? $q->fields[$get_value] : null;
|
|
} catch (Exception $e) {
|
|
$logger = VLogger::getInstance();
|
|
$logger->logDatabaseError($e->getMessage(), $sql ?? '', [$where_value ?? '']);
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/* validate table name against whitelist */
|
|
private function isValidTableName($table)
|
|
{
|
|
// Add your actual table names here
|
|
$allowedTables = [
|
|
'db_settings', 'db_conversion', 'db_videofiles', 'db_livefiles',
|
|
'db_accountuser', 'db_trackactivity', 'db_imagefiles', 'db_audiofiles',
|
|
'db_documentfiles', 'db_blogfiles', 'db_comments', 'db_responses',
|
|
'db_playlists', 'db_subscriptions', 'db_categories', 'db_channels',
|
|
'db_users', 'db_sessions', 'db_ip_tracking', 'db_banlist',
|
|
'db_fingerprints', 'db_fingerprint_bans', 'db_email_log',
|
|
'db_notifications', 'db_user_preferences', 'db_password_resets',
|
|
'db_logs', 'db_shortfiles', 'db_memberships', 'db_tokens',
|
|
'db_affiliates', 'db_advertising', 'db_servers', 'db_streaming',
|
|
// Template Builder tables
|
|
'db_templatebuilder_templates', 'db_templatebuilder_components',
|
|
'db_templatebuilder_assignments', 'db_templatebuilder_versions',
|
|
'db_templatebuilder_user_prefs', 'db_notifications_count'
|
|
];
|
|
return in_array($table, $allowedTables);
|
|
}
|
|
|
|
/* validate field name */
|
|
private function isValidFieldName($field)
|
|
{
|
|
// Only allow alphanumeric characters and underscores
|
|
return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $field);
|
|
}
|
|
/* insert a single entry */
|
|
public function singleInsert($db_table, $set_field, $set_value)
|
|
{
|
|
global $db;
|
|
$q = $db->execute(sprintf("INSERT INTO `%s` (`%s`) VALUES ('%s');", $db_table, $set_field, $set_value));
|
|
}
|
|
/* update global settings */
|
|
public function settingsUpdate()
|
|
{
|
|
global $db, $language, $cfg;
|
|
|
|
$cfg_vars = VArrayConfig::cfgSection();
|
|
$count = 0;
|
|
|
|
switch ($_GET['s']) {
|
|
case "backend-menu-entry2-sub7": //admin, main modules
|
|
if ($_POST['backend_menu_entry2_sub7_live'] == 0 and $_POST['backend_menu_entry2_sub7_video'] == 0 and $_POST['backend_menu_entry2_sub7_image'] == 0 and $_POST['backend_menu_entry2_sub7_audio'] == 0 and $_POST['backend_menu_entry2_sub7_doc'] == 0 and $_POST['backend_menu_entry2_sub7_blog'] == 0) {
|
|
echo VGenerate::noticeTpl('', $language['backend.menu.entry2.sub7.mod.err'], '');
|
|
return false;
|
|
}
|
|
$db_tbl = 'db_settings';
|
|
break;
|
|
case "backend-menu-entry3-sub20":
|
|
case "backend-menu-entry3-sub21":
|
|
case "backend-menu-entry3-sub22":
|
|
case "backend-menu-entry3-sub23":
|
|
case "backend-menu-entry3-sub24":
|
|
$db_tbl = 'db_conversion';
|
|
break;
|
|
default:
|
|
$db_tbl = 'db_settings';
|
|
break;
|
|
}
|
|
|
|
if (is_array($cfg_vars) and count($cfg_vars) > 0) {
|
|
foreach ($cfg_vars as $key => $post_field) {
|
|
// Fixed: Changed cfg_data to cfg_value to match actual database column name
|
|
$query = $db->execute(sprintf("UPDATE `%s` SET `cfg_value` = '%s' WHERE `cfg_name` = '%s' LIMIT 1; ", $db_tbl, $post_field, $key));
|
|
$count = $db->Affected_Rows() > 0 ? $count + 1 : $count;
|
|
|
|
if ($_GET['s'] == 'backend-menu-entry1-sub9' and $cfg['activity_logging'] == 1) {
|
|
$db->execute(sprintf("UPDATE `db_trackactivity` SET `%s`='%s';", $key, $post_field));
|
|
}
|
|
}
|
|
}
|
|
$opened_entry = VGenerate::keepEntryOpen();
|
|
return $count;
|
|
}
|
|
/* update table field values - SECURE VERSION */
|
|
public function doUpdate($db_table, $db_field, $update_array, $id_value = null)
|
|
{
|
|
global $db;
|
|
|
|
try {
|
|
if (!is_array($update_array) || empty($update_array)) {
|
|
return false;
|
|
}
|
|
|
|
// Validate table and field names
|
|
if (!$this->isValidTableName($db_table) || !$this->isValidFieldName($db_field)) {
|
|
throw new InvalidArgumentException('Invalid table or field name');
|
|
}
|
|
|
|
// Use provided ID or get from POST (with validation)
|
|
$id = $id_value !== null ? (int)$id_value : VSecurity::postParam('hc_id', 'int', 0);
|
|
if ($id <= 0) {
|
|
return false;
|
|
}
|
|
|
|
$setParts = [];
|
|
$values = [];
|
|
|
|
foreach ($update_array as $key => $value) {
|
|
if (!$this->isValidFieldName($key)) {
|
|
continue; // Skip invalid field names
|
|
}
|
|
$setParts[] = "`{$key}` = ?";
|
|
$values[] = $value;
|
|
}
|
|
|
|
if (empty($setParts)) {
|
|
return false;
|
|
}
|
|
|
|
$values[] = $id; // Add ID for WHERE clause
|
|
|
|
$query = "UPDATE `{$db_table}` SET " . implode(', ', $setParts) . " WHERE `{$db_field}` = ?";
|
|
$result = $db->Execute($query, $values);
|
|
|
|
if (!$result) {
|
|
$logger = VLogger::getInstance();
|
|
$logger->logDatabaseError($db->ErrorMsg(), $query, $values);
|
|
return false;
|
|
}
|
|
|
|
return $db->Affected_Rows() > 0;
|
|
} catch (Exception $e) {
|
|
$logger = VLogger::getInstance();
|
|
$logger->logDatabaseError($e->getMessage(), $query ?? '', $values ?? []);
|
|
throw $e;
|
|
}
|
|
}
|
|
/* update table fields for usr_id */
|
|
public function entryUpdate($dbt, $arr)
|
|
{
|
|
global $db;
|
|
if (count($arr) > 0) {
|
|
foreach ($arr as $dbf => $val) {$q .= "`" . $dbf . "` = '" . $val . "', ";}
|
|
$query = "UPDATE `" . $dbt . "` SET " . substr($q, 0, -2) . " WHERE `usr_id`='" . intval($_SESSION['USER_ID']) . "' LIMIT 1;";
|
|
$result = $db->execute($query);
|
|
|
|
if ($db->Affected_Rows() > 0) {
|
|
return true;
|
|
}
|
|
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
}
|
|
/* insert into table from array - SECURE VERSION */
|
|
public function doInsert($db_table, $insert_array)
|
|
{
|
|
global $db;
|
|
|
|
try {
|
|
if (!is_array($insert_array) || empty($insert_array)) {
|
|
return false;
|
|
}
|
|
|
|
// Validate table name
|
|
if (!$this->isValidTableName($db_table)) {
|
|
throw new InvalidArgumentException('Invalid table name');
|
|
}
|
|
|
|
$fields = [];
|
|
$placeholders = [];
|
|
$values = [];
|
|
|
|
foreach ($insert_array as $key => $value) {
|
|
if (!$this->isValidFieldName($key)) {
|
|
continue; // Skip invalid field names
|
|
}
|
|
$fields[] = "`{$key}`";
|
|
$placeholders[] = '?';
|
|
$values[] = $value;
|
|
}
|
|
|
|
if (empty($fields)) {
|
|
return false;
|
|
}
|
|
|
|
$query = "INSERT INTO `{$db_table}` (" . implode(', ', $fields) . ") VALUES (" . implode(', ', $placeholders) . ")";
|
|
$result = $db->Execute($query, $values);
|
|
|
|
if (!$result) {
|
|
$logger = VLogger::getInstance();
|
|
$logger->logDatabaseError($db->ErrorMsg(), $query, $values);
|
|
return false;
|
|
}
|
|
|
|
return $db->Affected_Rows() > 0;
|
|
} catch (Exception $e) {
|
|
$logger = VLogger::getInstance();
|
|
$logger->logDatabaseError($e->getMessage(), $query ?? '', $values ?? []);
|
|
throw $e;
|
|
}
|
|
}
|
|
/* get specific config values from database and assign them */
|
|
public function getConfigurations($settings)
|
|
{
|
|
global $cfg, $smarty, $db;
|
|
|
|
$db_table = 'db_settings';
|
|
$settings_array = explode(",", $settings);
|
|
|
|
$q_get = '`cfg_name` IN ("' . implode('", "', $settings_array) . '")';
|
|
|
|
// Fixed: Changed cfg_data to cfg_value to match actual database column name
|
|
$q_result = $db->Execute(sprintf("SELECT `cfg_name`, `cfg_value` FROM `%s` WHERE %s;", $db_table, $q_get));
|
|
|
|
if ($q_result) {
|
|
while (!$q_result->EOF) {
|
|
$cfg_name = $q_result->fields['cfg_name'];
|
|
$cfg_value = $q_result->fields['cfg_value'];
|
|
$cfg[$cfg_name] = $cfg_value;
|
|
$smarty->assign($cfg_name, $cfg_value);
|
|
@$q_result->MoveNext();
|
|
}
|
|
}
|
|
return $cfg;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Get latest videos within a time window
|
|
* @param int $limit Maximum number of videos to return
|
|
* @param int $time_window Time window in minutes (default: 5)
|
|
* @return array Array of video data
|
|
*/
|
|
public function getLatestVideos($limit = 5, $time_window = 5)
|
|
{
|
|
global $db;
|
|
$rows = [];
|
|
|
|
try {
|
|
// Validate inputs
|
|
$limit = max(1, min(100, (int)$limit)); // Between 1 and 100
|
|
$time_window = max(1, min(1440, (int)$time_window)); // Between 1 minute and 24 hours
|
|
|
|
$sql = "SELECT A.`file_key`, A.`file_title` AS `title`, A.`file_description` AS `description`,
|
|
A.`file_views` AS `views`, A.`file_tags` AS `tags`, B.`usr_user` AS `username`,
|
|
A.`upload_date`
|
|
FROM `db_videofiles` A
|
|
JOIN `db_accountuser` B ON A.`usr_id` = B.`usr_id`
|
|
WHERE A.`approved` = '1' AND A.`deleted` = '0' AND A.`active` = '1'
|
|
AND A.`privacy` IN (0, '0', 'public')
|
|
AND A.`upload_date` >= DATE_SUB(NOW(), INTERVAL ? MINUTE)
|
|
ORDER BY A.`upload_date` DESC
|
|
LIMIT ?";
|
|
|
|
$res = $db->Execute($sql, array($time_window, $limit));
|
|
|
|
if ($res) {
|
|
while (!$res->EOF) {
|
|
$rows[] = [
|
|
'file_key' => $res->fields['file_key'],
|
|
'title' => $res->fields['title'],
|
|
'description' => $res->fields['description'],
|
|
'views' => (int) $res->fields['views'],
|
|
'tags' => (string) $res->fields['tags'],
|
|
'username' => $res->fields['username'],
|
|
'upload_date' => $res->fields['upload_date']
|
|
];
|
|
$res->MoveNext();
|
|
}
|
|
}
|
|
} catch (Exception $e) {
|
|
$logger = VLogger::getInstance();
|
|
$logger->logDatabaseError($e->getMessage(), $sql ?? '', [$time_window, $limit]);
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Search videos by query
|
|
* @param string $query Search query
|
|
* @param int $limit Maximum number of results to return
|
|
* @return array Array of video data
|
|
*/
|
|
public function searchVideos($query, $limit = 10)
|
|
{
|
|
global $db;
|
|
$rows = [];
|
|
|
|
try {
|
|
// Validate inputs
|
|
$limit = max(1, min(100, (int)$limit)); // Between 1 and 100
|
|
$query = trim($query);
|
|
|
|
if (empty($query)) {
|
|
return $rows;
|
|
}
|
|
|
|
// Try fulltext search first (if available)
|
|
try {
|
|
$sql = "SELECT A.`file_key`, A.`file_title` AS `title`, A.`file_description` AS `description`,
|
|
A.`file_views` AS `views`, A.`file_tags` AS `tags`, B.`usr_user` AS `username`,
|
|
MATCH(A.`file_title`, A.`file_description`, A.`file_tags`) AGAINST (? IN BOOLEAN MODE) AS score
|
|
FROM `db_videofiles` A
|
|
JOIN `db_accountuser` B ON A.`usr_id` = B.`usr_id`
|
|
WHERE A.`approved` = '1' AND A.`deleted` = '0' AND A.`active` = '1'
|
|
AND A.`privacy` IN (0, '0', 'public')
|
|
AND MATCH(A.`file_title`, A.`file_description`, A.`file_tags`) AGAINST (? IN BOOLEAN MODE)
|
|
ORDER BY score DESC, A.`upload_date` DESC
|
|
LIMIT ?";
|
|
$res = $db->Execute($sql, array($query . '*', $query . '*', $limit));
|
|
} catch (Exception $e) {
|
|
// Fallback to LIKE search
|
|
$like = '%' . $query . '%';
|
|
$sql = "SELECT A.`file_key`, A.`file_title` AS `title`, A.`file_description` AS `description`,
|
|
A.`file_views` AS `views`, A.`file_tags` AS `tags`, B.`usr_user` AS `username`
|
|
FROM `db_videofiles` A
|
|
JOIN `db_accountuser` B ON A.`usr_id` = B.`usr_id`
|
|
WHERE A.`approved` = '1' AND A.`deleted` = '0' AND A.`active` = '1'
|
|
AND A.`privacy` IN (0, '0', 'public')
|
|
AND (A.`file_title` LIKE ? OR A.`file_description` LIKE ? OR A.`file_tags` LIKE ?)
|
|
ORDER BY A.`upload_date` DESC
|
|
LIMIT ?";
|
|
$res = $db->Execute($sql, array($like, $like, $like, $limit));
|
|
}
|
|
|
|
if ($res) {
|
|
while (!$res->EOF) {
|
|
$rows[] = [
|
|
'file_key' => $res->fields['file_key'],
|
|
'title' => $res->fields['title'],
|
|
'description' => $res->fields['description'],
|
|
'views' => (int) $res->fields['views'],
|
|
'tags' => (string) $res->fields['tags'],
|
|
'username' => $res->fields['username']
|
|
];
|
|
$res->MoveNext();
|
|
}
|
|
}
|
|
} catch (Exception $e) {
|
|
$logger = VLogger::getInstance();
|
|
$logger->logDatabaseError($e->getMessage(), $sql ?? '', [$query, $limit]);
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Get latest live streams within a time window
|
|
* @param int $limit Maximum number of streams to return
|
|
* @param int $time_window Time window in minutes (default: 5)
|
|
* @return array Array of stream data
|
|
*/
|
|
public function getLatestStreams($limit = 5, $time_window = 5)
|
|
{
|
|
global $db;
|
|
$rows = [];
|
|
|
|
try {
|
|
// Validate inputs
|
|
$limit = max(1, min(100, (int)$limit)); // Between 1 and 100
|
|
$time_window = max(1, min(1440, (int)$time_window)); // Between 1 minute and 24 hours
|
|
|
|
$sql = "SELECT A.`live_key` AS `stream_key`, A.`live_title` AS `title`, A.`live_description` AS `description`,
|
|
A.`live_viewers` AS `views`, A.`live_tags` AS `tags`, B.`usr_user` AS `username`,
|
|
A.`date_created`
|
|
FROM `db_livefiles` A
|
|
JOIN `db_accountuser` B ON A.`usr_id` = B.`usr_id`
|
|
WHERE A.`approved` = '1' AND A.`deleted` = '0' AND A.`active` = '1'
|
|
AND A.`privacy` IN (0, '0', 'public')
|
|
AND A.`date_created` >= DATE_SUB(NOW(), INTERVAL ? MINUTE)
|
|
ORDER BY A.`date_created` DESC
|
|
LIMIT ?";
|
|
|
|
$res = $db->Execute($sql, array($time_window, $limit));
|
|
|
|
if ($res) {
|
|
while (!$res->EOF) {
|
|
$rows[] = [
|
|
'stream_key' => $res->fields['stream_key'],
|
|
'title' => $res->fields['title'],
|
|
'description' => $res->fields['description'],
|
|
'views' => (int) $res->fields['views'],
|
|
'tags' => (string) $res->fields['tags'],
|
|
'username' => $res->fields['username'],
|
|
'date_created' => $res->fields['date_created']
|
|
];
|
|
$res->MoveNext();
|
|
}
|
|
}
|
|
} catch (Exception $e) {
|
|
$logger = VLogger::getInstance();
|
|
$logger->logDatabaseError($e->getMessage(), $sql ?? '', [$time_window, $limit]);
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* General-purpose query execution method
|
|
* Returns array of results instead of ADORecordSet for easier frontend use
|
|
*
|
|
* @param string $sql SQL query to execute
|
|
* @param array $params Optional parameters for prepared statement
|
|
* @param int|false $cache_time Optional cache time in seconds (false = no cache)
|
|
* @return array Array of associative arrays with query results, empty array on failure
|
|
*/
|
|
public function execute($sql, $params = [], $cache_time = false)
|
|
{
|
|
global $db;
|
|
$rows = [];
|
|
|
|
try {
|
|
// Validate SQL input
|
|
if (empty($sql) || !is_string($sql)) {
|
|
throw new InvalidArgumentException('Invalid SQL query');
|
|
}
|
|
|
|
// Ensure params is an array
|
|
if (!is_array($params)) {
|
|
$params = [$params];
|
|
}
|
|
|
|
// Execute query with or without caching
|
|
if ($cache_time && is_numeric($cache_time) && $cache_time > 0) {
|
|
$result = $db->CacheExecute($cache_time, $sql, $params);
|
|
} else {
|
|
$result = $db->Execute($sql, $params);
|
|
}
|
|
|
|
// Check for query errors
|
|
if (!$result) {
|
|
$logger = VLogger::getInstance();
|
|
$logger->logDatabaseError($db->ErrorMsg(), $sql, $params);
|
|
return [];
|
|
}
|
|
|
|
// Convert ADORecordSet to plain array
|
|
if ($result && !$result->EOF) {
|
|
while (!$result->EOF) {
|
|
$rows[] = $result->fields;
|
|
$result->MoveNext();
|
|
}
|
|
}
|
|
|
|
return $rows;
|
|
|
|
} catch (Exception $e) {
|
|
$logger = VLogger::getInstance();
|
|
$logger->logDatabaseError($e->getMessage(), $sql ?? '', $params ?? []);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sanitize input for database queries
|
|
* @param mixed $input Input to sanitize
|
|
* @return string Sanitized input
|
|
*/
|
|
public static function sanitizeInput($input)
|
|
{
|
|
global $db;
|
|
|
|
if (is_null($input)) {
|
|
return '';
|
|
}
|
|
|
|
if (is_array($input)) {
|
|
return array_map([__CLASS__, 'sanitizeInput'], $input);
|
|
}
|
|
|
|
// Remove any potential SQL injection characters
|
|
$input = strip_tags($input);
|
|
$input = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
|
|
|
|
// Use ADOdb's qstr method if available
|
|
if (isset($db) && method_exists($db, 'qstr')) {
|
|
return substr($db->qstr($input), 1, -1); // Remove surrounding quotes
|
|
}
|
|
|
|
// Fallback: basic escaping
|
|
return addslashes($input);
|
|
}
|
|
|
|
/**
|
|
* Build INSERT/UPDATE SET clause from associative array
|
|
* @param array $data Associative array of field => value pairs
|
|
* @return string SET clause for SQL query
|
|
*/
|
|
public static function build_insert_update($data)
|
|
{
|
|
if (!is_array($data) || empty($data)) {
|
|
return '';
|
|
}
|
|
|
|
$parts = [];
|
|
foreach ($data as $field => $value) {
|
|
// Validate field name
|
|
if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $field)) {
|
|
continue; // Skip invalid field names
|
|
}
|
|
|
|
// Handle different value types
|
|
if (is_null($value)) {
|
|
$parts[] = "`{$field}` = NULL";
|
|
} elseif (is_int($value) || is_float($value)) {
|
|
$parts[] = "`{$field}` = " . $value;
|
|
} else {
|
|
$sanitized = self::sanitizeInput($value);
|
|
$parts[] = "`{$field}` = '{$sanitized}'";
|
|
}
|
|
}
|
|
|
|
return implode(', ', $parts);
|
|
}
|
|
}
|