Added post editing, closes #97.

This commit is contained in:
flash 2018-12-30 04:02:35 +01:00
parent 3e4a8504ad
commit e3d4d41a9a
7 changed files with 218 additions and 41 deletions

View file

@ -46,6 +46,7 @@
&__text {
margin: 2px;
line-height: 1.2em;
flex: 1 1 auto;
@media (max-width: @site-mobile-width) {
margin: 4px;
@ -178,4 +179,31 @@
&__parser {
margin: 0;
}
&__actions {
display: flex;
padding: 1px;
}
&__action {
padding: 5px 10px;
margin: 1px;
color: inherit;
text-decoration: none;
transition: background-color .2s;
border-radius: 3px;
&:hover {
background-color: #fff2;
}
}
&__signature {
background-color: rgba(0, 0, 0, .2);
padding: 2px;
img {
vertical-align: middle;
}
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace Misuzu\DatabaseMigrations\AutomaticallyMarkEdited;
use PDO;
function migrate_up(PDO $conn): void
{
$conn->exec("
ALTER TABLE `msz_forum_posts`
CHANGE COLUMN `post_edited` `post_edited` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP AFTER `post_created`;
");
}
function migrate_down(PDO $conn): void
{
$conn->exec("
ALTER TABLE `msz_forum_posts`
CHANGE COLUMN `post_edited` `post_edited` TIMESTAMP NULL DEFAULT NULL AFTER `post_created`;
");
}

View file

@ -11,28 +11,35 @@ if (user_warning_check_restriction(user_session_current('user_id', 0))) {
return;
}
$forumPostingModes = [
'create', 'edit', 'quote',
'delete', 'restore', 'nuke',
];
if (!empty($_POST)) {
$mode = $_POST['post']['mode'] ?? 'create';
$postId = max(0, (int)($_POST['post']['id'] ?? 0));
$topicId = max(0, (int)($_POST['post']['topic'] ?? 0));
$forumId = max(0, (int)($_POST['post']['forum'] ?? 0));
} else {
$mode = $_GET['m'] ?? 'create';
$postId = max(0, (int)($_GET['p'] ?? 0));
$topicId = max(0, (int)($_GET['t'] ?? 0));
$forumId = max(0, (int)($_GET['f'] ?? 0));
}
if (!in_array($mode, $forumPostingModes, true)) {
echo render_error(400);
return;
}
if (empty($postId) && empty($topicId) && empty($forumId)) {
echo render_error(404);
return;
}
if (!empty($postId)) {
$getPost = db_prepare('
SELECT `post_id`, `topic_id`
FROM `msz_forum_posts`
WHERE `post_id` = :post_id
');
$getPost->bindValue('post_id', $postId);
$post = $getPost->execute() ? $getPost->fetch(PDO::FETCH_ASSOC) : false;
$post = forum_post_get($postId);
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'];
@ -68,7 +75,7 @@ if (empty($forum)) {
return;
}
$perms = forum_perms_get_user(MSZ_FORUM_PERMS_GENERAL, $forum['forum_id'], user_session_current('user_id', 0));
$perms = forum_perms_get_user(MSZ_FORUM_PERMS_GENERAL, $forum['forum_id'], user_session_current('user_id'));
if ($forum['forum_archived']
|| !empty($topic['topic_locked'])
@ -83,6 +90,19 @@ if (!forum_may_have_topics($forum['forum_type'])) {
return;
}
// edit mode stuff
if ($mode === 'edit') {
if (empty($post)) {
echo render_error(404);
return;
}
if (!perms_check($perms, $post['poster_id'] === user_session_current('user_id') ? MSZ_FORUM_PERM_EDIT_POST : MSZ_FORUM_PERM_EDIT_ANY_POST)) {
echo render_error(403);
return;
}
}
$notices = [];
if (!empty($_POST)) {
@ -122,24 +142,36 @@ if (!empty($_POST)) {
}
if (empty($notices)) {
if (!empty($topic)) {
forum_topic_bump($topic['topic_id']);
} else {
$topicId = forum_topic_create($forum['forum_id'], user_session_current('user_id', 0), $topicTitle);
switch ($mode) {
case 'create':
if (!empty($topic)) {
forum_topic_bump($topic['topic_id']);
} else {
$topicId = forum_topic_create($forum['forum_id'], user_session_current('user_id', 0), $topicTitle);
}
$postId = forum_post_create(
$topicId,
$forum['forum_id'],
user_session_current('user_id', 0),
ip_remote_address(),
$postText,
$postParser
);
forum_topic_mark_read(user_session_current('user_id', 0), $topicId, $forum['forum_id']);
break;
case 'edit':
if (!forum_post_edit($postId, ip_remote_address(), $postText, $postParser)) {
$notices[] = 'Post edit failed.';
}
break;
}
$postId = forum_post_create(
$topicId,
$forum['forum_id'],
user_session_current('user_id', 0),
ip_remote_address(),
$postText,
$postParser
);
forum_topic_mark_read(user_session_current('user_id', 0), $topicId, $forum['forum_id']);
header("Location: /forum/topic.php?p={$postId}#p{$postId}");
return;
if (empty($notices)) {
header("Location: /forum/topic.php?p={$postId}#p{$postId}");
return;
}
}
}
}
@ -148,6 +180,10 @@ if (!empty($topic)) {
tpl_var('posting_topic', $topic);
}
if ($mode === 'edit') { // $post is pretty much sure to be populated at this point
tpl_var('posting_post', $post);
}
// fetches additional data for simulating a forum post
$getDisplayInfo = db_prepare('
SELECT u.`user_country`, u.`user_created`, (
@ -167,4 +203,5 @@ echo tpl_render('forum.posting', [
'posting_forum' => $forum,
'posting_info' => $displayInfo,
'posting_notices' => $notices,
'posting_mode' => $mode,
]);

View file

@ -23,6 +23,31 @@ function forum_post_create(
return $createPost->execute() ? db_last_insert_id() : 0;
}
function forum_post_edit(
int $postId,
string $ipAddress,
string $text,
int $parser = MSZ_PARSER_PLAIN
): bool {
if ($postId < 1) {
return false;
}
$updatePost = db_prepare('
UPDATE `msz_forum_posts`
SET `post_ip` = INET6_ATON(:post_ip),
`post_text` = :post_text,
`post_parse` = :post_parse
WHERE `post_id` = :post_id
');
$updatePost->bindValue('post_id', $postId);
$updatePost->bindValue('post_ip', $ipAddress);
$updatePost->bindValue('post_text', $text);
$updatePost->bindValue('post_parse', $parser);
return $updatePost->execute();
}
function forum_post_find(int $postId): array
{
$getPostInfo = db_prepare('
@ -43,7 +68,42 @@ function forum_post_find(int $postId): array
');
$getPostInfo->bindValue('post_id', $postId);
return $getPostInfo->execute() ? $getPostInfo->fetch() : [];
return $getPostInfo->execute() ? $getPostInfo->fetch(PDO::FETCH_ASSOC) : [];
}
function forum_post_get(int $postId, bool $allowDeleted = false): array
{
$getPost = db_prepare(sprintf(
'
SELECT
p.`post_id`, p.`post_text`, p.`post_created`, p.`post_parse`,
p.`topic_id`, p.`post_deleted`, p.`post_edited`,
INET6_NTOA(p.`post_ip`) as `post_ip`,
u.`user_id` as `poster_id`,
u.`username` as `poster_name`,
u.`user_created` as `poster_joined`,
u.`user_country` as `poster_country`,
COALESCE(u.`user_colour`, r.`role_colour`) as `poster_colour`,
(
SELECT COUNT(`post_id`)
FROM `msz_forum_posts`
WHERE `user_id` = p.`user_id`
AND `post_deleted` IS NULL
) as `poster_post_count`
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 `post_id` = :post_id
%1$s
ORDER BY `post_id`
',
$allowDeleted ? '' : 'AND `post_deleted` IS NULL'
));
$getPost->bindValue('post_id', $postId);
$post = $getPost->execute() ? $getPost->fetch(PDO::FETCH_ASSOC) : false;
return $post ? $post : [];
}
function forum_post_listing(int $topicId, int $offset = 0, int $take = 0, bool $showDeleted = false): array
@ -86,5 +146,5 @@ function forum_post_listing(int $topicId, int $offset = 0, int $take = 0, bool $
$getPosts->bindValue('take', $take);
}
return $getPosts->execute() ? $getPosts->fetchAll() : [];
return $getPosts->execute() ? $getPosts->fetchAll(PDO::FETCH_ASSOC) : [];
}

View file

@ -321,19 +321,24 @@
</div>
{% endmacro %}
{% macro forum_post_listing(posts, opening_post_id) %}
{% macro forum_post_listing(posts, opening_post_id, user_id, perms) %}
{% from _self import forum_post_entry %}
{% for post in posts %}
{{ forum_post_entry(post, post.post_id == opening_post_id) }}
{{ forum_post_entry(
post,
post.post_id == opening_post_id,
perms|perms_check(constant('MSZ_FORUM_PERM_CREATE_POST')),
perms|perms_check(constant(user_id == post.poster_id ? 'MSZ_FORUM_PERM_EDIT_POST' : 'MSZ_FORUM_PERM_EDIT_ANY_POST')),
perms|perms_check(constant(user_id == post.poster_id ? 'MSZ_FORUM_PERM_DELETE_POST' : 'MSZ_FORUM_PERM_DELETE_ANY_POST'))
) }}
{% endfor %}
{% endmacro %}
{% 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) %}
{% macro forum_post_entry(post, is_original_post, can_post, can_edit, can_delete) %}
{% set is_deleted = post.post_deleted is not null %}
<div class="container forum__post{% if post.post_deleted is not null %} forum__post--deleted{% endif %}" id="p{{ post.post_id }}" style="{{ post.poster_colour|html_colour('--accent-colour') }}">
<div class="container forum__post{% if is_deleted %} forum__post--deleted{% endif %}" id="p{{ post.post_id }}" style="{{ post.poster_colour|html_colour('--accent-colour') }}">
<div class="forum__post__info">
<div class="forum__post__info__background"></div>
<div class="forum__post__info__content">
@ -363,6 +368,9 @@
<div class="forum__post__details">
<a class="forum__post__datetime" href="/forum/topic.php?t={{ post.topic_id }}#p{{ post.post_id }}">
<time datetime="{{ post.post_created|date('c') }}" title="{{ post.post_created|date('r') }}">{{ post.post_created|time_diff }}</time>
{% if post.post_edited is not null %}
(edited <time datetime="{{ post.post_edited|date('c') }}" title="{{ post.post_edited|date('r') }}">{{ post.post_edited|time_diff }}</time>)
{% endif %}
</a>
<a class="forum__post__id" href="/forum/topic.php?p={{ post.post_id }}#p{{ post.post_id }}">
@ -373,6 +381,25 @@
<div class="forum__post__text{% if post.post_parse == constant('MSZ_PARSER_MARKDOWN') %} markdown{% endif %}">
{{ post.post_text|escape|parse_text(post.post_parse)|raw }}
</div>
{% if can_post or can_edit or can_delete %}
<div class="forum__post__actions">
{% if is_deleted %}
<a href="/forum/posting.php?p={{ post.post_id }}&amp;m=restore" class="forum__post__action"><i class="fas fa-magic fa-fw"></i> Restore</a>
<a href="/forum/posting.php?p={{ post.post_id }}&amp;m=nuke" class="forum__post__action"><i class="fas fa-radiation-alt fa-fw"></i> Permanently Delete</a>
{% else %}
{# if can_post %}
<a href="/forum/posting.php?p={{ post.post_id }}&amp;m=quote" class="forum__post__action"><i class="fas fa-quote-left fa-fw"></i> Quote</a>
{% endif #}
{% if can_edit %}
<a href="/forum/posting.php?p={{ post.post_id }}&amp;m=edit" class="forum__post__action"><i class="fas fa-edit fa-fw"></i> Edit</a>
{% endif %}
{# if can_delete %}
<a href="/forum/posting.php?p={{ post.post_id }}&amp;m=delete" class="forum__post__action"><i class="far fa-trash-alt fa-fw"></i> Delete</a>
{% endif #}
{% endif %}
</div>
{% endif %}
</div>
</div>
{% endmacro %}

View file

@ -8,9 +8,14 @@
{% block content %}
<form method="post" action="/forum/posting.php">
{{ input_hidden('post[' ~ (is_reply ? 'topic' : 'forum') ~ ']', is_reply ? posting_topic.topic_id : posting_forum.forum_id) }}
{{ input_hidden('post[mode]', posting_mode) }}
{{ input_csrf('forum_post') }}
{{ forum_header(is_reply ? posting_topic.topic_title : input_text('post[title]', 'forum__header__input', '', 'text', 'Enter your title here...'), posting_breadcrumbs) }}
{% if posting_post is defined %}
{{ input_hidden('post[id]', posting_post.post_id) }}
{% endif %}
{% if posting_notices|length > 0 %}
<div class="warning">
<div class="warning__content">
@ -21,31 +26,31 @@
</div>
{% endif %}
<div class="container forum__post" style="{{ current_user.user_colour|html_colour('--accent-colour') }}">
<div class="container forum__post" style="{{ posting_post.poster_colour|default(current_user.user_colour)|html_colour('--accent-colour') }}">
<div class="forum__post__info">
<div class="forum__post__info__background"></div>
<div class="forum__post__info__content">
<span class="avatar forum__post__avatar" style="background-image:url('/profile.php?u={{ current_user.user_id }}&amp;m=avatar');"></span>
<span class="avatar forum__post__avatar" style="background-image:url('/profile.php?u={{ posting_post.poster_id|default(current_user.user_id) }}&amp;m=avatar');"></span>
<span class="forum__post__username">{{ current_user.username }}</span>
<span class="forum__post__username">{{ posting_post.poster_name|default(current_user.username) }}</span>
<div class="forum__post__icons">
<div class="flag flag--{{ posting_info.user_country|lower }}" title="{{ posting_info.user_country|country_name }}"></div>
<div class="forum__post__posts-count">{{ posting_info.user_forum_posts|number_format }} posts</div>
<div class="flag flag--{{ posting_post.poster_country|default(posting_info.user_country)|lower }}" title="{{ posting_post.poster_country|default(posting_info.user_country)|country_name }}"></div>
<div class="forum__post__posts-count">{{ posting_post.poster_post_count|default(posting_info.user_forum_posts)|number_format }} posts</div>
</div>
<div class="forum__post__joined">
joined <time datetime="{{ posting_info.user_created|date('c') }}" title="{{ posting_info.user_created|date('r') }}">{{ posting_info.user_created|time_diff }}</time>
joined <time datetime="{{ posting_post.poster_joined|default(posting_info.user_created)|date('c') }}" title="{{ posting_post.poster_joined|default(posting_info.user_created)|date('r') }}">{{ posting_post.poster_joined|default(posting_info.user_created)|time_diff }}</time>
</div>
</div>
</div>
<div class="forum__post__content">
<textarea name="post[text]" class="forum__post__text forum__post__text--edit" placeholder="Type your post content here..."></textarea>
<textarea name="post[text]" class="forum__post__text forum__post__text--edit" placeholder="Type your post content here...">{{ posting_post.post_text|default('') }}</textarea>
<div class="forum__post__options">
<div class="forum__post__settings">
{{ input_select('post[parser]', constant('MSZ_PARSERS_NAMES'), constant('MSZ_PARSER_BBCODE'), null, null, null, 'forum__post__parser') }}
{{ input_select('post[parser]', constant('MSZ_PARSERS_NAMES'), posting_post.post_parse|default(constant('MSZ_PARSER_BBCODE')), null, null, null, 'forum__post__parser') }}
</div>
<div class="forum__post__buttons">

View file

@ -22,6 +22,6 @@
{{ forum_header(topic_info.topic_title, topic_breadcrumbs) }}
{{ forum_topic_locked(topic_info.topic_locked, topic_info.topic_archived) }}
{{ topic_tools }}
{{ forum_post_listing(topic_posts, topic_info.topic_first_post_id) }}
{{ forum_post_listing(topic_posts, topic_info.topic_first_post_id, current_user.user_id, topic_perms) }}
{{ topic_tools }}
{% endblock %}