Building CRUD Plugin






<?php
/**
* Plugin Name: Student Database
* Description: A CRUD plugin for managing student records with create, read, update, and delete functionalities.
* Version: 1.0
* Author: Shiara Bayonla
*/
if (!defined('ABSPATH')) {
exit;
}
// 1. Create Database Table on Activation
function sdp_create_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'students';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name tinytext NOT NULL,
email varchar(100) NOT NULL,
course varchar(100) NOT NULL,
PRIMARY KEY (id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
register_activation_hook(__FILE__, 'sdp_create_table');
// 2. Enqueue Scripts (Optional, kept from your code)
function sdp_enqueue_scripts() {
wp_enqueue_script(
'sdp-script',
plugin_dir_url(__FILE__) . 'js/script.js',
array(),
'1.0',
true
);
}
add_action('wp_enqueue_scripts', 'sdp_enqueue_scripts');
// 3. Main Shortcode Function
function sdp_crud_shortcode() {
global $wpdb;
$table_name = $wpdb->prefix . 'students';
$page_url = get_permalink();
$message = '';
// --- LOGIC HANDLERS (Moved to top for correct processing) ---
// A. Handle Delete
if (isset($_GET['action']) && $_GET['action'] == 'delete' && isset($_GET['id'])) {
if (isset($_GET['_wpnonce']) && wp_verify_nonce($_GET['_wpnonce'], 'sdp_delete_student_' . $_GET['id'])) {
$id_to_delete = intval($_GET['id']);
$wpdb->delete($table_name, array('id' => $id_to_delete));
$message = '<div class="notice notice-success inline"><p>Student deleted successfully!</p></div>';
} else {
$message = '<div class="notice notice-error inline"><p>Security check failed.</p></div>';
}
}
// B. Handle Update
if (isset($_POST['update_student'])) {
$id = intval($_POST['student_id']);
$name = sanitize_text_field($_POST['student_name']);
$email = sanitize_email($_POST['student_email']);
$course = sanitize_text_field($_POST['student_course']);
$wpdb->update(
$table_name,
['name' => $name, 'email' => $email, 'course' => $course],
['id' => $id]
);
$message = '<div class="notice notice-success inline"><p>Student updated successfully!</p></div>';
}
// C. Handle Create (Add New)
if (isset($_POST['submit_student'])) {
$name = sanitize_text_field($_POST['student_name']);
$email = sanitize_email($_POST['student_email']);
$course = sanitize_text_field($_POST['student_course']);
$wpdb->insert(
$table_name,
['name' => $name, 'email' => $email, 'course' => $course]
);
$message = '<div class="notice notice-success inline"><p>Student added successfully!</p></div>';
}
// D. Check if Editing
$student_to_edit = null;
if (isset($_GET['action']) && $_GET['action'] == 'edit' && isset($_GET['id'])) {
$id_to_edit = intval($_GET['id']);
$student_to_edit = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $id_to_edit));
}
// --- DISPLAY OUTPUT ---
ob_start();
?>
<div class="wrap" style="background-color: #ffffff; padding: 20px; border-radius: 5px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<?php echo $message; ?>
<h2 style="padding-top: 0;"><b><?php echo $student_to_edit ? 'Edit Student' : 'Add New Student'; ?></b></h2>
<form method="post" action="<?php echo esc_url(remove_query_arg(['action', 'id'], $page_url)); ?>">
<?php if ($student_to_edit) : ?>
<input type="hidden" name="student_id" value="<?php echo esc_attr($student_to_edit->id); ?>">
<?php endif; ?>
<p>
<label for="student_name">Name:</label><br>
<input type="text" id="student_name" name="student_name" value="<?php echo $student_to_edit ? esc_attr($student_to_edit->name) : ''; ?>" required style="width: 100%; max-width: 400px;">
</p>
<p>
<label for="student_email">Email:</label><br>
<input type="email" id="student_email" name="student_email" value="<?php echo $student_to_edit ? esc_attr($student_to_edit->email) : ''; ?>" required style="width: 100%; max-width: 400px;">
</p>
<p>
<label for="student_course">Course:</label><br>
<input type="text" id="student_course" name="student_course" value="<?php echo $student_to_edit ? esc_attr($student_to_edit->course) : ''; ?>" required style="width: 100%; max-width: 400px;">
</p>
<p>
<?php if ($student_to_edit) : ?>
<input type="submit" name="update_student" class="button button-primary" value="Update Student">
<a href="<?php echo esc_url($page_url); ?>" class="button">Cancel</a>
<?php else : ?>
<input type="submit" name="submit_student" class="button button-primary" value="Add Student">
<?php endif; ?>
</p>
</form>
<hr style="margin: 30px 0;">
<h2><b>Student List</b></h2>
<form method="get" action="<?php echo esc_url($page_url); ?>" style="margin-bottom: 20px;">
<p class="search-box" style="position: relative; float: none;">
<label class="screen-reader-text" for="student-search-input">Search Students:</label>
<input type="search" id="student-search-input" name="student_search" value="<?php echo isset($_GET['student_search']) ? esc_attr(sanitize_text_field($_GET['student_search'])) : ''; ?>">
<input type="submit" id="search-submit" class="button" value="Search Students">
</p>
</form>
<table class="wp-list-table widefat fixed striped" style="border: 1px solid #ddd; background-color: #ffffff;">
<thead>
<tr style="background-color: #ffffff;">
<th style="font-weight: bold;">ID</th>
<th style="font-weight: bold;">Name</th>
<th style="font-weight: bold;">Email</th>
<th style="font-weight: bold;">Course</th>
<th style="font-weight: bold;">Actions</th>
</tr>
</thead>
<tbody>
<?php
$sql = "SELECT * FROM $table_name";
$search_query = isset($_GET['student_search']) ? sanitize_text_field($_GET['student_search']) : '';
if (!empty($search_query)) {
$sql .= $wpdb->prepare(" WHERE name LIKE %s", '%' . $wpdb->esc_like($search_query) . '%');
}
$students = $wpdb->get_results($sql);
if (empty($students)) {
echo '<tr><td colspan="5" style="text-align: center; padding: 20px;">No students found.</td></tr>';
} else {
foreach ($students as $student) {
$edit_url = add_query_arg(['action' => 'edit', 'id' => $student->id], $page_url);
$delete_nonce = wp_create_nonce('sdp_delete_student_' . $student->id);
$delete_url = add_query_arg([
'action' => 'delete',
'id' => $student->id,
'_wpnonce' => $delete_nonce
], $page_url);
echo "<tr>";
echo "<td>" . esc_html($student->id) . "</td>";
echo "<td>" . esc_html($student->name) . "</td>";
echo "<td>" . esc_html($student->email) . "</td>";
echo "<td>" . esc_html($student->course) . "</td>";
echo '<td>
<a href="' . esc_url($edit_url) . '" class="button">Edit</a>
<a href="' . esc_url($delete_url) . '" class="button sdp-delete-link" onclick="return confirm(\'Are you sure?\')">Delete</a>
</td>';
echo "</tr>";
}
}
?>
</tbody>
</table>
</div>
<?php
return ob_get_clean();
}
add_shortcode('student_database', 'sdp_crud_shortcode');
Learnings
Placing PHP form handling logic at the very top of the function is critical to ensure the page displays updated data immediately after a submission. I practiced overriding default WordPress styles by applying inline CSS, specifically setting the background color to white to create a cleaner, custom interface. Finally, I reinforced my ability to build a secure CRUD system by combining SQL queries with WordPress nonces to safely manage student records.