CMS dengan PHP Part 5: Manajemen Pengguna & Sistem Peran (Roles)

CMS dengan PHP Part 5: Manajemen Pengguna & Sistem Peran (Roles)

CMS dengan PHP Part 5: Manajemen Pengguna & Sistem Peran (Roles)

Novian Hidayat
2025-05-29

Amankan dan kelola akses CMS Anda lebih baik! Di Part 5 ini, kita akan membuat fitur CRUD untuk pengguna, mengimplementasikan sistem peran (admin, editor), dan membatasi akses fitur berdasarkan peran pengguna di area admin.

Membangun CMS Sederhana dengan PHP & MySQL - Part 5: Manajemen Pengguna & Sistem Peran (Roles)

Selamat datang di Bagian 5 dari seri tutorial CMS PHP! Sejauh ini, kita telah membangun frontend yang dinamis (Part 1), sistem login aman (Part 2), manajemen artikel (Part 3), dan manajemen kategori (Part 4). Sekarang, kita akan meningkatkan aspek pengelolaan dan keamanan CMS dengan mengimplementasikan Manajemen Pengguna dan Sistem Peran (Roles).

Dengan fitur ini, kita bisa:

  1. Membuat beberapa akun pengguna untuk mengakses area admin.
  2. Menetapkan peran yang berbeda untuk setiap pengguna (misalnya, admin dan editor).
  3. Membatasi akses ke fitur-fitur tertentu di area admin berdasarkan peran pengguna.
  4. Mengimplementasikan operasi CRUD (Create, Read, Update, Delete) untuk pengguna (biasanya hanya bisa dilakukan oleh pengguna dengan peran admin).

Fitur ini krusial jika CMS akan digunakan oleh lebih dari satu orang atau jika Anda ingin membatasi kemampuan pengguna tertentu.

Prasyarat

Pastikan Anda telah mengikuti dan memahami bagian-bagian sebelumnya dari seri ini, terutama Part 2 mengenai sistem login dan sesi, karena kita akan banyak bekerja dengan variabel sesi $_SESSION['role'].

Persiapan: Memperjelas Peran

Kita sudah memiliki kolom role di tabel users (dibuat di Part 2) dengan nilai default 'editor' dan kita membuat user admin awal dengan peran 'admin'. Mari kita definisikan apa saja yang bisa dilakukan oleh masing-masing peran:

  • Admin:
    • Akses penuh ke semua fitur CMS.
    • Bisa mengelola artikel, kategori, dan pengguna lain.
    • Bisa mengubah pengaturan situs (jika ada nanti).
  • Editor:
    • Bisa mengelola artikel (menambah, mengedit, menghapus artikelnya sendiri atau semua artikel, tergantung kebijakan).
    • Bisa mengelola kategori.
    • Tidak bisa mengelola pengguna atau pengaturan situs.

Untuk tutorial ini, kita akan buat editor bisa mengelola semua artikel dan kategori, tetapi tidak bisa mengelola pengguna.

Langkah 1: Membatasi Akses Navigasi Berdasarkan Peran

Kita sudah melakukan ini sedikit di admin/index.php dan admin/manage_posts.php (dan halaman manajemen lainnya) untuk link “Kelola Pengguna”. Mari kita pastikan ini konsisten.

Modifikasi Navigasi di File Admin (admin/index.php, admin/manage_posts.php, admin/manage_categories.php, admin/add_post.php, admin/edit_post.php, admin/add_category.php, admin/edit_category.php):

Cari bagian navigasi di file-file tersebut:

<nav class="admin-nav">
     <ul>
        <li><a href="index.php" class="<?php echo (basename($_SERVER['PHP_SELF']) == 'index.php') ? 'active' : ''; ?>">Dashboard</a></li>
        <li><a href="manage_posts.php" class="<?php echo (basename($_SERVER['PHP_SELF']) == 'manage_posts.php' || basename($_SERVER['PHP_SELF']) == 'add_post.php' || basename($_SERVER['PHP_SELF']) == 'edit_post.php') ? 'active' : ''; ?>">Kelola Artikel</a></li>
        <li><a href="manage_categories.php" class="<?php echo (basename($_SERVER['PHP_SELF']) == 'manage_categories.php' || basename($_SERVER['PHP_SELF']) == 'add_category.php' || basename($_SERVER['PHP_SELF']) == 'edit_category.php') ? 'active' : ''; ?>">Kelola Kategori</a></li>
        <?php if (isset($current_user_role) && $current_user_role === 'admin'): ?>
            <li><a href="manage_users.php" class="<?php echo (basename($_SERVER['PHP_SELF']) == 'manage_users.php' || basename($_SERVER['PHP_SELF']) == 'add_user.php' || basename($_SERVER['PHP_SELF']) == 'edit_user.php') ? 'active' : ''; ?>">Kelola Pengguna</a></li>
        <?php endif; ?>
        <li><a href="../index.php" target="_blank">Lihat Situs</a></li>
    </ul>
