diff --git a/src/processors/assets/images/pp/30f28b416b64c436a9bbc32dbe7bbe6b.png b/src/processors/assets/images/pp/30f28b416b64c436a9bbc32dbe7bbe6b.png new file mode 100644 index 00000000..7ec285db Binary files /dev/null and b/src/processors/assets/images/pp/30f28b416b64c436a9bbc32dbe7bbe6b.png differ diff --git a/src/processors/assets/images/pp/3fbcad3aa80dfa3bc0eb2043b8c40e2c.png b/src/processors/assets/images/pp/3fbcad3aa80dfa3bc0eb2043b8c40e2c.png new file mode 100644 index 00000000..7ec285db Binary files /dev/null and b/src/processors/assets/images/pp/3fbcad3aa80dfa3bc0eb2043b8c40e2c.png differ diff --git a/src/processors/assets/images/pp/890eae390ea339ca08307c847d5758a3.png b/src/processors/assets/images/pp/890eae390ea339ca08307c847d5758a3.png new file mode 100644 index 00000000..fd6f88d4 Binary files /dev/null and b/src/processors/assets/images/pp/890eae390ea339ca08307c847d5758a3.png differ diff --git a/src/processors/create_bar_tab.php b/src/processors/create_bar_tab.php new file mode 100644 index 00000000..35ffbad6 --- /dev/null +++ b/src/processors/create_bar_tab.php @@ -0,0 +1,54 @@ + 'error', 'message' => 'Security token validation failed.']); + exit(); +} + +// Check if user_id is set in the POST request +if (isset($_POST['user_id']) && !empty($_POST['user_id'])) { + // Validate user_id as integer + $user_id = intval($_POST['user_id']); + if ($user_id <= 0) { + echo json_encode(['status' => 'error', 'message' => 'Invalid user ID.']); + exit(); + } + + $created_at = date('Y-m-d H:i:s'); // Use current date and time for created_at + + // First, check if a bar tab already exists for this user_id + $stmt = $conn->prepare("SELECT * FROM bar_tabs WHERE user_id = ? LIMIT 1"); + $stmt->bind_param("i", $user_id); + $stmt->execute(); + $checkResult = $stmt->get_result(); + + if ($checkResult->num_rows > 0) { + // If a bar tab already exists for this user_id, return an error message + echo json_encode(['status' => 'error', 'message' => 'A bar tab already exists for this user.']); + } else { + // Prepare the SQL query to insert a new record into the bar_tabs table + $stmt = $conn->prepare("INSERT INTO bar_tabs (user_id) VALUES (?)"); + $stmt->bind_param("i", $user_id); + + // Execute the query + if ($stmt->execute()) { + // If the insertion is successful, return a success message + echo json_encode(['status' => 'success', 'message' => 'Bar tab created successfully.']); + } else { + // If there's an error, return an error message + echo json_encode(['status' => 'error', 'message' => 'Error: ' . $conn->error]); + } + } +} else { + // If user_id is not provided, return an error message + echo json_encode(['status' => 'error', 'message' => 'User ID is required.']); +} +?> + + diff --git a/src/processors/logout.php b/src/processors/logout.php new file mode 100644 index 00000000..bb8c3707 --- /dev/null +++ b/src/processors/logout.php @@ -0,0 +1,14 @@ + diff --git a/src/processors/process_application.php b/src/processors/process_application.php new file mode 100644 index 00000000..e9f59c1b --- /dev/null +++ b/src/processors/process_application.php @@ -0,0 +1,202 @@ + 'process_application.php']); + http_response_code(403); + die('Security token validation failed. Please try again.'); + } + + // Get all the form fields with validation + $first_name = validateName($_POST['first_name'] ?? ''); + if ($first_name === false) { + die('Invalid first name format.'); + } + + $last_name = validateName($_POST['last_name'] ?? ''); + if ($last_name === false) { + die('Invalid last name format.'); + } + + $id_number = validateSAIDNumber($_POST['id_number'] ?? ''); + if ($id_number === false) { + die('Invalid ID number format.'); + } + + $dob = validateDate($_POST['dob'] ?? ''); + if ($dob === false) { + die('Invalid date of birth format.'); + } + + $occupation = sanitizeTextInput($_POST['occupation'] ?? '', 100); + $tel_cell = validatePhoneNumber($_POST['tel_cell'] ?? ''); + if ($tel_cell === false) { + die('Invalid phone number format.'); + } + + $email = validateEmail($_POST['email'] ?? ''); + if ($email === false) { + die('Invalid email format.'); + } + + // Spouse or Partner details (optional) + $spouse_first_name = !empty($_POST['spouse_first_name']) ? validateName($_POST['spouse_first_name']) : null; + $spouse_last_name = !empty($_POST['spouse_last_name']) ? validateName($_POST['spouse_last_name']) : null; + $spouse_id_number = !empty($_POST['spouse_id_number']) ? validateSAIDNumber($_POST['spouse_id_number']) : null; + $spouse_dob = !empty($_POST['spouse_dob']) ? validateDate($_POST['spouse_dob']) : NULL; + $spouse_occupation = !empty($_POST['spouse_occupation']) ? sanitizeTextInput($_POST['spouse_occupation'], 100) : null; + $spouse_tel_cell = !empty($_POST['spouse_tel_cell']) ? validatePhoneNumber($_POST['spouse_tel_cell']) : null; + $spouse_email = !empty($_POST['spouse_email']) ? validateEmail($_POST['spouse_email']) : null; + + // Children details (optional) + $child_name1 = !empty($_POST['child_name1']) ? $_POST['child_name1'] : null; + $child_dob1 = !empty($_POST['child_dob1']) ? $_POST['child_dob1'] : null; + $child_name2 = !empty($_POST['child_name2']) ? $_POST['child_name2'] : null; + $child_dob2 = !empty($_POST['child_dob2']) ? $_POST['child_dob2'] : null; + $child_name3 = !empty($_POST['child_name3']) ? $_POST['child_name3'] : null; + $child_dob3 = !empty($_POST['child_dob3']) ? $_POST['child_dob3'] : null; + + // Address and other details + $physical_address = $_POST['physical_address']; + $postal_address = $_POST['postal_address']; + $interests_hobbies = $_POST['interests_hobbies']; + + // Primary vehicle details + $vehicle_make = $_POST['vehicle_make']; + $vehicle_model = $_POST['vehicle_model']; + $vehicle_year = $_POST['vehicle_year']; + $vehicle_registration = $_POST['vehicle_registration']; + + // Secondary vehicle details (optional) + $secondary_vehicle_make = !empty($_POST['secondary_vehicle_make']) ? $_POST['secondary_vehicle_make'] : null; + $secondary_vehicle_model = !empty($_POST['secondary_vehicle_model']) ? $_POST['secondary_vehicle_model'] : null; + $secondary_vehicle_year = !empty($_POST['secondary_vehicle_year']) ? $_POST['secondary_vehicle_year'] : null; + $secondary_vehicle_registration = !empty($_POST['secondary_vehicle_registration']) ? $_POST['secondary_vehicle_registration'] : null; + + // Start a transaction to ensure data consistency + $conn->begin_transaction(); + + try { + // Insert into the member application table + $stmt = $conn->prepare("INSERT INTO membership_application ( + user_id, first_name, last_name, id_number, dob, occupation, tel_cell, email, + spouse_first_name, spouse_last_name, spouse_id_number, spouse_dob, spouse_occupation, spouse_tel_cell, spouse_email, + child_name1, child_dob1, child_name2, child_dob2, child_name3, child_dob3, + physical_address, postal_address, interests_hobbies, vehicle_make, vehicle_model, vehicle_year, vehicle_registration, + secondary_vehicle_make, secondary_vehicle_model, secondary_vehicle_year, secondary_vehicle_registration + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + + // Check if preparation was successful + if (!$stmt) { + die("SQL error: " . $conn->error); + } + + $stmt->bind_param( + "isssssssssssssssssssssssssssssss", + $user_id, + $first_name, + $last_name, + $id_number, + $dob, + $occupation, + $tel_cell, + $email, + $spouse_first_name, + $spouse_last_name, + $spouse_id_number, + $spouse_dob, + $spouse_occupation, + $spouse_tel_cell, + $spouse_email, + $child_name1, + $child_dob1, + $child_name2, + $child_dob2, + $child_name3, + $child_dob3, + $physical_address, + $postal_address, + $interests_hobbies, + $vehicle_make, + $vehicle_model, + $vehicle_year, + $vehicle_registration, + $secondary_vehicle_make, + $secondary_vehicle_model, + $secondary_vehicle_year, + $secondary_vehicle_registration + ); + + if ($stmt->execute()) { + // Insert into the membership fees table + $payment_amount = calculateProrata(210); // Assuming a fixed membership fee, adjust as needed + $payment_date = date('Y-m-d'); + $membership_start_date = $payment_date; + // $membership_end_date = date('Y-12-31'); + + // Get today's date + $today = new DateTime(); + + // Determine the target February + if ($today->format('n') > 2) { + // If we're past February, target is next year's Feb 28/29 + $year = $today->format('Y') + 1; + } else { + // Otherwise, this year's February + $year = $today->format('Y'); + } + + // Handle leap year (Feb 29) automatically + $membership_end_date = (new DateTime("$year-02-01")) + ->modify('last day of this month') + ->format('Y-m-d'); + + $stmt = $conn->prepare("INSERT INTO membership_fees (user_id, payment_amount, payment_date, membership_start_date, membership_end_date, payment_status, payment_id) + VALUES (?, ?, ?, ?, ?, 'PENDING', ?)"); + $stmt->bind_param("idssss", $user_id, $payment_amount, $payment_date, $membership_start_date, $membership_end_date, $eft_id); + + if ($stmt->execute()) { + // Commit the transaction + $conn->commit(); + addSubsEFT($eft_id, $user_id, $status, $payment_amount, $description); + sendInvoice(getEmail($user_id), getFullName($user_id), $eft_id, formatCurrency($payment_amount), $description); + sendAdminNotification('4WDCSA.co.za - New Membership Application - '.$last_name , 'A new member has signed up, '.$first_name.' '.$last_name); + header("Location: indemnity"); + // Success message + $response = [ + 'status' => 'success', + 'message' => 'Your membership application has been submitted successfully!' + ]; + } else { + throw new Exception("Failed to insert membership fee. SQL error: " . $conn->error); + } + } else { + throw new Exception("Failed to insert member application.SQL error: " . $conn->error); + } + } catch (Exception $e) { + // Rollback the transaction in case of error + $conn->rollback(); + + // Error response + $response = [ + 'status' => 'error', + 'message' => 'Error: ' . $e->getMessage() + ]; + } + + // Return the response in JSON format + echo json_encode($response); +} +?> + diff --git a/src/processors/process_booking.php b/src/processors/process_booking.php new file mode 100644 index 00000000..fb9d9c13 --- /dev/null +++ b/src/processors/process_booking.php @@ -0,0 +1,95 @@ + 'process_booking.php']); + echo json_encode(['status' => 'error', 'message' => 'Security token validation failed. Please try again.']); + exit(); + } + + // Validate dates and integers + $from_date = validateDate($_POST['from_date'] ?? ''); + if ($from_date === false) { + echo json_encode(['status' => 'error', 'message' => 'Invalid from date format.']); + exit(); + } + + $to_date = validateDate($_POST['to_date'] ?? ''); + if ($to_date === false) { + echo json_encode(['status' => 'error', 'message' => 'Invalid to date format.']); + exit(); + } + + $num_vehicles = validateInteger($_POST['vehicles'] ?? 0, 1, 10); + if ($num_vehicles === false) { + echo json_encode(['status' => 'error', 'message' => 'Invalid number of vehicles.']); + exit(); + } + + $num_adults = validateInteger($_POST['adults'] ?? 0, 0, 20); + if ($num_adults === false) { + echo json_encode(['status' => 'error', 'message' => 'Invalid number of adults.']); + exit(); + } + + $num_children = validateInteger($_POST['children'] ?? 0, 0, 20); + if ($num_children === false) { + echo json_encode(['status' => 'error', 'message' => 'Invalid number of children.']); + exit(); + } + + // Get values from the form + $add_firewood = isset($_POST['AddExtra']) ? 1 : 0; // Checkbox for extras + $is_member = isset($_POST['is_member']) ? (int)$_POST['is_member'] : 0; // Hidden member status + $type = "camping"; + + // Calculate the total number of nights + $date1 = new DateTime($from_date); + $date2 = new DateTime($to_date); + $nights = $date2->diff($date1)->days; + + // Determine rate per night + $rate_per_night = ($is_member) ? 0 : 200; // Free for members, R200 for non-members + + // Calculate the total cost + $vehicle_cost = $rate_per_night * $num_vehicles * $nights; + $firewood_cost = $add_firewood ? 50 : 0; + $total_amount = $vehicle_cost + $firewood_cost; + + // Calculate discount if the user is a member + $discount_amount = ($is_member) ? $vehicle_cost : 0; + + // Insert booking into the database + $sql = "INSERT INTO bookings (booking_type, user_id, from_date, to_date, num_vehicles, num_adults, num_children, add_firewood, total_amount, discount_amount) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + $stmt = $conn->prepare($sql); + $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()) { + // Redirect to success page or display success message + echo ""; + } else { + // Handle error if insert fails + echo ""; + } + + $stmt->close(); + $conn->close(); +} else { + echo "Invalid request."; +} +?> + diff --git a/src/processors/process_camp_booking.php b/src/processors/process_camp_booking.php new file mode 100644 index 00000000..ad0b5e33 --- /dev/null +++ b/src/processors/process_camp_booking.php @@ -0,0 +1,146 @@ +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_camp_booking.php']); + echo json_encode(['status' => 'error', 'message' => 'Security token validation failed. Please try again.']); + exit(); + } + + // Validate dates and integers + $from_date = validateDate($_POST['from_date'] ?? ''); + if ($from_date === false) { + echo json_encode(['status' => 'error', 'message' => 'Invalid from date format.']); + exit(); + } + + $to_date = validateDate($_POST['to_date'] ?? ''); + if ($to_date === false) { + echo json_encode(['status' => 'error', 'message' => 'Invalid to date format.']); + exit(); + } + + $num_vehicles = validateInteger($_POST['vehicles'] ?? 1, 1, 10); + if ($num_vehicles === false) { + echo json_encode(['status' => 'error', 'message' => 'Invalid number of vehicles.']); + exit(); + } + + $num_adults = validateInteger($_POST['adults'] ?? 0, 0, 20); + if ($num_adults === false) { + echo json_encode(['status' => 'error', 'message' => 'Invalid number of adults.']); + exit(); + } + + $num_children = validateInteger($_POST['children'] ?? 0, 0, 20); + if ($num_children === false) { + echo json_encode(['status' => 'error', 'message' => 'Invalid number of children.']); + exit(); + } + + // Get values from the form + $add_firewood = isset($_POST['AddExtra']) ? 1 : 0; // Checkbox for extras + // $is_member = isset($_POST['is_member']) ? (int)$_POST['is_member'] : 0; // Hidden member status + $type = "camping"; + + // Calculate the total number of nights + $date1 = new DateTime($from_date); + $date2 = new DateTime($to_date); + $nights = $date2->diff($date1)->days; + + // Validate date range + if ($nights <= 0) { + echo ""; + exit(); + } + + // Determine rate per night + $rate_per_night = 200; // Free for members, R200 for non-members + + // Calculate the total cost + $vehicle_cost = $rate_per_night * $num_vehicles * $nights; + $total_discount = $is_member ? $vehicle_cost : 0; + $firewood_cost = $add_firewood ? 50 : 0; + $total_amount = $vehicle_cost + $firewood_cost; + $payment_amount = $total_amount - $total_discount; + $status = "AWAITING PAYMENT"; + $description = "BASE4 Camping"; + + $payment_id = uniqid(); + $eft_id = strtoupper($trip_code." ".getLastName($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, add_firewood, total_amount, discount_amount, status, payment_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + $stmt = $conn->prepare($sql); + $stmt->bind_param('sissiiiiddss', $type, $user_id, $from_date, $to_date, $num_vehicles, $num_adults, $num_children, $add_firewood, $total_amount, $total_discount, $status, $payment_id); + + if ($stmt->execute()) { + $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); + header("Location: payment_confirmation?booking_id=".$booking_id); + 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/process_course_booking.php b/src/processors/process_course_booking.php new file mode 100644 index 00000000..098abab0 --- /dev/null +++ b/src/processors/process_course_booking.php @@ -0,0 +1,145 @@ +alert('User is not logged in. Please log in to make a booking.'); window.location.href = 'login.php';"; + exit(); +} +$is_member = getUserMemberStatus($user_id); +$pending_member = getUserMemberStatusPending($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_course_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) + $additional_members = validateInteger($_POST['members'] ?? 0, 0, 20); + if ($additional_members === false) $additional_members = 0; + + $num_adults = validateInteger($_POST['non-members'] ?? 0, 0, 20); + if ($num_adults === false) $num_adults = 0; + + $course_id = validateInteger($_POST['course_id'] ?? 0, 1, 999999); + if ($course_id === false) { + http_response_code(400); + header('Content-Type: application/json'); + echo json_encode(['error' => 'Invalid course ID.']); + exit(); + } + + checkAndRedirectCourseBooking($course_id); + // Fetch trip costs from the database + $query = "SELECT date, cost_members, cost_nonmembers, course_type FROM courses WHERE course_id = ?"; + $stmt = $conn->prepare($query); + $stmt->bind_param('i', $course_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 + $course = $result->fetch_assoc(); + $type = $course['course_type']; + $date = $course['date']; + $cost_members = intval($course['cost_members']); + $cost_nonmembers = intval($course['cost_nonmembers']); + + if ($type === "driver_training") { + $description = "Basic 4X4 Driver Training Course " . $date; + } elseif ($type === "bush_mechanics") { + $description = "Bush Mechanics Course " . $date; + } elseif ($type === "rescue_recovery") { + $description = "Rescue & Recovery Training Course " . $date; + } else { + $description = "General Course " . $date; // Default fallback description + } + + // Initialize total and discount amount + $total = 0; + + // Calculate total based on membership + if ($is_member || $pending_member) { + $num_members = 1 + $additional_members; + $total = ($num_members * $cost_members) + ($num_adults * $cost_nonmembers); + $payment_amount = $total; + } else { + $num_members = 0; + $total = (($cost_nonmembers) + ($num_adults * $cost_nonmembers)); + $payment_amount = $total; + $num_adults = $num_adults + 1; + } + + $status = "AWAITING PAYMENT"; + $type = 'course'; + $payment_id = uniqid(); + $num_vehicles = 1; + $discountAmount = 0; + $eft_id = strtoupper("COURSE ".date("m-d", strtotime($date))." ".getInitialSurname($user_id)); + $notes = ""; + if ($pending_member){ + $notes = "Membership Payment pending at time of booking. Please confirm payment has been received."; + } + + + // Insert booking into the database + $sql = "INSERT INTO bookings (booking_type, user_id, from_date, to_date, num_vehicles, num_adults, total_amount, discount_amount, status, payment_id, course_id, course_non_members, eft_id, notes) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + $stmt = $conn->prepare($sql); + + if (!$stmt) { + die("Preparation failed: " . $conn->error); + } + + $stmt->bind_param('sissiiddssiiss', $type, $user_id, $date, $date, $num_vehicles, $num_members, $total, $discountAmount, $status, $payment_id, $course_id, $num_adults, $eft_id, $notes); + + if ($stmt->execute()) { + $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); + sendInvoice(getEmail($user_id), getFullName($user_id), $eft_id, formatCurrency($payment_amount), $description); + 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 + } + } 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/process_eft.php b/src/processors/process_eft.php new file mode 100644 index 00000000..f57cf5fd --- /dev/null +++ b/src/processors/process_eft.php @@ -0,0 +1,99 @@ + 'process_eft.php']); + http_response_code(403); + die('Security token validation failed.'); + } +} + +if (!isset($_GET['token']) || empty($_GET['token'])) { + die("Invalid request."); +} + +$token = $_GET['token']; +// echo $token; +$eft_id = decryptData($token, $salt); +$user = getUserIdFromEFT($eft_id); + +// echo $eft_id; +// Start transaction for atomicity +$conn->begin_transaction(); + +try { + // Update the efts table to set status = 'PAID' + $updateEFT = "UPDATE efts SET status = 'PAID' WHERE eft_id = ?"; + $stmt = $conn->prepare($updateEFT); + if (!$stmt) { + throw new Exception("Prepare failed: " . $conn->error); + } + + $stmt->bind_param("s", $eft_id); + if (!$stmt->execute()) { + throw new Exception("EFT update failed: " . $stmt->error); + } + $stmt->close(); + + // Retrieve the booking_id from efts table + $getBooking = "SELECT booking_id FROM efts WHERE eft_id = ?"; + $stmt = $conn->prepare($getBooking); + if (!$stmt) { + throw new Exception("Prepare failed: " . $conn->error); + } + + $stmt->bind_param("s", $eft_id); + $stmt->execute(); + $stmt->bind_result($booking_id); + $stmt->fetch(); + $stmt->close(); + + if (!empty($booking_id)) { + // Update the bookings table if booking_id exists + $updateBooking = "UPDATE bookings SET status = 'PAID' WHERE booking_id = ?"; + $stmt = $conn->prepare($updateBooking); + if (!$stmt) { + throw new Exception("Prepare failed: " . $conn->error); + } + + $stmt->bind_param("i", $booking_id); + if (!$stmt->execute()) { + throw new Exception("Booking update failed: " . $stmt->error); + } + } else { + // If no booking_id is found, update membership_fees instead + $updateMembership = "UPDATE membership_fees SET payment_status = 'PAID' WHERE payment_id = ?"; + $stmt = $conn->prepare($updateMembership); + if (!$stmt) { + throw new Exception("Prepare failed: " . $conn->error); + } + + $stmt->bind_param("s", $eft_id); + if (!$stmt->execute()) { + throw new Exception("Membership fee update failed: " . $stmt->error); + } + } + $stmt->close(); + + // Commit transaction if everything was successful + $conn->commit(); + sendPaymentConfirmation(getEmail($user), getFullName($user), getEftDescription($eft_id)); + header("Location: admin_efts"); + exit(); // Ensure no further code is executed after the redirect +} catch (Exception $e) { + // Rollback transaction if an error occurs + $conn->rollback(); + echo "Error: " . $e->getMessage(); +} + + +// Close database connection +$conn->close(); + diff --git a/src/processors/process_membership_payment.php b/src/processors/process_membership_payment.php new file mode 100644 index 00000000..cede548e --- /dev/null +++ b/src/processors/process_membership_payment.php @@ -0,0 +1,78 @@ +alert('User is not logged in. Please log in to make a booking.'); window.location.href = 'login.php';"; + exit(); +} +$is_member = getUserMemberStatus($user_id); + +$query = "SELECT payment_amount, payment_status, membership_end_date FROM membership_fees WHERE user_id = ?"; +$stmt = $conn->prepare($query); +$stmt->bind_param('i', $user_id); +$stmt->execute(); +$result = $stmt->get_result(); + +// Check if trip exists +if ($result->num_rows === 0) { + $response = ['error' => 'Application Fee not found.']; + header('Content-Type: application/json'); + echo json_encode($response); + exit(); +} + +// Fetch trip details +$fee = $result->fetch_assoc(); +$payment_status = $fee['payment_status']; +$membership_end_date = $fee['membership_end_date']; +$payment_amount = intval($fee['payment_amount']); + +$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."); + } + + $stmt->close(); + $conn->close(); +} else { + throw new Exception("Failed to prepare statement for membership_fees table: " . $conn->error); +} + +// Get the current date +$current_date = new DateTime(); + +// Convert $membership_end_date to a DateTime object +$membership_end_date_obj = DateTime::createFromFormat('Y-m-d', $membership_end_date); + +// 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") +) { + + // 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 +} + diff --git a/src/processors/process_payments.php b/src/processors/process_payments.php new file mode 100644 index 00000000..a06d43f6 --- /dev/null +++ b/src/processors/process_payments.php @@ -0,0 +1,152 @@ + + + + +
+ +
+ +
+
+ + +
+
+
+ +
+ +
+ + × +
+ + + 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 ' +
+
+ +

