diff --git a/README.md b/README.md index cdc16c5..44c0cba 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,69 @@ -# Decrypt -### [+] Created By HTR-TECH (@***tahmid.rayat***) -### [+] Disclaimer : -***Decrypter is a tool to decrypt Encrypted Bash Scripts into a Readable Format.This Tool is created for Educational Purpose only.I am not responsible for any misuse of this tool.*** +# GENZMOVIE - +GENZMOVIE is a PHP + MySQL movie streaming website with OPhim auto crawler, admin dashboard, ad monetization, user accounts, watchlist/history, SEO routes, and responsive UI. -### [+] Installation -```apt update``` +## Project Structure -```apt install git python2 -y``` +```text +genzmovie/ +├── config/ +├── controllers/ +├── models/ +├── views/ +├── assets/ +├── uploads/ +├── admin/ +├── crawler/ +├── api/ +├── includes/ +├── database/ +├── index.php +└── .htaccess +``` -```git clone https://github.com/hax0rtahm1d/decrypt``` +## Features -```cd decrypt``` +- Netflix-style homepage with featured slider and category blocks. +- Search + autocomplete + filter by genre/year/country/quality. +- Movie details with metadata, episodes, and related movies. +- Player supports iframe embed and direct MP4/HLS source. +- Multiple server episode links. +- User register/login/logout with dashboard (favorites + watch history). +- Ad monetization: header/sidebar/popup/footer/video pre-roll placements. +- Admin dashboard for crawler trigger, ad management, overview of movies/users/comments. +- OPhim crawler imports movie metadata + episodes + links. +- SEO: friendly route `/phim/{slug}` and XML sitemap endpoint. +- Security basics: password hashing, prepared statements, CSRF token, output escaping. -```python2 dec.py``` +## Installation Guide -### Or, Use Single Command +1. **Copy files** into web root (Apache/Nginx + PHP 8+). +2. **Create database**: + ```bash + mysql -u root -p < genzmovie/database/schema.sql + ``` +3. **Update DB credentials** in `genzmovie/config/config.php`. +4. **Set document root** to project root or `/genzmovie` and enable mod_rewrite. +5. **Open website**: + - Frontend: `http://localhost/genzmovie/` + - Admin: `http://localhost/genzmovie/admin/login.php` +6. **Default admin account**: + - Email: `admin@genzmovie.local` + - Password: `admin123` -``` -apt update && apt install git python2 -y && git clone https://github.com/hax0rtahm1d/decrypt && cd decrypt && python2 dec.py -``` +## Crawler + +- Manual import: use **Import từ OPhim** button in `/admin`. +- Cronjob: + ```bash + */30 * * * * php /path/to/genzmovie/crawler/ophim_crawler.php + ``` + +## Sitemap + +- XML sitemap endpoint: `http://localhost/genzmovie/api/sitemap.php` + +## Notes -## [+] Find Me on : -[![Github](https://img.shields.io/badge/Github-HTR--TECH-green?style=for-the-badge&logo=github)](https://github.com/htr-tech) -[![Instagram](https://img.shields.io/badge/IG-%40tahmid.rayat-red?style=for-the-badge&logo=instagram)](https://www.instagram.com/tahmid.rayat) -[![Messenger](https://img.shields.io/badge/Chat-Messenger-blue?style=for-the-badge&logo=messenger)](https://m.me/tahmid.rayat.official) +- Add your Google AdSense / ad network code at admin ads form. +- For production, configure HTTPS, strict CSP, and hardened session settings. diff --git a/genzmovie/.htaccess b/genzmovie/.htaccess new file mode 100644 index 0000000..0090bb0 --- /dev/null +++ b/genzmovie/.htaccess @@ -0,0 +1,5 @@ +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^phim/([a-zA-Z0-9-]+)/?$ index.php?route=movie&slug=$1 [QSA,L] +RewriteRule ^$ index.php?route=home [QSA,L] diff --git a/genzmovie/admin/index.php b/genzmovie/admin/index.php new file mode 100644 index 0000000..222157d --- /dev/null +++ b/genzmovie/admin/index.php @@ -0,0 +1,47 @@ +prepare('INSERT INTO ads (name, position, ad_type, ad_code, status, created_at, updated_at) VALUES (:name,:position,:ad_type,:ad_code,1,NOW(),NOW())'); + $stmt->execute([ + 'name' => $_POST['name'], + 'position' => $_POST['position'], + 'ad_type' => $_POST['ad_type'], + 'ad_code' => $_POST['ad_code'], + ]); + $message = 'Đã thêm quảng cáo'; + } +} + +$movies = $pdo->query('SELECT * FROM movies ORDER BY id DESC LIMIT 20')->fetchAll(); +$users = $pdo->query('SELECT id,name,email,status FROM users ORDER BY id DESC LIMIT 20')->fetchAll(); +$comments = $pdo->query('SELECT c.id,c.content,m.title FROM comments c JOIN movies m ON m.id=c.movie_id ORDER BY c.id DESC LIMIT 20')->fetchAll(); +$ads = $pdo->query('SELECT * FROM ads ORDER BY id DESC')->fetchAll(); +?> +Admin - GENZMOVIE +
+