</nav>

Perubahan:

  • Menambahkan class="active" secara dinamis ke link navigasi yang sesuai dengan halaman saat ini.
  • Memastikan link “Kelola Pengguna” hanya muncul jika $current_user_role (dari auth_check.php) adalah 'admin'.
  • Variabel $current_user_role harus tersedia. Pastikan auth_check.php di-include dan mendefinisikannya.

Langkah 2: Halaman Manajemen Pengguna (admin/manage_users.php)

Halaman ini (hanya bisa diakses oleh admin) akan menampilkan daftar semua pengguna dan menyediakan opsi CRUD.

Buat file admin/manage_users.php:

<?php
// admin/manage_users.php

require_once 'auth_check.php';
require_once '../includes/db_connect.php';
require_once '../includes/functions.php';

// Hanya admin yang boleh mengakses halaman ini
if ($current_user_role !== 'admin') {
    set_flash_message('auth_error', "Anda tidak memiliki izin untuk mengakses halaman ini.", "error");
    header("Location: index.php"); // Redirect ke dashboard atau halaman lain
    exit;
}

$page_title_admin = "Kelola Pengguna";
?>
<!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" class="active">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('user_action'); ?>
        <?php display_flash_message('auth_error'); // Untuk pesan error dari halaman ini atau auth_check ?>


        <a href="add_user.php" class="btn btn-add">Tambah Pengguna Baru</a>

        <table class="admin-table">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Username</th>
                    <th>Email</th>
                    <th>Peran</th>
                    <th>Tanggal Dibuat</th>
                    <th>Aksi</th>
                </tr>
            </thead>
            <tbody>
                <?php
                $sql = "SELECT id, username, email, role, created_at FROM users ORDER BY created_at DESC";
                $result = mysqli_query($conn, $sql);

                if (mysqli_num_rows($result) > 0) {
                    while ($user = mysqli_fetch_assoc($result)) {
                        echo "<tr>";
                        echo "<td>" . $user['id'] . "</td>";
                        echo "<td>" . htmlspecialchars($user['username']) . "</td>";
                        echo "<td>" . htmlspecialchars($user['email']) . "</td>";
                        echo "<td>" . ucfirst(htmlspecialchars($user['role'])) . "</td>";
                        echo "<td>" . date('d M Y, H:i', strtotime($user['created_at'])) . "</td>";
                        echo "<td>";
                        echo "<a href='edit_user.php?id=" . $user['id'] . "' class='btn btn-edit'>Edit</a> ";
                        // Admin tidak boleh menghapus dirinya sendiri
                        // Dan mungkin ada batasan lain (misal, hanya ada 1 admin, tidak boleh dihapus)
                        if ($user['id'] != $current_user_id && $user['username'] !== 'admin' /* Tambahan pencegahan super admin */ ) { 
                            echo "<a href='delete_user.php?id=" . $user['id'] . "' class='btn btn-delete' onclick='return confirm(\"Apakah Anda yakin ingin menghapus pengguna ini?\");'>Hapus</a>";
                        } else {
                            echo "<span class='btn btn-disabled'>Hapus</span>"; // Tombol disable
                        }
                        echo "</td>";
                        echo "</tr>";
                    }
                } else {
                    echo "<tr><td colspan='6' style='text-align:center;'>Belum ada pengguna terdaftar.</td></tr>";
                }
                ?>
            </tbody>
        </table>
    </div>
    <?php if(isset($conn)) { mysqli_close($conn); } ?>
</body>
</html>

