+
+
+
+
+
+
+ ×
+
+
+
+ prepare($sql);
+ $stmt->bind_param("s", $status);
+ $stmt->execute();
+ $result = $stmt->get_result();
+
+ if ($result->num_rows > 0) {
+ // Loop through each row
+ while ($row = $result->fetch_assoc()) {
+ $eft_id = $row['eft_id'];
+ $file_name = str_replace(' ', '_', $eft_id);
+ $eft_user = $row['user_id'];
+ $eft_amount = $row['amount'];
+ $eft_description = $row['description'];
+
+ // Output the HTML structure with dynamic data
+ echo '
+
+
+
+
' . htmlspecialchars($eft_description) . '
+ ' . getFullName($eft_user) . '
+
+ ';
+ }
+ } else {
+ echo '
There are no pending payments for processing.
';
+ }
+ // Close connection
+ $conn->close();
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/processors/process_signature.php b/src/processors/process_signature.php
new file mode 100644
index 00000000..ee0b0a35
--- /dev/null
+++ b/src/processors/process_signature.php
@@ -0,0 +1,70 @@
+ 'error', 'message' => 'User not logged in']));
+}
+
+if (isset($_POST['signature'])) {
+ // CSRF Token Validation
+ // if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
+ // auditLog($_SESSION['user_id'], 'CSRF_VALIDATION_FAILED', 'membership_application', null, ['endpoint' => 'process_signature.php']);
+ // die(json_encode(['status' => 'error', 'message' => 'Security token validation failed']));
+ // }
+
+ $user_id = $_SESSION['user_id']; // Get the user ID from the session
+ $signature = $_POST['signature']; // Base64 image data
+
+ // Decode the base64 image
+ $signature = str_replace('data:image/png;base64,', '', $signature);
+ $signature = str_replace(' ', '+', $signature);
+ $signatureData = base64_decode($signature);
+
+ // Create a file path for the signature image
+ $fileName = 'signature_' . $user_id . '.png';
+ $filePath = 'uploads/signatures/' . $fileName;
+
+ // Ensure the directory exists
+ if (!is_dir('uploads/signatures')) {
+ mkdir('uploads/signatures', 0777, true);
+ }
+
+ // Save the image file
+ if (file_put_contents($filePath, $signatureData)) {
+ // Update the database
+
+ if ($conn->connect_error) {
+ die(json_encode(['status' => 'error', 'message' => 'Database connection failed']));
+ }
+
+ // Update the signature and indemnity acceptance in the membership application table
+ $stmt = $conn->prepare("UPDATE membership_application SET sig = ?, accept_indemnity = 1 WHERE user_id = ?");
+ $stmt->bind_param('si', $filePath, $user_id);
+
+ if ($stmt->execute()) {
+ // Check the payment status
+ $paymentStatus = checkMembershipPaymentStatus($user_id) ? 'PAID' : 'NOT_PAID';
+
+ // Respond with the appropriate redirect URL based on the payment status
+ echo json_encode([
+ 'status' => 'success',
+ 'message' => 'Signature saved successfully!',
+ 'paymentStatus' => $paymentStatus // Send payment status
+ ]);
+ } else {
+ echo json_encode(['status' => 'error', 'message' => 'Database update failed']);
+ }
+
+ $stmt->close();
+ $conn->close();
+ } else {
+ echo json_encode(['status' => 'error', 'message' => 'Failed to save signature']);
+ }
+} else {
+ echo json_encode(['status' => 'error', 'message' => 'Signature not provided']);
+}
+
diff --git a/src/processors/process_trip_booking.php b/src/processors/process_trip_booking.php
new file mode 100644
index 00000000..bda5b5a4
--- /dev/null
+++ b/src/processors/process_trip_booking.php
@@ -0,0 +1,172 @@
+alert('User is not logged in. Please log in to make a booking.'); window.location.href = 'login.php';";
+ exit();
+}
+$is_member = getUserMemberStatus($user_id);
+
+// Check if the form has been submitted
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ // CSRF Token Validation
+ if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
+ auditLog($user_id, 'CSRF_VALIDATION_FAILED', 'bookings', null, ['endpoint' => 'process_trip_booking.php']);
+ http_response_code(403);
+ header('Content-Type: application/json');
+ echo json_encode(['error' => 'Security token validation failed.']);
+ exit();
+ }
+
+ // Input variables from the form (use default values if not provided)
+ $num_vehicles = validateInteger($_POST['vehicles'] ?? 1, 1, 10);
+ if ($num_vehicles === false) $num_vehicles = 1;
+
+ $num_adults = validateInteger($_POST['adults'] ?? 1, 1, 20);
+ if ($num_adults === false) $num_adults = 1;
+
+ $num_children = validateInteger($_POST['children'] ?? 0, 0, 20);
+ if ($num_children === false) $num_children = 0;
+
+ $num_pensioners = validateInteger($_POST['pensioners'] ?? 0, 0, 20);
+ if ($num_pensioners === false) $num_pensioners = 0;
+ // Fetch trip costs from the database
+ $query = "SELECT trip_name, cost_members, cost_nonmembers, cost_pensioner_member, cost_pensioner, booking_fee, start_date, end_date, trip_code FROM trips WHERE trip_id = ?";
+ $stmt = $conn->prepare($query);
+ $stmt->bind_param('i', $trip_id);
+ $stmt->execute();
+ $result = $stmt->get_result();
+
+ // Check if trip exists
+ if ($result->num_rows === 0) {
+ $response = ['error' => 'Trip not found.'];
+ header('Content-Type: application/json');
+ echo json_encode($response);
+ exit();
+ }
+
+ // Fetch trip details
+ $trip = $result->fetch_assoc();
+ $trip_code = $trip['trip_code'];
+ $trip_name = $trip['trip_name'];
+ $cost_members = intval($trip['cost_members']);
+ $cost_nonmembers = intval($trip['cost_nonmembers']);
+ $cost_pensioner_member = intval($trip['cost_pensioner_member']);
+ $cost_pensioner = intval($trip['cost_pensioner']);
+ $member_discount = $cost_nonmembers - $cost_members;
+ $member_discount_pensioner = $cost_pensioner - $cost_pensioner_member;
+ $booking_fee = $trip['booking_fee'];
+ $radioCost = $radio ? 50 : 0;
+ $start_date = $trip['start_date']; // Start date of the trip
+ $end_date = $trip['end_date']; // End date of the trip
+
+
+ // Assume the membership status is determined elsewhere
+ $is_member = getUserMemberStatus($user_id);
+
+ // Initialize total and discount amount
+ $total = 0;
+ $discountAmount = 0;
+
+ // Calculate total based on membership
+ if ($is_member) {
+ $total = (($num_adults + $num_children) * $cost_nonmembers) + ($num_pensioners * $cost_pensioner) + $radioCost + ($num_vehicles * $booking_fee);
+ $discountAmount = (($num_adults + $num_children) * $member_discount) + ($num_pensioners * $member_discount_pensioner );
+ $payment_amount = $total - $discountAmount;
+ } else {
+ $total = (($num_adults + $num_children) * $cost_nonmembers) + ($num_pensioners * $cost_pensioner) + $radioCost + ($num_vehicles * $booking_fee);
+ $payment_amount = $total;
+ }
+
+ $status = "AWAITING PAYMENT";
+ $description = $trip_name;
+ $type = 'trip';
+ $payment_id = uniqid();
+ // $eft_id = strtoupper(base_convert(time(), 10, 36)); // Convert timestamp to base36
+ $eft_id = strtoupper($trip_code." ".getInitialSurname($user_id));
+
+
+ // Insert booking into the database
+ $sql = "INSERT INTO bookings (booking_type, user_id, from_date, to_date, num_vehicles, num_adults, num_children, total_amount, discount_amount, status, payment_id, trip_id, radio, eft_id, num_pensioners)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ $stmt = $conn->prepare($sql);
+
+ if (!$stmt) {
+ die("Preparation failed: " . $conn->error);
+ }
+
+ $stmt->bind_param('sissiiiddssiisi', $type, $user_id, $start_date, $end_date, $num_vehicles, $num_adults, $num_children, $total, $discountAmount, $status, $payment_id, $trip_id, $radio, $eft_id, $num_pensioners);
+
+ if ($stmt->execute()) {
+ // Get the generated booking_id
+ $booking_id = $conn->insert_id;
+
+ if ($payment_amount < 1) {
+ if (processZeroPayment($payment_id, $payment_amount, $description)) {
+ echo "";
+ } else {
+ $error_message = $stmt->error;
+ echo "Error processing booking: $error_message";
+ }
+ } else {
+ addEFT($eft_id, $booking_id, $user_id, $status, $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
+ }
+ } else {
+ // Handle error if insert fails and echo the MySQL error
+ $error_message = $stmt->error;
+ echo "Error processing booking: $error_message";
+ }
+
+ // if ($stmt->execute()) {
+ // if ($payment_amount < 1) {
+ // if (processZeroPayment($payment_id, $payment_amount, $description)) {
+ // echo "";
+ // } else {
+ // $error_message = $stmt->error;
+ // echo "Error processing booking: $error_message";
+ // }
+ // } else {
+ // if (processPayment($payment_id, $payment_amount, $description)) {
+ // echo "";
+ // } else {
+ // $error_message = $stmt->error;
+ // echo "Error processing booking: $error_message";
+ // }
+ // }
+ // } else {
+ // // Handle error if insert fails and echo the MySQL error
+ // $error_message = $stmt->error;
+ // echo "Error processing booking: $error_message";
+ // }
+
+ $stmt->close();
+ $conn->close();
+} else {
+ echo "Invalid request.";
+}
+
diff --git a/src/processors/register_user.php b/src/processors/register_user.php
new file mode 100644
index 00000000..a2a3a2c2
--- /dev/null
+++ b/src/processors/register_user.php
@@ -0,0 +1,147 @@
+connect_error) {
+ die("Connection failed: " . $conn->connect_error);
+}
+
+
+// Form processing
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ // CSRF Token Validation
+ if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) {
+ auditLog(null, 'CSRF_VALIDATION_FAILED', 'users', null, ['endpoint' => 'register_user.php']);
+ echo json_encode(['status' => 'error', 'message' => 'Security token validation failed. Please try again.']);
+ exit();
+ }
+
+ // Check rate limiting on registration endpoint (by IP)
+ $ip = getClientIPAddress();
+ $cutoffTime = date('Y-m-d H:i:s', time() - (3600)); // Last hour
+
+ $stmt = $conn->prepare("SELECT COUNT(*) as count FROM audit_log WHERE action = 'REGISTRATION_ATTEMPT' AND ip_address = ? AND created_at > ?");
+ $stmt->bind_param('ss', $ip, $cutoffTime);
+ $stmt->execute();
+ $stmt->bind_result($regAttempts);
+ $stmt->fetch();
+ $stmt->close();
+
+ // Allow max 5 registration attempts per IP per hour
+ if ($regAttempts >= 5) {
+ auditLog(null, 'REGISTRATION_RATE_LIMIT_EXCEEDED', 'users', null, ['ip' => $ip, 'attempts' => $regAttempts]);
+ echo json_encode(['status' => 'error', 'message' => 'Too many registration attempts. Please try again later.']);
+ exit();
+ }
+
+ // Validate and sanitize first name
+ $first_name = validateName($_POST['first_name'] ?? '');
+ if ($first_name === false) {
+ auditLog(null, 'REGISTRATION_INVALID_FIRST_NAME', 'users');
+ echo json_encode(['status' => 'error', 'message' => 'Invalid first name. Only letters, spaces, hyphens, and apostrophes allowed (2-100 characters).']);
+ exit();
+ }
+
+ // Validate and sanitize last name
+ $last_name = validateName($_POST['last_name'] ?? '');
+ if ($last_name === false) {
+ auditLog(null, 'REGISTRATION_INVALID_LAST_NAME', 'users');
+ echo json_encode(['status' => 'error', 'message' => 'Invalid last name. Only letters, spaces, hyphens, and apostrophes allowed (2-100 characters).']);
+ exit();
+ }
+
+ // Validate and sanitize phone number
+ $phone_number = validatePhoneNumber($_POST['phone_number'] ?? '');
+ if ($phone_number === false) {
+ auditLog(null, 'REGISTRATION_INVALID_PHONE', 'users');
+ echo json_encode(['status' => 'error', 'message' => 'Invalid phone number format.']);
+ exit();
+ }
+
+ // Validate email
+ $email = validateEmail($_POST['email'] ?? '');
+ if ($email === false) {
+ auditLog(null, 'REGISTRATION_INVALID_EMAIL', 'users');
+ echo json_encode(['status' => 'error', 'message' => 'Invalid email format.']);
+ exit();
+ }
+
+ $password = $_POST['password'] ?? '';
+ $password_confirm = $_POST['password_confirm'] ?? '';
+
+ // Validate password strength (minimum 8 characters, must contain uppercase, lowercase, number, special char)
+ if (strlen($password) < 8) {
+ echo json_encode(['status' => 'error', 'message' => 'Password must be at least 8 characters long.']);
+ exit();
+ }
+
+ if (!preg_match('/[A-Z]/', $password) || !preg_match('/[a-z]/', $password) ||
+ !preg_match('/[0-9]/', $password) || !preg_match('/[!@#$%^&*]/', $password)) {
+ echo json_encode(['status' => 'error', 'message' => 'Password must contain uppercase, lowercase, number, and special character (!@#$%^&*).']);
+ exit();
+ }
+
+ if ($password !== $password_confirm) {
+ echo json_encode(['status' => 'error', 'message' => 'Passwords do not match.']);
+ exit();
+ }
+
+ // Check if the email is already registered
+ $stmt = $conn->prepare('SELECT user_id FROM users WHERE email = ?');
+ $stmt->bind_param('s', $email);
+ $stmt->execute();
+ $stmt->store_result();
+
+ if ($stmt->num_rows > 0) {
+ auditLog(null, 'REGISTRATION_EMAIL_EXISTS', 'users', null, ['email' => $email]);
+ echo json_encode(['status' => 'error', 'message' => 'Email is already registered.']);
+ $stmt->close();
+ $conn->close();
+ exit();
+ }
+
+ $stmt->close();
+
+ // Hash password
+ $hashed_password = password_hash($password, PASSWORD_BCRYPT);
+
+ // Generate email verification token
+ $token = bin2hex(random_bytes(50));
+
+ // Prepare and execute query
+ $stmt = $conn->prepare('INSERT INTO users (first_name, last_name, phone_number, email, password, token, is_verified, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
+ $is_verified = 0; // Not verified
+ $type = 'credentials';
+ $stmt->bind_param('ssssssis', $first_name, $last_name, $phone_number, $email, $hashed_password, $token, $is_verified, $type);
+
+ if ($stmt->execute()) {
+ $newUser_id = $conn->insert_id;
+ processLegacyMembership($newUser_id);
+ auditLog($newUser_id, 'USER_REGISTRATION', 'users', $newUser_id, ['email' => $email]);
+
+ if (sendVerificationEmail($email, $first_name . ' ' . $last_name, $token)) {
+ sendEmail($_ENV['ADMIN_EMAIL'], '4WDCSA: New User Registration', $first_name . ' ' . $last_name . ' (' . $email . ') has just created an account using Credentials.');
+ echo json_encode(['status' => 'success', 'message' => 'Registration successful. Please check your email to verify your account.']);
+ } else {
+ echo json_encode(['status' => 'error', 'message' => 'Failed to send verification email.']);
+ }
+ } else {
+ auditLog(null, 'REGISTRATION_DATABASE_ERROR', 'users', null, ['email' => $email]);
+ echo json_encode(['status' => 'error', 'message' => 'Failed to register user.']);
+ }
+
+ $stmt->close();
+}
+
+$conn->close();
+
diff --git a/src/processors/send_reset_link.php b/src/processors/send_reset_link.php
new file mode 100644
index 00000000..6b6f460d
--- /dev/null
+++ b/src/processors/send_reset_link.php
@@ -0,0 +1,49 @@
+ 'error', 'message' => 'Something went wrong');
+
+if (isset($_POST['email'])) {
+ $email = $_POST['email'];
+
+ // Check if the email exists
+ $sql = "SELECT user_id FROM users WHERE email = ?";
+ $stmt = $conn->prepare($sql);
+ $stmt->bind_param("s", $email);
+ $stmt->execute();
+ $result = $stmt->get_result();
+
+ if ($result->num_rows > 0) {
+ $user = $result->fetch_assoc();
+ $user_id = $user['user_id'];
+
+ // Generate a unique token
+ $token = bin2hex(random_bytes(50));
+
+ // Store the token and expiration time in the database
+ $expiry = date("Y-m-d H:i:s", strtotime('+3 hour')); // Token expires in 1 hour
+ $sql = "INSERT INTO password_resets (user_id, token, expires_at) VALUES (?, ?, ?)
+ ON DUPLICATE KEY UPDATE token = VALUES(token), expires_at = VALUES(expires_at)";
+ $stmt = $conn->prepare($sql);
+ $stmt->bind_param("iss", $user_id, $token, $expiry);
+ $stmt->execute();
+
+ // Send the reset link to the user
+ $reset_link = "https://www.4wdcsa.co.za/reset_password.php?token=$token";
+ $subject = "Password Reset Request";
+ $message = "Click the following link to reset your password: $reset_link";
+ sendEmail($email, $subject, $message);
+
+ $response['status'] = 'success';
+ $response['message'] = 'Password reset link has been sent to your email.';
+ } else {
+ $response['message'] = 'Email not found.';
+ }
+}
+
+echo json_encode($response);
+?>
+
diff --git a/src/processors/submit_order.php b/src/processors/submit_order.php
new file mode 100644
index 00000000..dc7ea87d
--- /dev/null
+++ b/src/processors/submit_order.php
@@ -0,0 +1,48 @@
+ 'error', 'message' => 'Security token validation failed.']);
+ exit();
+}
+
+if (isset($_POST['tab_id']) && isset($_SESSION['cart'][$_POST['tab_id']])) {
+ $tab_id = (int) $_POST['tab_id']; // Ensure it's an integer
+ $drinks = $_SESSION['cart'][$tab_id];
+ $created_at = date('Y-m-d H:i:s');
+
+ $errors = []; // Array to store SQL errors
+
+ foreach ($drinks as $drink) {
+ $drink_id = (int) $drink['item_id']; // Ensure drink ID is an integer
+ $drink_name = $drink['item_name']; // No escaping needed with prepared statements
+ $drink_price = (float) $drink['item_price']; // Ensure price is a float
+ $user_id = (int) $drink['user_id']; // Convert to integer
+
+ // Insert each drink into the bar_transactions table using prepared statement
+ $stmt = $conn->prepare("INSERT INTO bar_transactions (user_id, tab_id, item_id, item_name, item_price) VALUES (?, ?, ?, ?, ?)");
+ $stmt->bind_param("iiisi", $user_id, $tab_id, $drink_id, $drink_name, $drink_price);
+
+ if (!$stmt->execute()) {
+ $errors[] = "Error inserting drink ID $drink_id: " . $conn->error;
+ }
+ }
+
+ if (empty($errors)) {
+ // Clear the cart for this tab after successful submission
+ unset($_SESSION['cart'][$tab_id]);
+ echo json_encode(['status' => 'success', 'message' => 'Order submitted successfully!']);
+ } else {
+ // Log all errors and return failure message
+ error_log(implode("\n", $errors)); // Log errors to the server
+ echo json_encode(['status' => 'error', 'message' => 'Some items failed to be added.', 'errors' => $errors]);
+ }
+} else {
+ echo json_encode(['status' => 'error', 'message' => 'Cart is empty or tab ID is invalid.']);
+}
+
diff --git a/src/processors/submit_pop.php b/src/processors/submit_pop.php
new file mode 100644
index 00000000..628bcf82
--- /dev/null
+++ b/src/processors/submit_pop.php
@@ -0,0 +1,215 @@
+Invalid submission: missing eft_id or file.
";
+ exit;
+ }
+
+ // Validate file using hardened validation function
+ $validationResult = validateFileUpload($_FILES['pop_file'], 'proof_of_payment');
+
+ if ($validationResult === false) {
+ echo "Invalid file. Only PDF files under 10MB are allowed.
";
+ exit;
+ }
+
+ $target_dir = "uploads/pop/";
+ $randomFilename = $validationResult['filename'];
+ $target_file = $target_dir . $randomFilename;
+
+ // Make sure target directory exists and writable
+ if (!is_dir($target_dir)) {
+ mkdir($target_dir, 0755, true);
+ }
+
+ if (!is_writable($target_dir)) {
+ echo "Unable to move uploaded file.
";
+ exit;
+ }
+}
+
+
+// Fetch bookings for dropdown
+$stmt = $conn->prepare("
+ SELECT eft_id AS id, 'booking' AS type FROM bookings WHERE user_id = ? AND status = 'AWAITING PAYMENT'
+ UNION
+ SELECT payment_id AS id, 'membership' AS type FROM membership_fees WHERE user_id = ? AND payment_status = 'PENDING'
+");
+$stmt->bind_param("ii", $user_id, $user_id);
+$stmt->execute();
+$result = $stmt->get_result();
+$items = $result->fetch_all(MYSQLI_ASSOC);
+
+
+
+
+$bannerFolder = 'assets/images/banners/';
+$bannerImages = glob($bannerFolder . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
+
+$randomBanner = 'assets/images/base4/camping.jpg'; // default fallback
+if (!empty($bannerImages)) {
+ $randomBanner = $bannerImages[array_rand($bannerImages)];
+}
+?>
+
Submit Proof of Payment
+ +To finalise your booking/membership, select the payment reference below, and then upload your PDF proof of payment.
+