Update site: auth fixes, new admin pages, payment flow, membership renewal, helpers

This commit is contained in:
twotalesanimation
2026-06-11 10:01:48 +02:00
parent 32e50ffc39
commit bc8d923221
56 changed files with 5155 additions and 534 deletions
+24 -13
View File
@@ -5,6 +5,11 @@ require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php");
session_start();
// Enable error reporting for debugging
error_reporting(E_ALL);
ini_set('display_errors', 0); // Don't display, but log them
ini_set('log_errors', 1);
if (!isset($_SESSION['user_id'])) {
http_response_code(401);
echo "Not authorized";
@@ -32,36 +37,42 @@ echo $author_id;
$cover_image_path = null;
// Only attempt upload if a file was submitted
if (!empty($_FILES['cover_image']['name'])) {
if (!empty($_FILES['cover_image']['name']) && $_FILES['cover_image']['error'] === UPLOAD_ERR_OK) {
$uploadDir = $rootPath . "/uploads/blogs/" . $article_id . "/";
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
// Create directory if it doesn't exist (match working pattern)
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
// Validate file using existing function
$file_result = validateFileUpload($_FILES['cover_image'], 'profile_picture');
if ($file_result === false) {
// Simple validation - check extension
$extension = strtolower(pathinfo($_FILES['cover_image']['name'], PATHINFO_EXTENSION));
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (!in_array($extension, $allowedExtensions)) {
http_response_code(400);
echo "Invalid file upload";
echo "Invalid file type. Allowed: jpg, jpeg, png, gif, webp";
exit;
}
// Use fixed filename "cover" to avoid creating multiple copies on autosave
$extension = $file_result['extension'];
$filename = "cover." . $extension;
// Delete old cover if it exists with different extension
array_map('unlink', glob($uploadDir . "cover.*"));
$oldCovers = glob($uploadDir . "cover.*");
if ($oldCovers) {
foreach ($oldCovers as $oldCover) {
@unlink($oldCover);
}
}
$targetPath = $uploadDir . $filename;
$cover_image_path = "/uploads/blogs/" . $article_id . "/" . $filename;
// Move the uploaded file
if (move_uploaded_file($_FILES['cover_image']['tmp_name'], $targetPath)) {
// File moved successfully, $cover_image_path is set
} else {
if (!move_uploaded_file($_FILES['cover_image']['tmp_name'], $targetPath)) {
http_response_code(500);
echo "Failed to move uploaded file.";
echo "Failed to move uploaded file";
exit;
}
}
+2 -2
View File
@@ -26,8 +26,8 @@ if (isset($_FILES['cover_image']) && $_FILES['cover_image']['error'] === UPLOAD_
$upload_dir = $rootPath . '/uploads/blogs/' . $folder_id . '/';
// Create directory if it doesn't exist
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0755, true);
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}
// Validate and process the file
+49
View File
@@ -0,0 +1,49 @@
<?php
ob_start();
header('Content-Type: application/json');
$rootPath = dirname(dirname(__DIR__));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . '/src/config/functions.php');
require_once($rootPath . '/src/config/connection.php');
// Check admin status
session_start();
if (empty($_SESSION['user_id'])) {
ob_end_clean();
echo json_encode(['status' => 'error', 'message' => 'Unauthorized access']);
exit;
}
$user_role = getUserRole();
if (!in_array($user_role, ['admin', 'superadmin'])) {
ob_end_clean();
echo json_encode(['status' => 'error', 'message' => 'Unauthorized access']);
exit;
}
try {
$course_id = intval($_POST['course_id'] ?? 0);
if ($course_id <= 0) {
throw new Exception('Invalid course ID');
}
$stmt = $conn->prepare("DELETE FROM courses WHERE course_id = ?");
$stmt->bind_param("i", $course_id);
if (!$stmt->execute()) {
throw new Exception('Failed to delete course: ' . $stmt->error);
}
$stmt->close();
ob_end_clean();
echo json_encode(['status' => 'success', 'message' => 'Course deleted successfully']);
} catch (Exception $e) {
ob_end_clean();
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
?>
+67 -37
View File
@@ -1,46 +1,76 @@
<?php
$rootPath = dirname(dirname(__DIR__));
include_once($rootPath . '/header.php');
checkAdmin();
<?php
ob_start();
header('Content-Type: application/json');
$event_id = $_POST['event_id'] ?? null;
$rootPath = dirname(dirname(__DIR__));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . '/src/config/functions.php');
require_once($rootPath . '/src/config/connection.php');
if (!$event_id) {
echo json_encode(['status' => 'error', 'message' => 'Event ID is required']);
// Start session if not already started
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Check admin status
if (empty($_SESSION['user_id'])) {
ob_end_clean();
echo json_encode(['status' => 'error', 'message' => 'Unauthorized access']);
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']);
$user_role = getUserRole();
if (!in_array($user_role, ['admin', 'superadmin'])) {
ob_end_clean();
echo json_encode(['status' => 'error', 'message' => 'Unauthorized access']);
exit;
}
$stmt->close();
try {
$event_id = intval($_POST['event_id'] ?? 0);
if ($event_id <= 0) {
throw new Exception('Invalid event ID');
}
// 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()) {
ob_end_clean();
echo json_encode(['status' => 'success', 'message' => 'Event deleted successfully']);
} else {
ob_end_clean();
echo json_encode(['status' => 'error', 'message' => 'Failed to delete event']);
}
$delete_stmt->close();
} else {
ob_end_clean();
echo json_encode(['status' => 'error', 'message' => 'Event not found']);
}
$stmt->close();
} catch (Exception $e) {
ob_end_clean();
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
+1 -1
View File
@@ -174,7 +174,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($stmt->execute()) {
// Insert into the membership fees table
$payment_amount = calculateProrata(210); // Assuming a fixed membership fee, adjust as needed
$payment_amount = calculateProrata();
$payment_date = date('Y-m-d');
$membership_start_date = $payment_date;
// $membership_end_date = date('Y-12-31');
+14
View File
@@ -3,6 +3,7 @@ $rootPath = dirname(dirname(__DIR__));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php");
require_once($rootPath . "/src/helpers/notification_helper.php");
// Start session to retrieve the logged-in user's ID
session_start();
@@ -79,6 +80,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stmt->bind_param('sissiiiidd', $type, $user_id, $from_date, $to_date, $num_vehicles, $num_adults, $num_children, $add_firewood, $total_amount, $discount_amount);
if ($stmt->execute()) {
// Get booking id and audit
$booking_id = $conn->insert_id;
if (function_exists('auditLog')) {
auditLog($user_id, 'BOOKING_CREATED', 'bookings', $booking_id, ['total_amount' => $total_amount, 'from' => $from_date, 'to' => $to_date]);
}
$event = 'new_booking_created';
$sub_feed = 'bookings';
$data = [
'actor_id' => $_SESSION['user_id'] ?? null,
'actor_avatar' => $_SESSION['profile_pic'] ?? null, // used by UI to show avatar
'title' => "New Booking Created with Booking ID: {$booking_id}"
];
addNotification(null, $event, $sub_feed, $data, null);
// Redirect to success page or display success message
echo "<script>alert('Booking successfully created!'); window.location.href = 'booking.php';</script>";
} else {
+100
View File
@@ -0,0 +1,100 @@
<?php
ob_start();
header('Content-Type: application/json');
$rootPath = dirname(dirname(__DIR__));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . '/src/config/functions.php');
require_once($rootPath . '/src/config/connection.php');
// Check admin status
session_start();
if (empty($_SESSION['user_id'])) {
ob_end_clean();
echo json_encode(['status' => 'error', 'message' => 'Unauthorized access']);
exit;
}
$user_role = getUserRole();
if (!in_array($user_role, ['admin', 'superadmin'])) {
ob_end_clean();
echo json_encode(['status' => 'error', 'message' => 'Unauthorized access']);
exit;
}
try {
$course_id = $_POST['course_id'] ?? null;
$course_type = trim($_POST['course_type'] ?? '');
$code = trim($_POST['code'] ?? '');
$date = trim($_POST['date'] ?? '');
$capacity = intval($_POST['capacity'] ?? 0);
$cost_members = floatval($_POST['cost_members'] ?? 0);
$cost_nonmembers = floatval($_POST['cost_nonmembers'] ?? 0);
$instructor = trim($_POST['instructor'] ?? '');
$instructor_email = trim($_POST['instructor_email'] ?? '');
$allowed_types = ['driver_training','bush_mechanics','rescue_recovery','ladies_driver_training'];
if (!in_array($course_type, $allowed_types)) {
throw new Exception('Invalid course type');
}
if (empty($date) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
throw new Exception('Invalid date format');
}
// If code not provided, generate from type + date using ABBR_MMDD format
if (empty($code)) {
$abbrMap = [
'driver_training' => 'DRVTRN',
'bush_mechanics' => 'BUSHMEC',
'rescue_recovery' => 'RESREC',
'ladies_driver_training' => 'LADYTRN'
];
$abbr = $abbrMap[$course_type] ?? strtoupper(preg_replace('/[^A-Z0-9]/', '', $course_type));
// ensure abbr fits (reserve 1 char for underscore and 4 for MMDD)
$abbr = substr($abbr, 0, 7);
$mmdd = date('md', strtotime($date));
$code = strtoupper(substr($abbr . '_' . $mmdd, 0, 12));
}
if ($capacity <= 0) {
throw new Exception('Capacity must be greater than 0');
}
if (empty($instructor)) {
throw new Exception('Instructor name is required');
}
if ($course_id) {
// Update
$stmt = $conn->prepare("UPDATE courses SET course_type = ?, code = ?, date = ?, capacity = ?, cost_members = ?, cost_nonmembers = ?, instructor = ?, instructor_email = ? WHERE course_id = ?");
$stmt->bind_param("sssiddssi", $course_type, $code, $date, $capacity, $cost_members, $cost_nonmembers, $instructor, $instructor_email, $course_id);
if (!$stmt->execute()) {
throw new Exception('Failed to update course: ' . $stmt->error);
}
$stmt->close();
} else {
// Insert - booked defaults to 0
$stmt = $conn->prepare("INSERT INTO courses (course_type, code, date, capacity, booked, cost_members, cost_nonmembers, instructor, instructor_email) VALUES (?, ?, ?, ?, 0, ?, ?, ?, ?)");
$stmt->bind_param("sssiddss", $course_type, $code, $date, $capacity, $cost_members, $cost_nonmembers, $instructor, $instructor_email);
if (!$stmt->execute()) {
throw new Exception('Failed to create course: ' . $stmt->error);
}
$course_id = $conn->insert_id;
$stmt->close();
}
ob_end_clean();
echo json_encode(['status' => 'success', 'message' => $course_id ? 'Course saved successfully' : 'Course created successfully', 'course_id' => $course_id]);
} catch (Exception $e) {
ob_end_clean();
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
?>
+40 -6
View File
@@ -3,6 +3,7 @@ $rootPath = dirname(dirname(__DIR__));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php");
require_once($rootPath . "/src/helpers/notification_helper.php");
session_start();
@@ -93,10 +94,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$status = "AWAITING PAYMENT";
$type = 'course';
$payment_id = uniqid();
$payment_id = generatePaymentRef('COURSE', $course_id, $user_id);
$publicRef = bin2hex(random_bytes(16));
$num_vehicles = 1;
$discountAmount = 0;
$eft_id = strtoupper("COURSE ".date("m-d", strtotime($date))." ".getInitialSurname($user_id));
$eft_id = $payment_id;
$notes = "";
if ($pending_member){
$notes = "Membership Payment pending at time of booking. Please confirm payment has been received.";
@@ -117,6 +119,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($stmt->execute()) {
$booking_id = $conn->insert_id;
// Audit booking creation
if (function_exists('auditLog')) {
auditLog($user_id, 'COURSE_BOOKING_CREATED', 'bookings', $booking_id, ['course_id' => $course_id, 'payment_id' => $payment_id, 'amount' => $payment_amount]);
}
$event = 'new_course_booking_created';
$sub_feed = 'bookings';
$data = [
'actor_id' => $_SESSION['user_id'] ?? null,
'actor_avatar' => $_SESSION['profile_pic'] ?? null, // used by UI to show avatar
'title' => "New Course Booking Created : {$payment_id}"
];
addNotification(null, $event, $sub_feed, $data, null);
if ($payment_amount < 1) {
if (processZeroPayment($payment_id, $payment_amount, $description)) {
echo "<script>alert('Booking successfully created!'); window.location.href = 'bookings.php';</script>";
@@ -125,11 +140,30 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
echo "Error processing booking: $error_message";
}
} else {
addEFT($eft_id, $booking_id, $user_id, $status, $payment_amount, $description);
sendInvoice(getEmail($user_id), getFullName($user_id), $eft_id, formatCurrency($payment_amount), $description);
// Create payments row
$pstmt = $conn->prepare("INSERT INTO payments (payment_id, user_id, amount, status, description, booking_id, public_ref) VALUES (?, ?, ?, ?, ?, ?, ?)");
if ($pstmt) {
$pstmt->bind_param('sidssis', $payment_id, $user_id, $payment_amount, $status, $description, $booking_id, $publicRef);
$pstmt->execute();
$pstmt->close();
}
// Create iKhokha payment link
$resp = createIkhokhaPayment($payment_id, $payment_amount, $description, $publicRef);
// Send invoice and admin notification (keep for records)
sendAdminNotification('New Course Booking - '.getFullName($user_id), getFullName($user_id).' has booked for '.$description);
header("Location: payment_confirmation?token=".encryptData($booking_id, $salt));
exit(); // Ensure no further code is executed after the redirect
// Redirect user to payment link if available
$paylink = $resp['paylinkUrl'] ?? $resp['paylinkURL'] ?? $resp['paylink_url'] ?? null;
if ($paylink) {
header('Location: ' . $paylink);
exit();
} else {
// Fallback: redirect to legacy payment confirmation page
header("Location: payment_confirmation?token=".encryptData($booking_id, $salt));
exit();
}
}
} else {
// Handle error if insert fails and echo the MySQL error
+12 -16
View File
@@ -78,19 +78,17 @@ if (!$name || !$type || !$location || !$date || !$time || !$feature || !$descrip
$image_path = null;
if (!empty($_FILES['image']['name'])) {
$upload_dir = $rootPath . '/assets/images/events/';
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0755, true);
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}
$file_name = uniqid() . '_' . basename($_FILES['image']['name']);
$target_file = $upload_dir . $file_name;
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$file_type = finfo_file($finfo, $_FILES['image']['tmp_name']);
finfo_close($finfo);
// Validate image file
$allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($file_type, $allowed_types)) {
// Validate file extension
$ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
$allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (!in_array($ext, $allowed_extensions)) {
echo json_encode(['status' => 'error', 'message' => 'Invalid image file type. Only JPEG, PNG, GIF, and WebP are allowed']);
exit;
}
@@ -110,19 +108,17 @@ if (!empty($_FILES['image']['name'])) {
$promo_path = null;
if (!empty($_FILES['promo']['name'])) {
$upload_dir = $rootPath . '/assets/images/events/';
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0755, true);
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}
$file_name = uniqid() . '_promo_' . basename($_FILES['promo']['name']);
$target_file = $upload_dir . $file_name;
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$file_type = finfo_file($finfo, $_FILES['promo']['tmp_name']);
finfo_close($finfo);
// Validate image file
$allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($file_type, $allowed_types)) {
// Validate file extension
$ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
$allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (!in_array($ext, $allowed_extensions)) {
echo json_encode(['status' => 'error', 'message' => 'Invalid promo image file type. Only JPEG, PNG, GIF, and WebP are allowed']);
exit;
}
+60 -32
View File
@@ -15,64 +15,92 @@ if (!$user_id) {
echo "<script>alert('User is not logged in. Please log in to make a booking.'); window.location.href = 'login.php';</script>";
exit();
}
$is_member = getUserMemberStatus($user_id);
$query = "SELECT payment_amount, payment_status, membership_end_date FROM membership_fees WHERE user_id = ?";
// Fetch the membership fee record for this user
$query = "SELECT fee_id, payment_amount, payment_status, membership_end_date FROM membership_fees WHERE user_id = ?";
$stmt = $conn->prepare($query);
if (!$stmt) {
http_response_code(500);
echo json_encode(['error' => 'Server error preparing statement']);
exit();
}
$stmt->bind_param('i', $user_id);
$stmt->execute();
$result = $stmt->get_result();
// Check if trip exists
// Check if membership fee exists
if ($result->num_rows === 0) {
$response = ['error' => 'Application Fee not found.'];
$response = ['error' => 'Membership fee not found.'];
header('Content-Type: application/json');
echo json_encode($response);
exit();
}
// Fetch trip details
// Fetch fee details
$fee = $result->fetch_assoc();
$fee_id = isset($fee['fee_id']) ? intval($fee['fee_id']) : null;
$payment_status = $fee['payment_status'];
$membership_end_date = $fee['membership_end_date'];
$payment_amount = intval($fee['payment_amount']);
$payment_amount = floatval($fee['payment_amount']);
$publicRef = bin2hex(random_bytes(16));
$description = "4WDCSA: Membership Fee " . getFullName($user_id) . " " . date("Y");
$payment_id = uniqid();
$eft_id = "SUBS 2025 ".getLastName($user_id);
// Update the membership_fees table to set payment_id
$stmt = $conn->prepare("UPDATE membership_fees SET payment_id = ? WHERE user_id = ?");
if ($stmt) {
$stmt->bind_param("ss", $payment_id, $user_id);
if (!$stmt->execute()) {
throw new Exception("Failed to update membership_fees table.");
// Persist the generated payment_id back to the membership_fees row (use fee_id to be precise)
$updateStmt = $conn->prepare("UPDATE membership_fees SET payment_id = ? WHERE fee_id = ?");
if ($updateStmt) {
$updateStmt->bind_param("si", $payment_id, $fee_id);
if (!$updateStmt->execute()) {
throw new Exception("Failed to update membership_fees table: " . $updateStmt->error);
}
$stmt->close();
$conn->close();
$updateStmt->close();
} else {
throw new Exception("Failed to prepare statement for membership_fees table: " . $conn->error);
}
// Get the current date
$current_date = new DateTime();
// If the amount is zero, treat as paid immediately
if ($payment_amount < 1) {
if (processZeroPayment($payment_id, $payment_amount, $description)) {
// Update membership_fees status to PAID
$paidStmt = $conn->prepare("UPDATE membership_fees SET payment_status = 'PAID' WHERE fee_id = ?");
if ($paidStmt) {
$paidStmt->bind_param('i', $fee_id);
$paidStmt->execute();
$paidStmt->close();
}
echo "<script>alert('Membership payment recorded.'); window.location.href = 'memberships.php';</script>";
exit();
} else {
echo "<script>alert('Failed to process membership payment.'); window.location.href = 'memberships.php';</script>";
exit();
}
} else {
// Create payments row
$status = "AWAITING PAYMENT";
$pstmt = $conn->prepare("INSERT INTO payments (payment_id, user_id, amount, status, description, public_ref) VALUES (?, ?, ?, ?, ?, ?)");
if ($pstmt) {
$pstmt->bind_param('sidsss', $payment_id, $user_id, $payment_amount, $status, $description, $publicRef);
$pstmt->execute();
$pstmt->close();
}
// Convert $membership_end_date to a DateTime object
$membership_end_date_obj = DateTime::createFromFormat('Y-m-d', $membership_end_date);
// Create iKhokha payment link
$resp = createIkhokhaPayment($payment_id, $payment_amount, $description, $publicRef);
// Check if the current date is after membership_end_date
// OR if the current date is before or on membership_end_date AND payment_status is "PENDING"
if (
$current_date > $membership_end_date_obj ||
($current_date <= $membership_end_date_obj && $payment_status === "PENDING")
) {
// Send invoice and admin notification if desired
// sendInvoice(getEmail($user_id), getFullName($user_id), 'MEMBERSHIP-'.date('Y'), formatCurrency($payment_amount), $description);
sendAdminNotification('Membership Payment Initiated - '.getFullName($user_id), getFullName($user_id).' initiated a membership payment.');
// Call the processMembershipPayment function
// processMembershipPayment($payment_id, $payment_amount, $description);
addMembershipEFT($eft_id, $user_id, $status, $amount, $description, $membershipfee_id);
header("Location: payment_confirmation?booking_id=" . $booking_id);
exit(); // Ensure no further code is executed after the redirect
// Redirect user to payment link if available
$paylink = $resp['paylinkUrl'] ?? $resp['paylinkURL'] ?? $resp['paylink_url'] ?? null;
if ($paylink) {
header('Location: ' . $paylink);
exit();
} else {
// Fallback: redirect to a membership page with an encrypted token
header("Location: membership_confirmation?token=" . encryptData($payment_id, $salt));
exit();
}
}
+114 -5
View File
@@ -36,7 +36,7 @@ if (isset($_POST['signature'])) {
$filePath = $rootPath . '/uploads/signatures/' . $fileName;
// Ensure the directory exists
if (!is_dir($rootPath . '/uploads/signatures')) {
if (!file_exists($rootPath . '/uploads/signatures')) {
mkdir($rootPath . '/uploads/signatures', 0777, true);
}
@@ -56,17 +56,122 @@ if (isset($_POST['signature'])) {
$stmt->bind_param('si', $display_path, $user_id);
if ($stmt->execute()) {
// Audit: signature saved
if (function_exists('auditLog')) {
auditLog($user_id, 'SIGNATURE_SAVED', 'membership_application', null, ['path' => $display_path]);
}
// Check the payment status
$paymentStatus = checkMembershipPaymentStatus($user_id) ? 'PAID' : 'NOT_PAID';
// Respond with the appropriate redirect URL based on the payment status
// If not paid, create a payments row (if missing) and initiate iKhokha paylink
$paylink = null;
if ($paymentStatus !== 'PAID') {
// Fetch the membership fee row to get amount and payment_id
$mfStmt = $conn->prepare("SELECT fee_id, payment_amount, payment_id FROM membership_fees WHERE user_id = ? ORDER BY fee_id DESC LIMIT 1");
if ($mfStmt) {
$mfStmt->bind_param('i', $user_id);
$mfStmt->execute();
$mfRes = $mfStmt->get_result();
$mf = $mfRes->fetch_assoc();
$mfStmt->close();
} else {
$mf = null;
}
if ($mf && isset($mf['payment_amount'])) {
$amount = floatval($mf['payment_amount']);
// Use existing payment_id or generate one
$payment_id = $mf['payment_id'] ?? generatePaymentRef('SUBS', null, $user_id);;
if (empty($mf['payment_id'])) {
// Persist generated payment_id back to membership_fees
$u = $conn->prepare("UPDATE membership_fees SET payment_id = ? WHERE fee_id = ?");
if ($u) {
$u->bind_param('si', $payment_id, $mf['fee_id']);
$u->execute();
$u->close();
}
}
// Ensure a payments row exists
$checkP = $conn->prepare("SELECT COUNT(*) AS cnt FROM payments WHERE payment_id = ? LIMIT 1");
if ($checkP) {
$checkP->bind_param('s', $payment_id);
$checkP->execute();
$r = $checkP->get_result()->fetch_assoc();
$exists = intval($r['cnt']) > 0;
$checkP->close();
} else {
$exists = false;
}
if (!$exists) {
$publicRef = bin2hex(random_bytes(16));
// If current month is December, attribute the membership year to the next year
$currentYear = intval(date('Y'));
$month = intval(date('n'));
if ($month === 12) {
$membershipYear = $currentYear + 1;
} else {
$membershipYear = $currentYear;
}
$description = 'Membership Fees ' . $membershipYear . ' ' . getInitialSurname($user_id);
$status = 'AWAITING PAYMENT';
$ins = $conn->prepare("INSERT INTO payments (payment_id, user_id, amount, status, description, public_ref) VALUES (?, ?, ?, ?, ?, ?)");
if ($ins) {
$ins->bind_param('sidsss', $payment_id, $user_id, $amount, $status, $description, $publicRef);
if ($ins->execute()) {
// Audit: payment row created for membership
if (function_exists('auditLog')) {
auditLog($user_id, 'MEMBERSHIP_PAYMENT_CREATED', 'payments', null, ['payment_id' => $payment_id, 'amount' => $amount]);
}
}
$ins->close();
}
}
// Create iKhokha paylink via helper (functions.php)
try {
$publicRef = $publicRef ?? bin2hex(random_bytes(16));
$resp = createIkhokhaPayment($payment_id, $amount, $desc ?? ('Membership Fee ' . date('Y')), $publicRef);
$paylink = $resp['paylinkUrl'] ?? $resp['paylinkURL'] ?? $resp['paylink_url'] ?? null;
// After creating paylink, update paymentStatus to AWAITING PAYMENT
$paymentStatus = $paylink ? 'AWAITING PAYMENT' : $paymentStatus;
$token = encryptData($payment_id, $_ENV['SALT']);
// Audit: paylink created (or attempted)
if (function_exists('auditLog')) {
auditLog($user_id, 'IKHOKHA_PAYLINK_CREATED', 'payments', null, ['payment_id' => $payment_id, 'paylink' => $paylink]);
}
} catch (Exception $e) {
// Log but do not fail signature save
error_log('iKhokha create error: ' . $e->getMessage());
if (function_exists('auditLog')) {
auditLog($user_id, 'IKHOKHA_PAYLINK_FAILED', 'payments', null, ['payment_id' => $payment_id, 'error' => $e->getMessage()]);
}
}
}
}
// Respond with the appropriate redirect URL and paylink (if created)
ob_end_clean();
echo json_encode([
$response = [
'status' => 'success',
'message' => 'Signature saved successfully!',
'paymentStatus' => $paymentStatus // Send payment status
]);
'paymentStatus' => $paymentStatus,
'token' => $token ?? null
];
if (!empty($paylink)) {
$response['paylinkUrl'] = $paylink;
}
if (!empty($payment_id)) {
$response['payment_id'] = $payment_id;
}
echo json_encode($response);
} else {
// Audit: signature save failed
if (function_exists('auditLog')) {
auditLog($user_id, 'SIGNATURE_SAVE_FAILED', 'membership_application', null, ['user_id' => $user_id]);
}
ob_end_clean();
echo json_encode(['status' => 'error', 'message' => 'Database update failed']);
}
@@ -78,6 +183,10 @@ if (isset($_POST['signature'])) {
echo json_encode(['status' => 'error', 'message' => 'Failed to save signature']);
}
} else {
// Audit: no signature provided in request
if (function_exists('auditLog') && isset($_SESSION['user_id'])) {
auditLog($_SESSION['user_id'], 'SIGNATURE_NOT_PROVIDED', 'membership_application', null, ['endpoint' => 'process_signature.php']);
}
ob_end_clean();
echo json_encode(['status' => 'error', 'message' => 'Signature not provided']);
}
+2 -2
View File
@@ -136,8 +136,8 @@ try {
$upload_dir = $rootPath . '/assets/images/trips/';
// Create directory if it doesn't exist
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0755, true);
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}
$allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
+44 -7
View File
@@ -3,6 +3,7 @@ $rootPath = dirname(dirname(__DIR__));
require_once($rootPath . "/src/config/env.php");
require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php");
require_once($rootPath . "/src/helpers/notification_helper.php");
session_start();
// Get the trip_id from the request (ensure it's sanitized)
@@ -78,6 +79,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$member_discount = $cost_nonmembers - $cost_members;
$member_discount_pensioner = $cost_pensioner - $cost_pensioner_member;
$booking_fee = $trip['booking_fee'];
// Radio option (boolean/int) — ensure defined from POST
$radio = isset($_POST['radio']) ? intval($_POST['radio']) : 0;
$radioCost = $radio ? 50 : 0;
$start_date = $trip['start_date']; // Start date of the trip
$end_date = $trip['end_date']; // End date of the trip
@@ -103,9 +106,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$status = "AWAITING PAYMENT";
$description = $trip_name;
$type = 'trip';
$payment_id = uniqid();
$payment_id = generatePaymentRef('TRIP', $trip_id, $user_id);
$publicRef = bin2hex(random_bytes(16));
// $eft_id = strtoupper(base_convert(time(), 10, 36)); // Convert timestamp to base36
$eft_id = strtoupper($trip_code." ".getInitialSurname($user_id));
// $eft_id = strtoupper($trip_code." ".getInitialSurname($user_id));
// Insert booking into the database
@@ -123,6 +127,21 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Get the generated booking_id
$booking_id = $conn->insert_id;
// Audit booking creation
if (function_exists('auditLog')) {
auditLog($user_id, 'TRIP_BOOKING_CREATED', 'bookings', $booking_id, ['trip_id' => $trip_id, 'payment_id' => $payment_id, 'amount' => $payment_amount]);
}
// Create notification for new booking
$event = 'new_trip_booking_created';
$sub_feed = 'bookings';
$data = [
'actor_id' => $_SESSION['user_id'] ?? null,
'actor_avatar' => $_SESSION['profile_pic'] ?? null, // used by UI to show avatar
'title' => "New Trip Booking Created: {$payment_id}"
];
addNotification(null, $event, $sub_feed, $data, null);
if ($payment_amount < 1) {
if (processZeroPayment($payment_id, $payment_amount, $description)) {
echo "<script>alert('Booking successfully created!'); window.location.href = 'bookings.php';</script>";
@@ -131,11 +150,29 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
echo "Error processing booking: $error_message";
}
} else {
addEFT($eft_id, $booking_id, $user_id, $status, $payment_amount, $description);
sendInvoice(getEmail($user_id), getFullName($user_id), $eft_id, formatCurrency($payment_amount), $description);
sendAdminNotification('New Trip Booking - '.getFullName($user_id), getFullName($user_id).' has booked for '.$description);
header("Location: payment_confirmation?token=".encryptData($booking_id, $salt));
exit(); // Ensure no further code is executed after the redirect
// Create payments row
$pstmt = $conn->prepare("INSERT INTO payments (payment_id, user_id, amount, status, description, booking_id, public_ref) VALUES (?, ?, ?, ?, ?, ?, ?)");
if ($pstmt) {
$pstmt->bind_param('sidssis', $payment_id, $user_id, $payment_amount, $status, $description, $booking_id, $publicRef);
$pstmt->execute();
$pstmt->close();
}
// Create iKhokha payment link
$resp = createIkhokhaPayment($payment_id, $payment_amount, $description, $publicRef);
// Send invoice and admin notification
sendAdminNotification('New Trip Booking - '.getFullName($user_id), getFullName($user_id).' has booked for '.$description);
// Redirect to payment link if available
$paylink = $resp['paylinkUrl'] ?? $resp['paylinkURL'] ?? $resp['paylink_url'] ?? null;
if ($paylink) {
header('Location: ' . $paylink);
exit();
} else {
header("Location: payment_confirmation?token=".encryptData($booking_id, $salt));
exit();
}
}
} else {
// Handle error if insert fails and echo the MySQL error
+16 -16
View File
@@ -52,26 +52,25 @@ try {
// Create album directory
$albumDir = $rootPath . '/assets/uploads/gallery/' . $album_id;
if (!is_dir($albumDir)) {
if (!mkdir($albumDir, 0755, true)) {
throw new Exception('Failed to create album directory');
}
if (!file_exists($albumDir)) {
mkdir($albumDir, 0777, true);
}
// Handle cover image upload
$coverImagePath = null;
if (isset($_FILES['cover_image']) && $_FILES['cover_image']['error'] !== UPLOAD_ERR_NO_FILE) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (isset($_FILES['cover_image']) && $_FILES['cover_image']['error'] === UPLOAD_ERR_OK) {
$maxSize = 5 * 1024 * 1024; // 5MB
$fileName = $_FILES['cover_image']['name'];
$fileTmpName = $_FILES['cover_image']['tmp_name'];
$fileSize = $_FILES['cover_image']['size'];
$fileMime = mime_content_type($fileTmpName);
// Validate file extension
$ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
// Validate file
if (!in_array($fileMime, $allowedMimes)) {
throw new Exception('Invalid cover image file type');
if (!in_array($ext, $allowedExtensions)) {
throw new Exception('Invalid cover image file type. Allowed: jpg, jpeg, png, gif, webp');
}
if ($fileSize > $maxSize) {
@@ -96,8 +95,7 @@ try {
}
// Handle photo uploads
if (isset($_FILES['photos']) && $_FILES['photos']['error'][0] !== UPLOAD_ERR_NO_FILE) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (isset($_FILES['photos']) && $_FILES['photos']['error'][0] === UPLOAD_ERR_OK) {
$maxSize = 5 * 1024 * 1024; // 5MB
$displayOrder = 1;
@@ -111,11 +109,13 @@ try {
$fileName = $_FILES['photos']['name'][$i];
$fileTmpName = $_FILES['photos']['tmp_name'][$i];
$fileSize = $_FILES['photos']['size'][$i];
$fileMime = mime_content_type($fileTmpName);
// Validate file extension
$ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
// Validate file
if (!in_array($fileMime, $allowedMimes)) {
throw new Exception('Invalid file type: ' . $fileName);
if (!in_array($ext, $allowedExtensions)) {
throw new Exception('Invalid file type: ' . $fileName . '. Allowed: jpg, jpeg, png, gif, webp');
}
if ($fileSize > $maxSize) {
+3 -8
View File
@@ -43,14 +43,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$filename = str_replace(' ', '_', $eft_id) . '.pdf';
$target_file = $target_dir . $filename;
// Make sure target directory exists and writable
if (!is_dir($target_dir)) {
mkdir($target_dir, 0755, true);
}
if (!is_writable($target_dir)) {
echo "<div class='alert alert-danger'>Upload directory is not writable: $target_dir</div>";
exit;
// Make sure target directory exists
if (!file_exists($target_dir)) {
mkdir($target_dir, 0777, true);
}
if (move_uploaded_file($_FILES['pop_file']['tmp_name'], $target_file)) {
+30 -22
View File
@@ -76,25 +76,29 @@ try {
$updateStmt->close();
// Handle cover image upload if provided
if (isset($_FILES['cover_image']) && $_FILES['cover_image']['error'] !== UPLOAD_ERR_NO_FILE) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
$maxSize = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['cover_image']) && $_FILES['cover_image']['error'] === UPLOAD_ERR_OK) {
$fileName = $_FILES['cover_image']['name'];
$fileTmpName = $_FILES['cover_image']['tmp_name'];
$fileSize = $_FILES['cover_image']['size'];
$fileMime = mime_content_type($fileTmpName);
// Validate file
if (!in_array($fileMime, $allowedMimes)) {
throw new Exception('Invalid cover image file type');
// Validate file extension
$ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (!in_array($ext, $allowedExtensions)) {
throw new Exception('Invalid cover image file type. Allowed: jpg, jpeg, png, gif, webp');
}
if ($fileSize > $maxSize) {
if ($fileSize > 5 * 1024 * 1024) {
throw new Exception('Cover image file too large (max 5MB)');
}
$albumDir = $rootPath . '/assets/uploads/gallery/' . $album_id;
// Create directory if it doesn't exist (match working pattern)
if (!file_exists($albumDir)) {
mkdir($albumDir, 0777, true);
}
// Delete old cover if it exists
$oldCoverStmt = $conn->prepare("SELECT cover_image FROM photo_albums WHERE album_id = ?");
@@ -104,16 +108,15 @@ try {
if ($oldCoverResult->num_rows > 0) {
$oldCover = $oldCoverResult->fetch_assoc();
if ($oldCover['cover_image']) {
$oldCoverPath = $_SERVER['DOCUMENT_ROOT'] . $oldCover['cover_image'];
$oldCoverPath = $rootPath . $oldCover['cover_image'];
if (file_exists($oldCoverPath)) {
unlink($oldCoverPath);
@unlink($oldCoverPath);
}
}
}
$oldCoverStmt->close();
// Generate unique filename
$ext = pathinfo($fileName, PATHINFO_EXTENSION);
$newFileName = 'cover_' . uniqid() . '.' . $ext;
$filePath = $albumDir . '/' . $newFileName;
$coverImagePath = '/assets/uploads/gallery/' . $album_id . '/' . $newFileName;
@@ -130,12 +133,15 @@ try {
}
// Handle photo uploads if any
if (isset($_FILES['photos']) && $_FILES['photos']['error'][0] !== UPLOAD_ERR_NO_FILE) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (isset($_FILES['photos']) && $_FILES['photos']['error'][0] === UPLOAD_ERR_OK) {
$maxSize = 5 * 1024 * 1024; // 5MB
$albumDir = $rootPath . '/assets/uploads/gallery/' . $album_id;
// Create directory if it doesn't exist (match working pattern)
if (!file_exists($albumDir)) {
mkdir($albumDir, 0777, true);
}
// Get current max display order
$orderStmt = $conn->prepare("SELECT MAX(display_order) as max_order FROM photos WHERE album_id = ?");
$orderStmt->bind_param("i", $album_id);
@@ -153,15 +159,17 @@ try {
$fileName = $_FILES['photos']['name'][$i];
$fileTmpName = $_FILES['photos']['tmp_name'][$i];
$fileSize = $_FILES['photos']['size'][$i];
$fileMime = mime_content_type($fileTmpName);
// Validate file
if (!in_array($fileMime, $allowedMimes)) {
throw new Exception('Invalid file type: ' . $fileName);
// Validate file extension
$ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (!in_array($ext, $allowedExtensions)) {
throw new Exception('Invalid file type: ' . $fileName . '. Allowed: jpg, jpeg, png, gif, webp');
}
if ($fileSize > $maxSize) {
throw new Exception('File too large: ' . $fileName);
throw new Exception('File too large: ' . $fileName . ' (max 5MB)');
}
// Generate unique filename
+3 -9
View File
@@ -43,15 +43,9 @@ if (isset($_FILES['profile_picture']) && $_FILES['profile_picture']['error'] !=
$target_dir = $rootPath . "/assets/images/pp/";
$target_file = $target_dir . $randomFilename;
// Ensure upload directory exists and is writable
if (!is_dir($target_dir)) {
mkdir($target_dir, 0755, true);
}
if (!is_writable($target_dir)) {
$response['message'] = 'Upload directory is not writable.';
echo json_encode($response);
exit();
// Ensure upload directory exists
if (!file_exists($target_dir)) {
mkdir($target_dir, 0777, true);
}
// Move the uploaded file