diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dce270a --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# FoOlSlideX .gitignore + +# Configuration files +config.php +session.php +.installed + +# Secrets +library/secrets/ + +# Database files +database/*/_cnt.sdb +database/*/cache/ +database/*/data/ + +# IDE and OS files +.DS_Store +Thumbs.db +.vscode/ +*.swp +*.swo + +# Logs +*.log \ No newline at end of file diff --git a/.installed b/.installed new file mode 100644 index 0000000..62b7dbf --- /dev/null +++ b/.installed @@ -0,0 +1 @@ +2025-08-13 18:01:32 \ No newline at end of file diff --git a/app/Controllers/Api/ApiController.php b/app/Controllers/Api/ApiController.php new file mode 100644 index 0000000..356b3f7 --- /dev/null +++ b/app/Controllers/Api/ApiController.php @@ -0,0 +1,101 @@ +jsonResponse([ + 'name' => 'FoOlSlideX API', + 'version' => '1.0', + 'endpoints' => [ + 'GET /api/titles' => 'Get all titles', + 'GET /api/titles/{id}' => 'Get a specific title', + 'GET /api/titles/{id}/chapters' => 'Get chapters for a title', + 'GET /api/chapters/{id}' => 'Get a specific chapter', + 'GET /api/search' => 'Search titles' + ] + ]); + } + + public function titles() + { + $page = $_GET['page'] ?? 1; + $limit = $_GET['limit'] ?? 20; + $offset = ($page - 1) * $limit; + + $titles = Title::query() + ->orderBy(['title' => 'ASC']) + ->limit($limit) + ->skip($offset) + ->getQuery() + ->fetch(); + + $this->jsonResponse([ + 'data' => $titles, + 'page' => (int)$page, + 'limit' => (int)$limit, + 'total' => Title::count() + ]); + } + + public function title($id) + { + $title = Title::find($id); + + if (!$title) { + $this->jsonResponse(['error' => 'Title not found'], 404); + return; + } + + $this->jsonResponse($title); + } + + public function titleChapters($id) + { + $chapters = Chapter::findByTitle($id); + + if (!$chapters) { + $this->jsonResponse(['error' => 'No chapters found for this title'], 404); + return; + } + + $this->jsonResponse($chapters); + } + + public function chapter($id) + { + $chapter = Chapter::find($id); + + if (!$chapter) { + $this->jsonResponse(['error' => 'Chapter not found'], 404); + return; + } + + $this->jsonResponse($chapter); + } + + public function search() + { + $query = $_GET['q'] ?? ''; + + if (empty($query)) { + $this->jsonResponse(['error' => 'Search query is required'], 400); + return; + } + + $titles = Title::searchByTitle($query); + + $this->jsonResponse([ + 'query' => $query, + 'results' => $titles, + 'count' => count($titles) + ]); + } +} \ No newline at end of file diff --git a/app/Controllers/ApiController.php b/app/Controllers/ApiController.php new file mode 100644 index 0000000..6c216e6 --- /dev/null +++ b/app/Controllers/ApiController.php @@ -0,0 +1,17 @@ +redirect('api.php'); + } +} \ No newline at end of file diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php new file mode 100644 index 0000000..1f11948 --- /dev/null +++ b/app/Controllers/AuthController.php @@ -0,0 +1,112 @@ +redirect('index.php'); + return; + } + + $this->render("pages/login.tpl", [ + "pagetitle" => "Login " . $config["divider"] . " " . $config["title"] + ]); + } + + public function login() + { + global $db, $config, $logged; + + if ($logged) { + $this->redirect('index.php'); + return; + } + + // This would be implemented to handle the actual login logic + // For now, we'll just redirect to the original login page + $this->redirect('ajax/account/login.php'); + } + + public function showSignup() + { + global $config, $logged; + + if ($logged) { + $this->redirect('index.php'); + return; + } + + $this->render("pages/signup.tpl", [ + "pagetitle" => "Sign Up " . $config["divider"] . " " . $config["title"] + ]); + } + + public function signup() + { + global $db, $config, $logged; + + if ($logged) { + $this->redirect('index.php'); + return; + } + + // This would be implemented to handle the actual signup logic + // For now, we'll just redirect to the original signup page + $this->redirect('ajax/account/signup.php'); + } + + public function logout() + { + global $config; + + // This would be implemented to handle the actual logout logic + // For now, we'll just redirect to the original logout page + $this->redirect('ajax/account/logout.php'); + } + + public function activate() + { + global $config, $logged, $user; + + if (!$logged) { + $this->redirect('index.php'); + return; + } + + if ($logged && $user["level"] >= 50) { + $this->redirect('index.php'); + return; + } + + if (!isset($_GET["token"]) || empty($_GET["token"])) { + $this->redirect('index.php'); + return; + } + + $token = clean($_GET["token"] ?? ""); + + if (empty($token)) { + $this->redirect('index.php'); + return; + } + + $check = $db["activation"]->findOneBy(["token", "==", $token]); + if (empty($check)) { + $this->redirect('index.php'); + return; + } + + $data = array( + "level" => 50 + ); + $db["users"]->updateById($user["id"], $data); + $this->redirect('index.php'); + } +} \ No newline at end of file diff --git a/app/Controllers/ChapterController.php b/app/Controllers/ChapterController.php new file mode 100644 index 0000000..a7b8587 --- /dev/null +++ b/app/Controllers/ChapterController.php @@ -0,0 +1,100 @@ +redirect('index.php'); + return; + } + + $id = cat($id); + $chapter = $db["chapters"]->findById($id); + $title = $db["titles"]->findById($chapter["title"]); + + if (empty($title)) { + $this->redirect('titles.php'); + return; + } + + if (empty($chapter)) { + $this->redirect('title.php?id=' . $title["id"]); + return; + } + + $fct = formatChapterTitle($chapter["volume"], $chapter["number"], "full"); + + $chapters = $db["chapters"]->findBy(["title", "==", $title["id"]]); + + $isNextChapter = false; + $isPrevChapter = false; + + foreach ($chapters as $key => $ch) { + if ($ch["id"] == $id) { + if (!empty($chapters[($key + 1)])) $isNextChapter = $chapters[($key + 1)]["id"]; + if (!empty($chapters[($key - 1)])) $isPrevChapter = $chapters[($key - 1)]["id"]; + } + } + + $images = glob(ps(__DIR__ . "/../data/chapters/{$id}/*.{jpg,png,jpeg,webp,gif}"), GLOB_BRACE); + natsort($images); + + $comments = $db["chapterComments"]->findBy(["chapter.id", "==", $title["id"]], ["id" => "DESC"]); + + chapterVisit($chapter); + + $imgind = []; + $ic = 1; + foreach ($images as $ii) { + $ii = pathinfo($ii); + $imgind[$ic]["order"] = $ic; + $imgind[$ic]["name"] = $ii["filename"]; + $imgind[$ic]["ext"] = $ii["extension"]; + $ic++; + } + + $page = 1; + if ($readingmode == "single") { + if (!isset($_GET["page"]) || empty($_GET["page"]) || $_GET["page"] == "0") { + $this->redirect('chapter.php?id=' . $id . '&page=1'); + return; + } + + $page = cat($_GET["page"]); + + $isNextPage = true; + $isPrevPage = true; + + $nextPage = $page + 1; + $prevPage = $page - 1; + + $imgCount = count($images); + + if ($nextPage >= $imgCount) $isNextPage = false; + if ($prevPage <= 0) $isPrevPage = false; + } + + $this->render("pages/chapter.tpl", [ + "fullChapterTitle" => $fct, + "chapters" => $chapters, + "nextChapter" => $isNextChapter, + "prevChapter" => $isPrevChapter, + "images" => $images, + "commentsCount" => count($comments), + "title" => $title, + "chapter" => $chapter, + "imgind" => $imgind, + "pagetitle" => "Read " . $title["title"] . " " . formatChapterTitle($chapter["volume"], $chapter["number"]) . " " . $config["divider"] . " " . $config["title"], + "isNextPage" => $isNextPage ?? null, + "isPrevPage" => $isPrevPage ?? null, + "currentPage" => $page ?? null + ]); + } +} \ No newline at end of file diff --git a/app/Controllers/ErrorController.php b/app/Controllers/ErrorController.php new file mode 100644 index 0000000..5136a9d --- /dev/null +++ b/app/Controllers/ErrorController.php @@ -0,0 +1,30 @@ +render("pages/404.tpl", [ + "pagetitle" => "Page Not Found " . $config["divider"] . " " . $config["title"] + ]); + } + + public function forbidden() + { + global $config; + + http_response_code(403); + + $this->render("pages/403.tpl", [ + "pagetitle" => "Access Forbidden " . $config["divider"] . " " . $config["title"] + ]); + } +} \ No newline at end of file diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php new file mode 100644 index 0000000..923ea4a --- /dev/null +++ b/app/Controllers/HomeController.php @@ -0,0 +1,46 @@ +createQueryBuilder() + ->orderBy(["id" => "DESC"]) + ->distinct(["title.id"]) + ->getQuery() + ->fetch(); + + foreach ($recentlyUpdated as $key => $rec) { + $title = $db["titles"]->findById($rec["title"]); + $recentlyUpdated[$key]["title"] = $title; + $recentlyUpdated[$key]["title"]["summary1"] = shorten($parsedown->text($purifier->purify($title["summary"])), 400); + $recentlyUpdated[$key]["title"]["summary2"] = shorten($parsedown->text($purifier->purify($title["summary"])), 100); + } + + $chapters = $db["chapters"]->createQueryBuilder() + ->orderBy(["id" => "DESC"]) + ->limit($config["perpage"]["chapters"]) + ->getQuery() + ->fetch(); + + foreach ($chapters as $key => $ch) { + $title = $db["titles"]->findById($ch["title"]); + $uploader = $db["users"]->findById($ch["user"]); + $chapters[$key]["title"] = $title; + $chapters[$key]["user"] = $uploader; + } + + $this->render("pages/index.tpl", [ + "chapters" => $chapters, + "recentlyUpdated" => $recentlyUpdated, + "pagetitle" => $config["title"] . " " . $config["divider"] . " " . $config["slogan"] + ]); + } +} \ No newline at end of file diff --git a/app/Controllers/ReleasesController.php b/app/Controllers/ReleasesController.php new file mode 100644 index 0000000..535ef61 --- /dev/null +++ b/app/Controllers/ReleasesController.php @@ -0,0 +1,45 @@ +createQueryBuilder() + ->orderBy(["id" => "DESC"]) + ->limit($limit) + ->skip($skip) + ->getQuery() + ->fetch(); + + foreach ($chapters as $key => $ch) { + $title = $db["titles"]->findById($ch["title"]); + $uploader = $db["users"]->findById($ch["user"]); + $chapters[$key]["title"] = $title; + $chapters[$key]["user"] = $uploader; + } + + $pagis = array(); + $totalPages = ceil($db["chapters"]->count() / $limit); + for ($i = 1; $i <= $totalPages; $i++) { + array_push($pagis, $i); + } + + $this->render("pages/releases.tpl", [ + "page" => $page, + "chapters" => $chapters, + "pagis" => $pagis, + "totalPages" => $totalPages, + "pagetitle" => "Releases - Page " . $page . " " . $config["divider"] . " " . $config["title"] + ]); + } +} \ No newline at end of file diff --git a/app/Controllers/TitleController.php b/app/Controllers/TitleController.php new file mode 100644 index 0000000..1becb32 --- /dev/null +++ b/app/Controllers/TitleController.php @@ -0,0 +1,156 @@ +redirect('titles.php'); + return; + } + + $id = cat($id); + $title = $db["titles"]->findById($id); + + if (empty($title)) { + $this->redirect('titles.php'); + return; + } + + $title["authors2"] = $title["authors"]; + $title["artists2"] = $title["artists"]; + if (!empty($title["authors"])) $title["authors"] = explode(",", $title["authors"]); + if (!empty($title["artists"])) $title["artists"] = explode(",", $title["artists"]); + + if (!empty($title["tags"]["formats"])) { + $_array = array(); + foreach ($title["tags"]["formats"] as $item) { + if (!empty($item) && is_numeric($item)) { + $call = getTag("format", $item); + if (!empty($call)) array_push($_array, $call); + } + } + $title["tags"]["_formats"] = $title["tags"]["formats"]; + $title["tags"]["formats"] = $_array; + } else { + $title["tags"]["_formats"] = array(); + } + if (!empty($title["tags"]["warnings"])) { + $_array = array(); + foreach ($title["tags"]["warnings"] as $item) { + if (!empty($item) && is_numeric($item)) { + $call = getTag("warnings", $item); + if (!empty($call)) array_push($_array, $call); + } + } + $title["tags"]["_warnings"] = $title["tags"]["warnings"]; + $title["tags"]["warnings"] = $_array; + } else { + $title["tags"]["_warnings"] = array(); + } + if (!empty($title["tags"]["themes"])) { + $_array = array(); + foreach ($title["tags"]["themes"] as $item) { + if (!empty($item) && is_numeric($item)) { + $call = getTag("theme", $item); + if (!empty($call)) array_push($_array, $call); + } + } + $title["tags"]["_themes"] = $title["tags"]["themes"]; + $title["tags"]["themes"] = $_array; + } else { + $title["tags"]["_themes"] = array(); + } + if (!empty($title["tags"]["genres"])) { + $_array = array(); + foreach ($title["tags"]["genres"] as $item) { + if (!empty($item) && is_numeric($item)) { + $call = getTag("genre", $item); + if (!empty($call)) array_push($_array, $call); + } + } + $title["tags"]["_genres"] = $title["tags"]["genres"]; + $title["tags"]["genres"] = $_array; + } else { + $title["tags"]["_genres"] = array(); + } + + $upl = []; + if ($logged && $user["level"] >= 75) { + // Gotta implement a function here that makes it for all files inside /custom/ + $upl["theme"] = valCustom("theme"); + $upl["genre"] = valCustom("genre"); + $upl["warnings"] = valCustom("warnings"); + $upl["format"] = valCustom("format"); + $upl["langs"] = valCLang("upload_langs"); + } + + if (!empty($title["summary"])) { + $title["summary2"] = $title["summary"]; + $title["summary"] = $parsedown->text($purifier->purify($title["summary"])); + } else { + $title["summary2"] = ""; + } + + $chapters = array(); + $chapterLangs = array(); + $chapterCount = 0; + if (!empty($db["chapters"]->findOneBy(["title", "==", $title["id"]]))) { + // Chapter Languages + $chapterLangs = $db["chapters"]->createQueryBuilder() + ->select(["language"]) + ->where(["title", "==", $title["id"]]) + ->distinct("language.0") + ->getQuery() + ->fetch(); + sort($chapterLangs); + // Preferred Language + if (!in_array($preflang, array_column(array_column($chapterLangs, "language"), 1))) { + // Set preferred language if not available + } + // Actual Chapters + foreach ($chapterLangs as $key => $chLang) { + $_chapters = $db["chapters"]->createQueryBuilder() + ->where([["title", "==", $title["id"]], "AND", ["language.1", "==", $chLang["language"][1]]]) + ->orderBy(["volume" => "DESC"]) + ->orderBy(["number" => "DESC"]) + ->getQuery() + ->fetch(); + if (!empty($_chapters)) { + $chapterLangs[$key]["language"]["chapters"] = array(); + $chapterLangs[$key]["language"]["count"] = count($_chapters); + foreach ($_chapters as $_chapter) { + $uploader = $db["users"]->findById($_chapter["user"]); + $_chapter["user"] = $uploader; + // array_push($chapterLangs[$chLang[]], $_chapter); + array_push($chapterLangs[$key]["language"]["chapters"], $_chapter); + $chapterCount++; + } + } + } + } + + $comments = $db["titleComments"]->findBy(["title", "==", $title["id"]], ["id" => "DESC"]); + + titleVisit($title); + + $this->render("pages/title.tpl", [ + "commentsCount" => count($comments), + "chapterLangs" => $chapterLangs, + "chapterCount" => $chapterCount, + "pagetitle" => $title["title"] . " (Title) " . $config["divider"] . " " . $config["title"], + "title" => $title, + "upl_theme" => $upl["theme"] ?? null, + "upl_genre" => $upl["genre"] ?? null, + "upl_warnings" => $upl["warnings"] ?? null, + "upl_format" => $upl["format"] ?? null, + "upl_langs" => $upl["langs"] ?? null + ]); + } +} \ No newline at end of file diff --git a/app/Controllers/TitlesController.php b/app/Controllers/TitlesController.php new file mode 100644 index 0000000..4f3f1d7 --- /dev/null +++ b/app/Controllers/TitlesController.php @@ -0,0 +1,51 @@ +createQueryBuilder() + ->orderBy(["title" => "ASC"]) + ->limit($limit) + ->skip($skip) + ->getQuery() + ->fetch(); + + $pagis = array(); + $totalPages = ceil($db["titles"]->count() / $limit); + for ($i = 1; $i <= $totalPages; $i++) { + array_push($pagis, $i); + } + + $upl = []; + if($logged && $user["level"] >= 75) { + // Gotta implement a function here that makes it for all files inside /custom/ + $upl["theme"] = valCustom("theme"); + $upl["genre"] = valCustom("genre"); + $upl["warnings"] = valCustom("warnings"); + $upl["format"] = valCustom("format"); + } + + $this->render("pages/titles.tpl", [ + "page" => $page, + "titles" => $titles, + "pagis" => $pagis, + "totalPages" => $totalPages, + "pagetitle" => "Titles - Page " . $page . " " . $config["divider"] . " " . $config["title"], + "upl_theme" => $upl["theme"] ?? null, + "upl_genre" => $upl["genre"] ?? null, + "upl_warnings" => $upl["warnings"] ?? null, + "upl_format" => $upl["format"] ?? null + ]); + } +} \ No newline at end of file diff --git a/app/Core/Cache.php b/app/Core/Cache.php new file mode 100644 index 0000000..fc41ad5 --- /dev/null +++ b/app/Core/Cache.php @@ -0,0 +1,83 @@ + $data['expires']) { + unlink($file); + return null; + } + + return $data['value']; + } + + public static function put($key, $value, $ttl = null) + { + $ttl = $ttl ?? self::$defaultTtl; + + // Create cache directory if it doesn't exist + if (!is_dir(self::$cacheDir)) { + mkdir(self::$cacheDir, 0755, true); + } + + $file = self::getCacheFile($key); + $data = [ + 'value' => $value, + 'expires' => time() + $ttl + ]; + + file_put_contents($file, serialize($data)); + } + + public static function forget($key) + { + $file = self::getCacheFile($key); + if (file_exists($file)) { + unlink($file); + } + } + + public static function flush() + { + $files = glob(self::$cacheDir . '*'); + foreach ($files as $file) { + if (is_file($file)) { + unlink($file); + } + } + } + + protected static function getCacheFile($key) + { + // Sanitize key for filesystem + $key = preg_replace('/[^a-zA-Z0-9_\-\.]/', '_', $key); + return self::$cacheDir . $key . '.cache'; + } + + public static function remember($key, $ttl, $callback) + { + $value = self::get($key); + if ($value !== null) { + return $value; + } + + $value = call_user_func($callback); + self::put($key, $value, $ttl); + return $value; + } +} \ No newline at end of file diff --git a/app/Core/Controller.php b/app/Core/Controller.php new file mode 100644 index 0000000..d6244fb --- /dev/null +++ b/app/Core/Controller.php @@ -0,0 +1,71 @@ +smarty = $smarty; + $this->smarty->assign("config", $config); + $this->smarty->assign("lang", $lang); + $this->smarty->assign("theme", $theme); + $this->smarty->assign("userlang", $userlang); + $this->smarty->assign("usertheme", $usertheme); + $this->smarty->assign("version", $version); + $this->smarty->assign("logged", $logged); + $this->smarty->assign("user", $user); + $this->smarty->assign("preflang", $preflang); + } + + protected function render($template, $data = []) + { + foreach ($data as $key => $value) { + $this->smarty->assign($key, $value); + } + + // Use output buffering to capture all content + ob_start(); + + try { + // Render header + $this->smarty->display("parts/header.tpl"); + + // Render main template + $this->smarty->display($template); + + // Render footer + $this->smarty->display("parts/footer.tpl"); + + // Get the complete content + $content = ob_get_contents(); + ob_end_clean(); + + // Send content-length header and output all at once + if (!headers_sent()) { + header('Content-Length: ' . strlen($content)); + } + echo $content; + + } catch (Exception $e) { + ob_end_clean(); + throw $e; + } + } + + protected function redirect($url) + { + Response::redirect($url); + } + + protected function jsonResponse($data, $code = 200) + { + Response::json($data, $code); + } +} \ No newline at end of file diff --git a/app/Core/Csrf.php b/app/Core/Csrf.php new file mode 100644 index 0000000..7a25449 --- /dev/null +++ b/app/Core/Csrf.php @@ -0,0 +1,60 @@ + $hourAgo; + }); + + return $token; + } + + public static function verify($token) + { + if (!isset($_SESSION)) { + session_start(); + } + + if (!isset($_SESSION['csrf_tokens']) || !is_array($_SESSION['csrf_tokens'])) { + return false; + } + + if (!isset($_SESSION['csrf_tokens'][$token])) { + return false; + } + + // Check if token is expired (older than 1 hour) + $tokenTime = $_SESSION['csrf_tokens'][$token]; + if (time() - $tokenTime > 3600) { + unset($_SESSION['csrf_tokens'][$token]); + return false; + } + + // Remove token after use + unset($_SESSION['csrf_tokens'][$token]); + return true; + } + + public static function field() + { + $token = self::generate(); + return ''; + } +} \ No newline at end of file diff --git a/app/Core/FileUpload.php b/app/Core/FileUpload.php new file mode 100644 index 0000000..3a50410 --- /dev/null +++ b/app/Core/FileUpload.php @@ -0,0 +1,137 @@ +file = $_FILES[$fileKey]; + } + } + + public function name($name) + { + $this->name = $name; + return $this; + } + + public function allowedTypes($types) + { + $this->allowedTypes = $types; + return $this; + } + + public function maxSize($size) + { + $this->maxSize = $size; + return $this; + } + + public function uploadDir($dir) + { + $this->uploadDir = rtrim($dir, '/') . '/'; + return $this; + } + + public function validate() + { + if (!$this->file) { + throw new \Exception('No file provided'); + } + + // Check for upload errors + if ($this->file['error'] !== UPLOAD_ERR_OK) { + throw new \Exception('File upload error: ' . $this->file['error']); + } + + // Check file size + if ($this->maxSize > 0 && $this->file['size'] > $this->maxSize) { + throw new \Exception('File size exceeds maximum allowed size'); + } + + // Check file type + if (!empty($this->allowedTypes)) { + $fileType = mime_content_type($this->file['tmp_name']); + if (!in_array($fileType, $this->allowedTypes) && + !in_array($this->file['type'], $this->allowedTypes)) { + throw new \Exception('File type not allowed'); + } + } + + return $this; + } + + public function save() + { + $this->validate(); + + // Generate filename if not provided + if (!$this->name) { + $extension = pathinfo($this->file['name'], PATHINFO_EXTENSION); + $this->name = uniqid() . '.' . $extension; + } + + // Create upload directory if it doesn't exist + if (!is_dir($this->uploadDir)) { + mkdir($this->uploadDir, 0755, true); + } + + // Move uploaded file + $destination = $this->uploadDir . $this->name; + if (!move_uploaded_file($this->file['tmp_name'], $destination)) { + throw new \Exception('Failed to move uploaded file'); + } + + return $this->name; + } + + public function saveAsTemp() + { + $this->validate(); + + // Generate temporary filename + $extension = pathinfo($this->file['name'], PATHINFO_EXTENSION); + $tempName = uniqid() . '.' . $extension; + $tempDir = '../public/data/tmp/'; + + // Create temp directory if it doesn't exist + if (!is_dir($tempDir)) { + mkdir($tempDir, 0755, true); + } + + $destination = $tempDir . $tempName; + if (!move_uploaded_file($this->file['tmp_name'], $destination)) { + throw new \Exception('Failed to move uploaded file to temp directory'); + } + + return $tempName; + } + + public static function delete($path) + { + if (file_exists($path)) { + unlink($path); + } + } + + public static function sanitizeFileName($fileName) + { + // Remove illegal characters + $fileName = preg_replace('/[^a-zA-Z0-9._-]/', '', $fileName); + + // Limit length + if (strlen($fileName) > 255) { + $fileName = substr($fileName, 0, 255); + } + + return $fileName; + } +} \ No newline at end of file diff --git a/app/Core/Helper.php b/app/Core/Helper.php new file mode 100644 index 0000000..06bb229 --- /dev/null +++ b/app/Core/Helper.php @@ -0,0 +1,123 @@ +diff($ago); + + $diff->w = floor($diff->d / 7); + $diff->d -= $diff->w * 7; + + $string = array( + 'y' => 'year', + 'm' => 'month', + 'w' => 'week', + 'd' => 'day', + 'h' => 'hour', + 'i' => 'min', + 's' => 'second', + ); + foreach ($string as $k => &$v) { + if ($diff->$k) { + $v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : ''); + } else { + unset($string[$k]); + } + } + + if (!$full) $string = array_slice($string, 0, 1); + return $string ? implode(', ', $string) . ' ago' : 'just now'; + } + + public static function shorten($text, $maxlength = 25) + { + // This function is used, to display only a certain amount of characters + if (strlen($text) > $maxlength) + return substr($text, 0, $maxlength) . "..."; + return $text; + } + + public static function strContains($haystack, $needle) + { + // Only available in PHP 8.x which I don't like at all + return $needle !== "" && mb_strpos($haystack, $needle) !== false; + } +} \ No newline at end of file diff --git a/app/Core/Logger.php b/app/Core/Logger.php new file mode 100644 index 0000000..63bbef3 --- /dev/null +++ b/app/Core/Logger.php @@ -0,0 +1,81 @@ + 100, + 'INFO' => 200, + 'WARNING' => 300, + 'ERROR' => 400, + 'CRITICAL' => 500 + ]; + + public static function debug($message, $context = []) + { + self::log('DEBUG', $message, $context); + } + + public static function info($message, $context = []) + { + self::log('INFO', $message, $context); + } + + public static function warning($message, $context = []) + { + self::log('WARNING', $message, $context); + } + + public static function error($message, $context = []) + { + self::log('ERROR', $message, $context); + } + + public static function critical($message, $context = []) + { + self::log('CRITICAL', $message, $context); + } + + protected static function log($level, $message, $context = []) + { + // Create log directory if it doesn't exist + $logDir = dirname(self::$logFile); + if (!is_dir($logDir)) { + mkdir($logDir, 0755, true); + } + + // Format the log message + $timestamp = date('Y-m-d H:i:s'); + $contextString = !empty($context) ? json_encode($context) : ''; + $logMessage = "[{$timestamp}] {$level}: {$message} {$contextString}" . PHP_EOL; + + // Write to log file + file_put_contents(self::$logFile, $logMessage, FILE_APPEND | LOCK_EX); + } + + public static function getLogs($level = null, $limit = 100) + { + if (!file_exists(self::$logFile)) { + return []; + } + + $logs = file(self::$logFile, FILE_IGNORE_NEW_LINES); + $filteredLogs = []; + + if ($level) { + $level = strtoupper($level); + foreach ($logs as $log) { + if (strpos($log, "[{$level}]") !== false) { + $filteredLogs[] = $log; + } + } + } else { + $filteredLogs = $logs; + } + + // Return last N logs + return array_slice($filteredLogs, -$limit); + } +} \ No newline at end of file diff --git a/app/Core/Middleware.php b/app/Core/Middleware.php new file mode 100644 index 0000000..303eb2e --- /dev/null +++ b/app/Core/Middleware.php @@ -0,0 +1,56 @@ +findAll(); + } + + public static function find($id) + { + return self::$db[static::$table]->findById($id); + } + + public static function where($field, $operator, $value) + { + return self::$db[static::$table]->findBy([$field, $operator, $value]); + } + + public static function firstWhere($field, $operator, $value) + { + return self::$db[static::$table]->findOneBy([$field, $operator, $value]); + } + + public static function create($data) + { + return self::$db[static::$table]->insert($data); + } + + public static function update($id, $data) + { + return self::$db[static::$table]->updateById($id, $data); + } + + public static function delete($id) + { + return self::$db[static::$table]->deleteById($id); + } + + public static function query() + { + return self::$db[static::$table]->createQueryBuilder(); + } + + public static function count() + { + return self::$db[static::$table]->count(); + } +} \ No newline at end of file diff --git a/app/Core/Request.php b/app/Core/Request.php new file mode 100644 index 0000000..e183e68 --- /dev/null +++ b/app/Core/Request.php @@ -0,0 +1,67 @@ + [], + 'POST' => [], + 'HEAD' => [] + ]; + + public function get($uri, $controller, $middleware = []) + { + $this->routes['GET'][$uri] = [ + 'controller' => $controller, + 'middleware' => $middleware + ]; + } + + public function post($uri, $controller, $middleware = []) + { + $this->routes['POST'][$uri] = [ + 'controller' => $controller, + 'middleware' => $middleware + ]; + } + + public function resolve($uri, $method) + { + // Remove query string parameters + $uri = strtok($uri, '?'); + + // Remove leading slash + $uri = trim($uri, '/'); + + // If URI is empty, default to home + if (empty($uri)) { + $uri = '/'; + } + + // Handle HEAD requests as GET requests + $routeMethod = $method === 'HEAD' ? 'GET' : $method; + + // Check if method is supported + if (!isset($this->routes[$routeMethod])) { + return $this->callController([ + 'controller' => 'ErrorController@notFound', + 'middleware' => [] + ]); + } + + if (isset($this->routes[$routeMethod][$uri])) { + return $this->callController($this->routes[$routeMethod][$uri]); + } + + // Handle dynamic routes (e.g., /title/123) + foreach ($this->routes[$routeMethod] as $route => $routeData) { + $routePattern = $this->convertToRegex($route); + if (preg_match($routePattern, $uri, $matches)) { + // Remove the first match (full match) + array_shift($matches); + return $this->callController($routeData, $matches); + } + } + + // Route not found + return $this->callController([ + 'controller' => 'ErrorController@notFound', + 'middleware' => [] + ]); + } + + protected function convertToRegex($route) + { + // Convert route parameters like /title/{id} to regex pattern + $route = preg_replace('/\//', '\\/', $route); + $route = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '([a-zA-Z0-9_]+)', $route); + return '/^' . $route . '$/'; + } + + protected function callController($routeData, $params = []) + { + // Run middleware + foreach ($routeData['middleware'] as $middleware) { + Middleware::run($middleware); + } + + // Split controller and method + $parts = explode('@', $routeData['controller']); + $controllerName = $parts[0]; + $method = $parts[1] ?? 'index'; + + // Build full class name + $class = "App\\Controllers\\{$controllerName}"; + + // Check if class exists + if (!class_exists($class)) { + throw new \Exception("Controller {$controllerName} not found"); + } + + // Create controller instance + $controllerInstance = new $class(); + + // Check if method exists + if (!method_exists($controllerInstance, $method)) { + throw new \Exception("Method {$method} not found in controller {$controllerName}"); + } + + // Call method with parameters + return call_user_func_array([$controllerInstance, $method], $params); + } +} \ No newline at end of file diff --git a/app/Core/Validator.php b/app/Core/Validator.php new file mode 100644 index 0000000..be34674 --- /dev/null +++ b/app/Core/Validator.php @@ -0,0 +1,98 @@ + $rule) { + $validator->check($field, $data[$field] ?? null, $rule); + } + return $validator; + } + + protected function check($field, $value, $rules) + { + $rules = explode('|', $rules); + foreach ($rules as $rule) { + if (!$this->passes($value, $rule)) { + $this->errors[$field][] = $this->message($field, $rule); + } + } + } + + protected function passes($value, $rule) + { + switch ($rule) { + case 'required': + return !empty($value); + case 'email': + return filter_var($value, FILTER_VALIDATE_EMAIL); + case 'numeric': + return is_numeric($value); + case 'array': + return is_array($value); + default: + if (strpos($rule, 'min:') === 0) { + $min = substr($rule, 4); + return strlen($value) >= $min; + } + if (strpos($rule, 'max:') === 0) { + $max = substr($rule, 4); + return strlen($value) <= $max; + } + if (strpos($rule, 'size:') === 0) { + $size = substr($rule, 5); + return strlen($value) == $size; + } + return true; + } + } + + protected function message($field, $rule) + { + switch ($rule) { + case 'required': + return "The {$field} field is required."; + case 'email': + return "The {$field} must be a valid email address."; + case 'numeric': + return "The {$field} must be a number."; + case 'array': + return "The {$field} must be an array."; + default: + if (strpos($rule, 'min:') === 0) { + $min = substr($rule, 4); + return "The {$field} must be at least {$min} characters."; + } + if (strpos($rule, 'max:') === 0) { + $max = substr($rule, 4); + return "The {$field} may not be greater than {$max} characters."; + } + if (strpos($rule, 'size:') === 0) { + $size = substr($rule, 5); + return "The {$field} must be {$size} characters."; + } + return "The {$field} field is invalid."; + } + } + + public function fails() + { + return !empty($this->errors); + } + + public function errors() + { + return $this->errors; + } + + public function first($field) + { + return $this->errors[$field][0] ?? null; + } +} \ No newline at end of file diff --git a/app/Models/Bookmark.php b/app/Models/Bookmark.php new file mode 100644 index 0000000..edb72c4 --- /dev/null +++ b/app/Models/Bookmark.php @@ -0,0 +1,50 @@ + $userId, + 'title' => $titleId, + 'timestamp' => date('Y-m-d H:i:s') + ]; + + return self::create($data); + } + + public static function removeBookmark($userId, $titleId) + { + $bookmark = self::findByUserAndTitle($userId, $titleId); + if ($bookmark) { + return self::delete($bookmark['id']); + } + return false; + } + + public static function isBookmarked($userId, $titleId) + { + return self::findByUserAndTitle($userId, $titleId) !== null; + } +} \ No newline at end of file diff --git a/app/Models/Chapter.php b/app/Models/Chapter.php new file mode 100644 index 0000000..380cf0a --- /dev/null +++ b/app/Models/Chapter.php @@ -0,0 +1,34 @@ +orderBy(['id' => 'DESC']) + ->limit($limit) + ->getQuery() + ->fetch(); + } + + public static function findByLanguage($titleId, $language) + { + return self::query() + ->where([['title', '=', $titleId], 'AND', ['language.1', '=', $language]]) + ->orderBy(['volume' => 'DESC']) + ->orderBy(['number' => 'DESC']) + ->getQuery() + ->fetch(); + } +} \ No newline at end of file diff --git a/app/Models/ReadingHistory.php b/app/Models/ReadingHistory.php new file mode 100644 index 0000000..2ec7b18 --- /dev/null +++ b/app/Models/ReadingHistory.php @@ -0,0 +1,74 @@ + $page, + 'last_read' => date('Y-m-d H:i:s') + ]); + } + + // Create new record + $data = [ + 'user' => $userId, + 'chapter' => $chapterId, + 'page' => $page, + 'last_read' => date('Y-m-d H:i:s') + ]; + + return self::create($data); + } + + public static function getRecentHistory($userId, $limit = 10) + { + return self::query() + ->where(['user', '=', $userId]) + ->orderBy(['last_read' => 'DESC']) + ->limit($limit) + ->getQuery() + ->fetch(); + } + + public static function getProgress($userId, $titleId) + { + // Get all chapters for this title + $chapters = \App\Models\Chapter::findByTitle($titleId); + + // Get reading history for this user and title's chapters + $history = []; + foreach ($chapters as $chapter) { + $record = self::findByUserAndChapter($userId, $chapter['id']); + if ($record) { + $history[] = $record; + } + } + + return [ + 'total_chapters' => count($chapters), + 'read_chapters' => count($history), + 'progress' => count($chapters) > 0 ? (count($history) / count($chapters)) * 100 : 0, + 'history' => $history + ]; + } +} \ No newline at end of file diff --git a/app/Models/Session.php b/app/Models/Session.php new file mode 100644 index 0000000..d637fb8 --- /dev/null +++ b/app/Models/Session.php @@ -0,0 +1,24 @@ + $level]); + } +} \ No newline at end of file diff --git a/app/autoload.php b/app/autoload.php new file mode 100644 index 0000000..a8e0db3 --- /dev/null +++ b/app/autoload.php @@ -0,0 +1,25 @@ +get('/', 'HomeController@index'); + +// Titles pages +$router->get('/titles', 'TitlesController@index'); +$router->get('/titles/{page}', 'TitlesController@index'); +$router->get('/title/{id}', 'TitleController@show'); + +// Chapters pages +$router->get('/chapter/{id}', 'ChapterController@show'); +$router->get('/releases', 'ReleasesController@index'); +$router->get('/releases/{page}', 'ReleasesController@index'); + +// User authentication +$router->get('/login', 'AuthController@showLogin'); +$router->post('/login', 'AuthController@login', ['csrf']); +$router->get('/signup', 'AuthController@showSignup'); +$router->post('/signup', 'AuthController@signup', ['csrf']); +$router->get('/logout', 'AuthController@logout'); +$router->get('/activate', 'AuthController@activate'); + +// API routes +$router->get('/api', 'ApiController@index'); +$router->get('/api/titles', 'Api\\ApiController@titles'); +$router->get('/api/titles/{id}', 'Api\\ApiController@title'); +$router->get('/api/titles/{id}/chapters', 'Api\\ApiController@titleChapters'); +$router->get('/api/chapters/{id}', 'Api\\ApiController@chapter'); +$router->get('/api/search', 'Api\\ApiController@search'); + +// Error routes +$router->get('/error/404', 'ErrorController@notFound'); +$router->get('/error/403', 'ErrorController@forbidden'); + +return $router; \ No newline at end of file diff --git a/autoload.php b/autoload.php index 19451e6..6434053 100644 --- a/autoload.php +++ b/autoload.php @@ -10,6 +10,9 @@ $config["debug"] == true ? error_reporting(E_ALL) && ini_set('display_errors', 1) : error_reporting(0) && ini_set('display_errors', 0); require_once "funky.php"; +// App autoloader for MVC structure +require_once "app/autoload.php"; + // SleekDB require_once ps(__DIR__ . $config["path"]["sleek"] . "/Store.php"); $db["users"] = new \SleekDB\Store("users", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]); @@ -95,3 +98,6 @@ $smarty->assign("logged", $logged); $smarty->assign("user", $user); $smarty->assign("preflang", $preflang); + +// Initialize database for models +App\Core\Model::setDatabase($db); diff --git a/config.php b/config.php index 8288454..6288bfc 100644 --- a/config.php +++ b/config.php @@ -6,7 +6,7 @@ $config["logs"] = true; $config["debug"] = true; $config["url"] = "http://localhost/fsx/public/"; -$config["email"] = "saintly@h33t.moe"; +$config["email"] = "test@example.com"; $config["activation"] = false; // Activate account through email? $config["shareAnonymousAnalytics"] = true; $config["api"] = true; // not really working rn, still please let it enabled until newer versions @@ -43,7 +43,8 @@ // Themes and Languages $config["themes"] = array( - "nucleus" => "Nucleus" + "nucleus" => "Nucleus", + "netflix" => "Netflix" ); $config["langs"] = array( "en" => "English", diff --git a/create_admin.php b/create_admin.php new file mode 100644 index 0000000..e4f6262 --- /dev/null +++ b/create_admin.php @@ -0,0 +1,35 @@ +findAll(null, 1); +if (!empty($existingUsers)) { + die("Admin user already exists!"); +} + +// Create admin user data +$username = "admin"; +$password = password_hash("admin123", PASSWORD_BCRYPT); +$email = "admin@example.com"; + +$data = array( + "username" => $username, + "password" => $password, + "email" => $email, + "avatar" => $config["default"]["avatar"], + "level" => 100, // Administrator level + "theme" => $config["default"]["theme"], + "lang" => $config["default"]["lang"], + "banned" => false, + "bannedReason" => null, + "timestamp" => now() +); + +// Insert the user +$db["users"]->insert($data); + +echo "Admin user created successfully!\n"; +echo "Username: admin\n"; +echo "Password: admin123\n"; +echo "Email: admin@example.com\n"; \ No newline at end of file diff --git a/database/activation/_cnt.sdb b/database/activation/_cnt.sdb new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/database/activation/_cnt.sdb @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/database/alertReads/_cnt.sdb b/database/alertReads/_cnt.sdb new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/database/alertReads/_cnt.sdb @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/database/alerts/_cnt.sdb b/database/alerts/_cnt.sdb new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/database/alerts/_cnt.sdb @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/database/alerts/cache/d1426e828ca5e3290a674976a6ee043b.no_lifetime.json b/database/alerts/cache/d1426e828ca5e3290a674976a6ee043b.no_lifetime.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/database/alerts/cache/d1426e828ca5e3290a674976a6ee043b.no_lifetime.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/database/chapterComments/_cnt.sdb b/database/chapterComments/_cnt.sdb new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/database/chapterComments/_cnt.sdb @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/database/chapterViews/_cnt.sdb b/database/chapterViews/_cnt.sdb new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/database/chapterViews/_cnt.sdb @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/database/chapters/_cnt.sdb b/database/chapters/_cnt.sdb new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/database/chapters/_cnt.sdb @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/database/chapters/cache/ce4ef4420ef09f9e1a13c1a7ba46022f.no_lifetime.json b/database/chapters/cache/ce4ef4420ef09f9e1a13c1a7ba46022f.no_lifetime.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/database/chapters/cache/ce4ef4420ef09f9e1a13c1a7ba46022f.no_lifetime.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/database/chapters/cache/cef09747df32485d44d903f9d476bbab.no_lifetime.json b/database/chapters/cache/cef09747df32485d44d903f9d476bbab.no_lifetime.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/database/chapters/cache/cef09747df32485d44d903f9d476bbab.no_lifetime.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/database/profileComments/_cnt.sdb b/database/profileComments/_cnt.sdb new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/database/profileComments/_cnt.sdb @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/database/profileViews/_cnt.sdb b/database/profileViews/_cnt.sdb new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/database/profileViews/_cnt.sdb @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/database/readChapters/_cnt.sdb b/database/readChapters/_cnt.sdb new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/database/readChapters/_cnt.sdb @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/database/sessions/_cnt.sdb b/database/sessions/_cnt.sdb new file mode 100644 index 0000000..e440e5c --- /dev/null +++ b/database/sessions/_cnt.sdb @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/database/titleComments/_cnt.sdb b/database/titleComments/_cnt.sdb new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/database/titleComments/_cnt.sdb @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/database/titleViews/_cnt.sdb b/database/titleViews/_cnt.sdb new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/database/titleViews/_cnt.sdb @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/database/titles/_cnt.sdb b/database/titles/_cnt.sdb new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/database/titles/_cnt.sdb @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/database/users/_cnt.sdb b/database/users/_cnt.sdb new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/database/users/_cnt.sdb @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/database/visitLogs/_cnt.sdb b/database/visitLogs/_cnt.sdb new file mode 100644 index 0000000..d8263ee --- /dev/null +++ b/database/visitLogs/_cnt.sdb @@ -0,0 +1 @@ +2 \ No newline at end of file diff --git a/database/visitLogs/cache/47878e419a85d26981bece57eab7242b.no_lifetime.json b/database/visitLogs/cache/47878e419a85d26981bece57eab7242b.no_lifetime.json new file mode 100644 index 0000000..82db654 --- /dev/null +++ b/database/visitLogs/cache/47878e419a85d26981bece57eab7242b.no_lifetime.json @@ -0,0 +1 @@ +{"ip":"::1","timestamp":"2025-08-13 18:02:09","id":1} \ No newline at end of file diff --git a/database/visitLogs/data/1.json b/database/visitLogs/data/1.json new file mode 100644 index 0000000..82db654 --- /dev/null +++ b/database/visitLogs/data/1.json @@ -0,0 +1 @@ +{"ip":"::1","timestamp":"2025-08-13 18:02:09","id":1} \ No newline at end of file diff --git a/database/visitLogs/data/2.json b/database/visitLogs/data/2.json new file mode 100644 index 0000000..5183c7f --- /dev/null +++ b/database/visitLogs/data/2.json @@ -0,0 +1 @@ +{"ip":"","timestamp":"2025-08-13 18:43:26","id":2} \ No newline at end of file diff --git a/install_app.php b/install_app.php new file mode 100644 index 0000000..cf52fbb --- /dev/null +++ b/install_app.php @@ -0,0 +1,49 @@ + "Netflix", + "author" => "Theme Developer", + "website" => "https://github.com/foolslidex/themes", + "plugins" => [ + "daisyTheme", + "logoImage", + "readChapters", + "simpleAlert", + "started", + "userLangs", + "readingMode" + ], + "version" => "1.0.0" +]; \ No newline at end of file diff --git a/library/themes/netflix/pages/403.tpl b/library/themes/netflix/pages/403.tpl new file mode 100644 index 0000000..5c9f63c --- /dev/null +++ b/library/themes/netflix/pages/403.tpl @@ -0,0 +1,16 @@ + +
+
+

