Initial dev export (exclude uploads/runtime)
This commit is contained in:
316
modules/blog/BlogController.php
Normal file
316
modules/blog/BlogController.php
Normal file
@@ -0,0 +1,316 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Blog;
|
||||
|
||||
use Core\Http\Response;
|
||||
use Core\Services\Auth;
|
||||
use Core\Services\Database;
|
||||
use Core\Views\View;
|
||||
use DateTime;
|
||||
use PDO;
|
||||
use Throwable;
|
||||
|
||||
class BlogController
|
||||
{
|
||||
private View $view;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->view = new View(__DIR__ . '/views');
|
||||
}
|
||||
|
||||
public function index(): Response
|
||||
{
|
||||
$db = Database::get();
|
||||
$posts = [];
|
||||
$page = null;
|
||||
if ($db instanceof PDO) {
|
||||
$pageStmt = $db->prepare("SELECT title, content_html FROM ac_pages WHERE is_blog_index = 1 AND is_published = 1 LIMIT 1");
|
||||
$pageStmt->execute();
|
||||
$page = $pageStmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
|
||||
$stmt = $db->prepare("
|
||||
SELECT title, slug, excerpt, published_at, featured_image_url, author_name, category, tags
|
||||
FROM ac_posts
|
||||
WHERE is_published = 1
|
||||
ORDER BY COALESCE(published_at, created_at) DESC
|
||||
");
|
||||
$stmt->execute();
|
||||
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
return new Response($this->view->render('site/index.php', [
|
||||
'title' => 'News',
|
||||
'posts' => $posts,
|
||||
'page' => $page,
|
||||
]));
|
||||
}
|
||||
|
||||
public function show(): Response
|
||||
{
|
||||
$slug = trim((string)($_GET['slug'] ?? ''));
|
||||
if ($slug === '') {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
$db = Database::get();
|
||||
if (!$db instanceof PDO) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("
|
||||
SELECT title, content_html, published_at, featured_image_url, author_name, category, tags
|
||||
FROM ac_posts
|
||||
WHERE slug = :slug AND is_published = 1
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([':slug' => $slug]);
|
||||
$post = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$post) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
return new Response($this->view->render('site/show.php', [
|
||||
'title' => (string)$post['title'],
|
||||
'content_html' => (string)$post['content_html'],
|
||||
'published_at' => (string)($post['published_at'] ?? ''),
|
||||
'featured_image_url' => (string)($post['featured_image_url'] ?? ''),
|
||||
'author_name' => (string)($post['author_name'] ?? ''),
|
||||
'category' => (string)($post['category'] ?? ''),
|
||||
'tags' => (string)($post['tags'] ?? ''),
|
||||
]));
|
||||
}
|
||||
|
||||
public function adminIndex(): Response
|
||||
{
|
||||
if ($guard = $this->guard(['admin', 'manager'])) {
|
||||
return $guard;
|
||||
}
|
||||
$db = Database::get();
|
||||
$posts = [];
|
||||
if ($db instanceof PDO) {
|
||||
$stmt = $db->query("SELECT id, title, slug, author_name, is_published, published_at, updated_at FROM ac_posts ORDER BY updated_at DESC");
|
||||
$posts = $stmt ? $stmt->fetchAll(PDO::FETCH_ASSOC) : [];
|
||||
}
|
||||
return new Response($this->view->render('admin/index.php', [
|
||||
'title' => 'Posts',
|
||||
'posts' => $posts,
|
||||
]));
|
||||
}
|
||||
|
||||
public function adminEdit(): Response
|
||||
{
|
||||
if ($guard = $this->guard(['admin', 'manager'])) {
|
||||
return $guard;
|
||||
}
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||
$post = [
|
||||
'id' => 0,
|
||||
'title' => '',
|
||||
'slug' => '',
|
||||
'excerpt' => '',
|
||||
'featured_image_url' => '',
|
||||
'author_name' => '',
|
||||
'category' => '',
|
||||
'tags' => '',
|
||||
'content_html' => '',
|
||||
'is_published' => 0,
|
||||
'published_at' => '',
|
||||
];
|
||||
|
||||
$db = Database::get();
|
||||
if ($id > 0 && $db instanceof PDO) {
|
||||
$stmt = $db->prepare("SELECT * FROM ac_posts WHERE id = :id LIMIT 1");
|
||||
$stmt->execute([':id' => $id]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($row) {
|
||||
$post = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return new Response($this->view->render('admin/edit.php', [
|
||||
'title' => $id > 0 ? 'Edit Post' : 'New Post',
|
||||
'post' => $post,
|
||||
'error' => '',
|
||||
]));
|
||||
}
|
||||
|
||||
public function adminSave(): Response
|
||||
{
|
||||
if ($guard = $this->guard(['admin', 'manager'])) {
|
||||
return $guard;
|
||||
}
|
||||
$db = Database::get();
|
||||
if (!$db instanceof PDO) {
|
||||
return new Response('', 302, ['Location' => '/admin/posts']);
|
||||
}
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$title = trim((string)($_POST['title'] ?? ''));
|
||||
$slug = trim((string)($_POST['slug'] ?? ''));
|
||||
$excerpt = trim((string)($_POST['excerpt'] ?? ''));
|
||||
$featuredImage = trim((string)($_POST['featured_image_url'] ?? ''));
|
||||
$authorName = trim((string)($_POST['author_name'] ?? ''));
|
||||
$category = trim((string)($_POST['category'] ?? ''));
|
||||
$tags = trim((string)($_POST['tags'] ?? ''));
|
||||
$content = (string)($_POST['content_html'] ?? '');
|
||||
$isPublished = isset($_POST['is_published']) ? 1 : 0;
|
||||
$publishedAt = trim((string)($_POST['published_at'] ?? ''));
|
||||
|
||||
if ($title === '') {
|
||||
return $this->renderEditError($id, $title, $slug, $excerpt, $featuredImage, $authorName, $category, $tags, $content, $isPublished, $publishedAt, 'Title is required.');
|
||||
}
|
||||
|
||||
if ($slug === '') {
|
||||
$slug = $this->slugify($title);
|
||||
} else {
|
||||
$slug = $this->slugify($slug);
|
||||
}
|
||||
|
||||
if ($publishedAt !== '') {
|
||||
try {
|
||||
$dt = new DateTime($publishedAt);
|
||||
$publishedAt = $dt->format('Y-m-d H:i:s');
|
||||
} catch (Throwable $e) {
|
||||
$publishedAt = '';
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if ($id > 0) {
|
||||
$chk = $db->prepare("SELECT id FROM ac_posts WHERE slug = :slug AND id != :id LIMIT 1");
|
||||
$chk->execute([':slug' => $slug, ':id' => $id]);
|
||||
} else {
|
||||
$chk = $db->prepare("SELECT id FROM ac_posts WHERE slug = :slug LIMIT 1");
|
||||
$chk->execute([':slug' => $slug]);
|
||||
}
|
||||
if ($chk->fetch()) {
|
||||
return $this->renderEditError($id, $title, $slug, $excerpt, $featuredImage, $authorName, $category, $tags, $content, $isPublished, $publishedAt, 'Slug already exists.');
|
||||
}
|
||||
|
||||
if ($id > 0) {
|
||||
$stmt = $db->prepare("
|
||||
UPDATE ac_posts
|
||||
SET title = :title, slug = :slug, excerpt = :excerpt,
|
||||
featured_image_url = :featured_image_url, author_name = :author_name,
|
||||
category = :category, tags = :tags, content_html = :content,
|
||||
is_published = :published, published_at = :published_at
|
||||
WHERE id = :id
|
||||
");
|
||||
$stmt->execute([
|
||||
':title' => $title,
|
||||
':slug' => $slug,
|
||||
':excerpt' => $excerpt !== '' ? $excerpt : null,
|
||||
':featured_image_url' => $featuredImage !== '' ? $featuredImage : null,
|
||||
':author_name' => $authorName !== '' ? $authorName : null,
|
||||
':category' => $category !== '' ? $category : null,
|
||||
':tags' => $tags !== '' ? $tags : null,
|
||||
':content' => $content,
|
||||
':published' => $isPublished,
|
||||
':published_at' => $publishedAt !== '' ? $publishedAt : null,
|
||||
':id' => $id,
|
||||
]);
|
||||
} else {
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO ac_posts (title, slug, excerpt, featured_image_url, author_name, category, tags, content_html, is_published, published_at)
|
||||
VALUES (:title, :slug, :excerpt, :featured_image_url, :author_name, :category, :tags, :content, :published, :published_at)
|
||||
");
|
||||
$stmt->execute([
|
||||
':title' => $title,
|
||||
':slug' => $slug,
|
||||
':excerpt' => $excerpt !== '' ? $excerpt : null,
|
||||
':featured_image_url' => $featuredImage !== '' ? $featuredImage : null,
|
||||
':author_name' => $authorName !== '' ? $authorName : null,
|
||||
':category' => $category !== '' ? $category : null,
|
||||
':tags' => $tags !== '' ? $tags : null,
|
||||
':content' => $content,
|
||||
':published' => $isPublished,
|
||||
':published_at' => $publishedAt !== '' ? $publishedAt : null,
|
||||
]);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
return $this->renderEditError($id, $title, $slug, $excerpt, $featuredImage, $authorName, $category, $tags, $content, $isPublished, $publishedAt, 'Unable to save post.');
|
||||
}
|
||||
|
||||
return new Response('', 302, ['Location' => '/admin/posts']);
|
||||
}
|
||||
|
||||
public function adminDelete(): Response
|
||||
{
|
||||
if ($guard = $this->guard(['admin', 'manager'])) {
|
||||
return $guard;
|
||||
}
|
||||
$db = Database::get();
|
||||
if (!$db instanceof PDO) {
|
||||
return new Response('', 302, ['Location' => '/admin/posts']);
|
||||
}
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
if ($id > 0) {
|
||||
$stmt = $db->prepare("DELETE FROM ac_posts WHERE id = :id");
|
||||
$stmt->execute([':id' => $id]);
|
||||
}
|
||||
return new Response('', 302, ['Location' => '/admin/posts']);
|
||||
}
|
||||
|
||||
private function renderEditError(
|
||||
int $id,
|
||||
string $title,
|
||||
string $slug,
|
||||
string $excerpt,
|
||||
string $featuredImage,
|
||||
string $authorName,
|
||||
string $category,
|
||||
string $tags,
|
||||
string $content,
|
||||
int $isPublished,
|
||||
string $publishedAt,
|
||||
string $error
|
||||
): Response {
|
||||
$post = [
|
||||
'id' => $id,
|
||||
'title' => $title,
|
||||
'slug' => $slug,
|
||||
'excerpt' => $excerpt,
|
||||
'featured_image_url' => $featuredImage,
|
||||
'author_name' => $authorName,
|
||||
'category' => $category,
|
||||
'tags' => $tags,
|
||||
'content_html' => $content,
|
||||
'is_published' => $isPublished,
|
||||
'published_at' => $publishedAt,
|
||||
];
|
||||
return new Response($this->view->render('admin/edit.php', [
|
||||
'title' => $id > 0 ? 'Edit Post' : 'New Post',
|
||||
'post' => $post,
|
||||
'error' => $error,
|
||||
]));
|
||||
}
|
||||
|
||||
private function notFound(): Response
|
||||
{
|
||||
$view = new View();
|
||||
return new Response($view->render('site/404.php', [
|
||||
'title' => 'Not Found',
|
||||
'message' => 'Post not found.',
|
||||
]), 404);
|
||||
}
|
||||
|
||||
private function slugify(string $value): string
|
||||
{
|
||||
$value = strtolower(trim($value));
|
||||
$value = preg_replace('~[^a-z0-9]+~', '-', $value) ?? $value;
|
||||
$value = trim($value, '-');
|
||||
return $value !== '' ? $value : 'post';
|
||||
}
|
||||
|
||||
private function guard(array $roles): ?Response
|
||||
{
|
||||
if (!Auth::check()) {
|
||||
return new Response('', 302, ['Location' => '/admin/login']);
|
||||
}
|
||||
if (!Auth::hasRole($roles)) {
|
||||
return new Response('', 302, ['Location' => '/admin']);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user