//further progress
This commit is contained in:
parent
7ea5e9414d
commit
ceb05fc3f7
15 changed files with 373 additions and 651 deletions
|
@ -77,10 +77,8 @@ require_once 'src/manage.php';
|
|||
require_once 'src/url.php';
|
||||
require_once 'src/Forum/perms.php';
|
||||
require_once 'src/Forum/forum.php';
|
||||
require_once 'src/Forum/leaderboard.php';
|
||||
require_once 'src/Forum/post.php';
|
||||
require_once 'src/Forum/topic.php';
|
||||
require_once 'src/Forum/validate.php';
|
||||
|
||||
$dbConfig = parse_ini_file(MSZ_CONFIG . '/config.ini', true, INI_SCANNER_TYPED);
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Forum\ForumLeaderboard;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once '../../misuzu.php';
|
||||
|
@ -14,7 +15,7 @@ $leaderboardMode = !empty($_GET['mode']) && is_string($_GET['mode']) && ctype_lo
|
|||
$leaderboardId = !empty($_GET['id']) && is_string($_GET['id'])
|
||||
&& ctype_digit($_GET['id'])
|
||||
? $_GET['id']
|
||||
: MSZ_FORUM_LEADERBOARD_CATEGORY_ALL;
|
||||
: ForumLeaderboard::CATEGORY_ALL;
|
||||
$leaderboardIdLength = strlen($leaderboardId);
|
||||
|
||||
$leaderboardYear = $leaderboardIdLength === 4 || $leaderboardIdLength === 6 ? substr($leaderboardId, 0, 4) : null;
|
||||
|
@ -22,8 +23,8 @@ $leaderboardMonth = $leaderboardIdLength === 6 ? substr($leaderboardId, 4, 2) :
|
|||
|
||||
$unrankedForums = !empty($_GET['allow_unranked']) ? [] : Config::get('forum_leader.unranked.forum', Config::TYPE_ARR);
|
||||
$unrankedTopics = !empty($_GET['allow_unranked']) ? [] : Config::get('forum_leader.unranked.topic', Config::TYPE_ARR);
|
||||
$leaderboards = forum_leaderboard_categories();
|
||||
$leaderboard = forum_leaderboard_listing($leaderboardYear, $leaderboardMonth, $unrankedForums, $unrankedTopics);
|
||||
$leaderboards = ForumLeaderboard::categories();
|
||||
$leaderboard = ForumLeaderboard::listing($leaderboardYear, $leaderboardMonth, $unrankedForums, $unrankedTopics);
|
||||
|
||||
$leaderboardName = 'All Time';
|
||||
|
||||
|
|
|
@ -5,6 +5,10 @@ use Misuzu\Forum\ForumCategory;
|
|||
use Misuzu\Forum\ForumCategoryNotFoundException;
|
||||
use Misuzu\Forum\ForumTopic;
|
||||
use Misuzu\Forum\ForumTopicNotFoundException;
|
||||
use Misuzu\Forum\ForumTopicCreationFailedException;
|
||||
use Misuzu\Forum\ForumTopicUpdateFailedException;
|
||||
use Misuzu\Forum\ForumPost;
|
||||
use Misuzu\Forum\ForumPostNotFoundException;
|
||||
use Misuzu\Net\IPAddress;
|
||||
use Misuzu\Parsers\Parser;
|
||||
use Misuzu\Users\User;
|
||||
|
@ -130,6 +134,7 @@ if($mode === 'edit') {
|
|||
}
|
||||
|
||||
$notices = [];
|
||||
$isNewTopic = false;
|
||||
|
||||
if(!empty($_POST)) {
|
||||
$topicTitle = $_POST['post']['title'] ?? '';
|
||||
|
@ -141,7 +146,7 @@ if(!empty($_POST)) {
|
|||
if(!CSRF::validateRequest()) {
|
||||
$notices[] = 'Could not verify request.';
|
||||
} else {
|
||||
$isEditingTopic = empty($topicInfo) || ($mode === 'edit' && $post['is_opening_post']);
|
||||
$isEditingTopic = $isNewTopic || ($mode === 'edit' && $post['is_opening_post']);
|
||||
|
||||
if($mode === 'create') {
|
||||
$timeoutCheck = max(1, forum_timeout($forumInfo->getId(), $currentUserId));
|
||||
|
@ -153,20 +158,14 @@ if(!empty($_POST)) {
|
|||
}
|
||||
|
||||
if($isEditingTopic) {
|
||||
$originalTopicTitle = empty($topicInfo) ? null : $topicInfo->getTitle();
|
||||
$originalTopicTitle = $isNewTopic ? null : $topicInfo->getTitle();
|
||||
$topicTitleChanged = $topicTitle !== $originalTopicTitle;
|
||||
$originalTopicType = empty($topicInfo) ? ForumTopic::TYPE_DISCUSSION : $topicInfo->getType();
|
||||
$originalTopicType = $isNewTopic ? ForumTopic::TYPE_DISCUSSION : $topicInfo->getType();
|
||||
$topicTypeChanged = $topicType !== null && $topicType !== $originalTopicType;
|
||||
|
||||
switch(forum_validate_title($topicTitle)) {
|
||||
case 'too-short':
|
||||
$notices[] = 'Topic title was too short.';
|
||||
break;
|
||||
|
||||
case 'too-long':
|
||||
$notices[] = 'Topic title was too long.';
|
||||
break;
|
||||
}
|
||||
$validateTopicTitle = ForumTopic::validateTitle($topicTitle);
|
||||
if(!empty($validateTopicTitle))
|
||||
$notices[] = ForumTopic::titleValidationErrorString($validateTopicTitle);
|
||||
|
||||
if($mode === 'create' && $topicType === null) {
|
||||
$topicType = array_key_first($topicTypes);
|
||||
|
@ -175,19 +174,12 @@ if(!empty($_POST)) {
|
|||
}
|
||||
}
|
||||
|
||||
if(!Parser::isValid($postParser)) {
|
||||
if(!Parser::isValid($postParser))
|
||||
$notices[] = 'Invalid parser selected.';
|
||||
}
|
||||
|
||||
switch(forum_validate_post($postText)) {
|
||||
case 'too-short':
|
||||
$notices[] = 'Post content was too short.';
|
||||
break;
|
||||
|
||||
case 'too-long':
|
||||
$notices[] = 'Post content was too long.';
|
||||
break;
|
||||
}
|
||||
$postBodyValidation = ForumPost::validateBody($postText);
|
||||
if(!empty($postBodyValidation))
|
||||
$notices[] = ForumPost::bodyValidationErrorString($postBodyValidation);
|
||||
|
||||
if(empty($notices)) {
|
||||
switch($mode) {
|
||||
|
@ -195,12 +187,9 @@ if(!empty($_POST)) {
|
|||
if(!empty($topicInfo)) {
|
||||
$topicInfo->bumpTopic();
|
||||
} else {
|
||||
$topicId = forum_topic_create(
|
||||
$forumInfo->getId(),
|
||||
$currentUserId,
|
||||
$topicTitle,
|
||||
$topicType
|
||||
);
|
||||
$isNewTopic = true;
|
||||
$topicInfo = ForumTopic::create($forumInfo, $currentUser, $topicTitle, $topicType);
|
||||
$topicId = $topicInfo->getId();
|
||||
}
|
||||
|
||||
$postId = forum_post_create(
|
||||
|
@ -213,7 +202,7 @@ if(!empty($_POST)) {
|
|||
$postSignature
|
||||
);
|
||||
forum_topic_mark_read($currentUserId, $topicId, $forumInfo->getId());
|
||||
$forumInfo->increaseTopicPostCount(empty($topicInfo));
|
||||
$forumInfo->increaseTopicPostCount($isNewTopic);
|
||||
break;
|
||||
|
||||
case 'edit':
|
||||
|
@ -222,7 +211,11 @@ if(!empty($_POST)) {
|
|||
}
|
||||
|
||||
if($isEditingTopic && ($topicTitleChanged || $topicTypeChanged)) {
|
||||
if(!forum_topic_update($topicId, $topicTitle, $topicType)) {
|
||||
$topicInfo->setTitle($topicTitle)->setType($topicType);
|
||||
|
||||
try {
|
||||
$topicInfo->update();
|
||||
} catch(ForumTopicUpdateFailedException $ex) {
|
||||
$notices[] = 'Topic update failed.';
|
||||
}
|
||||
}
|
||||
|
@ -230,7 +223,7 @@ if(!empty($_POST)) {
|
|||
}
|
||||
|
||||
if(empty($notices)) {
|
||||
$redirect = url(empty($topicInfo) ? 'forum-topic' : 'forum-post', [
|
||||
$redirect = url($isNewTopic ? 'forum-topic' : 'forum-post', [
|
||||
'topic' => $topicId ?? 0,
|
||||
'post' => $postId ?? 0,
|
||||
'post_fragment' => 'p' . ($postId ?? 0),
|
||||
|
@ -242,7 +235,7 @@ if(!empty($_POST)) {
|
|||
}
|
||||
}
|
||||
|
||||
if(!empty($topicInfo)) {
|
||||
if(!$isNewTopic && !empty($topicInfo)) {
|
||||
Template::set('posting_topic', $topicInfo);
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ $canNukeOrRestore = $canDeleteAny && $topicInfo->isDeleted();
|
|||
$canDelete = !$topicInfo->isDeleted() && (
|
||||
$canDeleteAny || (
|
||||
$topicPostsTotal > 0
|
||||
&& $topicPostsTotal <= MSZ_FORUM_TOPIC_DELETE_POST_LIMIT
|
||||
&& $topicPostsTotal <= ForumTopic::DELETE_POST_LIMIT
|
||||
&& $canDeleteOwn
|
||||
&& $topicInfo->getUserId() === $topicUserId
|
||||
)
|
||||
|
@ -104,47 +104,20 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
|||
|
||||
switch($moderationMode) {
|
||||
case 'delete':
|
||||
$canDeleteCode = forum_topic_can_delete($topicInfo, $topicUserId);
|
||||
$canDeleteMsg = '';
|
||||
$responseCode = 200;
|
||||
$canDeleteCodes = [
|
||||
'view' => 404,
|
||||
'deleted' => 404,
|
||||
'owner' => 403,
|
||||
'age' => 403,
|
||||
'permission' => 403,
|
||||
'posts' => 403,
|
||||
'' => 200,
|
||||
];
|
||||
$canDelete = $topicInfo->canDelete($topicUser);
|
||||
$canDeleteMsg = ForumTopic::canDeleteErrorString($canDelete);
|
||||
$responseCode = $canDeleteCodes[$canDelete] ?? 500;
|
||||
|
||||
switch($canDeleteCode) {
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_USER:
|
||||
$responseCode = 401;
|
||||
$canDeleteMsg = 'You must be logged in to delete topics.';
|
||||
break;
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_TOPIC:
|
||||
$responseCode = 404;
|
||||
$canDeleteMsg = "This topic doesn't exist.";
|
||||
break;
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_DELETED:
|
||||
$responseCode = 404;
|
||||
$canDeleteMsg = 'This topic has already been marked as deleted.';
|
||||
break;
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_OWNER:
|
||||
$responseCode = 403;
|
||||
$canDeleteMsg = 'You can only delete your own topics.';
|
||||
break;
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_OLD:
|
||||
$responseCode = 401;
|
||||
$canDeleteMsg = 'This topic has existed for too long. Ask a moderator to remove if it absolutely necessary.';
|
||||
break;
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_PERM:
|
||||
$responseCode = 401;
|
||||
$canDeleteMsg = 'You are not allowed to delete topics.';
|
||||
break;
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_POSTS:
|
||||
$responseCode = 403;
|
||||
$canDeleteMsg = 'This topic already has replies, you may no longer delete it. Ask a moderator to remove if it absolutely necessary.';
|
||||
break;
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_OK:
|
||||
break;
|
||||
default:
|
||||
$responseCode = 500;
|
||||
$canDeleteMsg = sprintf('Unknown error \'%d\'', $canDelete);
|
||||
}
|
||||
|
||||
if($canDeleteCode !== MSZ_E_FORUM_TOPIC_DELETE_OK) {
|
||||
if($canDelete !== '') {
|
||||
if($isXHR) {
|
||||
http_response_code($responseCode);
|
||||
echo json_encode([
|
||||
|
@ -181,26 +154,18 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
|||
}
|
||||
}
|
||||
|
||||
$deleteTopic = forum_topic_delete($topicInfo->getId());
|
||||
|
||||
if($deleteTopic) {
|
||||
AuditLog::create(AuditLog::FORUM_TOPIC_DELETE, [$topicInfo->getId()]);
|
||||
}
|
||||
$topicInfo->delete();
|
||||
AuditLog::create(AuditLog::FORUM_TOPIC_DELETE, [$topicInfo->getId()]);
|
||||
|
||||
if($isXHR) {
|
||||
echo json_encode([
|
||||
'success' => $deleteTopic,
|
||||
'success' => true,
|
||||
'topic_id' => $topicInfo->getId(),
|
||||
'message' => $deleteTopic ? 'Topic deleted!' : 'Failed to delete topic.',
|
||||
'message' => 'Topic deleted!',
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!$deleteTopic) {
|
||||
echo render_error(500);
|
||||
break;
|
||||
}
|
||||
|
||||
url_redirect('forum-category', [
|
||||
'forum' => $topicInfo->getCategoryId(),
|
||||
]);
|
||||
|
@ -232,13 +197,7 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
|||
}
|
||||
}
|
||||
|
||||
$restoreTopic = forum_topic_restore($topicInfo->getId());
|
||||
|
||||
if(!$restoreTopic) {
|
||||
echo render_error(500);
|
||||
break;
|
||||
}
|
||||
|
||||
$topicInfo->restore();
|
||||
AuditLog::create(AuditLog::FORUM_TOPIC_RESTORE, [$topicInfo->getId()]);
|
||||
http_response_code(204);
|
||||
|
||||
|
@ -275,13 +234,7 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
|||
}
|
||||
}
|
||||
|
||||
$nukeTopic = forum_topic_nuke($topicInfo->getId());
|
||||
|
||||
if(!$nukeTopic) {
|
||||
echo render_error(500);
|
||||
break;
|
||||
}
|
||||
|
||||
$topicInfo->nuke();
|
||||
AuditLog::create(AuditLog::FORUM_TOPIC_NUKE, [$topicInfo->getId()]);
|
||||
http_response_code(204);
|
||||
|
||||
|
|
|
@ -344,50 +344,19 @@ switch($profileMode) {
|
|||
|
||||
case 'forum-topics':
|
||||
$template = 'profile.topics';
|
||||
$topicsCount = forum_topic_count_user($profileUser->getId(), $currentUserId);
|
||||
$topicsPagination = new Pagination($topicsCount, 20);
|
||||
|
||||
if(!$topicsPagination->hasValidOffset()) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$topics = forum_topic_listing_user(
|
||||
$profileUser->getId(), $currentUserId,
|
||||
$topicsPagination->getOffset(), $topicsPagination->getRange()
|
||||
);
|
||||
|
||||
Template::set([
|
||||
'title' => $profileUser->getUsername() . ' / topics',
|
||||
'canonical_url' => url('user-profile-forum-topics', ['user' => $profileUser->getId(), 'page' => Pagination::param()]),
|
||||
'profile_topics' => $topics,
|
||||
'profile_topics_pagination' => $topicsPagination,
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'forum-posts':
|
||||
$template = 'profile.posts';
|
||||
$postsCount = forum_post_count_user($profileUser->getId());
|
||||
$postsPagination = new Pagination($postsCount, 20);
|
||||
|
||||
if(!$postsPagination->hasValidOffset()) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$posts = forum_post_listing(
|
||||
$profileUser->getId(),
|
||||
$postsPagination->getOffset(),
|
||||
$postsPagination->getRange(),
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
Template::set([
|
||||
'title' => $profileUser->getUsername() . ' / posts',
|
||||
'canonical_url' => url('user-profile-forum-posts', ['user' => $profileUser->getId(), 'page' => Pagination::param()]),
|
||||
'profile_posts' => $posts,
|
||||
'profile_posts_pagination' => $postsPagination,
|
||||
]);
|
||||
break;
|
||||
|
||||
|
|
|
@ -414,7 +414,7 @@ class ForumCategory {
|
|||
}
|
||||
|
||||
if($save && !$this->isRoot()) {
|
||||
$setCounts = \Misuzu\DB::prepare(
|
||||
$setCounts = DB::prepare(
|
||||
'UPDATE `msz_forum_categories`'
|
||||
. ' SET `forum_count_topics` = :topics, `forum_count_posts` = :posts'
|
||||
. ' WHERE `forum_id` = :forum'
|
||||
|
|
103
src/Forum/ForumLeaderboard.php
Normal file
103
src/Forum/ForumLeaderboard.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
namespace Misuzu\Forum;
|
||||
|
||||
use Misuzu\DB;
|
||||
|
||||
final class ForumLeaderboard {
|
||||
public const START_YEAR = 2018;
|
||||
public const START_MONTH = 12;
|
||||
public const CATEGORY_ALL = 0;
|
||||
|
||||
public static function isValidYear(?int $year): bool {
|
||||
return !is_null($year) && $year >= self::START_YEAR && $year <= date('Y');
|
||||
}
|
||||
|
||||
public static function isValidMonth(?int $year, ?int $month): bool {
|
||||
if(is_null($month) || !self::isValidYear($year) || $month < 1 || $month > 12)
|
||||
return false;
|
||||
|
||||
$combo = sprintf('%04d%02d', $year, $month);
|
||||
$start = sprintf('%04d%02d', self::START_YEAR, self::START_MONTH);
|
||||
$current = date('Ym');
|
||||
|
||||
return $combo >= $start && $combo <= $current;
|
||||
}
|
||||
|
||||
public static function categories(): array {
|
||||
$categories = [
|
||||
self::CATEGORY_ALL => 'All Time',
|
||||
];
|
||||
|
||||
$currentYear = date('Y');
|
||||
$currentMonth = date('m');
|
||||
|
||||
for($i = $currentYear; $i >= self::START_YEAR; $i--) {
|
||||
$categories[$i] = sprintf('Leaderboard %d', $i);
|
||||
}
|
||||
|
||||
for($i = $currentYear, $j = $currentMonth;;) {
|
||||
$categories[sprintf('%d%02d', $i, $j)] = sprintf('Leaderboard %d-%02d', $i, $j);
|
||||
|
||||
if($j <= 1) {
|
||||
$i--; $j = 12;
|
||||
} else $j--;
|
||||
|
||||
if($i <= self::START_YEAR && $j < self::START_MONTH)
|
||||
break;
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
public static function listing(
|
||||
?int $year = null,
|
||||
?int $month = null,
|
||||
array $unrankedForums = [],
|
||||
array $unrankedTopics = []
|
||||
): array {
|
||||
$hasYear = self::isValidYear($year);
|
||||
$hasMonth = $hasYear && self::isValidMonth($year, $month);
|
||||
$unrankedForums = implode(',', $unrankedForums);
|
||||
$unrankedTopics = implode(',', $unrankedTopics);
|
||||
|
||||
$rawLeaderboard = DB::query(sprintf(
|
||||
'
|
||||
SELECT
|
||||
u.`user_id`, u.`username`,
|
||||
COUNT(fp.`post_id`) as `posts`
|
||||
FROM `msz_users` AS u
|
||||
INNER JOIN `msz_forum_posts` AS fp
|
||||
ON fp.`user_id` = u.`user_id`
|
||||
WHERE fp.`post_deleted` IS NULL
|
||||
%s %s %s
|
||||
GROUP BY u.`user_id`
|
||||
HAVING `posts` > 0
|
||||
ORDER BY `posts` DESC
|
||||
',
|
||||
$unrankedForums ? sprintf('AND fp.`forum_id` NOT IN (%s)', $unrankedForums) : '',
|
||||
$unrankedTopics ? sprintf('AND fp.`topic_id` NOT IN (%s)', $unrankedTopics) : '',
|
||||
!$hasYear ? '' : sprintf(
|
||||
'AND DATE(fp.`post_created`) BETWEEN \'%1$04d-%2$02d-01\' AND \'%1$04d-%3$02d-31\'',
|
||||
$year,
|
||||
$hasMonth ? $month : 1,
|
||||
$hasMonth ? $month : 12
|
||||
)
|
||||
))->fetchAll();
|
||||
|
||||
$leaderboard = [];
|
||||
$ranking = 0;
|
||||
$lastPosts = null;
|
||||
|
||||
foreach($rawLeaderboard as $entry) {
|
||||
if(is_null($lastPosts) || $lastPosts > $entry['posts']) {
|
||||
$ranking++;
|
||||
$lastPosts = $entry['posts'];
|
||||
}
|
||||
|
||||
$entry['rank'] = $ranking;
|
||||
$leaderboard[] = $entry;
|
||||
}
|
||||
|
||||
return $leaderboard;
|
||||
}
|
||||
}
|
|
@ -15,6 +15,9 @@ class ForumPostCreationFailedException extends ForumPostException {}
|
|||
class ForumPost {
|
||||
public const PER_PAGE = 10;
|
||||
|
||||
public const BODY_MIN_LENGTH = 1;
|
||||
public const BODY_MAX_LENGTH = 60000;
|
||||
|
||||
// Database fields
|
||||
private $post_id = -1;
|
||||
private $topic_id = -1;
|
||||
|
@ -176,11 +179,6 @@ class ForumPost {
|
|||
public function isDeleted(): bool {
|
||||
return $this->getDeletedTime() >= 0;
|
||||
}
|
||||
public function setDeleted(bool $deleted): self {
|
||||
if($this->isDeleted() !== $deleted)
|
||||
$this->post_deleted = $deleted ? time() : null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isOpeningPost(): bool {
|
||||
return $this->getTopic()->isOpeningPost($this);
|
||||
|
@ -211,6 +209,58 @@ class ForumPost {
|
|||
return $this->getUser()->getId() === $user->getId();
|
||||
}
|
||||
|
||||
public static function validateBody(string $body): string {
|
||||
$length = mb_strlen(trim($body));
|
||||
if($length < self::BODY_MIN_LENGTH)
|
||||
return 'short';
|
||||
if($length > self::BODY_MAX_LENGTH)
|
||||
return 'long';
|
||||
return '';
|
||||
}
|
||||
public static function bodyValidationErrorString(string $error): string {
|
||||
switch($error) {
|
||||
case 'short':
|
||||
return sprintf('Post body was too short, it has to be at least %d characters!', self::BODY_MIN_LENGTH);
|
||||
case 'long':
|
||||
return sprintf("Post body was too long, it can't be longer than %d characters!", self::BODY_MAX_LENGTH);
|
||||
case '':
|
||||
return 'Post body is correctly formatted!';
|
||||
default:
|
||||
return 'Post body is incorrectly formatted.';
|
||||
}
|
||||
}
|
||||
|
||||
public static function deleteTopic(ForumTopic $topic): void {
|
||||
// Deleting posts should only be possible while the topic is already in a deleted state
|
||||
if(!$topic->isDeleted())
|
||||
return;
|
||||
DB::prepare(
|
||||
'UPDATE `' . DB::PREFIX . self::TABLE . '`'
|
||||
. ' SET `post_deleted` = NOW()'
|
||||
. ' WHERE `topic_id` = :topic'
|
||||
. ' AND `post_deleted` IS NULL'
|
||||
)->bind('topic', $topic->getId())->execute();
|
||||
}
|
||||
public static function restoreTopic(ForumTopic $topic): void {
|
||||
// This looks like an error but it's not, run this before restoring the topic
|
||||
if(!$topic->isDeleted())
|
||||
return;
|
||||
DB::prepare(
|
||||
'UPDATE `' . DB::PREFIX . self::TABLE . '`'
|
||||
. ' SET `post_deleted` = NULL'
|
||||
. ' WHERE `topic_id` = :topic'
|
||||
. ' AND `post_deleted` = FROM_UNIXTIME(:deleted)'
|
||||
)->bind('topic', $topic->getId())->bind('deleted', $topic->getDeletedTime())->execute();
|
||||
}
|
||||
public static function nukeTopic(ForumTopic $topic): void { // Does this need to exist? Happens implicitly through foreign keys.
|
||||
// Hard deleting should only be allowed if the topic is already soft deleted
|
||||
if(!$topic->isDeleted())
|
||||
return;
|
||||
DB::prepare('DELETE FROM `' . DB::PREFIX . self::TABLE . '` WHERE `topic_id` = :topic')
|
||||
->bind('topic', $topic->getId())
|
||||
->execute();
|
||||
}
|
||||
|
||||
private static function countQueryBase(): string {
|
||||
return sprintf(self::QUERY_SELECT, sprintf('COUNT(*)', self::TABLE));
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ use Misuzu\Users\User;
|
|||
|
||||
class ForumTopicException extends ForumException {}
|
||||
class ForumTopicNotFoundException extends ForumTopicException {}
|
||||
class ForumTopicCreationFailedException extends ForumTopicException {}
|
||||
class ForumTopicUpdateFailedException extends ForumTopicException {}
|
||||
|
||||
class ForumTopic {
|
||||
public const TYPE_DISCUSSION = 0;
|
||||
|
@ -35,6 +37,12 @@ class ForumTopic {
|
|||
self::TYPE_GLOBAL_ANNOUNCEMENT,
|
||||
];
|
||||
|
||||
public const TITLE_MIN_LENGTH = 3;
|
||||
public const TITLE_MAX_LENGTH = 100;
|
||||
|
||||
public const DELETE_AGE_LIMIT = 60 * 60 * 24;
|
||||
public const DELETE_POST_LIMIT = 2;
|
||||
|
||||
// Database fields
|
||||
private $topic_id = -1;
|
||||
private $forum_id = -1;
|
||||
|
@ -236,11 +244,6 @@ class ForumTopic {
|
|||
public function isDeleted(): bool {
|
||||
return $this->getDeletedTime() >= 0;
|
||||
}
|
||||
public function setDeleted(bool $deleted): self {
|
||||
if($this->isDeleted() !== $deleted)
|
||||
$this->topic_deleted = $deleted ? time() : null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLockedTime(): int {
|
||||
return $this->topic_locked === null ? -1 : $this->topic_locked;
|
||||
|
@ -274,7 +277,7 @@ class ForumTopic {
|
|||
public function hasUnread(?User $user): bool {
|
||||
if($user === null)
|
||||
return false;
|
||||
return true;
|
||||
return mt_rand(0, 10) >= 5;
|
||||
}
|
||||
|
||||
public function hasParticipated(?User $user): bool {
|
||||
|
@ -304,6 +307,112 @@ class ForumTopic {
|
|||
return $this->getCategory()->canView($user);
|
||||
}
|
||||
|
||||
public function canDelete(User $user): string {
|
||||
if(false) // check if viewable
|
||||
return 'view';
|
||||
|
||||
// check if user can view deleted posts/is mod
|
||||
$canDeleteAny = false;
|
||||
|
||||
if($this->isDeleted())
|
||||
return $canDeleteAny ? 'deleted' : 'view';
|
||||
|
||||
if(!$canDeleteAny) {
|
||||
if(false) // check if user can delete posts
|
||||
return 'permission';
|
||||
if($user->getId() !== $this->getUserId())
|
||||
return 'owner';
|
||||
if($this->getCreatedTime() <= time() - self::DELETE_AGE_LIMIT)
|
||||
return 'age';
|
||||
if($this->getActualPostCount(true) >= self::DELETE_POST_LIMIT)
|
||||
return 'posts';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
public static function canDeleteErrorString(string $error): string {
|
||||
switch($error) {
|
||||
case 'view':
|
||||
return 'This topic doesn\'t exist.';
|
||||
case 'deleted':
|
||||
return 'This topic has already been marked as deleted.';
|
||||
case 'permission':
|
||||
return 'You aren\'t allowed to this topic.';
|
||||
case 'owner':
|
||||
return 'You can only delete your own topics.';
|
||||
case 'age':
|
||||
return 'This topic is too old to be deleted. Ask a moderator to remove it if you deem it absolutely necessary.';
|
||||
case 'posts':
|
||||
return 'This topic has too many replies to be deleted. Ask a moderator to remove it if you deem it absolutely necessary.';
|
||||
case '':
|
||||
return 'Topic can be deleted!';
|
||||
default:
|
||||
return 'Topic cannot be deleted.';
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(): void {
|
||||
if($this->isDeleted())
|
||||
return;
|
||||
$this->topic_deleted = time();
|
||||
DB::prepare('UPDATE `' . DB::PREFIX . self::TABLE . '` SET `topic_deleted` = NOW() WHERE `topic_id` = :topic')
|
||||
->bind('topic', $this->getId())
|
||||
->execute();
|
||||
ForumPost::deleteTopic($this);
|
||||
}
|
||||
public function restore(): void {
|
||||
if(!$this->isDeleted())
|
||||
return;
|
||||
ForumPost::restoreTopic($this);
|
||||
DB::prepare('UPDATE `' . DB::PREFIX . self::TABLE . '` SET `topic_deleted` = NULL WHERE `topic_id` = :topic')
|
||||
->bind('topic', $this->getId())
|
||||
->execute();
|
||||
$this->topic_deleted = null;
|
||||
}
|
||||
public function nuke(): void {
|
||||
if(!$this->isDeleted())
|
||||
return;
|
||||
DB::prepare('DELETE FROM `' . DB::PREFIX . self::TABLE . '` WHERE `topic_id` = :topic')
|
||||
->bind('topic', $this->getId())
|
||||
->execute();
|
||||
//ForumPost::nukeTopic($this);
|
||||
}
|
||||
|
||||
public static function create(ForumCategory $category, User $user, string $title, int $type = self::TYPE_DISCUSSION): ForumTopic {
|
||||
$create = DB::prepare(
|
||||
'INSERT INTO `msz_forum_topics` (`forum_id`, `user_id`, `topic_title`, `topic_type`) VALUES (:forum, :user, :title, :type)'
|
||||
)->bind('forum', $category->getId())->bind('user', $user->getId())
|
||||
->bind('title', $title)->bind('type', $type)
|
||||
->execute();
|
||||
if(!$create)
|
||||
throw new ForumTopicCreationFailedException;
|
||||
$topicId = DB::lastId();
|
||||
if($topicId < 1)
|
||||
throw new ForumTopicCreationFailedException;
|
||||
|
||||
try {
|
||||
return self::byId($topicId);
|
||||
} catch(ForumTopicNotFoundException $ex) {
|
||||
throw new ForumTopicCreationFailedException;
|
||||
}
|
||||
}
|
||||
|
||||
public function update(): void {
|
||||
if($this->getId() < 1)
|
||||
throw new ForumTopicUpdateFailedException;
|
||||
|
||||
if(!DB::prepare(
|
||||
'UPDATE `msz_forum_topics`'
|
||||
. ' SET `topic_title` = :title,'
|
||||
. ' `topic_type` = :type'
|
||||
. ' WHERE `topic_id` = :topic'
|
||||
)->bind('topic', $this->getId())
|
||||
->bind('title', $this->getTitle())
|
||||
->bind('type', $this->getType())
|
||||
->execute())
|
||||
throw new ForumTopicUpdateFailedException;
|
||||
}
|
||||
|
||||
public function synchronise(bool $save = true): array {
|
||||
$stats = DB::prepare(
|
||||
'SELECT :topic AS `topic`, ('
|
||||
|
@ -337,6 +446,27 @@ class ForumTopic {
|
|||
return $stats;
|
||||
}
|
||||
|
||||
public static function validateTitle(string $title): string {
|
||||
$length = mb_strlen(trim($title));
|
||||
if($length < self::TITLE_MIN_LENGTH)
|
||||
return 'short';
|
||||
if($length > self::TITLE_MAX_LENGTH)
|
||||
return 'long';
|
||||
return '';
|
||||
}
|
||||
public static function titleValidationErrorString(string $error): string {
|
||||
switch($error) {
|
||||
case 'short':
|
||||
return sprintf('Topic title was too short, it has to be at least %d characters!', self::TITLE_MIN_LENGTH);
|
||||
case 'long':
|
||||
return sprintf("Topic title was too long, it can't be longer than %d characters!", self::TITLE_MAX_LENGTH);
|
||||
case '':
|
||||
return 'Topic title is correctly formatted!';
|
||||
default:
|
||||
return 'Topic title is incorrectly formatted.';
|
||||
}
|
||||
}
|
||||
|
||||
private static function countQueryBase(): string {
|
||||
return sprintf(self::QUERY_SELECT, sprintf('COUNT(*)', self::TABLE));
|
||||
}
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
<?php
|
||||
define('MSZ_FORUM_LEADERBOARD_START_YEAR', 2018);
|
||||
define('MSZ_FORUM_LEADERBOARD_START_MONTH', 12);
|
||||
define('MSZ_FORUM_LEADERBOARD_CATEGORY_ALL', 0);
|
||||
|
||||
function forum_leaderboard_year_valid(?int $year): bool {
|
||||
return !is_null($year) && $year >= MSZ_FORUM_LEADERBOARD_START_YEAR && $year <= date('Y');
|
||||
}
|
||||
|
||||
function forum_leaderboard_month_valid(?int $year, ?int $month): bool {
|
||||
if(is_null($month) || !forum_leaderboard_year_valid($year) || $month < 1 || $month > 12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$combo = sprintf('%04d%02d', $year, $month);
|
||||
$start = sprintf('%04d%02d', MSZ_FORUM_LEADERBOARD_START_YEAR, MSZ_FORUM_LEADERBOARD_START_MONTH);
|
||||
$current = date('Ym');
|
||||
|
||||
return $combo >= $start && $combo <= $current;
|
||||
}
|
||||
|
||||
function forum_leaderboard_categories(): array {
|
||||
$categories = [
|
||||
MSZ_FORUM_LEADERBOARD_CATEGORY_ALL => 'All Time',
|
||||
];
|
||||
|
||||
$currentYear = date('Y');
|
||||
$currentMonth = date('m');
|
||||
|
||||
for($i = $currentYear; $i >= MSZ_FORUM_LEADERBOARD_START_YEAR; $i--) {
|
||||
$categories[$i] = sprintf('Leaderboard %d', $i);
|
||||
}
|
||||
|
||||
for($i = $currentYear, $j = $currentMonth;;) {
|
||||
$categories[sprintf('%d%02d', $i, $j)] = sprintf('Leaderboard %d-%02d', $i, $j);
|
||||
|
||||
if($j <= 1) {
|
||||
$i--; $j = 12;
|
||||
} else $j--;
|
||||
|
||||
if($i <= MSZ_FORUM_LEADERBOARD_START_YEAR && $j < MSZ_FORUM_LEADERBOARD_START_MONTH)
|
||||
break;
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
function forum_leaderboard_listing(
|
||||
?int $year = null,
|
||||
?int $month = null,
|
||||
array $unrankedForums = [],
|
||||
array $unrankedTopics = []
|
||||
): array {
|
||||
$hasYear = forum_leaderboard_year_valid($year);
|
||||
$hasMonth = $hasYear && forum_leaderboard_month_valid($year, $month);
|
||||
$unrankedForums = implode(',', $unrankedForums);
|
||||
$unrankedTopics = implode(',', $unrankedTopics);
|
||||
|
||||
$rawLeaderboard = \Misuzu\DB::query(sprintf(
|
||||
'
|
||||
SELECT
|
||||
u.`user_id`, u.`username`,
|
||||
COUNT(fp.`post_id`) as `posts`
|
||||
FROM `msz_users` AS u
|
||||
INNER JOIN `msz_forum_posts` AS fp
|
||||
ON fp.`user_id` = u.`user_id`
|
||||
WHERE fp.`post_deleted` IS NULL
|
||||
%s %s %s
|
||||
GROUP BY u.`user_id`
|
||||
HAVING `posts` > 0
|
||||
ORDER BY `posts` DESC
|
||||
',
|
||||
$unrankedForums ? sprintf('AND fp.`forum_id` NOT IN (%s)', $unrankedForums) : '',
|
||||
$unrankedTopics ? sprintf('AND fp.`topic_id` NOT IN (%s)', $unrankedTopics) : '',
|
||||
!$hasYear ? '' : sprintf(
|
||||
'AND DATE(fp.`post_created`) BETWEEN \'%1$04d-%2$02d-01\' AND \'%1$04d-%3$02d-31\'',
|
||||
$year,
|
||||
$hasMonth ? $month : 1,
|
||||
$hasMonth ? $month : 12
|
||||
)
|
||||
))->fetchAll();
|
||||
|
||||
$leaderboard = [];
|
||||
$ranking = 0;
|
||||
$lastPosts = null;
|
||||
|
||||
foreach($rawLeaderboard as $entry) {
|
||||
if(is_null($lastPosts) || $lastPosts > $entry['posts']) {
|
||||
$ranking++;
|
||||
$lastPosts = $entry['posts'];
|
||||
}
|
||||
|
||||
$entry['rank'] = $ranking;
|
||||
$leaderboard[] = $entry;
|
||||
}
|
||||
|
||||
return $leaderboard;
|
||||
}
|
|
@ -1,55 +1,4 @@
|
|||
<?php
|
||||
function forum_topic_create(
|
||||
int $forumId,
|
||||
int $userId,
|
||||
string $title,
|
||||
int $type = \Misuzu\Forum\ForumTopic::TYPE_DISCUSSION
|
||||
): int {
|
||||
if(empty($title) || !in_array($type, \Misuzu\Forum\ForumTopic::TYPES)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$createTopic = \Misuzu\DB::prepare('
|
||||
INSERT INTO `msz_forum_topics`
|
||||
(`forum_id`, `user_id`, `topic_title`, `topic_type`)
|
||||
VALUES
|
||||
(:forum_id, :user_id, :topic_title, :topic_type)
|
||||
');
|
||||
$createTopic->bind('forum_id', $forumId);
|
||||
$createTopic->bind('user_id', $userId);
|
||||
$createTopic->bind('topic_title', $title);
|
||||
$createTopic->bind('topic_type', $type);
|
||||
|
||||
return $createTopic->execute() ? \Misuzu\DB::lastId() : 0;
|
||||
}
|
||||
|
||||
function forum_topic_update(int $topicId, ?string $title, ?int $type = null): bool {
|
||||
if($topicId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure it's null and not some other kinda empty
|
||||
if(empty($title)) {
|
||||
$title = null;
|
||||
}
|
||||
|
||||
if($type !== null && !in_array($type, \Misuzu\Forum\ForumTopic::TYPES)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$updateTopic = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_topics`
|
||||
SET `topic_title` = COALESCE(:topic_title, `topic_title`),
|
||||
`topic_type` = COALESCE(:topic_type, `topic_type`)
|
||||
WHERE `topic_id` = :topic_id
|
||||
');
|
||||
$updateTopic->bind('topic_id', $topicId);
|
||||
$updateTopic->bind('topic_title', $title);
|
||||
$updateTopic->bind('topic_type', $type);
|
||||
|
||||
return $updateTopic->execute();
|
||||
}
|
||||
|
||||
function forum_topic_views_increment(int $topicId): void {
|
||||
if($topicId < 1) {
|
||||
return;
|
||||
|
@ -72,6 +21,7 @@ function forum_topic_mark_read(int $userId, int $topicId, int $forumId): void {
|
|||
// previously a TRIGGER was used to achieve this behaviour,
|
||||
// but those explode when running on a lot of queries (like forum_mark_read() does)
|
||||
// so instead we get to live with this garbage now
|
||||
// JUST TO CLARIFY: "this behaviour" refers to forum_topic_views_increment only being executed when the topic is viewed for the first time
|
||||
try {
|
||||
$markAsRead = \Misuzu\DB::prepare('
|
||||
INSERT INTO `msz_forum_topics_track`
|
||||
|
@ -104,283 +54,3 @@ function forum_topic_mark_read(int $userId, int $topicId, int $forumId): void {
|
|||
$markAsRead->execute();
|
||||
}
|
||||
}
|
||||
|
||||
function forum_topic_count_user(int $authorId, int $userId, bool $showDeleted = false): int {
|
||||
$getTopics = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT COUNT(`topic_id`)
|
||||
FROM `msz_forum_topics` AS t
|
||||
WHERE t.`user_id` = :author_id
|
||||
%1$s
|
||||
',
|
||||
$showDeleted ? '' : 'AND t.`topic_deleted` IS NULL'
|
||||
));
|
||||
$getTopics->bind('author_id', $authorId);
|
||||
//$getTopics->bind('user_id', $userId);
|
||||
|
||||
return (int)$getTopics->fetchColumn();
|
||||
}
|
||||
|
||||
// Remove unneccesary stuff from the sql stmt
|
||||
function forum_topic_listing_user(
|
||||
int $authorId,
|
||||
int $userId,
|
||||
int $offset = 0,
|
||||
int $take = 0,
|
||||
bool $showDeleted = false
|
||||
): array {
|
||||
$hasPagination = $offset >= 0 && $take > 0;
|
||||
$getTopics = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT
|
||||
:user_id AS `target_user_id`,
|
||||
t.`topic_id`, t.`topic_title`, t.`topic_locked`, t.`topic_type`, t.`topic_created`,
|
||||
t.`topic_bumped`, t.`topic_deleted`, t.`topic_count_views`, f.`forum_type`,
|
||||
au.`user_id` AS `author_id`, au.`username` AS `author_name`,
|
||||
COALESCE(au.`user_colour`, ar.`role_colour`) AS `author_colour`,
|
||||
lp.`post_id` AS `response_id`,
|
||||
lp.`post_created` AS `response_created`,
|
||||
lu.`user_id` AS `respondent_id`,
|
||||
lu.`username` AS `respondent_name`,
|
||||
COALESCE(lu.`user_colour`, lr.`role_colour`) AS `respondent_colour`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
%5$s
|
||||
) AS `topic_count_posts`,
|
||||
(
|
||||
SELECT CEIL(COUNT(`post_id`) / %6$d)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
%5$s
|
||||
) AS `topic_pages`,
|
||||
(
|
||||
SELECT
|
||||
`target_user_id` > 0
|
||||
AND
|
||||
t.`topic_bumped` > NOW() - INTERVAL 1 MONTH
|
||||
AND (
|
||||
SELECT COUNT(ti.`topic_id`) < 1
|
||||
FROM `msz_forum_topics_track` AS tt
|
||||
RIGHT JOIN `msz_forum_topics` AS ti
|
||||
ON ti.`topic_id` = tt.`topic_id`
|
||||
WHERE ti.`topic_id` = t.`topic_id`
|
||||
AND tt.`user_id` = `target_user_id`
|
||||
AND `track_last_read` >= `topic_bumped`
|
||||
)
|
||||
) AS `topic_unread`,
|
||||
(
|
||||
SELECT COUNT(`post_id`) > 0
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
AND `user_id` = `target_user_id`
|
||||
LIMIT 1
|
||||
) AS `topic_participated`
|
||||
FROM `msz_forum_topics` AS t
|
||||
LEFT JOIN `msz_forum_categories` AS f
|
||||
ON f.`forum_id` = t.`forum_id`
|
||||
LEFT JOIN `msz_users` AS au
|
||||
ON t.`user_id` = au.`user_id`
|
||||
LEFT JOIN `msz_roles` AS ar
|
||||
ON ar.`role_id` = au.`display_role`
|
||||
LEFT JOIN `msz_forum_posts` AS lp
|
||||
ON lp.`post_id` = (
|
||||
SELECT `post_id`
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
%5$s
|
||||
ORDER BY `post_id` DESC
|
||||
LIMIT 1
|
||||
)
|
||||
LEFT JOIN `msz_users` AS lu
|
||||
ON lu.`user_id` = lp.`user_id`
|
||||
LEFT JOIN `msz_roles` AS lr
|
||||
ON lr.`role_id` = lu.`display_role`
|
||||
WHERE au.`user_id` = :author_id
|
||||
%1$s
|
||||
ORDER BY FIELD(t.`topic_type`, %4$s), t.`topic_bumped` DESC
|
||||
%2$s
|
||||
',
|
||||
$showDeleted ? '' : 'AND t.`topic_deleted` IS NULL',
|
||||
$hasPagination ? 'LIMIT :offset, :take' : '',
|
||||
\Misuzu\Forum\ForumTopic::TYPE_GLOBAL_ANNOUNCEMENT,
|
||||
implode(',', \Misuzu\Forum\ForumTopic::TYPE_ORDER),
|
||||
$showDeleted ? '' : 'AND `post_deleted` IS NULL',
|
||||
\Misuzu\Forum\ForumPost::PER_PAGE
|
||||
));
|
||||
$getTopics->bind('author_id', $authorId);
|
||||
$getTopics->bind('user_id', $userId);
|
||||
|
||||
if($hasPagination) {
|
||||
$getTopics->bind('offset', $offset);
|
||||
$getTopics->bind('take', $take);
|
||||
}
|
||||
|
||||
return $getTopics->fetchAll();
|
||||
}
|
||||
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_OK', 0); // deleting is fine
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_USER', 1); // invalid user
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_TOPIC', 2); // topic doesn't exist
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_DELETED', 3); // topic is already marked as deleted
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_OWNER', 4); // you may only delete your own topics
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_OLD', 5); // topic has existed for too long to be deleted
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_PERM', 6); // you aren't allowed to delete topics
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_POSTS', 7); // the topic already has replies
|
||||
|
||||
// only allow topics made within a day of posting to be deleted by normal users
|
||||
define('MSZ_FORUM_TOPIC_DELETE_TIME_LIMIT', 60 * 60 * 24);
|
||||
|
||||
// only allow topics with a single post to be deleted, includes soft deleted posts
|
||||
define('MSZ_FORUM_TOPIC_DELETE_POST_LIMIT', 1);
|
||||
|
||||
// set $userId to null for system request, make sure this is NEVER EVER null on user request
|
||||
// $topicId can also be a the return value of forum_topic_get if you already grabbed it once before
|
||||
function forum_topic_can_delete($topicId, ?int $userId = null): int {
|
||||
if($userId !== null && $userId < 1) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_USER;
|
||||
}
|
||||
|
||||
if(is_array($topicId)) {
|
||||
$topic = $topicId;
|
||||
} elseif(is_int($topicId)) {
|
||||
try {
|
||||
$topic = \Misuzu\Forum\ForumTopic::byId($topicId);
|
||||
} catch(\Misuzu\Forum\ForumTopicNotFoundException $ex) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_TOPIC;
|
||||
}
|
||||
}
|
||||
|
||||
if($topicId instanceof \Misuzu\Forum\ForumTopic) {
|
||||
$topic = [
|
||||
'forum_id' => $topicId->getCategoryId(),
|
||||
'topic_deleted' => $topicId->isDeleted(),
|
||||
'author_user_id' => $topicId->getUserId(),
|
||||
'topic_created' => date('c', $topicId->getCreatedTime()),
|
||||
'topic_count_posts' => $topicId->getActualPostCount(true),
|
||||
'topic_count_posts_deleted' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
if(empty($topic)) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_TOPIC;
|
||||
}
|
||||
|
||||
$isSystemReq = $userId === null;
|
||||
$perms = $isSystemReq ? 0 : forum_perms_get_user($topic['forum_id'], $userId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
$canDeleteAny = $isSystemReq ? true : perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST);
|
||||
$canViewPost = $isSystemReq ? true : perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM);
|
||||
$postIsDeleted = !empty($topic['topic_deleted']);
|
||||
|
||||
if(!$canViewPost) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_TOPIC;
|
||||
}
|
||||
|
||||
if($postIsDeleted) {
|
||||
return $canDeleteAny ? MSZ_E_FORUM_TOPIC_DELETE_DELETED : MSZ_E_FORUM_TOPIC_DELETE_TOPIC;
|
||||
}
|
||||
|
||||
if($isSystemReq) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_OK;
|
||||
}
|
||||
|
||||
if(!$canDeleteAny) {
|
||||
if(!perms_check($perms, MSZ_FORUM_PERM_DELETE_POST)) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_PERM;
|
||||
}
|
||||
|
||||
if($topic['author_user_id'] !== $userId) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_OWNER;
|
||||
}
|
||||
|
||||
if(strtotime($topic['topic_created']) <= time() - MSZ_FORUM_TOPIC_DELETE_TIME_LIMIT) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_OLD;
|
||||
}
|
||||
|
||||
$totalReplies = $topic['topic_count_posts'] + $topic['topic_count_posts_deleted'];
|
||||
|
||||
if($totalReplies > MSZ_E_FORUM_TOPIC_DELETE_POSTS) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_POSTS;
|
||||
}
|
||||
}
|
||||
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_OK;
|
||||
}
|
||||
|
||||
function forum_topic_delete(int $topicId): bool {
|
||||
if($topicId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$markTopicDeleted = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_topics`
|
||||
SET `topic_deleted` = NOW()
|
||||
WHERE `topic_id` = :topic
|
||||
AND `topic_deleted` IS NULL
|
||||
');
|
||||
$markTopicDeleted->bind('topic', $topicId);
|
||||
|
||||
if(!$markTopicDeleted->execute()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$markPostsDeleted = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_posts` as p
|
||||
SET p.`post_deleted` = (
|
||||
SELECT `topic_deleted`
|
||||
FROM `msz_forum_topics`
|
||||
WHERE `topic_id` = p.`topic_id`
|
||||
)
|
||||
WHERE p.`topic_id` = :topic
|
||||
AND p.`post_deleted` IS NULL
|
||||
');
|
||||
$markPostsDeleted->bind('topic', $topicId);
|
||||
|
||||
return $markPostsDeleted->execute();
|
||||
}
|
||||
|
||||
function forum_topic_restore(int $topicId): bool {
|
||||
if($topicId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$markPostsRestored = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_posts` as p
|
||||
SET p.`post_deleted` = NULL
|
||||
WHERE p.`topic_id` = :topic
|
||||
AND p.`post_deleted` = (
|
||||
SELECT `topic_deleted`
|
||||
FROM `msz_forum_topics`
|
||||
WHERE `topic_id` = p.`topic_id`
|
||||
)
|
||||
');
|
||||
$markPostsRestored->bind('topic', $topicId);
|
||||
|
||||
if(!$markPostsRestored->execute()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$markTopicRestored = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_topics`
|
||||
SET `topic_deleted` = NULL
|
||||
WHERE `topic_id` = :topic
|
||||
AND `topic_deleted` IS NOT NULL
|
||||
');
|
||||
$markTopicRestored->bind('topic', $topicId);
|
||||
|
||||
return $markTopicRestored->execute();
|
||||
}
|
||||
|
||||
function forum_topic_nuke(int $topicId): bool {
|
||||
if($topicId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$nukeTopic = \Misuzu\DB::prepare('
|
||||
DELETE FROM `msz_forum_topics`
|
||||
WHERE `topic_id` = :topic
|
||||
');
|
||||
$nukeTopic->bind('topic', $topicId);
|
||||
return $nukeTopic->execute();
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
define('MSZ_TOPIC_TITLE_LENGTH_MIN', 3);
|
||||
define('MSZ_TOPIC_TITLE_LENGTH_MAX', 100);
|
||||
define('MSZ_POST_TEXT_LENGTH_MIN', 1);
|
||||
define('MSZ_POST_TEXT_LENGTH_MAX', 60000);
|
||||
|
||||
function forum_validate_title(string $title): string {
|
||||
$length = mb_strlen(trim($title));
|
||||
|
||||
if($length < MSZ_TOPIC_TITLE_LENGTH_MIN) {
|
||||
return 'too-short';
|
||||
}
|
||||
|
||||
if($length > MSZ_TOPIC_TITLE_LENGTH_MAX) {
|
||||
return 'too-long';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function forum_validate_post(string $text): string {
|
||||
$length = mb_strlen(trim($text));
|
||||
|
||||
if($length < MSZ_POST_TEXT_LENGTH_MIN) {
|
||||
return 'too-short';
|
||||
}
|
||||
|
||||
if($length > MSZ_POST_TEXT_LENGTH_MAX) {
|
||||
return 'too-long';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
{% extends 'forum/master.twig' %}
|
||||
{% from 'macros.twig' import avatar %}
|
||||
{% from 'forum/macros.twig' import forum_header %}
|
||||
|
||||
{% set title = 'Forum Leaderboard » ' ~ leaderboard_name %}
|
||||
{% set canonical_url = url('forum-leaderboard', {
|
||||
|
@ -9,18 +8,23 @@
|
|||
}) %}
|
||||
|
||||
{% block content %}
|
||||
{{ forum_header(title, [], false, canonical_url, [
|
||||
{
|
||||
'html': '<i class="fab fa-markdown fa-fw"></i> Markdown',
|
||||
'url': url('forum-leaderboard', {'id': leaderboard_id, 'mode': 'markdown'}),
|
||||
'display': leaderboard_mode != 'markdown',
|
||||
},
|
||||
{
|
||||
'html': '<i class="fas fa-table fa-fw"></i> Table',
|
||||
'url': url('forum-leaderboard', {'id': leaderboard_id}),
|
||||
'display': leaderboard_mode == 'markdown',
|
||||
},
|
||||
]) }}
|
||||
<div class="container forum__header">
|
||||
<a class="forum__header__title" href="{{ canonical_url }}">
|
||||
{{ title }}
|
||||
</a>
|
||||
|
||||
<div class="forum__header__actions">
|
||||
{% if leaderboard_mode == 'markdown' %}
|
||||
<a class="forum__header__action" href="{{ url('forum-leaderboard', {'id': leaderboard_id}) }}">
|
||||
<i class="fas fa-table fa-fw"></i> Table
|
||||
</a>
|
||||
{% else %}
|
||||
<a class="forum__header__action" href="{{ url('forum-leaderboard', {'id': leaderboard_id, 'mode': 'markdown'}) }}">
|
||||
<i class="fas fa-markdown fa-fw"></i> Markdown
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container forum__leaderboard__categories">
|
||||
{% for id, name in leaderboard_categories %}
|
||||
|
|
|
@ -1,22 +1,13 @@
|
|||
{% extends 'profile/master.twig' %}
|
||||
{% from 'forum/macros.twig' import forum_post_listing %}
|
||||
|
||||
{% block content %}
|
||||
<div class="profile">
|
||||
{% include 'profile/_layout/header.twig' %}
|
||||
|
||||
{% set sp = profile_posts_pagination.pages > 1
|
||||
? '<div class="container profile__pagination">' ~ profile_posts_pagination.render(canonical_url) ~ '</div>'
|
||||
: '' %}
|
||||
|
||||
{% if sp is not empty %}
|
||||
{{ sp|raw }}
|
||||
{% endif %}
|
||||
|
||||
{{ forum_post_listing(profile_posts) }}
|
||||
|
||||
{% if sp is not empty %}
|
||||
{{ sp|raw }}
|
||||
{% endif %}
|
||||
<div class="warning">
|
||||
<div class="warning__content">
|
||||
<p>User post listing is gone for a while, it will be back someday but with less bad.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,22 +1,13 @@
|
|||
{% extends 'profile/master.twig' %}
|
||||
{% from 'forum/macros.twig' import forum_topic_listing %}
|
||||
|
||||
{% block content %}
|
||||
<div class="profile">
|
||||
{% include 'profile/_layout/header.twig' %}
|
||||
|
||||
{% set sp = profile_topics_pagination.pages > 1
|
||||
? '<div class="container profile__pagination">' ~ profile_topics_pagination.render(canonical_url) ~ '</div>'
|
||||
: '' %}
|
||||
|
||||
{% if sp is not empty %}
|
||||
{{ sp|raw }}
|
||||
{% endif %}
|
||||
|
||||
{{ forum_topic_listing(profile_topics) }}
|
||||
|
||||
{% if sp is not empty %}
|
||||
{{ sp|raw }}
|
||||
{% endif %}
|
||||
<div class="warning">
|
||||
<div class="warning__content">
|
||||
<p>User topic listing is gone for a while, it will be back someday but with less bad.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue