diff --git a/public/auth.php b/public/auth.php index 3dd675c0..701bd13b 100644 --- a/public/auth.php +++ b/public/auth.php @@ -13,9 +13,7 @@ $usernameValidationErrors = [ 'trim' => 'Your username may not start or end with spaces!', 'short' => sprintf('Your username is too short, it has to be at least %d characters!', MSZ_USERNAME_MIN_LENGTH), 'long' => sprintf("Your username is too long, it can't be longer than %d characters!", MSZ_USERNAME_MAX_LENGTH), - 'double-spaces' => "Your username can't contain double spaces.", 'invalid' => 'Your username contains invalid characters.', - 'spacing' => 'Please use either underscores or spaces, not both!', 'in-use' => 'This username is already taken!', ]; diff --git a/src/Users/user.php b/src/Users/user.php index 7b6df0e1..231c9784 100644 --- a/src/Users/user.php +++ b/src/Users/user.php @@ -48,6 +48,20 @@ function user_password_hash(string $password): string return password_hash($password, MSZ_USERS_PASSWORD_HASH_ALGO); } +function user_id_from_username(string $username): int +{ + $getId = Database::prepare('SELECT `user_id` FROM `msz_users` WHERE LOWER(`username`) = LOWER(:username)'); + $getId->bindValue('username', $username); + return $getId->execute() ? (int)$getId->fetchColumn() : 0; +} + +function user_username_from_id(int $userId): string +{ + $getId = Database::prepare('SELECT `username` FROM `msz_users` WHERE `user_id` = :user_id'); + $getId->bindValue('user_id', $userId); + return $getId->execute() ? (string)$getId->fetchColumn() : 'deleted'; +} + define('MSZ_USER_AVATAR_FORMAT', '%d.msz'); function user_avatar_delete(int $userId): void diff --git a/src/Users/validation.php b/src/Users/validation.php index 608174ad..21567d23 100644 --- a/src/Users/validation.php +++ b/src/Users/validation.php @@ -8,7 +8,8 @@ define('MSZ_USERNAME_MIN_LENGTH', 3); define('MSZ_USERNAME_MAX_LENGTH', 16); // Username character constraint. -define('MSZ_USERNAME_REGEX', '#^[A-Za-z0-9-_]+$#u'); +define('MSZ_USERNAME_REGEX', '[A-Za-z0-9-_]+'); +define('MSZ_USERNAME_REGEX_FULL', '#^' . MSZ_USERNAME_REGEX . '$#u'); // Minimum entropy value for passwords. define('MSZ_PASSWORD_MIN_ENTROPY', 32); @@ -29,18 +30,10 @@ function user_validate_username(string $username, bool $checkInUse = false): str return 'long'; } - if (strpos($username, ' ') !== false) { - return 'double-spaces'; - } - - if (!preg_match(MSZ_USERNAME_REGEX, $username)) { + if (!preg_match(MSZ_USERNAME_REGEX_FULL, $username)) { return 'invalid'; } - if (strpos($username, '_') !== false && strpos($username, ' ') !== false) { - return 'spacing'; - } - if ($checkInUse) { $getUser = Database::prepare(' SELECT COUNT(`user_id`) diff --git a/src/comments.php b/src/comments.php index 3207f4bd..b8d969db 100644 --- a/src/comments.php +++ b/src/comments.php @@ -1,6 +1,8 @@ MSZ_COMMENTS_VOTE_DISLIKE, ]); +// gets parsed on post +define('MSZ_COMMENTS_MARKUP_USERNAME', '#\B(?:@{1}(' . MSZ_USERNAME_REGEX . '))#u'); + +// gets parsed on fetch +define('MSZ_COMMENTS_MARKUP_USER_ID', '#\B(?:@{2}([0-9]+))#u'); + +function comments_parse_for_store(string $text): string +{ + return preg_replace_callback(MSZ_COMMENTS_MARKUP_USERNAME, function ($matches) { + return ($userId = user_id_from_username($matches[1])) < 1 + ? $matches[0] + : "@@{$userId}"; + }, $text); +} + +function comments_parse_for_display(string $text): string +{ + return preg_replace_callback(MSZ_COMMENTS_MARKUP_USER_ID, function ($matches) { + return '@' . user_username_from_id($matches[1]) . ''; + }, $text); +} + // usually this is not how you're suppose to handle permission checking, // but in the context of comments this is fine since the same shit is used // for every comment section. @@ -202,14 +226,25 @@ function comments_category_get(int $category, int $user, ?int $parent = null): a $commentsCount = count($comments); for ($i = 0; $i < $commentsCount; $i++) { + $comments[$i]['comment_html'] = nl2br(comments_parse_for_display(htmlentities($comments[$i]['comment_text']))); $comments[$i]['comment_replies'] = comments_category_get($category, $user, $comments[$i]['comment_id']); } return $comments; } -function comments_post_create(int $user, int $category, string $text, bool $pinned = false, ?int $reply = null): int -{ +function comments_post_create( + int $user, + int $category, + string $text, + bool $pinned = false, + ?int $reply = null, + bool $parse = true +): int { + if ($parse) { + $text = comments_parse_for_store($text); + } + $create = Database::prepare(' INSERT INTO `msz_comments_posts` (`user_id`, `category_id`, `comment_text`, `comment_pinned`, `comment_reply_to`) @@ -236,7 +271,7 @@ function comments_post_delete(int $commentId, bool $delete = true): bool return $deleteComment->execute(); } -function comments_post_get(int $commentId): array +function comments_post_get(int $commentId, bool $parse = true): array { $fetch = Database::prepare(' SELECT @@ -253,7 +288,14 @@ function comments_post_get(int $commentId): array WHERE `comment_id` = :id '); $fetch->bindValue('id', $commentId); - return $fetch->execute() ? $fetch->fetch(PDO::FETCH_ASSOC) : []; + $comment = $fetch->execute() ? $fetch->fetch(PDO::FETCH_ASSOC) : false; + $comment = $comment ? $comment : []; // prevent type errors + + if ($comment && $parse) { + $comment['comment_html'] = nl2br(comments_parse_for_display(htmlentities($comment['comment_text']))); + } + + return $comment; } function comments_post_exists(int $commentId): bool diff --git a/templates/_layout/comments.twig b/templates/_layout/comments.twig index e309b8a4..12f7e563 100644 --- a/templates/_layout/comments.twig +++ b/templates/_layout/comments.twig @@ -80,7 +80,7 @@ {% endif %}