Admin Dashboard

+
+
+
Crawler Control

Cronjob: */30 * * * * php /path/genzmovie/crawler/ophim_crawler.php

+
Ads Management
+
+
+
Movie Management
IDTitleYear
+
User Management
IDNameEmailStatus
+
Comment Management
IDMovieComment
+
Ads
NamePositionType
+
diff --git a/genzmovie/admin/login.php b/genzmovie/admin/login.php new file mode 100644 index 0000000..f2f046c --- /dev/null +++ b/genzmovie/admin/login.php @@ -0,0 +1,28 @@ +prepare('SELECT * FROM admins WHERE email = :email LIMIT 1'); + $stmt->execute(['email' => trim($_POST['email'])]); + $admin = $stmt->fetch(); + if ($admin && password_verify($_POST['password'], $admin['password'])) { + $_SESSION['admin'] = $admin; + header('Location: /genzmovie/admin/index.php'); + exit; + } + $error = 'Sai thông tin đăng nhập'; + } +} +?> + +
+

Admin Login

+
+
diff --git a/genzmovie/api/sitemap.php b/genzmovie/api/sitemap.php new file mode 100644 index 0000000..4684968 --- /dev/null +++ b/genzmovie/api/sitemap.php @@ -0,0 +1,16 @@ +query('SELECT slug, updated_at FROM movies ORDER BY updated_at DESC')->fetchAll(); +echo "\n"; +?> + + / + + + /phim/ + + + + diff --git a/genzmovie/assets/css/style.css b/genzmovie/assets/css/style.css new file mode 100644 index 0000000..9e9cef5 --- /dev/null +++ b/genzmovie/assets/css/style.css @@ -0,0 +1,9 @@ +body { font-family: 'Segoe UI', sans-serif; } +.movie-card img { height: 260px; object-fit: cover; } +.movie-card:hover { transform: translateY(-4px); transition: .2s; box-shadow: 0 0 12px rgba(255,255,255,.12); } +.featured-img { max-height: 420px; object-fit: cover; filter: brightness(.6); } +.bg-overlay { background: rgba(0,0,0,.55); } +.autocomplete-box { position:absolute; top:100%; left:0; right:0; background:#111; z-index:1000; border:1px solid #444; } +.autocomplete-box a { display:block; color:#fff; padding:6px 10px; text-decoration:none; } +.autocomplete-box a:hover { background:#222; } +.ad-slot iframe, .ad-slot img { max-width: 100%; } diff --git a/genzmovie/assets/js/app.js b/genzmovie/assets/js/app.js new file mode 100644 index 0000000..671bd68 --- /dev/null +++ b/genzmovie/assets/js/app.js @@ -0,0 +1,24 @@ +$(function () { + const $search = $('#movie-search'); + const $box = $('#autocomplete-box'); + + $search.on('input', function () { + const q = $(this).val().trim(); + if (q.length < 2) { + $box.empty(); + return; + } + $.getJSON('/genzmovie/?route=autocomplete&q=' + encodeURIComponent(q), function (items) { + $box.empty(); + items.forEach(item => { + $box.append(`${item.title}`); + }); + }); + }); + + $(document).on('click', function (e) { + if (!$(e.target).closest('#movie-search').length) { + $box.empty(); + } + }); +}); diff --git a/genzmovie/config/config.php b/genzmovie/config/config.php new file mode 100644 index 0000000..08ee222 --- /dev/null +++ b/genzmovie/config/config.php @@ -0,0 +1,15 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, + ]); + } + + return self::$instance; + } +} diff --git a/genzmovie/controllers/AuthController.php b/genzmovie/controllers/AuthController.php new file mode 100644 index 0000000..2dc437a --- /dev/null +++ b/genzmovie/controllers/AuthController.php @@ -0,0 +1,51 @@ +findByEmail(trim($_POST['email'])); + if ($user && password_verify($_POST['password'], $user['password'])) { + $_SESSION['user'] = $user; + redirect('/'); + } + $error = 'Email hoặc mật khẩu không đúng.'; + } + } + include __DIR__ . '/../views/auth/login.php'; + } + + public function register(): void + { + $error = null; + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (!validate_csrf($_POST['csrf_token'] ?? null)) { + $error = 'CSRF token invalid.'; + } else { + $userModel = new User(); + $ok = $userModel->create(trim($_POST['name']), trim($_POST['email']), $_POST['password']); + if ($ok) { + redirect('/?route=login'); + } + $error = 'Không thể tạo tài khoản.'; + } + } + include __DIR__ . '/../views/auth/register.php'; + } + + public function logout(): void + { + unset($_SESSION['user']); + redirect('/'); + } +} diff --git a/genzmovie/controllers/HomeController.php b/genzmovie/controllers/HomeController.php new file mode 100644 index 0000000..5bfb182 --- /dev/null +++ b/genzmovie/controllers/HomeController.php @@ -0,0 +1,31 @@ + 'series', + 'Phim chiếu rạp' => 'theater', + 'Phim bộ' => 'series', + 'Phim lẻ' => 'single', + 'Phim Netflix đề cử' => 'netflix', + 'Phim hành động' => 'Hành Động', + 'Phim kinh dị' => 'Kinh Dị', + 'Phim hoạt hình' => 'Hoạt Hình', + ]; + + $featured = $movieModel->featured(); + $moviesBySection = []; + foreach ($sections as $label => $key) { + $moviesBySection[$label] = $movieModel->byCategory($key); + } + + include __DIR__ . '/../views/home/index.php'; + } +} diff --git a/genzmovie/controllers/MovieController.php b/genzmovie/controllers/MovieController.php new file mode 100644 index 0000000..7220793 --- /dev/null +++ b/genzmovie/controllers/MovieController.php @@ -0,0 +1,75 @@ +findBySlug($slug); + + if (!$movie) { + http_response_code(404); + echo 'Movie not found'; + return; + } + + $episodes = $episodeModel->byMovie((int) $movie['id']); + $currentEpisode = $episodes[0] ?? null; + if (isset($_GET['episode_id'])) { + $selected = $episodeModel->find((int) $_GET['episode_id']); + if ($selected && (int) $selected['movie_id'] === (int) $movie['id']) { + $currentEpisode = $selected; + } + } + + $related = $movieModel->related((int) $movie['id'], (string) $movie['tags'], (int) $movie['year']); + + if (current_user_id()) { + $stmt = Database::connection()->prepare('INSERT INTO watch_history (user_id, movie_id, episode_id, watched_at) VALUES (:user_id, :movie_id, :episode_id, NOW())'); + $stmt->execute([ + 'user_id' => current_user_id(), + 'movie_id' => $movie['id'], + 'episode_id' => $currentEpisode['id'] ?? null, + ]); + } + + include __DIR__ . '/../views/movie/show.php'; + } + + public function search(): void + { + $keyword = trim($_GET['q'] ?? ''); + $movieModel = new Movie(); + $movies = $keyword !== '' ? $movieModel->search($keyword) : []; + include __DIR__ . '/../views/movie/list.php'; + } + + public function filter(): void + { + $movieModel = new Movie(); + $movies = $movieModel->filter([ + 'genre' => $_GET['genre'] ?? null, + 'year' => $_GET['year'] ?? null, + 'country' => $_GET['country'] ?? null, + 'quality' => $_GET['quality'] ?? null, + ]); + $keyword = 'Lọc phim'; + include __DIR__ . '/../views/movie/list.php'; + } + + public function autocomplete(): void + { + header('Content-Type: application/json'); + $keyword = trim($_GET['q'] ?? ''); + $movieModel = new Movie(); + $movies = $keyword !== '' ? array_slice($movieModel->search($keyword), 0, 8) : []; + echo json_encode(array_map(static fn(array $movie) => ['title' => $movie['title'], 'slug' => $movie['slug']], $movies), JSON_UNESCAPED_UNICODE); + } +} diff --git a/genzmovie/controllers/UserController.php b/genzmovie/controllers/UserController.php new file mode 100644 index 0000000..114419d --- /dev/null +++ b/genzmovie/controllers/UserController.php @@ -0,0 +1,48 @@ +favorites((int) $user['id']); + + $stmt = Database::connection()->prepare('SELECT m.title, m.slug, wh.watched_at FROM watch_history wh JOIN movies m ON m.id = wh.movie_id WHERE wh.user_id = :uid ORDER BY wh.watched_at DESC LIMIT 30'); + $stmt->execute(['uid' => $user['id']]); + $history = $stmt->fetchAll(); + + include __DIR__ . '/../views/user/dashboard.php'; + } + + public function toggleFavorite(): void + { + require_login(); + if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !validate_csrf($_POST['csrf_token'] ?? null)) { + http_response_code(419); + exit('Invalid request'); + } + + $userId = (int) $_SESSION['user']['id']; + $movieId = (int) ($_POST['movie_id'] ?? 0); + $pdo = Database::connection(); + + $check = $pdo->prepare('SELECT id FROM favorites WHERE user_id = :uid AND movie_id = :mid LIMIT 1'); + $check->execute(['uid' => $userId, 'mid' => $movieId]); + $found = $check->fetch(); + + if ($found) { + $pdo->prepare('DELETE FROM favorites WHERE id = :id')->execute(['id' => $found['id']]); + } else { + $pdo->prepare('INSERT INTO favorites (user_id, movie_id, created_at) VALUES (:uid, :mid, NOW())')->execute(['uid' => $userId, 'mid' => $movieId]); + } + + redirect('/?route=dashboard'); + } +} diff --git a/genzmovie/crawler/ophim_crawler.php b/genzmovie/crawler/ophim_crawler.php new file mode 100644 index 0000000..b77f46e --- /dev/null +++ b/genzmovie/crawler/ophim_crawler.php @@ -0,0 +1,107 @@ + true, + CURLOPT_TIMEOUT => 20, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_SSL_VERIFYPEER => false, + ]); + $response = curl_exec($ch); + curl_close($ch); + if ($response === false) { + return null; + } + $decoded = json_decode($response, true); + return is_array($decoded) ? $decoded : null; +} + +$pdo = Database::connection(); +$listData = ophim_get(OPHIM_API_BASE . '/danh-sach/phim-moi-cap-nhat?page=1'); +if (!$listData || empty($listData['data']['items'])) { + echo "Crawler: no items found\n"; + return; +} + +foreach ($listData['data']['items'] as $item) { + $slug = $item['slug'] ?? null; + if (!$slug) { + continue; + } + + $detail = ophim_get(OPHIM_API_BASE . '/phim/' . urlencode($slug)); + if (!$detail || empty($detail['data']['item'])) { + continue; + } + + $movie = $detail['data']['item']; + $title = $movie['name'] ?? 'N/A'; + $origin = $movie['origin_name'] ?? ''; + $description = strip_tags($movie['content'] ?? ''); + $poster = $movie['poster_url'] ?? ''; + $year = (int) ($movie['year'] ?? date('Y')); + $country = $movie['country'][0]['name'] ?? 'Việt Nam'; + $director = implode(', ', array_column($movie['director'] ?? [], 'name')); + $actors = implode(', ', $movie['actor'] ?? []); + $quality = $movie['quality'] ?? 'HD'; + $language = $movie['lang'] ?? 'Vietsub'; + $tags = implode(',', array_column($movie['category'] ?? [], 'name')); + $type = $movie['type'] ?? 'single'; + + $check = $pdo->prepare('SELECT id FROM movies WHERE slug = :slug LIMIT 1'); + $check->execute(['slug' => $slug]); + $existing = $check->fetch(); + + if ($existing) { + $movieId = (int) $existing['id']; + $update = $pdo->prepare('UPDATE movies SET title=:title, original_title=:original_title, description=:description, poster=:poster, year=:year, country=:country, director=:director, actors=:actors, quality=:quality, language=:language, tags=:tags, type=:type, updated_at=NOW() WHERE id=:id'); + $update->execute(compact('title', 'origin', 'description', 'poster', 'year', 'country', 'director', 'actors', 'quality', 'language', 'tags', 'type') + ['id' => $movieId, 'original_title' => $origin]); + } else { + $insert = $pdo->prepare('INSERT INTO movies (title, slug, original_title, description, poster, year, country, director, actors, quality, language, tags, type, is_featured, meta_title, meta_description, meta_keywords, created_at, updated_at) VALUES (:title,:slug,:original_title,:description,:poster,:year,:country,:director,:actors,:quality,:language,:tags,:type,0,:meta_title,:meta_description,:meta_keywords,NOW(),NOW())'); + $insert->execute([ + 'title' => $title, + 'slug' => $slug, + 'original_title' => $origin, + 'description' => $description, + 'poster' => $poster, + 'year' => $year, + 'country' => $country, + 'director' => $director, + 'actors' => $actors, + 'quality' => $quality, + 'language' => $language, + 'tags' => $tags, + 'type' => $type, + 'meta_title' => $title, + 'meta_description' => mb_strimwidth($description, 0, 160, '...'), + 'meta_keywords' => $tags, + ]); + $movieId = (int) $pdo->lastInsertId(); + } + + $pdo->prepare('DELETE FROM episodes WHERE movie_id = :movie_id')->execute(['movie_id' => $movieId]); + foreach (($detail['data']['episodes'] ?? []) as $server) { + $serverName = $server['server_name'] ?? 'Server 1'; + foreach (($server['server_data'] ?? []) as $episode) { + $pdo->prepare('INSERT INTO episodes (movie_id, server_name, episode_number, embed_link, mp4_link, subtitle_link, created_at, updated_at) VALUES (:movie_id,:server_name,:episode_number,:embed_link,:mp4_link,:subtitle_link,NOW(),NOW())') + ->execute([ + 'movie_id' => $movieId, + 'server_name' => $serverName, + 'episode_number' => $episode['name'] ?? '1', + 'embed_link' => $episode['link_embed'] ?? '', + 'mp4_link' => $episode['link_m3u8'] ?? '', + 'subtitle_link' => '', + ]); + } + } +} + +echo "Crawler finished\n"; diff --git a/genzmovie/database/schema.sql b/genzmovie/database/schema.sql new file mode 100644 index 0000000..8f693f4 --- /dev/null +++ b/genzmovie/database/schema.sql @@ -0,0 +1,122 @@ +CREATE DATABASE IF NOT EXISTS genzmovie CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE genzmovie; + +CREATE TABLE admins ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(120) NOT NULL, + email VARCHAR(160) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE users ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(120) NOT NULL, + email VARCHAR(160) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + status ENUM('active','banned') DEFAULT 'active', + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE movies ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + slug VARCHAR(255) UNIQUE NOT NULL, + original_title VARCHAR(255) DEFAULT '', + description TEXT, + poster VARCHAR(255), + year INT, + country VARCHAR(120), + director VARCHAR(255), + actors TEXT, + quality VARCHAR(20) DEFAULT 'HD', + language VARCHAR(30) DEFAULT 'Vietsub', + tags VARCHAR(255), + type VARCHAR(50) DEFAULT 'single', + is_featured TINYINT(1) DEFAULT 0, + meta_title VARCHAR(255), + meta_description VARCHAR(255), + meta_keywords VARCHAR(255), + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE genres ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(80) UNIQUE NOT NULL, + slug VARCHAR(80) UNIQUE NOT NULL +); + +CREATE TABLE movie_genres ( + movie_id INT NOT NULL, + genre_id INT NOT NULL, + PRIMARY KEY (movie_id, genre_id), + FOREIGN KEY (movie_id) REFERENCES movies(id) ON DELETE CASCADE, + FOREIGN KEY (genre_id) REFERENCES genres(id) ON DELETE CASCADE +); + +CREATE TABLE episodes ( + id INT AUTO_INCREMENT PRIMARY KEY, + movie_id INT NOT NULL, + server_name VARCHAR(80) DEFAULT 'Server 1', + episode_number VARCHAR(20) NOT NULL, + embed_link TEXT, + mp4_link TEXT, + subtitle_link VARCHAR(255), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (movie_id) REFERENCES movies(id) ON DELETE CASCADE +); + +CREATE TABLE watch_history ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + movie_id INT NOT NULL, + episode_id INT NULL, + watched_at DATETIME, + INDEX idx_user_watched (user_id, watched_at), + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (movie_id) REFERENCES movies(id) ON DELETE CASCADE +); + +CREATE TABLE favorites ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + movie_id INT NOT NULL, + created_at DATETIME, + UNIQUE KEY uniq_fav (user_id, movie_id), + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (movie_id) REFERENCES movies(id) ON DELETE CASCADE +); + +CREATE TABLE comments ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + movie_id INT NOT NULL, + content TEXT NOT NULL, + created_at DATETIME, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (movie_id) REFERENCES movies(id) ON DELETE CASCADE +); + +CREATE TABLE ads ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(120) NOT NULL, + position ENUM('header','sidebar','popup','video_preroll','footer') NOT NULL, + ad_type ENUM('adsense','custom_html','script') NOT NULL, + ad_code TEXT NOT NULL, + status TINYINT(1) DEFAULT 1, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE settings ( + id INT AUTO_INCREMENT PRIMARY KEY, + setting_key VARCHAR(120) UNIQUE NOT NULL, + setting_value TEXT +); + +INSERT INTO admins (name, email, password, created_at, updated_at) +VALUES ('Administrator', 'admin@genzmovie.local', '$2y$10$3M5v0HcObv7pwzR4zuI3d.8kd8V6VgP9SN5w2em4fS5s95x80nA4.', NOW(), NOW()); diff --git a/genzmovie/includes/auth.php b/genzmovie/includes/auth.php new file mode 100644 index 0000000..c46d70f --- /dev/null +++ b/genzmovie/includes/auth.php @@ -0,0 +1,18 @@ +'; +} + +function validate_csrf(?string $token): bool +{ + return !empty($token) && !empty($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token); +} diff --git a/genzmovie/includes/helpers.php b/genzmovie/includes/helpers.php new file mode 100644 index 0000000..2c6365a --- /dev/null +++ b/genzmovie/includes/helpers.php @@ -0,0 +1,25 @@ +index(); + break; + case 'movie': + (new MovieController())->show($_GET['slug'] ?? ''); + break; + case 'search': + (new MovieController())->search(); + break; + case 'filter': + (new MovieController())->filter(); + break; + case 'autocomplete': + (new MovieController())->autocomplete(); + break; + case 'login': + (new AuthController())->login(); + break; + case 'register': + (new AuthController())->register(); + break; + case 'logout': + (new AuthController())->logout(); + break; + case 'dashboard': + (new UserController())->dashboard(); + break; + case 'favorite': + (new UserController())->toggleFavorite(); + break; + default: + http_response_code(404); + echo '404 Not Found'; +} diff --git a/genzmovie/models/Ad.php b/genzmovie/models/Ad.php new file mode 100644 index 0000000..f7dfa6e --- /dev/null +++ b/genzmovie/models/Ad.php @@ -0,0 +1,15 @@ +prepare('SELECT * FROM ads WHERE position = :position AND status = 1 ORDER BY id DESC'); + $stmt->execute(['position' => $position]); + return $stmt->fetchAll(); + } +} diff --git a/genzmovie/models/Episode.php b/genzmovie/models/Episode.php new file mode 100644 index 0000000..d9732b9 --- /dev/null +++ b/genzmovie/models/Episode.php @@ -0,0 +1,29 @@ +prepare('SELECT * FROM episodes WHERE movie_id = :movie_id ORDER BY server_name ASC, episode_number ASC'); + $stmt->execute(['movie_id' => $movieId]); + return $stmt->fetchAll(); + } + + public function find(int $id): ?array + { + $stmt = Database::connection()->prepare('SELECT * FROM episodes WHERE id = :id LIMIT 1'); + $stmt->execute(['id' => $id]); + $episode = $stmt->fetch(); + return $episode ?: null; + } + + public function save(array $data): void + { + $stmt = Database::connection()->prepare('INSERT INTO episodes (movie_id, server_name, episode_number, embed_link, mp4_link, subtitle_link, created_at, updated_at) VALUES (:movie_id, :server_name, :episode_number, :embed_link, :mp4_link, :subtitle_link, NOW(), NOW())'); + $stmt->execute($data); + } +} diff --git a/genzmovie/models/Movie.php b/genzmovie/models/Movie.php new file mode 100644 index 0000000..20ca27b --- /dev/null +++ b/genzmovie/models/Movie.php @@ -0,0 +1,89 @@ +prepare('SELECT * FROM movies ORDER BY is_featured DESC, updated_at DESC LIMIT :limit'); + $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); + $stmt->execute(); + return $stmt->fetchAll(); + } + + public function byCategory(string $category, int $limit = 12): array + { + $sql = 'SELECT DISTINCT m.* FROM movies m + LEFT JOIN movie_genres mg ON mg.movie_id = m.id + LEFT JOIN genres g ON g.id = mg.genre_id + WHERE m.type = :category OR g.name = :category + ORDER BY m.updated_at DESC LIMIT :limit'; + $stmt = Database::connection()->prepare($sql); + $stmt->bindValue(':category', $category); + $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); + $stmt->execute(); + return $stmt->fetchAll(); + } + + public function search(string $keyword): array + { + $stmt = Database::connection()->prepare('SELECT * FROM movies WHERE title LIKE :keyword OR actors LIKE :keyword OR tags LIKE :keyword ORDER BY updated_at DESC'); + $stmt->execute(['keyword' => "%{$keyword}%"]); + return $stmt->fetchAll(); + } + + public function filter(array $filters): array + { + $sql = 'SELECT DISTINCT m.* FROM movies m LEFT JOIN movie_genres mg ON mg.movie_id = m.id LEFT JOIN genres g ON g.id = mg.genre_id WHERE 1=1'; + $params = []; + + if (!empty($filters['genre'])) { + $sql .= ' AND g.name = :genre'; + $params['genre'] = $filters['genre']; + } + if (!empty($filters['year'])) { + $sql .= ' AND m.year = :year'; + $params['year'] = (int) $filters['year']; + } + if (!empty($filters['country'])) { + $sql .= ' AND m.country = :country'; + $params['country'] = $filters['country']; + } + if (!empty($filters['quality'])) { + $sql .= ' AND m.quality = :quality'; + $params['quality'] = $filters['quality']; + } + $sql .= ' ORDER BY m.updated_at DESC'; + + $stmt = Database::connection()->prepare($sql); + $stmt->execute($params); + return $stmt->fetchAll(); + } + + public function findBySlug(string $slug): ?array + { + $stmt = Database::connection()->prepare('SELECT * FROM movies WHERE slug = :slug LIMIT 1'); + $stmt->execute(['slug' => $slug]); + $movie = $stmt->fetch(); + return $movie ?: null; + } + + public function related(int $movieId, string $genresCsv, int $year): array + { + $stmt = Database::connection()->prepare('SELECT * FROM movies WHERE id != :id AND (FIND_IN_SET(:genres, tags) OR year = :year) ORDER BY updated_at DESC LIMIT 8'); + $stmt->execute(['id' => $movieId, 'genres' => $genresCsv, 'year' => $year]); + return $stmt->fetchAll(); + } + + public function save(array $data): int + { + $sql = 'INSERT INTO movies (title, slug, original_title, description, poster, year, country, director, actors, quality, language, tags, type, is_featured, meta_title, meta_description, meta_keywords, created_at, updated_at) + VALUES (:title, :slug, :original_title, :description, :poster, :year, :country, :director, :actors, :quality, :language, :tags, :type, :is_featured, :meta_title, :meta_description, :meta_keywords, NOW(), NOW())'; + $stmt = Database::connection()->prepare($sql); + $stmt->execute($data); + return (int) Database::connection()->lastInsertId(); + } +} diff --git a/genzmovie/models/User.php b/genzmovie/models/User.php new file mode 100644 index 0000000..2bd474c --- /dev/null +++ b/genzmovie/models/User.php @@ -0,0 +1,33 @@ +prepare('INSERT INTO users (name, email, password, created_at, updated_at) VALUES (:name, :email, :password, NOW(), NOW())'); + return $stmt->execute([ + 'name' => $name, + 'email' => $email, + 'password' => password_hash($password, PASSWORD_BCRYPT), + ]); + } + + public function findByEmail(string $email): ?array + { + $stmt = Database::connection()->prepare('SELECT * FROM users WHERE email = :email LIMIT 1'); + $stmt->execute(['email' => $email]); + $user = $stmt->fetch(); + return $user ?: null; + } + + public function favorites(int $userId): array + { + $stmt = Database::connection()->prepare('SELECT m.* FROM favorites f JOIN movies m ON m.id = f.movie_id WHERE f.user_id = :user_id ORDER BY f.created_at DESC'); + $stmt->execute(['user_id' => $userId]); + return $stmt->fetchAll(); + } +} diff --git a/genzmovie/views/auth/login.php b/genzmovie/views/auth/login.php new file mode 100644 index 0000000..a385315 --- /dev/null +++ b/genzmovie/views/auth/login.php @@ -0,0 +1,10 @@ + +
+

Đăng nhập

+
+
+ + + +
+ diff --git a/genzmovie/views/auth/register.php b/genzmovie/views/auth/register.php new file mode 100644 index 0000000..097c3fb --- /dev/null +++ b/genzmovie/views/auth/register.php @@ -0,0 +1,11 @@ + +
+

Đăng ký

+
+
+ + + + +
+ diff --git a/genzmovie/views/home/index.php b/genzmovie/views/home/index.php new file mode 100644 index 0000000..7228151 --- /dev/null +++ b/genzmovie/views/home/index.php @@ -0,0 +1,30 @@ + + + + + $items): ?> +
+
+

