diff options
Diffstat (limited to 'src/index.php')
-rw-r--r-- | src/index.php | 286 |
1 files changed, 215 insertions, 71 deletions
diff --git a/src/index.php b/src/index.php index de46204..a42aa19 100644 --- a/src/index.php +++ b/src/index.php @@ -1,5 +1,8 @@ <?php +use FFMpeg\Coordinate\TimeCode; +use FFMpeg\FFMpeg; +use FFMpeg\FFProbe; use Gregwar\Captcha\CaptchaBuilder; use mystic\forum\Database; use mystic\forum\exceptions\DatabaseConnectionException; @@ -23,11 +26,6 @@ if (($_SERVER["HTTP_USER_AGENT"] ?? "") === "") { exit; } -// function exception_error_handler($errno, $errstr, $errfile, $errline ) { -// throw new ErrorException(html_entity_decode($errstr), $errno, 0, $errfile, $errline); -// } -// set_error_handler("exception_error_handler"); - define("REGISTRATION_ENABLED", isTrue(env("REGISTRATION_ENABLED") ?? "")); session_name("fsid"); @@ -44,6 +42,26 @@ $_action = $_GET["_action"] ?? null; $GLOBALS["action"] = $_action; +function msg_error(string $err, bool $skipLoginCheck = false): void { + _view("template_start", ["_title" => __("Error")]); + _view("template_navigation_start"); + if (!$skipLoginCheck) + _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($GLOBALS["db"])]); + _view("template_navigation_end"); + _view("alert_error", ["message" => $err]); + _view("template_end"); +} + +function msg_info(string $msg, bool $skipLoginCheck = false): void { + _view("template_start", ["_title" => __("Information")]); + _view("template_navigation_start"); + if (!$skipLoginCheck) + _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($GLOBALS["db"])]); + _view("template_navigation_end"); + _view("alert_info", ["message" => $msg]); + _view("template_end"); +} + function generateCaptchaText(): string { $phrase = ""; for ($i = 0; $i < CAPTCHA_PHRASE_LENGTH; ++$i) @@ -124,18 +142,35 @@ function env(string $key): ?string { } require_once __DIR__ . "/vendor/autoload.php"; - require_once __DIR__ . "/application/i18n.php"; -i18n_locale("de"); + +if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["lang"])) { + parse_str($_SERVER["QUERY_STRING"], $query); + if (empty($query["lang"])) { + setcookie("lang", "", 100); + } else { + setcookie("lang", $query["lang"], time()+60*60*24*30); + } + unset($query["lang"]); + $path = strtok($_SERVER["REQUEST_URI"], "?"); + header("Location: $path?" . http_build_query($query)); + exit; +} + +$user_locale = env("LOCALE") ?? $_COOKIE["lang"] ?? locale_accept_from_http($_SERVER["HTTP_ACCEPT_LANGUAGE"] ?? ""); +$chosen_locale = locale_lookup(i18n_get_available_locales(), $user_locale, true, "en"); +i18n_locale($user_locale); $db = null; try { $db = new Database(Database::getConnectionString("db", getenv("POSTGRES_USER"), getenv("POSTGRES_PASSWORD"), getenv("POSTGRES_DBNAME"))); } catch (DatabaseConnectionException $ex) { - Messaging::error([ - Messaging::bold(__("Failed to connect to database!")), - Messaging::italic($ex->getMessage()), - ]); + msg_error( + __("Failed to connect to database:\n%details%", [ + "details" => $ex->getMessage(), + ]), + true + ); exit; } @@ -217,7 +252,7 @@ if ($_action === "auth") { if (!REGISTRATION_ENABLED) { http_response_code(403); - Messaging::error(__("Public registration disabled")); + msg_error(__("Public registration disabled")); exit; } @@ -308,9 +343,7 @@ if ($_action === "auth") { $db->insert($user); - Messaging::info( - Messaging::html(nl2br(htmlentities(__("Your account has been created!\nPlease check your emails for an activation link!"), true))) - ); + msg_info(__("Your account has been created!\nPlease check your emails for an activation link!")); } else { _view("template_start", ["_title" => __("Register")]); _view("template_navigation_start"); @@ -330,7 +363,7 @@ if ($_action === "auth") { if (!$db->fetchWhere($user, [ "activated", "activation_token" ])) { http_response_code(400); - Messaging::error(__("Invalid token")); + msg_error(__("Invalid token")); exit; } @@ -338,7 +371,7 @@ if ($_action === "auth") { if ($expectedSignature !== $sig) { http_response_code(400); - Messaging::error(__("Invalid signature.")); + msg_error(__("Invalid signature.")); exit; } @@ -347,19 +380,17 @@ if ($_action === "auth") { if (!$db->update($user)) { http_response_code(400); - Messaging::error(__("Failed to update user")); + msg_error(__("Failed to update user")); exit; } - Messaging::info([ - Messaging::html(nl2br(__( - "Your account has been activated!\nPlease click %link%here%/link% to log in!", - [ - "link" => '<a href="?_action=auth">', - "/link" => '</a>', - ] - )), true), - ]); + msg_info("?!HTML::" . __( + "Your account has been activated!\nPlease click %link%here%/link% to log in!", + [ + "link" => '<a href="?_action=auth">', + "/link" => '</a>', + ] + )); } elseif ($_action === "logout") { RequestUtils::unsetAuthorizedUser(); header("Location: " . ($_GET["next"] ?? ".")); @@ -369,14 +400,14 @@ if ($_action === "auth") { $topic->id = $topicId; if (!$db->fetch($topic)) { http_response_code(404); - Messaging::error("No topic exists with this id"); + msg_error("No topic exists with this id"); exit; } if (RequestUtils::isRequestMethod("POST")) { if (!$currentUser) { http_response_code(403); - Messaging::error("You need to be logged in to add new posts!"); + msg_error("You need to be logged in to add new posts!"); exit; } @@ -447,8 +478,8 @@ if ($_action === "auth") { /** @var Post $post */ foreach ($posts as $post) { - /** @var ?User $topicAuthor */ - $topicAuthor = null; + /** @var ?User $postAuthor */ + $postAuthor = null; if ($post->authorId !== null && !isset($userCache[$post->authorId])) { $usr = new User(); $usr->id = $post->authorId; @@ -456,13 +487,14 @@ if ($_action === "auth") { $userCache[$post->authorId] = &$usr; } if (isset($userCache[$post->authorId])) - $topicAuthor = &$userCache[$post->authorId]; + $postAuthor = &$userCache[$post->authorId]; $attachments = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $post->id ]); _view("view_post", [ "post" => $post, - "postAuthor" => $topicAuthor, + "postAuthor" => $postAuthor, + "topicAuthor" => $topicAuthor, "attachments" => $attachments, ]); } @@ -479,7 +511,7 @@ if ($_action === "auth") { } elseif ($_action === "newtopic") { if (!$currentUser) { http_response_code(403); - Messaging::error("You need to be logged in to create new topics!"); + msg_error("You need to be logged in to create new topics!"); exit; } @@ -560,7 +592,7 @@ if ($_action === "auth") { if (!$db->fetchWhere($user, "name")) { http_response_code(404); - Messaging::error(__("No user with name @%user_handle%", [ "user_handle" => $userHandle ])); + msg_error(__("No user with name @%user_handle%", [ "user_handle" => $userHandle ])); exit; } @@ -571,7 +603,7 @@ if ($_action === "auth") { $user->id = $userId; if (!$db->fetch($user)) { http_response_code(404); - Messaging::error(__("No user exists with this id")); + msg_error(__("No user exists with this id")); exit; } @@ -646,14 +678,16 @@ if ($_action === "auth") { } else { $posts = $db->fetchCustom(Post::class, 'WHERE author_id = $1 ORDER BY post_date DESC', [ $userId ]); $topics = []; + $attachments = []; foreach ($posts as $post) { - if (isset($topics[$post->topicId])) - continue; - $topic = new Topic(); - $topic->id = $post->topicId; - if (!$db->fetch($topic)) - continue; - $topics[$post->topicId] = $topic; + if (!isset($topics[$post->topicId])) { + $topic = new Topic(); + $topic->id = $post->topicId; + if ($db->fetch($topic)) + $topics[$post->topicId] = $topic; + } + $attachs = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $post->id ]); + $attachments[$post->id] = $attachs; } _view("template_start", ["_title" => $user->displayName]); _view("template_navigation_start"); @@ -663,6 +697,7 @@ if ($_action === "auth") { "user" => $user, "posts" => $posts, "topics" => $topics, + "attachments" => $attachments, "lastNameChangeTooRecent" => $lastNameChangeTooRecent, ]); _view("template_end"); @@ -670,7 +705,7 @@ if ($_action === "auth") { } elseif ($_action === "attachment") { if (!$currentUser) { http_response_code(403); - Messaging::error(__("You must be logged in to view attachments")); + msg_error(__("You must be logged in to view attachments")); exit; } @@ -679,7 +714,7 @@ if ($_action === "auth") { $attachment->id = $attId; if (!$db->fetch($attachment)) { http_response_code(404); - Messaging::error(__("No attachment exists with this id")); + msg_error(__("No attachment exists with this id")); exit; } @@ -710,7 +745,7 @@ if ($_action === "auth") { $user->id = $userId; if (!$db->fetch($user)) { http_response_code(404); - Messaging::error(__("No user exists with this id")); + msg_error(__("No user exists with this id")); exit; } @@ -747,13 +782,16 @@ if ($_action === "auth") { $attachment->id = $attId; if (!$db->fetch($attachment)) { http_response_code(404); - Messaging::error(__("No attachment exists with this id")); + msg_error(__("No attachment exists with this id")); exit; } - if (!str_starts_with($attachment->mimeType, "image/")) { + $isImage = str_starts_with($attachment->mimeType, "image/"); + $isVideo = str_starts_with($attachment->mimeType, "video/"); + + if (!$isImage && !$isVideo) { http_response_code(400); - Messaging::error(__("Attachment is not an image")); + msg_error(__("Attachment is neither an image nor a video")); exit; } @@ -772,13 +810,41 @@ if ($_action === "auth") { if ($info->contentHash === $contentHash) { header("Content-Type: image/jpeg"); header("Cache-Control: max-age=86400"); - header("X-Debug-Content: $cacheFileData"); + //header("X-Debug-Content: $cacheFileData"); readfile($cacheFileData); exit; } } - $im = imagecreatefromstring($attachment->contents); + if ($isVideo) { + $suffix = (microtime(true) * 1000) . "-" . random_int(0, 99999); + $tempVid = sys_get_temp_dir() . "/video_" . $suffix; + file_put_contents($tempVid, $attachment->contents); + $tempImg = sys_get_temp_dir() . "/image_" . $suffix . ".jpg"; + + try { + $ffprobe = FFProbe::create(); + /** @var string $duration */ + $duration = $ffprobe + ->format($tempVid) + ->get("duration", "0"); + + $screenshotFramePoint = TimeCode::fromSeconds(floatval($duration) / 2.0); + + $ffmpeg = FFMpeg::create(); + $video = $ffmpeg->open($tempVid); + $screenshot = $video + ->frame($screenshotFramePoint) + ->save($tempImg); + $im = imagecreatefromjpeg($tempImg); + } finally { + if (is_file($tempVid)) unlink($tempVid); + if (is_file($tempImg)) unlink($tempImg); + } + } elseif ($isImage) { + $im = imagecreatefromstring($attachment->contents); + } + $w = imagesx($im); $h = imagesy($im); $r = $w / floatval($h); @@ -810,7 +876,7 @@ if ($_action === "auth") { if (!$currentUser) { http_response_code(403); - Messaging::error("You need to be logged in to delete posts!"); + msg_error("You need to be logged in to delete posts!"); exit; } @@ -821,7 +887,7 @@ if ($_action === "auth") { if (!$db->fetch($post) || $post->deleted) { http_response_code(404); - Messaging::error("No post exists with this id"); + msg_error("No post exists with this id"); exit; } @@ -836,7 +902,7 @@ if ($_action === "auth") { if (!$canEdit) { http_response_code(403); - Messaging::error("You don't have permission to delete this post"); + msg_error("You don't have permission to delete this post"); exit; } @@ -847,7 +913,7 @@ if ($_action === "auth") { $expectedConfirm = base64_encode(hash("sha256", "confirm" . $post->id, true)); if ($confirm !== $expectedConfirm) { http_response_code(400); - Messaging::error("Invalid confirmation"); + msg_error("Invalid confirmation"); exit; } @@ -856,14 +922,14 @@ if ($_action === "auth") { if (!$db->update($post)) { http_response_code(500); - Messaging::error("Failed to delete post"); + msg_error("Failed to delete post"); exit; } foreach ($attachments as $attachment) { if (!$db->delete($attachment)) { http_response_code(500); - Messaging::error("Failed to delete attachment"); + msg_error("Failed to delete attachment"); exit; } } @@ -886,7 +952,7 @@ if ($_action === "auth") { if (!$currentUser) { http_response_code(403); - Messaging::error("You need to be logged in to update posts!"); + msg_error(__("You need to be logged in to update posts!")); exit; } @@ -898,7 +964,7 @@ if ($_action === "auth") { if (!$db->fetch($post) || $post->deleted) { http_response_code(404); - Messaging::error("No post exists with this id"); + msg_error(__("No post exists with this id")); exit; } @@ -913,7 +979,7 @@ if ($_action === "auth") { if (!$canEdit) { http_response_code(403); - Messaging::error("You don't have permission to edit this post"); + msg_error(__("You don't have permission to edit this post")); exit; } @@ -924,7 +990,7 @@ if ($_action === "auth") { if (!$db->update($post)) { http_response_code(500); - Messaging::error("Failed to update post"); + msg_error(__("Failed to update post")); exit; } @@ -934,7 +1000,7 @@ if ($_action === "auth") { if (!$currentUser) { http_response_code(403); - Messaging::error("You need to be logged in to delete topics!"); + msg_error(__("You need to be logged in to delete topics!")); exit; } @@ -945,7 +1011,7 @@ if ($_action === "auth") { if (!$db->fetch($topic)) { http_response_code(404); - Messaging::error("No topic exists with this id"); + msg_error(__("No topic exists with this id")); exit; } @@ -960,7 +1026,7 @@ if ($_action === "auth") { if (!$canEdit) { http_response_code(403); - Messaging::error("You don't have permission to delete this topic"); + msg_error(__("You don't have permission to delete this topic")); exit; } @@ -969,13 +1035,13 @@ if ($_action === "auth") { $expectedConfirm = base64_encode(hash("sha256", "confirm" . $topic->id, true)); if ($confirm !== $expectedConfirm) { http_response_code(400); - Messaging::error("Invalid confirmation"); + msg_error(__("Invalid confirmation")); exit; } if (!$db->delete($topic)) { http_response_code(500); - Messaging::error("Failed to delete topic"); + msg_error(__("Failed to delete topic")); exit; } @@ -996,7 +1062,7 @@ if ($_action === "auth") { if (!$currentUser) { http_response_code(403); - Messaging::error("You need to be logged in to update topics!"); + msg_error(__("You need to be logged in to update topics!")); exit; } @@ -1008,7 +1074,7 @@ if ($_action === "auth") { if (!$db->fetch($topic)) { http_response_code(404); - Messaging::error("No topic exists with this id"); + msg_error(__("No topic exists with this id")); exit; } @@ -1023,7 +1089,7 @@ if ($_action === "auth") { if (!$canEdit) { http_response_code(403); - Messaging::error("You don't have permission to update this topic"); + msg_error(__("You don't have permission to update this topic")); exit; } @@ -1031,7 +1097,7 @@ if ($_action === "auth") { if (!$db->update($topic)) { http_response_code(500); - Messaging::error("Failed to update topic"); + msg_error(__("Failed to update topic")); exit; } @@ -1045,6 +1111,84 @@ if ($_action === "auth") { header("Pragma: no-cache"); header("Cache-Control: no-cache"); $builder->save(null, 40); +} elseif ($_action === "ji18n") { + header("Content-Type: application/javascript; charset=UTF-8"); + echo 'var I18N_MESSAGES = ' . json_encode(i18n_get_message_store(i18n_get_current_locale()), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . ";\n"; +} elseif ($_action === "ctheme") { + // options + $enableLogging = true; + $etag_strip_gzip_suffix = true; + + $cssFatal = function(string $msg): never { + if (!headers_sent()) + http_response_code(500); + echo "/*!FATAL $msg */\n"; + exit; + }; + $cssError = function(string &$buffer, string $msg) use($enableLogging): void { + if ($enableLogging) + $buffer .= "/*!ERROR $msg */\n"; + }; + $cssWarning = function(string &$buffer, string $msg) use ($enableLogging): void { + if ($enableLogging) + $buffer .= "/*!WARN $msg */\n"; + }; + + $buffer = ""; + + header("Content-Type: text/css; charset=UTF-8"); + header("Cache-Control: no-cache"); + // Disable Apache's gzip filter, as it interferes with our ETag + // (Apache adds '-gzip' as a ETag suffix) + if (!$etag_strip_gzip_suffix) + apache_setenv('no-gzip', '1'); + + $themeName = $_GET["theme"] ?? $_COOKIE["theme"] ?? env("MYSTIC_FORUM_THEME") ?? "default"; + if (!preg_match('/^[a-z0-9_-]+$/i', $themeName)) { + $cssWarning($buffer, "Invalid theme '" . str_replace('*/', '*\\/', $themeName) . "'"); + $cssWarning($buffer, "Loading default theme"); + $themeName = "default"; + } + $themePath = __DIR__ . '/themes/' . $themeName . '/theme.json'; + $themeDefaultPath = __DIR__ . '/themes/default/theme.json'; + if (!is_file($themePath) && is_file($themeDefaultPath)) { + $cssWarning($buffer, "Invalid theme '" . str_replace('*/', '*\\/', $themeName) . "'"); + $cssWarning($buffer, "Loading default theme"); + $themePath = $themeDefaultPath; + } elseif (!is_file($themePath) && !is_file($themeDefaultPath)) { + $cssFatal("Failed to load default theme"); + } + $themeDir = dirname($themePath); + $theme = json_decode(file_get_contents($themePath)); + if ($theme->{'$format'} !== 1) + $cssFatal("Invalid theme format"); + foreach ($theme->files as $file) { + if (is_array($file)) { + if ($enableLogging) $buffer .= "/*!INLINE start */\n"; + $buffer .= implode("\n", $file); + if ($enableLogging) $buffer .= "/*!INLINE end */\n"; + } elseif (is_file($filePath = $themeDir . "/" . $file)) { + if ($enableLogging) $buffer .= "/*!INCLUDE " . basename($file) . " */\n"; + $buffer .= file_get_contents($filePath); + if ($enableLogging) $buffer .= "/*!INCLUDE end */\n"; + } else + $cssError($buffer, "Could not include file $file"); + } + $etag = md5($buffer); + + $ifNoneMatch = $_SERVER["HTTP_IF_NONE_MATCH"] ?? null; + if ($ifNoneMatch !== null) { + $ifNoneMatch = trim($ifNoneMatch, '"'); + if ($etag_strip_gzip_suffix) + $ifNoneMatch = preg_replace('/-gzip$/', '', $ifNoneMatch); + } + + header("ETag: \"$etag\""); + if ($ifNoneMatch === $etag) + http_response_code(304); + else + echo $buffer; + } elseif ($_action === null) { _view("template_start"); _view("template_navigation_start"); @@ -1054,5 +1198,5 @@ if ($_action === "auth") { _view("template_end"); } else { http_response_code(404); - Messaging::error("Invalid or unknown action $_action"); + msg_error(__("Invalid or unknown action $_action")); } |