Perubahan Penting:

  • Pengecekan Peran di Awal: Jika pengguna bukan admin, mereka akan diarahkan.
  • Tombol Hapus Bersyarat:
    • Pengguna tidak bisa menghapus akunnya sendiri ($user['id'] != $current_user_id).
    • Sebagai contoh tambahan, kita mencegah penghapusan user dengan username admin (bisa dianggap sebagai super admin). Anda bisa menyesuaikan logika ini.
  • Menambahkan kelas btn-disabled jika tombol hapus tidak aktif (perlu styling di CSS).

CSS untuk Tombol Disabled (css/admin_style.css):

/* css/admin_style.css (Tambahkan) */
.btn-disabled {
    background-color: #adb5bd;
    color: #fff;
    cursor: not-allowed;
    opacity: 0.65;
}

Langkah 3: Halaman Tambah Pengguna (admin/add_user.php)

Formulir untuk membuat akun pengguna baru (hanya bisa diakses oleh admin).

Buat file admin/add_user.php:

<?php
// admin/add_user.php

require_once 'auth_check.php';
require_once '../includes/db_connect.php';
require_once '../includes/functions.php';

if ($current_user_role !== 'admin') {
    set_flash_message('auth_error', "Anda tidak memiliki izin untuk mengakses halaman ini.", "error");
    header("Location: index.php");
    exit;
}

