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;
|
||||
}
|
||||
}
|
||||
19
modules/blog/module.php
Normal file
19
modules/blog/module.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Core\Http\Router;
|
||||
use Modules\Blog\BlogController;
|
||||
|
||||
require_once __DIR__ . '/BlogController.php';
|
||||
|
||||
return function (Router $router): void {
|
||||
$controller = new BlogController();
|
||||
$router->get('/news', [$controller, 'index']);
|
||||
$router->get('/news/post', [$controller, 'show']);
|
||||
|
||||
$router->get('/admin/posts', [$controller, 'adminIndex']);
|
||||
$router->get('/admin/posts/new', [$controller, 'adminEdit']);
|
||||
$router->get('/admin/posts/edit', [$controller, 'adminEdit']);
|
||||
$router->post('/admin/posts/save', [$controller, 'adminSave']);
|
||||
$router->post('/admin/posts/delete', [$controller, 'adminDelete']);
|
||||
};
|
||||
67
modules/blog/views/admin/edit.php
Normal file
67
modules/blog/views/admin/edit.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
$pageTitle = $title ?? 'Edit Post';
|
||||
$post = $post ?? [];
|
||||
$error = $error ?? '';
|
||||
ob_start();
|
||||
?>
|
||||
<section class="admin-card">
|
||||
<div class="badge">Blog</div>
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:16px; margin-top:16px;">
|
||||
<div>
|
||||
<h1 style="font-size:28px; margin:0;"><?= htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8') ?></h1>
|
||||
<p style="color: var(--muted); margin-top:6px;">Write a news post or update.</p>
|
||||
</div>
|
||||
<a href="/admin/posts" class="btn outline">Back</a>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div style="margin-top:16px; color:#f3b0b0; font-size:13px;"><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="/admin/posts/save" style="margin-top:18px; display:grid; gap:16px;">
|
||||
<input type="hidden" name="id" value="<?= (int)($post['id'] ?? 0) ?>">
|
||||
<div class="admin-card" style="padding:16px;">
|
||||
<div style="display:grid; gap:12px;">
|
||||
<label class="label">Title</label>
|
||||
<input class="input" name="title" value="<?= htmlspecialchars((string)($post['title'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="Post title">
|
||||
<label class="label">Slug</label>
|
||||
<input class="input" name="slug" value="<?= htmlspecialchars((string)($post['slug'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="post-title">
|
||||
<label class="label">Excerpt</label>
|
||||
<textarea class="input" name="excerpt" rows="3" style="resize:vertical;"><?= htmlspecialchars((string)($post['excerpt'] ?? ''), ENT_QUOTES, 'UTF-8') ?></textarea>
|
||||
<label class="label">Featured Image URL</label>
|
||||
<input class="input" name="featured_image_url" value="<?= htmlspecialchars((string)($post['featured_image_url'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="https://example.com/cover.jpg">
|
||||
<label class="label">Author</label>
|
||||
<input class="input" name="author_name" value="<?= htmlspecialchars((string)($post['author_name'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="AudioCore Team">
|
||||
<label class="label">Category</label>
|
||||
<input class="input" name="category" value="<?= htmlspecialchars((string)($post['category'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="News">
|
||||
<label class="label">Tags (comma separated)</label>
|
||||
<input class="input" name="tags" value="<?= htmlspecialchars((string)($post['tags'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="release, label, update">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
|
||||
<label class="label" style="margin:0;">Content (HTML)</label>
|
||||
<button type="button" class="btn outline small" data-media-picker="blog_content_html">Insert Media</button>
|
||||
</div>
|
||||
<textarea class="input" id="blog_content_html" name="content_html" rows="16" style="resize:vertical; font-family:'IBM Plex Mono', monospace; font-size:13px; line-height:1.6;"><?= htmlspecialchars((string)($post['content_html'] ?? ''), ENT_QUOTES, 'UTF-8') ?></textarea>
|
||||
<label class="label">Published At</label>
|
||||
<input class="input" name="published_at" value="<?= htmlspecialchars((string)($post['published_at'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="2026-01-25 18:30:00">
|
||||
<label style="display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:0.2em;">
|
||||
<input type="checkbox" name="is_published" value="1" <?= ((int)($post['is_published'] ?? 0) === 1) ? 'checked' : '' ?>>
|
||||
Published
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:flex-end; gap:12px; align-items:center;">
|
||||
<button type="submit" class="btn">Save post</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if (!empty($post['id'])): ?>
|
||||
<form method="post" action="/admin/posts/delete" onsubmit="return confirm('Delete this post?');" style="margin-top:12px;">
|
||||
<input type="hidden" name="id" value="<?= (int)($post['id'] ?? 0) ?>">
|
||||
<button type="submit" class="btn outline">Delete</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../../../admin/views/layout.php';
|
||||
54
modules/blog/views/admin/index.php
Normal file
54
modules/blog/views/admin/index.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
$pageTitle = 'Posts';
|
||||
$posts = $posts ?? [];
|
||||
ob_start();
|
||||
?>
|
||||
<section class="admin-card">
|
||||
<div class="badge">Blog</div>
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:16px; margin-top:16px;">
|
||||
<div>
|
||||
<h1 style="font-size:28px; margin:0;">Posts</h1>
|
||||
<p style="color: var(--muted); margin-top:6px;">Publish news updates and announcements.</p>
|
||||
</div>
|
||||
<a href="/admin/posts/new" class="btn small">New Post</a>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:18px; display:grid; gap:10px;">
|
||||
<div style="display:grid; grid-template-columns: 2fr 1fr 140px 140px 160px 120px; gap:12px; font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:0.2em;">
|
||||
<div>Title</div>
|
||||
<div>Slug</div>
|
||||
<div>Author</div>
|
||||
<div>Status</div>
|
||||
<div>Published</div>
|
||||
<div>Actions</div>
|
||||
</div>
|
||||
<?php if (!$posts): ?>
|
||||
<div style="color: var(--muted); font-size:13px;">No posts yet.</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($posts as $post): ?>
|
||||
<div style="display:grid; grid-template-columns: 2fr 1fr 140px 140px 160px 120px; gap:12px; align-items:center; padding:10px 12px; border-radius:16px; border:1px solid var(--stroke); background: rgba(14,14,16,0.9);">
|
||||
<div style="font-weight:600;"><?= htmlspecialchars((string)($post['title'] ?? ''), ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<div style="font-size:12px; color:var(--muted); font-family: 'IBM Plex Mono', monospace;">
|
||||
<?= htmlspecialchars((string)($post['slug'] ?? ''), ENT_QUOTES, 'UTF-8') ?>
|
||||
</div>
|
||||
<div style="font-size:12px; color:var(--muted);">
|
||||
<?= htmlspecialchars((string)($post['author_name'] ?? ''), ENT_QUOTES, 'UTF-8') ?>
|
||||
</div>
|
||||
<div style="font-size:12px; color:<?= ((int)($post['is_published'] ?? 0) === 1) ? 'var(--accent-2)' : 'var(--muted)' ?>;">
|
||||
<?= ((int)($post['is_published'] ?? 0) === 1) ? 'Published' : 'Draft' ?>
|
||||
</div>
|
||||
<div style="font-size:12px; color:var(--muted);">
|
||||
<?= htmlspecialchars((string)($post['published_at'] ?? ''), ENT_QUOTES, 'UTF-8') ?>
|
||||
</div>
|
||||
<div style="display:flex; gap:8px;">
|
||||
<a href="/admin/posts/edit?id=<?= (int)$post['id'] ?>" class="btn outline small">Edit</a>
|
||||
<a href="/news/<?= htmlspecialchars((string)($post['slug'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" class="btn outline small">View</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../../../admin/views/layout.php';
|
||||
61
modules/blog/views/site/index.php
Normal file
61
modules/blog/views/site/index.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
$pageTitle = $title ?? 'News';
|
||||
$posts = $posts ?? [];
|
||||
$page = $page ?? null;
|
||||
ob_start();
|
||||
?>
|
||||
<section class="card">
|
||||
<div class="badge">News</div>
|
||||
<?php if ($page && !empty($page['content_html'])): ?>
|
||||
<div style="margin-top:12px; color:var(--muted); line-height:1.7;">
|
||||
<?= (string)$page['content_html'] ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<h1 style="margin-top:12px; font-size:30px;">Latest Updates</h1>
|
||||
<p style="color:var(--muted); margin-top:8px;">News, updates, and announcements.</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="margin-top:18px; display:grid; gap:12px;">
|
||||
<?php if (!$posts): ?>
|
||||
<div style="color:var(--muted);">No posts yet.</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($posts as $post): ?>
|
||||
<article style="padding:14px 16px; border-radius:16px; border:1px solid rgba(255,255,255,0.12); background: rgba(0,0,0,0.25);">
|
||||
<div style="font-size:11px; text-transform:uppercase; letter-spacing:0.28em; color:var(--muted);">Post</div>
|
||||
<h2 style="margin:8px 0 6px; font-size:22px;"><?= htmlspecialchars((string)($post['title'] ?? ''), ENT_QUOTES, 'UTF-8') ?></h2>
|
||||
<?php if (!empty($post['featured_image_url'])): ?>
|
||||
<img src="<?= htmlspecialchars((string)$post['featured_image_url'], ENT_QUOTES, 'UTF-8') ?>" alt="" style="width:100%; border-radius:12px; margin:10px 0;">
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($post['published_at'])): ?>
|
||||
<div style="font-size:12px; color:var(--muted); margin-bottom:8px;"><?= htmlspecialchars((string)$post['published_at'], ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php endif; ?>
|
||||
<div style="font-size:12px; color:var(--muted); margin-bottom:8px;">
|
||||
<?php if (!empty($post['author_name'])): ?>
|
||||
<?= htmlspecialchars((string)$post['author_name'], ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($post['category'])): ?>
|
||||
<?php if (!empty($post['author_name'])): ?> · <?php endif; ?>
|
||||
<?= htmlspecialchars((string)$post['category'], ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<p style="color:var(--muted); line-height:1.6;">
|
||||
<?= htmlspecialchars((string)($post['excerpt'] ?? ''), ENT_QUOTES, 'UTF-8') ?>
|
||||
</p>
|
||||
<?php if (!empty($post['tags'])): ?>
|
||||
<div style="margin-top:8px; display:flex; flex-wrap:wrap; gap:6px;">
|
||||
<?php foreach (array_filter(array_map('trim', explode(',', (string)$post['tags'] ?? ''))) as $tag): ?>
|
||||
<span style="font-size:11px; color:#c8ccd8; border:1px solid rgba(255,255,255,0.15); padding:4px 8px; border-radius:999px;">
|
||||
<?= htmlspecialchars((string)$tag, ENT_QUOTES, 'UTF-8') ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<a href="/news/<?= htmlspecialchars((string)($post['slug'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" style="display:inline-flex; margin-top:10px; font-size:12px; text-transform:uppercase; letter-spacing:0.2em; color:#9ad4ff;">Read more</a>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../../../views/site/layout.php';
|
||||
47
modules/blog/views/site/show.php
Normal file
47
modules/blog/views/site/show.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
$pageTitle = $title ?? 'Post';
|
||||
$contentHtml = $content_html ?? '';
|
||||
$publishedAt = $published_at ?? '';
|
||||
$featuredImage = $featured_image_url ?? '';
|
||||
$authorName = $author_name ?? '';
|
||||
$category = $category ?? '';
|
||||
$tags = $tags ?? '';
|
||||
ob_start();
|
||||
?>
|
||||
<section class="card">
|
||||
<div class="badge">News</div>
|
||||
<h1 style="margin-top:12px; font-size:30px;"><?= htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8') ?></h1>
|
||||
<?php if ($publishedAt !== '' || $authorName !== '' || $category !== ''): ?>
|
||||
<div style="font-size:12px; color:var(--muted); margin-top:6px;">
|
||||
<?php if ($publishedAt !== ''): ?>
|
||||
<?= htmlspecialchars($publishedAt, ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php endif; ?>
|
||||
<?php if ($authorName !== ''): ?>
|
||||
<?php if ($publishedAt !== ''): ?> · <?php endif; ?>
|
||||
<?= htmlspecialchars($authorName, ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php endif; ?>
|
||||
<?php if ($category !== ''): ?>
|
||||
<?php if ($publishedAt !== '' || $authorName !== ''): ?> · <?php endif; ?>
|
||||
<?= htmlspecialchars($category, ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($featuredImage !== ''): ?>
|
||||
<img src="<?= htmlspecialchars($featuredImage, ENT_QUOTES, 'UTF-8') ?>" alt="" style="width:100%; border-radius:16px; margin-top:16px;">
|
||||
<?php endif; ?>
|
||||
<div style="margin-top:14px; color:var(--muted); line-height:1.8;">
|
||||
<?= $contentHtml ?>
|
||||
</div>
|
||||
<?php if ($tags !== ''): ?>
|
||||
<div style="margin-top:16px; display:flex; flex-wrap:wrap; gap:6px;">
|
||||
<?php foreach (array_filter(array_map('trim', explode(',', (string)$tags))) as $tag): ?>
|
||||
<span style="font-size:11px; color:#c8ccd8; border:1px solid rgba(255,255,255,0.15); padding:4px 8px; border-radius:999px;">
|
||||
<?= htmlspecialchars((string)$tag, ENT_QUOTES, 'UTF-8') ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../../../views/site/layout.php';
|
||||
Reference in New Issue
Block a user