From 93817fef3ead7cfd4fcde25ea2bcec02d01310a4 Mon Sep 17 00:00:00 2001 From: Jonas Kohl Date: Fri, 13 Sep 2024 19:57:43 +0200 Subject: A lot of changes again --- src/index.php | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 175 insertions(+), 18 deletions(-) (limited to 'src/index.php') diff --git a/src/index.php b/src/index.php index 109cd2e..1cc0d01 100644 --- a/src/index.php +++ b/src/index.php @@ -8,15 +8,16 @@ use mystic\forum\orm\Post; use mystic\forum\orm\Topic; use mystic\forum\orm\User; use mystic\forum\orm\UserPermissions; +use mystic\forum\utils\FileUtils; use mystic\forum\utils\RequestUtils; use mystic\forum\utils\ValidationUtils; header_remove("X-Powered-By"); -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"); +// 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", true); @@ -34,6 +35,7 @@ $GLOBALS["action"] = $_action; function _view(string $name, array $params = []): void { $___NAME = $name; + $___PARAMS = &$params; extract($params); echo "\n"; include __DIR__ . "/application/views/" . $___NAME . ".php"; @@ -425,12 +427,99 @@ if ($_action === "auth") { Messaging::error("No user exists with this id"); exit; } - _view("template_start", ["_title" => "Forum"]); - _view("template_navigation_start"); - _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); - _view("template_navigation_end"); - _view("view_user", ["user" => $user]); - _view("template_end"); + + $lastNameChangeTooRecent = false; + $isOwnProfile = $user->id === $currentUser?->id; + if ($isOwnProfile && $user->nameLastChanged !== null) { + $diff = $user->nameLastChanged->diff(new DateTime()); + $diffSeconds = (new DateTime())->setTimestamp(0)->add($diff)->getTimestamp(); + $diffDays = $diffSeconds / 60.0 / 60.0 / 24.0 / 30.0; + $lastNameChangeTooRecent = $diffDays <= 30; + } + + if (RequestUtils::isRequestMethod("POST")) { + $displayName = RequestUtils::getRequiredField("display_name"); + $pfpAction = RequestUtils::getRequiredField("pfp_action"); + + $userName = $_POST["name"] ?? $user->name; + + $user->displayName = $displayName; + + $userName = strtolower($userName); + + if ($userName !== $user->name) { + if ($lastNameChangeTooRecent) { + RequestUtils::triggerFormError("You can only change your username every 30 days!"); + } else { + if (!ValidationUtils::isUsernameValid($userName)) + RequestUtils::triggerFormError("Invalid username!"); + if (!ValidationUtils::isUsernameAvailable($db, $userName)) + RequestUtils::triggerFormError("This username is already taken!"); + $user->name = $userName; + $user->nameLastChanged = new DateTimeImmutable(); + } + } + + switch ($pfpAction) { + case "keep": + // Do nothing + break; + case "remove": + $user->profilePicture = null; + break; + case "replace": { + if (!isset($_FILES["pfp"]) || $_FILES["pfp"]["error"] !== UPLOAD_ERR_OK) { + RequestUtils::triggerFormError("Please upload an image to change your profile picture"); + } + $im = @imagecreatefromjpeg($_FILES["pfp"]["tmp_name"]); + if ($im === false) + $im = @imagecreatefrompng($_FILES["pfp"]["tmp_name"]); + if ($im === false) + RequestUtils::triggerFormError("Please upload a valid PNG or JPEG file"); + /** @var \GdImage $im */ + $thumb = imagecreatetruecolor(64, 64); + imagecopyresampled($thumb, $im, 0, 0, 0, 0, 64, 64, imagesx($im), imagesy($im)); + imagedestroy($im); + $stream = fopen("php://memory", "w+"); + imagejpeg($thumb, $stream, 50); + rewind($stream); + imagedestroy($thumb); + $user->profilePicture = stream_get_contents($stream); + fclose($stream); + } break; + default: + RequestUtils::triggerFormError("Invalid value for pfp_action"); + break; + } + + if (!$db->update($user)) + RequestUtils::triggerFormError("Failed to save changes"); + + header("Location: $_SERVER[REQUEST_URI]"); + } else { + $posts = $db->fetchCustom(Post::class, 'WHERE author_id = $1 ORDER BY post_date DESC', [ $userId ]); + $topics = []; + 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; + } + _view("template_start", ["_title" => "Forum"]); + _view("template_navigation_start"); + _view("template_navigation", ["user" => $currentUser]); + _view("template_navigation_end"); + _view("view_user", [ + "user" => $user, + "posts" => $posts, + "topics" => $topics, + "lastNameChangeTooRecent" => $lastNameChangeTooRecent, + ]); + _view("template_end"); + } } elseif ($_action === "attachment") { if (!$currentUser) { http_response_code(403); @@ -447,10 +536,48 @@ if ($_action === "auth") { exit; } - header("Content-Type: " . $attachment->mimeType); + $extension = pathinfo($attachment->name, PATHINFO_EXTENSION); + header("Content-Type: " . FileUtils::getMimeTypeForExtension($extension)); header("Content-Length: " . strlen($attachment->contents)); header("Cache-Control: no-cache"); + header("Content-Disposition: inline; filename=\"" . $attachment->name . "\""); echo $attachment->contents; +} elseif ($_action === "profilepicture") { + $userId = $_GET["user"] ?? throw new Exception("Missing user id"); + $user = new User(); + $user->id = $userId; + if (!$db->fetch($user)) { + http_response_code(404); + Messaging::error("No user exists with this id"); + exit; + } + + $ifNoneMatch = $_SERVER["HTTP_IF_NONE_MATCH"] ?? null; + if ($ifNoneMatch !== null) + $ifNoneMatch = trim($ifNoneMatch, '"'); + + if ($user->profilePicture === null) { + $fallback = __DIR__ . "/application/assets/user-fallback.jpg"; + $etag = md5("\0"); + header("Content-Type: image/jpeg"); + header("Content-Length: " . filesize($fallback)); + header("Cache-Control: no-cache"); + header("ETag: \"" . $etag . "\""); + if ($ifNoneMatch === $etag) + http_response_code(304); + else + readfile($fallback); + } else { + $etag = md5($user->profilePicture); + header("Content-Type: image/jpeg"); + header("Content-Length: " . strlen($user->profilePicture)); + header("Cache-Control: no-cache"); + header("ETag: \"" . $etag . "\""); + if ($ifNoneMatch === $etag) + http_response_code(304); + else + echo $user->profilePicture; + } } elseif ($_action === "thumb") { $attId = $_GET["attachment"] ?? throw new Exception("Missing attachment id"); @@ -468,7 +595,27 @@ if ($_action === "auth") { exit; } - // TODO Cache thumbnail + $contentHash = hash("sha256", $attachment->contents); + + $cacheId = bin2hex($attachment->id); + $cacheDir = sys_get_temp_dir() . "/mystic/forum/0/cache/thumbs/" . substr($cacheId, 0, 2) . "/" . substr($cacheId, 0, 8) . "/"; + if (!is_dir($cacheDir)) + mkdir($cacheDir, recursive: true); + + $cacheFileData = $cacheDir . $cacheId . ".data"; + $cacheFileInfo = $cacheDir . $cacheId . ".info"; + + if (is_file($cacheFileData) && is_file($cacheFileInfo)) { + $info = json_decode(file_get_contents($cacheFileInfo)); + if ($info->contentHash === $contentHash) { + header("Content-Type: image/jpeg"); + header("Cache-Control: max-age=86400"); + header("X-Debug-Content: $cacheFileData"); + readfile($cacheFileData); + exit; + } + } + $im = imagecreatefromstring($attachment->contents); $w = imagesx($im); $h = imagesy($im); @@ -487,9 +634,15 @@ if ($_action === "auth") { imagedestroy($im); header("Content-Type: image/jpeg"); - header("Cache-Control: no-cache"); - imagejpeg($thumb, null, 40); + header("Cache-Control: max-age=86400"); + imagejpeg($thumb, $cacheFileData, 40); imagedestroy($thumb); + file_put_contents($cacheFileInfo, json_encode([ + "format" => 1, + "contentHash" => $contentHash, + "created" => time(), + ], JSON_UNESCAPED_SLASHES)); + readfile($cacheFileData); } elseif ($_action === "deletepost") { RequestUtils::ensureRequestMethod("POST"); @@ -504,7 +657,7 @@ if ($_action === "auth") { $post = new Post(); $post->id = $postId; - if (!$db->fetch($post)) { + if (!$db->fetch($post) || $post->deleted) { http_response_code(404); Messaging::error("No post exists with this id"); exit; @@ -525,6 +678,8 @@ if ($_action === "auth") { exit; } + $attachments = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $post->id ]); + $confirm = $_POST["confirm"] ?? null; if ($confirm !== null) { $expectedConfirm = base64_encode(hash("sha256", "confirm" . $post->id, true)); @@ -542,8 +697,6 @@ if ($_action === "auth") { Messaging::error("Failed to delete post"); exit; } - - $attachments = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $post->id ]); foreach ($attachments as $attachment) { if (!$db->delete($attachment)) { @@ -559,7 +712,11 @@ if ($_action === "auth") { _view("template_navigation_start"); _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); _view("template_navigation_end"); - _view("form_delete_post_confirm", ["post" => $post]); + _view("form_delete_post_confirm", [ + "post" => $post, + "postAuthor" => $postAuthor, + "attachments" => $attachments, + ]); _view("template_end"); } } elseif ($_action === null) { -- cgit v1.2.3