$page_title_admin = "Tambah Pengguna Baru";
$username = ''; $email = ''; $role = 'editor'; // Default role
$errors = [];

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $username = trim($_POST['username']);
    $email = trim($_POST['email']);
    $password = trim($_POST['password']);
    $password_confirm = trim($_POST['password_confirm']);
    $role = isset($_POST['role']) ? trim($_POST['role']) : 'editor';

    // Validasi
    if (empty($username)) $errors[] = "Username tidak boleh kosong.";
    if (empty($email)) $errors[] = "Email tidak boleh kosong.";
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = "Format email tidak valid.";
    if (empty($password)) $errors[] = "Password tidak boleh kosong.";
    if (strlen($password) < 6) $errors[] = "Password minimal 6 karakter."; // Contoh panjang minimal
    if ($password !== $password_confirm) $errors[] = "Konfirmasi password tidak cocok.";
    if (!in_array($role, ['admin', 'editor'])) $errors[] = "Peran tidak valid.";

    // Cek keunikan username
    if (empty($errors)) {
        $sql_check_user = "SELECT id FROM users WHERE username = ? OR email = ?";
        if($stmt_check = mysqli_prepare($conn, $sql_check_user)){
            mysqli_stmt_bind_param($stmt_check, "ss", $username, $email);
            mysqli_stmt_execute($stmt_check);
            mysqli_stmt_store_result($stmt_check);
            if(mysqli_stmt_num_rows($stmt_check) > 0){
                $errors[] = "Username atau Email sudah terdaftar.";
            }
            mysqli_stmt_close($stmt_check);
        }
    }
    
    if (empty($errors)) {
        $hashed_password = password_hash($password, PASSWORD_DEFAULT);
        $sql_insert = "INSERT INTO users (username, email, password, role) VALUES (?, ?, ?, ?)";
        if ($stmt = mysqli_prepare($conn, $sql_insert)) {
            mysqli_stmt_bind_param($stmt, "ssss", $username, $email, $hashed_password, $role);
            if (mysqli_stmt_execute($stmt)) {
                set_flash_message('user_action', "Pengguna \"" . htmlspecialchars($username) . "\" berhasil ditambahkan!", "success");
                header("Location: manage_users.php");
                exit;
            } else {
                $errors[] = "Gagal menyimpan pengguna: " . 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="username">Username:</label>
                <input type="text" name="username" id="username" value="<?php echo htmlspecialchars($username); ?>" required>
            </div>
            <div class="form-group">
                <label for="email">Email:</label>
                <input type="email" name="email" id="email" value="<?php echo htmlspecialchars($email); ?>" required>
            </div>
            <div class="form-group">
                <label for="password">Password:</label>
                <input type="password" name="password" id="password" required>
                <small>Minimal 6 karakter.</small>
            </div>
            <div class="form-group">
                <label for="password_confirm">Konfirmasi Password:</label>
                <input type="password" name="password_confirm" id="password_confirm" required>
            </div>
            <div class="form-group">
                <label for="role">Peran:</label>
                <select name="role" id="role">
                    <option value="editor" <?php echo ($role === 'editor') ? 'selected' : ''; ?>>Editor</option>
                    <option value="admin" <?php echo ($role === 'admin') ? 'selected' : ''; ?>>Admin</option>
                </select>
            </div>
            <div class="form-actions">
                <button type="submit" class="btn btn-submit">Simpan Pengguna</button>
                <a href="manage_users.php" class="btn btn-cancel">Batal</a>
            </div>
        </form>
    </div>
    <?php if(isset($conn)) { mysqli_close($conn); } ?>
</body>
</html>

Perubahan Penting:

  • Validasi input lebih banyak (email, konfirmasi password, panjang password).
  • filter_var($email, FILTER_VALIDATE_EMAIL) untuk validasi format email.
  • Pengecekan keunikan username DAN email.
  • Password di-hash menggunakan password_hash().

Langkah 4: Halaman Edit Pengguna (admin/edit_user.php)

Formulir untuk memodifikasi data pengguna yang ada (hanya bisa diakses oleh admin).

Buat file admin/edit_user.php:

<?php
// admin/edit_user.php

require_once 'auth_check.php';
require_once '../includes/db_connect.php';
require_once '../includes/functions.php';

if ($current_user_role !== 'admin') {
    set_flash_message('auth_error', "Anda tidak memiliki izin untuk mengakses halaman ini.", "error");
    header("Location: index.php");
    exit;
}

$page_title_admin = "Edit Pengguna";
$user_id_to_edit = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$user_data_original = null;
$username = ''; $email = ''; $role = '';
$errors = [];

if ($user_id_to_edit > 0) {
    $sql_select = "SELECT username, email, role FROM users WHERE id = ?";
    if ($stmt_select = mysqli_prepare($conn, $sql_select)) {
        mysqli_stmt_bind_param($stmt_select, "i", $user_id_to_edit);
        mysqli_stmt_execute($stmt_select);
        $result_select = mysqli_stmt_get_result($stmt_select);
        if ($user = mysqli_fetch_assoc($result_select)) {
            $user_data_original = $user;
            $username = $user['username'];
            $email = $user['email'];
            $role = $user['role'];
        } else {
            set_flash_message('user_action', "Pengguna dengan ID $user_id_to_edit tidak ditemukan.", "error");
            header("Location: manage_users.php");
            exit;
        }
        mysqli_stmt_close($stmt_select);
    } else { die("Error SELECT: " . mysqli_error($conn)); }
} else {
    set_flash_message('user_action', "ID Pengguna tidak valid.", "error");
    header("Location: manage_users.php");
    exit;
}

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $username_new = trim($_POST['username']);
    $email_new = trim($_POST['email']);
    $password_new = trim($_POST['password']); // Password baru (opsional)
    $password_new_confirm = trim($_POST['password_confirm']); // Konfirmasi password baru
    $role_new = isset($_POST['role']) ? trim($_POST['role']) : $role;

    if (empty($username_new)) $errors[] = "Username tidak boleh kosong.";
    if (empty($email_new)) $errors[] = "Email tidak boleh kosong.";
    if (!filter_var($email_new, FILTER_VALIDATE_EMAIL)) $errors[] = "Format email tidak valid.";
    if (!in_array($role_new, ['admin', 'editor'])) $errors[] = "Peran tidak valid.";

    // Validasi password hanya jika diisi
    if (!empty($password_new)) {
        if (strlen($password_new) < 6) $errors[] = "Password baru minimal 6 karakter.";
        if ($password_new !== $password_new_confirm) $errors[] = "Konfirmasi password baru tidak cocok.";
    }

    // Cek keunikan username dan email (jika berubah)
    if (empty($errors)) {
        $check_query_parts = [];
        $check_params = [];
        $check_types = "";

        if ($username_new !== $user_data_original['username']) {
            $check_query_parts[] = "username = ?";
            $check_params[] = $username_new;
            $check_types .= "s";
        }
        if ($email_new !== $user_data_original['email']) {
            $check_query_parts[] = "email = ?";
            $check_params[] = $email_new;
            $check_types .= "s";
        }

        if (!empty($check_query_parts)) {
            $sql_check_user = "SELECT id FROM users WHERE (" . implode(' OR ', $check_query_parts) . ") AND id != ?";
            $check_params[] = $user_id_to_edit; // Tambahkan ID user saat ini untuk eksklusi
            $check_types .= "i";

            if($stmt_check = mysqli_prepare($conn, $sql_check_user)){
                mysqli_stmt_bind_param($stmt_check, $check_types, ...$check_params);
                mysqli_stmt_execute($stmt_check);
                mysqli_stmt_store_result($stmt_check);
                if(mysqli_stmt_num_rows($stmt_check) > 0){
                    if ($username_new !== $user_data_original['username'] && in_array("username = ?", $check_query_parts)) $errors[] = "Username sudah terdaftar.";
                    if ($email_new !== $user_data_original['email'] && in_array("email = ?", $check_query_parts)) $errors[] = "Email sudah terdaftar.";
                }
                mysqli_stmt_close($stmt_check);
            }
        }
    }
    
    if (empty($errors)) {
        if (!empty($password_new)) {
            $hashed_password_new = password_hash($password_new, PASSWORD_DEFAULT);
            $sql_update = "UPDATE users SET username = ?, email = ?, password = ?, role = ? WHERE id = ?";
            $types_update = "ssssi";
            $params_update = [$username_new, $email_new, $hashed_password_new, $role_new, $user_id_to_edit];
        } else {
            // Jika password tidak diubah
            $sql_update = "UPDATE users SET username = ?, email = ?, role = ? WHERE id = ?";
            $types_update = "sssi";
            $params_update = [$username_new, $email_new, $role_new, $user_id_to_edit];
        }

        if ($stmt_update = mysqli_prepare($conn, $sql_update)) {
            mysqli_stmt_bind_param($stmt_update, $types_update, ...$params_update);
            if (mysqli_stmt_execute($stmt_update)) {
                set_flash_message('user_action', "Pengguna \"" . htmlspecialchars($username_new) . "\" berhasil diperbarui!", "success");
                // Jika admin mengedit dirinya sendiri dan mengubah peran, mungkin perlu logika logout/re-login atau update sesi
                if ($user_id_to_edit == $_SESSION['user_id'] && $_SESSION['role'] !== $role_new) {
                    $_SESSION['role'] = $role_new; // Update peran di sesi
                }
                 if ($user_id_to_edit == $_SESSION['user_id'] && $_SESSION['username'] !== $username_new) {
                    $_SESSION['username'] = $username_new; // Update username di sesi
                }
                header("Location: manage_users.php");
                exit;
            } else {
                $errors[] = "Gagal memperbarui pengguna: " . 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
    $username = $username_new;
    $email = $email_new;
    $role = $role_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($user_data_original['username']); ?>"</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 $user_id_to_edit; ?>" method="post" class="admin-form">
            <div class="form-group">
                <label for="username">Username:</label>
                <input type="text" name="username" id="username" value="<?php echo htmlspecialchars($username); ?>" required>
            </div>
            <div class="form-group">
                <label for="email">Email:</label>
                <input type="email" name="email" id="email" value="<?php echo htmlspecialchars($email); ?>" required>
            </div>
            <div class="form-group">
                <label for="password">Password Baru (Kosongkan jika tidak ingin diubah):</label>
                <input type="password" name="password" id="password">
                <small>Minimal 6 karakter jika diisi.</small>
            </div>
            <div class="form-group">
                <label for="password_confirm">Konfirmasi Password Baru:</label>
                <input type="password" name="password_confirm" id="password_confirm">
            </div>
            <div class="form-group">
                <label for="role">Peran:</label>
                <select name="role" id="role" <?php echo ($user_id_to_edit == $current_user_id && $username == 'admin') ? 'disabled' : ''; ?>>
                <!-- Admin super (misal, user 'admin' atau user dengan ID tertentu) tidak bisa mengubah perannya sendiri menjadi editor -->
                    <option value="editor" <?php echo ($role === 'editor') ? 'selected' : ''; ?>>Editor</option>
                    <option value="admin" <?php echo ($role === 'admin') ? 'selected' : ''; ?>>Admin</option>
                </select>
                <?php if ($user_id_to_edit == $current_user_id && $username == 'admin'): ?>
                    <small>Peran untuk super admin tidak dapat diubah.</small>
                <?php endif; ?>
            </div>
            <div class="form-actions">
                <button type="submit" class="btn btn-submit">Simpan Perubahan</button>
                <a href="manage_users.php" class="btn btn-cancel">Batal</a>
            </div>
        </form>
    </div>
    <?php if(isset($conn)) { mysqli_close($conn); } ?>
</body>
</html>

Perubahan Penting:

  • Password Opsional: Pengguna bisa mengedit profil tanpa harus mengubah password. Password hanya diupdate jika field password baru diisi.
  • Pengecekan Keunikan Username/Email yang Diubah: Hanya cek jika nilainya berbeda dari yang lama.
  • Batasan Edit Peran: Contoh sederhana untuk mencegah admin utama mengubah perannya sendiri menjadi editor atau pengguna lain mengubah peran admin utama. Logika ini bisa lebih kompleks.
  • Update Sesi: Jika admin mengedit profilnya sendiri dan mengubah peran atau username, sesi perlu diupdate agar informasi yang ditampilkan di header admin (misal, Selamat datang, AdminBaru (Admin)) menjadi akurat.

Langkah 5: Skrip Hapus Pengguna (admin/delete_user.php)

Logika untuk menghapus akun pengguna (hanya bisa diakses oleh admin).

Buat file admin/delete_user.php:

<?php
// admin/delete_user.php

require_once 'auth_check.php';
require_once '../includes/db_connect.php';
require_once '../includes/functions.php';

if ($current_user_role !== 'admin') {
    set_flash_message('auth_error', "Anda tidak memiliki izin untuk melakukan tindakan ini.", "error");
    header("Location: index.php");
    exit;
}

$user_id_to_delete = isset($_GET['id']) ? (int)$_GET['id'] : 0;

if ($user_id_to_delete > 0) {
    // Admin tidak boleh menghapus dirinya sendiri
    if ($user_id_to_delete == $current_user_id) {
        set_flash_message('user_action', "Anda tidak dapat menghapus akun Anda sendiri.", "error");
        header("Location: manage_users.php");
        exit;
    }

    // (Opsional) Ambil username untuk pesan flash dan tambahan pencegahan
    $username_to_delete = "Pengguna";
    $sql_get_user = "SELECT username FROM users WHERE id = ?";
    if($stmt_get_user = mysqli_prepare($conn, $sql_get_user)){
        mysqli_stmt_bind_param($stmt_get_user, "i", $user_id_to_delete);
        mysqli_stmt_execute($stmt_get_user);
        $result_user = mysqli_stmt_get_result($stmt_get_user);
        if($row_user = mysqli_fetch_assoc($result_user)){
            $username_to_delete = $row_user['username'];
            // Pencegahan tambahan untuk super admin (jika ada)
            if ($username_to_delete === 'admin' && $user_id_to_delete != $current_user_id) { // Jika bukan menghapus diri sendiri tapi mencoba hapus 'admin'
                 set_flash_message('user_action', "Pengguna 'admin' utama tidak dapat dihapus.", "error");
                 header("Location: manage_users.php");
                 exit;
            }
        } else {
             set_flash_message('user_action', "Gagal menghapus: Pengguna tidak ditemukan.", "error");
             header("Location: manage_users.php");
             exit;
        }
        mysqli_stmt_close($stmt_get_user);
    }


    // Pertimbangkan apa yang terjadi pada konten yang dibuat oleh pengguna ini.
    // Opsi:
    // 1. Hapus juga semua artikel yang dibuatnya (cascade delete, perlu setup di DB atau query tambahan).
    // 2. Atur author_id artikel menjadi NULL atau ke user default 'anonymous'.
    // Untuk tutorial ini, kita tidak akan menangani konten terkait secara otomatis. Ini bisa jadi fitur lanjutan.
    // Kita hanya hapus user. Jika ada foreign key `author_id` di `posts` dengan `ON DELETE SET NULL`,
    // maka artikel akan kehilangan penulisnya. Jika `ON DELETE RESTRICT`, penghapusan akan gagal jika user punya post.
    // Kita asumsikan saat ini tidak ada constraint yang mencegah, atau kita akan menambahkan `ON DELETE SET NULL` pada `author_id` di tabel `posts`.

    // Jika Anda ingin mengatur ulang `author_id` pada postingan pengguna yang dihapus:
    // $sql_reassign_posts = "UPDATE posts SET author_id = NULL WHERE author_id = ?"; 
    // (Jalankan ini sebelum menghapus user)

    $sql_delete = "DELETE FROM users WHERE id = ?";
    if ($stmt_delete = mysqli_prepare($conn, $sql_delete)) {
        mysqli_stmt_bind_param($stmt_delete, "i", $user_id_to_delete);
        if (mysqli_stmt_execute($stmt_delete)) {
            if (mysqli_stmt_affected_rows($stmt_delete) > 0) {
                set_flash_message('user_action', "Pengguna \"" . htmlspecialchars($username_to_delete) . "\" berhasil dihapus.", "success");
            } else {
                set_flash_message('user_action', "Gagal menghapus: Pengguna tidak ditemukan atau sudah dihapus.", "error");
            }
        } else {
            set_flash_message('user_action', "Gagal menghapus pengguna: " . mysqli_stmt_error($stmt_delete), "error");
        }
        mysqli_stmt_close($stmt_delete);
    } else {
         set_flash_message('user_action', "Gagal mempersiapkan statement DELETE: " . mysqli_error($conn), "error");
    }
} else {
    set_flash_message('user_action', "ID Pengguna tidak valid untuk dihapus.", "error");
}

header("Location: manage_users.php");
exit;
?>

Perubahan Penting:

  • Pencegahan Menghapus Diri Sendiri: Sangat penting.
  • Pencegahan Menghapus Super Admin (Contoh): Logika tambahan untuk melindungi akun admin utama.
  • Konten Terkait: Komentar penting tentang apa yang terjadi pada artikel yang dibuat oleh pengguna yang dihapus. Penanganan ini (misalnya, mengatur ulang author_id) tergantung pada kebijakan aplikasi Anda dan bagaimana foreign key diatur. Untuk ON DELETE SET NULL pada posts.author_id (jika Anda menambahkannya), ini akan otomatis.

Modifikasi Tabel posts untuk author_id (Jika belum di Part 1): Jika Anda belum memiliki author_id di tabel posts atau belum mengatur foreign key-nya, Anda bisa melakukannya sekarang:

ALTER TABLE `posts`
ADD COLUMN `author_id` INT(11) UNSIGNED NULL AFTER `image_path`; 
-- Tambahkan foreign key constraint JIKA tabel users sudah ada
-- ALTER TABLE `posts`
-- ADD CONSTRAINT `fk_post_author`
--     FOREIGN KEY (`author_id`)
--     REFERENCES `users`(`id`)
--     ON DELETE SET NULL ON UPDATE CASCADE; 

Pastikan Anda menambahkan foreign key fk_post_author setelah tabel users dibuat. Jika Anda membuatnya sekarang, pastikan ON DELETE SET NULL digunakan agar saat user dihapus, artikelnya tidak error.


Langkah 6: Mengintegrasikan author_id ke Manajemen Artikel

Sekarang setelah kita memiliki pengguna, kita perlu memastikan author_id disimpan dengan benar saat artikel dibuat/diedit.

1. Modifikasi admin/add_post.php: Kita sudah menambahkan $author_id = $_SESSION['user_id']; dan menyimpannya saat INSERT. Ini sudah benar.

2. Modifikasi admin/edit_post.php: Saat mengedit, author_id biasanya tidak diubah (artikel tetap milik penulis aslinya). Jadi, tidak perlu field untuk mengubah author_id di form edit, kecuali jika admin memiliki hak untuk itu. Untuk sekarang, kita biarkan author_id tidak bisa diubah di form edit.

3. Menampilkan Nama Penulis di Frontend: Kita perlu melakukan JOIN tabel posts dengan users untuk mendapatkan nama penulis.

Modifikasi index.php (daftar artikel):

// index.php (bagian query SQL)
$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,
               u.username as author_name 
        FROM posts p
        LEFT JOIN categories c ON p.category_id = c.id
        LEFT JOIN users u ON p.author_id = u.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)):
// Modifikasi <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['author_name'])): ?>
        | <span class="post-author">Oleh: <?php echo htmlspecialchars($post['author_name']); ?></span>
    <?php endif; ?>
    <?php if (!empty($post['category_name'])): ?>
        | <span class="post-category">Kategori: <a href="<?php echo site_url('category.php?slug=' . htmlspecialchars($post['category_slug'])); ?>"><?php echo htmlspecialchars($post['category_name']); ?></a></span>
    <?php endif; ?>
</p>
<?php
// ...

Modifikasi single.php (detail artikel):

// single.php (bagian query SQL)
$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,
               u.username as author_name
        FROM posts p
        LEFT JOIN categories c ON p.category_id = c.id
        LEFT JOIN users u ON p.author_id = u.id
        WHERE p.slug = '$slug' AND p.status = 'published'";
// ... (lanjutkan dengan mysqli_query dan fetch) ...

// Di dalam <p class="post-meta-single">:
// Modifikasi atau tambahkan:
?>
<p class="post-meta-single">
    <span class="post-date-single">Dipublikasikan: <?php echo date('l, j F Y H:i', strtotime($post['created_at'])); ?></span>
    <?php if ($post['created_at'] != $post['updated_at']): ?>
        <br><span class="post-update-date-single">Diperbarui: <?php echo date('l, j F Y H:i', strtotime($post['updated_at'])); ?></span>
    <?php endif; ?>
    <?php if (!empty($post['author_name'])): ?>
        <br><span class="post-author-single">Oleh: <?php echo htmlspecialchars($post['author_name']); ?></span>
    <?php endif; ?>
    <?php if (!empty($post['category_name'])): ?>
        <br><span class="post-category-single">Kategori: <a href="<?php echo site_url('category.php?slug=' . htmlspecialchars($post['category_slug'])); ?>"><?php echo htmlspecialchars($post['category_name']); ?></a></span>
    <?php endif; ?>
</p>
<?php
// ...

Menguji Fitur Manajemen Pengguna dan Peran

  1. Login sebagai Admin:
    • Buka admin/manage_users.php. Anda seharusnya bisa melihat daftar pengguna.
    • Coba tambah pengguna baru dengan peran “Editor”.
    • Coba edit pengguna yang baru dibuat (ubah username, email, atau peran).
    • Coba hapus pengguna “Editor” tersebut.
    • Pastikan Anda tidak bisa menghapus akun admin Anda sendiri atau akun “admin” utama.
  2. Login sebagai Editor (jika sudah dibuat):
    • Logout dari akun admin.
    • Login dengan akun “Editor” yang baru.
    • Periksa navigasi admin. Link “Kelola Pengguna” seharusnya tidak muncul.
    • Coba akses admin/manage_users.php secara langsung melalui URL. Anda seharusnya diarahkan atau melihat pesan error “tidak ada izin”.
    • Pastikan editor bisa mengakses dan mengelola artikel serta kategori.
  3. Frontend:
    • Periksa halaman index.php dan single.php. Nama penulis artikel seharusnya muncul jika author_id sudah terisi dengan benar di tabel posts.

Refleksi Part 5 dan Apa Selanjutnya?

Selamat! Anda telah mencapai tonggak penting dengan mengimplementasikan manajemen pengguna dan sistem peran. CMS Anda sekarang jauh lebih aman dan siap untuk kolaborasi atau pengelolaan dengan tingkat akses yang berbeda.

Poin Kunci yang Telah Dicapai:

  • CRUD penuh untuk manajemen pengguna di area admin.
  • Implementasi sistem peran (admin, editor).
  • Pembatasan akses fitur berdasarkan peran pengguna.
  • Pencegahan tindakan kritis (menghapus diri sendiri, menghapus admin utama).
  • Integrasi author_id pada artikel dan menampilkannya di frontend.

Apa Selanjutnya? Seri dasar CMS kita hampir lengkap! Beberapa area yang bisa menjadi fokus untuk bagian selanjutnya atau sebagai pengembangan mandiri:

  • Part 6 (Opsional): Fitur Tambahan & Penyempurnaan
    • Upload Gambar Unggulan untuk Artikel: Menggunakan $_FILES PHP.
    • Editor WYSIWYG untuk Konten Artikel: Integrasi TinyMCE atau sejenisnya.
    • Sistem Komentar Sederhana: Untuk interaksi pengunjung.
    • Pagination: Untuk halaman daftar artikel dan manajemen di admin.
    • Pencarian: Fitur pencarian artikel di frontend.
    • Pengaturan Situs Dasar: Menyimpan judul situs, deskripsi, dll. di database.
    • Keamanan Lanjutan & Praktik Terbaik: XSS, CSRF, validasi lebih ketat.

Pilih topik yang paling menarik atau paling relevan untuk kebutuhan Anda. Untuk seri tutorial ini, Upload Gambar Unggulan dan Pagination adalah tambahan yang sangat umum dan berguna.

Teruslah belajar dan jangan takut untuk memodifikasi kode ini lebih lanjut!

Tutorial Terkait