Fixed news tables and finished the shoddy news management panel.

This commit is contained in:
flash 2018-10-11 23:43:52 +02:00
parent 1e0ab7312f
commit 7b33ef088d
11 changed files with 361 additions and 50 deletions

View file

@ -0,0 +1,47 @@
<?php
namespace Misuzu\DatabaseMigrations\FixNewsTables;
use PDO;
function migrate_up(PDO $conn): void
{
$conn->exec("
ALTER TABLE `msz_news_categories`
CHANGE COLUMN `is_hidden` `category_is_hidden` TINYINT(1) NOT NULL DEFAULT '0' AFTER `category_description`,
CHANGE COLUMN `created_at` `category_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `category_is_hidden`,
DROP COLUMN `updated_at`;
");
$conn->exec("
ALTER TABLE `msz_news_posts`
CHANGE COLUMN `comment_section_id` `comment_section_id` INT(10) UNSIGNED NULL DEFAULT NULL AFTER `user_id`,
CHANGE COLUMN `is_featured` `post_is_featured` TINYINT(1) NOT NULL DEFAULT '0' AFTER `comment_section_id`,
CHANGE COLUMN `scheduled_for` `post_scheduled` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `post_text`,
CHANGE COLUMN `created_at` `post_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `post_scheduled`,
CHANGE COLUMN `updated_at` `post_updated` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP AFTER `post_created`,
CHANGE COLUMN `deleted_at` `post_deleted` TIMESTAMP NULL DEFAULT NULL AFTER `post_updated`,
ADD INDEX `news_posts_indices` (`post_is_featured`, `post_scheduled`, `post_created`);
");
}
function migrate_down(PDO $conn): void
{
$conn->exec("
ALTER TABLE `msz_news_posts`
CHANGE COLUMN `post_is_featured` `is_featured` TINYINT(1) NOT NULL DEFAULT '0' AFTER `category_id`,
CHANGE COLUMN `post_scheduled` `scheduled_for` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `post_text`,
CHANGE COLUMN `post_created` `created_at` TIMESTAMP NULL DEFAULT NULL AFTER `scheduled_for`,
CHANGE COLUMN `post_updated` `updated_at` TIMESTAMP NULL DEFAULT NULL AFTER `created_at`,
CHANGE COLUMN `post_deleted` `deleted_at` TIMESTAMP NULL DEFAULT NULL AFTER `updated_at`,
CHANGE COLUMN `comment_section_id` `comment_section_id` INT(10) UNSIGNED NULL DEFAULT NULL AFTER `deleted_at`,
DROP INDEX `news_posts_indices`;
");
$conn->exec("
ALTER TABLE `msz_news_categories`
CHANGE COLUMN `category_is_hidden` `is_hidden` TINYINT(1) NOT NULL DEFAULT '0' AFTER `category_description`,
CHANGE COLUMN `category_created` `created_at` TIMESTAMP NULL DEFAULT NULL AFTER `is_hidden`,
ADD COLUMN `updated_at` TIMESTAMP NULL DEFAULT NULL AFTER `created_at`;
");
}

View file

@ -4,16 +4,24 @@ require_once '../../misuzu.php';
$newsPerms = perms_get_user(MSZ_PERMS_NEWS, user_session_current('user_id', 0)); $newsPerms = perms_get_user(MSZ_PERMS_NEWS, user_session_current('user_id', 0));
switch ($_GET['v'] ?? null) { switch ($_GET['v'] ?? null) {
case 'index': default:
break;
case 'posts': case 'posts':
if (!perms_check($newsPerms, MSZ_PERM_NEWS_MANAGE_POSTS)) { if (!perms_check($newsPerms, MSZ_PERM_NEWS_MANAGE_POSTS)) {
echo render_error(403); echo render_error(403);
break; break;
} }
echo 'posts'; $postTake = 15;
$postOffset = (int)($_GET['o'] ?? 0);
$posts = news_posts_get($postOffset, $postTake, null, false, true, false);
$postsCount = news_posts_count(null, false, true, false);
echo tpl_render('manage.news.posts', [
'news_posts' => $posts,
'posts_offset' => $postOffset,
'posts_take' => $postTake,
'posts_count' => $postsCount,
]);
break; break;
case 'categories': case 'categories':
@ -24,9 +32,58 @@ switch ($_GET['v'] ?? null) {
$catTake = 15; $catTake = 15;
$catOffset = (int)($_GET['o'] ?? 0); $catOffset = (int)($_GET['o'] ?? 0);
$cats = news_categories_get($catOffset, $catTake); $categories = news_categories_get($catOffset, $catTake, true, false, true);
$categoryCount = news_categories_count(true);
echo 'cats'; echo tpl_render('manage.news.categories', [
var_dump($cats); 'news_categories' => $categories,
'categories_offset' => $catOffset,
'categories_take' => $catTake,
'categories_count' => $categoryCount,
]);
break;
case 'category':
$category = [];
$categoryId = (int)($_GET['c'] ?? null);
if (!empty($_POST['category']) && csrf_verify('news_category', $_POST['csrf'] ?? '')) {
$categoryId = news_category_create(
$_POST['category']['name'] ?? null,
$_POST['category']['description'] ?? null,
!empty($_POST['category']['hidden']),
(int)($_POST['category']['id'] ?? null)
);
}
if ($categoryId > 0) {
$category = news_category_get($categoryId);
}
echo tpl_render('manage.news.category', compact('category'));
break;
case 'post':
$post = [];
$postId = (int)($_GET['p'] ?? null);
$categories = news_categories_get(0, 0, false, false, true);
if (!empty($_POST['post']) && csrf_verify('news_post', $_POST['csrf'] ?? '')) {
$postId = news_post_create(
$_POST['post']['title'] ?? null,
$_POST['post']['text'] ?? null,
(int)($_POST['post']['category'] ?? null),
user_session_current('user_id'),
!empty($_POST['post']['featured']),
null,
(int)($_POST['post']['id'] ?? null)
);
}
if ($postId > 0) {
$post = news_post_get($postId);
}
echo tpl_render('manage.news.post', compact('post', 'categories'));
break; break;
} }

View file

@ -43,7 +43,7 @@ if ($postId !== null) {
} }
if ($categoryId !== null) { if ($categoryId !== null) {
$category = news_categories_single($categoryId, true); $category = news_category_get($categoryId, true);
if (!$category || $postsOffset < 0 || $postsOffset >= $category['posts_count']) { if (!$category || $postsOffset < 0 || $postsOffset >= $category['posts_count']) {
echo render_error(404); echo render_error(404);

View file

@ -2,11 +2,79 @@
define('MSZ_PERM_NEWS_MANAGE_POSTS', 1); define('MSZ_PERM_NEWS_MANAGE_POSTS', 1);
define('MSZ_PERM_NEWS_MANAGE_CATEGORIES', 1 << 1); define('MSZ_PERM_NEWS_MANAGE_CATEGORIES', 1 << 1);
function news_post_create(
string $title,
string $text,
int $category,
int $user,
bool $featured = false,
?int $scheduled = null,
?int $postId = null
): int {
if ($postId < 1) {
$post = db_prepare('
INSERT INTO `msz_news_posts`
(`category_id`, `user_id`, `post_is_featured`, `post_title`, `post_text`, `post_scheduled`)
VALUES
(:category, :user, :featured, :title, :text, COALESCE(:scheduled, CURRENT_TIMESTAMP))
');
} else {
$post = db_prepare('
UPDATE `msz_news_posts`
SET `category_id` = :category,
`user_id` = :user,
`post_is_featured` = :featured,
`post_title` = :title,
`post_text` = :text,
`post_scheduled` = COALESCE(:scheduled, `post_scheduled`)
WHERE `post_id` = :id
');
$post->bindValue('id', $postId);
}
$post->bindValue('title', $title);
$post->bindValue('text', $text);
$post->bindValue('category', $category);
$post->bindValue('user', $user);
$post->bindValue('featured', $featured ? 1 : 0);
$post->bindValue('scheduled', empty($scheduled) ? null : date('Y-m-d H:i:s', $scheduled));
return $post->execute() ? ($postId < 1 ? (int)db_last_insert_id() : $postId) : 0;
}
function news_category_create(string $name, string $description, bool $isHidden, ?int $categoryId = null): int
{
if ($categoryId < 1) {
$category = db_prepare('
INSERT INTO `msz_news_categories`
(`category_name`, `category_description`, `category_is_hidden`)
VALUES
(:name, :description, :hidden)
');
} else {
$category = db_prepare('
UPDATE `msz_news_categories`
SET `category_name` = :name,
`category_description` = :description,
`category_is_hidden` = :hidden
WHERE `category_id` = :id
');
$category->bindValue('id', $categoryId);
}
$category->bindValue('name', $name);
$category->bindValue('description', $description);
$category->bindValue('hidden', $isHidden ? 1 : 0);
return $category->execute() ? ($categoryId < 1 ? (int)db_last_insert_id() : $categoryId) : 0;
}
function news_categories_get( function news_categories_get(
int $offset, int $offset,
int $take, int $take,
bool $includePostCount = false, bool $includePostCount = false,
bool $featuredOnly = false, bool $featuredOnly = false,
bool $includeHidden = false,
bool $exposeScheduled = false, bool $exposeScheduled = false,
bool $excludeDeleted = true bool $excludeDeleted = true
): array { ): array {
@ -16,28 +84,30 @@ function news_categories_get(
$query = sprintf( $query = sprintf(
' '
SELECT SELECT
`category_id`, `category_name`, `is_hidden`, `category_id`, `category_name`, `category_is_hidden`,
`created_at`, `updated_at`, `category_created`,
( (
SELECT COUNT(`post_id`) SELECT COUNT(`post_id`)
FROM `msz_news_posts` FROM `msz_news_posts`
WHERE %2$s %3$s %4$s WHERE %2$s %3$s %4$s
) as `posts_count` ) as `posts_count`
FROM `msz_news_categories` FROM `msz_news_categories`
%5$s
GROUP BY `category_id` GROUP BY `category_id`
ORDER BY `category_id` DESC ORDER BY `category_id` DESC
%1$s %1$s
', ',
$getAll ? '' : 'LIMIT :offset, :take', $getAll ? '' : 'LIMIT :offset, :take',
$featuredOnly ? '`is_featured`' : '1', $featuredOnly ? '`post_is_featured` != 0' : '1',
$exposeScheduled ? '' : 'AND `scheduled_for` < NOW()', $exposeScheduled ? '' : 'AND `post_scheduled` < NOW()',
$excludeDeleted ? 'AND `deleted_at` IS NULL' : '' $excludeDeleted ? 'AND `post_deleted` IS NULL' : '',
$includeHidden ? '' : 'WHERE `category_is_hidden` != 0'
); );
} else { } else {
$query = sprintf(' $query = sprintf('
SELECT SELECT
`category_id`, `category_name`, `is_hidden`, `category_id`, `category_name`, `category_is_hidden`,
`created_at`, `updated_at` `category_created`
FROM `msz_news_categories` FROM `msz_news_categories`
ORDER BY `category_id` DESC ORDER BY `category_id` DESC
%s %s
@ -55,7 +125,18 @@ function news_categories_get(
return $cats ? $cats : []; return $cats ? $cats : [];
} }
function news_categories_single( function news_categories_count(bool $includeHidden = false): int
{
$countCats = db_prepare(sprintf('
SELECT COUNT(`category_id`)
FROM `msz_news_categories`
%s
', $includeHidden ? '' : 'WHERE `category_is_hidden` = 0'));
return $countCats->execute() ? (int)$countCats->fetchColumn() : 0;
}
function news_category_get(
int $category, int $category,
bool $includePostCount = false, bool $includePostCount = false,
bool $featuredOnly = false, bool $featuredOnly = false,
@ -66,25 +147,29 @@ function news_categories_single(
$query = sprintf( $query = sprintf(
' '
SELECT SELECT
c.`category_id`, c.`category_name`, c.`category_description`, `category_id`, `category_name`, `category_description`,
COUNT(p.`post_id`) AS `posts_count` `category_is_hidden`, `category_created`,
FROM `msz_news_categories` as c (
LEFT JOIN `msz_news_posts` as p SELECT COUNT(`post_id`)
ON c.`category_id` = p.`category_id` FROM `msz_news_posts`
WHERE c.`category_id` = :category %1$s %2$s %3$s WHERE `category_id` = `category_id` %1$s %2$s %3$s
GROUP BY c.`category_id` ) as `posts_count`
FROM `msz_news_categories`
WHERE `category_id` = :category
GROUP BY `category_id`
', ',
$featuredOnly ? 'AND p.`is_featured` = 1' : '', $featuredOnly ? 'AND `post_is_featured` != 0' : '',
$exposeScheduled ? '' : 'AND p.`scheduled_for` < NOW()', $exposeScheduled ? '' : 'AND `post_scheduled` < NOW()',
$excludeDeleted ? 'AND p.`deleted_at` IS NULL' : '' $excludeDeleted ? 'AND `post_deleted` IS NULL' : ''
); );
} else { } else {
$query = ' $query = '
SELECT SELECT
c.`category_id`, c.`category_name`, c.`category_description`, `category_id`, `category_name`, `category_description`,
FROM `msz_news_categories` as c `category_is_hidden`, `category_created`
WHERE c.`category_id` = :category FROM `msz_news_categories`
GROUP BY c.`category_id` WHERE `category_id` = :category
GROUP BY `category_id`
'; ';
} }
@ -109,9 +194,9 @@ function news_posts_count(
WHERE %1$s %2$s %3$s %4$s WHERE %1$s %2$s %3$s %4$s
', ',
$hasCategory ? '`category_id` = :category' : '1', $hasCategory ? '`category_id` = :category' : '1',
$featuredOnly ? 'AND `is_featured` = 1' : '', $featuredOnly ? 'AND `post_is_featured` != 0' : '',
$exposeScheduled ? '' : 'AND `scheduled_for` < NOW()', $exposeScheduled ? '' : 'AND `post_scheduled` < NOW()',
$excludeDeleted ? 'AND `deleted_at` IS NULL' : '' $excludeDeleted ? 'AND `post_deleted` IS NULL' : ''
)); ));
if ($hasCategory) { if ($hasCategory) {
@ -135,7 +220,8 @@ function news_posts_get(
$getPosts = db_prepare(sprintf( $getPosts = db_prepare(sprintf(
' '
SELECT SELECT
p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`, p.`post_id`, p.`post_is_featured`, p.`post_title`, p.`post_text`, p.`comment_section_id`,
p.`post_created`, p.`post_updated`, p.`post_deleted`, p.`post_scheduled`,
c.`category_id`, c.`category_name`, c.`category_id`, c.`category_name`,
u.`user_id`, u.`username`, u.`user_id`, u.`username`,
COALESCE(u.`user_colour`, r.`role_colour`) as `user_colour`, COALESCE(u.`user_colour`, r.`role_colour`) as `user_colour`,
@ -152,13 +238,13 @@ function news_posts_get(
LEFT JOIN `msz_roles` as r LEFT JOIN `msz_roles` as r
ON u.`display_role` = r.`role_id` ON u.`display_role` = r.`role_id`
WHERE %5$s %2$s %3$s %4$s WHERE %5$s %2$s %3$s %4$s
ORDER BY p.`created_at` DESC ORDER BY p.`post_created` DESC
%1$s %1$s
', ',
$getAll ? '' : 'LIMIT :offset, :take', $getAll ? '' : 'LIMIT :offset, :take',
$featuredOnly ? 'AND p.`is_featured` = 1' : '', $featuredOnly ? 'AND p.`post_is_featured` != 0' : '',
$exposeScheduled ? '' : 'AND p.`scheduled_for` < NOW()', $exposeScheduled ? '' : 'AND p.`post_scheduled` < NOW()',
$excludeDeleted ? 'AND p.`deleted_at` IS NULL' : '', $excludeDeleted ? 'AND p.`post_deleted` IS NULL' : '',
$hasCategory ? 'p.`category_id` = :category' : '1' $hasCategory ? 'p.`category_id` = :category' : '1'
)); ));
@ -191,7 +277,8 @@ function news_post_get(int $postId): array
{ {
$getPost = db_prepare(' $getPost = db_prepare('
SELECT SELECT
p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`, p.`comment_section_id`, p.`post_id`, p.`post_title`, p.`post_text`, p.`post_is_featured`, p.`post_scheduled`,
p.`post_created`, p.`post_updated`, p.`post_deleted`, p.`comment_section_id`,
c.`category_id`, c.`category_name`, c.`category_id`, c.`category_name`,
u.`user_id`, u.`username`, u.`user_id`, u.`username`,
COALESCE(u.`user_colour`, r.`role_colour`) as `user_colour` COALESCE(u.`user_colour`, r.`role_colour`) as `user_colour`

View file

@ -4,7 +4,8 @@
<div class="container container--new"> <div class="container container--new">
<div class="container__title">Overview</div> <div class="container__title">Overview</div>
<div class="container__content"> <div class="container__content">
Welcome to Manage, here you can manage things. <p>Welcome to Manage, here you can manage things.</p>
<p>Please teach me how to give a shit about management panel. -flashwave</p>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -0,0 +1,21 @@
{% extends 'manage/news/master.twig' %}
{% from 'macros.twig' import pagination %}
{% block manage_content %}
<div class="container container--new">
<div class="container__title">Categories</div>
<a href="?v=category" class="input__button input__button--new">New Category</a>
{% for cat in news_categories %}
<p>
<a href="?v=category&amp;c={{ cat.category_id }}" class="input__button input__button--new">{{ cat.category_id }}</a>
{{ cat.category_name }},
{{ cat.category_is_hidden }},
{{ cat.category_created }}
</p>
{% endfor %}
{{ pagination(categories_count, categories_take, categories_offset, '?v=categories') }}
</div>
{% endblock %}

View file

@ -0,0 +1,31 @@
{% extends 'manage/news/master.twig' %}
{% set is_new = category|length < 1 %}
{% block manage_content %}
<form method="post" action="" class="container container--new">
<div class="container__title">{{ is_new ? 'New Category' : 'Editing ' ~ category.category_name }}</div>
{{ 'news_category'|csrf|raw }}
<input type="hidden" name="category[id]" value="{{ category.category_id|default(0) }}">
<table style="color:inherit">
<tr>
<td>Name</td>
<td><input type="text" required name="category[name]" class="input__text input__text--new" value="{{ category.category_name|default() }}"></td>
</tr>
<tr>
<td>Description</td>
<td><textarea name="category[description]" required class="input__textarea input__textarea--new">{{ category.category_description|default() }}</textarea></td>
</tr>
<tr>
<td>Is Hidden</td>
<td><input type="checkbox" name="category[hidden]" {% if category.category_is_hidden|default(false) %}checked{% endif %}></td>
</tr>
</table>
<button class="input__button input__button--new">Save</button>
</form>
{% endblock %}

View file

@ -0,0 +1,41 @@
{% extends 'manage/news/master.twig' %}
{% set is_new = post|length < 1 %}
{% block manage_content %}
<form method="post" action="" class="container container--new">
<div class="container__title">{{ is_new ? 'New Post' : 'Editing ' ~ post.post_title }}</div>
{{ 'news_post'|csrf|raw }}
<input type="hidden" name="post[id]" value="{{ post.post_id|default(0) }}">
<table style="color:inherit">
<tr>
<td>Name</td>
<td><input type="text" required name="post[title]" class="input__text input__text--new" value="{{ post.post_title|default() }}"></td>
</tr>
<tr>
<td>Category</td>
<td>
<select name="post[category]" class="input__select input__select--new">
{% for category in categories %}
<option value="{{ category.category_id }}" {% if category.category_id == post.category_id|default(0) %}selected{% endif %}>{{ category.category_name }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td>Is Featured</td>
<td><input type="checkbox" name="post[featured]" {% if post.post_is_featured|default(false) %}checked{% endif %}></td>
</tr>
<tr>
<td colspan="2"><textarea name="post[text]" required class="input__textarea input__textarea--new">{{ post.post_text|default() }}</textarea></td>
</tr>
</table>
<button class="input__button input__button--new">Save</button>
</form>
{% endblock %}

View file

@ -0,0 +1,27 @@
{% extends 'manage/news/master.twig' %}
{% from 'macros.twig' import pagination %}
{% block manage_content %}
<div class="container container--new">
<div class="container__title">News Posts</div>
<a href="?v=post" class="input__button input__button--new">New Post</a>
{% for post in news_posts %}
<p>
<a href="?v=post&amp;p={{ post.post_id }}" class="input__button input__button--new">{{ post.post_id }}</a>
<a href="?v=category&amp;c={{ post.category_id }}" class="input__button input__button--new">Cat: {{ post.category_id }}</a>
{{ post.post_is_featured }},
{{ post.user_id }},
{{ post.post_title }},
{{ post.post_scheduled }},
{{ post.post_created }},
{{ post.post_updated }},
{{ post.post_deleted }},
{{ post.comment_section_id }}
</p>
{% endfor %}
{{ pagination(posts_count, posts_take, posts_offset, '?v=categories') }}
</div>
{% endblock %}

View file

@ -22,8 +22,8 @@
<div class="news__preview__info"> <div class="news__preview__info">
<a href="/news.php?p={{ post.post_id }}" class="news__preview__link"> <a href="/news.php?p={{ post.post_id }}" class="news__preview__link">
<time class="news__preview__date" datetime="{{ post.created_at|date('c') }}" title="{{ post.created_at|date('r') }}"> <time class="news__preview__date" datetime="{{ post.post_created|date('c') }}" title="{{ post.post_created|date('r') }}">
{{ post.created_at|time_diff }} {{ post.post_created|time_diff }}
</time> </time>
</a> </a>

View file

@ -36,22 +36,21 @@
<div class="news__post__info__name"> <div class="news__post__info__name">
Posted Posted
</div> </div>
<time class="news__post__info__value" datetime="{{ post.created_at|date('c') }}" title="{{ post.created_at|date('r') }}"> <time class="news__post__info__value" datetime="{{ post.post_created|date('c') }}" title="{{ post.post_created|date('r') }}">
{{ post.created_at|time_diff }} {{ post.post_created|time_diff }}
</time> </time>
</div> </div>
{# TODO: make this work {% if post.post_updated|date('U') > post.post_created|date('U') %}
{% if post.updated_at is defined %}
<div class="news__post__detail news__post__info"> <div class="news__post__detail news__post__info">
<div class="news__post__info__name"> <div class="news__post__info__name">
Updated Updated
</div> </div>
<div class="news__post__info__value"> <time class="news__post__info__value" datetime="{{ post.post_updated|date('c') }}" title="{{ post.post_updated|date('r') }}">
{{ post.updated_at }} {{ post.post_updated|time_diff }}
</div> </time>
</div> </div>
{% endif %} #} {% endif %}
</div> </div>
</div> </div>
</div> </div>