Membangun CMS Sederhana dengan PHP & MySQL - Part 4: Manajemen Kategori & Integrasi dengan Artikel
Selamat datang kembali di seri tutorial CMS PHP kita! Setelah berhasil membangun sistem login (Part 2) dan manajemen artikel CRUD (Part 3), kini saatnya kita membuat konten lebih terstruktur dengan menambahkan Manajemen Kategori.
Kategori membantu mengelompokkan artikel berdasarkan topik, memudahkan navigasi bagi pengunjung, dan pengelolaan konten bagi admin. Di Bagian 4 ini, kita akan:
- Membuat tabel
categories
di database. - Mengimplementasikan operasi CRUD (Create, Read, Update, Delete) untuk kategori di area admin.
- Mengintegrasikan pemilihan kategori saat menambah/mengedit artikel.
- Menampilkan kategori artikel di frontend (daftar artikel dan halaman detail).
- (Opsional) Membuat halaman arsip kategori untuk menampilkan semua artikel dalam satu kategori.
Prasyarat
Pastikan Anda telah menyelesaikan Part 1, Part 2, dan Part 3. Kita akan memodifikasi beberapa file yang sudah ada dan menambahkan fungsionalitas baru.
Persiapan Database: Tabel categories
Pertama, kita perlu tabel untuk menyimpan kategori.
-
Buka phpMyAdmin dan pilih database
cms_sederhana_db
Anda. -
Jalankan query SQL berikut untuk membuat tabel
categories
:CREATE TABLE `categories` ( `id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(100) NOT NULL UNIQUE, `slug` VARCHAR(100) NOT NULL UNIQUE, `description` TEXT NULL, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Penjelasan Tabel
categories
:name
: Nama kategori yang akan ditampilkan, harus unik.slug
: Versi nama yang ramah URL, juga unik.description
: Deskripsi singkat opsional tentang kategori.
-
Modifikasi Tabel
posts
: Kita perlu menambahkan kolomcategory_id
di tabelposts
sebagai foreign key ke tabelcategories
. Jika Anda sudah menambahkannya di Part 1 (sebagaiNULL
), kita hanya perlu memastikan tipenya benar atau menambahkannya jika belum.Jika kolom
category_id
belum ada di tabelposts
:ALTER TABLE `posts` ADD COLUMN `category_id` INT(11) UNSIGNED NULL AFTER `author_id`, ADD CONSTRAINT `fk_post_category` FOREIGN KEY (`category_id`) REFERENCES `categories`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
ON DELETE SET NULL
: Jika sebuah kategori dihapus,category_id
pada artikel terkait akan diatur menjadiNULL
(artikel tidak kehilangan kategori, hanya tidak terkait lagi). Alternatifnya bisaON DELETE RESTRICT
untuk mencegah penghapusan kategori jika masih ada artikel terkait.ON UPDATE CASCADE
: Jikaid
kategori berubah (jarang terjadi),category_id
di artikel juga akan terupdate.
Isi Beberapa Data Kategori Dummy: Masukkan beberapa kategori contoh melalui phpMyAdmin atau SQL:
INSERT INTO `categories` (`name`, `slug`, `description`) VALUES ('Teknologi', 'teknologi', 'Artikel seputar dunia teknologi dan inovasi.'), ('Tutorial', 'tutorial', 'Panduan langkah demi langkah untuk berbagai topik.'), ('Berita', 'berita', 'Informasi dan berita terkini.');
Langkah 1: Halaman Manajemen Kategori Admin (admin/manage_categories.php
)
Mirip dengan manage_posts.php
, halaman ini akan menampilkan daftar kategori dan opsi CRUD.
Buat file admin/manage_categories.php
:
<?php
// admin/manage_categories.php
require_once 'auth_check.php';
require_once '../includes/db_connect.php';
require_once '../includes/functions.php';
$page_title_admin = "Kelola Kategori";
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($page_title_admin); ?> - CMS Admin</title>
<link rel="stylesheet" href="../css/admin_style.css">
</head>
<body>
<header class="admin-header">
<h1>Admin Panel</h1>
<div>Selamat datang, <?php echo htmlspecialchars($current_username); ?> | <a href="logout.php">Logout</a></div>
</header>
<div class="admin-container">
<nav class="admin-nav">
<ul>
<li><a href="index.php">Dashboard</a></li>
<li><a href="manage_posts.php">Kelola Artikel</a></li>
<li><a href="manage_categories.php" class="active">Kelola Kategori</a></li>
<?php if ($current_user_role === 'admin'): ?>
<li><a href="manage_users.php">Kelola Pengguna</a></li>
<?php endif; ?>
<li><a href="../index.php" target="_blank">Lihat Situs</a></li>
</ul>
</nav>
<h2><?php echo htmlspecialchars($page_title_admin); ?></h2>
<?php display_flash_message('category_action'); ?>
<a href="add_category.php" class="btn btn-add">Tambah Kategori Baru</a>
<table class="admin-table">
<thead>
<tr>
<th>ID</th>
<th>Nama Kategori</th>
<th>Slug</th>
<th>Jumlah Artikel</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
<?php
// Query untuk mengambil kategori beserta jumlah artikel terkait
// (LEFT JOIN untuk tetap menampilkan kategori meskipun belum ada artikel)
$sql = "SELECT c.id, c.name, c.slug, COUNT(p.id) as post_count
FROM categories c
LEFT JOIN posts p ON c.id = p.category_id
GROUP BY c.id, c.name, c.slug
ORDER BY c.name ASC";
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
while ($category = mysqli_fetch_assoc($result)) {
echo "<tr>";
echo "<td>" . $category['id'] . "</td>";
echo "<td>" . htmlspecialchars($category['name']) . "</td>";
echo "<td>" . htmlspecialchars($category['slug']) . "</td>";
echo "<td>" . $category['post_count'] . "</td>";
echo "<td>
<a href='edit_category.php?id=" . $category['id'] . "' class='btn btn-edit'>Edit</a>
<a href='delete_category.php?id=" . $category['id'] . "' class='btn btn-delete' onclick='return confirm(\"Apakah Anda yakin ingin menghapus kategori ini? Menghapus kategori akan mengatur kategori artikel terkait menjadi tidak terkategori (NULL).\");'>Hapus</a>
</td>";
echo "</tr>";
}
} else {
echo "<tr><td colspan='5' style='text-align:center;'>Belum ada kategori.</td></tr>";
}
?>
</tbody>
</table>
</div>
<?php if(isset($conn)) { mysqli_close($conn); } ?>
</body>
</html>
Perubahan Utama:
- Query SQL menggunakan
LEFT JOIN
dengan tabelposts
danCOUNT(p.id)
untuk menghitung jumlah artikel dalam setiap kategori.GROUP BY
diperlukan saat menggunakan fungsi agregat sepertiCOUNT
. - Konfirmasi JavaScript untuk hapus kategori menjelaskan efeknya pada artikel.
Langkah 2: Halaman Tambah Kategori (admin/add_category.php
)
Formulir untuk membuat kategori baru.
Buat file admin/add_category.php
:
<?php
// admin/add_category.php
require_once 'auth_check.php';
require_once '../includes/db_connect.php';
require_once '../includes/functions.php';
$page_title_admin = "Tambah Kategori Baru";
$name = '';
$slug = '';
$description = '';
$errors = [];
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$name = trim($_POST['name']);
$slug_custom = trim($_POST['slug']);
$description = trim($_POST['description']);
if (empty($name)) {
$errors[] = "Nama kategori tidak boleh kosong.";
}
if (!empty($slug_custom)) {
$slug = generate_slug($slug_custom);
} else {
$slug = generate_slug($name);
}
if (!empty($slug)) {
$sql_check_slug = "SELECT id FROM categories WHERE slug = ?";
if($stmt_check = mysqli_prepare($conn, $sql_check_slug)) {
mysqli_stmt_bind_param($stmt_check, "s", $slug);
mysqli_stmt_execute($stmt_check);
mysqli_stmt_store_result($stmt_check);
if(mysqli_stmt_num_rows($stmt_check) > 0) {
$errors[] = "Slug '" . htmlspecialchars($slug) . "' sudah digunakan.";
}
mysqli_stmt_close($stmt_check);
}
} elseif(empty($name) && empty($errors)) {
$errors[] = "Nama kategori tidak valid untuk membuat slug.";
}
if (empty($errors)) {
$sql_insert = "INSERT INTO categories (name, slug, description) VALUES (?, ?, ?)";
if ($stmt = mysqli_prepare($conn, $sql_insert)) {
mysqli_stmt_bind_param($stmt, "sss", $name, $slug, $description);
if (mysqli_stmt_execute($stmt)) {
set_flash_message('category_action', "Kategori \"" . htmlspecialchars($name) . "\" berhasil ditambahkan!", "success");
header("Location: manage_categories.php");
exit;
} else {
$errors[] = "Gagal menyimpan kategori: " . mysqli_stmt_error($stmt);
}
mysqli_stmt_close($stmt);
} else {
$errors[] = "Gagal mempersiapkan statement: " . mysqli_error($conn);
}
}
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($page_title_admin); ?> - CMS Admin</title>
<link rel="stylesheet" href="../css/admin_style.css">
</head>
<body>
<header class="admin-header">
<h1>Admin Panel</h1>
<div>Selamat datang, <?php echo htmlspecialchars($current_username); ?> | <a href="logout.php">Logout</a></div>
</header>
<div class="admin-container">
<nav class="admin-nav">
<ul>
<li><a href="index.php">Dashboard</a></li>
<li><a href="manage_posts.php">Kelola Artikel</a></li>
<li><a href="manage_categories.php">Kelola Kategori</a></li>
<?php if ($current_user_role === 'admin'): ?>
<li><a href="manage_users.php">Kelola Pengguna</a></li>
<?php endif; ?>
</ul>
</nav>
<h2><?php echo htmlspecialchars($page_title_admin); ?></h2>
<?php if (!empty($errors)): ?>
<div class="flash-message error">
<strong>Error!</strong>
<ul><?php foreach ($errors as $error) echo "<li>" . htmlspecialchars($error) . "</li>"; ?></ul>
</div>
<?php endif; ?>
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post" class="admin-form">
<div class="form-group">
<label for="name">Nama Kategori:</label>
<input type="text" name="name" id="name" value="<?php echo htmlspecialchars($name); ?>" required>
</div>
<div class="form-group">
<label for="slug">Slug (Opsional):</label>
<input type="text" name="slug" id="slug" value="<?php echo htmlspecialchars($slug); ?>" placeholder="akan-dibuat-otomatis-jika-kosong">
</div>
<div class="form-group">
<label for="description">Deskripsi (Opsional):</label>
<textarea name="description" id="description" rows="3"><?php echo htmlspecialchars($description); ?></textarea>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-submit">Simpan Kategori</button>
<a href="manage_categories.php" class="btn btn-cancel">Batal</a>
</div>
</form>
</div>
<?php if(isset($conn)) { mysqli_close($conn); } ?>
</body>
</html>
Logikanya sangat mirip dengan add_post.php
, termasuk pembuatan slug dan pengecekan keunikan.
Langkah 3: Halaman Edit Kategori (admin/edit_category.php
)
Formulir untuk memodifikasi kategori yang sudah ada.
Buat file admin/edit_category.php
:
<?php
// admin/edit_category.php
require_once 'auth_check.php';
require_once '../includes/db_connect.php';
require_once '../includes/functions.php';
$page_title_admin = "Edit Kategori";
$category_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$category_data_original = null; // Menyimpan data asli
$name = ''; $slug = ''; $description = '';
$errors = [];
if ($category_id > 0) {
$sql_select = "SELECT name, slug, description FROM categories WHERE id = ?";
if ($stmt_select = mysqli_prepare($conn, $sql_select)) {
mysqli_stmt_bind_param($stmt_select, "i", $category_id);
mysqli_stmt_execute($stmt_select);
$result_select = mysqli_stmt_get_result($stmt_select);
if ($cat = mysqli_fetch_assoc($result_select)) {
$category_data_original = $cat;
$name = $cat['name'];
$slug = $cat['slug'];
$description = $cat['description'];
} else {
set_flash_message('category_action', "Kategori dengan ID $category_id tidak ditemukan.", "error");
header("Location: manage_categories.php");
exit;
}
mysqli_stmt_close($stmt_select);
} else {
die("Error SELECT: " . mysqli_error($conn));
}
} else {
set_flash_message('category_action', "ID Kategori tidak valid.", "error");
header("Location: manage_categories.php");
exit;
}
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$name_new = trim($_POST['name']);
$slug_new_custom = trim($_POST['slug']);
$description_new = trim($_POST['description']);
if (empty($name_new)) $errors[] = "Nama kategori tidak boleh kosong.";
$final_slug = $slug; // Default ke slug lama
if (!empty($slug_new_custom)) {
$potential_slug = generate_slug($slug_new_custom);
if ($potential_slug !== $slug) {
$final_slug = $potential_slug;
}
} elseif ($name_new !== $name) {
$final_slug = generate_slug($name_new);
}
if ($final_slug !== $slug) { // Hanya cek jika slug berubah
$sql_check_slug = "SELECT id FROM categories WHERE slug = ? AND id != ?";
if($stmt_check = mysqli_prepare($conn, $sql_check_slug)) {
mysqli_stmt_bind_param($stmt_check, "si", $final_slug, $category_id);
mysqli_stmt_execute($stmt_check);
mysqli_stmt_store_result($stmt_check);
if(mysqli_stmt_num_rows($stmt_check) > 0) {
$errors[] = "Slug '" . htmlspecialchars($final_slug) . "' sudah digunakan.";
}
mysqli_stmt_close($stmt_check);
}
}
if (empty($errors)) {
$sql_update = "UPDATE categories SET name = ?, slug = ?, description = ? WHERE id = ?";
if ($stmt_update = mysqli_prepare($conn, $sql_update)) {
mysqli_stmt_bind_param($stmt_update, "sssi", $name_new, $final_slug, $description_new, $category_id);
if (mysqli_stmt_execute($stmt_update)) {
set_flash_message('category_action', "Kategori \"" . htmlspecialchars($name_new) . "\" berhasil diperbarui!", "success");
header("Location: manage_categories.php");
exit;
} else {
$errors[] = "Gagal memperbarui kategori: " . mysqli_stmt_error($stmt_update);
}
mysqli_stmt_close($stmt_update);
} else {
$errors[] = "Gagal mempersiapkan statement UPDATE: " . mysqli_error($conn);
}
}
// Update variabel untuk sticky form jika ada error
$name = $name_new;
$slug = $final_slug;
$description = $description_new;
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($page_title_admin); ?> - CMS Admin</title>
<link rel="stylesheet" href="../css/admin_style.css">
</head>
<body>
<header class="admin-header">
<h1>Admin Panel</h1>
<div>Selamat datang, <?php echo htmlspecialchars($current_username); ?> | <a href="logout.php">Logout</a></div>
</header>
<div class="admin-container">
<nav class="admin-nav">
<ul>
<li><a href="index.php">Dashboard</a></li>
<li><a href="manage_posts.php">Kelola Artikel</a></li>
<li><a href="manage_categories.php">Kelola Kategori</a></li>
<?php if ($current_user_role === 'admin'): ?>
<li><a href="manage_users.php">Kelola Pengguna</a></li>
<?php endif; ?>
</ul>
</nav>
<h2><?php echo htmlspecialchars($page_title_admin); ?>: "<?php echo htmlspecialchars($category_data_original['name']); ?>"</h2>
<?php if (!empty($errors)): ?>
<div class="flash-message error">
<strong>Error!</strong>
<ul><?php foreach ($errors as $error) echo "<li>" . htmlspecialchars($error) . "</li>"; ?></ul>
</div>
<?php endif; ?>
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>?id=<?php echo $category_id; ?>" method="post" class="admin-form">
<div class="form-group">
<label for="name">Nama Kategori:</label>
<input type="text" name="name" id="name" value="<?php echo htmlspecialchars($name); ?>" required>
</div>
<div class="form-group">
<label for="slug">Slug (Opsional):</label>
<input type="text" name="slug" id="slug" value="<?php echo htmlspecialchars($slug); ?>">
</div>
<div class="form-group">
<label for="description">Deskripsi (Opsional):</label>
<textarea name="description" id="description" rows="3"><?php echo htmlspecialchars($description); ?></textarea>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-submit">Simpan Perubahan</button>
<a href="manage_categories.php" class="btn btn-cancel">Batal</a>
</div>
</form>
</div>
<?php if(isset($conn)) { mysqli_close($conn); } ?>
</body>
</html>
Sama seperti edit_post.php
, halaman ini mengambil data yang ada, mengizinkan modifikasi, dan melakukan update.
Langkah 4: Skrip Hapus Kategori (admin/delete_category.php
)
Logika untuk menghapus kategori.
Buat file admin/delete_category.php
:
<?php
// admin/delete_category.php
require_once 'auth_check.php';
require_once '../includes/db_connect.php';
require_once '../includes/functions.php';
$category_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($category_id > 0) {
// Ambil nama kategori untuk pesan flash
$category_name = "Kategori";
$sql_get_name = "SELECT name FROM categories WHERE id = ?";
if($stmt_get_name = mysqli_prepare($conn, $sql_get_name)){
mysqli_stmt_bind_param($stmt_get_name, "i", $category_id);
mysqli_stmt_execute($stmt_get_name);
$result_name = mysqli_stmt_get_result($stmt_get_name);
if($row_name = mysqli_fetch_assoc($result_name)){
$category_name = $row_name['name'];
}
mysqli_stmt_close($stmt_get_name);
}
// Karena kita menggunakan ON DELETE SET NULL pada foreign key di tabel posts,
// kita bisa langsung menghapus kategori. Artikel terkait akan memiliki category_id = NULL.
// Jika menggunakan ON DELETE RESTRICT, kita perlu cek dulu apakah ada post terkait.
$sql_delete = "DELETE FROM categories WHERE id = ?";
if ($stmt_delete = mysqli_prepare($conn, $sql_delete)) {
mysqli_stmt_bind_param($stmt_delete, "i", $category_id);
if (mysqli_stmt_execute($stmt_delete)) {
if (mysqli_stmt_affected_rows($stmt_delete) > 0) {
set_flash_message('category_action', "Kategori \"" . htmlspecialchars($category_name) . "\" berhasil dihapus.", "success");
} else {
set_flash_message('category_action', "Gagal menghapus: Kategori tidak ditemukan atau sudah dihapus.", "error");
}
} else {
set_flash_message('category_action', "Gagal menghapus kategori: " . mysqli_stmt_error($stmt_delete), "error");
}
mysqli_stmt_close($stmt_delete);
} else {
set_flash_message('category_action', "Gagal mempersiapkan statement DELETE: " . mysqli_error($conn), "error");
}
} else {
set_flash_message('category_action', "ID Kategori tidak valid untuk dihapus.", "error");
}
header("Location: manage_categories.php");
exit;
?>
Karena kita sudah mengatur ON DELETE SET NULL
pada foreign key fk_post_category
, kita tidak perlu khawatir tentang artikel yang terkait. category_id
mereka akan otomatis menjadi NULL
.
Langkah 5: Integrasi Kategori ke Manajemen Artikel
Sekarang kita akan memodifikasi form tambah dan edit artikel untuk menyertakan pilihan kategori.
1. Modifikasi admin/add_post.php
:
Tambahkan field dropdown untuk kategori.
Di dalam <form>
sebelum <div class="form-actions">
, tambahkan:
<!-- ... (field status sebelumnya) ... -->
<div class="form-group">
<label for="category_id">Kategori:</label>
<select name="category_id" id="category_id">
<option value="">-- Pilih Kategori --</option>
<?php
$sql_categories = "SELECT id, name FROM categories ORDER BY name ASC";
$result_categories = mysqli_query($conn, $sql_categories);
if ($result_categories && mysqli_num_rows($result_categories) > 0) {
while ($cat = mysqli_fetch_assoc($result_categories)) {
// Untuk add_post, $category_id_selected belum ada, jadi tidak perlu cek 'selected'
echo "<option value=\"" . $cat['id'] . "\">" . htmlspecialchars($cat['name']) . "</option>";
}
}
?>
</select>
</div>
<!-- ... (lanjut ke form-actions) ... -->
Dan di bagian proses PHP (setelah validasi dasar), ambil nilai category_id
:
// admin/add_post.php (bagian proses PHP)
// ...
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// ... (ambil title, content, status, slug_custom) ...
$category_id_selected = isset($_POST['category_id']) ? (int)$_POST['category_id'] : null;
// ... (validasi title, content, status) ...
// Validasi category_id (opsional, jika kategori wajib)
// if ($category_id_selected === null || $category_id_selected == 0) {
// $errors[] = "Kategori harus dipilih.";
// }
// ... (proses slug dan cek keunikan slug) ...
if (empty($errors)) {
// Modifikasi SQL INSERT dan bind_param
$sql_insert = "INSERT INTO posts (title, content, slug, status, author_id, category_id) VALUES (?, ?, ?, ?, ?, ?)";
if ($stmt = mysqli_prepare($conn, $sql_insert)) {
$author_id = $_SESSION['user_id'];
// Jika category_id_selected kosong (tidak dipilih), kirim NULL ke database
$category_id_to_db = ($category_id_selected == 0 || empty($category_id_selected)) ? NULL : $category_id_selected;
mysqli_stmt_bind_param($stmt, "ssssii", $title, $content, $slug, $status, $author_id, $category_id_to_db);
// ... (lanjutkan dengan execute, set_flash_message, dll.) ...
}
// ...
}
// Update variabel untuk sticky form
// ...
$category_id = $category_id_selected; // Untuk sticky form dropdown
}
// ...
?>
<!-- Di dalam form, untuk sticky dropdown (modifikasi loop option) -->
<option value="">-- Pilih Kategori --</option>
<?php
// ... (query $sql_categories) ...
if ($result_categories && mysqli_num_rows($result_categories) > 0) {
while ($cat = mysqli_fetch_assoc($result_categories)) {
$selected_attr = (isset($category_id) && $category_id == $cat['id']) ? 'selected' : '';
echo "<option value=\"" . $cat['id'] . "\" $selected_attr>" . htmlspecialchars($cat['name']) . "</option>";
}
}
?>
2. Modifikasi admin/edit_post.php
:
Sama seperti add_post.php
, tambahkan dropdown kategori, tapi juga pastikan kategori yang tersimpan untuk artikel tersebut terpilih secara default.
Di bagian pengambilan data asli artikel:
// admin/edit_post.php (bagian ambil data asli)
// ...
$status = ''; $slug = ''; $category_id_current = null; // Tambah $category_id_current
// ...
if ($post_id > 0) {
// Modifikasi SQL SELECT untuk mengambil category_id juga
$sql_select = "SELECT title, content, slug, status, category_id FROM posts WHERE id = ?";
// ... (proses mysqli_prepare, bind_param, execute, get_result) ...
if ($post_data = mysqli_fetch_assoc($result_select)) {
// ... (isi title, content, status, slug) ...
$category_id_current = $post_data['category_id']; // Ambil ID kategori saat ini
}
// ...
}
// ...
Di dalam <form>
sebelum <div class="form-actions">
, tambahkan dropdown kategori:
<!-- ... (field status sebelumnya) ... -->
<div class="form-group">
<label for="category_id">Kategori:</label>
<select name="category_id" id="category_id">
<option value="">-- Tidak Berkategori --</option>
<?php
$sql_categories_edit = "SELECT id, name FROM categories ORDER BY name ASC";
$result_categories_edit = mysqli_query($conn, $sql_categories_edit);
if ($result_categories_edit && mysqli_num_rows($result_categories_edit) > 0) {
while ($cat_edit = mysqli_fetch_assoc($result_categories_edit)) {
// Cek apakah kategori ini adalah yang terpilih untuk post saat ini
$selected_attr = ($category_id_current == $cat_edit['id']) ? 'selected' : '';
echo "<option value=\"" . $cat_edit['id'] . "\" $selected_attr>" . htmlspecialchars($cat_edit['name']) . "</option>";
}
}
?>
</select>
</div>
<!-- ... (lanjut ke form-actions) ... -->
Dan di bagian proses PHP saat form disubmit:
// admin/edit_post.php (bagian proses PHP)
// ...
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// ... (ambil title_new, content_new, status_new, slug_new_custom) ...
$category_id_new_selected = isset($_POST['category_id']) ? (int)$_POST['category_id'] : null;
// ... (validasi dan proses slug) ...
if (empty($errors)) {
// Modifikasi SQL UPDATE dan bind_param
$sql_update = "UPDATE posts SET title = ?, content = ?, slug = ?, status = ?, category_id = ?, updated_at = NOW() WHERE id = ?";
if ($stmt_update = mysqli_prepare($conn, $sql_update)) {
$category_id_to_db_update = ($category_id_new_selected == 0 || empty($category_id_new_selected)) ? NULL : $category_id_new_selected;
mysqli_stmt_bind_param($stmt_update, "ssssii", $title_new, $content_new, $final_slug, $status_new, $category_id_to_db_update, $post_id);
// ... (lanjutkan dengan execute, set_flash_message, dll.) ...
}
// ...
}
// Update variabel untuk sticky form
// ...
$category_id_current = $category_id_new_selected; // Untuk sticky dropdown
}
// ...
Langkah 6: Menampilkan Kategori di Frontend
1. Modifikasi index.php
(daftar artikel):
Kita akan mengambil nama kategori dan menampilkannya.
// index.php (bagian query SQL)
// Modifikasi query untuk JOIN dengan tabel categories
$sql = "SELECT p.id, p.title, p.content, p.slug, p.created_at, p.image_path,
c.name as category_name, c.slug as category_slug
FROM posts p
LEFT JOIN categories c ON p.category_id = c.id
WHERE p.status = 'published'
ORDER BY p.created_at DESC";
// ... (lanjutkan dengan mysqli_query dan loop) ...
// Di dalam loop while($post = mysqli_fetch_assoc($result)):
// Tambahkan ini di dalam <p class="post-meta">
?>
<p class="post-meta">
<span class="post-date">Dipublikasikan pada: <?php echo date('j F Y', strtotime($post['created_at'])); ?></span>
<?php if (!empty($post['category_name'])): ?>
| <span class="post-category">Kategori: <a href="category.php?slug=<?php echo htmlspecialchars($post['category_slug']); ?>"><?php echo htmlspecialchars($post['category_name']); ?></a></span>
<?php endif; ?>
</p>
<?php
// ...
2. Modifikasi single.php
(detail artikel):
Sama, kita akan mengambil dan menampilkan nama kategori.
// single.php (bagian query SQL)
// Modifikasi query untuk JOIN dengan tabel categories
$sql = "SELECT p.id, p.title, p.content, p.slug, p.created_at, p.updated_at, p.image_path,
c.name as category_name, c.slug as category_slug
FROM posts p
LEFT JOIN categories c ON p.category_id = c.id
WHERE p.slug = '$slug' AND p.status = 'published'";
// ... (lanjutkan dengan mysqli_query dan fetch) ...
// Di dalam <p class="post-meta-single">:
// Tambahkan ini:
?>
<p class="post-meta-single">
<!-- ... (tanggal publikasi dan update) ... -->
<?php if (!empty($post['category_name'])): ?>
<span class="post-category-single">Kategori: <a href="category.php?slug=<?php echo htmlspecialchars($post['category_slug']); ?>"><?php echo htmlspecialchars($post['category_name']); ?></a></span>
<?php endif; ?>
</p>
<?php
// ...
Tambahkan CSS untuk info kategori di frontend (css/style.css
):
/* css/style.css (Tambahkan) */
.post-meta .post-category a,
.post-meta-single .post-category-single a {
color: #007bff;
text-decoration: none;
font-weight: 500;
}
.post-meta .post-category a:hover,
.post-meta-single .post-category-single a:hover {
text-decoration: underline;
}
Langkah 7: (Opsional) Halaman Arsip Kategori (category.php
)
Halaman ini akan menampilkan semua artikel yang termasuk dalam kategori tertentu, berdasarkan slug kategori.
Buat file category.php
di root proyek:
<?php
// category.php - Menampilkan artikel berdasarkan kategori
require_once 'includes/db_connect.php';
require_once 'includes/functions.php';
$category_slug = isset($_GET['slug']) ? mysqli_real_escape_string($conn, trim($_GET['slug'])) : '';
$category_name = "Kategori Tidak Ditemukan";
$posts_in_category = [];
if (!empty($category_slug)) {
// 1. Dapatkan informasi kategori
$sql_cat_info = "SELECT id, name, description FROM categories WHERE slug = ?";
if($stmt_cat_info = mysqli_prepare($conn, $sql_cat_info)) {
mysqli_stmt_bind_param($stmt_cat_info, "s", $category_slug);
mysqli_stmt_execute($stmt_cat_info);
$result_cat_info = mysqli_stmt_get_result($stmt_cat_info);
if ($cat_info = mysqli_fetch_assoc($result_cat_info)) {
$category_id = $cat_info['id'];
$category_name = htmlspecialchars($cat_info['name']);
$category_description = !empty($cat_info['description']) ? htmlspecialchars($cat_info['description']) : '';
// 2. Dapatkan artikel dalam kategori ini
$sql_posts = "SELECT p.id, p.title, p.content, p.slug, p.created_at, p.image_path
FROM posts p
WHERE p.category_id = ? AND p.status = 'published'
ORDER BY p.created_at DESC";
if($stmt_posts = mysqli_prepare($conn, $sql_posts)) {
mysqli_stmt_bind_param($stmt_posts, "i", $category_id);
mysqli_stmt_execute($stmt_posts);
$result_posts = mysqli_stmt_get_result($stmt_posts);
while ($row = mysqli_fetch_assoc($result_posts)) {
$posts_in_category[] = $row;
}
mysqli_stmt_close($stmt_posts);
}
}
mysqli_stmt_close($stmt_cat_info);
}
}
$page_title = "Artikel Kategori: " . $category_name;
$page_description = "Daftar artikel dalam kategori " . $category_name . ". " . (isset($category_description) ? $category_description : '');
require_once 'includes/header.php';
?>
<section class="category-archive-section">
<header class="category-header">
<h1 class="section-title">Kategori: <?php echo $category_name; ?></h1>
<?php if (isset($category_description) && !empty($category_description)): ?>
<p class="category-description"><?php echo $category_description; ?></p>
<?php endif; ?>
</header>
<?php if (!empty($posts_in_category)): ?>
<div class="post-grid">
<?php foreach ($posts_in_category as $post): ?>
<article class="post-item">
<?php if (!empty($post['image_path'])): ?>
<div class="post-thumbnail">
<a href="<?php echo site_url('single.php?slug=' . htmlspecialchars($post['slug'])); ?>">
<img src="<?php echo site_url(htmlspecialchars($post['image_path'])); ?>" alt="<?php echo htmlspecialchars($post['title']); ?>">
</a>
</div>
<?php endif; ?>
<div class="post-content-summary">
<h3 class="post-title">
<a href="<?php echo site_url('single.php?slug=' . htmlspecialchars($post['slug'])); ?>">
<?php echo htmlspecialchars($post['title']); ?>
</a>
</h3>
<p class="post-meta">
<span class="post-date">Dipublikasikan pada: <?php echo date('j F Y', strtotime($post['created_at'])); ?></span>
</p>
<div class="post-excerpt">
<?php echo htmlspecialchars(generate_excerpt($post['content'], 150)); ?>
</div>
<a href="<?php echo site_url('single.php?slug=' . htmlspecialchars($post['slug'])); ?>" class="read-more-btn">Baca Selengkapnya »</a>
</div>
</article>
<?php endforeach; ?>
</div>
<?php elseif (!empty($category_slug) && $category_name !== "Kategori Tidak Ditemukan"): ?>
<p class="no-posts">Tidak ada artikel yang ditemukan dalam kategori "<?php echo $category_name; ?>".</p>
<?php else: ?>
<p class="no-posts">Kategori yang Anda cari tidak ditemukan.</p>
<p><a href="<?php echo site_url(); ?>">Kembali ke Beranda</a></p>
<?php endif; ?>
</section>
<?php
require_once 'includes/footer.php';
if (isset($conn)) { mysqli_close($conn); }
?>
Tambahkan CSS untuk category.php
(opsional):
/* css/style.css (Tambahkan) */
.category-header {
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #e9ecef;
}
.category-header .section-title { /* Override margin bawah default */
margin-bottom: 10px;
}
.category-description {
font-size: 1.1rem;
color: #6c757d;
}
Menguji Fitur Kategori
- Area Admin:
- Buka
admin/manage_categories.php
. Tambah, edit, dan hapus beberapa kategori. - Buka
admin/add_post.php
atauadmin/edit_post.php
. Anda seharusnya melihat dropdown kategori. Pilih kategori untuk beberapa artikel.
- Buka
- Frontend:
- Buka
index.php
. Artikel yang memiliki kategori seharusnya sekarang menampilkan nama kategori dan link ke halaman arsip kategori. - Buka halaman
single.php
untuk artikel yang memiliki kategori. Nama kategori seharusnya juga muncul di sana. - Klik link nama kategori. Anda akan diarahkan ke
category.php?slug=NAMA_SLUG_KATEGORI
yang menampilkan semua artikel dalam kategori tersebut. - Coba akses
category.php
dengan slug yang tidak ada.
- Buka
Refleksi Part 4 dan Apa Selanjutnya?
Kerja bagus! Dengan menambahkan manajemen kategori, CMS kita menjadi jauh lebih terorganisir dan fungsional. Pengguna sekarang bisa mengelompokkan konten, dan pengunjung bisa menjelajahi artikel berdasarkan topik yang diminati.
Poin Kunci yang Telah Dicapai:
- Pembuatan tabel
categories
dan relasi dengan tabelposts
. - Implementasi CRUD penuh untuk kategori di area admin.
- Integrasi pemilihan kategori dalam form manajemen artikel.
- Menampilkan informasi kategori di halaman daftar dan detail artikel frontend.
- (Opsional) Pembuatan halaman arsip kategori.
Di Part 5 dari seri ini, kita akan membahas Manajemen Pengguna dan Peran (Roles). Ini akan memungkinkan kita memiliki beberapa pengguna dengan tingkat akses yang berbeda di area admin, sebuah fitur penting untuk CMS yang lebih kolaboratif atau aman.
Teruslah bereksperimen dan jangan ragu untuk mencoba memodifikasi atau menambahkan fitur kecil lainnya!