+ Xem tất cả +
+
+ +
+
+ + + diff --git a/genzmovie/views/layouts/footer.php b/genzmovie/views/layouts/footer.php new file mode 100644 index 0000000..268098b --- /dev/null +++ b/genzmovie/views/layouts/footer.php @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + diff --git a/genzmovie/views/layouts/header.php b/genzmovie/views/layouts/header.php new file mode 100644 index 0000000..1d88792 --- /dev/null +++ b/genzmovie/views/layouts/header.php @@ -0,0 +1,46 @@ +activeByPosition('header'); +$sidebarAds = $adModel->activeByPosition('sidebar'); +$popupAds = $adModel->activeByPosition('popup'); +?> + + + + + + <?= e($metaTitle ?? APP_NAME) ?> + + + + + + + + + +
+ + +
+
+
diff --git a/genzmovie/views/movie/list.php b/genzmovie/views/movie/list.php new file mode 100644 index 0000000..990ad14 --- /dev/null +++ b/genzmovie/views/movie/list.php @@ -0,0 +1,12 @@ + +

Kết quả:

+
+ +
+
+
+
+
+
+
+ diff --git a/genzmovie/views/movie/show.php b/genzmovie/views/movie/show.php new file mode 100644 index 0000000..cc75f8b --- /dev/null +++ b/genzmovie/views/movie/show.php @@ -0,0 +1,55 @@ + + +
+
<?= e($movie['title']) ?>
+
+

+

+

+
    +
  • Năm:
  • +
  • Quốc gia:
  • +
  • Đạo diễn:
  • +
  • Diễn viên:
  • +
+ +
+ + + +
+ +
+
+ +
+

Trình phát

+ + +
+ + + + +

Chưa có tập phim.

+ +
+ +
+
Danh sách tập / server
+
+ + - Tập + +
+
+ +
+

Phim liên quan

+
+
+ + diff --git a/genzmovie/views/partials/movie-card.php b/genzmovie/views/partials/movie-card.php new file mode 100644 index 0000000..9579efa --- /dev/null +++ b/genzmovie/views/partials/movie-card.php @@ -0,0 +1,15 @@ + diff --git a/genzmovie/views/user/dashboard.php b/genzmovie/views/user/dashboard.php new file mode 100644 index 0000000..e0cc70d --- /dev/null +++ b/genzmovie/views/user/dashboard.php @@ -0,0 +1,13 @@ + +

Xin chào,

+
+
+
Phim yêu thích
+
+
+
+
Lịch sử xem
+
  • -
+
+
+