403

+

Access Denied

+

Sorry, you don't have permission to access this page.

+ +
+
\ No newline at end of file diff --git a/library/themes/netflix/pages/404.tpl b/library/themes/netflix/pages/404.tpl new file mode 100644 index 0000000..2dda146 --- /dev/null +++ b/library/themes/netflix/pages/404.tpl @@ -0,0 +1,16 @@ + +
+
+

404

+

Page Not Found

+

Sorry, we couldn't find the page you're looking for.

+ +
+
\ No newline at end of file diff --git a/library/themes/netflix/pages/chapter.tpl b/library/themes/netflix/pages/chapter.tpl new file mode 100644 index 0000000..26f7115 --- /dev/null +++ b/library/themes/netflix/pages/chapter.tpl @@ -0,0 +1,102 @@ + +
+
+ + +
+
+ + + + + + + + +
+ +
+ +
+ +
+ + +
+ +
+ + +
+
+
+

{$chapter.title}

+

{$manga.title} - Chapter {$chapter.number}

+
+
+ + +
+
+
+ + + + \ No newline at end of file diff --git a/library/themes/netflix/pages/index.tpl b/library/themes/netflix/pages/index.tpl new file mode 100644 index 0000000..9919f8a --- /dev/null +++ b/library/themes/netflix/pages/index.tpl @@ -0,0 +1,124 @@ + +
+
+
+
+
+

