diff --git a/README.md b/README.md
index 0e2d7ef..9151f8a 100644
--- a/README.md
+++ b/README.md
@@ -6,19 +6,23 @@ ChurchTube is a premium, self-hosted video platform designed specifically for ch
## ✨ Features
-- **Premium UX**: Modern, responsive design with glassmorphism and smooth animations.
+- **Premium UX**: Modern, responsive design with forced **Dark Mode** and glassmorphism.
- **Dual Video Sources**: Upload videos directly or link them from external sources (NAS, Google Drive, Cloud).
-- **Smart Google Drive Support**: Automatically converts Google Drive links for seamless embedding.
+- **Smart Google Drive Support**: Automatically converts Google Drive links for seamless embedding and mobile-friendly controls.
+- **User Identity**:
+ - Custom **User Avatars** for a more personal community experience.
+ - Profile management for passwords and identity.
+- **Timestamped Bookmarks**: Save the exact second of a sermon and jump back to it later from your profile.
- **Interactive Community**:
- AJAX-based commenting (no video reloads).
- 5 reaction types (👍, ❤️, 🙏, 💡, 👏).
- Automated Profanity Filter with auto-reporting.
-- **Robust Moderation**:
+ - Users can delete their own comments.
+- **Administrative Accountability**:
+ - **System Logs**: Track logins, failed attempts, video plays, and comment history with IP address auditing.
- Role-Based Access Control (Admin, Moderator, Editor, User).
- - Dedicated Admin/Moderator dashboard for reports and users.
- **Custom Branding**: Real-time control over site title, colors, logo, and footer.
-- **Search & Discovery**: Tag-based categorization, search, and intelligent recommendations.
-- **Analytics**: Engagement-based view counting (only counts when video is played).
+- **Search & Discovery**: Keyword search, intelligent recommendations, and pagination.
## 🛠️ Technology Stack
diff --git a/admin/edit_video.php b/admin/edit_video.php
index 6d19337..903fa42 100644
--- a/admin/edit_video.php
+++ b/admin/edit_video.php
@@ -85,6 +85,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$pdo->prepare("INSERT IGNORE INTO video_tags (video_id, tag_id) VALUES (?, ?)")->execute([$id, $tag_id]);
}
}
+ cleanupTags();
$success = "Video updated successfully!";
// Refresh video data
$stmt = $pdo->prepare("SELECT * FROM videos WHERE id = ?");
diff --git a/admin/index.php b/admin/index.php
index af489e5..4c4dc5b 100644
--- a/admin/index.php
+++ b/admin/index.php
@@ -15,6 +15,7 @@ if (isset($_GET['delete'])) {
}
$pdo->prepare("DELETE FROM videos WHERE id = ?")->execute([$id]);
+ cleanupTags();
header('Location: index.php?msg=deleted');
exit;
}
@@ -51,6 +52,10 @@ echo str_replace(['assets/', 'index.php', 'login.php', 'logout.php', 'admin/'],
!
+
+
+ System Logs
+
diff --git a/admin/logs.php b/admin/logs.php
new file mode 100644
index 0000000..838ceed
--- /dev/null
+++ b/admin/logs.php
@@ -0,0 +1,80 @@
+prepare($query);
+$stmt->execute($params);
+$logs = $stmt->fetchAll();
+
+ob_start();
+require_once '../includes/header.php';
+$header = ob_get_clean();
+echo str_replace(['assets/', 'index.php', 'login.php', 'logout.php', 'admin/'], ['../assets/', '../index.php', '../login.php', '../logout.php', './'], $header);
+?>
+
+
+
+
System Logs
+
+
+ All Logs
+ >Auth
+ >Plays
+ >Comments
+ >Errors
+ >System
+
+
Back to Dashboard
+
+
+
+
+
+
+
+ Time
+ Type
+ User
+ Message
+ IP Address
+
+
+
+
+
+ = date('M d, H:i:s', strtotime($l['created_at'])) ?>
+
+
+ = strtoupper($l['type']) ?>
+
+
+ = htmlspecialchars($l['username'] ?? 'Guest') ?>
+ = htmlspecialchars($l['message']) ?>
+ = htmlspecialchars($l['ip_address']) ?>
+
+
+
+
+ No logs found.
+
+
+
+
+
+
+
+
diff --git a/api/delete_comment.php b/api/delete_comment.php
index 66340ac..838e953 100644
--- a/api/delete_comment.php
+++ b/api/delete_comment.php
@@ -4,20 +4,35 @@ require_once '../includes/auth.php';
header('Content-Type: application/json');
-if (!isModerator()) {
- echo json_encode(['success' => false, 'error' => 'Moderator privileges required']);
+if (!isLoggedIn()) {
+ echo json_encode(['success' => false, 'error' => 'Login required']);
exit;
}
$comment_id = (int)($_POST['comment_id'] ?? 0);
-
if (!$comment_id) {
echo json_encode(['success' => false, 'error' => 'Invalid data']);
exit;
}
try {
+ // Check ownership or moderator status
+ $stmt = $pdo->prepare("SELECT user_id FROM comments WHERE id = ?");
+ $stmt->execute([$comment_id]);
+ $comment = $stmt->fetch();
+
+ if (!$comment) {
+ echo json_encode(['success' => false, 'error' => 'Comment not found']);
+ exit;
+ }
+
+ if ($comment['user_id'] != $_SESSION['user_id'] && !isModerator()) {
+ echo json_encode(['success' => false, 'error' => 'Unauthorized']);
+ exit;
+ }
+
$pdo->prepare("DELETE FROM comments WHERE id = ?")->execute([$comment_id]);
+ logEvent('comment', "Comment deleted: ID $comment_id by user " . $_SESSION['username']);
echo json_encode(['success' => true]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => 'DB error']);
diff --git a/api/get_comments.php b/api/get_comments.php
index 76d19d3..252f797 100644
--- a/api/get_comments.php
+++ b/api/get_comments.php
@@ -12,7 +12,7 @@ if (!$video_id) {
try {
$stmt = $pdo->prepare("
- SELECT c.*, u.username,
+ SELECT c.*, u.username, u.avatar_url,
(SELECT COUNT(*) FROM reactions WHERE comment_id = c.id AND reaction_type = 'thumb') as thumbs,
(SELECT COUNT(*) FROM reactions WHERE comment_id = c.id AND reaction_type = 'heart') as hearts,
(SELECT COUNT(*) FROM reactions WHERE comment_id = c.id AND reaction_type = 'pray') as prays,
diff --git a/api/increment_views.php b/api/increment_views.php
index a63c6a8..d771ca4 100644
--- a/api/increment_views.php
+++ b/api/increment_views.php
@@ -13,6 +13,13 @@ if (!$video_id) {
try {
$stmt = $pdo->prepare("UPDATE videos SET views = views + 1 WHERE id = ?");
$stmt->execute([$video_id]);
+
+ // Log the play event
+ $v_stmt = $pdo->prepare("SELECT title FROM videos WHERE id = ?");
+ $v_stmt->execute([$video_id]);
+ $title = $v_stmt->fetchColumn();
+ logEvent('play', "Started watching: $title (ID: $video_id)");
+
echo json_encode(['success' => true]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => 'Database error']);
diff --git a/api/post_comment.php b/api/post_comment.php
index 686a3d9..81a9e9c 100644
--- a/api/post_comment.php
+++ b/api/post_comment.php
@@ -33,6 +33,11 @@ foreach ($bad_words as $word) {
try {
$stmt = $pdo->prepare("INSERT INTO comments (video_id, user_id, comment_text, is_reported) VALUES (?, ?, ?, ?)");
if ($stmt->execute([$video_id, $_SESSION['user_id'], $filtered_text, $is_flagged ? 1 : 0])) {
+ // Log the comment
+ $v_stmt = $pdo->prepare("SELECT title FROM videos WHERE id = ?");
+ $v_stmt->execute([$video_id]);
+ $title = $v_stmt->fetchColumn();
+ logEvent('comment', "Commented on $title: $filtered_text" . ($is_flagged ? " [FLAGGED]" : ""));
echo json_encode(['success' => true]);
} else {
echo json_encode(['success' => false, 'error' => 'Database error']);
diff --git a/api/toggle_bookmark.php b/api/toggle_bookmark.php
new file mode 100644
index 0000000..e6052ea
--- /dev/null
+++ b/api/toggle_bookmark.php
@@ -0,0 +1,22 @@
+prepare("SELECT id FROM bookmarks WHERE user_id = ? AND video_id = ?");
+ $stmt->execute([$user_id, $video_id]);
+ $bookmark = $stmt->fetch();
+
+ if ($bookmark) {
+ $pdo->prepare("DELETE FROM bookmarks WHERE id = ?")->execute([$bookmark['id']]);
+ echo json_encode(['success' => true, 'action' => 'removed']);
+ } else {
+ $pdo->prepare("INSERT INTO bookmarks (user_id, video_id, video_timestamp) VALUES (?, ?, ?)")->execute([$user_id, $video_id, $timestamp]);
+ echo json_encode(['success' => true, 'action' => 'added']);
+ }
+}
+?>
diff --git a/api/toggle_theme.php b/api/toggle_theme.php
new file mode 100644
index 0000000..c1c3daf
--- /dev/null
+++ b/api/toggle_theme.php
@@ -0,0 +1,11 @@
+prepare("UPDATE users SET theme_preference = ? WHERE id = ?");
+ $stmt->execute([$theme, $_SESSION['user_id']]);
+ echo json_encode(['success' => true]);
+}
+?>
diff --git a/diagnostics.php b/diagnostics.php
deleted file mode 100644
index 23cc557..0000000
--- a/diagnostics.php
+++ /dev/null
@@ -1,87 +0,0 @@
-[PASS] $success";
- } else {
- echo "
[FAIL] $failure ";
- return false;
- }
- return true;
-}
-
-?>
-
-
-
-
-
ChurchTube Diagnostics
-
-
-
-
-
-
ChurchTube Diagnostics
-
-
System Health Check
-
- ='),
- "PHP Version: " . PHP_VERSION,
- "PHP Version: " . PHP_VERSION . " (Requires 7.4+)");
-
- // Extensions
- check_status(extension_loaded('pdo_mysql'), "PDO MySQL extension is loaded.", "PDO MySQL extension is MISSING.");
- check_status(extension_loaded('curl'), "CURL extension is loaded.", "CURL extension is MISSING (required for external link validation).");
-
- // Config File
- $has_config = file_exists('includes/config.php');
- if (check_status($has_config, "Configuration file exists.", "Configuration file (includes/config.php) is MISSING.")) {
- require_once 'includes/config.php';
- // DB Connection
- try {
- $pdo = new PDO("mysql:host=".DB_HOST.";dbname=".DB_NAME, DB_USER, DB_PASS);
- check_status(true, "Database connection established successfully.", "");
- } catch (Exception $e) {
- check_status(false, "", "Database connection failed: " . $e->getMessage());
- }
- }
-
- // Write Permissions
- check_status(is_writable('uploads'), "Uploads directory is writable.", "Uploads directory is NOT writable. Run: chmod 777 uploads");
-
- // PHP Settings
- $upload_max = ini_get('upload_max_filesize');
- $post_max = ini_get('post_max_size');
- $is_low = (int)$upload_max < 100 || (int)$post_max < 100;
-
- if ($is_low) {
- echo "[WARN] Max Upload Size: $upload_max (Post Max: $post_max) - This is low for videos! ";
- } else {
- echo "[PASS] Max Upload Size: $upload_max (Post Max: $post_max) ";
- }
- ?>
-
-
-
-
Pro-Tips & Fixes:
-
- MySQL Issues? Run: sudo systemctl status mysql. If stopped, run sudo systemctl start mysql.
- Upload Limits? Edit /etc/php/7.4/apache2/php.ini. Look for upload_max_filesize and post_max_size. Set them to 500M or more, then run sudo systemctl restart apache2.
- Permission Issues? Run: sudo chmod -R 777 uploads/ to ensure the web server can save video files.
-
-
-
-
-
-
-
diff --git a/includes/db.php b/includes/db.php
index 8ef75ea..a3fb709 100644
--- a/includes/db.php
+++ b/includes/db.php
@@ -5,6 +5,16 @@ try {
$pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4", DB_USER, DB_PASS);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
+ require_once 'functions.php';
+
+ function cleanupTags() {
+ global $pdo;
+ try {
+ $pdo->exec("DELETE FROM tags WHERE id NOT IN (SELECT tag_id FROM video_tags)");
+ } catch (Exception $e) {
+ // Fail silently
+ }
+ }
} catch (PDOException $e) {
die("Database Connection Error: " . $e->getMessage());
}
diff --git a/includes/functions.php b/includes/functions.php
new file mode 100644
index 0000000..8648a89
--- /dev/null
+++ b/includes/functions.php
@@ -0,0 +1,22 @@
+prepare("INSERT INTO logs (user_id, type, message, ip_address) VALUES (?, ?, ?, ?)");
+ $stmt->execute([$user_id, $type, $message, $ip]);
+ } catch (Exception $e) {
+ // Fail silently
+ }
+}
+?>
diff --git a/includes/header.php b/includes/header.php
index 15c0e99..575ebe9 100644
--- a/includes/header.php
+++ b/includes/header.php
@@ -19,23 +19,74 @@ $logo_url = get_setting('logo_url', '');
-
+
+
-
+
diff --git a/includes/settings_helper.php b/includes/settings_helper.php
index 7db6f72..d2cb3a9 100644
--- a/includes/settings_helper.php
+++ b/includes/settings_helper.php
@@ -1,13 +1,17 @@
prepare("SELECT setting_value FROM settings WHERE setting_key = ?");
- $stmt->execute([$key]);
- $res = $stmt->fetch();
- return $res ? $res['setting_value'] : $default;
- } catch (Exception $e) {
- return $default;
+ static $settings_cache = null;
+
+ if ($settings_cache === null) {
+ try {
+ $stmt = $pdo->query("SELECT setting_key, setting_value FROM settings");
+ $settings_cache = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
+ } catch (Exception $e) {
+ $settings_cache = [];
+ }
}
+
+ return isset($settings_cache[$key]) ? $settings_cache[$key] : $default;
}
?>
diff --git a/index.php b/index.php
index 935c6ff..bf55d58 100644
--- a/index.php
+++ b/index.php
@@ -8,24 +8,43 @@ require_once 'includes/db.php';
require_once 'includes/settings_helper.php';
require_once 'includes/header.php';
-$search = $_GET['q'] ?? '';
-$tag_filter = $_GET['tag'] ?? '';
+$page = isset($_GET['p']) ? (int)$_GET['p'] : 1;
+$limit = 10;
+$offset = ($page - 1) * $limit;
$query = "SELECT DISTINCT v.*, u.username as uploader FROM videos v
- JOIN users u ON v.uploader_id = u.id
+ LEFT JOIN users u ON v.uploader_id = u.id
LEFT JOIN video_tags vt ON v.id = vt.video_id
LEFT JOIN tags t ON vt.tag_id = t.id";
+$search = $_GET['q'] ?? '';
+$tag_filter = $_GET['tag'] ?? '';
$params = [];
+$where_clauses = [];
if ($search) {
- $query .= " WHERE (v.title LIKE ? OR v.description LIKE ? OR t.name LIKE ?)";
- $params = ["%$search%", "%$search%", "%$search%"];
-} elseif ($tag_filter) {
- $query .= " WHERE t.name = ?";
- $params = [$tag_filter];
+ $where_clauses[] = "(v.title LIKE ? OR v.description LIKE ? OR t.name LIKE ?)";
+ $params = array_merge($params, ["%$search%", "%$search%", "%$search%"]);
+}
+if ($tag_filter) {
+ $where_clauses[] = "t.name = ?";
+ $params[] = $tag_filter;
}
-$query .= " ORDER BY v.release_date DESC, v.created_at DESC";
+if (!empty($where_clauses)) {
+ $query .= " WHERE " . implode(" AND ", $where_clauses);
+}
+
+// Count total for pagination
+$count_query = "SELECT COUNT(DISTINCT v.id) FROM videos v " .
+ "LEFT JOIN video_tags vt ON v.id = vt.video_id " .
+ "LEFT JOIN tags t ON vt.tag_id = t.id " .
+ (!empty($where_clauses) ? " WHERE " . implode(" AND ", $where_clauses) : "");
+$total_stmt = $pdo->prepare($count_query);
+$total_stmt->execute($params);
+$total_count = $total_stmt->fetchColumn();
+$total_pages = ceil($total_count / $limit);
+
+$query .= " ORDER BY v.release_date DESC, v.created_at DESC LIMIT $limit OFFSET $offset";
$stmt = $pdo->prepare($query);
$stmt->execute($params);
$videos = $stmt->fetchAll();
@@ -34,13 +53,6 @@ $videos = $stmt->fetchAll();
$popular_tags = $pdo->query("SELECT name FROM tags LIMIT 10")->fetchAll(PDO::FETCH_COLUMN);
?>
-
-
+ 1): ?>
+
+
+
diff --git a/install.php b/install.php
index 3c3445c..f6f3618 100644
--- a/install.php
+++ b/install.php
@@ -33,6 +33,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
password VARCHAR(255) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
role ENUM('admin', 'moderator', 'editor', 'user') DEFAULT 'user',
+ avatar_url TEXT,
+ theme_preference ENUM('dark', 'light') DEFAULT 'dark',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS videos (
@@ -78,6 +80,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
FOREIGN KEY (video_id) REFERENCES videos(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);
+ CREATE TABLE IF NOT EXISTS bookmarks (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ user_id INT NOT NULL,
+ video_id INT NOT NULL,
+ video_timestamp FLOAT DEFAULT 0,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
+ FOREIGN KEY (video_id) REFERENCES videos(id) ON DELETE CASCADE
+ );
+ CREATE TABLE IF NOT EXISTS logs (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ user_id INT NULL,
+ type VARCHAR(50),
+ message TEXT,
+ ip_address VARCHAR(45),
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
+ );
CREATE TABLE IF NOT EXISTS settings (
setting_key VARCHAR(50) PRIMARY KEY,
setting_value TEXT
diff --git a/login.php b/login.php
index 31514bf..b94586f 100644
--- a/login.php
+++ b/login.php
@@ -21,9 +21,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['user_role'] = $user['role'];
+ logEvent('auth', "User logged in: $username");
header('Location: index.php');
exit;
} else {
+ logEvent('auth', "FAILED login attempt for username: $username");
$error = "Invalid username or password.";
}
}
diff --git a/profile.php b/profile.php
new file mode 100644
index 0000000..b25a8e3
--- /dev/null
+++ b/profile.php
@@ -0,0 +1,218 @@
+prepare("SELECT password FROM users WHERE id = ?");
+ $stmt->execute([$user_id]);
+ $user = $stmt->fetch();
+
+ if (password_verify($old_pass, $user['password'])) {
+ if ($new_pass === $confirm_pass) {
+ if (strlen($new_pass) >= 6) {
+ $hashed = password_hash($new_pass, PASSWORD_DEFAULT);
+ $pdo->prepare("UPDATE users SET password = ? WHERE id = ?")->execute([$hashed, $user_id]);
+ $success = "Password changed successfully!";
+ } else {
+ $error = "New password must be at least 6 characters.";
+ }
+ } else {
+ $error = "New passwords do not match.";
+ }
+ } else {
+ $error = "Incorrect current password.";
+ }
+ }
+
+ if (isset($_POST['update_avatar'])) {
+ if (isset($_FILES['avatar']) && $_FILES['avatar']['error'] === 0) {
+ $ext = strtolower(pathinfo($_FILES['avatar']['name'], PATHINFO_EXTENSION));
+ $allowed = ['jpg', 'jpeg', 'png', 'webp'];
+ if (in_array($ext, $allowed)) {
+ $filename = 'avatar_' . $user_id . '_' . time() . '.' . $ext;
+ if (move_uploaded_file($_FILES['avatar']['tmp_name'], 'uploads/' . $filename)) {
+ // Delete old avatar if exists
+ $stmt = $pdo->prepare("SELECT avatar_url FROM users WHERE id = ?");
+ $stmt->execute([$user_id]);
+ $old = $stmt->fetchColumn();
+ if ($old && strpos($old, 'uploads/') === 0) @unlink($old);
+
+ $avatar_url = 'uploads/' . $filename;
+ $pdo->prepare("UPDATE users SET avatar_url = ? WHERE id = ?")->execute([$avatar_url, $user_id]);
+ $success = "Avatar updated!";
+ }
+ } else {
+ $error = "Invalid image format.";
+ }
+ }
+ }
+}
+
+// Get user data
+$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
+$stmt->execute([$user_id]);
+$user_data = $stmt->fetch();
+$avatar = $user_data['avatar_url'] ?: '';
+
+// Get bookmarks
+$stmt = $pdo->prepare("SELECT v.* FROM videos v JOIN bookmarks b ON v.id = b.video_id WHERE b.user_id = ? ORDER BY b.created_at DESC");
+$stmt->execute([$user_id]);
+$bookmarks = $stmt->fetchAll();
+
+require_once 'includes/header.php';
+?>
+
+
+
+
+
+
+
+
+
+
+
= strtoupper(substr($_SESSION['username'], 0, 1)) ?>
+
+
+
= htmlspecialchars($_SESSION['username']) ?>
+
Member since = date('M Y', strtotime($user_data['created_at'])) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ My Bookmarks
+ prepare("SELECT v.*, b.video_timestamp FROM videos v JOIN bookmarks b ON v.id = b.video_id WHERE b.user_id = ? ORDER BY b.created_at DESC");
+ $stmt->execute([$user_id]);
+ $bookmarks = $stmt->fetchAll();
+
+ function formatTime($seconds) {
+ if ($seconds <= 0) return "";
+ $mins = floor($seconds / 60);
+ $secs = floor($seconds % 60);
+ return sprintf("%d:%02d", $mins, $secs);
+ }
+ ?>
+
+
+
+
You haven't bookmarked any sermons yet.
+
+
+
+
+
+
+
+
+
+
+ Security
+
+
Change Password
+
Keep your account secure by using a strong password.
+
+
+
+ = $success ?>
+
+
+
+
+
+ = $error ?>
+
+
+
+
+
+
+
+
+
+
diff --git a/watch.php b/watch.php
index 360abb4..ed04132 100644
--- a/watch.php
+++ b/watch.php
@@ -63,36 +63,24 @@ require_once 'includes/header.php';
-
+
-
-
-
+
+
+
+
+
-
-
+
-
@@ -108,36 +96,40 @@ require_once 'includes/header.php';
= number_format($video['views']) ?> views • = date('M d, Y', strtotime($video['release_date'])) ?>
- Share
-
-
-
+
+ Share
+
+ prepare("SELECT 1 FROM bookmarks WHERE user_id = ? AND video_id = ?");
+ $stmt->execute([$_SESSION['user_id'], $video_id]);
+ $is_bookmarked = $stmt->fetch();
+ ?>
+
+ = $is_bookmarked ? 'Bookmarked' : 'Bookmark' ?>
+
+
-
- = strtoupper(substr($video['uploader'], 0, 1)) ?>
+ prepare("SELECT avatar_url FROM users WHERE id = ?");
+ $u_stmt->execute([$video['uploader_id']]);
+ $uploader_avatar = $u_stmt->fetchColumn();
+ ?>
+
+
+
+
+
= strtoupper(substr($video['uploader'], 0, 1)) ?>
+
= htmlspecialchars($video['uploader']) ?>
-
= htmlspecialchars($video['description']) ?>
+
= htmlspecialchars($video['description']) ?>
@@ -155,8 +147,12 @@ require_once 'includes/header.php';