View Full PDF

+ +
+
+
' . 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 "
Upload directory is not writable: $target_dir
"; + exit; + } + + if (move_uploaded_file($_FILES['pop_file']['tmp_name'], $target_file)) { + chmod($target_file, 0644); + + // Update EFT and booking status + $payment_type = $_POST['payment_type'] ?? 'booking'; + + if ($payment_type === 'membership') { + // Update EFT and booking status + $stmt1 = $conn->prepare("UPDATE efts SET status = 'PROCESSING' WHERE eft_id = ?"); + $stmt1->bind_param("s", $eft_id); + $stmt1->execute(); + $stmt1->close(); + + // Update membership fee status + $stmt = $conn->prepare("UPDATE membership_fees SET payment_status = 'PROCESSING' WHERE payment_id = ?"); + $stmt->bind_param("s", $eft_id); + $stmt->execute(); + $stmt->close(); + } else { + // Update EFT and booking status + $stmt1 = $conn->prepare("UPDATE efts SET status = 'PROCESSING' WHERE eft_id = ?"); + $stmt1->bind_param("s", $eft_id); + $stmt1->execute(); + $stmt1->close(); + + $stmt2 = $conn->prepare("UPDATE bookings SET status = 'PROCESSING' WHERE eft_id = ?"); + $stmt2->bind_param("s", $eft_id); + $stmt2->execute(); + $stmt2->close(); + } + + // Send notification email using sendPOP() + $fullname = getFullName($user_id); + $eftDetails = getEFTDetails($eft_id); + + if ($eftDetails) { + $amount = "R" . number_format($eftDetails['amount'], 2); + $description = $eftDetails['description']; + } else { + $amount = "R0.00"; + $description = "Payment"; + } + + if (sendPOP($fullname, $randomFilename, $amount, $description)) { + $_SESSION['message'] = "Thank you! Your payment proof has been uploaded and notification sent."; + } else { + $_SESSION['message'] = "Payment uploaded, but notification email could not be sent."; + } + + // Log the action + auditLog($user_id, 'POP_UPLOAD', 'efts', $eft_id, ['filename' => $randomFilename, 'payment_type' => $payment_type]); + + header("Location: bookings"); + exit; + + } else { + 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.