Featured Manga Title

+

Brief description of the featured manga series that captures the reader's interest.

+
+ + +
+
+
+
+
+ + +{if $logged && !empty($continueReading)} +
+

Continue Watching

+
+ +
+
+{/if} + + +{if !empty($newReleases)} +
+

New Releases

+
+ {foreach from=$newReleases item=item key=key name=name} +
+
+ {$item.title.title} +
+ +
+
+

{$item.title.title}

+

Chapter {$item.number}

+
+ {/foreach} +
+
+{/if} + + +{if !empty($popularTitles)} +
+

Popular This Week

+
+ {foreach from=$popularTitles item=item key=key name=name} +
+
+ {$item.title} +
+ +
+
+

{$item.title}

+

{$item.chapterCount} chapters

+
+ {/foreach} +
+
+{/if} + + +{if !empty($recentlyUpdated)} +
+

Recently Updated

+
+ {foreach from=$recentlyUpdated item=item key=key name=name} +
+
+ {$item.title.title} +
+ +
+
+

{$item.title.title}

+

Updated {$item.lastUpdate}

+
+ {/foreach} +
+
+{/if} + + + + \ No newline at end of file diff --git a/library/themes/netflix/pages/releases.tpl b/library/themes/netflix/pages/releases.tpl new file mode 100644 index 0000000..d895e59 --- /dev/null +++ b/library/themes/netflix/pages/releases.tpl @@ -0,0 +1,66 @@ + +
+

Latest Releases

+

Discover the newest chapters added to our collection

+
+ + +
+ {if !empty($chapters)} +
+ {foreach from=$chapters item=chapter} +
+
+ {$chapter.title.title} +
+ +
+
+ NEW +
+
+
+

{$chapter.title.title}

+

Chapter {$chapter.number}

+
+ {$chapter.language.2} + {$chapter.language.0} +
+

{$chapter.timestamp|date_format:"M j, Y"}

+
+
+ {/foreach} +
+ + + {if $totalPages > 1} +
+
+ {if $currentPage > 1} + « + {/if} + + {for $i=1 to $totalPages} + {if $i == $currentPage} + + {else} + {$i} + {/if} + {/for} + + {if $currentPage < $totalPages} + » + {/if} +
+
+ {/if} + {else} +
+

No releases found.

+
+ {/if} +
\ No newline at end of file diff --git a/library/themes/netflix/pages/title.tpl b/library/themes/netflix/pages/title.tpl new file mode 100644 index 0000000..e7f7807 --- /dev/null +++ b/library/themes/netflix/pages/title.tpl @@ -0,0 +1,97 @@ + +
+
+
+
+
+
+
+ {$title.title} +
+
+

{$title.title}

+
+ {$title.sstatus} + {$title.releaseYear} +
+

{$title.summary}

+
+
+

Author

+

{$title.authors}

+
+
+

Artist

+

{$title.artists}

+
+
+
+ {foreach from=$title.tags.genres item=genre} + {$genre} + {/foreach} +
+ +
+
+
+
+
+
+ + +
+

Chapters

+
+ {if !empty($chapters)} + {foreach from=$chapters item=chapter} +
+
+

Chapter {$chapter.number}: {$chapter.name}

+

{$chapter.date}

+
+
+ + +
+
+ {/foreach} + {else} +

No chapters available for this title.

+ {/if} +
+
+ + +{if !empty($relatedTitles)} +
+

You Might Also Like

+
+ {foreach from=$relatedTitles item=item key=key name=name} +
+
+ {$item.title} +
+ +
+
+

{$item.title}

+
+ {/foreach} +
+
+{/if} \ No newline at end of file diff --git a/library/themes/netflix/pages/titles.tpl b/library/themes/netflix/pages/titles.tpl new file mode 100644 index 0000000..9a4daab --- /dev/null +++ b/library/themes/netflix/pages/titles.tpl @@ -0,0 +1,107 @@ + +
+

Browse Manga

+

Discover your next favorite series

+
+ + +
+
+
+
+ + +
+
+
+ + + +
+
+
+ + +
+ {if !empty($titles)} +
+ {foreach from=$titles item=title} +
+
+ {$title.title} +
+ +
+
+ + {if $title.sstatus == 1}Planned{elseif $title.sstatus == 2}Ongoing{elseif $title.sstatus == 3}Hiatus{elseif $title.sstatus == 4}Completed{elseif $title.sstatus == 5}Cancelled{/if} + +
+
+
+

{$title.title}

+

{$title.authors}

+
+ {$title.chapterCount} chapters +
+ + + + {$title.rating|number_format:1} +
+
+
+
+ {/foreach} +
+ + + {if $totalPages > 1} +
+
+ {if $currentPage > 1} + « + {/if} + + {for $i=1 to $totalPages} + {if $i == $currentPage} + + {else} + {$i} + {/if} + {/for} + + {if $currentPage < $totalPages} + » + {/if} +
+
+ {/if} + {else} +
+

No titles found matching your criteria.

+
+ {/if} +
\ No newline at end of file diff --git a/library/themes/netflix/parts/alert.tpl b/library/themes/netflix/parts/alert.tpl new file mode 100644 index 0000000..9f644dd --- /dev/null +++ b/library/themes/netflix/parts/alert.tpl @@ -0,0 +1,27 @@ +{if !empty($alert)} +
+
+
+ {if $alert.type == 'error'} + + + + {elseif $alert.type == 'success'} + + + + {elseif $alert.type == 'warning'} + + + + {/if} + {$alert.message} +
+ +
+
+{/if} \ No newline at end of file diff --git a/library/themes/netflix/parts/footer.tpl b/library/themes/netflix/parts/footer.tpl new file mode 100644 index 0000000..c1f07e8 --- /dev/null +++ b/library/themes/netflix/parts/footer.tpl @@ -0,0 +1,319 @@ + + + +{if (isset($rel) && $rel != 'tab') || !isset($rel)} + {if !$logged} + + + + + + {else} + + + {/if} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{/if} \ No newline at end of file diff --git a/library/themes/netflix/parts/header.tpl b/library/themes/netflix/parts/header.tpl new file mode 100644 index 0000000..4158e86 --- /dev/null +++ b/library/themes/netflix/parts/header.tpl @@ -0,0 +1,50 @@ +{if (isset($rel) && $rel != 'tab') || !isset($rel)} + + + + + + + {$pagetitle} + + + + + + + + + + + + + + + + + + + + + + + {include file="../parts/menu.tpl"} +{/if} \ No newline at end of file diff --git a/library/themes/netflix/parts/menu.tpl b/library/themes/netflix/parts/menu.tpl new file mode 100644 index 0000000..f30eaea --- /dev/null +++ b/library/themes/netflix/parts/menu.tpl @@ -0,0 +1,77 @@ + + + + + + + +
+{include file="../parts/alert.tpl"} \ No newline at end of file diff --git a/library/themes/netflix_design_layout.md b/library/themes/netflix_design_layout.md new file mode 100644 index 0000000..7b176e5 --- /dev/null +++ b/library/themes/netflix_design_layout.md @@ -0,0 +1,109 @@ +# Netflix-Inspired Manga Reader Design Layout + +## Homepage Structure + +```mermaid +graph TD + A[Header/Navigation] --> B[Featured Carousel] + B --> C[Continue Reading Section] + C --> D[New Releases Section] + D --> E[Popular Titles Section] + E --> F[Recently Updated Section] + F --> G[Footer] + + style A fill:#222,stroke:#fff + style B fill:#111,stroke:#e50914 + style C fill:#141414,stroke:#666 + style D fill:#141414,stroke:#666 + style E fill:#141414,stroke:#666 + style F fill:#141414,stroke:#666 + style G fill:#222,stroke:#fff +``` + +## Manga Card Design + +```mermaid +graph TD + H[Manga Card Container] --> I[Cover Image] + I --> J[Hover Overlay] + J --> K[Title] + J --> L[Rating] + J --> M[Status Badge] + J --> N[Quick Actions] + + style H fill:#333,stroke:#666 + style I fill:#444,stroke:#888 + style J fill:#00000080,stroke:#e50914 + style K fill:#e50914,stroke:#fff + style L fill:#ffd700,stroke:#000 + style M fill:#00c853,stroke:#000 + style N fill:#2962ff,stroke:#fff +``` + +## Reading Interface Modes + +```mermaid +graph TD + O[Reading Interface] --> P[Mode Selector] + P --> Q[Continuous Scrolling Mode] + P --> R[Page-by-Page Mode] + + Q --> S[Fullscreen Button] + Q --> T[Progress Tracker] + Q --> U[Navigation Arrows] + + R --> V[Page Navigation] + R --> W[Zoom Controls] + R --> X[Reading Settings] + + style O fill:#141414,stroke:#e50914 + style P fill:#222,stroke:#e50914 + style Q fill:#111,stroke:#666 + style R fill:#111,stroke:#666 + style S fill:#e50914,stroke:#fff + style T fill:#00c853,stroke:#000 + style U fill:#2962ff,stroke:#fff + style V fill:#2962ff,stroke:#fff + style W fill:#ffd700,stroke:#000 + style X fill:#7e57c2,stroke:#fff +``` + +## Color Palette + +- Primary Background: #141414 (Netflix Black) +- Secondary Background: #222222 (Dark Gray) +- Accent Color: #e50914 (Netflix Red) +- Text Primary: #ffffff (White) +- Text Secondary: #b3b3b3 (Light Gray) +- Success: #00c853 (Green) +- Warning: #ffd700 (Yellow) +- Info: #2962ff (Blue) +- Card Background: #333333 (Card Gray) + +## Typography + +- Headers: 'Helvetica Neue', Arial, sans-serif (Bold) +- Body Text: 'Helvetica Neue', Arial, sans-serif (Regular) +- Monospace: 'Courier New', monospace (For code/preformatted) + +## Spacing System + +- XS: 4px +- S: 8px +- M: 16px +- L: 24px +- XL: 32px +- XXL: 48px + +## Shadow System + +- Card Shadow: 0 4px 8px rgba(0, 0, 0, 0.3) +- Hover Shadow: 0 8px 16px rgba(0, 0, 0, 0.4) +- Focus Shadow: 0 0 0 2px #e50914 + +## Animation System + +- Hover Duration: 0.3s +- Transition Duration: 0.5s +- Carousel Transition: 1s +- Fade In: 0.7s \ No newline at end of file diff --git a/library/themes/netflix_implementation_plan.md b/library/themes/netflix_implementation_plan.md new file mode 100644 index 0000000..3b8493b --- /dev/null +++ b/library/themes/netflix_implementation_plan.md @@ -0,0 +1,507 @@ +# Netflix Theme Implementation Plan + +## 1. Theme Structure Implementation + +### info.php +```php + "Netflix", + "author" => "Your Name", + "website" => "https://yourwebsite.com/", + "plugins" => [ + "daisyTheme", + "logoImage", + "readChapters", + "simpleAlert", + "started", + "userLangs", + "readingMode" + ], + "version" => "1.0.0" +]; +``` + +## 2. Header Template (parts/header.tpl) + +Key changes from nucleus theme: +- Netflix-inspired dark theme styling +- Custom navigation with Netflix-style dropdowns +- Responsive design for mobile + +```smarty +{if (isset($rel) && $rel != 'tab') || !isset($rel)} + + + + + + + {$pagetitle} + + + + + + + + + + + + + + + + + {include file="../parts/menu.tpl"} +{/if} +``` + +## 3. Menu Template (parts/menu.tpl) + +Netflix-style navigation: +- Fixed top navigation bar +- Logo on left +- Navigation links in center +- User profile dropdown on right + +```smarty + + + + +
+{include file="../parts/alert.tpl"} +``` + +## 4. Homepage Template (pages/index.tpl) + +Key features: +- Featured carousel at top +- Multiple content sections +- Netflix-style hover effects + +```smarty + +
+
+
+
+
+

