Feature: Add trip publisher system - create, edit, delete, and publish trips
This commit is contained in:
@@ -82,6 +82,8 @@ RewriteRule ^admin_trip_bookings$ src/admin/admin_trip_bookings.php [L]
|
|||||||
RewriteRule ^admin_visitors$ src/admin/admin_visitors.php [L]
|
RewriteRule ^admin_visitors$ src/admin/admin_visitors.php [L]
|
||||||
RewriteRule ^admin_efts$ src/admin/admin_efts.php [L]
|
RewriteRule ^admin_efts$ src/admin/admin_efts.php [L]
|
||||||
RewriteRule ^add_campsite$ src/admin/add_campsite.php [L]
|
RewriteRule ^add_campsite$ src/admin/add_campsite.php [L]
|
||||||
|
RewriteRule ^admin_trips$ src/admin/admin_trips.php [L]
|
||||||
|
RewriteRule ^manage_trips$ src/admin/manage_trips.php [L]
|
||||||
|
|
||||||
# === API/AJAX ENDPOINTS ===
|
# === API/AJAX ENDPOINTS ===
|
||||||
RewriteRule ^fetch_users$ src/api/fetch_users.php [L]
|
RewriteRule ^fetch_users$ src/api/fetch_users.php [L]
|
||||||
@@ -111,6 +113,9 @@ RewriteRule ^update_user$ src/processors/update_user.php [L]
|
|||||||
RewriteRule ^upload_profile_picture$ src/processors/upload_profile_picture.php [L]
|
RewriteRule ^upload_profile_picture$ src/processors/upload_profile_picture.php [L]
|
||||||
RewriteRule ^send_reset_link$ src/processors/send_reset_link.php [L]
|
RewriteRule ^send_reset_link$ src/processors/send_reset_link.php [L]
|
||||||
RewriteRule ^logout$ src/processors/logout.php [L]
|
RewriteRule ^logout$ src/processors/logout.php [L]
|
||||||
|
RewriteRule ^process_trip$ src/processors/process_trip.php [L]
|
||||||
|
RewriteRule ^toggle_trip_published$ src/processors/toggle_trip_published.php [L]
|
||||||
|
RewriteRule ^delete_trip$ src/processors/delete_trip.php [L]
|
||||||
|
|
||||||
</IfModule>
|
</IfModule>
|
||||||
|
|
||||||
|
|||||||
@@ -283,6 +283,7 @@ if ($headerStyle === 'light') {
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="admin_web_users">Website Users</a></li>
|
<li><a href="admin_web_users">Website Users</a></li>
|
||||||
<li><a href="admin_members">4WDCSA Members</a></li>
|
<li><a href="admin_members">4WDCSA Members</a></li>
|
||||||
|
<li><a href="admin_trips">Manage Trips</a></li>
|
||||||
<li><a href="admin_trip_bookings">Trip Bookings</a></li>
|
<li><a href="admin_trip_bookings">Trip Bookings</a></li>
|
||||||
<li><a href="admin_course_bookings">Course Bookings</a></li>
|
<li><a href="admin_course_bookings">Course Bookings</a></li>
|
||||||
<li><a href="admin_efts">EFT Payments</a></li>
|
<li><a href="admin_efts">EFT Payments</a></li>
|
||||||
|
|||||||
150
src/admin/admin_trips.php
Normal file
150
src/admin/admin_trips.php
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
<?php
|
||||||
|
$headerStyle = 'light';
|
||||||
|
$rootPath = dirname(dirname(__DIR__));
|
||||||
|
include_once($rootPath . '/header.php');
|
||||||
|
checkAdmin();
|
||||||
|
|
||||||
|
// Fetch all trips with booking status
|
||||||
|
$trips_query = "
|
||||||
|
SELECT
|
||||||
|
trip_id, trip_name, location, start_date, end_date,
|
||||||
|
vehicle_capacity, places_booked, cost_members, published
|
||||||
|
FROM trips
|
||||||
|
ORDER BY start_date DESC
|
||||||
|
";
|
||||||
|
|
||||||
|
$result = $conn->query($trips_query);
|
||||||
|
$trips = [];
|
||||||
|
if ($result && $result->num_rows > 0) {
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$trips[] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$pageTitle = 'Manage Trips';
|
||||||
|
$breadcrumbs = [['Home' => 'index'], ['Admin' => 'admin'], [$pageTitle => '']];
|
||||||
|
require_once($rootPath . '/components/banner.php');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Trips Management Area start -->
|
||||||
|
<section class="trips-management-area py-100 rel z-1">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<div class="section-title mb-50">
|
||||||
|
<h2>Manage Trips</h2>
|
||||||
|
<a href="manage_trips" class="theme-btn">
|
||||||
|
<i class="far fa-plus"></i> Create New Trip
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Trip Name</th>
|
||||||
|
<th>Location</th>
|
||||||
|
<th>Start Date</th>
|
||||||
|
<th>End Date</th>
|
||||||
|
<th>Capacity</th>
|
||||||
|
<th>Booked</th>
|
||||||
|
<th>Cost (Member)</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (count($trips) > 0): ?>
|
||||||
|
<?php foreach ($trips as $trip): ?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong><?php echo htmlspecialchars($trip['trip_name']); ?></strong>
|
||||||
|
</td>
|
||||||
|
<td><?php echo htmlspecialchars($trip['location']); ?></td>
|
||||||
|
<td><?php echo date('M d, Y', strtotime($trip['start_date'])); ?></td>
|
||||||
|
<td><?php echo date('M d, Y', strtotime($trip['end_date'])); ?></td>
|
||||||
|
<td><?php echo $trip['vehicle_capacity']; ?></td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-info">
|
||||||
|
<?php echo $trip['places_booked'] . ' / ' . $trip['vehicle_capacity']; ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>R <?php echo number_format($trip['cost_members'], 2); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($trip['published'] == 1): ?>
|
||||||
|
<span class="badge bg-success">Published</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-warning">Draft</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="manage_trips?trip_id=<?php echo $trip['trip_id']; ?>"
|
||||||
|
class="btn btn-sm btn-primary" title="Edit">
|
||||||
|
<i class="far fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
<button class="btn btn-sm btn-danger delete-trip"
|
||||||
|
data-trip-id="<?php echo $trip['trip_id']; ?>"
|
||||||
|
title="Delete">
|
||||||
|
<i class="far fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="9" class="text-center py-4">
|
||||||
|
<p class="text-muted">No trips found. <a href="manage_trips">Create one</a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- Trips Management Area end -->
|
||||||
|
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('.delete-trip').on('click', function() {
|
||||||
|
if (!confirm('Are you sure you want to delete this trip? This action cannot be undone.')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tripId = $(this).data('trip-id');
|
||||||
|
var button = $(this);
|
||||||
|
var row = button.closest('tr');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: 'delete_trip',
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
trip_id: tripId
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
if (response.status === 'success') {
|
||||||
|
row.fadeOut(function() {
|
||||||
|
$(this).remove();
|
||||||
|
if ($('table tbody tr').length === 0) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + response.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
alert('Error deleting trip');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
|
||||||
198
src/admin/manage_trips.php
Normal file
198
src/admin/manage_trips.php
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
<?php
|
||||||
|
$headerStyle = 'light';
|
||||||
|
$rootPath = dirname(dirname(__DIR__));
|
||||||
|
include_once($rootPath . '/header.php');
|
||||||
|
checkAdmin();
|
||||||
|
|
||||||
|
$trip_id = $_GET['trip_id'] ?? null;
|
||||||
|
$trip = null;
|
||||||
|
|
||||||
|
// If editing an existing trip, fetch its data
|
||||||
|
if ($trip_id) {
|
||||||
|
$stmt = $conn->prepare("SELECT * FROM trips WHERE trip_id = ?");
|
||||||
|
$stmt->bind_param("i", $trip_id);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
$trip = $result->fetch_assoc();
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$pageTitle = $trip ? 'Edit Trip' : 'Create New Trip';
|
||||||
|
$breadcrumbs = [['Home' => 'index'], ['Admin' => 'admin_trips'], [$pageTitle => '']];
|
||||||
|
require_once($rootPath . '/components/banner.php');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Trip Manager Area start -->
|
||||||
|
<section class="trip-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="tripForm" enctype="multipart/form-data" method="POST" action="process_trip">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
|
||||||
|
<?php if ($trip): ?>
|
||||||
|
<input type="hidden" name="trip_id" value="<?php echo $trip['trip_id']; ?>">
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="section-title py-20">
|
||||||
|
<h2><?php echo $trip ? 'Edit Trip: ' . htmlspecialchars($trip['trip_name']) : 'Create New Trip'; ?></h2>
|
||||||
|
<div id="responseMessage"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Trip Information -->
|
||||||
|
<div class="row mt-35">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="trip_name">Trip Name *</label>
|
||||||
|
<input type="text" id="trip_name" name="trip_name" class="form-control" value="<?php echo $trip ? htmlspecialchars($trip['trip_name']) : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="trip_code">Trip Code</label>
|
||||||
|
<input type="text" id="trip_code" name="trip_code" class="form-control" maxlength="12" value="<?php echo $trip ? htmlspecialchars($trip['trip_code']) : ''; ?>" placeholder="e.g., TRIP001">
|
||||||
|
</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 $trip ? htmlspecialchars($trip['location']) : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="vehicle_capacity">Vehicle Capacity *</label>
|
||||||
|
<input type="number" id="vehicle_capacity" name="vehicle_capacity" class="form-control" min="1" value="<?php echo $trip ? $trip['vehicle_capacity'] : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dates -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="start_date">Start Date *</label>
|
||||||
|
<input type="date" id="start_date" name="start_date" class="form-control" value="<?php echo $trip ? $trip['start_date'] : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="end_date">End Date *</label>
|
||||||
|
<input type="date" id="end_date" name="end_date" class="form-control" value="<?php echo $trip ? $trip['end_date'] : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Descriptions -->
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="short_description">Short Description *</label>
|
||||||
|
<textarea id="short_description" name="short_description" class="form-control" rows="3" required><?php echo $trip ? htmlspecialchars($trip['short_description']) : ''; ?></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="long_description">Long Description *</label>
|
||||||
|
<textarea id="long_description" name="long_description" class="form-control" rows="6" required><?php echo $trip ? htmlspecialchars($trip['long_description']) : ''; ?></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pricing -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cost_members">Member Cost (R) *</label>
|
||||||
|
<input type="number" id="cost_members" name="cost_members" class="form-control" step="0.01" min="0" value="<?php echo $trip ? $trip['cost_members'] : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cost_nonmembers">Non-Member Cost (R) *</label>
|
||||||
|
<input type="number" id="cost_nonmembers" name="cost_nonmembers" class="form-control" step="0.01" min="0" value="<?php echo $trip ? $trip['cost_nonmembers'] : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cost_pensioner_member">Pensioner Member Cost (R) *</label>
|
||||||
|
<input type="number" id="cost_pensioner_member" name="cost_pensioner_member" class="form-control" step="0.01" min="0" value="<?php echo $trip ? $trip['cost_pensioner_member'] : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cost_pensioner">Pensioner Cost (R) *</label>
|
||||||
|
<input type="number" id="cost_pensioner" name="cost_pensioner" class="form-control" step="0.01" min="0" value="<?php echo $trip ? $trip['cost_pensioner'] : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="booking_fee">Booking Fee (R) *</label>
|
||||||
|
<input type="number" id="booking_fee" name="booking_fee" class="form-control" step="0.01" min="0" value="<?php echo $trip ? $trip['booking_fee'] : ''; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Images Upload -->
|
||||||
|
<div class="col-md-12 mt-20">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Trip Images</label>
|
||||||
|
<p class="text-muted">Upload images for this trip. Primary image will be named {trip_id}_01.jpg</p>
|
||||||
|
<input type="file" name="trip_images[]" class="form-control" accept="image/*" multiple>
|
||||||
|
<?php if ($trip): ?>
|
||||||
|
<small class="text-info">Images will be saved to: assets/images/trips/<?php echo $trip_id; ?>_{number}.jpg</small>
|
||||||
|
<?php endif; ?>
|
||||||
|
</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 $trip ? 'Update Trip' : 'Create Trip'; ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- Trip Manager Area end -->
|
||||||
|
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#tripForm').on('submit', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
var formData = new FormData(this);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: 'process_trip',
|
||||||
|
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_trips';
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
$('#responseMessage').html('<div class="alert alert-danger">' + response.message + '</div>');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.log('Error:', error);
|
||||||
|
$('#responseMessage').html('<div class="alert alert-danger">Error creating/updating trip</div>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php include_once($rootPath . '/components/insta_footer.php'); ?>
|
||||||
@@ -2810,3 +2810,95 @@ function url($page) {
|
|||||||
return '/' . $page . '.php';
|
return '/' . $page . '.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optimize image by resizing if it exceeds max dimensions
|
||||||
|
*
|
||||||
|
* @param string $filePath Path to the image file
|
||||||
|
* @param int $maxWidth Maximum width in pixels
|
||||||
|
* @param int $maxHeight Maximum height in pixels
|
||||||
|
* @return bool Success status
|
||||||
|
*/
|
||||||
|
function optimizeImage($filePath, $maxWidth = 1920, $maxHeight = 1080)
|
||||||
|
{
|
||||||
|
if (!file_exists($filePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get image info
|
||||||
|
$imageInfo = getimagesize($filePath);
|
||||||
|
if (!$imageInfo) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$width = $imageInfo[0];
|
||||||
|
$height = $imageInfo[1];
|
||||||
|
$mime = $imageInfo['mime'];
|
||||||
|
|
||||||
|
// Only resize if image is larger than max dimensions
|
||||||
|
if ($width <= $maxWidth && $height <= $maxHeight) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate new dimensions maintaining aspect ratio
|
||||||
|
$ratio = min($maxWidth / $width, $maxHeight / $height);
|
||||||
|
$newWidth = (int)($width * $ratio);
|
||||||
|
$newHeight = (int)($height * $ratio);
|
||||||
|
|
||||||
|
// Load image based on type
|
||||||
|
switch ($mime) {
|
||||||
|
case 'image/jpeg':
|
||||||
|
$source = imagecreatefromjpeg($filePath);
|
||||||
|
break;
|
||||||
|
case 'image/png':
|
||||||
|
$source = imagecreatefrompng($filePath);
|
||||||
|
break;
|
||||||
|
case 'image/gif':
|
||||||
|
$source = imagecreatefromgif($filePath);
|
||||||
|
break;
|
||||||
|
case 'image/webp':
|
||||||
|
$source = imagecreatefromwebp($filePath);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$source) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create resized image
|
||||||
|
$destination = imagecreatetruecolor($newWidth, $newHeight);
|
||||||
|
|
||||||
|
// Preserve transparency for PNG and GIF
|
||||||
|
if ($mime === 'image/png' || $mime === 'image/gif') {
|
||||||
|
$transparent = imagecolorallocatealpha($destination, 0, 0, 0, 127);
|
||||||
|
imagefill($destination, 0, 0, $transparent);
|
||||||
|
imagesavealpha($destination, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize
|
||||||
|
imagecopyresampled($destination, $source, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
|
||||||
|
|
||||||
|
// Save image
|
||||||
|
$success = false;
|
||||||
|
switch ($mime) {
|
||||||
|
case 'image/jpeg':
|
||||||
|
$success = imagejpeg($destination, $filePath, 85);
|
||||||
|
break;
|
||||||
|
case 'image/png':
|
||||||
|
$success = imagepng($destination, $filePath, 6);
|
||||||
|
break;
|
||||||
|
case 'image/gif':
|
||||||
|
$success = imagegif($destination, $filePath);
|
||||||
|
break;
|
||||||
|
case 'image/webp':
|
||||||
|
$success = imagewebp($destination, $filePath, 85);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free up memory
|
||||||
|
imagedestroy($source);
|
||||||
|
imagedestroy($destination);
|
||||||
|
|
||||||
|
return $success;
|
||||||
|
}
|
||||||
|
|||||||
@@ -259,6 +259,37 @@ include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<span class="subtitle mb-15"><?php echo $badge_text; ?></span>
|
<span class="subtitle mb-15"><?php echo $badge_text; ?></span>
|
||||||
|
|
||||||
|
<!-- Admin Publish/Unpublish Button -->
|
||||||
|
<?php
|
||||||
|
$user_role = $_SESSION['role'] ?? 'user';
|
||||||
|
if ($user_role === 'admin'):
|
||||||
|
// Fetch current published status
|
||||||
|
$status_stmt = $conn->prepare("SELECT published FROM trips WHERE trip_id = ?");
|
||||||
|
$status_stmt->bind_param("i", $trip_id);
|
||||||
|
$status_stmt->execute();
|
||||||
|
$status_result = $status_stmt->get_result();
|
||||||
|
$trip_status = $status_result->fetch_assoc();
|
||||||
|
$is_published = $trip_status['published'] ?? 0;
|
||||||
|
$status_stmt->close();
|
||||||
|
?>
|
||||||
|
<div class="admin-actions mt-20">
|
||||||
|
<button type="button" class="theme-btn" id="publishBtn" onclick="toggleTripPublished(<?php echo $trip_id; ?>)">
|
||||||
|
<?php if ($is_published): ?>
|
||||||
|
<i class="fas fa-eye-slash"></i> Unpublish Trip
|
||||||
|
<?php else: ?>
|
||||||
|
<i class="fas fa-eye"></i> Publish Trip
|
||||||
|
<?php endif; ?>
|
||||||
|
</button>
|
||||||
|
<span id="publishStatus" class="ml-3" style="margin-left: 10px;">
|
||||||
|
<?php if ($is_published): ?>
|
||||||
|
<span class="badge bg-success">Published</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-warning">Draft</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="col-xl-4 col-lg-5 text-lg-end" data-aos="fade-right" data-aos-duration="1500" data-aos-offset="50">
|
<!-- <div class="col-xl-4 col-lg-5 text-lg-end" data-aos="fade-right" data-aos-duration="1500" data-aos-offset="50">
|
||||||
<div class="tour-header-social mb-10">
|
<div class="tour-header-social mb-10">
|
||||||
@@ -673,4 +704,42 @@ include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Trip Publish/Unpublish Script -->
|
||||||
|
<script>
|
||||||
|
function toggleTripPublished(tripId) {
|
||||||
|
$.ajax({
|
||||||
|
url: 'toggle_trip_published',
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
trip_id: tripId
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
if (response.status === 'success') {
|
||||||
|
// Update button and status badge
|
||||||
|
const publishBtn = $('#publishBtn');
|
||||||
|
const statusBadge = $('#publishStatus');
|
||||||
|
|
||||||
|
if (response.published === 1) {
|
||||||
|
publishBtn.html('<i class="fas fa-eye-slash"></i> Unpublish Trip');
|
||||||
|
statusBadge.html('<span class="badge bg-success">Published</span>');
|
||||||
|
} else {
|
||||||
|
publishBtn.html('<i class="fas fa-eye"></i> Publish Trip');
|
||||||
|
statusBadge.html('<span class="badge bg-warning">Draft</span>');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
alert(response.message);
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + response.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.log('Error:', error);
|
||||||
|
alert('Error updating trip status');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php') ?>
|
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php') ?>
|
||||||
|
|||||||
52
src/processors/delete_trip.php
Normal file
52
src/processors/delete_trip.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
ob_start();
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$rootPath = dirname(dirname(__DIR__));
|
||||||
|
require_once($rootPath . '/src/config/functions.php');
|
||||||
|
require_once($rootPath . '/src/config/connection.php');
|
||||||
|
|
||||||
|
// Check admin status
|
||||||
|
session_start();
|
||||||
|
if (empty($_SESSION['user_id']) || $_SESSION['role'] !== 'admin') {
|
||||||
|
ob_end_clean();
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Unauthorized access']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$trip_id = intval($_POST['trip_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($trip_id <= 0) {
|
||||||
|
throw new Exception('Invalid trip ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete trip images from filesystem
|
||||||
|
$upload_dir = $rootPath . '/assets/images/trips/';
|
||||||
|
if (is_dir($upload_dir)) {
|
||||||
|
$files = glob($upload_dir . $trip_id . '_*.{jpg,jpeg,png,gif,webp}', GLOB_BRACE);
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if (is_file($file)) {
|
||||||
|
unlink($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete trip from database
|
||||||
|
$stmt = $conn->prepare("DELETE FROM trips WHERE trip_id = ?");
|
||||||
|
$stmt->bind_param("i", $trip_id);
|
||||||
|
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
throw new Exception('Failed to delete trip: ' . $stmt->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
ob_end_clean();
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Trip deleted successfully']);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
ob_end_clean();
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
156
src/processors/process_trip.php
Normal file
156
src/processors/process_trip.php
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<?php
|
||||||
|
ob_start();
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$rootPath = dirname(dirname(__DIR__));
|
||||||
|
require_once($rootPath . '/src/config/functions.php');
|
||||||
|
require_once($rootPath . '/src/config/connection.php');
|
||||||
|
|
||||||
|
// Check admin status
|
||||||
|
session_start();
|
||||||
|
if (empty($_SESSION['user_id']) || $_SESSION['role'] !== 'admin') {
|
||||||
|
ob_end_clean();
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Unauthorized access']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate CSRF token
|
||||||
|
if (empty($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token'] ?? '') {
|
||||||
|
ob_end_clean();
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid CSRF token']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$trip_id = $_POST['trip_id'] ?? null;
|
||||||
|
$trip_name = trim($_POST['trip_name'] ?? '');
|
||||||
|
$location = trim($_POST['location'] ?? '');
|
||||||
|
$trip_code = trim($_POST['trip_code'] ?? '');
|
||||||
|
$vehicle_capacity = intval($_POST['vehicle_capacity'] ?? 0);
|
||||||
|
$start_date = $_POST['start_date'] ?? '';
|
||||||
|
$end_date = $_POST['end_date'] ?? '';
|
||||||
|
$short_description = trim($_POST['short_description'] ?? '');
|
||||||
|
$long_description = trim($_POST['long_description'] ?? '');
|
||||||
|
$cost_members = floatval($_POST['cost_members'] ?? 0);
|
||||||
|
$cost_nonmembers = floatval($_POST['cost_nonmembers'] ?? 0);
|
||||||
|
$cost_pensioner_member = floatval($_POST['cost_pensioner_member'] ?? 0);
|
||||||
|
$cost_pensioner = floatval($_POST['cost_pensioner'] ?? 0);
|
||||||
|
$booking_fee = floatval($_POST['booking_fee'] ?? 0);
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
if (empty($trip_name) || empty($location) || empty($start_date) || empty($end_date)) {
|
||||||
|
throw new Exception('Required fields are missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($vehicle_capacity <= 0) {
|
||||||
|
throw new Exception('Vehicle capacity must be greater than 0');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strtotime($start_date) >= strtotime($end_date)) {
|
||||||
|
throw new Exception('Start date must be before end date');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If creating new trip, insert first to get trip_id
|
||||||
|
if (!$trip_id) {
|
||||||
|
$stmt = $conn->prepare("
|
||||||
|
INSERT INTO trips (
|
||||||
|
trip_name, location, trip_code, vehicle_capacity, start_date, end_date,
|
||||||
|
short_description, long_description, cost_members, cost_nonmembers,
|
||||||
|
cost_pensioner_member, cost_pensioner, booking_fee, published, places_booked
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0)
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->bind_param(
|
||||||
|
"sssiissssdddd",
|
||||||
|
$trip_name, $location, $trip_code, $vehicle_capacity,
|
||||||
|
$start_date, $end_date, $short_description, $long_description,
|
||||||
|
$cost_members, $cost_nonmembers, $cost_pensioner_member,
|
||||||
|
$cost_pensioner, $booking_fee
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
throw new Exception('Failed to create trip: ' . $stmt->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$trip_id = $conn->insert_id;
|
||||||
|
$stmt->close();
|
||||||
|
} else {
|
||||||
|
// Update existing trip
|
||||||
|
$stmt = $conn->prepare("
|
||||||
|
UPDATE trips SET
|
||||||
|
trip_name = ?, location = ?, trip_code = ?, vehicle_capacity = ?,
|
||||||
|
start_date = ?, end_date = ?, short_description = ?, long_description = ?,
|
||||||
|
cost_members = ?, cost_nonmembers = ?, cost_pensioner_member = ?,
|
||||||
|
cost_pensioner = ?, booking_fee = ?
|
||||||
|
WHERE trip_id = ?
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->bind_param(
|
||||||
|
"sssiisssdddddi",
|
||||||
|
$trip_name, $location, $trip_code, $vehicle_capacity,
|
||||||
|
$start_date, $end_date, $short_description, $long_description,
|
||||||
|
$cost_members, $cost_nonmembers, $cost_pensioner_member,
|
||||||
|
$cost_pensioner, $booking_fee, $trip_id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
throw new Exception('Failed to update trip: ' . $stmt->error);
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle image uploads
|
||||||
|
if (!empty($_FILES['trip_images']['name'][0])) {
|
||||||
|
$upload_dir = $rootPath . '/assets/images/trips/';
|
||||||
|
|
||||||
|
// Create directory if it doesn't exist
|
||||||
|
if (!is_dir($upload_dir)) {
|
||||||
|
mkdir($upload_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||||
|
$image_count = 1;
|
||||||
|
|
||||||
|
foreach ($_FILES['trip_images']['name'] as $key => $filename) {
|
||||||
|
if (empty($filename)) continue;
|
||||||
|
|
||||||
|
$file_ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
// Validate file extension
|
||||||
|
if (!in_array($file_ext, $allowed_extensions)) {
|
||||||
|
throw new Exception('Invalid file type: ' . $filename . '. Only images allowed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate file size (5MB max per file)
|
||||||
|
if ($_FILES['trip_images']['size'][$key] > 5 * 1024 * 1024) {
|
||||||
|
throw new Exception('File too large: ' . $filename . '. Max 5MB per file.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate filename: {trip_id}_0{number}.{ext}
|
||||||
|
$new_filename = $trip_id . '_0' . $image_count . '.' . $file_ext;
|
||||||
|
$file_path = $upload_dir . $new_filename;
|
||||||
|
|
||||||
|
// Move uploaded file
|
||||||
|
if (!move_uploaded_file($_FILES['trip_images']['tmp_name'][$key], $file_path)) {
|
||||||
|
throw new Exception('Failed to upload image: ' . $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimize image (resize if too large)
|
||||||
|
optimizeImage($file_path, 1920, 1080);
|
||||||
|
|
||||||
|
$image_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ob_end_clean();
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => $trip_id ? 'Trip updated successfully' : 'Trip created successfully',
|
||||||
|
'trip_id' => $trip_id
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
ob_end_clean();
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
59
src/processors/toggle_trip_published.php
Normal file
59
src/processors/toggle_trip_published.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
ob_start();
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$rootPath = dirname(dirname(__DIR__));
|
||||||
|
require_once($rootPath . '/src/config/functions.php');
|
||||||
|
require_once($rootPath . '/src/config/connection.php');
|
||||||
|
|
||||||
|
// Check admin status
|
||||||
|
session_start();
|
||||||
|
if (empty($_SESSION['user_id']) || $_SESSION['role'] !== 'admin') {
|
||||||
|
ob_end_clean();
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Unauthorized access']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$trip_id = intval($_POST['trip_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($trip_id <= 0) {
|
||||||
|
throw new Exception('Invalid trip ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch current published status
|
||||||
|
$stmt = $conn->prepare("SELECT published FROM trips WHERE trip_id = ?");
|
||||||
|
$stmt->bind_param("i", $trip_id);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows === 0) {
|
||||||
|
throw new Exception('Trip not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = $result->fetch_assoc();
|
||||||
|
$new_status = $row['published'] == 1 ? 0 : 1;
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
// Update published status
|
||||||
|
$stmt = $conn->prepare("UPDATE trips SET published = ? WHERE trip_id = ?");
|
||||||
|
$stmt->bind_param("ii", $new_status, $trip_id);
|
||||||
|
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
throw new Exception('Failed to update trip status: ' . $stmt->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
ob_end_clean();
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => $new_status == 1 ? 'Trip published successfully' : 'Trip unpublished successfully',
|
||||||
|
'published' => $new_status
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
ob_end_clean();
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
Reference in New Issue
Block a user