SQL is black magic
This commit is contained in:
parent
7d4126443c
commit
60e6accb2e
8 changed files with 243 additions and 46 deletions
|
@ -17,6 +17,8 @@
|
|||
font-weight: 700;
|
||||
padding: 3px;
|
||||
font-family: @mio-font-heading;
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
|
||||
&--link:hover {
|
||||
text-decoration: underline;
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
|
||||
&__text {
|
||||
margin: 2px;
|
||||
|
@ -37,14 +39,26 @@
|
|||
|
||||
&__author {
|
||||
border-right: 1px solid #9475b2;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
min-width: 150px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&__joined {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
&__link {
|
||||
margin: 15px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
background: #9475b2;
|
||||
color: #306;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
min-width: 20px;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 1px;
|
||||
|
|
127
public/forum/posting.php
Normal file
127
public/forum/posting.php
Normal file
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
use Misuzu\Database;
|
||||
use Misuzu\Net\IPAddress;
|
||||
|
||||
require_once __DIR__ . '/../../misuzu.php';
|
||||
|
||||
if (!$app->hasActiveSession()) {
|
||||
header('Location: /');
|
||||
return;
|
||||
}
|
||||
|
||||
$postRequest = $_SERVER['REQUEST_METHOD'] === 'POST';
|
||||
|
||||
$db = Database::connection();
|
||||
|
||||
// ORDER OF CHECKING
|
||||
// - $postId non-zero: enter quote mode
|
||||
// - $topicId non-zero: enter reply mode
|
||||
// - $forumId non-zero: enter create mode
|
||||
// - all zero: enter explode mode
|
||||
if ($postRequest) {
|
||||
$topicId = max(0, (int)($_POST['post']['topic'] ?? 0));
|
||||
$forumId = max(0, (int)($_POST['post']['forum'] ?? 0));
|
||||
} else {
|
||||
$postId = max(0, (int)($_GET['p'] ?? 0));
|
||||
$topicId = max(0, (int)($_GET['t'] ?? 0));
|
||||
$forumId = max(0, (int)($_GET['f'] ?? 0));
|
||||
}
|
||||
|
||||
if (!empty($postId)) {
|
||||
$getPost = $db->prepare('
|
||||
SELECT `post_id`, `topic_id`, `user_id`, `post_text`
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `post_id` = :post_id
|
||||
');
|
||||
$getPost->bindValue('post_id', $postId);
|
||||
$post = $getPost->execute() ? $getPost->fetch() : false;
|
||||
|
||||
if (isset($post['topic_id'])) { // should automatic cross-quoting be a thing? if so, check if $topicId is < 1 first
|
||||
$topicId = (int)$post['topic_id'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($topicId)) {
|
||||
$getTopic = $db->prepare('
|
||||
SELECT `topic_id`, `forum_id`
|
||||
FROM `msz_forum_topics`
|
||||
WHERE `topic_id` = :topic_id
|
||||
');
|
||||
$getTopic->bindValue('topic_id', $topicId);
|
||||
$topic = $getTopic->execute() ? $getTopic->fetch() : false;
|
||||
|
||||
if (isset($topic['forum_id'])) {
|
||||
$forumId = (int)$topic['forum_id'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($forumId)) {
|
||||
$getForum = $db->prepare('
|
||||
SELECT `forum_id`
|
||||
FROM `msz_forum_categories`
|
||||
WHERE `forum_id` = :forum_id
|
||||
');
|
||||
$getForum->bindValue('forum_id', $forumId);
|
||||
$forum = $getForum->execute() ? $getForum->fetch() : false;
|
||||
}
|
||||
|
||||
if ($postRequest) {
|
||||
$createPost = $db->prepare('
|
||||
INSERT INTO `msz_forum_posts`
|
||||
(`topic_id`, `forum_id`, `user_id`, `post_ip`, `post_text`)
|
||||
VALUES
|
||||
(:topic_id, :forum_id, :user_id, INET6_ATON(:post_ip), :post_text)
|
||||
');
|
||||
|
||||
if (isset($topic)) {
|
||||
$bumpTopic = $db->prepare('
|
||||
UPDATE `msz_forum_topics`
|
||||
SET `topic_bumped` = NOW()
|
||||
WHERE `topic_id` = :topic_id
|
||||
');
|
||||
$bumpTopic->bindValue('topic_id', $topic['topic_id']);
|
||||
$bumpTopic->execute();
|
||||
} else {
|
||||
$createTopic = $db->prepare('
|
||||
INSERT INTO `msz_forum_topics`
|
||||
(`forum_id`, `user_id`, `topic_title`)
|
||||
VALUES
|
||||
(:forum_id, :user_id, :topic_title)
|
||||
');
|
||||
$createTopic->bindValue('forum_id', $forum['forum_id']);
|
||||
$createTopic->bindValue('user_id', $app->getUserId());
|
||||
$createTopic->bindValue('topic_title', $_POST['post']['title']);
|
||||
$createTopic->execute();
|
||||
$topicId = (int)$db->lastInsertId();
|
||||
}
|
||||
|
||||
$createPost->bindValue('topic_id', $topicId);
|
||||
$createPost->bindValue('forum_id', $forum['forum_id']);
|
||||
$createPost->bindValue('user_id', $app->getUserId());
|
||||
$createPost->bindValue('post_ip', IPAddress::remote()->getString());
|
||||
$createPost->bindValue('post_text', $_POST['post']['text']);
|
||||
$createPost->execute();
|
||||
$postId = $db->lastInsertId();
|
||||
|
||||
header("Location: /forum/topic.php?p={$postId}#p{$postId}");
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<form method="post" action="/forum/posting.php">';
|
||||
|
||||
if (isset($topic)) {
|
||||
echo "<input type='hidden' name='post[topic]' value='{$topic['topic_id']}'>";
|
||||
} else {
|
||||
echo "<input type='hidden' name='post[forum]' value='{$forum['forum_id']}'>";
|
||||
echo '<input type="text" name="post[title]"><br>';
|
||||
}
|
||||
|
||||
echo '<textarea name="post[text]">';
|
||||
|
||||
if (isset($post)) {
|
||||
echo '[quote=' . $post['user_id'] . ']' . $post['post_text'] . '[/quote]';
|
||||
}
|
||||
|
||||
echo '</textarea><br>
|
||||
<button>Submit</button>
|
||||
</form>';
|
|
@ -13,23 +13,44 @@ $postsRange = max(min((int)($_GET['r'] ?? 10), 25), 5);
|
|||
|
||||
// find topic id
|
||||
if ($topicId < 1 && $postId > 0) {
|
||||
$getTopicId = $db->prepare('
|
||||
$getPostInfo = $db->prepare('
|
||||
SELECT
|
||||
:post_id as `target_post_id`,
|
||||
(
|
||||
SELECT `topic_id`
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `post_id` = :post_id
|
||||
WHERE `post_id` = `target_post_id`
|
||||
) as `target_topic_id`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = `target_topic_id`
|
||||
AND `post_id` < `target_post_id`
|
||||
ORDER BY `post_id`
|
||||
) as `preceeding_post_count`
|
||||
');
|
||||
$getTopicId->bindValue('post_id', $postId);
|
||||
$topicId = $getTopicId->execute() ? (int)$getTopicId->fetchColumn() : 0;
|
||||
$getPostInfo->bindValue('post_id', $postId);
|
||||
$postInfo = $getPostInfo->execute() ? $getPostInfo->fetch() : false;
|
||||
|
||||
if ($postInfo) {
|
||||
$topicId = (int)$postInfo['target_topic_id'];
|
||||
$postsOffset = floor($postInfo['preceeding_post_count'] / $postsRange) * $postsRange;
|
||||
}
|
||||
}
|
||||
|
||||
$getTopic = $db->prepare('
|
||||
SELECT
|
||||
t.`topic_id`, t.`forum_id`, t.`topic_title`, t.`topic_type`, t.`topic_status`,
|
||||
(
|
||||
SELECT MIN(p.`post_id`)
|
||||
FROM `msz_forum_posts` as p
|
||||
WHERE p.`topic_id` = t.`topic_id`
|
||||
) as `topic_first_post_id`
|
||||
SELECT MIN(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
) as `topic_first_post_id`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
) as `topic_post_count`
|
||||
FROM `msz_forum_topics` as t
|
||||
WHERE t.`topic_id` = :topic_id
|
||||
AND t.`topic_deleted` IS NULL
|
||||
|
@ -45,15 +66,33 @@ if (!$topic) {
|
|||
|
||||
$getPosts = $db->prepare('
|
||||
SELECT
|
||||
`post_id`, `post_text`, `post_created`,
|
||||
`topic_id`
|
||||
FROM `msz_forum_posts`
|
||||
p.`post_id`, p.`post_text`, p.`post_created`,
|
||||
p.`topic_id`,
|
||||
u.`user_id` as `poster_id`,
|
||||
u.`username` as `poster_name`,
|
||||
u.`created_at` as `poster_joined`,
|
||||
COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `poster_colour`
|
||||
FROM `msz_forum_posts` as p
|
||||
LEFT JOIN `msz_users` as u
|
||||
ON u.`user_id` = p.`user_id`
|
||||
LEFT JOIN `msz_roles` as r
|
||||
ON r.`role_id` = u.`display_role`
|
||||
WHERE `topic_id` = :topic_id
|
||||
AND `post_deleted` IS NULL
|
||||
ORDER BY `post_id`
|
||||
LIMIT :offset, :take
|
||||
');
|
||||
$getPosts->bindValue('topic_id', $topic['topic_id']);
|
||||
$getPosts->bindValue('offset', $postsOffset);
|
||||
$getPosts->bindValue('take', $postsRange);
|
||||
$posts = $getPosts->execute() ? $getPosts->fetchAll() : [];
|
||||
|
||||
if (!$posts) {
|
||||
http_response_code(404);
|
||||
echo $templating->render('errors.404');
|
||||
return;
|
||||
}
|
||||
|
||||
$lastParent = $topic['forum_id'];
|
||||
$breadcrumbs = [];
|
||||
$getBreadcrumb = $db->prepare('
|
||||
|
@ -81,4 +120,6 @@ echo $templating->render('forum.topic', [
|
|||
'topic_breadcrumbs' => $breadcrumbs,
|
||||
'topic_info' => $topic,
|
||||
'topic_posts' => $posts,
|
||||
'topic_offset' => $postsOffset,
|
||||
'topic_range' => $postsRange,
|
||||
]);
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
{% macro forum_category_buttons(forum) %}
|
||||
<div class="forum__actions forum__actions__content">
|
||||
<a class="input__button forum__actions__button">New Topic</a>
|
||||
<a href="/forum/posting.php?f={{ forum.forum_id }}" class="input__button forum__actions__button">New Topic</a>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
@ -104,6 +104,12 @@
|
|||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro forum_topic_buttons(topic) %}
|
||||
<div class="forum__actions forum__actions__content">
|
||||
<a href="/forum/posting.php?t={{ topic.topic_id }}" class="input__button forum__actions__button">Reply</a>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro forum_topic_listing(topics) %}
|
||||
{% from _self import forum_topic_entry %}
|
||||
|
||||
|
@ -202,35 +208,35 @@
|
|||
{% macro forum_post_listing(posts, opening_post_id) %}
|
||||
{% from _self import forum_post_entry %}
|
||||
|
||||
{% if posts|length > 0 %}
|
||||
{% for post in posts %}
|
||||
{{ forum_post_entry(post, post.post_id == opening_post_id) }}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="container">
|
||||
<div class="container__title">Information</div>
|
||||
<div class="container__content">
|
||||
This topic has no associated posts.
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro forum_post_entry(post, is_opening_post) %}
|
||||
{% set is_opening_post = is_opening_post|default(false) %}
|
||||
{% macro forum_post_entry(post, is_original_post, is_original_poster) %}
|
||||
{% set is_original_post = is_original_post|default(false) %}
|
||||
{% set is_original_poster = is_original_poster|default(false) %}
|
||||
|
||||
<div class="forum__post" id="p{{ post.post_id }}">
|
||||
<div class="forum__post__author">
|
||||
<a class="forum__post__author__link" href="/profile.php?u=1">
|
||||
{% if post.poster_id is not null %}
|
||||
<a class="forum__post__author__link" href="/profile.php?u={{ post.poster_id }}">
|
||||
<div
|
||||
class="avatar forum__post__author__avatar"
|
||||
style="background-image:url('/profile.php?u=1&m=avatar');">
|
||||
style="background-image:url('/profile.php?u={{ post.poster_id }}&m=avatar');">
|
||||
</div>
|
||||
<div class="forum__post__author__username">me</div>
|
||||
<div
|
||||
class="forum__post__author__username"
|
||||
style="color:{{ post.poster_colour|colour_get_css }}">{{ post.poster_name }}</div>
|
||||
</a>
|
||||
<div class="forum__post__author__joined">
|
||||
joined ages ago
|
||||
joined 5 years ago {# post.poster_joined #}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="forum__post__author__joined">
|
||||
deleted user
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="forum__post__content">
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
{% extends '@mio/forum/master.twig' %}
|
||||
{% from '@mio/macros.twig' import navigation, pagination %}
|
||||
{% from '@mio/forum/macros.twig' import forum_post_listing %}
|
||||
{% from '@mio/forum/macros.twig' import forum_post_listing, forum_topic_buttons %}
|
||||
|
||||
{% set title = topic_info.topic_title %}
|
||||
{% set canonical_url = '/forum/topic.php?t=' ~ topic_info.topic_id %}
|
||||
{% set base_url = '/forum/topic.php?t=' ~ topic_info.topic_id %}
|
||||
{% set canonical_url = base_url %}
|
||||
|
||||
{% set ftbuttons = forum_topic_buttons(topic_info) %}
|
||||
{% set ftpagination = pagination(topic_info.topic_post_count, topic_range, topic_offset, base_url) %}
|
||||
|
||||
{% block content %}
|
||||
{{ navigation(topic_breadcrumbs, topic_breadcrumbs|last, true, null, 'left') }}
|
||||
|
@ -14,7 +18,11 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{ ftbuttons }}
|
||||
{{ ftpagination }}
|
||||
{{ forum_post_listing(topic_posts, topic_info.topic_first_post_id) }}
|
||||
{{ ftpagination }}
|
||||
{{ ftbuttons }}
|
||||
|
||||
{{ navigation(mio_navigation, '/forum/') }}
|
||||
{% endblock %}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
{% from _self import pagination_class %}
|
||||
{% set classPrefix = classPrefix|default('') %}
|
||||
{% set separator = '%3F' in baseUrl|default('')|url_encode ? '&' : '?' %}
|
||||
{% set originalUrl = baseUrl %}
|
||||
{% set baseUrl = baseUrl ~ separator %}
|
||||
{% set pageCount = (itemCount / itemRange)|round(0, 'ceil') %}
|
||||
{% set currentPage = currentOffset // itemRange %}
|
||||
|
@ -46,15 +47,13 @@
|
|||
</span>
|
||||
</li>
|
||||
{% else %}
|
||||
{% set firstUrl = baseUrl|slice(0, (baseUrl|length) - (separator|length)) %}
|
||||
|
||||
<li class="{{ pagination_class('pagination__option', classPrefix) }} {{ pagination_class('pagination__option--first', classPrefix) }}">
|
||||
<a href="{{ firstUrl }}" class="{{ pagination_class('pagination__link', classPrefix) }} {{ pagination_class('pagination__link--first', classPrefix) }}" rel="first">
|
||||
<a href="{{ originalUrl }}" class="{{ pagination_class('pagination__link', classPrefix) }} {{ pagination_class('pagination__link--first', classPrefix) }}" rel="first">
|
||||
«
|
||||
</a>
|
||||
</li>
|
||||
<li class="{{ pagination_class('pagination__option', classPrefix) }} {{ pagination_class('pagination__option--prev', classPrefix) }}">
|
||||
<a href="{{ currentPage < 2 ? firstUrl : baseUrl ~ offsetParam ~ '=' ~ (useRanges ? ((currentPage - 1) * itemRange) : currentPage) }}" class="{{ pagination_class('pagination__link', classPrefix) }} {{ pagination_class('pagination__link--prev', classPrefix) }}" rel="prev">
|
||||
<a href="{{ currentPage < 2 ? originalUrl : baseUrl ~ offsetParam ~ '=' ~ (useRanges ? ((currentPage - 1) * itemRange) : currentPage) }}" class="{{ pagination_class('pagination__link', classPrefix) }} {{ pagination_class('pagination__link--prev', classPrefix) }}" rel="prev">
|
||||
‹
|
||||
</a>
|
||||
</li>
|
||||
|
@ -68,7 +67,7 @@
|
|||
{% for i in paginationStart..paginationStop %}
|
||||
{% if i >= 0 and i < pageCount %}
|
||||
<li class="{{ pagination_class('pagination__option', classPrefix) }}{{ currentPage == i ? ' ' ~ pagination_class('pagination__option--active', classPrefix) : '' }}">
|
||||
<a href="{{ baseUrl ~ offsetParam ~ '=' ~ (useRanges ? i * itemRange : i + 1) }}" class="{{ pagination_class('pagination__link', classPrefix) }}{{ currentPage == i ? ' ' ~ pagination_class('pagination__link--active', classPrefix) : '' }}">
|
||||
<a href="{{ i == 0 ? originalUrl : baseUrl ~ offsetParam ~ '=' ~ (useRanges ? i * itemRange : i + 1) }}" class="{{ pagination_class('pagination__link', classPrefix) }}{{ currentPage == i ? ' ' ~ pagination_class('pagination__link--active', classPrefix) : '' }}">
|
||||
{{ i + 1 }}
|
||||
</a>
|
||||
</li>
|
||||
|
|
Loading…
Add table
Reference in a new issue