Featured Manga Title

+

Brief description of the featured manga series that captures the reader's interest.

+
+ + +
+
+
+
+
+ + +
+

Continue Watching

+
+ +
+
+ + +
+

New Releases

+
+ +
+
+ Manga Title +
+ +
+
+

Manga Title

+

Chapter 15

+
+ +
+
+ + +
+

Popular This Week

+
+ +
+
+``` + +## 5. Title Page Template (pages/title.tpl) + +Netflix-style title page: +- Large cover image with gradient overlay +- Detailed information section +- Chapter list with hover effects + +```smarty + +
+
+
+
+
+
+
+ {$title.title} +
+
+

{$title.title}

+
+ {$title.sstatus} + 2023 +
+

{$title.summary}

+
+
+

Author

+

{$title.authors}

+
+
+

Artist

+

{$title.artists}

+
+
+
+ {foreach from=$title.tags.genres item=genre} + {$genre} + {/foreach} +
+ +
+
+
+
+
+
+ + +
+

Chapters

+
+ {foreach from=$chapters item=chapter} +
+
+

Chapter {$chapter.number}: {$chapter.name}

+

{$chapter.date}

+
+
+ + +
+
+ {/foreach} +
+
+``` + +## 6. Chapter Reading Template (pages/chapter.tpl) + +Dual-mode reading interface: +- Continuous scrolling mode +- Page-by-page mode +- Mode switching controls + +```smarty + +
+
+ + +
+
+ + + + + + + + +
+ +
+ +
+ +
+ + +``` + +## 7. CSS Structure (assets/netflix/main.css) + +```css +/* Netflix Theme CSS */ + +/* Color Variables */ +:root { + --netflix-black: #141414; + --netflix-dark: #181818; + --netflix-gray: #333333; + --netflix-light-gray: #e5e5e5; + --netflix-red: #e50914; + --netflix-white: #ffffff; +} + +/* Base Styles */ +body { + background-color: var(--netflix-black); + color: var(--netflix-white); + font-family: 'Roboto', sans-serif; + margin: 0; + padding: 0; +} + +/* Navigation */ +.bg-netflix-black { + background-color: #000000; +} + +.bg-netflix-dark { + background-color: var(--netflix-dark); +} + +/* Cards */ +.netflix-card { + background-color: var(--netflix-gray); + border-radius: 4px; + overflow: hidden; + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.netflix-card:hover { + transform: scale(1.05); + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3); + z-index: 10; +} + +/* Carousel */ +.carousel-container { + scrollbar-width: none; +} + +.carousel-container::-webkit-scrollbar { + display: none; +} + +/* Buttons */ +.netflix-button { + background-color: var(--netflix-red); + color: var(--netflix-white); + border: none; + border-radius: 4px; + padding: 8px 16px; + font-weight: bold; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.netflix-button:hover { + background-color: #f40612; +} + +/* Gradients */ +.netflix-gradient { + background: linear-gradient(to right, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.1) 100%); +} + +/* Responsive */ +@media (max-width: 768px) { + .netflix-card:hover { + transform: none; + } +} +``` + +## 8. JavaScript Components + +### Carousel (assets/netflix/carousel.js) +```javascript +// Netflix-style carousel functionality +document.addEventListener('DOMContentLoaded', function() { + const carousels = document.querySelectorAll('.carousel-container'); + + carousels.forEach(carousel => { + let isDown = false; + let startX; + let scrollLeft; + + carousel.addEventListener('mousedown', (e) => { + isDown = true; + startX = e.pageX - carousel.offsetLeft; + scrollLeft = carousel.scrollLeft; + }); + + carousel.addEventListener('mouseleave', () => { + isDown = false; + }); + + carousel.addEventListener('mouseup', () => { + isDown = false; + }); + + carousel.addEventListener('mousemove', (e) => { + if (!isDown) return; + e.preventDefault(); + const x = e.pageX - carousel.offsetLeft; + const walk = (x - startX) * 2; + carousel.scrollLeft = scrollLeft - walk; + }); + }); +}); +``` + +### Reader Mode Switching (assets/netflix/reader.js) +```javascript +// Reading mode switching functionality +document.addEventListener('DOMContentLoaded', function() { + const continuousModeBtn = document.getElementById('continuousMode'); + const pageModeBtn = document.getElementById('pageMode'); + const continuousReading = document.getElementById('continuousReading'); + const pageReading = document.getElementById('pageReading'); + + // Set initial mode + continuousReading.classList.remove('hidden'); + pageModeBtn.classList.remove('bg-red-600'); + + continuousModeBtn.addEventListener('click', function() { + continuousReading.classList.remove('hidden'); + pageReading.classList.add('hidden'); + continuousModeBtn.classList.add('bg-red-600'); + pageModeBtn.classList.remove('bg-red-600'); + }); + + pageModeBtn.addEventListener('click', function() { + pageReading.classList.remove('hidden'); + continuousReading.classList.add('hidden'); + pageModeBtn.classList.add('bg-red-600'); + continuousModeBtn.classList.remove('bg-red-600'); + }); +}); +``` + +## 9. Responsive Design Considerations + +- Mobile-first approach +- Touch-friendly navigation +- Adaptive grid layouts +- Optimized image loading +- Performance-focused animations + +## 10. Performance Optimization + +- Lazy loading for images +- CSS containment for large sections +- Efficient JavaScript event handling +- Minified assets +- Caching strategies \ No newline at end of file diff --git a/library/themes/netflix_theme_plan.md b/library/themes/netflix_theme_plan.md new file mode 100644 index 0000000..3747b1c --- /dev/null +++ b/library/themes/netflix_theme_plan.md @@ -0,0 +1,120 @@ +# Netflix-Inspired Manga Reader Theme Plan + +## Theme Structure + +Based on the analysis of the existing "nucleus" theme, the Netflix-inspired theme will follow this structure: + +``` +FoOlSlideX/library/themes/netflix/ +├── info.php +├── pages/ +│ ├── 403.tpl +│ ├── 404.tpl +│ ├── chapter.tpl +│ ├── index.tpl +│ ├── releases.tpl +│ ├── title.tpl +│ └── titles.tpl +└── parts/ + ├── alert.tpl + ├── footer.tpl + ├── header.tpl + └── menu.tpl +``` + +## Key Design Elements + +### 1. Dark Theme with Netflix-inspired Styling +- Dark background (#141414) with subtle gradients +- Red accent color for highlights and buttons +- Clean typography with Netflix-style fonts +- Minimalist design with focus on content + +### 2. Homepage Layout +- Featured titles carousel at the top with large cover images +- Multiple sections: + - Continue Reading (for logged-in users) + - New Releases + - Popular Titles + - Recently Updated +- Netflix-style hover effects on all cards + +### 3. Card-based Design +- Manga titles displayed in cards with cover images +- Hover effects that scale and show additional information +- Rating badges and status indicators +- Consistent sizing and spacing + +### 4. Reading Experience +- Two reading modes: + - Netflix-style continuous scrolling + - Traditional page-by-page reader +- Mode selector in the reading interface +- Fullscreen option for immersive reading +- Progress tracking integration + +## Implementation Approach + +### 1. Create Theme Directory Structure +- Set up the directory structure as outlined above +- Create info.php with theme metadata +- Copy and modify template files from the nucleus theme + +### 2. Design System +- Define color palette +- Create CSS classes for consistent styling +- Implement responsive grid system +- Add animation and transition effects + +### 3. Homepage Implementation +- Create featured carousel component +- Implement section layouts +- Add hover effects and interactions +- Optimize for performance + +### 4. Reading Interface +- Develop both reading modes +- Create mode switching mechanism +- Implement progress tracking +- Add navigation controls + +### 5. Responsive Design +- Ensure compatibility with mobile devices +- Optimize layouts for different screen sizes +- Test touch interactions +- Implement adaptive image loading + +## Technical Considerations + +### Performance Optimization +- Lazy loading for images +- Efficient CSS with minimal overrides +- Optimized JavaScript for interactions +- Caching strategies + +### Accessibility +- Proper contrast ratios for text +- Keyboard navigation support +- Screen reader compatibility +- Focus indicators for interactive elements + +## Assets Required + +### CSS Files +- main.css (core theme styles) +- carousel.css (carousel component styles) +- reader.css (reading interface styles) + +### JavaScript Files +- carousel.js (carousel functionality) +- reader.js (reading mode switching) +- theme.js (theme-specific interactions) + +## Implementation Timeline + +1. Theme structure and basic templates (2 days) +2. Homepage design and carousel implementation (3 days) +3. Card-based layouts and hover effects (2 days) +4. Reading interface development (3 days) +5. Responsive design and testing (2 days) +6. Performance optimization and final testing (1 day) \ No newline at end of file diff --git a/library/themes/netflix_theme_summary.md b/library/themes/netflix_theme_summary.md new file mode 100644 index 0000000..0a42647 --- /dev/null +++ b/library/themes/netflix_theme_summary.md @@ -0,0 +1,189 @@ +# Netflix-Inspired Manga Reader Theme - Implementation Summary + +## Project Overview + +This project involves creating a modern, Netflix-inspired theme for the FoOlSlideX manga reader platform. The theme will feature a dark aesthetic with grid layouts, hover effects, and a featured carousel on the homepage, similar to Netflix's design language. + +## Key Features + +### 1. Visual Design +- **Dark Theme**: Netflix-style dark background (#141414) with subtle gradients +- **Red Accent Color**: For highlights, buttons, and interactive elements +- **Typography**: Clean, modern fonts with appropriate hierarchy +- **Card-based Layout**: Manga titles displayed in cards with cover images + +### 2. Homepage Layout +- **Featured Carousel**: Large, prominent carousel at the top showcasing featured titles +- **Multiple Sections**: + - Continue Reading (for logged-in users) + - New Releases + - Popular Titles + - Recently Updated +- **Netflix-style Hover Effects**: Cards that scale and show additional information on hover + +### 3. Reading Experience +- **Dual Reading Modes**: + - Netflix-style continuous scrolling + - Traditional page-by-page reader +- **Mode Switching**: Easy toggle between both reading modes +- **Immersive Interface**: Fullscreen option and minimal UI for distraction-free reading + +### 4. Responsive Design +- **Mobile Optimization**: Touch-friendly navigation and controls +- **Adaptive Layouts**: Grid layouts that adjust to different screen sizes +- **Performance Focused**: Optimized for fast loading on all devices + +## Technical Implementation + +### Directory Structure +``` +FoOlSlideX/library/themes/netflix/ +├── info.php +├── pages/ +│ ├── 403.tpl +│ ├── 404.tpl +│ ├── chapter.tpl +│ ├── index.tpl +│ ├── releases.tpl +│ ├── title.tpl +│ └── titles.tpl +└── parts/ + ├── alert.tpl + ├── footer.tpl + ├── header.tpl + └── menu.tpl +``` + +### Asset Structure +``` +FoOlSlideX/public/assets/netflix/ +├── main.css +├── carousel.css +├── reader.css +├── theme.js +├── carousel.js +└── reader.js +``` + +### Core Components + +1. **Header & Navigation** + - Fixed top navigation bar with logo + - Minimalist menu with key sections + - User profile dropdown + +2. **Homepage Carousel** + - Full-width hero section with featured content + - Gradient overlays for text readability + - Call-to-action buttons + +3. **Content Sections** + - Horizontal scrolling rows for different categories + - Card-based design with hover effects + - Consistent spacing and typography + +4. **Title Page** + - Large cover image with gradient overlay + - Detailed information section + - Chapter list with hover effects + +5. **Reading Interface** + - Mode selector for continuous vs. page-by-page + - Navigation controls + - Progress tracking + +## Design System + +### Color Palette +- Primary Background: #141414 (Netflix Black) +- Secondary Background: #181818 (Dark Gray) +- Accent Color: #e50914 (Netflix Red) +- Text Primary: #ffffff (White) +- Text Secondary: #b3b3b3 (Light Gray) + +### Typography +- Headers: 'Roboto', sans-serif (Bold) +- Body Text: 'Roboto', sans-serif (Regular) +- Font Sizes: Consistent scale with appropriate hierarchy + +### Spacing System +- XS: 4px +- S: 8px +- M: 16px +- L: 24px +- XL: 32px +- XXL: 48px + +## Implementation Steps + +### Phase 1: Setup & Structure (Days 1-2) +1. Create theme directory structure +2. Implement info.php with theme metadata +3. Set up basic template files +4. Create asset directories + +### Phase 2: Visual Design & Homepage (Days 3-5) +1. Implement Netflix-style header and navigation +2. Create featured carousel component +3. Design content sections with card layouts +4. Add hover effects and animations + +### Phase 3: Reading Interface (Days 6-8) +1. Develop continuous scrolling reading mode +2. Implement page-by-page reading mode +3. Create mode switching mechanism +4. Add navigation controls + +### Phase 4: Responsive Design & Testing (Days 9-10) +1. Optimize layouts for mobile devices +2. Test touch interactions +3. Performance optimization +4. Cross-browser testing + +### Phase 5: Final Polish (Day 11) +1. Accessibility improvements +2. Final testing and bug fixes +3. Performance optimization +4. Documentation + +## Performance Considerations + +- Lazy loading for images +- Efficient CSS with minimal overrides +- Optimized JavaScript for interactions +- Caching strategies +- Minimal HTTP requests + +## Accessibility Features + +- Proper contrast ratios for text +- Keyboard navigation support +- Screen reader compatibility +- Focus indicators for interactive elements +- Semantic HTML structure + +## Estimated Timeline + +Total Project Duration: 11 Days + +1. Theme structure and basic templates: 2 days +2. Homepage design and carousel implementation: 3 days +3. Card-based layouts and hover effects: 2 days +4. Reading interface development: 3 days +5. Responsive design and final testing: 1 day + +## Success Metrics + +- Improved user engagement with manga content +- Faster page load times compared to previous theme +- Positive feedback on visual design and usability +- Smooth reading experience with both modes functional +- Mobile responsiveness and touch interaction quality + +## Future Enhancements + +- Personalized recommendations based on reading history +- Social features (ratings, reviews, sharing) +- Dark/light mode toggle +- Customizable homepage sections +- Advanced search and filtering options \ No newline at end of file diff --git a/library/themes/nucleus/pages/403.tpl b/library/themes/nucleus/pages/403.tpl new file mode 100644 index 0000000..8ba0503 --- /dev/null +++ b/library/themes/nucleus/pages/403.tpl @@ -0,0 +1,7 @@ +
+
+

403 - Access Forbidden

+

Sorry, you don't have permission to access this page.

+ Go Home +
+
\ No newline at end of file diff --git a/library/themes/nucleus/pages/404.tpl b/library/themes/nucleus/pages/404.tpl new file mode 100644 index 0000000..a2dd55b --- /dev/null +++ b/library/themes/nucleus/pages/404.tpl @@ -0,0 +1,7 @@ +
+
+

404 - Page Not Found

+

Sorry, the page you're looking for doesn't exist.

+ Go Home +
+
\ No newline at end of file diff --git a/login_admin.php b/login_admin.php new file mode 100644 index 0000000..7c2346f --- /dev/null +++ b/login_admin.php @@ -0,0 +1,47 @@ +findOneBy(["username", "=", $username]); +if (empty($check)) { + die("User not found!"); +} + +// Verify password +if (!password_verify($password, $check["password"])) { + die("Wrong Password!"); +} + +// Check if user is banned +if ($check["banned"]) { + die("Banned! Reason: " . $check["bannedReason"]); +} + +// Create session +$token = genToken(); +$session = array( + "user" => $check["id"], + "token" => $token, + "ip" => $_SERVER["REMOTE_ADDR"] ?? "127.0.0.1" +); +$db["sessions"]->insert($session); + +// Set cookie +setcookie(cat($config["title"]) . "_session", $token, time() + 606024 * 9999, "/"); + +echo "Login successful!\n"; +echo "Session token: " . $token . "\n"; +echo "User ID: " . $check["id"] . "\n"; +echo "User level: " . $check["level"] . "\n"; + +// Verify user is admin +if ($check["level"] >= 100) { + echo "User is administrator!\n"; +} else { + echo "User is not administrator.\n"; +} \ No newline at end of file diff --git a/public/.htaccess b/public/.htaccess index 918fb45..a30af4e 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -6,5 +6,10 @@ Options -Indexes # php_value post_max_size 500M # php_value upload_max_filesize 510M -ErrorDocument 404 /fsx/public/error.php -ErrorDocument 403 /fsx/public/error.php \ No newline at end of file +# Route all requests through index.php except for existing files and directories +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.*)$ index.php [QSA,L] + +ErrorDocument 404 /index.php?route=error/404 +ErrorDocument 403 /index.php?route=error/403 \ No newline at end of file diff --git a/public/assets/netflix/README.md b/public/assets/netflix/README.md new file mode 100644 index 0000000..dfedc7c --- /dev/null +++ b/public/assets/netflix/README.md @@ -0,0 +1,24 @@ +# Netflix Theme Assets + +This directory contains assets for the Netflix-inspired theme. + +## Required Assets + +1. **Hero Placeholder Image**: + - File: `hero-placeholder.jpg` + - Location: This directory + - Size: 1920x1080 pixels recommended + - Purpose: Background image for the hero carousel on the homepage + +## Implementation Notes + +- All CSS and JavaScript files are already created +- The hero placeholder image needs to be added for the homepage carousel to display properly +- All template files are complete and ready for use + +## Theme Activation + +To activate this theme: +1. Ensure all files in this directory are properly placed +2. Add the theme to the FoOlSlideX configuration +3. Select "Netflix" as the active theme in the admin panel \ No newline at end of file diff --git a/public/assets/netflix/carousel.js b/public/assets/netflix/carousel.js new file mode 100644 index 0000000..f358276 --- /dev/null +++ b/public/assets/netflix/carousel.js @@ -0,0 +1,138 @@ +// Netflix-style carousel functionality + +document.addEventListener('DOMContentLoaded', function() { + const carousels = document.querySelectorAll('.carousel-container'); + + carousels.forEach(carousel => { + let isDown = false; + let startX; + let scrollLeft; + + // Mouse events for desktop + carousel.addEventListener('mousedown', (e) => { + isDown = true; + carousel.classList.add('active'); + startX = e.pageX - carousel.offsetLeft; + scrollLeft = carousel.scrollLeft; + }); + + carousel.addEventListener('mouseleave', () => { + isDown = false; + carousel.classList.remove('active'); + }); + + carousel.addEventListener('mouseup', () => { + isDown = false; + carousel.classList.remove('active'); + }); + + carousel.addEventListener('mousemove', (e) => { + if (!isDown) return; + e.preventDefault(); + const x = e.pageX - carousel.offsetLeft; + const walk = (x - startX) * 2; + carousel.scrollLeft = scrollLeft - walk; + }); + + // Touch events for mobile + let startXTouch, startYTouch; + + carousel.addEventListener('touchstart', (e) => { + const touch = e.touches[0]; + startXTouch = touch.clientX; + startYTouch = touch.clientY; + isDown = true; + }); + + carousel.addEventListener('touchmove', (e) => { + if (!isDown) return; + e.preventDefault(); + const touch = e.touches[0]; + const x = touch.clientX; + const walk = (x - startXTouch) * 2; + carousel.scrollLeft = carousel.scrollLeft - walk; + }); + + carousel.addEventListener('touchend', () => { + isDown = false; + }); + }); + + // Initialize arrow navigation for carousels + initializeCarouselArrows(); +}); + +// Initialize arrow navigation for carousels +function initializeCarouselArrows() { + const nextButtons = document.querySelectorAll('.carousel-next'); + const prevButtons = document.querySelectorAll('.carousel-prev'); + + nextButtons.forEach(button => { + button.addEventListener('click', function() { + const carousel = this.parentElement.querySelector('.carousel-container'); + if (carousel) { + carousel.scrollBy({ left: 300, behavior: 'smooth' }); + } + }); + }); + + prevButtons.forEach(button => { + button.addEventListener('click', function() { + const carousel = this.parentElement.querySelector('.carousel-container'); + if (carousel) { + carousel.scrollBy({ left: -300, behavior: 'smooth' }); + } + }); + }); +} + +// Function to create a new carousel +function createCarousel(containerId, items) { + const container = document.getElementById(containerId); + if (!container) return; + + let carouselHTML = ` +
+ + + +
+ `; + + container.innerHTML = carouselHTML; + + // Reinitialize carousel functionality + initializeCarouselArrows(); +} + +// Export functions +window.createCarousel = createCarousel; \ No newline at end of file diff --git a/public/assets/netflix/main.css b/public/assets/netflix/main.css new file mode 100644 index 0000000..13e216a --- /dev/null +++ b/public/assets/netflix/main.css @@ -0,0 +1,282 @@ +/* Netflix Theme CSS */ + +/* Color Variables */ +:root { + --netflix-black: #141414; + --netflix-dark: #181818; + --netflix-gray: #333333; + --netflix-light-gray: #e5e5e5; + --netflix-red: #e50914; + --netflix-white: #ffffff; +} + +/* Base Styles */ +body { + background-color: var(--netflix-black); + color: var(--netflix-white); + font-family: 'Roboto', sans-serif; + margin: 0; + padding: 0; + transition: background-color 0.3s ease; +} + +/* Navigation */ +.bg-netflix-black { + background-color: #000000; +} + +.bg-netflix-dark { + background-color: var(--netflix-dark); +} + +.bg-netflix-gray { + background-color: var(--netflix-gray); +} + +/* Cards */ +.netflix-card { + background-color: var(--netflix-gray); + border-radius: 4px; + overflow: hidden; + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.netflix-card:hover { + transform: scale(1.05); + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3); + z-index: 10; +} + +/* Carousel */ +.carousel-container { + scrollbar-width: none; + scroll-behavior: smooth; +} + +.carousel-container::-webkit-scrollbar { + display: none; +} + +.carousel-container.active { + cursor: grabbing; +} + +/* Buttons */ +.netflix-button { + background-color: var(--netflix-red); + color: var(--netflix-white); + border: none; + border-radius: 4px; + padding: 8px 16px; + font-weight: bold; + cursor: pointer; + transition: background-color 0.3s ease, transform 0.2s ease; +} + +.netflix-button:hover { + background-color: #f40612; + transform: translateY(-2px); +} + +.netflix-button:active { + transform: translateY(0); +} + +/* Gradients */ +.netflix-gradient { + background: linear-gradient(to right, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.1) 100%); +} + +/* Hover Effects */ +.group:hover .opacity-0 { + opacity: 1 !important; +} + +.group:hover .bg-opacity-0 { + background-color: rgba(0, 0, 0, 0.7) !important; +} + +/* Transitions */ +.transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition { + transition: all 0.3s ease; +} + +.transition-opacity { + transition: opacity 0.3s ease; +} + +.transition-transform { + transition: transform 0.3s ease; +} + +.duration-300 { + transition-duration: 300ms; +} + +.ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.fade-in { + animation: fadeIn 0.5s ease-in-out; +} + +@keyframes slideInUp { + from { + transform: translateY(20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +.slide-in-up { + animation: slideInUp 0.5s ease-out; +} + +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +.pulse { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +/* Reading Interface */ +#continuousReading img, +#pageReading img { + transition: filter 0.3s ease; +} + +#continuousReading img:hover, +#pageReading img:hover { + filter: brightness(1.05); +} + +/* Scrollbar */ +.scrollbar-hide::-webkit-scrollbar { + display: none; +} + +.scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; +} + +/* Focus Styles */ +.focus\:outline-none:focus { + outline: none; +} + +.focus\:ring-2:focus { + outline: none; + box-shadow: 0 0 0 2px var(--netflix-red); +} + +/* Responsive */ +@media (max-width: 768px) { + .netflix-card:hover { + transform: none; + } + + .text-3xl { + font-size: 1.5rem; + } + + .text-5xl { + font-size: 2rem; + } + + .h-screen { + height: 60vh; + } +} + +/* Loading Animation */ +.loading-spinner { + border: 4px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top: 4px solid var(--netflix-red); + width: 40px; + height: 40px; + animation: spin 1s linear infinite; + margin: 0 auto; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Modal Styles */ +.modal-box { + background-color: var(--netflix-dark); + border: 1px solid var(--netflix-gray); +} + +/* Input Styles */ +.input { + background-color: var(--netflix-gray); + border: 1px solid #444; + color: var(--netflix-white); +} + +.input:focus { + border-color: var(--netflix-red); + box-shadow: 0 0 0 1px var(--netflix-red); +} + +/* Select Styles */ +.select { + background-color: var(--netflix-gray); + border: 1px solid #444; + color: var(--netflix-white); +} + +.select:focus { + border-color: var(--netflix-red); + box-shadow: 0 0 0 1px var(--netflix-red); +} + +/* Pagination */ +.join-item.btn { + background-color: var(--netflix-gray); + border: 1px solid #444; + color: var(--netflix-white); +} + +.join-item.btn:hover { + background-color: #444; +} + +.join-item.btn.bg-red-600 { + background-color: var(--netflix-red); + border-color: var(--netflix-red); +} + +.join-item.btn.bg-red-600:hover { + background-color: #f40612; +} \ No newline at end of file diff --git a/public/assets/netflix/reader.js b/public/assets/netflix/reader.js new file mode 100644 index 0000000..c30f13b --- /dev/null +++ b/public/assets/netflix/reader.js @@ -0,0 +1,189 @@ +// Netflix-style reading interface functionality + +document.addEventListener('DOMContentLoaded', function() { + // Initialize reading mode functionality + initializeReadingModes(); + + // Initialize navigation controls + initializeNavigationControls(); + + // Initialize fullscreen functionality + initializeFullscreen(); +}); + +// Initialize reading modes (continuous vs page-by-page) +function initializeReadingModes() { + const continuousModeBtn = document.getElementById('continuousMode'); + const pageModeBtn = document.getElementById('pageMode'); + const continuousReading = document.getElementById('continuousReading'); + const pageReading = document.getElementById('pageReading'); + + // If elements exist, set up mode switching + if (continuousModeBtn && pageModeBtn && continuousReading && pageReading) { + // Set initial mode (continuous by default) + continuousReading.classList.remove('hidden'); + pageModeBtn.classList.remove('bg-red-600'); + + // Continuous mode button event + continuousModeBtn.addEventListener('click', function() { + continuousReading.classList.remove('hidden'); + pageReading.classList.add('hidden'); + continuousModeBtn.classList.add('bg-red-600'); + pageModeBtn.classList.remove('bg-red-600'); + + // Save preference to localStorage + localStorage.setItem('readingMode', 'continuous'); + }); + + // Page mode button event + pageModeBtn.addEventListener('click', function() { + pageReading.classList.remove('hidden'); + continuousReading.classList.add('hidden'); + pageModeBtn.classList.add('bg-red-600'); + continuousModeBtn.classList.remove('bg-red-600'); + + // Save preference to localStorage + localStorage.setItem('readingMode', 'page'); + }); + + // Load saved preference + const savedMode = localStorage.getItem('readingMode'); + if (savedMode === 'page') { + pageModeBtn.click(); + } + } +} + +// Initialize navigation controls +function initializeNavigationControls() { + // Previous/Next chapter buttons + const prevChapterBtn = document.getElementById('prevChapter'); + const nextChapterBtn = document.getElementById('nextChapter'); + + if (prevChapterBtn) { + prevChapterBtn.addEventListener('click', function() { + // Implement previous chapter navigation + console.log('Previous chapter clicked'); + // This would typically navigate to the previous chapter + }); + } + + if (nextChapterBtn) { + nextChapterBtn.addEventListener('click', function() { + // Implement next chapter navigation + console.log('Next chapter clicked'); + // This would typically navigate to the next chapter + }); + } + + // Page navigation (for page-by-page mode) + const prevPageBtn = document.getElementById('prevPage'); + const nextPageBtn = document.getElementById('nextPage'); + + if (prevPageBtn) { + prevPageBtn.addEventListener('click', function() { + // Implement previous page navigation + console.log('Previous page clicked'); + // This would typically show the previous page + }); + } + + if (nextPageBtn) { + nextPageBtn.addEventListener('click', function() { + // Implement next page navigation + console.log('Next page clicked'); + // This would typically show the next page + }); + } +} + +// Initialize fullscreen functionality +function initializeFullscreen() { + const fullscreenBtn = document.getElementById('fullscreenBtn'); + + if (fullscreenBtn) { + fullscreenBtn.addEventListener('click', function() { + toggleFullscreen(); + }); + } +} + +// Toggle fullscreen mode +function toggleFullscreen() { + const elem = document.documentElement; + + if (!document.fullscreenElement) { + if (elem.requestFullscreen) { + elem.requestFullscreen(); + } else if (elem.mozRequestFullScreen) { // Firefox + elem.mozRequestFullScreen(); + } else if (elem.webkitRequestFullscreen) { // Chrome, Safari and Opera + elem.webkitRequestFullscreen(); + } else if (elem.msRequestFullscreen) { // IE/Edge + elem.msRequestFullscreen(); + } + } else { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.mozCancelFullScreen) { // Firefox + document.mozCancelFullScreen(); + } else if (document.webkitExitFullscreen) { // Chrome, Safari and Opera + document.webkitExitFullscreen(); + } else if (document.msExitFullscreen) { // IE/Edge + document.msExitFullscreen(); + } + } +} + +// Keyboard navigation +document.addEventListener('keydown', function(e) { + // Left arrow - Previous page/chapter + if (e.keyCode === 37) { + const prevPageBtn = document.getElementById('prevPage'); + const prevChapterBtn = document.getElementById('prevChapter'); + + if (prevPageBtn && !prevPageBtn.classList.contains('hidden')) { + prevPageBtn.click(); + } else if (prevChapterBtn) { + prevChapterBtn.click(); + } + } + + // Right arrow - Next page/chapter + if (e.keyCode === 39) { + const nextPageBtn = document.getElementById('nextPage'); + const nextChapterBtn = document.getElementById('nextChapter'); + + if (nextPageBtn && !nextPageBtn.classList.contains('hidden')) { + nextPageBtn.click(); + } else if (nextChapterBtn) { + nextChapterBtn.click(); + } + } + + // F key - Toggle fullscreen + if (e.keyCode === 70) { + toggleFullscreen(); + } +}); + +// Function to update page number display +function updatePageNumber(current, total) { + const pageDisplay = document.getElementById('pageDisplay'); + if (pageDisplay) { + pageDisplay.textContent = `Page ${current} of ${total}`; + } +} + +// Function to preload images for smoother reading experience +function preloadImages(imageUrls) { + imageUrls.forEach(url => { + const img = new Image(); + img.src = url; + }); +} + +// Export functions +window.toggleFullscreen = toggleFullscreen; +window.updatePageNumber = updatePageNumber; +window.preloadImages = preloadImages; \ No newline at end of file diff --git a/public/assets/netflix/theme.js b/public/assets/netflix/theme.js new file mode 100644 index 0000000..e3fa8fe --- /dev/null +++ b/public/assets/netflix/theme.js @@ -0,0 +1,70 @@ +// Netflix Theme JavaScript + +// Initialize theme when DOM is loaded +document.addEventListener('DOMContentLoaded', function() { + // Apply Netflix theme classes to body + document.body.classList.add('netflix-theme'); + + // Initialize any theme-specific components + initializeThemeComponents(); +}); + +// Initialize theme components +function initializeThemeComponents() { + // Initialize mobile menu toggle + initializeMobileMenu(); + + // Initialize search functionality + initializeSearch(); + + // Initialize any other theme-specific components + initializeThemeSpecificComponents(); +} + +// Mobile menu toggle functionality +function initializeMobileMenu() { + const mobileMenuButton = document.querySelector('.md\\:hidden button'); + const mobileMenu = document.querySelector('.md\\:hidden + div'); + + if (mobileMenuButton && mobileMenu) { + mobileMenuButton.addEventListener('click', function() { + mobileMenu.classList.toggle('hidden'); + }); + } +} + +// Search functionality +function initializeSearch() { + const searchButton = document.querySelector('.relative button'); + + if (searchButton) { + searchButton.addEventListener('click', function() { + // Implement search functionality + console.log('Search button clicked'); + }); + } +} + +// Theme-specific components initialization +function initializeThemeSpecificComponents() { + // Add any theme-specific initialization here + console.log('Netflix theme components initialized'); +} + +// Utility function to toggle classes +function toggleClass(element, className) { + if (element.classList.contains(className)) { + element.classList.remove(className); + } else { + element.classList.add(className); + } +} + +// Utility function to set cookie (for theme preferences) +function setThemeCookie(name, value) { + document.cookie = name + "=" + value + "; path=/"; +} + +// Export functions for use in other scripts +window.toggleClass = toggleClass; +window.setThemeCookie = setThemeCookie; \ No newline at end of file diff --git a/public/debug.php b/public/debug.php new file mode 100644 index 0000000..1798932 --- /dev/null +++ b/public/debug.php @@ -0,0 +1,155 @@ +FoOlSlideX Debug Information\n"; + +// Check if .installed file exists +echo "

1. Installation Check

\n"; +if (file_exists(__DIR__ . "/../.installed")) { + echo "✓ .installed file exists
\n"; + echo "Installation date: " . file_get_contents(__DIR__ . "/../.installed") . "
\n"; +} else { + echo "✗ .installed file missing - application not installed
\n"; +} + +// Check autoload.php +echo "

2. Autoload Check

\n"; +if (file_exists(__DIR__ . "/../autoload.php")) { + echo "✓ autoload.php exists
\n"; + try { + require_once __DIR__ . "/../autoload.php"; + echo "✓ autoload.php loaded successfully
\n"; + + // Check if global variables are set + echo "

Global Variables Check:

\n"; + echo "config: " . (isset($config) ? "✓ Set" : "✗ Missing") . "
\n"; + echo "db: " . (isset($db) ? "✓ Set" : "✗ Missing") . "
\n"; + echo "smarty: " . (isset($smarty) ? "✓ Set" : "✗ Missing") . "
\n"; + echo "parsedown: " . (isset($parsedown) ? "✓ Set" : "✗ Missing") . "
\n"; + echo "purifier: " . (isset($purifier) ? "✓ Set" : "✗ Missing") . "
\n"; + echo "logged: " . (isset($logged) ? "✓ Set" : "✗ Missing") . "
\n"; + echo "user: " . (isset($user) ? "✓ Set" : "✗ Missing") . "
\n"; + echo "usertheme: " . (isset($usertheme) ? "✓ Set ($usertheme)" : "✗ Missing") . "
\n"; + echo "userlang: " . (isset($userlang) ? "✓ Set ($userlang)" : "✗ Missing") . "
\n"; + + } catch (Exception $e) { + echo "✗ Error loading autoload.php: " . $e->getMessage() . "
\n"; + echo "Stack trace:
" . $e->getTraceAsString() . "
\n"; + } catch (Error $e) { + echo "✗ Fatal error loading autoload.php: " . $e->getMessage() . "
\n"; + echo "Stack trace:
" . $e->getTraceAsString() . "
\n"; + } +} else { + echo "✗ autoload.php missing
\n"; +} + +// Check app structure +echo "

3. App Structure Check

\n"; +$requiredFiles = [ + '../app/routes.php', + '../app/autoload.php', + '../app/Core/Router.php', + '../app/Controllers/HomeController.php', + '../app/Core/Controller.php' +]; + +foreach ($requiredFiles as $file) { + if (file_exists(__DIR__ . '/' . $file)) { + echo "✓ $file exists
\n"; + } else { + echo "✗ $file missing
\n"; + } +} + +// Check template directories +echo "

4. Template Check

\n"; +if (isset($config)) { + $templateDir = __DIR__ . '/..' . $config["smarty"]["template"]; + echo "Template directory: $templateDir
\n"; + if (is_dir($templateDir)) { + echo "✓ Template directory exists
\n"; + + if (isset($usertheme)) { + $themeDir = $templateDir . '/' . $usertheme; + echo "Theme directory: $themeDir
\n"; + if (is_dir($themeDir)) { + echo "✓ Theme directory exists
\n"; + + $requiredTemplates = [ + 'parts/header.tpl', + 'parts/footer.tpl', + 'pages/index.tpl' + ]; + + foreach ($requiredTemplates as $template) { + $templatePath = $themeDir . '/' . $template; + if (file_exists($templatePath)) { + echo "✓ $template exists
\n"; + } else { + echo "✗ $template missing
\n"; + } + } + } else { + echo "✗ Theme directory missing
\n"; + } + } + } else { + echo "✗ Template directory missing
\n"; + } +} + +// Test router +echo "

5. Router Test

\n"; +try { + if (file_exists(__DIR__ . "/../app/routes.php")) { + $router = require __DIR__ . "/../app/routes.php"; + echo "✓ Router loaded successfully
\n"; + echo "Router class: " . get_class($router) . "
\n"; + } else { + echo "✗ routes.php missing
\n"; + } +} catch (Exception $e) { + echo "✗ Error loading router: " . $e->getMessage() . "
\n"; +} + +// Test HomeController instantiation +echo "

6. HomeController Test

\n"; +try { + if (class_exists('App\\Controllers\\HomeController')) { + echo "✓ HomeController class exists
\n"; + + // Try to instantiate + $controller = new App\Controllers\HomeController(); + echo "✓ HomeController instantiated successfully
\n"; + + } else { + echo "✗ HomeController class not found
\n"; + } +} catch (Exception $e) { + echo "✗ Error instantiating HomeController: " . $e->getMessage() . "
\n"; + echo "Stack trace:
" . $e->getTraceAsString() . "
\n"; +} catch (Error $e) { + echo "✗ Fatal error instantiating HomeController: " . $e->getMessage() . "
\n"; + echo "Stack trace:
" . $e->getTraceAsString() . "
\n"; +} + +echo "

7. PHP Info

\n"; +echo "PHP Version: " . phpversion() . "
\n"; +echo "Memory Limit: " . ini_get('memory_limit') . "
\n"; +echo "Max Execution Time: " . ini_get('max_execution_time') . "
\n"; + +echo "

8. Error Log

\n"; +$errorLogPath = __DIR__ . '/../debug.log'; +if (file_exists($errorLogPath)) { + echo "Error log contents:
\n"; + echo "
" . htmlspecialchars(file_get_contents($errorLogPath)) . "
\n"; +} else { + echo "No error log found
\n"; +} + +echo "

Debug completed at " . date('Y-m-d H:i:s') . "

\n"; +?> \ No newline at end of file diff --git a/public/debug_releases.php b/public/debug_releases.php new file mode 100644 index 0000000..664b6b0 --- /dev/null +++ b/public/debug_releases.php @@ -0,0 +1,72 @@ +count(); + echo " ✓ Chapter count: " . $count . "\n"; + + echo "5. Testing pagination variables...\n"; + $page = 1; + $limit = $config["perpage"]["chapters"] ?? 10; + $skip = ($page - 1) * $limit; + echo " ✓ Page: $page, Limit: $limit, Skip: $skip\n"; + + echo "6. Testing chapter query...\n"; + $chapters = $db["chapters"]->createQueryBuilder() + ->orderBy(["id" => "DESC"]) + ->limit($limit) + ->skip($skip) + ->getQuery() + ->fetch(); + echo " ✓ Found " . count($chapters) . " chapters\n"; + + echo "7. Testing Smarty initialization...\n"; + if (!isset($smarty)) { + throw new Exception("Smarty not initialized"); + } + echo " ✓ Smarty initialized\n"; + + echo "8. Memory usage: " . memory_get_usage(true) . " bytes\n"; + echo "9. Peak memory: " . memory_get_peak_usage(true) . " bytes\n"; + + echo "\n=== DEBUG COMPLETED SUCCESSFULLY ===\n"; + +} catch (Exception $e) { + echo "ERROR: " . $e->getMessage() . "\n"; + echo "Stack trace:\n" . $e->getTraceAsString() . "\n"; +} catch (Error $e) { + echo "FATAL ERROR: " . $e->getMessage() . "\n"; + echo "Stack trace:\n" . $e->getTraceAsString() . "\n"; +} + +echo "\nScript execution completed at: " . date('Y-m-d H:i:s') . "\n"; +?> \ No newline at end of file diff --git a/public/debug_trace.php b/public/debug_trace.php new file mode 100644 index 0000000..5bd8cd7 --- /dev/null +++ b/public/debug_trace.php @@ -0,0 +1,141 @@ +getMessage() . "\n"; + exit; +} + +// Check funky.php +if (!file_exists(__DIR__ . "/../funky.php")) { + echo "ERROR: funky.php not found!\n"; + exit; +} +echo "5. funky.php exists ✓\n"; + +try { + require_once __DIR__ . "/../funky.php"; + echo "6. funky.php loaded ✓\n"; +} catch (Exception $e) { + echo "ERROR loading funky.php: " . $e->getMessage() . "\n"; + exit; +} + +// Check app autoload +if (!file_exists(__DIR__ . "/../app/autoload.php")) { + echo "ERROR: app/autoload.php not found!\n"; + exit; +} +echo "7. app/autoload.php exists ✓\n"; + +try { + require_once __DIR__ . "/../app/autoload.php"; + echo "8. app/autoload.php loaded ✓\n"; +} catch (Exception $e) { + echo "ERROR loading app/autoload.php: " . $e->getMessage() . "\n"; + exit; +} + +// Check SleekDB +$sleekPath = __DIR__ . "/../" . $config["path"]["sleek"] . "/Store.php"; +if (!file_exists($sleekPath)) { + echo "ERROR: SleekDB Store.php not found at: $sleekPath\n"; + exit; +} +echo "9. SleekDB Store.php exists ✓\n"; + +try { + require_once $sleekPath; + echo "10. SleekDB Store.php loaded ✓\n"; +} catch (Exception $e) { + echo "ERROR loading SleekDB: " . $e->getMessage() . "\n"; + exit; +} + +// Test database initialization +try { + $testDb = new \SleekDB\Store("users", __DIR__ . "/../" . $config["db"]["sleek"]["dir"], $config["db"]["sleek"]["config"]); + echo "11. Database connection test ✓\n"; +} catch (Exception $e) { + echo "ERROR with database: " . $e->getMessage() . "\n"; + exit; +} + +// Check session.php +if (!file_exists(__DIR__ . "/../session.php")) { + echo "ERROR: session.php not found!\n"; + exit; +} +echo "12. session.php exists ✓\n"; + +// Check theme files +$themePath = __DIR__ . "/../" . $config["smarty"]["template"] . "/" . $config["default"]["theme"] . "/info.php"; +if (!file_exists($themePath)) { + echo "ERROR: Theme info.php not found at: $themePath\n"; + exit; +} +echo "13. Theme info.php exists ✓\n"; + +// Check language files +$langPath = __DIR__ . "/../" . $config["path"]["langs"] . "/" . $config["default"]["lang"] . ".php"; +if (!file_exists($langPath)) { + echo "ERROR: Language file not found at: $langPath\n"; + exit; +} +echo "14. Language file exists ✓\n"; + +// Check routes +if (!file_exists(__DIR__ . "/../app/routes.php")) { + echo "ERROR: app/routes.php not found!\n"; + exit; +} +echo "15. app/routes.php exists ✓\n"; + +try { + $router = require __DIR__ . "/../app/routes.php"; + echo "16. Routes loaded ✓\n"; +} catch (Exception $e) { + echo "ERROR loading routes: " . $e->getMessage() . "\n"; + exit; +} + +echo "\n=== All basic components check passed! ===\n"; +echo "The issue might be in the full autoload.php execution.\n"; +echo "Let's test the full autoload...\n\n"; + +try { + require_once __DIR__ . "/../autoload.php"; + echo "17. Full autoload.php completed ✓\n"; + echo "\n=== SUCCESS: All components loaded successfully! ===\n"; + echo "If you see this message, the autoload works fine.\n"; + echo "The issue might be in the routing or controller execution.\n"; +} catch (Exception $e) { + echo "ERROR in full autoload: " . $e->getMessage() . "\n"; + echo "Stack trace:\n" . $e->getTraceAsString() . "\n"; +} catch (Error $e) { + echo "FATAL ERROR in autoload: " . $e->getMessage() . "\n"; + echo "Stack trace:\n" . $e->getTraceAsString() . "\n"; +} \ No newline at end of file diff --git a/public/index.php b/public/index.php index f8b1525..c447e5b 100644 --- a/public/index.php +++ b/public/index.php @@ -2,73 +2,22 @@ require "../autoload.php"; -// Popular Titles -// $views = array(); -// $_popularTitles = $db["titleViews"]->createQueryBuilder() -// ->getQuery() -// ->fetch(); -// $titleCount = 0; -// $titleCountArray = array(); -// foreach ($_popularTitles as $key => $pop) { -// // 10 is the amount of max titles -// $ago = timeAgo($pop["timestamp"]); -// if (str_contains($ago, "second") || str_contains($ago, "min") || str_contains($ago, "hour") || str_contains($ago, "day")) { -// $views[$pop["title"]["id"]]["views"] = 0; -// $views[$pop["title"]["id"]]["title"] = array(); -// $popularTitles[$pop["title"]["id"]] = array(); -// if ($titleCount <= 4 && !in_array($pop["title"]["id"], $titleCountArray)) { -// $titleCount++; -// array_push($titleCountArray, $pop["title"]["id"]); -// } -// } -// } -// foreach ($_popularTitles as $key => $pop) { -// $ago = timeAgo($pop["timestamp"]); -// if (str_contains($ago, "second") || str_contains($ago, "min") || str_contains($ago, "hour") || str_contains($ago, "day")) { -// $views[$pop["title"]["id"]]["views"]++; -// if (empty($views[$pop["title"]["id"]]["title"])) { -// $views[$pop["title"]["id"]]["title"] = $db["titles"]->findById($pop["title"]["id"]); -// $views[$pop["title"]["id"]]["title"]["summary1"] = shorten($parsedown->text($purifier->purify($views[$pop["title"]["id"]]["title"]["summary"])), 200); -// $views[$pop["title"]["id"]]["title"]["summary2"] = shorten($parsedown->text($purifier->purify($views[$pop["title"]["id"]]["title"]["summary"])), 200); -// } -// } -// } -// sort($views); -// $views = array_reverse($views); -// /Popular Titles -// $smarty->assign("popularTitles", $views); - -// Recently Updated Titles -$recentlyUpdated = $db["chapters"]->createQueryBuilder() - ->orderBy(["id" => "DESC"]) - ->distinct(["title.id"]) - ->getQuery() - ->fetch(); - -foreach ($recentlyUpdated as $key => $rec) { - $title = $db["titles"]->findById($rec["title"]); - $recentlyUpdated[$key]["title"] = $title; - $recentlyUpdated[$key]["title"]["summary1"] = shorten($parsedown->text($purifier->purify($title["summary"])), 400); - $recentlyUpdated[$key]["title"]["summary2"] = shorten($parsedown->text($purifier->purify($title["summary"])), 100); +use App\Core\Router; + +// Get the router instance +$router = require "../app/routes.php"; + +// Get the URI and HTTP method +$uri = $_SERVER['REQUEST_URI']; +$method = $_SERVER['REQUEST_METHOD']; + +// Resolve the route +try { + $router->resolve($uri, $method); +} catch (Exception $e) { + // Handle routing errors + if (!headers_sent()) { + http_response_code(500); + } + echo "Internal Server Error: " . $e->getMessage(); } - -$chapters = $db["chapters"]->createQueryBuilder() - ->orderBy(["id" => "DESC"]) - ->limit($config["perpage"]["chapters"]) - ->getQuery() - ->fetch(); - -foreach ($chapters as $key => $ch) { - $title = $db["titles"]->findById($ch["title"]); - $uploader = $db["users"]->findById($ch["user"]); - $chapters[$key]["title"] = $title; - $chapters[$key]["user"] = $uploader; -} - -$smarty->assign("chapters", $chapters); -$smarty->assign("recentlyUpdated", $recentlyUpdated); -$smarty->assign("pagetitle", $config["title"] . " " . $config["divider"] . " " . $config["slogan"]); - -$smarty->display("parts/header.tpl"); -$smarty->display("pages/index.tpl"); -$smarty->display("parts/footer.tpl"); diff --git a/public/releases.php b/public/releases.php index 9b4be1c..def39bd 100644 --- a/public/releases.php +++ b/public/releases.php @@ -22,7 +22,8 @@ } $pagis = array(); -$totalPages = $db["chapters"]->count() / $config["perpage"]["chapters"]; +$totalChapters = $db["chapters"]->count(); +$totalPages = $totalChapters > 0 ? ceil($totalChapters / $config["perpage"]["chapters"]) : 1; for ($i = 0; $i < $totalPages; $i++) { array_push($pagis, $i + 1); } diff --git a/public/router.php b/public/router.php new file mode 100644 index 0000000..25c00a6 --- /dev/null +++ b/public/router.php @@ -0,0 +1,15 @@ +useCache = true; if((!is_int($lifetime) || $lifetime < 0) && !is_null($lifetime)){ @@ -374,7 +374,7 @@ public function getQuery(): Query * @param bool $allowEmpty * @return QueryBuilder */ - public function groupBy(array $groupByFields, string $countKeyName = null, bool $allowEmpty = false): QueryBuilder + public function groupBy(array $groupByFields, ?string $countKeyName = null, bool $allowEmpty = false): QueryBuilder { $this->groupBy = [ "groupByFields" => $groupByFields, diff --git a/software/SleekDB/Store.php b/software/SleekDB/Store.php index f85960a..a0cf3a5 100644 --- a/software/SleekDB/Store.php +++ b/software/SleekDB/Store.php @@ -93,7 +93,7 @@ public function __construct(string $storeName, string $databasePath, array $conf * @throws InvalidArgumentException * @throws InvalidConfigurationException */ - public function changeStore(string $storeName, string $databasePath = null, array $configuration = []): Store + public function changeStore(string $storeName, ?string $databasePath = null, array $configuration = []): Store { if(empty($databasePath)){ $databasePath = $this->getDatabasePath(); @@ -219,7 +219,7 @@ public function getStorePath(): string * @throws IOException * @throws InvalidArgumentException */ - public function findAll(array $orderBy = null, int $limit = null, int $offset = null): array + public function findAll(?array $orderBy = null, ?int $limit = null, ?int $offset = null): array { $qb = $this->createQueryBuilder(); if(!is_null($orderBy)){ @@ -265,7 +265,7 @@ public function findById($id){ * @throws IOException * @throws InvalidArgumentException */ - public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array { $qb = $this->createQueryBuilder(); @@ -582,7 +582,7 @@ public function removeFieldsById($id, array $fieldsToRemove) * @throws IOException * @throws InvalidArgumentException */ - public function search(array $fields, string $query, array $orderBy = null, int $limit = null, int $offset = null): array + public function search(array $fields, string $query, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array { $qb = $this->createQueryBuilder(); diff --git a/software/Smarty/Smarty.class.php b/software/Smarty/Smarty.class.php index 5d2e3a4..e4c0404 100644 --- a/software/Smarty/Smarty.class.php +++ b/software/Smarty/Smarty.class.php @@ -1050,7 +1050,7 @@ public function _getTemplateId( $cache_id = null, $compile_id = null, $caching = null, - Smarty_Internal_Template $template = null + ?Smarty_Internal_Template $template = null ) { $template_name = (strpos($template_name, ':') === false) ? "{$this->default_resource_type}:{$template_name}" : $template_name; diff --git a/software/Smarty/compile/1c9f8753e03722dffb39969f63c3fd6e012dd1ca_0.file.titles.tpl.php b/software/Smarty/compile/1c9f8753e03722dffb39969f63c3fd6e012dd1ca_0.file.titles.tpl.php new file mode 100644 index 0000000..1b0b455 --- /dev/null +++ b/software/Smarty/compile/1c9f8753e03722dffb39969f63c3fd6e012dd1ca_0.file.titles.tpl.php @@ -0,0 +1,329 @@ +_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689e1d5c823e18_04232286', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + '1c9f8753e03722dffb39969f63c3fd6e012dd1ca' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\nucleus\\pages\\titles.tpl', + 1 => 1754941256, + 2 => 'file', + ), + ), + 'includes' => + array ( + ), +),false)) { +function content_689e1d5c823e18_04232286 (Smarty_Internal_Template $_smarty_tpl) { +?> +
+

+ Titles - Page tpl_vars['page']->value;?> + + tpl_vars['logged']->value && $_smarty_tpl->tpl_vars['user']->value['level'] >= 75) {?> + Add Title + +

+ tpl_vars['titles']->value)) {?> +
+ smarty->ext->_foreach->init($_smarty_tpl, $_smarty_tpl->tpl_vars['titles']->value, 'item', false, 'key', 'name', array ( +)); +$_smarty_tpl->tpl_vars['item']->do_else = true; +if ($_from !== null) foreach ($_from as $_smarty_tpl->tpl_vars['key']->value => $_smarty_tpl->tpl_vars['item']->value) { +$_smarty_tpl->tpl_vars['item']->do_else = false; +?> +
+
+
+ + Cover + +
+
+

+ + tpl_vars['item']->value['title'];?> + + +

+
+ + tpl_vars['item']->value['sstatus'] == 1) {?>Plannedtpl_vars['item']->value['sstatus'] == 2) {?>Ongoingtpl_vars['item']->value['sstatus'] == 3) {?>Hiatustpl_vars['item']->value['sstatus'] == 4) {?>CompletedCancelled + +
+
+
+
+ smarty->ext->_foreach->restore($_smarty_tpl, 1);?> +
+ +

There are no Titles on this page!

+ + + tpl_vars['pagis']->value)) {?> +
+ tpl_vars['page']->value > 1) {?> + « + + + smarty->ext->_foreach->init($_smarty_tpl, $_smarty_tpl->tpl_vars['pagis']->value, 'item', false, 'key', 'name', array ( +)); +$_smarty_tpl->tpl_vars['item']->do_else = true; +if ($_from !== null) foreach ($_from as $_smarty_tpl->tpl_vars['key']->value => $_smarty_tpl->tpl_vars['item']->value) { +$_smarty_tpl->tpl_vars['item']->do_else = false; +?> + tpl_vars['page']->value != $_smarty_tpl->tpl_vars['item']->value) {?> + tpl_vars['item']->value;?> + + + tpl_vars['item']->value;?> + + + smarty->ext->_foreach->restore($_smarty_tpl, 1);?> + tpl_vars['page']->value < $_smarty_tpl->tpl_vars['totalPages']->value) {?> + + » + +
+ +
+ + +tpl_vars['logged']->value && $_smarty_tpl->tpl_vars['user']->value['level'] >= 75) {?> + + + + +> + $(function() { + $("#cover").change(function(e) { + e.preventDefault(); + var fd = new FormData(); + var files = $("#cover")[0].files; + if (files.length > 0) { + fd.append("cover", files[0]); + $.ajax({ + type: "POST", + url: "ajax\\images\\tmp.php", + data: fd, + contentType: false, + processData: false, + success: function(msg) { + let result = JSON.parse(msg); + if (result.s == true) { + let preview = document.getElementById("imgpreview"); + preview.classList.remove("hidden"); + preview.src = "data/tmp/" + result.msg; + document.querySelector("input[name='cover']").value = + result.msg; + } else { + alert(result.msg); + } + } + }); + } + }); + }); + + $("#cover").change(function(e) { + var allowedTypes = ["image/jpeg", "image/jpg", "image/png", "image/webp"]; + var file = this.files[0]; + var fileType = file.type; + if (!allowedTypes.includes(fileType)) { + alert("Upload only supports JPG, JPEG, PNG and WEBP!"); + $("#cover").val(""); + return false; + } + }); + + $("#addTitleForm").submit(function(e) { + e.preventDefault(); + $.ajax({ + type: "POST", + url: "ajax\\titles\\add.php", + data: $(this).serialize(), + success: function(data) { + let result = JSON.parse(data); + if (result.s == true) { + //let text = document.getElementById("addtitle-result"); + //text.classList.add("hidden"); + window.location.replace("title.php?id=" + result.msg); + // Redirect to Title? + } else { + let text = document.getElementById("addtitle-result"); + text.classList.remove("hidden"); + text.innerHTML = "Error: " + result.msg; + } + } + }); + return false; + }); + +> +_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689cd3229d0d10_42390359', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + '1e90c8b1a41733f333db4275333651aac407b5c4' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\nucleus\\parts\\header.tpl', + 1 => 1754941256, + 2 => 'file', + ), + ), + 'includes' => + array ( + 'file:../parts/menu.tpl' => 1, + ), +),false)) { +function content_689cd3229d0d10_42390359 (Smarty_Internal_Template $_smarty_tpl) { +if (((isset($_smarty_tpl->tpl_vars['rel']->value)) && $_smarty_tpl->tpl_vars['rel']->value != 'tab') || !(isset($_smarty_tpl->tpl_vars['rel']->value))) {?> + + + + + + + + <?php echo $_smarty_tpl->tpl_vars['pagetitle']->value;?> + + + + src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"> +> + + + src="https://cdn.tailwindcss.com"> +> + + + + + + + + + + _subTemplateRender("file:../parts/menu.tpl", $_smarty_tpl->cache_id, $_smarty_tpl->compile_id, 0, $_smarty_tpl->cache_lifetime, array(), 0, false); +} +} +} diff --git a/software/Smarty/compile/20b3a3dde665e5033b2cf243a19d4345ceb94616_0.file.404.tpl.php b/software/Smarty/compile/20b3a3dde665e5033b2cf243a19d4345ceb94616_0.file.404.tpl.php new file mode 100644 index 0000000..ee4a872 --- /dev/null +++ b/software/Smarty/compile/20b3a3dde665e5033b2cf243a19d4345ceb94616_0.file.404.tpl.php @@ -0,0 +1,40 @@ +_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689e3179852826_02687571', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + '20b3a3dde665e5033b2cf243a19d4345ceb94616' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\netflix\\pages\\404.tpl', + 1 => 1755194954, + 2 => 'file', + ), + ), + 'includes' => + array ( + ), +),false)) { +function content_689e3179852826_02687571 (Smarty_Internal_Template $_smarty_tpl) { +?> +
+
+

404

+

Page Not Found

+

Sorry, we couldn't find the page you're looking for.

+ +
+
_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689ce04fcff495_69637049', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + '442cf3293e687c623401082b2276a22f478c02fd' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\nucleus\\pages\\releases.tpl', + 1 => 1754941256, + 2 => 'file', + ), + ), + 'includes' => + array ( + ), +),false)) { +function content_689ce04fcff495_69637049 (Smarty_Internal_Template $_smarty_tpl) { +?> +
+

+ Releases - Page tpl_vars['page']->value;?> + +

+ tpl_vars['chapters']->value)) {?> +
+ smarty->ext->_foreach->init($_smarty_tpl, $_smarty_tpl->tpl_vars['chapters']->value, 'item', false, 'key', 'name', array ( +)); +$_smarty_tpl->tpl_vars['item']->do_else = true; +if ($_from !== null) foreach ($_from as $_smarty_tpl->tpl_vars['key']->value => $_smarty_tpl->tpl_vars['item']->value) { +$_smarty_tpl->tpl_vars['item']->do_else = false; +?> + + smarty->ext->_foreach->restore($_smarty_tpl, 1);?> +
+ +

There are no Chapters on this page!

+ + + tpl_vars['pagis']->value)) {?> + +
+ tpl_vars['page']->value > 1) {?> + « + + + smarty->ext->_foreach->init($_smarty_tpl, $_smarty_tpl->tpl_vars['pagis']->value, 'item', false, 'key', 'name', array ( +)); +$_smarty_tpl->tpl_vars['item']->do_else = true; +if ($_from !== null) foreach ($_from as $_smarty_tpl->tpl_vars['key']->value => $_smarty_tpl->tpl_vars['item']->value) { +$_smarty_tpl->tpl_vars['item']->do_else = false; +?> + tpl_vars['page']->value != $_smarty_tpl->tpl_vars['item']->value) {?> + tpl_vars['item']->value;?> + + + tpl_vars['item']->value;?> + + + smarty->ext->_foreach->restore($_smarty_tpl, 1);?> + tpl_vars['page']->value < $_smarty_tpl->tpl_vars['totalPages']->value) {?> + + » + +
+ + +
+_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689e310122ff10_79566906', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + '4c0094e1abb72d2b49369fba914bbc30214c89f9' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\netflix\\parts\\alert.tpl', + 1 => 1755194995, + 2 => 'file', + ), + ), + 'includes' => + array ( + ), +),false)) { +function content_689e310122ff10_79566906 (Smarty_Internal_Template $_smarty_tpl) { +if (!empty($_smarty_tpl->tpl_vars['alert']->value)) {?> +
+
+
+ tpl_vars['alert']->value['type'] == 'error') {?> + + + + tpl_vars['alert']->value['type'] == 'success') {?> + + + + tpl_vars['alert']->value['type'] == 'warning') {?> + + + + + tpl_vars['alert']->value['message'];?> + +
+ +
+
+_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689cd32399b332_22462464', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + '63caa9efd219d3051e4bb1ec8205fc43c99f338f' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\nucleus\\pages\\index.tpl', + 1 => 1754941256, + 2 => 'file', + ), + ), + 'includes' => + array ( + ), +),false)) { +function content_689cd32399b332_22462464 (Smarty_Internal_Template $_smarty_tpl) { +?>
+ + tpl_vars['recentlyUpdated']->value)) {?> + +
+ smarty->ext->_foreach->init($_smarty_tpl, $_smarty_tpl->tpl_vars['recentlyUpdated']->value, 'item', false, 'key', 'name', array ( +)); +$_smarty_tpl->tpl_vars['item']->do_else = true; +if ($_from !== null) foreach ($_from as $_smarty_tpl->tpl_vars['key']->value => $_smarty_tpl->tpl_vars['item']->value) { +$_smarty_tpl->tpl_vars['item']->do_else = false; +?> + tpl_vars['key']->value+1;?> + + smarty->ext->_foreach->restore($_smarty_tpl, 1);?> +
+ +

There are no Titles at the Moment!

+ +
+ + +
+

+ + Latest Releases + +

+ tpl_vars['chapters']->value)) {?> +
+ smarty->ext->_foreach->init($_smarty_tpl, $_smarty_tpl->tpl_vars['chapters']->value, 'item', false, 'key', 'name', array ( +)); +$_smarty_tpl->tpl_vars['item']->do_else = true; +if ($_from !== null) foreach ($_from as $_smarty_tpl->tpl_vars['key']->value => $_smarty_tpl->tpl_vars['item']->value) { +$_smarty_tpl->tpl_vars['item']->do_else = false; +?> + + smarty->ext->_foreach->restore($_smarty_tpl, 1);?> + +
+ +

There are no Chapters on this page!

+ +
+_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689e3100d66c76_90209577', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + '6a93d4bf018663cc7f382c0fded744a231f81bba' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\netflix\\parts\\menu.tpl', + 1 => 1755194066, + 2 => 'file', + ), + ), + 'includes' => + array ( + 'file:../parts/alert.tpl' => 1, + ), +),false)) { +function content_689e3100d66c76_90209577 (Smarty_Internal_Template $_smarty_tpl) { +?> + + + + + + +
+_subTemplateRender("file:../parts/alert.tpl", $_smarty_tpl->cache_id, $_smarty_tpl->compile_id, 0, $_smarty_tpl->cache_lifetime, array(), 0, false); +} +} diff --git a/software/Smarty/compile/6c3b07e3549ce6997120c6e0a74b2ac6bee021eb_0.file.index.tpl.php b/software/Smarty/compile/6c3b07e3549ce6997120c6e0a74b2ac6bee021eb_0.file.index.tpl.php new file mode 100644 index 0000000..99caac8 --- /dev/null +++ b/software/Smarty/compile/6c3b07e3549ce6997120c6e0a74b2ac6bee021eb_0.file.index.tpl.php @@ -0,0 +1,200 @@ +_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689e31016140f3_20031746', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + '6c3b07e3549ce6997120c6e0a74b2ac6bee021eb' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\netflix\\pages\\index.tpl', + 1 => 1755194710, + 2 => 'file', + ), + ), + 'includes' => + array ( + ), +),false)) { +function content_689e31016140f3_20031746 (Smarty_Internal_Template $_smarty_tpl) { +?> +
+
+
+
+
+

Featured Manga Title

+

Brief description of the featured manga series that captures the reader's interest.

+
+ + +
+
+
+
+
+ + +tpl_vars['logged']->value && !empty($_smarty_tpl->tpl_vars['continueReading']->value)) {?> +
+

Continue Watching

+
+ +
+
+ + + +tpl_vars['newReleases']->value)) {?> +
+

New Releases

+
+ smarty->ext->_foreach->init($_smarty_tpl, $_smarty_tpl->tpl_vars['newReleases']->value, 'item', false, 'key', 'name', array ( +)); +$_smarty_tpl->tpl_vars['item']->do_else = true; +if ($_from !== null) foreach ($_from as $_smarty_tpl->tpl_vars['key']->value => $_smarty_tpl->tpl_vars['item']->value) { +$_smarty_tpl->tpl_vars['item']->do_else = false; +?> +
+
+ <?php echo $_smarty_tpl->tpl_vars['item']->value['title']['title'];?>
+ +
+ +
+
+

tpl_vars['item']->value['title']['title'];?> +

+

Chapter tpl_vars['item']->value['number'];?> +

+
+ smarty->ext->_foreach->restore($_smarty_tpl, 1);?> +
+
+ + + +tpl_vars['popularTitles']->value)) {?> +
+

Popular This Week

+
+ smarty->ext->_foreach->init($_smarty_tpl, $_smarty_tpl->tpl_vars['popularTitles']->value, 'item', false, 'key', 'name', array ( +)); +$_smarty_tpl->tpl_vars['item']->do_else = true; +if ($_from !== null) foreach ($_from as $_smarty_tpl->tpl_vars['key']->value => $_smarty_tpl->tpl_vars['item']->value) { +$_smarty_tpl->tpl_vars['item']->do_else = false; +?> +
+
+ <?php echo $_smarty_tpl->tpl_vars['item']->value['title'];?>
+ +
+ +
+
+

tpl_vars['item']->value['title'];?> +

+

tpl_vars['item']->value['chapterCount'];?> + chapters

+
+ smarty->ext->_foreach->restore($_smarty_tpl, 1);?> +
+
+ + + +tpl_vars['recentlyUpdated']->value)) {?> +
+

Recently Updated

+
+ smarty->ext->_foreach->init($_smarty_tpl, $_smarty_tpl->tpl_vars['recentlyUpdated']->value, 'item', false, 'key', 'name', array ( +)); +$_smarty_tpl->tpl_vars['item']->do_else = true; +if ($_from !== null) foreach ($_from as $_smarty_tpl->tpl_vars['key']->value => $_smarty_tpl->tpl_vars['item']->value) { +$_smarty_tpl->tpl_vars['item']->do_else = false; +?> +
+
+ <?php echo $_smarty_tpl->tpl_vars['item']->value['title']['title'];?>
+ +
+ +
+
+

tpl_vars['item']->value['title']['title'];?> +

+

Updated tpl_vars['item']->value['lastUpdate'];?> +

+
+ smarty->ext->_foreach->restore($_smarty_tpl, 1);?> +
+
+ + + + + src="assets/netflix/carousel.js"> +> + +> +document.addEventListener('DOMContentLoaded', function() { + // Initialize any additional carousel functionality if needed +}); + +>_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689e31020a2692_49970825', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + '790295cb86b0eaff03b243c3593d85dcb6dc6a2d' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\netflix\\parts\\footer.tpl', + 1 => 1755194148, + 2 => 'file', + ), + ), + 'includes' => + array ( + ), +),false)) { +function content_689e31020a2692_49970825 (Smarty_Internal_Template $_smarty_tpl) { +?>
+ + +tpl_vars['rel']->value)) && $_smarty_tpl->tpl_vars['rel']->value != 'tab') || !(isset($_smarty_tpl->tpl_vars['rel']->value))) {?> + tpl_vars['logged']->value) {?> + + + + + + + + + + + +> + function toggleCheck(id) { + let box = document.getElementById(id); + box.checked = !box.checked; + } + + tpl_vars['logged']->value) {?> + $("#loginForm").submit(function(e) { + e.preventDefault(); + $.ajax({ + type: "POST", + url: "ajax\\account\\login.php", + data: $(this).serialize(), + success: function(data) { + if (data == "success") { + location.reload(); + } else { + let text = document.getElementById("login-result"); + text.classList.remove("hidden"); + text.innerHTML = "Error: " + data; + } + } + }); + return false; + }); + + $("#signupForm").submit(function(e) { + e.preventDefault(); + $.ajax({ + type: "POST", + url: "ajax\\account\\signup.php", + data: $(this).serialize(), + success: function(data) { + if (data == "success") { + location.reload(); + } else { + let text = document.getElementById("signup-result"); + text.classList.remove("hidden"); + text.innerHTML = "Error: " + data; + } + } + }); + return false; + }); + + + tpl_vars['logged']->value) {?> + $("#userSettingsForm").submit(function(e) { + e.preventDefault(); + $.ajax({ + type: "POST", + url: "ajax\\account\\settings.php", + data: $(this).serialize(), + success: function(data) { + if (data == "success") { + location.reload(); + } else { + let text = document.getElementById("usersettings-result"); + text.classList.remove("hidden"); + text.innerHTML = "Error: " + data; + text.classList.add("text-error"); + text.classList.remove("text-success"); + } + } + }); + return false; + }); + + $("#logoutButton").click(function(e) { + e.preventDefault(); + $.ajax({ + type: "POST", + url: "ajax\\account\\logout.php", + data: $(this).serialize(), + success: function(data) { + if (data == "success") { + location.reload(); + } else { + alert("Error: " + data); + } + } + }); + return false; + }); + + $("#dismissAlert").click(function(e) { + e.preventDefault(); + $.ajax({ + type: "POST", + url: "ajax\\alerts\\dismiss.php", + success: function(data) { + if (data == "success") { + let div = document.getElementById("topAlert"); + div.classList.add("hidden"); + } else { + alert("Error: " + data); + } + } + }); + return false; + }); + + + function setCookie(name, value) { + Cookies.set("tpl_vars['config']->value['title']);?> +_" + name, value, { expires: 999 }) + } + + function updateUserLang(lang) { + tpl_vars['logged']->value) {?> + $.ajax({ + type: "POST", + url: "ajax\\account\\updateLang.php", + data: { + newLang: lang + }, + success: function(data) { + if (data == "success") { + location.reload(); + } else { + alert("Error: " + data); + } + } + }); + + location.reload(); + + } + + function toggleRead(id) { + let box = event.srcElement.id; + tpl_vars['logged']->value) {?> + alert("Error: Login to use this function!"); + toggleCheck(box); + + $.ajax({ + type: "POST", + url: "ajax\\chapters\\read.php", + data: { + user: tpl_vars['user']->value['id'];?> +, + chapter: id + }, + success: function(data) { + if (data != "success") { + alert("Error: " + data); + toggleCheck(box); + } + } + }); + + } + +> + + + + + + + + + + + + + + + + src="assets/nucleus/tabs.js"> +> + + src="assets/nucleus/tags.js"> +> + + src="assets/nucleus/autotheme.js"> +> + + src="assets/nucleus/carousel.js"> +> + + src="assets/js.cookie.js"> +> + + + + src="assets/netflix/theme.js"> +> + + src="assets/netflix/carousel.js"> +> + + src="assets/netflix/reader.js"> +> + + + +_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689e3100b41747_64636614', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + '7b6d43902ab2883e0a8ec299a9dfb398c7a4ee06' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\netflix\\parts\\header.tpl', + 1 => 1755194048, + 2 => 'file', + ), + ), + 'includes' => + array ( + 'file:../parts/menu.tpl' => 1, + ), +),false)) { +function content_689e3100b41747_64636614 (Smarty_Internal_Template $_smarty_tpl) { +if (((isset($_smarty_tpl->tpl_vars['rel']->value)) && $_smarty_tpl->tpl_vars['rel']->value != 'tab') || !(isset($_smarty_tpl->tpl_vars['rel']->value))) {?> + + + + + + + <?php echo $_smarty_tpl->tpl_vars['pagetitle']->value;?> + + + + + + + + + src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"> +> + + src="assets/netflix/theme.js"> +> + + + + + + + + + + src="https://cdn.tailwindcss.com"> +> + + + + + + _subTemplateRender("file:../parts/menu.tpl", $_smarty_tpl->cache_id, $_smarty_tpl->compile_id, 0, $_smarty_tpl->cache_lifetime, array(), 0, false); +} +} +} diff --git a/software/Smarty/compile/89f3d87d9ba2f5df504575c0d16d281ede9f5db8_0.file.footer.tpl.php b/software/Smarty/compile/89f3d87d9ba2f5df504575c0d16d281ede9f5db8_0.file.footer.tpl.php new file mode 100644 index 0000000..51ecd39 --- /dev/null +++ b/software/Smarty/compile/89f3d87d9ba2f5df504575c0d16d281ede9f5db8_0.file.footer.tpl.php @@ -0,0 +1,404 @@ +_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689cd324888026_07011381', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + '89f3d87d9ba2f5df504575c0d16d281ede9f5db8' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\nucleus\\parts\\footer.tpl', + 1 => 1754941256, + 2 => 'file', + ), + ), + 'includes' => + array ( + ), +),false)) { +function content_689cd324888026_07011381 (Smarty_Internal_Template $_smarty_tpl) { +?>
+ + +tpl_vars['rel']->value)) && $_smarty_tpl->tpl_vars['rel']->value != 'tab') || !(isset($_smarty_tpl->tpl_vars['rel']->value))) {?> + tpl_vars['logged']->value) {?> + + + + + + + + + + + +> + function toggleCheck(id) { + let box = document.getElementById(id); + box.checked = !box.checked; + } + + tpl_vars['logged']->value) {?> + $("#loginForm").submit(function(e) { + e.preventDefault(); + $.ajax({ + type: "POST", + url: "ajax\\account\\login.php", + data: $(this).serialize(), + success: function(data) { + if (data == "success") { + location.reload(); + } else { + let text = document.getElementById("login-result"); + text.classList.remove("hidden"); + text.innerHTML = "Error: " + data; + } + } + }); + return false; + }); + + $("#signupForm").submit(function(e) { + e.preventDefault(); + $.ajax({ + type: "POST", + url: "ajax\\account\\signup.php", + data: $(this).serialize(), + success: function(data) { + if (data == "success") { + location.reload(); + } else { + let text = document.getElementById("signup-result"); + text.classList.remove("hidden"); + text.innerHTML = "Error: " + data; + } + } + }); + return false; + }); + + + tpl_vars['logged']->value) {?> + $("#userSettingsForm").submit(function(e) { + e.preventDefault(); + $.ajax({ + type: "POST", + url: "ajax\\account\\settings.php", + data: $(this).serialize(), + success: function(data) { + if (data == "success") { + location.reload(); + } else { + let text = document.getElementById("usersettings-result"); + text.classList.remove("hidden"); + text.innerHTML = "Error: " + data; + text.classList.add("text-error"); + text.classList.remove("text-success"); + } + } + }); + return false; + }); + + $("#logoutButton").click(function(e) { + e.preventDefault(); + $.ajax({ + type: "POST", + url: "ajax\\account\\logout.php", + data: $(this).serialize(), + success: function(data) { + if (data == "success") { + location.reload(); + } else { + alert("Error: " + data); + } + } + }); + return false; + }); + + $("#dismissAlert").click(function(e) { + e.preventDefault(); + $.ajax({ + type: "POST", + url: "ajax\\alerts\\dismiss.php", + success: function(data) { + if (data == "success") { + let div = document.getElementById("topAlert"); + div.classList.add("hidden"); + } else { + alert("Error: " + data); + } + } + }); + return false; + }); + + + function setCookie(name, value) { + Cookies.set("tpl_vars['config']->value['title']);?> +_" + name, value, { expires: 999 }) + } + + function updateUserLang(lang) { + tpl_vars['logged']->value) {?> + $.ajax({ + type: "POST", + url: "ajax\\account\\updateLang.php", + data: { + newLang: lang + }, + success: function(data) { + if (data == "success") { + location.reload(); + } else { + alert("Error: " + data); + } + } + }); + + location.reload(); + + } + + function toggleRead(id) { + let box = event.srcElement.id; + tpl_vars['logged']->value) {?> + alert("Error: Login to use this function!"); + toggleCheck(box); + + $.ajax({ + type: "POST", + url: "ajax\\chapters\\read.php", + data: { + user: tpl_vars['user']->value['id'];?> +, + chapter: id + }, + success: function(data) { + if (data != "success") { + alert("Error: " + data); + toggleCheck(box); + } + } + }); + + } + +> + + + + + + + + + + + + + + + + src="assets/nucleus/tabs.js"> +> + + src="assets/nucleus/tags.js"> +> + + src="assets/nucleus/autotheme.js"> +> + + src="assets/nucleus/carousel.js"> +> + + src="assets/js.cookie.js"> +> + + + +_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689cd322da9bb2_27910351', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + 'b841130d5fb2fbb8b4d8819239a0162a3c7df32e' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\nucleus\\parts\\menu.tpl', + 1 => 1754941256, + 2 => 'file', + ), + ), + 'includes' => + array ( + 'file:../parts/alert.tpl' => 1, + ), +),false)) { +function content_689cd322da9bb2_27910351 (Smarty_Internal_Template $_smarty_tpl) { +?> + + + + + +
+_subTemplateRender("file:../parts/alert.tpl", $_smarty_tpl->cache_id, $_smarty_tpl->compile_id, 0, $_smarty_tpl->cache_lifetime, array(), 0, false); +} +} diff --git a/software/Smarty/compile/ca5b60a1bcb639f7e200656803933d86fa26d80b_0.file.404.tpl.php b/software/Smarty/compile/ca5b60a1bcb639f7e200656803933d86fa26d80b_0.file.404.tpl.php new file mode 100644 index 0000000..3b9ba60 --- /dev/null +++ b/software/Smarty/compile/ca5b60a1bcb639f7e200656803933d86fa26d80b_0.file.404.tpl.php @@ -0,0 +1,31 @@ +_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689e2f9f3076c2_84144043', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + 'ca5b60a1bcb639f7e200656803933d86fa26d80b' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\nucleus\\pages\\404.tpl', + 1 => 1755192861, + 2 => 'file', + ), + ), + 'includes' => + array ( + ), +),false)) { +function content_689e2f9f3076c2_84144043 (Smarty_Internal_Template $_smarty_tpl) { +?>
+
+

404 - Page Not Found

+

Sorry, the page you're looking for doesn't exist.

+ Go Home +
+
_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689e31aa7fe777_94286455', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + 'cc77098f62797e8f8f401ad12b822723fece3400' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\netflix\\pages\\titles.tpl', + 1 => 1755194934, + 2 => 'file', + ), + ), + 'includes' => + array ( + ), +),false)) { +function content_689e31aa7fe777_94286455 (Smarty_Internal_Template $_smarty_tpl) { +$_smarty_tpl->_checkPlugins(array(0=>array('file'=>'C:\\Coding\\MANGAS\\FoOlSlideX\\software\\Smarty\\plugins\\modifier.number_format.php','function'=>'smarty_modifier_number_format',),)); +?> + +
+

Browse Manga

+

Discover your next favorite series

+
+ + +
+
+
+
+ + +
+
+
+ + + +
+
+
+ + +
+ tpl_vars['titles']->value)) {?> +
+ smarty->ext->_foreach->init($_smarty_tpl, $_smarty_tpl->tpl_vars['titles']->value, 'title'); +$_smarty_tpl->tpl_vars['title']->do_else = true; +if ($_from !== null) foreach ($_from as $_smarty_tpl->tpl_vars['title']->value) { +$_smarty_tpl->tpl_vars['title']->do_else = false; +?> +
+
+ <?php echo $_smarty_tpl->tpl_vars['title']->value['title'];?>
+ +
+ +
+
+ + tpl_vars['title']->value['sstatus'] == 1) {?>Plannedtpl_vars['title']->value['sstatus'] == 2) {?>Ongoingtpl_vars['title']->value['sstatus'] == 3) {?>Hiatustpl_vars['title']->value['sstatus'] == 4) {?>Completedtpl_vars['title']->value['sstatus'] == 5) {?>Cancelled + +
+
+
+

tpl_vars['title']->value['title'];?> +

+

tpl_vars['title']->value['authors'];?> +

+
+ tpl_vars['title']->value['chapterCount'];?> + chapters +
+ + + + tpl_vars['title']->value['rating'],1);?> + +
+
+
+
+ smarty->ext->_foreach->restore($_smarty_tpl, 1);?> +
+ + + tpl_vars['totalPages']->value > 1) {?> +
+
+ tpl_vars['currentPage']->value > 1) {?> + « + + + tpl_vars['i'] = new Smarty_Variable(null, $_smarty_tpl->isRenderingCache);$_smarty_tpl->tpl_vars['i']->step = 1;$_smarty_tpl->tpl_vars['i']->total = (int) ceil(($_smarty_tpl->tpl_vars['i']->step > 0 ? $_smarty_tpl->tpl_vars['totalPages']->value+1 - (1) : 1-($_smarty_tpl->tpl_vars['totalPages']->value)+1)/abs($_smarty_tpl->tpl_vars['i']->step)); +if ($_smarty_tpl->tpl_vars['i']->total > 0) { +for ($_smarty_tpl->tpl_vars['i']->value = 1, $_smarty_tpl->tpl_vars['i']->iteration = 1;$_smarty_tpl->tpl_vars['i']->iteration <= $_smarty_tpl->tpl_vars['i']->total;$_smarty_tpl->tpl_vars['i']->value += $_smarty_tpl->tpl_vars['i']->step, $_smarty_tpl->tpl_vars['i']->iteration++) { +$_smarty_tpl->tpl_vars['i']->first = $_smarty_tpl->tpl_vars['i']->iteration === 1;$_smarty_tpl->tpl_vars['i']->last = $_smarty_tpl->tpl_vars['i']->iteration === $_smarty_tpl->tpl_vars['i']->total;?> + tpl_vars['i']->value == $_smarty_tpl->tpl_vars['currentPage']->value) {?> + + + tpl_vars['i']->value;?> + + + + + tpl_vars['currentPage']->value < $_smarty_tpl->tpl_vars['totalPages']->value) {?> + » + +
+
+ + +
+

No titles found matching your criteria.

+
+ +
_decodeProperties($_smarty_tpl, array ( + 'version' => '4.3.0', + 'unifunc' => 'content_689cd3232e0983_25870519', + 'has_nocache_code' => false, + 'file_dependency' => + array ( + 'f7858bc73069af01acefb51b5e48e5faecb123b3' => + array ( + 0 => 'C:\\Coding\\MANGAS\\FoOlSlideX\\library\\themes\\nucleus\\parts\\alert.tpl', + 1 => 1754941256, + 2 => 'file', + ), + ), + 'includes' => + array ( + ), +),false)) { +function content_689cd3232e0983_25870519 (Smarty_Internal_Template $_smarty_tpl) { +if (!empty($_smarty_tpl->tpl_vars['topAlert']->value) && !$_smarty_tpl->tpl_vars['readAlert']->value) {?> +
+
+ + tpl_vars['topAlert']->value['type'] == "info") {?> + + tpl_vars['topAlert']->value['type'] == "success") {?> + + tpl_vars['topAlert']->value['type'] == "warning") {?> + + + + + + tpl_vars['topAlert']->value['type']);?> +: tpl_vars['topAlert']->value['content'];?> + +
+ tpl_vars['logged']->value) {?> +
+ +
+ +
+ + +tpl_vars['logged']->value && $_smarty_tpl->tpl_vars['user']->value['level'] == 49) {?> +
+
+ + + + Info: Activate your account through the Email we sent you to use all functions of tpl_vars['config']->value['title'];?> +! +
+
+ext->getTemplateVars->getTemplateVars($this, $varName, $_ptr, $searchParents); } @@ -200,7 +200,7 @@ public function getTemplateVars($varName = null, Smarty_Internal_Data $_ptr = nu * * @param \Smarty_Internal_Data|null $data */ - public function _mergeVars(Smarty_Internal_Data $data = null) + public function _mergeVars(?Smarty_Internal_Data $data = null) { if (isset($data)) { if (!empty($this->tpl_vars)) { diff --git a/software/Smarty/sysplugins/smarty_internal_method_gettemplatevars.php b/software/Smarty/sysplugins/smarty_internal_method_gettemplatevars.php index 0470785..71fc6c3 100644 --- a/software/Smarty/sysplugins/smarty_internal_method_gettemplatevars.php +++ b/software/Smarty/sysplugins/smarty_internal_method_gettemplatevars.php @@ -34,7 +34,7 @@ class Smarty_Internal_Method_GetTemplateVars public function getTemplateVars( Smarty_Internal_Data $data, $varName = null, - Smarty_Internal_Data $_ptr = null, + ?Smarty_Internal_Data $_ptr = null, $searchParents = true ) { if (isset($varName)) { @@ -87,7 +87,7 @@ public function getTemplateVars( public function _getVariable( Smarty_Internal_Data $data, $varName, - Smarty_Internal_Data $_ptr = null, + ?Smarty_Internal_Data $_ptr = null, $searchParents = true, $errorEnable = true ) { diff --git a/software/Smarty/sysplugins/smarty_internal_resource_file.php b/software/Smarty/sysplugins/smarty_internal_resource_file.php index ae20606..2d7367f 100644 --- a/software/Smarty/sysplugins/smarty_internal_resource_file.php +++ b/software/Smarty/sysplugins/smarty_internal_resource_file.php @@ -25,7 +25,7 @@ class Smarty_Internal_Resource_File extends Smarty_Resource * * @throws \SmartyException */ - public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null) + public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null) { $source->filepath = $this->buildFilepath($source, $_template); if ($source->filepath !== false) { @@ -98,7 +98,7 @@ public function getBasename(Smarty_Template_Source $source) * @return string fully qualified filepath * @throws SmartyException */ - protected function buildFilepath(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null) + protected function buildFilepath(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null) { $file = $source->name; // absolute file ? diff --git a/software/Smarty/sysplugins/smarty_internal_runtime_codeframe.php b/software/Smarty/sysplugins/smarty_internal_runtime_codeframe.php index d0ca751..74bde2d 100644 --- a/software/Smarty/sysplugins/smarty_internal_runtime_codeframe.php +++ b/software/Smarty/sysplugins/smarty_internal_runtime_codeframe.php @@ -30,7 +30,7 @@ public function create( $content = '', $functions = '', $cache = false, - Smarty_Internal_TemplateCompilerBase $compiler = null + ?Smarty_Internal_TemplateCompilerBase $compiler = null ) { // build property code $properties[ 'version' ] = Smarty::SMARTY_VERSION; diff --git a/software/Smarty/sysplugins/smarty_internal_template.php b/software/Smarty/sysplugins/smarty_internal_template.php index 72d1d52..93731a5 100644 --- a/software/Smarty/sysplugins/smarty_internal_template.php +++ b/software/Smarty/sysplugins/smarty_internal_template.php @@ -149,7 +149,7 @@ class Smarty_Internal_Template extends Smarty_Internal_TemplateBase public function __construct( $template_resource, Smarty $smarty, - Smarty_Internal_Data $_parent = null, + ?Smarty_Internal_Data $_parent = null, $_cache_id = null, $_compile_id = null, $_caching = null, diff --git a/software/Smarty/sysplugins/smarty_internal_templatecompilerbase.php b/software/Smarty/sysplugins/smarty_internal_templatecompilerbase.php index d5c18d3..312194e 100644 --- a/software/Smarty/sysplugins/smarty_internal_templatecompilerbase.php +++ b/software/Smarty/sysplugins/smarty_internal_templatecompilerbase.php @@ -386,7 +386,7 @@ public function __construct(Smarty $smarty) public function compileTemplate( Smarty_Internal_Template $template, $nocache = null, - Smarty_Internal_TemplateCompilerBase $parent_compiler = null + ?Smarty_Internal_TemplateCompilerBase $parent_compiler = null ) { // get code frame of compiled template $_compiled_code = $template->smarty->ext->_codeFrame->create( @@ -417,7 +417,7 @@ public function compileTemplate( public function compileTemplateSource( Smarty_Internal_Template $template, $nocache = null, - Smarty_Internal_TemplateCompilerBase $parent_compiler = null + ?Smarty_Internal_TemplateCompilerBase $parent_compiler = null ) { try { // save template object in compiler class diff --git a/software/Smarty/sysplugins/smarty_resource.php b/software/Smarty/sysplugins/smarty_resource.php index 3c43a9f..f0391f5 100644 --- a/software/Smarty/sysplugins/smarty_resource.php +++ b/software/Smarty/sysplugins/smarty_resource.php @@ -173,8 +173,8 @@ public static function getUniqueTemplateName($obj, $template_resource) * @throws \SmartyException */ public static function source( - Smarty_Internal_Template $_template = null, - Smarty $smarty = null, + ?Smarty_Internal_Template $_template = null, + ?Smarty $smarty = null, $template_resource = null ) { return Smarty_Template_Source::load($_template, $smarty, $template_resource); @@ -196,7 +196,7 @@ abstract public function getContent(Smarty_Template_Source $source); * @param Smarty_Template_Source $source source object * @param Smarty_Internal_Template $_template template object */ - abstract public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null); + abstract public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null); /** * populate Source Object with timestamp and exists from Resource diff --git a/software/Smarty/sysplugins/smarty_template_source.php b/software/Smarty/sysplugins/smarty_template_source.php index 16b47f2..8627f58 100644 --- a/software/Smarty/sysplugins/smarty_template_source.php +++ b/software/Smarty/sysplugins/smarty_template_source.php @@ -156,8 +156,8 @@ public function __construct(Smarty $smarty, $resource, $type, $name) * @throws SmartyException */ public static function load( - Smarty_Internal_Template $_template = null, - Smarty $smarty = null, + ?Smarty_Internal_Template $_template = null, + ?Smarty $smarty = null, $template_resource = null ) { if ($_template) { diff --git a/test_admin.php b/test_admin.php new file mode 100644 index 0000000..9e878b2 --- /dev/null +++ b/test_admin.php @@ -0,0 +1,57 @@ +findOneBy(["token", "=", $token]); +if (!empty($session)) { + $user = $db["users"]->findOneBy(["id", "=", $session["user"]]); + $logged = true; + + echo "User logged in: " . $user["username"] . "\n"; + echo "User level: " . $user["level"] . "\n"; + + // Test admin functionality - check if user can add titles (requires level 75+) + if ($user["level"] >= 75) { + echo "User has permission to add/edit titles and chapters!\n"; + + // Test creating a sample title + $data = array( + "title" => "Test Manga", + "alts" => "Test Manga Alternative Title", + "authors" => "Test Author", + "artists" => "Test Artist", + "lang" => "en", + "status" => 2, + "sstatus" => 2, + "releaseYear" => "2023", + "completionYear" => "2023", + "summary" => "This is a test manga for verifying admin functionality.", + "tags" => array( + "formats" => array("manga"), + "warnings" => array("none"), + "themes" => array("action"), + "genres" => array("adventure") + ), + "creator" => $user["id"], + "lastEdited" => now(), + "timestamp" => now() + ); + + // Check if title already exists + $existing = $db["titles"]->findOneBy(["title", "=", "Test Manga"]); + if (empty($existing)) { + $db["titles"]->insert($data); + echo "Test title created successfully!\n"; + } else { + echo "Test title already exists.\n"; + } + + echo "Admin functionality verified successfully!\n"; + } else { + echo "User does not have sufficient permissions.\n"; + } +} else { + echo "Invalid session token.\n"; +} \ No newline at end of file