feat: create events management admin system
- Add manage_events.php form for creating/editing events - Add process_event.php endpoint for CRUD operations with image uploads - Add admin_events.php list view with sorting, filtering, and delete functionality - Add database migration to add created_by, published, created_at, updated_at columns to events table - Add event images directory structure - All features follow same patterns as trip management system
This commit is contained in:
14
docs/migrations/001_add_events_tracking_columns.sql
Normal file
14
docs/migrations/001_add_events_tracking_columns.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- Events Table Migration
|
||||||
|
-- Add missing columns to events table for proper tracking and publishing control
|
||||||
|
|
||||||
|
-- Add columns if they don't exist (using ALTER IGNORE for compatibility)
|
||||||
|
ALTER TABLE `events`
|
||||||
|
ADD COLUMN `created_by` int DEFAULT NULL AFTER `promo`,
|
||||||
|
ADD COLUMN `published` tinyint(1) DEFAULT 0 AFTER `created_by`,
|
||||||
|
ADD COLUMN `created_at` timestamp DEFAULT CURRENT_TIMESTAMP AFTER `published`,
|
||||||
|
ADD COLUMN `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER `created_at`;
|
||||||
|
|
||||||
|
-- Add indexes for better query performance
|
||||||
|
ALTER TABLE `events` ADD INDEX `idx_date` (`date`);
|
||||||
|
ALTER TABLE `events` ADD INDEX `idx_published` (`published`);
|
||||||
|
ALTER TABLE `events` ADD INDEX `idx_created_by` (`created_by`);
|
||||||
275
src/admin/admin_events.php
Normal file
275
src/admin/admin_events.php
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
<?php
|
||||||
|
$headerStyle = 'light';
|
||||||
|
$rootPath = dirname(dirname(__DIR__));
|
||||||
|
include_once($rootPath . '/header.php');
|
||||||
|
checkAdmin();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead th {
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
padding: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead th::after {
|
||||||
|
content: '\25B2';
|
||||||
|
/* Up arrow */
|
||||||
|
font-size: 0.8em;
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead th.asc::after {
|
||||||
|
content: '\25B2';
|
||||||
|
/* Up arrow */
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead th.desc::after {
|
||||||
|
content: '\25BC';
|
||||||
|
/* Down arrow */
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:nth-child(odd) {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:nth-child(even) {
|
||||||
|
background-color: rgb(255, 255, 255);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody td {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:nth-child(even) td:first-child {
|
||||||
|
border-top-left-radius: 10px;
|
||||||
|
border-bottom-left-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:nth-child(even) td:last-child {
|
||||||
|
border-top-right-radius: 10px;
|
||||||
|
border-bottom-right-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: rgb(255, 255, 255);
|
||||||
|
border-radius: 25px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-list {
|
||||||
|
color: #484848;
|
||||||
|
background: #f9f9f7;
|
||||||
|
border: 1px solid #d8d8d8;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-header {
|
||||||
|
padding: 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-header h4 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons a, .action-buttons button {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-decoration: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-published {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-draft {
|
||||||
|
background-color: #ffc107;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
const tables = document.querySelectorAll("table");
|
||||||
|
tables.forEach((table) => {
|
||||||
|
const headers = table.querySelectorAll("thead th");
|
||||||
|
const rows = Array.from(table.querySelectorAll("tbody tr"));
|
||||||
|
const filterInput = table.previousElementSibling;
|
||||||
|
|
||||||
|
headers.forEach((header, index) => {
|
||||||
|
header.addEventListener("click", () => {
|
||||||
|
const sortedRows = rows.sort((a, b) => {
|
||||||
|
const aText = a.cells[index].textContent.trim().toLowerCase();
|
||||||
|
const bText = b.cells[index].textContent.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (aText < bText) return -1;
|
||||||
|
if (aText > bText) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (header.classList.contains("asc")) {
|
||||||
|
header.classList.remove("asc");
|
||||||
|
header.classList.add("desc");
|
||||||
|
sortedRows.reverse();
|
||||||
|
} else {
|
||||||
|
headers.forEach(h => h.classList.remove("asc", "desc"));
|
||||||
|
header.classList.add("asc");
|
||||||
|
}
|
||||||
|
|
||||||
|
const tbody = table.querySelector("tbody");
|
||||||
|
tbody.innerHTML = "";
|
||||||
|
sortedRows.forEach(row => tbody.appendChild(row));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (rows.length === 0) {
|
||||||
|
filterInput.style.display = "none";
|
||||||
|
} else {
|
||||||
|
filterInput.addEventListener("input", function() {
|
||||||
|
const filterValue = filterInput.value.trim().toLowerCase();
|
||||||
|
rows.forEach(row => {
|
||||||
|
const rowText = row.textContent.trim().toLowerCase();
|
||||||
|
row.style.display = rowText.includes(filterValue) ? "" : "none";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function deleteEvent(eventId) {
|
||||||
|
if (confirm('Are you sure you want to delete this event?')) {
|
||||||
|
fetch('process_event?action=delete&event_id=' + eventId, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + data.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error:', error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$pageTitle = 'Manage Events';
|
||||||
|
$breadcrumbs = [['Home' => 'index'], [$pageTitle => '']];
|
||||||
|
require_once($rootPath . '/components/banner.php');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<section class="tour-list-page py-10 rel z-1">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-20">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<a href="manage_events" class="theme-btn style-two">+ Create New Event</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Query to retrieve all events
|
||||||
|
$stmt = $conn->prepare("SELECT event_id, name, type, location, date, published FROM events ORDER BY date DESC");
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
echo '<input type="text" class="filter-input" placeholder="Search events...">';
|
||||||
|
echo '<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Event Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Location</th>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>';
|
||||||
|
|
||||||
|
while ($event = $result->fetch_assoc()) {
|
||||||
|
$event_id = $event['event_id'];
|
||||||
|
$name = htmlspecialchars($event['name']);
|
||||||
|
$type = htmlspecialchars($event['type']);
|
||||||
|
$location = htmlspecialchars($event['location']);
|
||||||
|
$date = convertDate($event['date']);
|
||||||
|
$status = $event['published'] ? '<span class="badge badge-published">Published</span>' : '<span class="badge badge-draft">Draft</span>';
|
||||||
|
|
||||||
|
echo "<tr>
|
||||||
|
<td>{$name}</td>
|
||||||
|
<td>{$type}</td>
|
||||||
|
<td>{$location}</td>
|
||||||
|
<td>{$date}</td>
|
||||||
|
<td>{$status}</td>
|
||||||
|
<td>
|
||||||
|
<div class='action-buttons'>
|
||||||
|
<a href='manage_events?event_id={$event_id}' class='btn-edit'>Edit</a>
|
||||||
|
<button class='btn-delete' onclick='deleteEvent({$event_id})'>Delete</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</tbody></table>';
|
||||||
|
} else {
|
||||||
|
echo '<div class="alert alert-info">No events found. <a href="manage_events">Create the first event</a></div>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
|
||||||
183
src/admin/manage_events.php
Normal file
183
src/admin/manage_events.php
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
<?php
|
||||||
|
$headerStyle = 'light';
|
||||||
|
$rootPath = dirname(dirname(__DIR__));
|
||||||
|
include_once($rootPath . '/header.php');
|
||||||
|
checkAdmin();
|
||||||
|
|
||||||
|
$event_id = $_GET['event_id'] ?? null;
|
||||||
|
$event = null;
|
||||||
|
|
||||||
|
// If editing an existing event, fetch its data
|
||||||
|
if ($event_id) {
|
||||||
|
$stmt = $conn->prepare("SELECT * FROM events WHERE event_id = ?");
|
||||||
|
$stmt->bind_param("i", $event_id);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
$event = $result->fetch_assoc();
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$pageTitle = $event ? 'Edit Event' : 'Create New Event';
|
||||||
|
$breadcrumbs = [['Home' => 'index'], ['Admin' => 'admin_events'], [$pageTitle => '']];
|
||||||
|
require_once($rootPath . '/components/banner.php');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Event Manager Area start -->
|
||||||
|
<section class="event-manager-area py-100 rel z-1">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<div class="comment-form bgc-lighter z-1 rel mb-30 rmb-55">
|
||||||
|
<form id="eventForm" enctype="multipart/form-data" method="POST" action="process_event">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
|
||||||
|
<?php if ($event): ?>
|
||||||
|
<input type="hidden" name="event_id" value="<?php echo $event['event_id']; ?>">
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="section-title py-20">
|
||||||
|
<h2><?php echo $event ? 'Edit Event: ' . htmlspecialchars($event['name']) : 'Create New Event'; ?></h2>
|
||||||
|
<div id="responseMessage"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Event Information -->
|
||||||
|
<div class="row mt-35">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Event Name *</label>
|
||||||
|
<input type="text" id="name" name="name" class="form-control" value="<?php echo $event ? htmlspecialchars($event['name']) : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="type">Event Type *</label>
|
||||||
|
<input type="text" id="type" name="type" class="form-control" value="<?php echo $event ? htmlspecialchars($event['type']) : ''; ?>" placeholder="e.g., Workshop, Training, Rally" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="location">Location *</label>
|
||||||
|
<input type="text" id="location" name="location" class="form-control" value="<?php echo $event ? htmlspecialchars($event['location']) : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="date">Date *</label>
|
||||||
|
<input type="date" id="date" name="date" class="form-control" value="<?php echo $event ? $event['date'] : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Time -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="time">Time *</label>
|
||||||
|
<input type="time" id="time" name="time" class="form-control" value="<?php echo $event ? $event['time'] : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Feature/Category -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="feature">Feature/Category *</label>
|
||||||
|
<input type="text" id="feature" name="feature" class="form-control" value="<?php echo $event ? htmlspecialchars($event['feature']) : ''; ?>" placeholder="e.g., Off-Road Training, Social Event" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Descriptions -->
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description">Description *</label>
|
||||||
|
<textarea id="description" name="description" class="form-control" rows="6" required><?php echo $event ? htmlspecialchars($event['description']) : ''; ?></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Image Upload -->
|
||||||
|
<div class="col-md-12 mt-20">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="image">Event Image *</label>
|
||||||
|
<input type="file" id="image" name="image" class="form-control" accept="image/*" <?php echo !$event ? 'required' : ''; ?>>
|
||||||
|
<?php if ($event && $event['image']): ?>
|
||||||
|
<small class="text-info d-block mt-2">Current image: <img src="<?php echo $event['image']; ?>" alt="Event Image" style="max-width: 200px; margin-top: 10px;"></small>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Promo Image Upload -->
|
||||||
|
<div class="col-md-12 mt-20">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="promo">Promotional Image</label>
|
||||||
|
<input type="file" id="promo" name="promo" class="form-control" accept="image/*">
|
||||||
|
<small class="text-muted">This image will be displayed when users click "View Promo"</small>
|
||||||
|
<?php if ($event && $event['promo']): ?>
|
||||||
|
<small class="text-info d-block mt-2">Current promo: <img src="<?php echo $event['promo']; ?>" alt="Promo Image" style="max-width: 200px; margin-top: 10px;"></small>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Published Status -->
|
||||||
|
<div class="col-md-6 mt-20">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="published">
|
||||||
|
<input type="checkbox" id="published" name="published" value="1" <?php echo ($event && $event['published']) ? 'checked' : ''; ?>>
|
||||||
|
Publish Event (visible on website)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12 mt-20">
|
||||||
|
<div class="form-group mb-0">
|
||||||
|
<button type="submit" class="theme-btn style-two" style="width:100%;">
|
||||||
|
<?php echo $event ? 'Update Event' : 'Create Event'; ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- Event Manager Area end -->
|
||||||
|
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#eventForm').on('submit', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
var formData = new FormData(this);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: 'process_event',
|
||||||
|
type: 'POST',
|
||||||
|
data: formData,
|
||||||
|
contentType: false,
|
||||||
|
processData: false,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
if (response.status === 'success') {
|
||||||
|
$('#responseMessage').html('<div class="alert alert-success">' + response.message + '</div>');
|
||||||
|
setTimeout(function() {
|
||||||
|
window.location.href = 'admin_events';
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
$('#responseMessage').html('<div class="alert alert-danger">' + response.message + '</div>');
|
||||||
|
console.error('Server error:', response.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.log('AJAX Error:', error);
|
||||||
|
console.log('Response:', xhr.responseText);
|
||||||
|
$('#responseMessage').html('<div class="alert alert-danger">Error creating/updating event: ' + error + '</div>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
|
||||||
195
src/admin/process_event.php
Normal file
195
src/admin/process_event.php
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
<?php
|
||||||
|
$rootPath = dirname(dirname(__DIR__));
|
||||||
|
include_once($rootPath . '/header.php');
|
||||||
|
checkAdmin();
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Handle delete action
|
||||||
|
if ($_GET['action'] ?? null === 'delete') {
|
||||||
|
$event_id = $_GET['event_id'] ?? null;
|
||||||
|
|
||||||
|
if (!$event_id) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Event ID is required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get event details to delete associated files
|
||||||
|
$stmt = $conn->prepare("SELECT image, promo FROM events WHERE event_id = ?");
|
||||||
|
$stmt->bind_param("i", $event_id);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
$event = $result->fetch_assoc();
|
||||||
|
|
||||||
|
// Delete image files
|
||||||
|
if ($event['image'] && file_exists($rootPath . '/' . $event['image'])) {
|
||||||
|
unlink($rootPath . '/' . $event['image']);
|
||||||
|
}
|
||||||
|
if ($event['promo'] && file_exists($rootPath . '/' . $event['promo'])) {
|
||||||
|
unlink($rootPath . '/' . $event['promo']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete from database
|
||||||
|
$delete_stmt = $conn->prepare("DELETE FROM events WHERE event_id = ?");
|
||||||
|
$delete_stmt->bind_param("i", $event_id);
|
||||||
|
|
||||||
|
if ($delete_stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Event deleted successfully']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Failed to delete event']);
|
||||||
|
}
|
||||||
|
$delete_stmt->close();
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Event not found']);
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check CSRF token
|
||||||
|
if (!isset($_POST['csrf_token']) || !verifyCsrfToken($_POST['csrf_token'])) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'CSRF token validation failed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event_id = $_POST['event_id'] ?? null;
|
||||||
|
$name = $_POST['name'] ?? null;
|
||||||
|
$type = $_POST['type'] ?? null;
|
||||||
|
$location = $_POST['location'] ?? null;
|
||||||
|
$date = $_POST['date'] ?? null;
|
||||||
|
$time = $_POST['time'] ?? null;
|
||||||
|
$feature = $_POST['feature'] ?? null;
|
||||||
|
$description = $_POST['description'] ?? null;
|
||||||
|
$published = isset($_POST['published']) ? 1 : 0;
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!$name || !$type || !$location || !$date || !$time || !$feature || !$description) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'All required fields must be filled']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle image upload
|
||||||
|
$image_path = null;
|
||||||
|
if (!empty($_FILES['image']['name'])) {
|
||||||
|
$upload_dir = $rootPath . '/assets/images/events/';
|
||||||
|
if (!is_dir($upload_dir)) {
|
||||||
|
mkdir($upload_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_name = uniqid() . '_' . basename($_FILES['image']['name']);
|
||||||
|
$target_file = $upload_dir . $file_name;
|
||||||
|
$file_type = mime_content_type($_FILES['image']['tmp_name']);
|
||||||
|
|
||||||
|
// Validate image file
|
||||||
|
$allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||||
|
if (!in_array($file_type, $allowed_types)) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid image file type. Only JPEG, PNG, GIF, and WebP are allowed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move_uploaded_file($_FILES['image']['tmp_name'], $target_file)) {
|
||||||
|
$image_path = 'assets/images/events/' . $file_name;
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Failed to upload image']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} else if (!$event_id) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Image is required for new events']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle promo image upload
|
||||||
|
$promo_path = null;
|
||||||
|
if (!empty($_FILES['promo']['name'])) {
|
||||||
|
$upload_dir = $rootPath . '/assets/images/events/';
|
||||||
|
if (!is_dir($upload_dir)) {
|
||||||
|
mkdir($upload_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_name = uniqid() . '_promo_' . basename($_FILES['promo']['name']);
|
||||||
|
$target_file = $upload_dir . $file_name;
|
||||||
|
$file_type = mime_content_type($_FILES['promo']['tmp_name']);
|
||||||
|
|
||||||
|
// Validate image file
|
||||||
|
$allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||||
|
if (!in_array($file_type, $allowed_types)) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid promo image file type. Only JPEG, PNG, GIF, and WebP are allowed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move_uploaded_file($_FILES['promo']['tmp_name'], $target_file)) {
|
||||||
|
$promo_path = 'assets/images/events/' . $file_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($event_id) {
|
||||||
|
// Update existing event
|
||||||
|
$update_fields = [
|
||||||
|
'name' => $name,
|
||||||
|
'type' => $type,
|
||||||
|
'location' => $location,
|
||||||
|
'date' => $date,
|
||||||
|
'time' => $time,
|
||||||
|
'feature' => $feature,
|
||||||
|
'description' => $description,
|
||||||
|
'published' => $published,
|
||||||
|
'updated_at' => date('Y-m-d H:i:s')
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($image_path) {
|
||||||
|
$update_fields['image'] = $image_path;
|
||||||
|
}
|
||||||
|
if ($promo_path) {
|
||||||
|
$update_fields['promo'] = $promo_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$set_clause = implode(', ', array_map(function($key) {
|
||||||
|
return $key . ' = ?';
|
||||||
|
}, array_keys($update_fields)));
|
||||||
|
|
||||||
|
$values = array_values($update_fields);
|
||||||
|
$values[] = $event_id;
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("UPDATE events SET $set_clause WHERE event_id = ?");
|
||||||
|
|
||||||
|
// Build type string for bind_param
|
||||||
|
$type_str = str_repeat('s', count($update_fields)) . 'i';
|
||||||
|
$stmt->bind_param($type_str, ...$values);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Event updated successfully']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Failed to update event: ' . $stmt->error]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create new event
|
||||||
|
if (!$image_path) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Image is required for new events']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$promo_path = $promo_path ?? 'assets/images/events/default-promo.jpg';
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("
|
||||||
|
INSERT INTO events (name, type, location, date, time, feature, description, image, promo, published, created_by)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
");
|
||||||
|
|
||||||
|
$created_by = $_SESSION['user_id'] ?? 0;
|
||||||
|
|
||||||
|
$stmt->bind_param('ssssssssiii', $name, $type, $location, $date, $time, $feature, $description, $image_path, $promo_path, $published, $created_by);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Event created successfully']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Failed to create event: ' . $stmt->error]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'An error occurred: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user