المطلوب: إنشاء منصة بث احترافية شبيهة بنتفليكس لع...
생성일: 2025년 9월 13일
생성일: 2025년 9월 13일
المطلوب: إنشاء منصة بث احترافية شبيهة بنتفليكس لعرض الأفلام والمسلسلات، بملف واحد index.php يحتوي على كل HTML/CSS/JS/PHP، مع:
ابدا فورا بكتابة كود , اريد الصميم جذاب وراقي ,أرسل لك نسخة كاملة جاهزة دفعة واحدة
1️⃣ البنية الأساسية:
لا تستخدم Node.js. استخدم PHP (vanilla) + SQLite/MySQL + HTML/CSS/JS.
/db → قاعدة بيانات SQLite (db/app.db)
/movies → ملف JSON للمحتوى
/uploads → صور البوسترات والخلفيات
إنشاء تلقائي للجداول عند أول تشغيل.
Router داخلي للتعامل مع الصفحات (?route=...).
2️⃣ دعم الأفلام والمسلسلات:
جدول movies: بيانات عامة (type='movie' أو 'series').
جدول episodes: لكل حلقة (season, episode, title, duration, mp4_src).
لوحة تحكم لإضافة/تعديل/حذف المواسم والحلقات.
واجهة المستخدم تعرض المسلسلات بمواسم وحلقات منظمة.
Auto-Next للحلقة التالية.
إشعارات عند إضافة حلقات جديدة لمسلسلات يتابعها المستخدم.
4️⃣ المشغل الاحترافي:
HTML5 Video مع تصميم مخصص وأزرار تحكم (تشغيل/إيقاف، تقديم/ترجيع، صوت، سرعة، ملء الشاشة، وضع سينما).
يدعم جميع الروابط
دعم Range Requests للبث الفوري.
استئناف المشاهدة من آخر موضع.
قائمة حلقات جانبية للمسلسلات.
إدارة الجودة (Multi-quality): إمكانية إضافة روابط متعددة للفيديو بجودات مختلفة (480p, 720p, 1080p, 4K) حسب المتوفر.
5️⃣ الإشعارات الداخلية:
جدول notifications(id, user_id, message_ar, message_en, link, is_read, created_at).
إشعار عند إضافة فيلم/مسلسل جديد أو حلقة جديدة لمسلسل يتابعه المستخدم.
الأدمن يمكنه إرسال إشعارات يدوية لأي مستخدم أو للجميع من لوحة التحكم.
أيقونة جرس بالشريط العلوي مع قائمة منسدلة لآخر الإشعارات.
6️⃣ وضع ليلي/نهاري:
زر تبديل الوضع (شمس/قمر) في الشريط العلوي.
حفظ التفضيل في localStorage أو settings للمستخدم.
تطبيق الوضع عبر CSS Variables.
الوضع الليلي افتراضي.
7️⃣ نظام التقييم والتعليقات:
جدول ratings(user_id, movie_id, rating).
جدول comments(user_id, movie_id, comment, created_at).
تقييم من 1-5 نجوم مع عرض المتوسط.
التعليقات مرتبة من الأحدث للأقدم.
تعديل/حذف التعليق لصاحبه أو المدير.
8️⃣ البحث المتقدم والفلاتر:
صفحة بحث متقدم مع فلاتر: النوع، التصنيف، السنة، التقييم، المدة، الجودة، اللغة، الترتيب.
API: action=searchAdvanced مع المعاملات اللازمة.
عرض النتائج في شبكة مع شارات التقييم والجودة.
🔟 قائمة المشاهدة Watchlist:
جدول watchlist(user_id, movie_id).
زر إضافة/إزالة من القائمة.
قسم خاص في الحساب وعلى الرئيسية.
1️⃣1️⃣ الاستمرار في المشاهدة متعدد الأجهزة:
توسيع watch_history بحقول device, last_watched_at.
مزامنة الموضع عبر الأجهزة.
Auto resume عند فتح المحتوى.
1️⃣2️⃣ إحصائيات المستخدم:
عرض ساعات المشاهدة، التصنيفات المفضلة، تاريخ المشاهدة، مستوى التفاعل.
صفحة "إحصاءاتي" ببطاقات ورسوم بيانية بسيطة.
1️⃣3️⃣ نظام ترشيحات ذكي:
توصيات بناءً على التصنيفات المفضلة، التقييمات، المشاهدات.
أقسام: "مقترح لك"، "لأنك أحببت X"، "رائج الآن".
تحديث دوري للتوصيات.
1️⃣4️⃣ إدارة المستخدمين:
أدوار وصلاحيات (Admin, Editor, Moderator, User).
حظر/تجميد المستخدم مع سبب وفترة.
نظام تسجيل بالموافقة اليدوية: المستخدم يسجل، وحسابه يبقى pending حتى يوافق الأدمن أو يرفض مع سبب.
عرض قائمة طلبات التسجيل في لوحة التحكم.
1️⃣5️⃣ المراقبة والتحليل:
لوحة إحصاءات تعرض:
أكثر المحتوى مشاهدة.
أوقات الذروة.
نشاط المستخدمين.
الروابط المعطوبة.
سجل النشاطات (Activity Log) لكل العمليات الإدارية.
مراقبة أداء الخادم (إذا متاح).
1️⃣6️⃣ الأمان والأداء:
PDO + Prepared Statements.
CSRF Tokens.
password_hash لكلمات المرور.
ترميز HTML عند العرض.
Lazy Loading للصور.
Cache-Control و GZIP.
1️⃣7️⃣ التهيئة الأولية:
عند أول تشغيل: إنشاء المجلدات، القاعدة، الجداول، ملف movies.json بعينات باللغتين.
أول حساب يُسجَّل يصبح Admin تلقائيًا.
✅ النجاح يتحقق إذا:
كل الميزات تعمل بكامل وظائفها.
الأداء سريع، والواجهة متجاوبة، والمحتوى منظم.
دعم كامل للغة العربية .
هذا الملف النصي يحتوي على كل المتطلبات التفصيلية لبناء تطبيق
php<?php /****************************************************** * Netflix-like Streaming Platform - Single File index.php * Stack: PHP (vanilla) + SQLite + HTML/CSS/JS (no Node) * Language: Arabic-first UI (with EN fallbacks) * Author: You 🖤 — 2025-09-13 * * ✅ Features (condensed): * - Auto bootstrap: folders (/db, /movies, /uploads) + SQLite schema + seed JSON * - Internal Router (?route=...) + Actions (?action=...) * - Movies & Series (episodes/seasons) with multi-quality sources (JSON) * - Admin Panel: manage users, content, seasons/episodes, notifications * - HTML5 Custom Player (play/pause, seek, volume, speed, fs, cinema, next-ep) * - Range Requests (local & remote proxy) + Resume last position (sync devices) * - Notifications (auto + manual broadcast) + bell dropdown * - Night/Day mode via CSS variables (night default) + localStorage * - Ratings (1–5) + Comments (edit/delete by owner/admin) * - Advanced Search (action=searchAdvanced) with filters & sort * - Watchlist + Continue Watching * - Per-user stats & global analytics + activity log * - Security: PDO+prepared, CSRF, password_hash, HTML escaping, basic headers * - Perf: lazy images, gzip if possible, Cache-Control for assets ******************************************************/ // -------------------- 0) BASIC BOOTSTRAP & SECURITY -------------------- ini_set('display_errors', 0); error_reporting(E_ALL & ~E_NOTICE); if (function_exists('ob_gzhandler')) ob_start('ob_gzhandler'); else ob_start(); header_remove('X-Powered-By'); session_name('nfmini'); session_start(); date_default_timezone_set('UTC'); // CSP (relaxed for inlined styles/scripts — still blocks remote risky stuff) header("Content-Security-Policy: default-src 'self' blob: data:; img-src 'self' blob: data: https:; media-src 'self' blob: data: https:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; frame-ancestors 'none'"); // CSRF helpers if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = bin2hex(random_bytes(16)); function csrf_input() { return '<input type="hidden" name="csrf" value="'.$_SESSION['csrf'].'">'; } function csrf_check() { if ($_SERVER['REQUEST_METHOD']==='POST' && (!isset($_POST['csrf']) || !hash_equals($_SESSION['csrf'], $_POST['csrf']))) { http_response_code(400); exit('CSRF'); }} // Helpers function h($s){ return htmlspecialchars($s??'', ENT_QUOTES,'UTF-8'); } function j($x){ header('Content-Type: application/json; charset=utf-8'); echo json_encode($x, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); exit; } function redirect($url){ header("Location: $url"); exit; } function now(){ return (new DateTime())->format('Y-m-d H:i:s'); } function current_user(){ return $_SESSION['user'] ?? null; } function is_admin(){ return (current_user()['role'] ?? '') === 'admin'; } function is_mod(){ $r=current_user()['role']??''; return in_array($r,['admin','moderator','editor']); } // Paths $BASE = __DIR__; $DIR_DB = $BASE.'/db'; $DIR_MOVIES = $BASE.'/movies'; $DIR_UPLOADS = $BASE.'/uploads'; @is_dir($DIR_DB) || @mkdir($DIR_DB, 0775, true); @is_dir($DIR_MOVIES) || @mkdir($DIR_MOVIES, 0775, true); @is_dir($DIR_UPLOADS) || @mkdir($DIR_UPLOADS, 0775, true); // DB connect (SQLite) $DB_PATH = $DIR_DB . '/app.db'; $pdo = new PDO('sqlite:' . $DB_PATH); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->exec('PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA foreign_keys=ON;'); // -------------------- 1) SCHEMA (AUTO-CREATE) -------------------- $pdo->exec(" CREATE TABLE IF NOT EXISTS users( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, email TEXT UNIQUE, password_hash TEXT, role TEXT DEFAULT 'user', status TEXT DEFAULT 'pending', -- pending/active/banned/rejected ban_reason TEXT, ban_until TEXT, created_at TEXT ); CREATE TABLE IF NOT EXISTS movies( id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT CHECK(type IN ('movie','series')) NOT NULL, title TEXT, title_en TEXT, description TEXT, description_en TEXT, genres TEXT, year INTEGER, duration INTEGER, language TEXT, poster TEXT, backdrop TEXT, visibility INTEGER DEFAULT 1, qualities_json TEXT, -- for movies: {"480p":"url","720p":"..."} rating_avg REAL DEFAULT 0, rating_count INTEGER DEFAULT 0, created_at TEXT, updated_at TEXT ); CREATE TABLE IF NOT EXISTS episodes( id INTEGER PRIMARY KEY AUTOINCREMENT, movie_id INTEGER REFERENCES movies(id) ON DELETE CASCADE, season INTEGER, episode INTEGER, title TEXT, duration INTEGER, qualities_json TEXT, -- {"480p":"url","720p":"..."} created_at TEXT ); CREATE TABLE IF NOT EXISTS ratings( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, movie_id INTEGER REFERENCES movies(id) ON DELETE CASCADE, rating INTEGER CHECK(rating BETWEEN 1 AND 5), created_at TEXT, UNIQUE(user_id, movie_id) ); CREATE TABLE IF NOT EXISTS comments( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, movie_id INTEGER REFERENCES movies(id) ON DELETE CASCADE, comment TEXT, created_at TEXT ); CREATE TABLE IF NOT EXISTS watchlist( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, movie_id INTEGER REFERENCES movies(id) ON DELETE CASCADE, created_at TEXT, UNIQUE(user_id, movie_id) ); CREATE TABLE IF NOT EXISTS follows( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, movie_id INTEGER REFERENCES movies(id) ON DELETE CASCADE, created_at TEXT, UNIQUE(user_id, movie_id) ); CREATE TABLE IF NOT EXISTS watch_history( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, movie_id INTEGER REFERENCES movies(id) ON DELETE CASCADE, episode_id INTEGER REFERENCES episodes(id) ON DELETE CASCADE, position REAL DEFAULT 0, duration REAL DEFAULT 0, device TEXT, last_watched_at TEXT ); CREATE TABLE IF NOT EXISTS notifications( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, -- null = broadcast message_ar TEXT, message_en TEXT, link TEXT, is_read INTEGER DEFAULT 0, created_at TEXT ); CREATE TABLE IF NOT EXISTS activity_log( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, action TEXT, meta TEXT, created_at TEXT ); "); // Seed movies.json once $seedPath = $DIR_MOVIES.'/movies.json'; if (!file_exists($seedPath)) { $sample = [ "movies" => [ [ "type"=>"movie", "title"=>"رحلة إلى المجهول", "title_en"=>"Voyage to the Unknown", "description"=>"فيلم مغامرة ملحمي.", "description_en"=>"An epic adventure film.", "genres"=>"Adventure,Sci-Fi", "year"=>2024, "duration"=>7200, "language"=>"ar", "poster"=>"", "backdrop"=>"", "qualities"=>["720p"=>"https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4"] ] ], "series" => [ [ "type"=>"series", "title"=>"مدينة الظلال", "title_en"=>"City of Shadows", "description"=>"دراما جريمة مشوقة.", "description_en"=>"A gripping crime drama.", "genres"=>"Crime,Drama", "year"=>2023, "duration"=>0, "language"=>"ar", "poster"=>"", "backdrop"=>"", "seasons"=>[ [ "season"=>1, "episodes"=>[ ["episode"=>1,"title"=>"البدء","duration"=>1800,"qualities"=>["720p"=>"https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4"]], ["episode"=>2,"title"=>"الأثر","duration"=>1820,"qualities"=>["720p"=>"https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4"]] ] ] ] ] ] ]; file_put_contents($seedPath, json_encode($sample, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT)); // Also import into DB on first run if empty $c = (int)$pdo->query("SELECT COUNT(*) FROM movies")->fetchColumn(); if ($c===0) { $data = $sample; $insM = $pdo->prepare("INSERT INTO movies(type,title,title_en,description,description_en,genres,year,duration,language,poster,backdrop,qualities_json,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); $insE = $pdo->prepare("INSERT INTO episodes(movie_id,season,episode,title,duration,qualities_json,created_at) VALUES(?,?,?,?,?,?,?)"); foreach ($data['movies'] as $m) { $insM->execute([ $m['type'],$m['title'],$m['title_en'],$m['description'],$m['description_en'], $m['genres'],$m['year'],$m['duration'],$m['language'],$m['poster'],$m['backdrop'], json_encode($m['qualities'],JSON_UNESCAPED_UNICODE), now(), now() ]); } foreach ($data['series'] as $s) { $insM->execute([ $s['type'],$s['title'],$s['title_en'],$s['description'],$s['description_en'], $s['genres'],$s['year'],$s['duration'],$s['language'],$s['poster'],$s['backdrop'], null, now(), now() ]); $sid = (int)$pdo->lastInsertId(); foreach ($s['seasons'] as $season) { foreach ($season['episodes'] as $ep) { $insE->execute([$sid,$season['season'],$ep['episode'],$ep['title'],$ep['duration'],json_encode($ep['qualities'],JSON_UNESCAPED_UNICODE), now()]); } } } } } // First registered user becomes admin (set on registration handler) // -------------------- 2) ROUTER -------------------- $route = $_GET['route'] ?? 'home'; $action = $_GET['action'] ?? null; // -------------------- 3) ACTIONS (AJAX / POST) -------------------- if ($action) { if ($action!=='stream') header('Content-Type: application/json; charset=utf-8'); // LOGIN if ($action==='login' && $_SERVER['REQUEST_METHOD']==='POST') { csrf_check(); $email=trim($_POST['email']); $pass=$_POST['password']; $st=$pdo->prepare("SELECT * FROM users WHERE email=?"); $st->execute([$email]); $u=$st->fetch(PDO::FETCH_ASSOC); if ($u && password_verify($pass, $u['password_hash'])) { if ($u['status']==='pending') j(['ok'=>false,'msg'=>'حسابك قيد المراجعة.']); if ($u['status']==='banned') j(['ok'=>false,'msg'=>'حسابك محظور مؤقتاً.']); $_SESSION['user']=['id'=>$u['id'],'username'=>$u['username'],'email'=>$u['email'],'role'=>$u['role'],'status'=>$u['status']]; $pdo->prepare("INSERT INTO activity_log(user_id,action,meta,created_at) VALUES(?,?,?,?)")->execute([$u['id'],'login','{}',now()]); j(['ok'=>true]); } else j(['ok'=>false,'msg'=>'بيانات الدخول غير صحيحة.']); } // LOGOUT if ($action==='logout') { session_destroy(); j(['ok'=>true]); } // REGISTER if ($action==='register' && $_SERVER['REQUEST_METHOD']==='POST') { csrf_check(); $username=trim($_POST['username']); $email=trim($_POST['email']); $pass=$_POST['password']; if (!$username || !$email || !$pass) j(['ok'=>false,'msg'=>'حقول ناقصة.']); $hash=password_hash($pass,PASSWORD_DEFAULT); $role='user'; $status='pending'; // If first user: $cnt=(int)$pdo->query("SELECT COUNT(*) FROM users")->fetchColumn(); if ($cnt===0){ $role='admin'; $status='active'; } try{ $pdo->prepare("INSERT INTO users(username,email,password_hash,role,status,created_at) VALUES(?,?,?,?,?,?)")->execute([$username,$email,$hash,$role,$status,now()]); $uid=(int)$pdo->lastInsertId(); // Notify admins of pending registration if ($cnt>0){ $pdo->prepare("INSERT INTO notifications(user_id,message_ar,message_en,link,is_read,created_at) VALUES(NULL,?,?,?,?,?)") ->execute(["طلب تسجيل جديد: $username","New registration request: $username","?route=admin&tab=users",0,now()]); } j(['ok'=>true,'msg'=> $cnt===0 ? 'تم إنشاء حساب مدير.' : 'تم تسجيلك. بانتظار الموافقة.']); }catch(Exception $e){ j(['ok'=>false,'msg'=>'البريد أو الاسم مستخدم.']); } } // ADMIN: approve/reject/ban users if ($action==='admin_user_update' && $_SERVER['REQUEST_METHOD']==='POST' && is_admin()){ csrf_check(); $uid=(int)$_POST['user_id']; $op=$_POST['op']; if ($op==='approve'){ $pdo->prepare("UPDATE users SET status='active' WHERE id=?")->execute([$uid]); $pdo->prepare("INSERT INTO notifications(user_id,message_ar,message_en,link,is_read,created_at) VALUES(?,?,?,?,?,?)") ->execute([$uid,'تمت الموافقة على حسابك.','Your account has been approved.','?route=home',0,now()]); } elseif ($op==='reject'){ $reason=trim($_POST['reason']); $pdo->prepare("UPDATE users SET status='rejected' WHERE id=?")->execute([$uid]); $pdo->prepare("INSERT INTO notifications(user_id,message_ar,message_en,link,is_read,created_at) VALUES(?,?,?,?,?,?)") ->execute([$uid,'تم رفض حسابك: '.$reason,'Your account was rejected: '.$reason,'?route=home',0,now()]); } elseif ($op==='ban'){ $reason=trim($_POST['reason']); $until=trim($_POST['until']); $pdo->prepare("UPDATE users SET status='banned',ban_reason=?,ban_until=? WHERE id=?")->execute([$reason,$until,$uid]); } elseif ($op==='role'){ $role=$_POST['role']; $pdo->prepare("UPDATE users SET role=? WHERE id=?")->execute([$role,$uid]); } j(['ok'=>true]); } // CONTENT: add/update movie/series if ($action==='content_save' && $_SERVER['REQUEST_METHOD']==='POST' && is_mod()){ csrf_check(); $id=(int)($_POST['id']??0); $type=$_POST['type']; $title=$_POST['title']; $title_en=$_POST['title_en']; $desc=$_POST['description']; $desc_en=$_POST['description_en']; $genres=$_POST['genres']; $year=(int)$_POST['year']; $duration=(int)$_POST['duration']; $lang=$_POST['language']; $poster=$_POST['poster']; $backdrop=$_POST['backdrop']; $qualities = trim($_POST['qualities_json'] ?? ''); $vis = (int)($_POST['visibility'] ?? 1); if ($id){ $pdo->prepare("UPDATE movies SET type=?,title=?,title_en=?,description=?,description_en=?,genres=?,year=?,duration=?,language=?,poster=?,backdrop=?,qualities_json=?,visibility=?,updated_at=? WHERE id=?") ->execute([$type,$title,$title_en,$desc,$desc_en,$genres,$year,$duration,$lang,$poster,$backdrop,$qualities?:null,$vis,now(),$id]); } else { $pdo->prepare("INSERT INTO movies(type,title,title_en,description,description_en,genres,year,duration,language,poster,backdrop,qualities_json,visibility,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)") ->execute([$type,$title,$title_en,$desc,$desc_en,$genres,$year,$duration,$lang,$poster,$backdrop,$qualities?:null,$vis,now(),now()]); $id=(int)$pdo->lastInsertId(); // Broadcast notification $pdo->prepare("INSERT INTO notifications(user_id,message_ar,message_en,link,is_read,created_at) VALUES(NULL,?,?,?,?,?)") ->execute(["تمت إضافة عنوان جديد: $title","New title added: $title","?route=details&id=$id",0,now()]); } j(['ok'=>true,'id'=>$id]); } // EPISODE: add/update/delete if ($action==='episode_save' && $_SERVER['REQUEST_METHOD']==='POST' && is_mod()){ csrf_check(); $id=(int)($_POST['id']??0); $movie_id=(int)$_POST['movie_id']; $season=(int)$_POST['season']; $episode=(int)$_POST['episode']; $title=$_POST['title']; $duration=(int)$_POST['duration']; $qualities=trim($_POST['qualities_json']??''); if ($id){ $pdo->prepare("UPDATE episodes SET season=?,episode=?,title=?,duration=?,qualities_json=? WHERE id=?") ->execute([$season,$episode,$title,$duration,$qualities?:null,$id]); } else { $pdo->prepare("INSERT INTO episodes(movie_id,season,episode,title,duration,qualities_json,created_at) VALUES(?,?,?,?,?,?,?)") ->execute([$movie_id,$season,$episode,$title,$duration,$qualities?:null,now()]); // Notify followers of the series $titleSeries = $pdo->query("SELECT title FROM movies WHERE id=$movie_id")->fetchColumn(); $followers = $pdo->prepare("SELECT user_id FROM follows WHERE movie_id=?"); $followers->execute([$movie_id]); foreach ($followers->fetchAll(PDO::FETCH_COLUMN) as $uid){ $pdo->prepare("INSERT INTO notifications(user_id,message_ar,message_en,link,is_read,created_at) VALUES(?,?,?,?,?,?)") ->execute([$uid,"تمت إضافة حلقة جديدة إلى $titleSeries","New episode added to $titleSeries","?route=details&id=$movie_id",0,now()]); } } j(['ok'=>true]); } if ($action==='episode_delete' && $_SERVER['REQUEST_METHOD']==='POST' && is_mod()){ csrf_check(); $id=(int)$_POST['id']; $pdo->prepare("DELETE FROM episodes WHERE id=?")->execute([$id]); j(['ok'=>true]); } // WATCHLIST toggle if ($action==='watchlist_toggle' && current_user()){ csrf_check(); $uid=current_user()['id']; $mid=(int)$_POST['movie_id']; $exists=(int)$pdo->prepare("SELECT COUNT(*) FROM watchlist WHERE user_id=? AND movie_id=?")->execute([$uid,$mid])?:0; $st=$pdo->prepare("SELECT COUNT(*) FROM watchlist WHERE user_id=? AND movie_id=?"); $st->execute([$uid,$mid]); if ((int)$st->fetchColumn()>0){ $pdo->prepare("DELETE FROM watchlist WHERE user_id=? AND movie_id=?")->execute([$uid,$mid]); j(['ok'=>true,'in'=>false]); } else { $pdo->prepare("INSERT INTO watchlist(user_id,movie_id,created_at) VALUES(?,?,?)")->execute([$uid,$mid,now()]); j(['ok'=>true,'in'=>true]); } } // FOLLOW series if ($action==='follow_toggle' && current_user()){ csrf_check(); $uid=current_user()['id']; $mid=(int)$_POST['movie_id']; $st=$pdo->prepare("SELECT COUNT(*) FROM follows WHERE user_id=? AND movie_id=?"); $st->execute([$uid,$mid]); if ((int)$st->fetchColumn()>0){ $pdo->prepare("DELETE FROM follows WHERE user_id=? AND movie_id=?")->execute([$uid,$mid]); j(['ok'=>true,'in'=>false]); } else { $pdo->prepare("INSERT INTO follows(user_id,movie_id,created_at) VALUES(?,?,?)")->execute([$uid,$mid,now()]); j(['ok'=>true,'in'=>true]); } } // RATING if ($action==='rate' && current_user()){ csrf_check(); $uid=current_user()['id']; $mid=(int)$_POST['movie_id']; $rating=max(1,min(5,(int)$_POST['rating'])); $pdo->prepare("INSERT INTO ratings(user_id,movie_id,rating,created_at) VALUES(?,?,?,?) ON CONFLICT(user_id,movie_id) DO UPDATE SET rating=excluded.rating, created_at=excluded.created_at") ->execute([$uid,$mid,$rating,now()]); // recompute $avg=$pdo->query("SELECT ROUND(AVG(rating),2) FROM ratings WHERE movie_id=$mid")->fetchColumn(); $cnt=$pdo->query("SELECT COUNT(*) FROM ratings WHERE movie_id=$mid")->fetchColumn(); $pdo->prepare("UPDATE movies SET rating_avg=?, rating_count=? WHERE id=?")->execute([$avg,$cnt,$mid]); j(['ok'=>true,'avg'=>$avg,'count'=>$cnt]); } // COMMENT add/edit/delete if ($action==='comment_add' && current_user()){ csrf_check(); $uid=current_user()['id']; $mid=(int)$_POST['movie_id']; $txt=trim($_POST['comment']); if (!$txt) j(['ok'=>false]); $pdo->prepare("INSERT INTO comments(user_id,movie_id,comment,created_at) VALUES(?,?,?,?)")->execute([$uid,$mid,$txt,now()]); j(['ok'=>true]); } if ($action==='comment_del' && current_user()){ csrf_check(); $id=(int)$_POST['id']; $uid=current_user()['id']; $c=$pdo->prepare("SELECT user_id FROM comments WHERE id=?"); $c->execute([$id]); $owner=(int)$c->fetchColumn(); if ($owner===$uid || is_mod()){ $pdo->prepare("DELETE FROM comments WHERE id=?")->execute([$id]); j(['ok'=>true]); } else j(['ok'=>false]); } // NOTIFICATIONS read if ($action==='notif_mark' && current_user()){ csrf_check(); $nid=(int)$_POST['id']; $uid=current_user()['id']; $pdo->prepare("UPDATE notifications SET is_read=1 WHERE id=? AND (user_id=? OR user_id IS NULL)")->execute([$nid,$uid]); j(['ok'=>true]); } // Admin: send manual notification if ($action==='notif_send' && $_SERVER['REQUEST_METHOD']==='POST' && is_mod()){ csrf_check(); $uid = ($_POST['user_id']!=='all') ? (int)$_POST['user_id'] : null; $ar = trim($_POST['message_ar']); $en=trim($_POST['message_en']); $link=trim($_POST['link']); $pdo->prepare("INSERT INTO notifications(user_id,message_ar,message_en,link,is_read,created_at) VALUES(?,?,?,?,?,?)")->execute([$uid,$ar,$en,$link,0,now()]); j(['ok'=>true]); } // SAVE POSITION (resume) if ($action==='save_pos' && current_user()){ csrf_check(); $uid=current_user()['id']; $mid=(int)$_POST['movie_id']; $ep = (int)($_POST['episode_id']??0); $pos=(float)$_POST['position']; $dur=(float)$_POST['duration']; $dev=substr($_POST['device']??'web',0,32); $st=$pdo->prepare("SELECT id FROM watch_history WHERE user_id=? AND movie_id=? AND IFNULL(episode_id,0)=?"); $st->execute([$uid,$mid,$ep]); $id=$st->fetchColumn(); if ($id) $pdo->prepare("UPDATE watch_history SET position=?, duration=?, device=?, last_watched_at=? WHERE id=?")->execute([$pos,$dur,$dev,now(),$id]); else $pdo->prepare("INSERT INTO watch_history(user_id,movie_id,episode_id,position,duration,device,last_watched_at) VALUES(?,?,?,?,?,?,?)")->execute([$uid,$mid,$ep,$pos,$dur,$dev,now()]); j(['ok'=>true]); } // SEARCH ADVANCED (API) if ($action==='searchAdvanced') { $q = trim($_GET['q'] ?? ''); $type = $_GET['type'] ?? ''; $genre = $_GET['genre'] ?? ''; $year = (int)($_GET['year'] ?? 0); $minRating = (float)($_GET['minRating'] ?? 0); $lang = $_GET['lang'] ?? ''; $order = $_GET['order'] ?? 'latest'; // latest, rating, year $clauses = ["visibility=1"]; $params = []; if ($q!==''){ $clauses[]="(title LIKE ? OR title_en LIKE ? OR description LIKE ? OR description_en LIKE ?)"; for($i=0;$i<4;$i++) $params[]="%$q%"; } if ($type){ $clauses[]="type=?"; $params[]=$type; } if ($genre){ $clauses[]="genres LIKE ?"; $params[]="%$genre%"; } if ($year){ $clauses[]="year=?"; $params[]=$year; } if ($minRating>0){ $clauses[]="rating_avg>=?"; $params[]=$minRating; } if ($lang){ $clauses[]="language=?"; $params[]=$lang; } $orderBy = $order==='rating'?'rating_avg DESC':($order==='year'?'year DESC':'created_at DESC'); $sql="SELECT id,type,title,title_en,genres,year,language,poster,rating_avg FROM movies WHERE ".implode(' AND ',$clauses)." ORDER BY $orderBy LIMIT 60"; $st=$pdo->prepare($sql); $st->execute($params); $rows=$st->fetchAll(PDO::FETCH_ASSOC); j(['ok'=>true,'results'=>$rows]); } // STREAM (Range) local/remote if ($action==='stream') { // Parameters: src (url or local path under /uploads), download=0/1 $src = $_GET['src'] ?? ''; if (!$src) { http_response_code(404); exit; } // Security: allow only http(s) or local uploads path $isRemote = preg_match('~^https?://~i',$src); header('Content-Type: video/mp4'); // assume mp4 header('Cache-Control: public, max-age=86400'); if (!$isRemote) { $path = realpath($src[0]=='/'?$BASE.$src:$BASE.'/'.$src); if (!$path || strpos($path, realpath($DIR_UPLOADS))!==0 || !file_exists($path)) { http_response_code(404); exit; } $size = filesize($path); $start = 0; $length = $size; if (isset($_SERVER['HTTP_RANGE'])) { if (preg_match('/bytes=(\d+)-(\d*)/i', $_SERVER['HTTP_RANGE'], $m)) { $start = (int)$m[1]; $end = $m[2]!=='' ? (int)$m[2] : $size-1; $length = $end-$start+1; header('HTTP/1.1 206 Partial Content'); header("Content-Range: bytes $start-$end/$size"); } } header("Content-Length: $length"); header('Accept-Ranges: bytes'); $fp=fopen($path,'rb'); fseek($fp,$start); $chunk=8192; while(!feof($fp) && $length>0){ $read = ($length>$chunk)?$chunk:$length; echo fread($fp,$read); $length-=$read; @ob_flush(); flush(); } fclose($fp); exit; } else { // Remote proxy with Range forward (simple) $ch = curl_init($src); $headers = ['Connection: keep-alive']; if (isset($_SERVER['HTTP_RANGE'])) $headers[] = 'Range: '.$_SERVER['HTTP_RANGE']; curl_setopt_array($ch, [ CURLOPT_FOLLOWLOCATION=>true, CURLOPT_RETURNTRANSFER=>false, CURLOPT_HEADER=>true, CURLOPT_WRITEFUNCTION=>function($ch,$data){ echo $data; @ob_flush(); flush(); return strlen($data); }, CURLOPT_HTTPHEADER=>$headers, ]); // Stream headers from remote (filter) curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($ch,$hdr){ $h = trim($hdr); if (stripos($h,'Content-Type:')===0 || stripos($h,'Content-Length:')===0 || stripos($h,'Accept-Ranges:')===0 || stripos($h,'Content-Range:')===0 || stripos($h,'ETag:')===0 || stripos($h,'Last-Modified:')===0) { header($h, true); } if (preg_match('~^HTTP/\d\.\d\s+206~',$h)) header('HTTP/1.1 206 Partial Content'); return strlen($hdr); }); curl_exec($ch); curl_close($ch); exit; } } // THEME toggle (localStorage handles, kept for server setting if you want) if ($action==='noop'){ j(['ok'=>true]); } // Fallback j(['ok'=>false,'msg'=>'Unknown action']); } // -------------------- 4) QUERY HELPERS -------------------- function get_notifications($pdo, $uid, $limit=10){ $st=$pdo->prepare("SELECT * FROM notifications WHERE (user_id IS NULL OR user_id=?) ORDER BY id DESC LIMIT ?"); $st->execute([$uid,$limit]); return $st->fetchAll(PDO::FETCH_ASSOC); } function get_home_sections($pdo){ $latest = $pdo->query("SELECT * FROM movies WHERE visibility=1 ORDER BY created_at DESC LIMIT 18")->fetchAll(PDO::FETCH_ASSOC); $top = $pdo->query("SELECT * FROM movies WHERE visibility=1 ORDER BY rating_avg DESC, rating_count DESC LIMIT 12")->fetchAll(PDO::FETCH_ASSOC); $series = $pdo->query("SELECT * FROM movies WHERE visibility=1 AND type='series' ORDER BY updated_at DESC LIMIT 12")->fetchAll(PDO::FETCH_ASSOC); return compact('latest','top','series'); } function get_details($pdo,$id){ $st=$pdo->prepare("SELECT * FROM movies WHERE id=?"); $st->execute([$id]); $m=$st->fetch(PDO::FETCH_ASSOC); if (!$m) return null; $eps=[]; if ($m['type']==='series'){ $q=$pdo->prepare("SELECT * FROM episodes WHERE movie_id=? ORDER BY season ASC, episode ASC"); $q->execute([$id]); $rows=$q->fetchAll(PDO::FETCH_ASSOC); foreach($rows as $r){ $eps[$r['season']][]=$r; } } return [$m,$eps]; } function quality_list($json){ $q = $json ? json_decode($json,true) : []; if (!is_array($q)) $q=[]; // preserve order common: 4K,1080,720,480 $order = ['2160p','4K','1440p','1080p','720p','480p','360p','240p']; uksort($q, function($a,$b)use($order){ return (array_search($a,$order)??99) <=> (array_search($b,$order)??99); }); return $q; } // -------------------- 5) HTML (UI) -------------------- $user = current_user(); $theme = 'dark'; // default ?> <!doctype html> <html lang="ar" dir="rtl"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>سينيفلو | CineFlow</title> <link rel="icon" href="data:,"> <meta name="theme-color" content="#0b0d10"> <style> :root{ --bg:#0b0d10; --fg:#e6e7eb; --muted:#aab; --card:#12151a; --accent:#4da3ff; --dim:#1a1f27; --good:#2ecc71; --warn:#f1c40f; --bad:#e74c3c; } :root[data-theme="light"]{ --bg:#f6f7fb; --fg:#101217; --muted:#445; --card:#fff; --accent:#0066ff; --dim:#eef2f7; } *{box-sizing:border-box} body{margin:0;background:var(--bg);color:var(--fg);font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,"Noto Sans Arabic",Arial} a{color:inherit;text-decoration:none} .container{max-width:1200px;margin:0 auto;padding:16px} .topbar{position:sticky;top:0;background:linear-gradient(180deg,rgba(0,0,0,.65),transparent),var(--bg);backdrop-filter:saturate(1.2) blur(6px);z-index:50;border-bottom:1px solid rgba(255,255,255,.06)} .topbar .row{display:flex;align-items:center;gap:12px;padding:12px 16px} .brand{font-weight:800;letter-spacing:.5px} .brand span{color:var(--accent)} .search{flex:1;display:flex;gap:8px} .search input{flex:1;padding:10px;border-radius:10px;border:1px solid rgba(255,255,255,.08);background:var(--card);color:var(--fg)} .btn{padding:10px 14px;border-radius:10px;background:var(--accent);color:#fff;border:none;cursor:pointer;font-weight:600} .btn.secondary{background:var(--dim);color:var(--fg)} .iconbtn{padding:8px 10px;border-radius:10px;background:var(--card);border:1px solid rgba(255,255,255,.06);cursor:pointer} .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:14px} .card{background:var(--card);border-radius:14px;overflow:hidden;border:1px solid rgba(255,255,255,.06)} .card .thumb{aspect-ratio:2/3;background:#222;position:relative} .card .thumb img{width:100%;height:100%;object-fit:cover;display:block;loading:lazy} .badge{position:absolute;top:8px;left:8px;background:rgba(0,0,0,.6);padding:4px 8px;border-radius:8px;font-size:12px} .meta{padding:10px;font-size:14px;color:var(--muted)} h2.section{margin:18px 8px 8px} .row{display:flex;gap:12px;align-items:center} .spacer{flex:1} .badges{display:flex;gap:6px;flex-wrap:wrap} .tag{font-size:12px;background:var(--dim);padding:4px 8px;border-radius:8px} .tabs{display:flex;gap:8px;margin:10px 0} .tabs a{padding:8px 12px;border-radius:10px;background:var(--dim)} .tabs a.active{background:var(--accent);color:#fff} .hero{position:relative;border-radius:16px;overflow:hidden;background:linear-gradient(135deg,#162233,#0b0d10);border:1px solid rgba(255,255,255,.07)} .hero .inner{display:flex;gap:20px;padding:20px;align-items:center;flex-wrap:wrap} .hero img{width:220px;border-radius:12px} .hero h1{margin:0} .small{font-size:13px;color:var(--muted)} /* Player */ .player{display:flex;gap:14px} .player .video-wrap{flex:1;background:#000;border-radius:12px;overflow:hidden;border:1px solid rgba(255,255,255,.08)} .player .sidebar{width:320px;max-height:70vh;overflow:auto;background:var(--card);border-radius:12px;border:1px solid rgba(255,255,255,.08);padding:10px} .controls{display:flex;gap:8px;align-items:center;padding:8px;background:rgba(0,0,0,.6);position:absolute;bottom:0;left:0;right:0} .controls button,.controls select,.controls input[type=range]{background:rgba(255,255,255,.1);color:#fff;border:none;border-radius:8px;padding:8px} .video-container{position:relative} .cinema{max-width:calc(100vw - 60px);margin:0 auto} .notice{padding:10px;border-radius:10px;background:var(--dim);border:1px solid rgba(255,255,255,.08);color:var(--fg)} .table{width:100%;border-collapse:collapse} .table th,.table td{padding:8px;border-bottom:1px solid rgba(255,255,255,.08)} .kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px} .kpi{background:var(--card);border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:12px} textarea, select, input[type=number], input[type=text], input[type=email], input[type=password]{width:100%;padding:10px;border-radius:10px;border:1px solid rgba(255,255,255,.08);background:var(--card);color:var(--fg)} .form-row{display:grid;grid-template-columns:repeat(2,1fr);gap:10px} @media (max-width:900px){ .player{flex-direction:column}.player .sidebar{width:100%;max-height:unset} .hero img{width:140px} } </style> </head> <body> <div class="topbar"> <div class="row"> <div class="brand"><a href="?route=home">🎬 <span>سينيفلو</span></a></div> <form class="search" action="" method="get"> <input type="hidden" name="route" value="search"> <input type="text" name="q" placeholder="ابحث عن فيلم أو مسلسل..." value="<?=h($_GET['q']??'')?>"> <button class="btn">بحث</button> </form> <button id="themeBtn" class="iconbtn" title="الوضع"><span id="themeIcon">🌙</span></button> <?php if($user): ?> <?php $notifs = get_notifications($pdo,$user['id'],10); $unread = array_sum(array_map(fn($n)=>$n['is_read']?0:1,$notifs)); ?> <div class="dropdown" style="position:relative"> <button class="iconbtn" onclick="toggleBell()" title="الإشعارات">🔔<?= $unread? ' <span style=color:var(--warn)>'.$unread.'</span>':'' ?></button> <div id="bellMenu" style="display:none;position:absolute;right:0;background:var(--card);border:1px solid rgba(255,255,255,.08);border-radius:12px;min-width:320px;max-height:60vh;overflow:auto;padding:8px"> <?php if (!$notifs): ?><div class="small">لا إشعارات</div><?php endif; ?> <?php foreach($notifs as $n): ?> <div class="row" style="justify-content:space-between;border-bottom:1px solid rgba(255,255,255,.06);padding:6px 0"> <div> <div><?=h($n['message_ar'] ?: $n['message_en'])?></div> <div class="small"><?=h($n['created_at'])?></div> </div> <div class="row"> <?php if($n['link']): ?><a class="btn secondary" href="<?=h($n['link'])?>">فتح</a><?php endif; ?> <?php if(!$n['is_read']): ?><button class="iconbtn" onclick="markNotif(<?= (int)$n['id']?>)">تم</button><?php endif; ?> </div> </div> <?php endforeach; ?> </div> </div> <a class="iconbtn" href="?route=watchlist">📺 قائمتي</a> <a class="iconbtn" href="?route=stats">📊 إحصاءاتي</a> <?php if(is_mod()): ?><a class="iconbtn" href="?route=admin">🛠️ إدارة</a><?php endif; ?> <form method="post" onsubmit="return logout(event)"><button class="btn secondary">تسجيل الخروج</button></form> <?php else: ?> <a class="btn secondary" href="?route=login">دخول</a> <a class="btn" href="?route=register">إنشاء حساب</a> <?php endif; ?> </div> </div> <div class="container"> <?php // -------------------- 6) PAGES -------------------- if ($route==='home'){ $sec = get_home_sections($pdo); ?> <div class="hero"> <div class="inner"> <img src="https://image.tmdb.org/t/p/w342/qNBAXBIQlnOThrVvA6mA2B5ggV6.jpg" alt="" onerror="this.style.display='none'"> <div> <h1>مرحباً في <span class="brand"><span>سينيفلو</span></span></h1> <div class="small">منصة بث بسيطة وأنيقة — جرّب الوضع الليلي/النهاري 🌙/☀️، وابدأ المشاهدة فوراً مع الاستئناف التلقائي.</div> </div> </div> </div> <h2 class="section">أُضيف مؤخراً</h2> <div class="grid"> <?php foreach($sec['latest'] as $m): ?> <a class="card" href="?route=details&id=<?=$m['id']?>"> <div class="thumb"> <?php if($m['poster']): ?><img src="<?=h($m['poster'])?>" alt="poster" loading="lazy"><?php else: ?><img src="https://placehold.co/300x450?text=Poster" alt=""><?php endif; ?> <div class="badge"><?= $m['type']==='movie'?'فيلم':'مسلسل' ?></div> </div> <div class="meta"> <div style="font-weight:700"><?=h($m['title'])?></div> <div class="small"><?=h($m['year'])?> • ⭐ <?=h($m['rating_avg'])?></div> </div> </a> <?php endforeach; ?> </div> <h2 class="section">الأعلى تقييماً</h2> <div class="grid"> <?php foreach($sec['top'] as $m): ?> <a class="card" href="?route=details&id=<?=$m['id']?>"> <div class="thumb"> <img src="<?=h($m['poster']?:'https://placehold.co/300x450?text=Poster')?>" alt=""> <div class="badge">⭐ <?=h($m['rating_avg'])?></div> </div> <div class="meta"><div style="font-weight:700"><?=h($m['title'])?></div><div class="small"><?=h($m['genres'])?></div></div> </a> <?php endforeach; ?> </div> <h2 class="section">مسلسلات</h2> <div class="grid"> <?php foreach($sec['series'] as $m): ?> <a class="card" href="?route=details&id=<?=$m['id']?>"> <div class="thumb"><img src="<?=h($m['poster']?:'https://placehold.co/300x450?text=Poster')?>" alt=""></div> <div class="meta"><div style="font-weight:700"><?=h($m['title'])?></div><div class="small"><?=h($m['year'])?></div></div> </a> <?php endforeach; ?> </div> <?php } elseif ($route==='details'){ $id=(int)($_GET['id']??0); [$m,$eps] = get_details($pdo,$id) ?? [null,null]; if (!$m){ echo '<div class="notice">غير موجود</div>'; } else { $inWatchlist = false; if ($user){ $st=$pdo->prepare("SELECT COUNT(*) FROM watchlist WHERE user_id=? AND movie_id=?"); $st->execute([$user['id'],$id]); $inWatchlist=((int)$st->fetchColumn()>0); $fl=$pdo->prepare("SELECT COUNT(*) FROM follows WHERE user_id=? AND movie_id=?"); $fl->execute([$user['id'],$id]); $isFollowing=((int)$fl->fetchColumn()>0); } $ql = quality_list($m['qualities_json']); $avg = $m['rating_avg']; $cnt = $m['rating_count']; ?> <div class="hero"> <div class="inner"> <img src="<?=h($m['poster']?:'https://placehold.co/220x330?text=Poster')?>" alt=""> <div style="min-width:260px"> <h1><?=h($m['title'])?> <span class="small">(<?= $m['type']=='movie'?'فيلم':'مسلسل' ?>)</span></h1> <div class="badges"> <span class="tag"><?=h($m['year'])?></span> <span class="tag"><?=h($m['language']?:'العربية')?></span> <span class="tag"><?=h($m['genres'])?></span> <span class="tag">⭐ <?=h($avg)?> (<?=h($cnt)?>)</span> </div> <p class="small"><?=h($m['description'])?></p> <div class="row"> <?php if($user): ?> <button class="btn" onclick="toggleWatchlist(<?=$m['id']?>)"><?= $inWatchlist?'إزالة من قائمتي':'+ إلى قائمتي' ?></button> <?php if($m['type']==='series'): ?> <button class="btn secondary" onclick="toggleFollow(<?=$m['id']?>)"><?= $isFollowing?'إلغاء المتابعة':'متابعة المسلسل' ?></button> <?php endif; ?> <?php else: ?> <a class="btn" href="?route=login">سجّل للدخول</a> <?php endif; ?> </div> </div> </div> </div> <?php if($m['type']==='movie'): ?> <div class="player"> <div class="video-wrap"> <?php if($ql): $firstQ = array_key_first($ql); $firstURL = $ql[$firstQ]; ?> <div class="video-container" id="vc"> <video id="video" preload="metadata" playsinline></video> <div class="controls"> <button onclick="pp()" title="تشغيل/إيقاف">⏯</button> <button onclick="seekBy(-10)" title="ترجيع 10 ث">⏪ 10s</button> <button onclick="seekBy(10)" title="تقديم 10 ث">⏩ 10s</button> <input id="vol" type="range" min="0" max="1" step="0.01" title="الصوت"> <select id="speed" title="السرعة"><option>0.5</option><option>0.75</option><option selected>1</option><option>1.25</option><option>1.5</option><option>2</option></select> <select id="quality" title="الجودة"><?php foreach($ql as $q=>$u) echo "<option value='".h($u)."'>".h($q)."</option>"; ?></select> <button onclick="toggleCinema()" title="وضع سينما">🎞️</button> <button onclick="fs()" title="ملء الشاشة">⛶</button> <div class="spacer"></div> <div class="small" id="timeTxt">00:00 / 00:00</div> </div> </div> <?php else: ?> <div class="notice">لا توجد روابط فيديو بعد.</div> <?php endif; ?> </div> <div class="sidebar"> <div style="font-weight:700">التعليقات</div> <?php $cs=$pdo->prepare("SELECT c.*, u.username FROM comments c JOIN users u ON u.id=c.user_id WHERE movie_id=? ORDER BY id DESC"); $cs->execute([$m['id']]); $comments=$cs->fetchAll(PDO::FETCH_ASSOC); ?> <?php if($user): ?> <form onsubmit="return addComment(<?=$m['id']?>)" class="row" style="gap:6px;margin:8px 0"> <input type="hidden" name="csrf" value="<?= $_SESSION['csrf']?>"> <input id="cmt" type="text" placeholder="أضف تعليقاً..."> <button class="btn">إرسال</button> </form> <?php endif; ?> <div id="comments"> <?php foreach($comments as $c): ?> <div style="border-bottom:1px solid rgba(255,255,255,.06);padding:6px 0"> <div style="font-weight:600"><?=h($c['username'])?></div> <div><?=h($c['comment'])?></div> <div class="small"><?=$c['created_at']?> <?php if($user && ($user['id']==$c['user_id'] || is_mod())): ?><button class="iconbtn" onclick="delComment(<?=$c['id']?>)">حذف</button><?php endif; ?></div> </div> <?php endforeach; ?> </div> <div style="height:6px"></div> <div> <div style="font-weight:700;margin-bottom:6px">التقييم</div> <?php if($user): ?> <div class="row"> <?php for($i=1;$i<=5;$i++): ?> <button class="iconbtn" onclick="rate(<?=$m['id']?>,<?=$i?>)"><?=str_repeat('⭐',$i)?></button> <?php endfor; ?> </div> <?php else: ?> <div class="small">سجل للدخول لتقييم العمل.</div> <?php endif; ?> </div> </div> </div> <script> const MOVIE_ID = <?= (int)$m['id']?>, EP_ID = 0, CSRF='<?= $_SESSION['csrf']?>'; const firstUrl = <?= json_encode($firstURL??null) ?>; function mkStream(u){ if(!u) return null; return '?action=stream&src='+encodeURIComponent(u); } const v = document.getElementById('video'); const vol = document.getElementById('vol'), spd=document.getElementById('speed'), qSel=document.getElementById('quality'); const timeTxt=document.getElementById('timeTxt'); function fmt(t){ t=Math.floor(t); const m=String(Math.floor(t/60)).padStart(2,'0'); const s=String(t%60).padStart(2,'0'); return m+':'+s; } function updateTime(){ timeTxt.textContent = fmt(v.currentTime)+' / '+fmt(v.duration||0); } function loadQuality(u){ v.src = mkStream(u); v.play(); } function pp(){ if(v.paused) v.play(); else v.pause(); } function fs(){ if(v.requestFullscreen) v.requestFullscreen(); } function seekBy(s){ v.currentTime = Math.max(0, v.currentTime + s); } function toggleCinema(){ document.getElementById('vc').classList.toggle('cinema'); } vol.oninput=()=>v.volume=parseFloat(vol.value); spd.onchange=()=>v.playbackRate=parseFloat(spd.value); qSel.onchange=()=>loadQuality(qSel.value); v.addEventListener('timeupdate', ()=>{ updateTime(); if(v.duration){ throttleSave(); } }); v.addEventListener('loadedmetadata', updateTime); let saveTO=null; function throttleSave(){ clearTimeout(saveTO); saveTO=setTimeout(savePos,1000); } function savePos(){ fetch('?action=save_pos',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({csrf:CSRF,movie_id:MOVIE_ID,episode_id:0,position:v.currentTime||0,duration:v.duration||0,device:navigator.userAgent.slice(0,30)})}); } // Auto Resume (async ()=>{ const res = await fetch('?route=resume_api&id='+MOVIE_ID); const j = await res.json(); if (j && j.pos && j.pos>5){ v.currentTime = j.pos; } })(); if(firstUrl){ loadQuality(firstUrl); } </script> <?php else: // series ?> <div class="player"> <div class="video-wrap"> <?php $firstEp = null; foreach($eps as $sn=>$arr){ if(!$firstEp && $arr) $firstEp=$arr[0]; } $qle = $firstEp ? quality_list($firstEp['qualities_json']) : []; $fQ = $qle? array_key_first($qle):null; $fURL = $fQ? $qle[$fQ]:null; ?> <?php if($firstEp): ?> <div class="video-container" id="vc"> <video id="video" preload="metadata" playsinline></video> <div class="controls"> <button onclick="pp()">⏯</button> <button onclick="seekBy(-10)">⏪ 10s</button> <button onclick="seekBy(10)">⏩ 10s</button> <input id="vol" type="range" min="0" max="1" step="0.01"> <select id="speed"><option>0.75</option><option selected>1</option><option>1.25</option><option>1.5</option><option>2</option></select> <select id="quality"><?php foreach($qle as $q=>$u) echo "<option value='".h($u)."'>".h($q)."</option>"; ?></select> <button onclick="toggleCinema()">🎞️</button> <button onclick="fs()">⛶</button> <div class="spacer"></div> <div class="small" id="timeTxt">00:00 / 00:00</div> </div> </div> <?php else: ?><div class="notice">لا توجد حلقات بعد.</div><?php endif; ?> </div> <div class="sidebar"> <div style="font-weight:700;margin-bottom:6px">الحلقات</div> <?php foreach($eps as $sn=>$arr): ?> <div class="small" style="opacity:.8;margin-top:6px">الموسم <?= (int)$sn?></div> <?php foreach($arr as $ep): ?> <div class="row" style="justify-content:space-between;border-bottom:1px dashed rgba(255,255,255,.08);padding:6px 0"> <div>#<?=$ep['episode']?> - <?=h($ep['title'])?></div> <button class="iconbtn" onclick="playEp(<?=$ep['id']?>)">تشغيل</button> </div> <?php endforeach; ?> <?php endforeach; ?> <div style="height:12px"></div> <div style="font-weight:700">التعليقات</div> <?php $cs=$pdo->prepare("SELECT c.*, u.username FROM comments c JOIN users u ON u.id=c.user_id WHERE movie_id=? ORDER BY id DESC"); $cs->execute([$m['id']]); $comments=$cs->fetchAll(PDO::FETCH_ASSOC); ?> <?php if($user): ?> <form onsubmit="return addComment(<?=$m['id']?>)" class="row" style="gap:6px;margin:8px 0"> <input type="hidden" name="csrf" value="<?= $_SESSION['csrf']?>"> <input id="cmt" type="text" placeholder="أضف تعليقاً..."> <button class="btn">إرسال</button> </form> <?php endif; ?> <div id="comments"> <?php foreach($comments as $c): ?> <div style="border-bottom:1px solid rgba(255,255,255,.06);padding:6px 0"> <div style="font-weight:600"><?=h($c['username'])?></div> <div><?=h($c['comment'])?></div> <div class="small"><?=$c['created_at']?> <?php if($user && ($user['id']==$c['user_id'] || is_mod())): ?><button class="iconbtn" onclick="delComment(<?=$c['id']?>)">حذف</button><?php endif; ?></div> </div> <?php endforeach; ?> </div> </div> </div> <script> const MOVIE_ID = <?= (int)$m['id']?>, CSRF='<?= $_SESSION['csrf']?>'; let CURR_EP = <?= (int)($firstEp['id']??0) ?>; const v = document.getElementById('video'); const vol = document.getElementById('vol'), spd=document.getElementById('speed'), qSel=document.getElementById('quality'); const timeTxt=document.getElementById('timeTxt'); function mkStream(u){ return '?action=stream&src='+encodeURIComponent(u); } function fmt(t){ t=Math.floor(t); const m=String(Math.floor(t/60)).padStart(2,'0'); const s=String(t%60).padStart(2,'0'); return m+':'+s; } function updateTime(){ timeTxt.textContent = fmt(v.currentTime)+' / '+fmt(v.duration||0); } function pp(){ if(v.paused) v.play(); else v.pause(); } function fs(){ if(v.requestFullscreen) v.requestFullscreen(); } function seekBy(s){ v.currentTime = Math.max(0, v.currentTime + s); } function toggleCinema(){ document.getElementById('vc').classList.toggle('cinema'); } vol.oninput=()=>v.volume=parseFloat(vol.value); spd.onchange=()=>v.playbackRate=parseFloat(spd.value); qSel && (qSel.onchange=()=>{ v.src = mkStream(qSel.value); v.play(); }); v.addEventListener('timeupdate', ()=>{ updateTime(); if(v.duration){ throttleSave(); } }); v.addEventListener('ended', autoNext); v.addEventListener('loadedmetadata', updateTime); let saveTO=null; function throttleSave(){ clearTimeout(saveTO); saveTO=setTimeout(savePos,1000); } function savePos(){ fetch('?action=save_pos',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({csrf:CSRF,movie_id:MOVIE_ID,episode_id:CURR_EP,position:v.currentTime||0,duration:v.duration||0,device:navigator.userAgent.slice(0,30)})}); } async function playEp(id){ const r=await fetch('?route=episode_api&id='+id); const j=await r.json(); if(!j.ok) return; CURR_EP=id; const qs=j.qualities||{}; const first = Object.values(qs)[0]; if(qSel){ qSel.innerHTML=''; Object.keys(qs).forEach(k=>{ const o=document.createElement('option'); o.value=qs[k]; o.textContent=k; qSel.appendChild(o); }); } v.src = mkStream(first); v.play(); // resume if (j.pos>5) v.currentTime = j.pos; } async function autoNext(){ const r=await fetch('?route=next_ep_api&id='+CURR_EP); const j=await r.json(); if(j && j.next_id){ playEp(j.next_id); } } // boot (async ()=>{ if (CURR_EP) await playEp(CURR_EP); })(); </script> <?php endif; ?> <?php } } elseif ($route==='resume_api'){ header('Content-Type: application/json'); $id=(int)$_GET['id']; if(!$user) j(['ok'=>true,'pos'=>0]); $st=$pdo->prepare("SELECT position FROM watch_history WHERE user_id=? AND movie_id=? AND IFNULL(episode_id,0)=0 ORDER BY last_watched_at DESC LIMIT 1"); $st->execute([$user['id'],$id]); $pos=(float)$st->fetchColumn(); j(['ok'=>true,'pos'=>$pos]); } elseif ($route==='episode_api'){ header('Content-Type: application/json'); $id=(int)$_GET['id']; $e=$pdo->prepare("SELECT * FROM episodes WHERE id=?"); $e->execute([$id]); $ep=$e->fetch(PDO::FETCH_ASSOC); if(!$ep) j(['ok'=>false]); $pos=0; if($user){ $st=$pdo->prepare("SELECT position FROM watch_history WHERE user_id=? AND episode_id=? ORDER BY last_watched_at DESC LIMIT 1"); $st->execute([$user['id'],$id]); $pos=(float)$st->fetchColumn(); } j(['ok'=>true,'qualities'=>json_decode($ep['qualities_json'],true),'pos'=>$pos]); } elseif ($route==='next_ep_api'){ header('Content-Type: application/json'); $id=(int)$_GET['id']; $e=$pdo->prepare("SELECT movie_id,season,episode FROM episodes WHERE id=?"); $e->execute([$id]); $ep=$e->fetch(PDO::FETCH_ASSOC); if(!$ep) j(['ok'=>true,'next_id'=>null]); $n=$pdo->prepare("SELECT id FROM episodes WHERE movie_id=? AND (season>? OR (season=? AND episode>?)) ORDER BY season ASC, episode ASC LIMIT 1"); $n->execute([$ep['movie_id'],$ep['season'],$ep['season'],$ep['episode']]); $next=$n->fetchColumn(); j(['ok'=>true,'next_id'=>$next?:null]); } elseif ($route==='search'){ $q=trim($_GET['q']??''); ?> <h2 class="section">نتائج البحث: <?=h($q)?></h2> <div class="grid" id="results"></div> <script> (async ()=>{ const url = new URL(location.href); const q = url.searchParams.get('q')||''; const r = await fetch(`?action=searchAdvanced&q=${encodeURIComponent(q)}`); const j = await r.json(); const el = document.getElementById('results'); el.innerHTML=''; j.results.forEach(m=>{ const a=document.createElement('a'); a.className='card'; a.href='?route=details&id='+m.id; a.innerHTML=``; el.appendChild(a); }); })(); </script> <?php } elseif ($route==='watchlist' && $user){ $st=$pdo->prepare("SELECT m.* FROM watchlist w JOIN movies m ON m.id=w.movie_id WHERE w.user_id=? ORDER BY w.id DESC"); $st->execute([$user['id']]); $rows=$st->fetchAll(PDO::FETCH_ASSOC); ?><h2 class="section">قائمتي</h2><div class="grid"><?php foreach($rows as $m): ?><a class="card" href="?route=details&id=<?=$m['id']?>"><div class="thumb"><img src="<?=h($m['poster']?:'https://placehold.co/300x450?text=Poster')?>"><div class="badge"><?= $m['type']=='movie'?'فيلم':'مسلسل'?></div></div><div class="meta"><div style="font-weight:700"><?=h($m['title'])?></div><div class="small"><?=h($m['year'])?></div></div></a><?php endforeach; ?></div><?php } elseif ($route==='stats' && $user){ // user stats $uid=$user['id']; $watchedHrs = (float)$pdo->query("SELECT COALESCE(SUM(position),0)/3600 FROM watch_history WHERE user_id=$uid")->fetchColumn(); $favGenres = $pdo->query("SELECT TRIM(value) AS g, COUNT(*) c FROM (SELECT movies.genres FROM watch_history JOIN movies ON movies.id=watch_history.movie_id WHERE user_id=$uid) t, json_each('[\"'||replace(replace(genres,',','\",\"'),' ','')||'\"]') GROUP BY g ORDER BY c DESC LIMIT 5")->fetchAll(PDO::FETCH_ASSOC); $comments = (int)$pdo->query("SELECT COUNT(*) FROM comments WHERE user_id=$uid")->fetchColumn(); $ratings = (int)$pdo->query("SELECT COUNT(*) FROM ratings WHERE user_id=$uid")->fetchColumn(); ?> <h2 class="section">إحصاءاتي</h2> <div class="kpis"> <div class="kpi">⏱️ ساعات المشاهدة: <b><?=number_format($watchedHrs,2)?></b></div> <div class="kpi">💬 تعليقات: <b><?=$comments?></b></div> <div class="kpi">⭐ تقييمات: <b><?=$ratings?></b></div> <div class="kpi">🎯 التصنيفات المفضلة: <b><?php foreach($favGenres as $g){ echo h($g['g']).' '; } ?></b></div> </div> <div class="notice small" style="margin-top:10px">تلميح: يتم المزامنة بين الأجهزة تلقائياً عند تسجيل الدخول.</div> <?php } elseif ($route==='login'){ ?> <h2 class="section">تسجيل الدخول</h2> <form onsubmit="return login(event)" class="form"> <?= csrf_input()?> <div class="form-row"> <div><label>البريد</label><input type="email" name="email" required></div> <div><label>كلمة المرور</label><input type="password" name="password" required></div> </div> <button class="btn">دخول</button> </form> <div class="small">لا تملك حساباً؟ <a href="?route=register">سجل الآن</a></div> <?php } elseif ($route==='register'){ ?> <h2 class="section">تسجيل جديد</h2> <form onsubmit="return register(event)" class="form"> <?= csrf_input()?> <div class="form-row"> <div><label>الاسم</label><input type="text" name="username" required></div> <div><label>البريد</label><input type="email" name="email" required></div> </div> <div class="form-row"> <div><label>كلمة المرور</label><input type="password" name="password" required></div> </div> <button class="btn">إنشاء الحساب</button> </form> <div class="small">أول حساب يصبح مديراً تلقائياً.</div> <?php } elseif ($route==='admin' && is_mod()){ // Tabs: users, titles, episodes, notifs, analytics, logs $tab = $_GET['tab'] ?? 'titles'; ?> <h2 class="section">لوحة الإدارة</h2> <div class="tabs"> <a class="<?= $tab==='titles'?'active':''?>" href="?route=admin&tab=titles">العناوين</a> <a class="<?= $tab==='episodes'?'active':''?>" href="?route=admin&tab=episodes">الحلقات</a> <a class="<?= $tab==='users'?'active':''?>" href="?route=admin&tab=users">المستخدمون</a> <a class="<?= $tab==='notifs'?'active':''?>" href="?route=admin&tab=notifs">الإشعارات</a> <a class="<?= $tab==='analytics'?'active':''?>" href="?route=admin&tab=analytics">تحليلات</a> <a class="<?= $tab==='logs'?'active':''?>" href="?route=admin&tab=logs">السجل</a> </div> <?php if ($tab==='titles'){ $rows = $pdo->query("SELECT * FROM movies ORDER BY id DESC LIMIT 200")->fetchAll(PDO::FETCH_ASSOC); ?> <div class="row"><div class="spacer"></div><button class="btn" onclick="openTitleForm()">+ إضافة عنوان</button></div> <table class="table"> <tr><th>#</th><th>نوع</th><th>العنوان</th><th>سنة</th><th>رؤية</th><th>تحكم</th></tr> <?php foreach($rows as $r): ?> <tr> <td><?=$r['id']?></td><td><?=$r['type']?></td><td><?=h($r['title'])?></td><td><?=h($r['year'])?></td><td><?=$r['visibility']?'نعم':'لا'?></td> <td><a class="btn secondary" href="?route=details&id=<?=$r['id']?>">عرض</a> <button class="btn" onclick='editTitle(<?=json_encode($r,JSON_UNESCAPED_UNICODE)?>)'>تعديل</button></td> </tr> <?php endforeach; ?> </table> <dialog id="titleDlg"><form method="dialog" id="titleForm" onsubmit="return saveTitle(event)"> <h3>بيانات العنوان</h3> <?= csrf_input()?> <input type="hidden" name="id"> <div class="form-row"><div><label>نوع</label><select name="type"><option value="movie">فيلم</option><option value="series">مسلسل</option></select></div><div><label>الرؤية</label><select name="visibility"><option value="1">ظاهر</option><option value="0">مخفي</option></select></div></div> <div class="form-row"><div><label>العنوان</label><input name="title" required></div><div><label>Title EN</label><input name="title_en"></div></div> <div class="form-row"><div><label>الوصف</label><textarea name="description"></textarea></div><div><label>Description EN</label><textarea name="description_en"></textarea></div></div> <div class="form-row"><div><label>التصنيفات</label><input name="genres" placeholder="Action,Drama"></div><div><label>اللغة</label><input name="language" placeholder="ar/en"></div></div> <div class="form-row"><div><label>السنة</label><input type="number" name="year" value="2025"></div><div><label>المدة (ث)</label><input type="number" name="duration" value="0"></div></div> <div class="form-row"><div><label>Poster URL</label><input name="poster"></div><div><label>Backdrop URL</label><input name="backdrop"></div></div> <div><label>جودات الفيلم (JSON) — للأفلام فقط</label><textarea name="qualities_json" placeholder='{"720p":"https://...","1080p":"https://..."}'></textarea></div> <div class="row" style="justify-content:flex-end;gap:8px"><button class="btn secondary" onclick="this.closest('dialog').close()">إلغاء</button><button class="btn">حفظ</button></div> </form></dialog> <script> function openTitleForm(){ document.getElementById('titleForm').reset(); document.querySelector('#titleForm [name=id]').value=''; document.getElementById('titleDlg').showModal(); } function editTitle(r){ const f=document.getElementById('titleForm'); for(const k in r){ const el=f.querySelector(`[name=${k}]`); if(!el) continue; el.value = r[k]??''; } document.getElementById('titleDlg').showModal(); } async function saveTitle(e){ e.preventDefault(); const fd=new FormData(e.target); const res=await fetch('?action=content_save',{method:'POST',body:fd}); const j=await res.json(); if(j.ok) location.reload(); else alert('فشل الحفظ'); return false; } </script> <?php } if ($tab==='episodes'){ // pick series $srs=$pdo->query("SELECT id,title FROM movies WHERE type='series' ORDER BY updated_at DESC")->fetchAll(PDO::FETCH_ASSOC); $sid=(int)($_GET['sid'] ?? ($srs[0]['id']??0)); if ($sid){ $eps=$pdo->query("SELECT * FROM episodes WHERE movie_id=$sid ORDER BY season ASC, episode ASC")->fetchAll(PDO::FETCH_ASSOC); ?> <div class="row"> <form><input type="hidden" name="route" value="admin"><input type="hidden" name="tab" value="episodes"><select name="sid" onchange="this.form.submit()"><?php foreach($srs as $s) echo "<option ".($sid==$s['id']?'selected':'')." value='{$s['id']}'>".h($s['title'])."</option>"; ?></select></form> <div class="spacer"></div><button class="btn" onclick="openEpForm()">+ إضافة حلقة</button> </div> <table class="table"><tr><th>م</th><th>الموسم</th><th>الحلقة</th><th>العنوان</th><th>تحكم</th></tr> <?php foreach($eps as $e): ?> <tr><td><?=$e['id']?></td><td><?=$e['season']?></td><td><?=$e['episode']?></td><td><?=h($e['title'])?></td><td><button class="btn" onclick='editEp(<?=json_encode($e,JSON_UNESCAPED_UNICODE)?>)'>تعديل</button> <button class="btn secondary" onclick="delEp(<?=$e['id']?>)">حذف</button></td></tr> <?php endforeach; ?> </table> <dialog id="epDlg"><form id="epForm" onsubmit="return saveEp(event)"><?=csrf_input()?><input type="hidden" name="id"><input type="hidden" name="movie_id" value="<?=$sid?>"> <div class="form-row"><div><label>الموسم</label><input type="number" name="season" value="1"></div><div><label>الحلقة</label><input type="number" name="episode" value="1"></div></div> <div><label>العنوان</label><input name="title"></div> <div class="form-row"><div><label>المدة (ث)</label><input type="number" name="duration" value="0"></div></div> <div><label>جودات الحلقة (JSON)</label><textarea name="qualities_json" placeholder='{"720p":"https://..."}'></textarea></div> <div class="row" style="justify-content:flex-end;gap:8px"><button class="btn secondary" onclick="this.closest('dialog').close()">إلغاء</button><button class="btn">حفظ</button></div> </form></dialog> <script> function openEpForm(){ const f=document.getElementById('epForm'); f.reset(); f.querySelector('[name=id]').value=''; document.getElementById('epDlg').showModal(); } function editEp(e){ const f=document.getElementById('epForm'); for(const k in e){ const el=f.querySelector(`[name=${k}]`); if(el) el.value=e[k]??''; } document.getElementById('epDlg').showModal(); } async function saveEp(ev){ ev.preventDefault(); const fd=new FormData(ev.target); const r=await fetch('?action=episode_save',{method:'POST',body:fd}); const j=await r.json(); if(j.ok) location.reload(); else alert('فشل'); return false; } async function delEp(id){ if(!confirm('حذف؟')) return; const r=await fetch('?action=episode_delete',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({csrf:'<?= $_SESSION['csrf']?>',id})}); const j=await r.json(); if(j.ok) location.reload(); } </script> <?php } else { echo '<div class="notice">أضف/اختر مسلسلاً أولاً.</div>'; } } if ($tab==='users'){ $rows=$pdo->query("SELECT id,username,email,role,status,created_at FROM users ORDER BY id DESC LIMIT 200")->fetchAll(PDO::FETCH_ASSOC); ?> <table class="table"><tr><th>#</th><th>اسم</th><th>بريد</th><th>دور</th><th>حالة</th><th>تحكم</th></tr> <?php foreach($rows as $r): ?> <tr> <td><?=$r['id']?></td><td><?=h($r['username'])?></td><td><?=h($r['email'])?></td><td><?=h($r['role'])?></td><td><?=h($r['status'])?></td> <td class="row"> <?php if($r['status']==='pending'): ?><button class="btn" onclick="userOp(<?=$r['id']?>,'approve')">موافقة</button><button class="btn secondary" onclick="userReject(<?=$r['id']?>)">رفض</button><?php endif; ?> <select onchange="userRole(<?=$r['id']?>,this.value)"><option <?=$r['role']=='user'?'selected':''?>>user</option><option <?=$r['role']=='editor'?'selected':''?>>editor</option><option <?=$r['role']=='moderator'?'selected':''?>>moderator</option><option <?=$r['role']=='admin'?'selected':''?>>admin</option></select> <button class="btn secondary" onclick="userBan(<?=$r['id']?>)">حظر</button> </td> </tr> <?php endforeach; ?></table> <script> async function userOp(id,op,extra={}){ const fd=new URLSearchParams({csrf:'<?= $_SESSION['csrf']?>',user_id:id,op}); Object.entries(extra).forEach(([k,v])=>fd.append(k,v)); const r=await fetch('?action=admin_user_update',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:fd}); const j=await r.json(); if(j.ok) location.reload(); } function userReject(id){ const reason=prompt('سبب الرفض؟'); if(reason) userOp(id,'reject',{reason}); } function userBan(id){ const reason=prompt('سبب الحظر؟'); const until=prompt('حتى تاريخ (YYYY-MM-DD)?'); userOp(id,'ban',{reason,until}); } function userRole(id,role){ userOp(id,'role',{role}); } </script> <?php } if ($tab==='notifs'){ ?> <h3>إرسال إشعار</h3> <form onsubmit="return sendNotif(event)"><?=csrf_input()?> <div class="form-row"><div><label>لـ</label> <select name="user_id"> <option value="all">الجميع</option> <?php foreach($pdo->query("SELECT id,username FROM users WHERE status='active' ORDER BY username")->fetchAll(PDO::FETCH_ASSOC) as $u) echo "<option value='{$u['id']}'>".h($u['username'])."</option>"; ?> </select> </div> <div><label>رابط (اختياري)</label><input name="link" placeholder="?route=details&id=1"></div></div> <div class="form-row"><div><label>النص AR</label><input name="message_ar" required></div><div><label>Text EN</label><input name="message_en"></div></div> <button class="btn">إرسال</button> </form> <script> async function sendNotif(e){ e.preventDefault(); const fd=new FormData(e.target); const r=await fetch('?action=notif_send',{method:'POST',body:fd}); const j=await r.json(); if(j.ok) alert('تم'); return false; } </script> <?php } if ($tab==='analytics'){ $most = $pdo->query("SELECT movies.title, COUNT(*) c FROM watch_history JOIN movies ON movies.id=watch_history.movie_id GROUP BY movies.id ORDER BY c DESC LIMIT 10")->fetchAll(PDO::FETCH_ASSOC); $peaks = $pdo->query("SELECT strftime('%H',last_watched_at) h, COUNT(*) c FROM watch_history GROUP BY h ORDER BY c DESC LIMIT 5")->fetchAll(PDO::FETCH_ASSOC); $usersAct = $pdo->query("SELECT users.username, COUNT(*) c FROM watch_history JOIN users ON users.id=watch_history.user_id GROUP BY users.id ORDER BY c DESC LIMIT 10")->fetchAll(PDO::FETCH_ASSOC); $broken = []; // you can implement probing of URLs and mark broken (skipped for brevity) ?> <div class="kpis"> <div class="kpi">👀 أكثر المحتوى مشاهدة: <b><?=h($most[0]['title']??'—')?></b></div> <div class="kpi">⏰ أوقات الذروة: <b><?php foreach($peaks as $p){ echo h($p['h']).'h '; }?></b></div> <div class="kpi">👤 أنشط المستخدمين: <b><?php foreach($usersAct as $u){ echo h($u['username']).' '; }?></b></div> </div> <div class="notice small" style="margin-top:8px">مراقبة أداء الخادم تعتمد على بيئتك (يمكن قراءة /proc أو استخدام أدوات خارجية).</div> <?php } if ($tab==='logs'){ $logs=$pdo->query("SELECT * FROM activity_log ORDER BY id DESC LIMIT 100")->fetchAll(PDO::FETCH_ASSOC); ?><table class="table"><tr><th>زمن</th><th>مستخدم</th><th>حدث</th><th>بيانات</th></tr><?php foreach($logs as $l){ echo "<tr><td>".h($l['created_at'])."</td><td>".h($l['user_id'])."</td><td>".h($l['action'])."</td><td><code>".h($l['meta'])."</code></td></tr>"; } ?></table><?php } } else { // Not found or guard } ?> </div> <script> // ------------- GLOBAL JS (auth, comments, rating, notif, theme) ------------- const CSRF='<?= $_SESSION['csrf']?>'; async function login(e){ e.preventDefault(); const fd=new FormData(e.target); const r=await fetch('?action=login',{method:'POST',body:fd}); const j=await r.json(); if(j.ok) location.href='?route=home'; else alert(j.msg||'فشل'); return false; } async function register(e){ e.preventDefault(); const fd=new FormData(e.target); const r=await fetch('?action=register',{method:'POST',body:fd}); const j=await r.json(); if(j.ok){ alert(j.msg||'تم'); location.href='?route=login'; } else alert(j.msg||'فشل'); return false; } async function logout(e){ e && e.preventDefault(); await fetch('?action=logout'); location.href='?route=home'; return false; } async function toggleWatchlist(id){ const r=await fetch('?action=watchlist_toggle',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({csrf:CSRF,movie_id:id})}); const j=await r.json(); if(j.ok) location.reload(); } async function toggleFollow(id){ const r=await fetch('?action=follow_toggle',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({csrf:CSRF,movie_id:id})}); const j=await r.json(); if(j.ok) location.reload(); } async function addComment(mid){ const t=document.getElementById('cmt'); if(!t||!t.value.trim()) return false; const r=await fetch('?action=comment_add',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({csrf:CSRF,movie_id:mid,comment:t.value})}); const j=await r.json(); if(j.ok) location.reload(); return false; } async function delComment(id){ if(!confirm('حذف التعليق؟'))return; const r=await fetch('?action=comment_del',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({csrf:CSRF,id})}); const j=await r.json(); if(j.ok) location.reload(); } async function rate(mid,stars){ const r=await fetch('?action=rate',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({csrf:CSRF,movie_id:mid,rating:stars})}); const j=await r.json(); if(j.ok) alert('تم التقييم'); } function toggleBell(){ const m=document.getElementById('bellMenu'); m.style.display = (m.style.display==='block'?'none':'block'); } async function markNotif(id){ const r=await fetch('?action=notif_mark',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({csrf:CSRF,id})}); const j=await r.json(); if(j.ok) location.reload(); } // Theme const root = document.documentElement; function applyTheme(t){ root.setAttribute('data-theme', t); document.getElementById('themeIcon').textContent = t==='dark'?'🌙':'☀️'; localStorage.setItem('theme', t); } document.getElementById('themeBtn').onclick=()=>applyTheme(root.getAttribute('data-theme')==='dark'?'light':'dark'); applyTheme(localStorage.getItem('theme')||'dark'); </script> </body> </html>
hpcimapro.zya.me is currently unable to handle this request.
HTTP ERROR 500
خطأ 500 يعني أن PHP انهار قبل أن يرسل أي مخرجات. غالبًا السبب يكون صلاحيات/امتدادات ناقصة أو سطر معيّن يرمي استثناء. خذ هذه الخطوات السريعة — بدون انتظار — لتحديد السبب وإصلاحه:
ضع هذا “البلوك” في أعلى index.php
(قبل أي كود آخر)، ثم افتح ?route=diag
في المتصفح:
php<?php // ===== DEBUG START (حذف لاحقًا) ===== define('APP_DEBUG', true); if (APP_DEBUG) { ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); // اكتب الأخطاء لملف ini_set('log_errors', 1); ini_set('error_log', __DIR__ . '/php-error.log'); } // صفحة تشخيص: example.com/index.php?route=diag if (($_GET['route'] ?? '') === 'diag') { header('Content-Type: text/plain; charset=utf-8'); echo "PHP VERSION: " . PHP_VERSION . "\n"; echo "EXTENSIONS:\n - pdo_sqlite: " . (extension_loaded('pdo_sqlite')?'YES':'NO') . "\n"; echo " - sqlite3: " . (extension_loaded('sqlite3')?'YES':'NO') . "\n"; echo " - curl: " . (extension_loaded('curl')?'YES':'NO') . "\n"; echo " - mbstring: " . (extension_loaded('mbstring')?'YES':'NO') . "\n"; echo "\nPATHS & PERMS:\n"; foreach (['/db','/movies','/uploads'] as $d) { $abs = __DIR__.$d; @is_dir($abs) || @mkdir($abs, 0775, true); printf(" %s -> %s | writable=%s\n", $d, $abs, is_writable($abs)?'YES':'NO'); } $db = __DIR__.'/db/app.db'; $canCreate = @touch($db) ? 'YES' : 'NO'; echo " touch(db/app.db) => $canCreate\n"; if ($canCreate === 'YES') @unlink($db); exit; } // ===== DEBUG END =====
ماذا تتوقع؟
pdo_sqlite: NO
→ الخادم لا يدعم SQLite PDO.writable=NO
لأي مجلد → الصلاحيات تمنع إنشاء القاعدة/الملفات.curl: NO
→ البث عبر الروابط البعيدة (و/أو Range proxy) لن يعمل حتى تفعل curl
.بعد إصلاح المشكلة، احذف/عطّل بلوك DEBUG حتى لا تظهر الأخطاء للعموم.
أ) امتداد SQLite/PDO غير مفعّل
PDOException: could not find driver
.pdo_sqlite
و sqlite3
(وأيضًا curl
, mbstring
).ب) صلاحيات الكتابة للمجلدات
/db
و /movies
و /uploads
قابلة للكتابة بواسطة مستخدم خادم الويب.chmod 775 db movies uploads
chmod 777 db movies uploads
(مؤقتًا للاختبار).index.php
(الكود يستخدم __DIR__
).ج) امتداد cURL غير مفعّل
curl: YES
في صفحة ?route=diag
).د) استعلامات SQLite متقدمة
json_each
(موجودة عادةً في SQLite الحديثة). عند ندرة دعم JSON1 في بعض الاستضافات، قد تظهر أخطاء فقط عند فتح تبويب الإحصاءات. إن حدث ذلك:
هـ) أخطاء مخرجات/هيدر
index.php
بترميز خاطئ (BOM)، قد ترى Cannot modify header information
.افتح: https://hpcimapro.zya.me/index.php?route=diag
لو بقي 500 حتى على ?route=diag
:
php-error.log
الذي أنشأناه بجانب index.php
.error_log
في جذر الموقع).تأكد أن النسخة رفعتها باسم index.php
فعلًا (ليس index (1).php
) وأن DocumentRoot يشير لنفس المجلد.
إن كان pdo_sqlite: NO
ولا يمكنك تفعيله، أخبرني وسأعطيك نفس الملف مكيّفًا على MySQL (بتغيير اتصال PDO وإنشاء الجداول)، مع إعدادات:
json_each
) إلى بدائل.display_errors=0
، واحذف صفحة diag
).uploads/
قابلة للكتابة لرفع الصور مستقبلاً.movies.json
.لو أردت، أزودك الآن بكتلة اتصال بديلة لـ MySQL + إرشادات إنشاء قاعدة — لكن غالبًا تفعيل pdo_sqlite
و curl
وضبط الصلاحيات سيحلّ خطأ 500 فورًا.
PHP VERSION: 8.3.19
EXTENSIONS:
PATHS & PERMS:
/db -> /home/vol12_7/hstn.me/mseet_39929192/htdocs/db | writable=YES
/movies -> /home/vol12_7/hstn.me/mseet_39929192/htdocs/movies | writable=YES
/uploads -> /home/vol12_7/hstn.me/mseet_39929192/htdocs/uploads | writable=YES
touch(db/app.db) => YES
الحمدلله — التشخيص يبيّن أن الامتدادات والصلاحيات ممتازة ✅ (SQLite وcURL وكل المجلدات قابلة للكتابة). إذن خطأ 500 غالبًا سببه استثناء داخل الكود نفسه (seed/queries/headers). خلّيني أعطيك “هوت-فيكس” سريع جاهز تُلصقه، يعالج أشهر النقاط التي قد تُسقط الصفحة حتى مع بيئة سليمة:
استبدل كتلة الاتصال + الـ PRAGMA + الإنشاء الأولي بهذه النسخة (ضعها مكان الجزء الذي يبدأ بـ $pdo = new PDO('sqlite:' . $DB_PATH);
وحتى نهاية بلوك “Seed movies.json once”):
php// DB connect (SQLite) + robust error handling try { if (!file_exists($DIR_DB)) @mkdir($DIR_DB, 0775, true); if (!file_exists($DB_PATH)) { @touch($DB_PATH); @chmod($DB_PATH, 0664); } $pdo = new PDO('sqlite:' . $DB_PATH); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // بعض الاستضافات لا تسمح بـ WAL افتراضيًا → جرّب ثم تجاهل الخطأ try { $pdo->exec('PRAGMA journal_mode=WAL;'); } catch(Throwable $e) {} try { $pdo->exec('PRAGMA synchronous=NORMAL;'); } catch(Throwable $e) {} try { $pdo->exec('PRAGMA foreign_keys=ON;'); } catch(Throwable $e) {} } catch (Throwable $e) { error_log("DB_CONNECT_FAIL: ".$e->getMessage()); http_response_code(500); exit("DB connection failed. Check php-error.log"); } // -------------------- 1) SCHEMA (AUTO-CREATE) -------------------- try { $pdo->exec(" CREATE TABLE IF NOT EXISTS users( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, email TEXT UNIQUE, password_hash TEXT, role TEXT DEFAULT 'user', status TEXT DEFAULT 'pending', ban_reason TEXT, ban_until TEXT, created_at TEXT ); CREATE TABLE IF NOT EXISTS movies( id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT CHECK(type IN ('movie','series')) NOT NULL, title TEXT, title_en TEXT, description TEXT, description_en TEXT, genres TEXT, year INTEGER, duration INTEGER, language TEXT, poster TEXT, backdrop TEXT, visibility INTEGER DEFAULT 1, qualities_json TEXT, rating_avg REAL DEFAULT 0, rating_count INTEGER DEFAULT 0, created_at TEXT, updated_at TEXT ); CREATE TABLE IF NOT EXISTS episodes( id INTEGER PRIMARY KEY AUTOINCREMENT, movie_id INTEGER REFERENCES movies(id) ON DELETE CASCADE, season INTEGER, episode INTEGER, title TEXT, duration INTEGER, qualities_json TEXT, created_at TEXT ); CREATE TABLE IF NOT EXISTS ratings( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, movie_id INTEGER REFERENCES movies(id) ON DELETE CASCADE, rating INTEGER CHECK(rating BETWEEN 1 AND 5), created_at TEXT, UNIQUE(user_id, movie_id) ); CREATE TABLE IF NOT EXISTS comments( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, movie_id INTEGER REFERENCES movies(id) ON DELETE CASCADE, comment TEXT, created_at TEXT ); CREATE TABLE IF NOT EXISTS watchlist( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, movie_id INTEGER REFERENCES movies(id) ON DELETE CASCADE, created_at TEXT, UNIQUE(user_id, movie_id) ); CREATE TABLE IF NOT EXISTS follows( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, movie_id INTEGER REFERENCES movies(id) ON DELETE CASCADE, created_at TEXT, UNIQUE(user_id, movie_id) ); CREATE TABLE IF NOT EXISTS watch_history( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, movie_id INTEGER REFERENCES movies(id) ON DELETE CASCADE, episode_id INTEGER REFERENCES episodes(id) ON DELETE CASCADE, position REAL DEFAULT 0, duration REAL DEFAULT 0, device TEXT, last_watched_at TEXT ); CREATE TABLE IF NOT EXISTS notifications( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, message_ar TEXT, message_en TEXT, link TEXT, is_read INTEGER DEFAULT 0, created_at TEXT ); CREATE TABLE IF NOT EXISTS activity_log( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, action TEXT, meta TEXT, created_at TEXT ); "); } catch (Throwable $e) { error_log("SCHEMA_FAIL: ".$e->getMessage()); http_response_code(500); exit("Schema creation failed. See php-error.log"); } // Seed movies.json once (بـ try/catch لمنع السقوط) $seedPath = $DIR_MOVIES.'/movies.json'; try { if (!file_exists($DIR_MOVIES)) @mkdir($DIR_MOVIES, 0775, true); if (!file_exists($seedPath)) { $sample = [ "movies" => [[ "type"=>"movie","title"=>"رحلة إلى المجهول","title_en"=>"Voyage to the Unknown", "description"=>"فيلم مغامرة ملحمي.","description_en"=>"An epic adventure film.", "genres"=>"Adventure,Sci-Fi","year"=>2024,"duration"=>7200,"language"=>"ar", "poster"=>"","backdrop"=>"", "qualities"=>["720p"=>"https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4"] ]], "series" => [[ "type"=>"series","title"=>"مدينة الظلال","title_en"=>"City of Shadows", "description"=>"دراما جريمة مشوقة.","description_en"=>"A gripping crime drama.", "genres"=>"Crime,Drama","year"=>2023,"duration"=>0,"language"=>"ar", "poster"=>"","backdrop"=>"", "seasons"=>[["season"=>1,"episodes"=>[ ["episode"=>1,"title"=>"البدء","duration"=>1800,"qualities"=>["720p"=>"https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4"]], ["episode"=>2,"title"=>"الأثر","duration"=>1820,"qualities"=>["720p"=>"https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4"]] ]]] ]] ]; file_put_contents($seedPath, json_encode($sample, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT)); $c = (int)$pdo->query("SELECT COUNT(*) FROM movies")->fetchColumn(); if ($c===0) { $insM = $pdo->prepare("INSERT INTO movies(type,title,title_en,description,description_en,genres,year,duration,language,poster,backdrop,qualities_json,visibility,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); $insE = $pdo->prepare("INSERT INTO episodes(movie_id,season,episode,title,duration,qualities_json,created_at) VALUES(?,?,?,?,?,?,?)"); foreach ($sample['movies'] as $m) { $insM->execute([ $m['type'],$m['title'],$m['title_en'],$m['description'],$m['description_en'], $m['genres'],$m['year'],$m['duration'],$m['language'],$m['poster'],$m['backdrop'], json_encode($m['qualities'],JSON_UNESCAPED_UNICODE),1, date('Y-m-d H:i:s'), date('Y-m-d H:i:s') ]); } foreach ($sample['series'] as $s) { $insM->execute([ $s['type'],$s['title'],$s['title_en'],$s['description'],$s['description_en'], $s['genres'],$s['year'],$s['duration'],$s['language'],$s['poster'],$s['backdrop'], null,1, date('Y-m-d H:i:s'), date('Y-m-d H:i:s') ]); $sid = (int)$pdo->lastInsertId(); foreach ($s['seasons'] as $season) { foreach ($season['episodes'] as $ep) { $insE->execute([$sid,$season['season'],$ep['episode'],$ep['title'],$ep['duration'],json_encode($ep['qualities'],JSON_UNESCAPED_UNICODE), date('Y-m-d H:i:s')]); } } } } } } catch (Throwable $e) { error_log("SEED_FAIL: ".$e->getMessage()); // تابع بدون إسقاط الصفحة }
استبدل كامل بلوك صفحة stats
بالنسخة التالية (تحسب التصنيفات المفضلة في PHP بدل json_each
):
phpelseif ($route==='stats' && $user){ $uid=$user['id']; $watchedHrs = (float)$pdo->query("SELECT COALESCE(SUM(position),0)/3600 FROM watch_history WHERE user_id=$uid")->fetchColumn(); $comments = (int)$pdo->query("SELECT COUNT(*) FROM comments WHERE user_id=$uid")->fetchColumn(); $ratings = (int)$pdo->query("SELECT COUNT(*) FROM ratings WHERE user_id=$uid")->fetchColumn(); // اجلب الأنواع وشكّل العدّ في PHP (تجنّب json_each) $rows = $pdo->query("SELECT movies.genres FROM watch_history JOIN movies ON movies.id=watch_history.movie_id WHERE user_id=$uid")->fetchAll(PDO::FETCH_COLUMN); $gcount = []; foreach ($rows as $gstr) { foreach (explode(',', (string)$gstr) as $g) { $g = trim($g); if ($g==='') continue; $gcount[$g] = ($gcount[$g] ?? 0) + 1; } } arsort($gcount); $favGenres = array_slice(array_keys($gcount), 0, 5); ?> <h2 class="section">إحصاءاتي</h2> <div class="kpis"> <div class="kpi">⏱️ ساعات المشاهدة: <b><?=number_format($watchedHrs,2)?></b></div> <div class="kpi">💬 تعليقات: <b><?=$comments?></b></div> <div class="kpi">⭐ تقييمات: <b><?=$ratings?></b></div> <div class="kpi">🎯 التصنيفات المفضلة: <b><?= h(implode(' ', $favGenres)) ?></b></div> </div> <div class="notice small" style="margin-top:10px">تلميح: يتم المزامنة بين الأجهزة تلقائياً عند تسجيل الدخول.</div> <?php }
بدّل دالة الأكشن هذه:
php// WATCHLIST toggle if ($action==='watchlist_toggle' && current_user()){ csrf_check(); $uid=current_user()['id']; $mid=(int)$_POST['movie_id']; $st=$pdo->prepare("SELECT COUNT(*) FROM watchlist WHERE user_id=? AND movie_id=?"); $st->execute([$uid,$mid]); if ((int)$st->fetchColumn()>0){ $pdo->prepare("DELETE FROM watchlist WHERE user_id=? AND movie_id=?")->execute([$uid,$mid]); j(['ok'=>true,'in'=>false]); } else { $pdo->prepare("INSERT INTO watchlist(user_id,movie_id,created_at) VALUES(?,?,?)")->execute([$uid,$mid,now()]); j(['ok'=>true,'in'=>true]); } }
(يحذف السطر المتكرر الذي كان يعيّن $exists
من execute()
بطريق الخطأ.)
بعض مزوّدي الاستضافة يرفضون ترويسات معيّنة. لإزالة أي احتمال سقوط بسبب الهيدرات، يمكنك (مؤقتًا) تبسيط الـ CSP:
ابحث عن هذا السطر:
phpheader("Content-Security-Policy: default-src 'self' blob: data:; img-src 'self' blob: data: https:; media-src 'self' blob: data: https:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; frame-ancestors 'none'");
وحوّله مؤقتًا إلى:
php@header("Content-Security-Policy: default-src 'self' data: blob: https:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; frame-ancestors 'none'");
(لاحظ @
لتجاهل الخطأ إن رفض الخادم.)
/?route=home
.
index.php
وانسخ آخر 30 سطرًا — هناك سنرى الاستثناء بالتحديد ونصلّحه فورًا.بما أن صفحة
?route=diag
تعمل عندك، فالسبب ليس السيرفر ولا الامتدادات — غالبًا استثناء ضمن seed/queries أو هيدر. الحزم أعلاه تمنع السقوط وتُسجّل السبب في اللوج.
لو ترغب، أرسل لي آخر أسطر php-error.log حالًا، وسأطلعك على التعديل الدقيق التالي (سطر/سطرين) حسب الخطأ الفعلي.