+
+ 0) {?> + +
+ +
+
    +
  • + Select Payment Reference: + + +
  • +
+
  • + +
  • +
    +
    + +
    + +
    + +
    +
    +
    +
    +
    + + + + diff --git a/src/processors/update_application.php b/src/processors/update_application.php new file mode 100644 index 00000000..2373e7b1 --- /dev/null +++ b/src/processors/update_application.php @@ -0,0 +1,129 @@ +begin_transaction(); + + try { + // Prepare the SQL update statement + $stmt = $conn->prepare("UPDATE membership_application SET + first_name = ?, last_name = ?, id_number = ?, dob = ?, occupation = ?, tel_cell = ?, email = ?, + spouse_first_name = ?, spouse_last_name = ?, spouse_id_number = ?, spouse_dob = ?, spouse_occupation = ?, spouse_tel_cell = ?, spouse_email = ?, + child_name1 = ?, child_dob1 = ?, child_name2 = ?, child_dob2 = ?, child_name3 = ?, child_dob3 = ?, + physical_address = ?, postal_address = ?, interests_hobbies = ?, vehicle_make = ?, vehicle_model = ?, vehicle_year = ?, vehicle_registration = ?, + secondary_vehicle_make = ?, secondary_vehicle_model = ?, secondary_vehicle_year = ?, secondary_vehicle_registration = ? + WHERE user_id = ?"); + + // Check if preparation was successful + if (!$stmt) { + die("SQL error: " . $conn->error); + } + + $stmt->bind_param( + "sssssssssssssssssssssssssssssssi", + $first_name, + $last_name, + $id_number, + $dob, + $occupation, + $tel_cell, + $email, + $spouse_first_name, + $spouse_last_name, + $spouse_id_number, + $spouse_dob, + $spouse_occupation, + $spouse_tel_cell, + $spouse_email, + $child_name1, + $child_dob1, + $child_name2, + $child_dob2, + $child_name3, + $child_dob3, + $physical_address, + $postal_address, + $interests_hobbies, + $vehicle_make, + $vehicle_model, + $vehicle_year, + $vehicle_registration, + $secondary_vehicle_make, + $secondary_vehicle_model, + $secondary_vehicle_year, + $secondary_vehicle_registration, + $user_id // User ID for WHERE condition + ); + + if ($stmt->execute()) { + $conn->commit(); + header("Location: membership_details"); + exit(); // Ensure no further code is executed after the redirect + } else { + throw new Exception("Failed to update member application. SQL error: " . $conn->error); + } + } catch (Exception $e) { + // Rollback the transaction in case of error + $conn->rollback(); + + // Error response + $response = [ + 'status' => 'error', + 'message' => 'Error: ' . $e->getMessage() + ]; + } +} +?> + diff --git a/src/processors/update_user.php b/src/processors/update_user.php new file mode 100644 index 00000000..7afc7e23 --- /dev/null +++ b/src/processors/update_user.php @@ -0,0 +1,42 @@ + 'error', 'message' => 'Something went wrong'); + +// Check if the user is logged in +if (!isset($_SESSION['user_id'])) { + $response['message'] = 'You are not logged in.'; + echo json_encode($response); + exit(); +} + +$user_id = $_SESSION['user_id']; + +// Handle updating user details (excluding profile picture) +if (isset($_POST['first_name'], $_POST['last_name'], $_POST['phone_number'], $_POST['email'])) { + $first_name = ucwords(strtolower($_POST['first_name'])); + $last_name = ucwords(strtolower($_POST['last_name'])); + $phone_number = $_POST['phone_number']; + $email = $_POST['email']; + + // Update user details in the database + $sql = "UPDATE users SET first_name = ?, last_name = ?, phone_number = ?, email = ? WHERE user_id = ?"; + $stmt = $conn->prepare($sql); + $stmt->bind_param("ssssi", $first_name, $last_name, $phone_number, $email, $user_id); + + if ($stmt->execute()) { + $response['status'] = 'success'; + $response['message'] = 'User details updated successfully'; + } else { + $response['message'] = 'Failed to update user details'; + } +} else { + $response['message'] = 'Invalid form submission'; +} + +echo json_encode($response); + diff --git a/src/processors/upload_profile_picture.php b/src/processors/upload_profile_picture.php new file mode 100644 index 00000000..b54bdb47 --- /dev/null +++ b/src/processors/upload_profile_picture.php @@ -0,0 +1,81 @@ + 'error', 'message' => 'Something went wrong'); + +// Check if the user is logged in +if (!isset($_SESSION['user_id'])) { + $response['message'] = 'You are not logged in.'; + echo json_encode($response); + exit(); +} + +$user_id = $_SESSION['user_id']; + +// Handle profile picture upload +if (isset($_FILES['profile_picture']) && $_FILES['profile_picture']['error'] != UPLOAD_ERR_NO_FILE) { + // Validate file using hardened validation function + $validationResult = validateFileUpload($_FILES['profile_picture'], 'profile_picture'); + + if ($validationResult === false) { + $response['message'] = 'Invalid file. Only JPG, JPEG, PNG, GIF, and WEBP images under 5MB are allowed.'; + echo json_encode($response); + exit(); + } + + // Extract validated filename + $randomFilename = $validationResult['filename']; + $target_dir = "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(); + } + + // Move the uploaded file + if (move_uploaded_file($_FILES['profile_picture']['tmp_name'], $target_file)) { + // Set secure file permissions (readable but not executable) + chmod($target_file, 0644); + + // Update the profile picture path in the database + $sql = "UPDATE users SET profile_pic = ? WHERE user_id = ?"; + $stmt = $conn->prepare($sql); + if (!$stmt) { + $response['message'] = 'Database error.'; + echo json_encode($response); + exit(); + } + + $stmt->bind_param("si", $target_file, $user_id); + if ($stmt->execute()) { + $_SESSION['profile_pic'] = $target_file; + $response['status'] = 'success'; + $response['message'] = 'Profile picture updated successfully'; + + // Log the action + auditLog($user_id, 'PROFILE_PIC_UPLOAD', 'users', $user_id, ['filename' => $randomFilename]); + } else { + $response['message'] = 'Failed to update profile picture in the database'; + } + $stmt->close(); + } else { + $response['message'] = 'Failed to move uploaded file.'; + } +} else { + $response['message'] = 'No file uploaded or file error.'; +} + +echo json_encode($response); +?> + diff --git a/src/processors/uploads/pop/c97bc77d8901921ed82978c4ed68ec95.pdf b/src/processors/uploads/pop/c97bc77d8901921ed82978c4ed68ec95.pdf new file mode 100644 index 00000000..75687d47 Binary files /dev/null and b/src/processors/uploads/pop/c97bc77d8901921ed82978c4ed68ec95.pdf differ diff --git a/src/processors/uploads/signatures/signature_155.png b/src/processors/uploads/signatures/signature_155.png new file mode 100644 index 00000000..773e000b Binary files /dev/null and b/src/processors/uploads/signatures/signature_155.png differ diff --git a/src/processors/validate_login.php b/src/processors/validate_login.php new file mode 100644 index 00000000..9182c8b4 --- /dev/null +++ b/src/processors/validate_login.php @@ -0,0 +1,223 @@ + 'error', 'message' => 'Database connection failed.']); + exit(); +} + +// Google Client Setup +$client = new Google_Client(); +$client->setClientId($_ENV['GOOGLE_CLIENT_ID']); +$client->setClientSecret($_ENV['GOOGLE_CLIENT_SECRET']); +$client->setRedirectUri($_ENV['HOST'] . '/validate_login'); +$client->addScope("email"); +$client->addScope("profile"); +// 👇 Add this to force the account picker +$client->setPrompt('select_account'); + +// Check if Google login code is set +if (isset($_GET['code'])) { + $token = $client->fetchAccessTokenWithAuthCode($_GET['code']); + if (!isset($token["error"])) { + $client->setAccessToken($token['access_token']); + $google_oauth = new Google_Service_Oauth2($client); + $google_account_info = $google_oauth->userinfo->get(); + + // Get user info from Google + $email = $google_account_info->email; + $name = $google_account_info->name; + $first_name = $google_account_info->given_name; + $last_name = $google_account_info->family_name; + $picture = $google_account_info->picture; + + // Check if the user exists in the database + $query = "SELECT * FROM users WHERE email = ?"; + $stmt = $conn->prepare($query); + $stmt->bind_param("s", $email); + $stmt->execute(); + $result = $stmt->get_result(); + + if ($result->num_rows == 0) { + // User does not exist, so register them + $password = null; // No password for Google login + $query = "INSERT INTO users (email, first_name, last_name, profile_pic, password, is_verified) VALUES (?, ?, ?, ?, ?, ?)"; + $stmt = $conn->prepare($query); + $is_verified = 1; // Assuming Google users are considered verified + $stmt->bind_param("sssssi", $email, $first_name, $last_name, $picture, $password, $is_verified); + if ($stmt->execute()) { + // User successfully registered, set session and redirect + sendEmail('chrispintoza@gmail.com', '4WDCSA: New User Login', $name.' has just created an account using Google Login.'); + $_SESSION['user_id'] = $conn->insert_id; + $_SESSION['first_name'] = $first_name; + $_SESSION['profile_pic'] = $picture; + processLegacyMembership($_SESSION['user_id']); + // echo json_encode(['status' => 'success', 'message' => 'Google login successful']); + header("Location: index"); + exit(); + } else { + // echo json_encode(['status' => 'error', 'message' => 'Failed to register user.']); + header("Location: index"); + exit(); + } + } else { + // User exists, set session and redirect + $row = $result->fetch_assoc(); + $_SESSION['user_id'] = $row['user_id']; + $_SESSION['first_name'] = $row['first_name']; + $_SESSION['profile_pic'] = $row['profile_pic']; + sendEmail('chrispintoza@gmail.com', '4WDCSA: New User Login', $name.' has just logged in using Google Login.'); + // echo json_encode(['status' => 'success', 'message' => 'Google login successful']); + header("Location: index"); + exit(); + } + + $stmt->close(); + } else { + echo "Login failed."; + exit(); + } +} + +// Check if email and password login is requested +if (isset($_POST['email']) && isset($_POST['password'])) { + // CSRF Token Validation + if (!isset($_POST['csrf_token']) || !validateCSRFToken($_POST['csrf_token'])) { + auditLog(null, 'CSRF_VALIDATION_FAILED', 'users', null, ['endpoint' => 'validate_login.php']); + echo json_encode(['status' => 'error', 'message' => 'Security token validation failed. Please try again.']); + exit(); + } + + // Retrieve and validate email input + $email = validateEmail($_POST['email']); + if ($email === false) { + auditLog(null, 'INVALID_EMAIL_FORMAT', 'users', null, ['email' => $_POST['email']]); + echo json_encode(['status' => 'error', 'message' => 'Invalid email format.']); + exit(); + } + + // Retrieve and sanitize password + $password = isset($_POST['password']) ? trim($_POST['password']) : ''; + + // Basic validation + if (empty($email) || empty($password)) { + echo json_encode(['status' => 'error', 'message' => 'Please enter both email and password.']); + exit(); + } + + // Check for account lockout + $lockoutStatus = checkAccountLockout($email); + if ($lockoutStatus['is_locked']) { + auditLog(null, 'LOGIN_ATTEMPT_LOCKED_ACCOUNT', 'users', null, [ + 'email' => $email, + 'locked_until' => $lockoutStatus['locked_until'] + ]); + echo json_encode([ + 'status' => 'error', + 'message' => 'Account is temporarily locked due to multiple failed login attempts. Please try again in ' . $lockoutStatus['minutes_remaining'] . ' minutes.' + ]); + exit(); + } + + // Check recent failed attempts + $recentFailedAttempts = countRecentFailedAttempts($email); + if ($recentFailedAttempts >= 5) { + // Lock account for 15 minutes + lockAccount($email, 15); + auditLog(null, 'ACCOUNT_LOCKED_THRESHOLD', 'users', null, [ + 'email' => $email, + 'failed_attempts' => $recentFailedAttempts + ]); + echo json_encode([ + 'status' => 'error', + 'message' => 'Account locked due to multiple failed login attempts. Please try again in 15 minutes.' + ]); + exit(); + } + + // Prepare SQL statement to fetch user details + $query = "SELECT * FROM users WHERE email = ?"; + $stmt = $conn->prepare($query); + + if (!$stmt) { + echo json_encode(['status' => 'error', 'message' => 'Database query preparation failed.']); + exit(); + } + + $stmt->bind_param("s", $email); + $stmt->execute(); + $result = $stmt->get_result(); + + // Check if user exists and verify password + if ($result->num_rows == 1) { + $row = $result->fetch_assoc(); + + // Check if the user is verified + if ($row['is_verified'] == 0) { + recordLoginAttempt($email, false); + auditLog(null, 'LOGIN_ATTEMPT_UNVERIFIED_ACCOUNT', 'users', $row['user_id']); + echo json_encode(['status' => 'error', 'message' => 'Your account is not verified. Please check your email for the verification link.']); + exit(); + } + + if (password_verify($password, $row['password'])) { + // Record successful attempt + recordLoginAttempt($email, true); + + // Regenerate session ID to prevent session fixation attacks + session_regenerate_id(true); + + // Password is correct, set up session + $_SESSION['user_id'] = $row['user_id']; + $_SESSION['first_name'] = $row['first_name']; + $_SESSION['profile_pic'] = $row['profile_pic']; + + // Set session timeout (30 minutes) + $_SESSION['login_time'] = time(); + $_SESSION['session_timeout'] = 1800; // 30 minutes in seconds + + auditLog($row['user_id'], 'LOGIN_SUCCESS', 'users', $row['user_id']); + echo json_encode(['status' => 'success', 'message' => 'Successful Login']); + } else { + // Password is incorrect - record failed attempt + recordLoginAttempt($email, false); + auditLog(null, 'LOGIN_FAILED_INVALID_PASSWORD', 'users', null, ['email' => $email]); + + // Check if this was the threshold failure + $newFailureCount = countRecentFailedAttempts($email); + if ($newFailureCount >= 5) { + lockAccount($email, 15); + echo json_encode([ + 'status' => 'error', + 'message' => 'Too many failed login attempts. Account locked for 15 minutes.' + ]); + } else { + $attemptsRemaining = 5 - $newFailureCount; + echo json_encode([ + 'status' => 'error', + 'message' => 'Invalid password. ' . $attemptsRemaining . ' attempts remaining before account lockout.' + ]); + } + } + } else { + // User does not exist - still record attempt + recordLoginAttempt($email, false); + auditLog(null, 'LOGIN_FAILED_USER_NOT_FOUND', 'users', null, ['email' => $email]); + echo json_encode(['status' => 'error', 'message' => 'User with that email does not exist.']); + } + + // Close the statement and connection + $stmt->close(); +} + +// Close connection +$conn->close(); +exit(); +?> +