Online Car Rental Service in Html Css and JavaScript with PHP and My SQL

Introduction to the Project

The Online Car Rental System is a comprehensive, full-stack web application designed to revolutionize the vehicle rental industry. This platform connects car owners and rental agencies with customers looking for temporary vehicle access. Whether for personal travel, business trips, or special occasions, the system provides a seamless experience for browsing, booking, and managing car rentals.

This application features role-based access control with four distinct user types: Admin, Agency Owners, Customers, and Drivers. The system includes advanced features like real-time availability, GPS tracking, digital contracts, payment processing, and review systems.

Key Features

Admin Features

  • Dashboard Overview: Comprehensive analytics on rentals, revenue, fleet utilization
  • Agency Management: Approve/verify new rental agencies, manage commissions
  • Customer Management: View and manage customer profiles and rental history
  • Fleet Overview: Monitor all vehicles across agencies
  • Financial Reports: Generate revenue reports, track payments
  • Dispute Resolution: Handle customer-agency disputes
  • System Settings: Configure rental policies, pricing rules, and fees
  • User Management: Manage all user accounts and permissions

Agency Owner Features

  • Fleet Management: Add, edit, and manage vehicles with photos and specifications
  • Pricing Management: Set dynamic pricing based on season, demand, and duration
  • Booking Management: View and manage all bookings, confirm or cancel reservations
  • Availability Calendar: Real-time calendar showing vehicle availability
  • Customer Communication: Message customers directly
  • Maintenance Tracking: Schedule and track vehicle maintenance
  • Earnings Dashboard: Track revenue, payouts, and pending payments
  • Promotions: Create discount codes and special offers
  • Driver Management: Assign drivers for chauffeur services
  • Insurance Management: Track insurance policies and claims

Customer Features

  • Vehicle Search: Search by location, dates, vehicle type, price range
  • Advanced Filters: Filter by fuel type, transmission, seating capacity, features
  • Compare Vehicles: Side-by-side comparison of multiple vehicles
  • Booking Process: Easy step-by-step booking with instant confirmation
  • Payment Options: Multiple payment methods including cards, digital wallets
  • Rental History: View past and upcoming rentals
  • Reviews & Ratings: Rate vehicles and agencies after rental
  • Favorites: Save favorite vehicles for quick booking
  • Digital Contract: Sign rental agreements digitally
  • Insurance Options: Choose from various insurance packages
  • GPS Tracking: Track rented vehicle location (with consent)
  • Customer Support: 24/7 chat and support system

Driver Features (Chauffeur Services)

  • Schedule Management: View assigned bookings and routes
  • Trip Details: Access customer information and pickup locations
  • Navigation Integration: Integrated maps and directions
  • Trip Status: Update trip progress (departed, arrived, completed)
  • Earnings Tracking: Track trips and earnings
  • Vehicle Inspection: Report vehicle issues before/after trips
  • Customer Communication: Contact customers for pickup coordination

General Features

  • Real-time Availability: Instant availability checking
  • Secure Payments: Integrated payment gateway with multiple options
  • Digital Contracts: E-signature integration for rental agreements
  • Email/SMS Notifications: Booking confirmations, reminders, updates
  • Review System: Rate and review vehicles and agencies
  • Location-based Search: Find vehicles near specific locations
  • Price Calculator: Instant price calculation with all fees
  • Multi-language Support: Interface in multiple languages
  • Responsive Design: Works on desktop, tablet, and mobile
  • Dark Mode: Optional dark theme for better user experience

Technology Stack

  • Frontend: HTML5, CSS3, JavaScript (ES6+), Bootstrap 5
  • Backend: PHP 8.0+ (Core PHP with OOP MVC pattern)
  • Database: MySQL 8.0+
  • Additional Libraries:
  • Google Maps API for location services
  • Stripe/PayPal SDK for payments
  • Twilio for SMS notifications
  • PHPMailer for emails
  • TCPDF for PDF generation (contracts, invoices)
  • Chart.js for analytics
  • FullCalendar.js for booking calendar
  • Select2 for enhanced dropdowns
  • DataTables for advanced tables
  • Moment.js for date handling
  • Signature Pad for digital signatures

Project File Structure

car-rental-system/
│
├── assets/
│   ├── css/
│   │   ├── style.css
│   │   ├── dashboard.css
│   │   ├── booking.css
│   │   ├── responsive.css
│   │   └── dark-mode.css
│   ├── js/
│   │   ├── main.js
│   │   ├── dashboard.js
│   │   ├── booking.js
│   │   ├── search.js
│   │   ├── maps.js
│   │   ├── payment.js
│   │   ├── signature.js
│   │   └── validation.js
│   ├── images/
│   │   ├── vehicles/
│   │   ├── agencies/
│   │   ├── drivers/
│   │   └── icons/
│   └── plugins/
│       ├── fullcalendar/
│       ├── datatables/
│       ├── select2/
│       └── signature-pad/
│
├── includes/
│   ├── config.php
│   ├── Database.php
│   ├── functions.php
│   ├── auth.php
│   ├── Vehicle.php
│   ├── Booking.php
│   ├── Agency.php
│   ├── User.php
│   ├── Payment.php
│   ├── Review.php
│   ├── Contract.php
│   ├── Notification.php
│   ├── Driver.php
│   └── helpers/
│       ├── DateHelper.php
│       ├── PriceHelper.php
│       ├── LocationHelper.php
│       └── ValidationHelper.php
│
├── admin/
│   ├── dashboard.php
│   ├── manage_agencies.php
│   ├── agency_details.php
│   ├── approve_agency.php
│   ├── manage_customers.php
│   ├── manage_vehicles.php
│   ├── manage_bookings.php
│   ├── manage_drivers.php
│   ├── manage_reviews.php
│   ├── financial_reports.php
│   ├── revenue_analytics.php
│   ├── commission_settings.php
│   ├── disputes.php
│   └── system_settings.php
│
├── agency/
│   ├── dashboard.php
│   ├── vehicles.php
│   ├── add_vehicle.php
│   ├── edit_vehicle.php
│   ├── vehicle_details.php
│   ├── availability_calendar.php
│   ├── bookings.php
│   ├── booking_details.php
│   ├── manage_bookings.php
│   ├── drivers.php
│   ├── add_driver.php
│   ├── earnings.php
│   ├── payout_history.php
│   ├── promotions.php
│   ├── add_promotion.php
│   ├── maintenance.php
│   ├── schedule_maintenance.php
│   ├── reviews.php
│   ├── profile.php
│   ├── settings.php
│   └── documents.php
│
├── customer/
│   ├── dashboard.php
│   ├── search.php
│   ├── vehicle_details.php
│   ├── booking.php
│   ├── checkout.php
│   ├── payment.php
│   ├── sign_contract.php
│   ├── booking_confirmation.php
│   ├── my_bookings.php
│   ├── booking_details.php
│   ├── cancel_booking.php
│   ├── extend_booking.php
│   ├── add_review.php
│   ├── favorites.php
│   ├── profile.php
│   ├── documents.php
│   └── support.php
│
├── driver/
│   ├── dashboard.php
│   ├── assigned_trips.php
│   ├── trip_details.php
│   ├── start_trip.php
│   ├── complete_trip.php
│   ├── earnings.php
│   ├── vehicle_inspection.php
│   ├── report_issue.php
│   ├── profile.php
│   └── schedule.php
│
├── api/
│   ├── search_vehicles.php
│   ├── get_availability.php
│   ├── calculate_price.php
│   ├── book_vehicle.php
│   ├── process_payment.php
│   ├── cancel_booking.php
│   ├── extend_booking.php
│   ├── get_locations.php
│   ├── track_vehicle.php
│   └── send_message.php
│
├── includes/
│   └── notifications/
│       ├── email_templates/
│       │   ├── booking_confirmation.php
│       │   ├── reminder.php
│       │   ├── cancellation.php
│       │   ├── extension.php
│       │   └── invoice.php
│       └── sms_templates/
│
├── uploads/
│   ├── vehicles/
│   ├── agencies/
│   ├── drivers/
│   ├── customers/
│   └── contracts/
│
├── cron/
│   ├── send_reminders.php
│   ├── update_availability.php
│   ├── process_payouts.php
│   └── cleanup_old_data.php
│
├── vendor/
│
├── index.php
├── login.php
├── register.php
├── forgot_password.php
├── reset_password.php
├── logout.php
├── terms.php
├── privacy.php
├── .env
├── .gitignore
├── composer.json
├── .htaccess
└── sql/
└── database.sql

Database Schema

File: sql/database.sql

-- Create Database
CREATE DATABASE IF NOT EXISTS `car_rental_system`;
USE `car_rental_system`;
-- Users Table
CREATE TABLE `users` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` VARCHAR(20) UNIQUE NOT NULL,
`email` VARCHAR(100) UNIQUE NOT NULL,
`password` VARCHAR(255) NOT NULL,
`first_name` VARCHAR(50) NOT NULL,
`last_name` VARCHAR(50) NOT NULL,
`phone` VARCHAR(20),
`phone_country_code` VARCHAR(5),
`date_of_birth` DATE,
`gender` ENUM('male', 'female', 'other') NULL,
`address_line1` VARCHAR(255),
`address_line2` VARCHAR(255),
`city` VARCHAR(100),
`state` VARCHAR(50),
`country` VARCHAR(50),
`postal_code` VARCHAR(20),
`profile_picture` VARCHAR(255) DEFAULT 'default.png',
`id_proof_type` ENUM('passport', 'driving_license', 'national_id') NULL,
`id_proof_number` VARCHAR(50),
`id_proof_file` VARCHAR(255),
`driving_license_number` VARCHAR(50),
`driving_license_file` VARCHAR(255),
`license_expiry` DATE,
`role` ENUM('admin', 'agency', 'customer', 'driver') NOT NULL DEFAULT 'customer',
`status` ENUM('active', 'inactive', 'suspended', 'pending_verification') DEFAULT 'pending_verification',
`email_verified` BOOLEAN DEFAULT FALSE,
`phone_verified` BOOLEAN DEFAULT FALSE,
`identity_verified` BOOLEAN DEFAULT FALSE,
`verification_token` VARCHAR(255),
`reset_token` VARCHAR(255),
`reset_expires` DATETIME,
`last_login` DATETIME,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_email` (`email`),
INDEX `idx_role` (`role`),
INDEX `idx_status` (`status`)
);
-- Agencies Table
CREATE TABLE `agencies` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`agency_name` VARCHAR(255) NOT NULL,
`registration_number` VARCHAR(100),
`tax_number` VARCHAR(100),
`business_address` TEXT,
`business_city` VARCHAR(100),
`business_state` VARCHAR(50),
`business_country` VARCHAR(50),
`business_postal_code` VARCHAR(20),
`business_phone` VARCHAR(20),
`business_email` VARCHAR(100),
`website` VARCHAR(255),
`description` TEXT,
`logo` VARCHAR(255) DEFAULT 'default-agency.png',
`cover_image` VARCHAR(255),
`license_document` VARCHAR(255),
`insurance_document` VARCHAR(255),
`commission_rate` DECIMAL(5,2) DEFAULT 10.00,
`payout_method` ENUM('bank', 'paypal', 'stripe') DEFAULT 'bank',
`payout_details` TEXT,
`total_vehicles` INT DEFAULT 0,
`total_bookings` INT DEFAULT 0,
`rating` DECIMAL(3,2) DEFAULT 0.00,
`total_reviews` INT DEFAULT 0,
`is_verified` BOOLEAN DEFAULT FALSE,
`is_featured` BOOLEAN DEFAULT FALSE,
`status` ENUM('pending', 'active', 'suspended') DEFAULT 'pending',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
INDEX `idx_status` (`status`),
INDEX `idx_verified` (`is_verified`),
INDEX `idx_featured` (`is_featured`)
);
-- Vehicle Categories
CREATE TABLE `vehicle_categories` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`description` TEXT,
`icon` VARCHAR(50) DEFAULT 'fa-car',
`image` VARCHAR(255),
`sort_order` INT DEFAULT 0,
`is_active` BOOLEAN DEFAULT TRUE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
-- Vehicle Makes/Manufacturers
CREATE TABLE `vehicle_makes` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`logo` VARCHAR(255),
`is_active` BOOLEAN DEFAULT TRUE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
-- Vehicle Models
CREATE TABLE `vehicle_models` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`make_id` INT(11) NOT NULL,
`name` VARCHAR(100) NOT NULL,
`year` YEAR,
`vehicle_type` ENUM('sedan', 'suv', 'hatchback', 'coupe', 'convertible', 'wagon', 'van', 'truck', 'luxury') DEFAULT 'sedan',
`transmission` ENUM('manual', 'automatic', 'semi-automatic') DEFAULT 'manual',
`fuel_type` ENUM('petrol', 'diesel', 'electric', 'hybrid', 'cng') DEFAULT 'petrol',
`seating_capacity` INT DEFAULT 5,
`doors` INT DEFAULT 4,
`engine_size` DECIMAL(3,1),
`horsepower` INT,
`fuel_efficiency_city` DECIMAL(5,2),
`fuel_efficiency_highway` DECIMAL(5,2),
`image` VARCHAR(255),
`is_active` BOOLEAN DEFAULT TRUE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`make_id`) REFERENCES `vehicle_makes`(`id`) ON DELETE CASCADE,
INDEX `idx_make` (`make_id`)
);
-- Vehicles Table
CREATE TABLE `vehicles` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`vehicle_id` VARCHAR(20) UNIQUE NOT NULL,
`agency_id` INT(11) NOT NULL,
`category_id` INT(11),
`make_id` INT(11) NOT NULL,
`model_id` INT(11) NOT NULL,
`registration_number` VARCHAR(50) UNIQUE NOT NULL,
`vin` VARCHAR(50) UNIQUE, -- Vehicle Identification Number
`color` VARCHAR(50),
`mileage` INT DEFAULT 0,
`fuel_level` DECIMAL(5,2) DEFAULT 100.00,
`condition` ENUM('excellent', 'good', 'fair', 'needs_maintenance') DEFAULT 'good',
`features` TEXT, -- JSON array of features
`description` TEXT,
`images` TEXT, -- JSON array of image paths
`price_per_day` DECIMAL(10,2) NOT NULL,
`price_per_week` DECIMAL(10,2),
`price_per_month` DECIMAL(10,2),
`security_deposit` DECIMAL(10,2) DEFAULT 500.00,
`minimum_rental_days` INT DEFAULT 1,
`maximum_rental_days` INT DEFAULT 30,
`free_mileage_per_day` INT DEFAULT 100,
`additional_mileage_charge` DECIMAL(5,2) DEFAULT 0.50,
`late_return_fee_per_hour` DECIMAL(10,2) DEFAULT 10.00,
`cancellation_policy` ENUM('flexible', 'moderate', 'strict') DEFAULT 'moderate',
`insurance_included` BOOLEAN DEFAULT TRUE,
`insurance_details` TEXT,
`gps_tracking_enabled` BOOLEAN DEFAULT FALSE,
`pickup_location` VARCHAR(255),
`pickup_instructions` TEXT,
`return_location` VARCHAR(255),
`return_instructions` TEXT,
`latitude` DECIMAL(10,8),
`longitude` DECIMAL(11,8),
`status` ENUM('available', 'booked', 'maintenance', 'unavailable') DEFAULT 'available',
`is_active` BOOLEAN DEFAULT TRUE,
`views_count` INT DEFAULT 0,
`booking_count` INT DEFAULT 0,
`rating` DECIMAL(3,2) DEFAULT 0.00,
`total_reviews` INT DEFAULT 0,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`agency_id`) REFERENCES `agencies`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`category_id`) REFERENCES `vehicle_categories`(`id`),
FOREIGN KEY (`make_id`) REFERENCES `vehicle_makes`(`id`),
FOREIGN KEY (`model_id`) REFERENCES `vehicle_models`(`id`),
INDEX `idx_agency` (`agency_id`),
INDEX `idx_status` (`status`),
INDEX `idx_price` (`price_per_day`),
INDEX `idx_location` (`latitude`, `longitude`)
);
-- Vehicle Availability (specific date-based pricing and availability)
CREATE TABLE `vehicle_availability` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`vehicle_id` INT(11) NOT NULL,
`date` DATE NOT NULL,
`is_available` BOOLEAN DEFAULT TRUE,
`price_modifier` DECIMAL(5,2) DEFAULT 0.00, -- percentage +/-
`custom_price` DECIMAL(10,2),
`notes` VARCHAR(255),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`vehicle_id`) REFERENCES `vehicles`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_vehicle_date` (`vehicle_id`, `date`)
);
-- Drivers Table (for chauffeur services)
CREATE TABLE `drivers` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`agency_id` INT(11) NOT NULL,
`license_number` VARCHAR(50) NOT NULL,
`license_expiry` DATE NOT NULL,
`license_file` VARCHAR(255),
`experience_years` INT,
`languages` TEXT,
`rating` DECIMAL(3,2) DEFAULT 0.00,
`total_trips` INT DEFAULT 0,
`status` ENUM('available', 'on_trip', 'off_duty', 'unavailable') DEFAULT 'available',
`base_rate_per_hour` DECIMAL(10,2) DEFAULT 15.00,
`base_rate_per_km` DECIMAL(5,2) DEFAULT 0.50,
`is_active` BOOLEAN DEFAULT TRUE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`agency_id`) REFERENCES `agencies`(`id`) ON DELETE CASCADE,
INDEX `idx_status` (`status`)
);
-- Bookings Table
CREATE TABLE `bookings` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`booking_id` VARCHAR(20) UNIQUE NOT NULL,
`customer_id` INT(11) NOT NULL,
`vehicle_id` INT(11) NOT NULL,
`driver_id` INT(11) NULL,
`agency_id` INT(11) NOT NULL,
`pickup_date` DATETIME NOT NULL,
`return_date` DATETIME NOT NULL,
`actual_pickup_date` DATETIME,
`actual_return_date` DATETIME,
`pickup_location` VARCHAR(255),
`return_location` VARCHAR(255),
`trip_purpose` VARCHAR(255),
`number_of_days` INT NOT NULL,
`total_mileage_limit` INT,
`base_price` DECIMAL(10,2) NOT NULL,
`driver_price` DECIMAL(10,2) DEFAULT 0.00,
`insurance_price` DECIMAL(10,2) DEFAULT 0.00,
`additional_charges` DECIMAL(10,2) DEFAULT 0.00,
`discount_amount` DECIMAL(10,2) DEFAULT 0.00,
`tax_amount` DECIMAL(10,2) DEFAULT 0.00,
`total_amount` DECIMAL(10,2) NOT NULL,
`security_deposit` DECIMAL(10,2) DEFAULT 0.00,
`currency` VARCHAR(3) DEFAULT 'USD',
`payment_status` ENUM('pending', 'partial', 'paid', 'refunded', 'failed') DEFAULT 'pending',
`payment_method` VARCHAR(50),
`transaction_id` VARCHAR(255),
`booking_status` ENUM('pending', 'confirmed', 'in_progress', 'completed', 'cancelled', 'no_show') DEFAULT 'pending',
`cancellation_reason` TEXT,
`cancelled_by` ENUM('customer', 'agency', 'admin') NULL,
`cancelled_at` DATETIME,
`customer_notes` TEXT,
`agency_notes` TEXT,
`checklist_completed` BOOLEAN DEFAULT FALSE,
`contract_signed` BOOLEAN DEFAULT FALSE,
`contract_file` VARCHAR(255),
`signature_date` DATETIME,
`gps_tracking_enabled` BOOLEAN DEFAULT FALSE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`customer_id`) REFERENCES `users`(`id`),
FOREIGN KEY (`vehicle_id`) REFERENCES `vehicles`(`id`),
FOREIGN KEY (`driver_id`) REFERENCES `drivers`(`id`),
FOREIGN KEY (`agency_id`) REFERENCES `agencies`(`id`),
INDEX `idx_booking_id` (`booking_id`),
INDEX `idx_dates` (`pickup_date`, `return_date`),
INDEX `idx_status` (`booking_status`, `payment_status`)
);
-- Booking Extensions
CREATE TABLE `booking_extensions` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`booking_id` INT(11) NOT NULL,
`original_return_date` DATETIME NOT NULL,
`new_return_date` DATETIME NOT NULL,
`additional_days` INT NOT NULL,
`additional_amount` DECIMAL(10,2) NOT NULL,
`status` ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
`approved_by` INT(11),
`approved_at` DATETIME,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`booking_id`) REFERENCES `bookings`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`approved_by`) REFERENCES `users`(`id`)
);
-- Payments Table
CREATE TABLE `payments` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`payment_id` VARCHAR(50) UNIQUE NOT NULL,
`booking_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`amount` DECIMAL(10,2) NOT NULL,
`currency` VARCHAR(3) DEFAULT 'USD',
`payment_method` VARCHAR(50) NOT NULL,
`payment_type` ENUM('booking', 'deposit', 'extension', 'fine', 'refund') DEFAULT 'booking',
`transaction_id` VARCHAR(255),
`gateway_response` TEXT,
`status` ENUM('pending', 'completed', 'failed', 'refunded') DEFAULT 'pending',
`refund_amount` DECIMAL(10,2),
`refund_reason` TEXT,
`refunded_at` DATETIME,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`booking_id`) REFERENCES `bookings`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`),
INDEX `idx_payment_id` (`payment_id`),
INDEX `idx_status` (`status`)
);
-- Payouts to Agencies
CREATE TABLE `payouts` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`agency_id` INT(11) NOT NULL,
`amount` DECIMAL(10,2) NOT NULL,
`commission_amount` DECIMAL(10,2) NOT NULL,
`net_amount` DECIMAL(10,2) NOT NULL,
`period_start` DATE NOT NULL,
`period_end` DATE NOT NULL,
`status` ENUM('pending', 'processing', 'completed', 'failed') DEFAULT 'pending',
`payment_method` VARCHAR(50),
`transaction_id` VARCHAR(255),
`notes` TEXT,
`processed_at` DATETIME,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`agency_id`) REFERENCES `agencies`(`id`) ON DELETE CASCADE
);
-- Reviews Table
CREATE TABLE `reviews` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`booking_id` INT(11) NOT NULL,
`customer_id` INT(11) NOT NULL,
`vehicle_id` INT(11) NOT NULL,
`agency_id` INT(11) NOT NULL,
`rating` TINYINT NOT NULL CHECK (rating >= 1 AND rating <= 5),
`title` VARCHAR(255),
`comment` TEXT,
`pros` TEXT,
`cons` TEXT,
`cleanliness_rating` TINYINT,
`maintenance_rating` TINYINT,
`pickup_experience_rating` TINYINT,
`value_rating` TINYINT,
`agency_response` TEXT,
`response_date` DATETIME,
`helpful_count` INT DEFAULT 0,
`is_verified` BOOLEAN DEFAULT TRUE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`booking_id`) REFERENCES `bookings`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`customer_id`) REFERENCES `users`(`id`),
FOREIGN KEY (`vehicle_id`) REFERENCES `vehicles`(`id`),
FOREIGN KEY (`agency_id`) REFERENCES `agencies`(`id`),
UNIQUE KEY `unique_review` (`booking_id`),
INDEX `idx_rating` (`rating`)
);
-- Favorites/Wishlist
CREATE TABLE `favorites` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`customer_id` INT(11) NOT NULL,
`vehicle_id` INT(11) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`customer_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`vehicle_id`) REFERENCES `vehicles`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_favorite` (`customer_id`, `vehicle_id`)
);
-- Notifications Table
CREATE TABLE `notifications` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`type` ENUM('booking_confirmation', 'booking_reminder', 'booking_cancelled', 'payment_received', 'review_received', 'system', 'promotion') NOT NULL,
`title` VARCHAR(255) NOT NULL,
`message` TEXT NOT NULL,
`data` JSON,
`is_read` BOOLEAN DEFAULT FALSE,
`read_at` DATETIME,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
INDEX `idx_user_read` (`user_id`, `is_read`)
);
-- Messages/Chat
CREATE TABLE `messages` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`sender_id` INT(11) NOT NULL,
`receiver_id` INT(11) NOT NULL,
`booking_id` INT(11),
`message` TEXT NOT NULL,
`is_read` BOOLEAN DEFAULT FALSE,
`read_at` DATETIME,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`sender_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`receiver_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`booking_id`) REFERENCES `bookings`(`id`) ON DELETE SET NULL,
INDEX `idx_booking` (`booking_id`)
);
-- Vehicle Inspections
CREATE TABLE `vehicle_inspections` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`booking_id` INT(11) NOT NULL,
`inspector_id` INT(11), -- driver or agency staff
`inspection_type` ENUM('pre_rental', 'post_rental') NOT NULL,
`fuel_level` DECIMAL(5,2),
`mileage` INT,
`damages` TEXT,
`photos` TEXT,
`notes` TEXT,
`signature_customer` VARCHAR(255),
`signature_inspector` VARCHAR(255),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`booking_id`) REFERENCES `bookings`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`inspector_id`) REFERENCES `users`(`id`)
);
-- Promotions/Coupons
CREATE TABLE `promotions` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`agency_id` INT(11),
`code` VARCHAR(50) UNIQUE NOT NULL,
`description` TEXT,
`discount_type` ENUM('percentage', 'fixed') NOT NULL,
`discount_value` DECIMAL(10,2) NOT NULL,
`minimum_booking_amount` DECIMAL(10,2),
`maximum_discount` DECIMAL(10,2),
`valid_from` DATETIME NOT NULL,
`valid_to` DATETIME NOT NULL,
`usage_limit` INT,
`used_count` INT DEFAULT 0,
`is_active` BOOLEAN DEFAULT TRUE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`agency_id`) REFERENCES `agencies`(`id`) ON DELETE CASCADE,
INDEX `idx_code` (`code`)
);
-- System Settings
CREATE TABLE `system_settings` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`setting_key` VARCHAR(100) UNIQUE NOT NULL,
`setting_value` TEXT,
`description` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
-- Insert Default Admin
INSERT INTO `users` (`user_id`, `email`, `password`, `first_name`, `last_name`, `role`, `email_verified`, `status`) 
VALUES ('ADMIN001', '[email protected]', '$2y$10$YourHashedPasswordHere', 'System', 'Administrator', 'admin', TRUE, 'active');
-- Insert Default System Settings
INSERT INTO `system_settings` (`setting_key`, `setting_value`, `description`) VALUES
('site_name', 'CarRental System', 'Website name'),
('site_email', '[email protected]', 'Contact email'),
('site_phone', '+1-555-123-4567', 'Contact phone'),
('site_address', '123 Business Ave, City, State 12345', 'Business address'),
('timezone', 'America/New_York', 'Default timezone'),
('date_format', 'Y-m-d', 'Date format'),
('time_format', 'H:i', 'Time format'),
('currency', 'USD', 'Default currency'),
('currency_symbol', '$', 'Currency symbol'),
('commission_rate', '10.00', 'Default commission rate for agencies (%)'),
('enable_payments', '1', 'Enable online payments'),
('payment_gateway', 'stripe', 'Payment gateway (stripe/paypal)'),
('stripe_key', '', 'Stripe publishable key'),
('stripe_secret', '', 'Stripe secret key'),
('paypal_client_id', '', 'PayPal client ID'),
('paypal_secret', '', 'PayPal secret'),
('enable_sms', '0', 'Enable SMS notifications'),
('sms_provider', 'twilio', 'SMS provider'),
('twilio_sid', '', 'Twilio SID'),
('twilio_token', '', 'Twilio token'),
('twilio_phone', '', 'Twilio phone number'),
('google_maps_api_key', '', 'Google Maps API key'),
('max_advance_booking', '180', 'Maximum days in advance for booking'),
('min_advance_booking', '1', 'Minimum hours in advance for booking'),
('cancellation_policy', '24', 'Hours before pickup for free cancellation'),
('late_return_fee_per_hour', '10.00', 'Fee for late return per hour'),
('security_deposit_percentage', '20', 'Security deposit as percentage of total'),
('driver_base_rate', '15.00', 'Base rate for driver per hour'),
('free_mileage_per_day', '100', 'Free mileage per day in km/miles'),
('additional_mileage_charge', '0.50', 'Charge per additional km/mile'),
('app_version', '1.0.0', 'Application version');
-- Insert Vehicle Categories
INSERT INTO `vehicle_categories` (`name`, `icon`, `sort_order`) VALUES
('Economy', 'fa-car', 1),
('Compact', 'fa-car-side', 2),
('Midsize', 'fa-caravan', 3),
('Standard', 'fa-shuttle-van', 4),
('Full-size', 'fa-truck', 5),
('SUV', 'fa-truck-monster', 6),
('Luxury', 'fa-gem', 7),
('Convertible', 'fa-car-convertible', 8),
('Van', 'fa-van-shuttle', 9),
('Pickup', 'fa-truck-pickup', 10);
-- Insert Vehicle Makes
INSERT INTO `vehicle_makes` (`name`) VALUES
('Toyota'), ('Honda'), ('Ford'), ('Chevrolet'), ('Nissan'),
('BMW'), ('Mercedes-Benz'), ('Audi'), ('Volkswagen'), ('Hyundai'),
('Kia'), ('Mazda'), ('Subaru'), ('Lexus'), ('Jeep');
-- Insert Vehicle Models (sample)
INSERT INTO `vehicle_models` (`make_id`, `name`, `vehicle_type`, `transmission`, `fuel_type`, `seating_capacity`) VALUES
(1, 'Camry', 'sedan', 'automatic', 'petrol', 5),
(1, 'Corolla', 'sedan', 'automatic', 'petrol', 5),
(1, 'RAV4', 'suv', 'automatic', 'petrol', 5),
(1, 'Highlander', 'suv', 'automatic', 'hybrid', 7),
(2, 'Civic', 'sedan', 'automatic', 'petrol', 5),
(2, 'Accord', 'sedan', 'automatic', 'petrol', 5),
(2, 'CR-V', 'suv', 'automatic', 'petrol', 5),
(3, 'F-150', 'pickup', 'automatic', 'petrol', 5),
(3, 'Explorer', 'suv', 'automatic', 'petrol', 7),
(4, 'Malibu', 'sedan', 'automatic', 'petrol', 5),
(4, 'Equinox', 'suv', 'automatic', 'petrol', 5),
(4, 'Suburban', 'suv', 'automatic', 'petrol', 8),
(5, 'Altima', 'sedan', 'automatic', 'petrol', 5),
(5, 'Rogue', 'suv', 'automatic', 'petrol', 5),
(5, 'Pathfinder', 'suv', 'automatic', 'petrol', 7);

Core PHP Classes

Database Class

File: includes/Database.php

<?php
/**
* Database Class
* Handles all database connections and operations using PDO with singleton pattern
*/
class Database {
private static $instance = null;
private $connection;
private $statement;
private $host;
private $dbname;
private $username;
private $password;
/**
* Private constructor for singleton pattern
*/
private function __construct() {
$this->host = DB_HOST;
$this->dbname = DB_NAME;
$this->username = DB_USER;
$this->password = DB_PASS;
try {
$this->connection = new PDO(
"mysql:host={$this->host};dbname={$this->dbname};charset=utf8mb4",
$this->username,
$this->password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
} catch (PDOException $e) {
die("Database connection failed: " . $e->getMessage());
}
}
/**
* Get database instance (Singleton)
*/
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Prepare and execute query with parameters
*/
public function query($sql, $params = []) {
try {
$this->statement = $this->connection->prepare($sql);
$this->statement->execute($params);
return $this->statement;
} catch (PDOException $e) {
$this->logError($e->getMessage(), $sql, $params);
throw new Exception("Database query failed: " . $e->getMessage());
}
}
/**
* Get single row
*/
public function getRow($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetch();
}
/**
* Get multiple rows
*/
public function getRows($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetchAll();
}
/**
* Get single value
*/
public function getValue($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetchColumn();
}
/**
* Insert data and return last insert ID
*/
public function insert($table, $data) {
$columns = implode(', ', array_keys($data));
$placeholders = ':' . implode(', :', array_keys($data));
$sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";
$this->query($sql, $data);
return $this->connection->lastInsertId();
}
/**
* Update data
*/
public function update($table, $data, $where, $whereParams = []) {
$set = [];
foreach (array_keys($data) as $column) {
$set[] = "{$column} = :{$column}";
}
$sql = "UPDATE {$table} SET " . implode(', ', $set) . " WHERE {$where}";
$params = array_merge($data, $whereParams);
return $this->query($sql, $params)->rowCount();
}
/**
* Delete data
*/
public function delete($table, $where, $params = []) {
$sql = "DELETE FROM {$table} WHERE {$where}";
return $this->query($sql, $params)->rowCount();
}
/**
* Begin transaction
*/
public function beginTransaction() {
return $this->connection->beginTransaction();
}
/**
* Commit transaction
*/
public function commit() {
return $this->connection->commit();
}
/**
* Rollback transaction
*/
public function rollback() {
return $this->connection->rollBack();
}
/**
* Get last insert ID
*/
public function lastInsertId() {
return $this->connection->lastInsertId();
}
/**
* Log database errors
*/
private function logError($message, $sql, $params) {
$logFile = __DIR__ . '/../logs/database.log';
$logDir = dirname($logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
$timestamp = date('Y-m-d H:i:s');
$logMessage = "[{$timestamp}] Error: {$message}\n";
$logMessage .= "SQL: {$sql}\n";
$logMessage .= "Params: " . json_encode($params) . "\n";
$logMessage .= "------------------------\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
}
/**
* Prevent cloning of the instance
*/
private function __clone() {}
/**
* Prevent unserializing of the instance
*/
public function __wakeup() {}
}
?>

Configuration File

File: includes/config.php

<?php
/**
* Configuration File
* Loads environment variables and sets up constants
*/
// Start session if not started
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Load environment variables from .env file
function loadEnv($path) {
if (!file_exists($path)) {
return false;
}
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (strpos(trim($line), '#') === 0) {
continue;
}
list($name, $value) = explode('=', $line, 2);
$name = trim($name);
$value = trim($value);
if (!array_key_exists($name, $_ENV)) {
$_ENV[$name] = $value;
putenv(sprintf('%s=%s', $name, $value));
}
}
return true;
}
// Load environment variables
loadEnv(__DIR__ . '/../.env');
// Database Configuration
define('DB_HOST', getenv('DB_HOST') ?: 'localhost');
define('DB_NAME', getenv('DB_NAME') ?: 'car_rental_system');
define('DB_USER', getenv('DB_USER') ?: 'root');
define('DB_PASS', getenv('DB_PASS') ?: '');
// Application Configuration
define('APP_NAME', getenv('APP_NAME') ?: 'Car Rental System');
define('APP_URL', getenv('APP_URL') ?: 'http://localhost/car-rental-system');
define('APP_VERSION', getenv('APP_VERSION') ?: '1.0.0');
define('DEBUG_MODE', getenv('DEBUG_MODE') === 'true');
// Security Configuration
define('SESSION_TIMEOUT', getenv('SESSION_TIMEOUT') ?: 3600); // 1 hour
define('BCRYPT_ROUNDS', 12);
define('CSRF_TOKEN_NAME', 'csrf_token');
// Upload Configuration
define('UPLOAD_DIR', __DIR__ . '/../uploads/');
define('MAX_FILE_SIZE', getenv('MAX_FILE_SIZE') ?: 5 * 1024 * 1024); // 5MB
define('ALLOWED_EXTENSIONS', ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx']);
// Pagination
define('ITEMS_PER_PAGE', getenv('ITEMS_PER_PAGE') ?: 20);
// Date/Time Configuration
date_default_timezone_set(getenv('TIMEZONE') ?: 'America/New_York');
define('DATE_FORMAT', 'Y-m-d');
define('TIME_FORMAT', 'H:i');
define('DATETIME_FORMAT', 'Y-m-d H:i:s');
// Currency Configuration
define('CURRENCY', getenv('CURRENCY') ?: 'USD');
define('CURRENCY_SYMBOL', getenv('CURRENCY_SYMBOL') ?: '$');
// Rental Settings
define('COMMISSION_RATE', getenv('COMMISSION_RATE') ?: 10.00);
define('SECURITY_DEPOSIT_PERCENTAGE', getenv('SECURITY_DEPOSIT_PERCENTAGE') ?: 20);
define('FREE_MILEAGE_PER_DAY', getenv('FREE_MILEAGE_PER_DAY') ?: 100);
define('ADDITIONAL_MILEAGE_CHARGE', getenv('ADDITIONAL_MILEAGE_CHARGE') ?: 0.50);
define('LATE_RETURN_FEE_PER_HOUR', getenv('LATE_RETURN_FEE_PER_HOUR') ?: 10.00);
define('MAX_ADVANCE_BOOKING', getenv('MAX_ADVANCE_BOOKING') ?: 180); // days
define('MIN_ADVANCE_BOOKING', getenv('MIN_ADVANCE_BOOKING') ?: 1); // hours
define('CANCELLATION_POLICY', getenv('CANCELLATION_POLICY') ?: 24); // hours
// Payment Settings
define('ENABLE_PAYMENTS', getenv('ENABLE_PAYMENTS') === 'true');
define('PAYMENT_GATEWAY', getenv('PAYMENT_GATEWAY') ?: 'stripe');
define('STRIPE_KEY', getenv('STRIPE_KEY') ?: '');
define('STRIPE_SECRET', getenv('STRIPE_SECRET') ?: '');
define('PAYPAL_CLIENT_ID', getenv('PAYPAL_CLIENT_ID') ?: '');
define('PAYPAL_SECRET', getenv('PAYPAL_SECRET') ?: '');
// Notification Settings
define('ENABLE_SMS', getenv('ENABLE_SMS') === 'true');
define('ENABLE_EMAIL', getenv('ENABLE_EMAIL') === 'true');
define('SMS_PROVIDER', getenv('SMS_PROVIDER') ?: 'twilio');
define('TWILIO_SID', getenv('TWILIO_SID') ?: '');
define('TWILIO_TOKEN', getenv('TWILIO_TOKEN') ?: '');
define('TWILIO_PHONE', getenv('TWILIO_PHONE') ?: '');
// Google Maps
define('GOOGLE_MAPS_API_KEY', getenv('GOOGLE_MAPS_API_KEY') ?: '');
// Error Reporting
if (DEBUG_MODE) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
} else {
error_reporting(0);
ini_set('display_errors', 0);
}
// Include required files
require_once __DIR__ . '/Database.php';
require_once __DIR__ . '/functions.php';
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/Vehicle.php';
require_once __DIR__ . '/Booking.php';
require_once __DIR__ . '/Agency.php';
require_once __DIR__ . '/Payment.php';
require_once __DIR__ . '/Review.php';
require_once __DIR__ . '/Notification.php';
require_once __DIR__ . '/Driver.php';
// Initialize database connection
$db = Database::getInstance();
// Load system settings
$settings = $db->getRows("SELECT setting_key, setting_value FROM system_settings");
foreach ($settings as $setting) {
if (!defined(strtoupper($setting['setting_key']))) {
define(strtoupper($setting['setting_key']), $setting['setting_value']);
}
}
// Set timezone for MySQL
$db->query("SET time_zone = ?", [date('P')]);
?>

Helper Functions

File: includes/functions.php

<?php
/**
* Helper Functions
* Common utility functions used throughout the application
*/
/**
* Sanitize input data
*/
function sanitize($input) {
if (is_array($input)) {
return array_map('sanitize', $input);
}
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
/**
* Generate CSRF token
*/
function generateCSRFToken() {
if (!isset($_SESSION[CSRF_TOKEN_NAME])) {
$_SESSION[CSRF_TOKEN_NAME] = bin2hex(random_bytes(32));
}
return $_SESSION[CSRF_TOKEN_NAME];
}
/**
* Verify CSRF token
*/
function verifyCSRFToken($token) {
if (!isset($_SESSION[CSRF_TOKEN_NAME]) || $token !== $_SESSION[CSRF_TOKEN_NAME]) {
return false;
}
return true;
}
/**
* Redirect to URL
*/
function redirect($url) {
header("Location: " . APP_URL . $url);
exit();
}
/**
* Format amount with currency
*/
function formatAmount($amount, $currency = null) {
if ($currency === null) {
$currency = CURRENCY;
}
$symbols = [
'USD' => '$',
'EUR' => '€',
'GBP' => '£',
'JPY' => '¥',
'INR' => '₹',
'CAD' => 'C$',
'AUD' => 'A$'
];
$symbol = $symbols[$currency] ?? CURRENCY_SYMBOL;
return $symbol . ' ' . number_format($amount, 2);
}
/**
* Format date
*/
function formatDate($date, $format = null) {
if ($format === null) {
$format = DATE_FORMAT;
}
if ($date instanceof DateTime) {
return $date->format($format);
}
return date($format, strtotime($date));
}
/**
* Format time
*/
function formatTime($time, $format = null) {
if ($format === null) {
$format = TIME_FORMAT;
}
return date($format, strtotime($time));
}
/**
* Format datetime
*/
function formatDateTime($datetime, $format = null) {
if ($format === null) {
$format = DATETIME_FORMAT;
}
return date($format, strtotime($datetime));
}
/**
* Get time ago string
*/
function timeAgo($datetime) {
$time = strtotime($datetime);
$now = time();
$diff = $now - $time;
if ($diff < 60) {
return $diff . ' seconds ago';
} elseif ($diff < 3600) {
return floor($diff / 60) . ' minutes ago';
} elseif ($diff < 86400) {
return floor($diff / 3600) . ' hours ago';
} elseif ($diff < 2592000) {
return floor($diff / 86400) . ' days ago';
} elseif ($diff < 31536000) {
return floor($diff / 2592000) . ' months ago';
} else {
return floor($diff / 31536000) . ' years ago';
}
}
/**
* Calculate number of days between two dates
*/
function dateDiffInDays($date1, $date2) {
$diff = strtotime($date2) - strtotime($date1);
return ceil($diff / (60 * 60 * 24));
}
/**
* Calculate rental price
*/
function calculateRentalPrice($vehicle, $pickupDate, $returnDate, $includeDriver = false, $driverHours = 0) {
$days = dateDiffInDays($pickupDate, $returnDate);
// Base price calculation
if ($days >= 30 && $vehicle['price_per_month'] > 0) {
$months = floor($days / 30);
$remainingDays = $days % 30;
$price = ($months * $vehicle['price_per_month']) + ($remainingDays * $vehicle['price_per_day']);
} elseif ($days >= 7 && $vehicle['price_per_week'] > 0) {
$weeks = floor($days / 7);
$remainingDays = $days % 7;
$price = ($weeks * $vehicle['price_per_week']) + ($remainingDays * $vehicle['price_per_day']);
} else {
$price = $days * $vehicle['price_per_day'];
}
// Add driver cost if requested
$driverPrice = 0;
if ($includeDriver && $driverHours > 0) {
$driverPrice = $driverHours * DRIVER_BASE_RATE;
}
// Calculate security deposit
$securityDeposit = $price * (SECURITY_DEPOSIT_PERCENTAGE / 100);
// Calculate tax (example: 10%)
$taxAmount = $price * 0.10;
$total = $price + $driverPrice + $taxAmount;
return [
'days' => $days,
'base_price' => $price,
'driver_price' => $driverPrice,
'tax_amount' => $taxAmount,
'security_deposit' => $securityDeposit,
'total_amount' => $total,
'currency' => CURRENCY
];
}
/**
* Check if vehicle is available for given dates
*/
function isVehicleAvailable($vehicleId, $pickupDate, $returnDate, $excludeBookingId = null) {
$db = Database::getInstance();
$sql = "SELECT COUNT(*) FROM bookings 
WHERE vehicle_id = :vehicle_id 
AND booking_status NOT IN ('cancelled', 'completed')
AND (
(pickup_date BETWEEN :pickup_date AND :return_date)
OR (return_date BETWEEN :pickup_date AND :return_date)
OR (pickup_date <= :pickup_date AND return_date >= :return_date)
)";
$params = [
'vehicle_id' => $vehicleId,
'pickup_date' => $pickupDate,
'return_date' => $returnDate
];
if ($excludeBookingId) {
$sql .= " AND id != :exclude_id";
$params['exclude_id'] = $excludeBookingId;
}
$count = $db->getValue($sql, $params);
return $count == 0;
}
/**
* Generate unique booking ID
*/
function generateBookingId() {
return 'BK' . date('Ymd') . strtoupper(uniqid());
}
/**
* Generate unique vehicle ID
*/
function generateVehicleId() {
return 'VH' . date('Y') . str_pad(mt_rand(1, 9999), 4, '0', STR_PAD_LEFT);
}
/**
* Generate unique user ID
*/
function generateUserId($role) {
$prefix = strtoupper(substr($role, 0, 3));
return $prefix . date('Y') . str_pad(mt_rand(1, 9999), 4, '0', STR_PAD_LEFT);
}
/**
* Upload file
*/
function uploadFile($file, $targetDir, $allowedTypes = null) {
if ($allowedTypes === null) {
$allowedTypes = ALLOWED_EXTENSIONS;
}
// Check for errors
if ($file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'error' => 'Upload failed with error code: ' . $file['error']];
}
// Check file size
if ($file['size'] > MAX_FILE_SIZE) {
return ['success' => false, 'error' => 'File size exceeds limit'];
}
// Check file type
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $allowedTypes)) {
return ['success' => false, 'error' => 'File type not allowed'];
}
// Generate unique filename
$filename = uniqid() . '_' . time() . '.' . $extension;
$targetPath = $targetDir . '/' . $filename;
// Create directory if not exists
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
// Upload file
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
return [
'success' => true,
'filename' => $filename,
'original_name' => $file['name'],
'path' => $targetPath
];
}
return ['success' => false, 'error' => 'Failed to move uploaded file'];
}
/**
* Send email notification
*/
function sendEmail($to, $subject, $template, $data = []) {
if (!ENABLE_EMAIL) {
return false;
}
// Load email template
$templateFile = __DIR__ . "/notifications/email_templates/{$template}.php";
if (!file_exists($templateFile)) {
logError("Email template not found: {$template}");
return false;
}
// Extract data for template
extract($data);
ob_start();
include $templateFile;
$message = ob_get_clean();
// Use PHPMailer for better email handling
require_once __DIR__ . '/../vendor/autoload.php';
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
try {
// Server settings
$mail->isSMTP();
$mail->Host       = SMTP_HOST;
$mail->SMTPAuth   = true;
$mail->Username   = SMTP_USER;
$mail->Password   = SMTP_PASS;
$mail->SMTPSecure = SMTP_ENCRYPTION;
$mail->Port       = SMTP_PORT;
// Recipients
$mail->setFrom(SITE_EMAIL, SITE_NAME);
$mail->addAddress($to);
// Content
$mail->isHTML(true);
$mail->Subject = $subject;
$mail->Body    = $message;
$mail->send();
return true;
} catch (Exception $e) {
logError("Email sending failed: " . $mail->ErrorInfo);
return false;
}
}
/**
* Send SMS notification
*/
function sendSMS($to, $message) {
if (!ENABLE_SMS) {
return false;
}
require_once __DIR__ . '/../vendor/autoload.php';
try {
$client = new Twilio\Rest\Client(TWILIO_SID, TWILIO_TOKEN);
$client->messages->create(
$to,
[
'from' => TWILIO_PHONE,
'body' => $message
]
);
return true;
} catch (Exception $e) {
logError("SMS sending failed: " . $e->getMessage());
return false;
}
}
/**
* Log error
*/
function logError($message, $context = []) {
$logFile = __DIR__ . '/../logs/error.log';
$logDir = dirname($logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
$timestamp = date('Y-m-d H:i:s');
$contextStr = !empty($context) ? ' ' . json_encode($context) : '';
$logMessage = "[{$timestamp}] {$message}{$contextStr}\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
}
/**
* Get user IP address
*/
function getUserIP() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
return $_SERVER['REMOTE_ADDR'];
}
}
/**
* Generate random string
*/
function generateRandomString($length = 32) {
return bin2hex(random_bytes($length / 2));
}
/**
* Validate email
*/
function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
/**
* Validate phone number
*/
function validatePhone($phone) {
return preg_match('/^[0-9\-\(\)\/\+\s]+$/', $phone);
}
/**
* Get star rating HTML
*/
function getStarRating($rating) {
$fullStars = floor($rating);
$halfStar = ($rating - $fullStars) >= 0.5;
$emptyStars = 5 - $fullStars - ($halfStar ? 1 : 0);
$html = '';
for ($i = 0; $i < $fullStars; $i++) {
$html .= '<i class="fas fa-star text-warning"></i>';
}
if ($halfStar) {
$html .= '<i class="fas fa-star-half-alt text-warning"></i>';
}
for ($i = 0; $i < $emptyStars; $i++) {
$html .= '<i class="far fa-star text-warning"></i>';
}
return $html;
}
/**
* Calculate distance between two coordinates
*/
function calculateDistance($lat1, $lon1, $lat2, $lon2) {
$theta = $lon1 - $lon2;
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + 
cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
$dist = acos($dist);
$dist = rad2deg($dist);
$miles = $dist * 60 * 1.1515;
return $miles * 1.609344; // Return in kilometers
}
/**
* Generate pagination
*/
function paginate($currentPage, $totalPages, $url) {
if ($totalPages <= 1) {
return '';
}
$html = '<nav aria-label="Page navigation"><ul class="pagination justify-content-center">';
// Previous button
if ($currentPage > 1) {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '?page=' . ($currentPage - 1) . '">Previous</a></li>';
} else {
$html .= '<li class="page-item disabled"><span class="page-link">Previous</span></li>';
}
// Page numbers
for ($i = 1; $i <= $totalPages; $i++) {
if ($i == $currentPage) {
$html .= '<li class="page-item active"><span class="page-link">' . $i . '</span></li>';
} else {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '?page=' . $i . '">' . $i . '</a></li>';
}
}
// Next button
if ($currentPage < $totalPages) {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '?page=' . ($currentPage + 1) . '">Next</a></li>';
} else {
$html .= '<li class="page-item disabled"><span class="page-link">Next</span></li>';
}
$html .= '</ul></nav>';
return $html;
}
?>

Authentication Class

File: includes/auth.php

<?php
/**
* Authentication Class
* Handles user authentication, registration, and session management
*/
class Auth {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Register new user
*/
public function register($data) {
try {
// Check if email already exists
$existing = $this->db->getRow(
"SELECT id FROM users WHERE email = ?",
[$data['email']]
);
if ($existing) {
return ['success' => false, 'error' => 'Email already registered'];
}
// Hash password
$hashedPassword = password_hash($data['password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
// Generate user ID
$userId = generateUserId($data['role'] ?? 'customer');
// Generate verification token
$verificationToken = generateRandomString();
// Prepare user data
$userData = [
'user_id' => $userId,
'email' => $data['email'],
'password' => $hashedPassword,
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'phone' => $data['phone'] ?? null,
'phone_country_code' => $data['phone_country_code'] ?? null,
'role' => $data['role'] ?? 'customer',
'status' => ($data['role'] === 'agency') ? 'pending_verification' : 'active',
'verification_token' => $verificationToken
];
// Insert user
$newUserId = $this->db->insert('users', $userData);
if ($newUserId) {
// If role is agency, create agency record
if ($data['role'] === 'agency') {
$this->createAgencyRecord($newUserId, $data);
}
// If role is driver, create driver record
if ($data['role'] === 'driver' && isset($data['agency_id'])) {
$this->createDriverRecord($newUserId, $data);
}
// Send verification email
$this->sendVerificationEmail($data['email'], $verificationToken);
return [
'success' => true,
'user_id' => $newUserId,
'message' => 'Registration successful. Please check your email to verify your account.'
];
}
return ['success' => false, 'error' => 'Registration failed'];
} catch (Exception $e) {
logError('Registration error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Registration failed: ' . $e->getMessage()];
}
}
/**
* Create agency record
*/
private function createAgencyRecord($userId, $data) {
$agencyData = [
'user_id' => $userId,
'agency_name' => $data['agency_name'],
'registration_number' => $data['registration_number'] ?? null,
'tax_number' => $data['tax_number'] ?? null,
'business_address' => $data['business_address'],
'business_city' => $data['business_city'],
'business_state' => $data['business_state'],
'business_country' => $data['business_country'],
'business_postal_code' => $data['business_postal_code'],
'business_phone' => $data['business_phone'],
'business_email' => $data['business_email'],
'description' => $data['description'] ?? null,
'commission_rate' => COMMISSION_RATE,
'status' => 'pending'
];
$this->db->insert('agencies', $agencyData);
}
/**
* Create driver record
*/
private function createDriverRecord($userId, $data) {
$driverData = [
'user_id' => $userId,
'agency_id' => $data['agency_id'],
'license_number' => $data['license_number'],
'license_expiry' => $data['license_expiry'],
'experience_years' => $data['experience_years'] ?? 0,
'languages' => $data['languages'] ?? null,
'status' => 'available'
];
$this->db->insert('drivers', $driverData);
}
/**
* Login user
*/
public function login($email, $password, $remember = false) {
try {
// Get user
$user = $this->db->getRow(
"SELECT * FROM users WHERE email = ?",
[$email]
);
if (!$user) {
return ['success' => false, 'error' => 'Invalid email or password'];
}
// Check if account is active
if ($user['status'] === 'suspended') {
return ['success' => false, 'error' => 'Your account has been suspended. Please contact support.'];
}
if ($user['status'] === 'pending_verification') {
return ['success' => false, 'error' => 'Your account is pending verification. Please check your email.'];
}
// Check if email verified
if (!$user['email_verified']) {
return ['success' => false, 'error' => 'Please verify your email before logging in'];
}
// Verify password
if (!password_verify($password, $user['password'])) {
return ['success' => false, 'error' => 'Invalid email or password'];
}
// Check if password needs rehash
if (password_needs_rehash($user['password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS])) {
$newHash = password_hash($password, PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
$this->db->update('users', ['password' => $newHash], 'id = :id', ['id' => $user['id']]);
}
// Set session
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_email'] = $user['email'];
$_SESSION['user_name'] = $user['first_name'] . ' ' . $user['last_name'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['user_id_display'] = $user['user_id'];
$_SESSION['logged_in'] = true;
$_SESSION['login_time'] = time();
// Update last login
$this->db->update(
'users',
['last_login' => date('Y-m-d H:i:s')],
'id = :id',
['id' => $user['id']]
);
// Set remember me cookie
if ($remember) {
$this->setRememberMe($user['id']);
}
// Get additional data based on role
if ($user['role'] === 'agency') {
$agency = $this->db->getRow(
"SELECT id, status FROM agencies WHERE user_id = ?",
[$user['id']]
);
$_SESSION['agency_id'] = $agency['id'];
$_SESSION['agency_status'] = $agency['status'];
}
if ($user['role'] === 'driver') {
$driver = $this->db->getRow(
"SELECT id, agency_id FROM drivers WHERE user_id = ?",
[$user['id']]
);
$_SESSION['driver_id'] = $driver['id'];
$_SESSION['agency_id'] = $driver['agency_id'];
}
return ['success' => true, 'user' => $user];
} catch (Exception $e) {
logError('Login error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Login failed'];
}
}
/**
* Set remember me cookie
*/
private function setRememberMe($userId) {
$token = generateRandomString(64);
$expires = time() + (86400 * 30); // 30 days
// Store token in remember_tokens table
$this->db->insert('remember_tokens', [
'user_id' => $userId,
'token' => password_hash($token, PASSWORD_DEFAULT),
'expires_at' => date('Y-m-d H:i:s', $expires)
]);
setcookie('remember_token', $userId . ':' . $token, $expires, '/', '', false, true);
}
/**
* Check remember me cookie
*/
public function checkRememberMe() {
if (isset($_COOKIE['remember_token']) && !$this->isLoggedIn()) {
list($userId, $token) = explode(':', $_COOKIE['remember_token']);
$stored = $this->db->getRow(
"SELECT * FROM remember_tokens 
WHERE user_id = ? AND expires_at > NOW() 
ORDER BY created_at DESC LIMIT 1",
[$userId]
);
if ($stored && password_verify($token, $stored['token'])) {
// Log the user in
$user = $this->db->getRow("SELECT * FROM users WHERE id = ?", [$userId]);
if ($user && $user['status'] === 'active') {
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_email'] = $user['email'];
$_SESSION['user_name'] = $user['first_name'] . ' ' . $user['last_name'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['logged_in'] = true;
return true;
}
}
}
return false;
}
/**
* Logout user
*/
public function logout() {
// Clear remember token from database
if (isset($_COOKIE['remember_token'])) {
list($userId, $token) = explode(':', $_COOKIE['remember_token']);
$this->db->delete('remember_tokens', 'user_id = ?', [$userId]);
}
// Clear session
$_SESSION = array();
// Clear session cookie
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
}
// Clear remember me cookie
setcookie('remember_token', '', time() - 3600, '/');
// Destroy session
session_destroy();
}
/**
* Check if user is logged in
*/
public function isLoggedIn() {
return isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true;
}
/**
* Get current user
*/
public function getCurrentUser() {
if (!$this->isLoggedIn()) {
return null;
}
return $this->db->getRow(
"SELECT * FROM users WHERE id = ?",
[$_SESSION['user_id']]
);
}
/**
* Check if user has role
*/
public function hasRole($role) {
return isset($_SESSION['user_role']) && $_SESSION['user_role'] === $role;
}
/**
* Require login
*/
public function requireLogin() {
if (!$this->isLoggedIn()) {
$_SESSION['error'] = 'Please login to access this page';
redirect('/login.php');
}
}
/**
* Require role
*/
public function requireRole($role) {
$this->requireLogin();
if (!$this->hasRole($role)) {
$_SESSION['error'] = 'You do not have permission to access this page';
// Redirect based on role
if ($this->hasRole('admin')) {
redirect('/admin/dashboard.php');
} elseif ($this->hasRole('agency')) {
redirect('/agency/dashboard.php');
} elseif ($this->hasRole('driver')) {
redirect('/driver/dashboard.php');
} else {
redirect('/customer/dashboard.php');
}
}
}
/**
* Verify email
*/
public function verifyEmail($token) {
$user = $this->db->getRow(
"SELECT id FROM users WHERE verification_token = ?",
[$token]
);
if ($user) {
$this->db->update(
'users',
['email_verified' => true, 'verification_token' => null],
'id = :id',
['id' => $user['id']]
);
return true;
}
return false;
}
/**
* Send verification email
*/
private function sendVerificationEmail($email, $token) {
$subject = "Verify your email - " . APP_NAME;
$data = [
'verification_link' => APP_URL . "/verify.php?token=" . $token
];
return sendEmail($email, $subject, 'verification', $data);
}
/**
* Forgot password
*/
public function forgotPassword($email) {
$user = $this->db->getRow(
"SELECT id FROM users WHERE email = ?",
[$email]
);
if ($user) {
$token = generateRandomString();
$expires = date('Y-m-d H:i:s', strtotime('+1 hour'));
$this->db->update(
'users',
['reset_token' => $token, 'reset_expires' => $expires],
'id = :id',
['id' => $user['id']]
);
// Send reset email
$subject = "Password Reset - " . APP_NAME;
$data = [
'reset_link' => APP_URL . "/reset_password.php?token=" . $token
];
return sendEmail($email, $subject, 'password_reset', $data);
}
return false;
}
/**
* Reset password
*/
public function resetPassword($token, $password) {
$user = $this->db->getRow(
"SELECT id FROM users WHERE reset_token = ? AND reset_expires > NOW()",
[$token]
);
if ($user) {
$hashedPassword = password_hash($password, PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
$this->db->update(
'users',
['password' => $hashedPassword, 'reset_token' => null, 'reset_expires' => null],
'id = :id',
['id' => $user['id']]
);
return true;
}
return false;
}
/**
* Update user profile
*/
public function updateProfile($userId, $data) {
try {
$updateData = [
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'phone' => $data['phone'] ?? null,
'phone_country_code' => $data['phone_country_code'] ?? null,
'address_line1' => $data['address_line1'] ?? null,
'address_line2' => $data['address_line2'] ?? null,
'city' => $data['city'] ?? null,
'state' => $data['state'] ?? null,
'country' => $data['country'] ?? null,
'postal_code' => $data['postal_code'] ?? null
];
// Handle profile picture upload
if (isset($_FILES['profile_picture']) && $_FILES['profile_picture']['error'] == 0) {
$uploadDir = UPLOAD_DIR . 'customers/';
$result = uploadFile($_FILES['profile_picture'], $uploadDir);
if ($result['success']) {
$updateData['profile_picture'] = $result['filename'];
}
}
$this->db->update('users', $updateData, 'id = :id', ['id' => $userId]);
return ['success' => true, 'message' => 'Profile updated successfully'];
} catch (Exception $e) {
logError('Profile update error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to update profile'];
}
}
/**
* Change password
*/
public function changePassword($userId, $currentPassword, $newPassword) {
$user = $this->db->getRow("SELECT password FROM users WHERE id = ?", [$userId]);
if (!password_verify($currentPassword, $user['password'])) {
return ['success' => false, 'error' => 'Current password is incorrect'];
}
$hashedPassword = password_hash($newPassword, PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
$this->db->update('users', ['password' => $hashedPassword], 'id = :id', ['id' => $userId]);
return ['success' => true, 'message' => 'Password changed successfully'];
}
/**
* Upload identity documents
*/
public function uploadIdentityDocuments($userId, $files) {
try {
$updateData = [];
// Upload ID proof
if (isset($files['id_proof']) && $files['id_proof']['error'] == 0) {
$uploadDir = UPLOAD_DIR . 'documents/';
$result = uploadFile($files['id_proof'], $uploadDir, ['jpg', 'jpeg', 'png', 'pdf']);
if ($result['success']) {
$updateData['id_proof_file'] = $result['filename'];
$updateData['id_proof_type'] = $files['id_proof_type'];
$updateData['id_proof_number'] = $files['id_proof_number'];
}
}
// Upload driving license
if (isset($files['driving_license']) && $files['driving_license']['error'] == 0) {
$uploadDir = UPLOAD_DIR . 'documents/';
$result = uploadFile($files['driving_license'], $uploadDir, ['jpg', 'jpeg', 'png', 'pdf']);
if ($result['success']) {
$updateData['driving_license_file'] = $result['filename'];
$updateData['driving_license_number'] = $files['driving_license_number'];
$updateData['license_expiry'] = $files['license_expiry'];
}
}
if (!empty($updateData)) {
$this->db->update('users', $updateData, 'id = :id', ['id' => $userId]);
}
return ['success' => true, 'message' => 'Documents uploaded successfully'];
} catch (Exception $e) {
logError('Document upload error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to upload documents'];
}
}
}
// Initialize Auth
$auth = new Auth();
// Check remember me
if (!$auth->isLoggedIn()) {
$auth->checkRememberMe();
}
?>

Frontend Pages

Main Landing Page

File: index.php

```php
<?php
require_once 'includes/config.php';

// Get featured vehicles
$vehicle = new Vehicle();
$featuredVehicles = $vehicle->getVehicles(['featured' => true, 'limit' => 6]);

// Get vehicle categories
$categories = $db->getRows(
"SELECT * FROM vehicle_categories WHERE is_active = 1 ORDER BY sort_order ASC"
);

// Get popular locations (for search)
$popularLocations = [
'New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix',
'Philadelphia', 'San Antonio', 'San Diego', 'Dallas', 'San Jose'
];
?>





<?php echo APP_NAME; ?> - Rent Cars Online

<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Select2 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet" />
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/style.css">

        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link active" href="index.php">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#how-it-works">How It Works</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#vehicles">Vehicles</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#categories">Categories</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#contact">Contact</a>
</li>
<?php if ($auth->isLoggedIn()): ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown">
<i class="fas fa-user-circle me-1"></i>
<?php echo htmlspecialchars($_SESSION['user_name']); ?>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="<?php 
echo $_SESSION['user_role'] == 'admin' ? 'admin/dashboard.php' : 
($_SESSION['user_role'] == 'agency' ? 'agency/dashboard.php' : 
($_SESSION['user_role'] == 'driver' ? 'driver/dashboard.php' : 'customer/dashboard.php')); 
?>">
<i class="fas fa-tachometer-alt me-2"></i>Dashboard
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item text-danger" href="logout.php">
<i class="fas fa-sign-out-alt me-2"></i>Logout
</a>
</li>
</ul>
</li>
<?php else: ?>
<li class="nav-item">
<a class="nav-link" href="login.php">Login</a>
</li>
<li class="nav-item">
<a class="btn btn-primary ms-2" href="register.php">Sign Up</a>
</li>
<?php endif; ?>
</ul>
</div>
</div>
</nav>
<!-- Hero Section with Search -->
<section class="hero-section bg-primary text-white py-5 mt-5">
<div class="container py-5">
<div class="row align-items-center">
<div class="col-lg-6">
<h1 class="display-4 fw-bold mb-4">Find Your Perfect Rental Car</h1>
<p class="lead mb-4">Choose from thousands of vehicles at the best prices. Easy booking, secure payments, and 24/7 support.</p>
</div>
<div class="col-lg-6">
<div class="search-card bg-white p-4 rounded-4 shadow">
<h4 class="text-dark mb-4">Search for a vehicle</h4>
<form action="customer/search.php" method="GET" id="searchForm">
<div class="mb-3">
<label class="form-label text-dark">
<i class="fas fa-map-marker-alt text-primary me-1"></i>Pickup Location
</label>
<select class="form-select select2" name="location" required>
<option value="">Select location</option>
<?php foreach ($popularLocations as $location): ?>
<option value="<?php echo $location; ?>"><?php echo $location; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label text-dark">
<i class="fas fa-calendar-alt text-primary me-1"></i>Pickup Date
</label>
<input type="date" class="form-control" name="pickup_date" 
min="<?php echo date('Y-m-d'); ?>" 
value="<?php echo date('Y-m-d'); ?>" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label text-dark">
<i class="fas fa-clock text-primary me-1"></i>Pickup Time
</label>
<input type="time" class="form-control" name="pickup_time" 
value="10:00" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label text-dark">
<i class="fas fa-calendar-alt text-primary me-1"></i>Return Date
</label>
<input type="date" class="form-control" name="return_date" 
min="<?php echo date('Y-m-d', strtotime('+1 day')); ?>" 
value="<?php echo date('Y-m-d', strtotime('+3 days')); ?>" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label text-dark">
<i class="fas fa-clock text-primary me-1"></i>Return Time
</label>
<input type="time" class="form-control" name="return_time" 
value="10:00" required>
</div>
</div>
<button type="submit" class="btn btn-primary w-100 py-3">
<i class="fas fa-search me-2"></i>Search Available Vehicles
</button>
</form>
</div>
</div>
</div>
</div>
</section>
<!-- How It Works -->
<section id="how-it-works" class="py-5">
<div class="container">
<h2 class="text-center fw-bold mb-5">How It Works</h2>
<div class="row g-4">
<div class="col-md-3">
<div class="card h-100 border-0 shadow-sm text-center p-4">
<div class="step-icon bg-primary text-white rounded-circle mx-auto mb-3">
<i class="fas fa-search"></i>
</div>
<h5>1. Search</h5>
<p class="text-muted">Enter your location, dates, and browse available vehicles</p>
</div>
</div>
<div class="col-md-3">
<div class="card h-100 border-0 shadow-sm text-center p-4">
<div class="step-icon bg-primary text-white rounded-circle mx-auto mb-3">
<i class="fas fa-car"></i>
</div>
<h5>2. Choose</h5>
<p class="text-muted">Select your preferred vehicle and any add-ons</p>
</div>
</div>
<div class="col-md-3">
<div class="card h-100 border-0 shadow-sm text-center p-4">
<div class="step-icon bg-primary text-white rounded-circle mx-auto mb-3">
<i class="fas fa-credit-card"></i>
</div>
<h5>3. Book & Pay</h5>
<p class="text-muted">Complete your booking with secure payment</p>
</div>
</div>
<div class="col-md-3">
<div class="card h-100 border-0 shadow-sm text-center p-4">
<div class="step-icon bg-primary text-white rounded-circle mx-auto mb-3">
<i class="fas fa-key"></i>
</div>
<h5>4. Drive</h5>
<p class="text-muted">Pick up your car and hit the road</p>
</div>
</div>
</div>
</div>
</section>
<!-- Why Choose Us -->
<section class="py-5 bg-light">
<div class="container">
<h2 class="text-center fw-bold mb-5">Why Choose Us</h2>
<div class="row g-4">
<div class="col-md-4">
<div class="d-flex align-items-start">
<div class="feature-icon bg-primary bg-opacity-10 text-primary rounded-circle p-3 me-3">
<i class="fas fa-tag fa-2x"></i>
</div>
<div>
<h5>Best Price Guarantee</h5>
<p class="text-muted">Find a lower price? We'll match it and give you 10% off</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="d-flex align-items-start">
<div class="feature-icon bg-primary bg-opacity-10 text-primary rounded-circle p-3 me-3">
<i class="fas fa-clock fa-2x"></i>
</div>
<div>
<h5>24/7 Customer Support</h5>
<p class="text-muted">Our team is always here to help you anytime, anywhere</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="d-flex align-items-start">
<div class="feature-icon bg-primary bg-opacity-10 text-primary rounded-circle p-3 me-3">
<i class="fas fa-shield-alt fa-2x"></i>
</div>
<div>
<h5>Secure Payments</h5>
<p class="text-muted">Your transactions are protected with bank-level security</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="d-flex align-items-start">
<div class="feature-icon bg-primary bg-opacity-10 text-primary rounded-circle p-3 me-3">
<i class="fas fa-car-side fa-2x"></i>
</div>
<div>
<h5>Wide Selection</h5>
<p class="text-muted">Choose from thousands of vehicles across all categories</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="d-flex align-items-start">
<div class="feature-icon bg-primary bg-opacity-10 text-primary rounded-circle p-3 me-3">
<i class="fas fa-map-marked-alt fa-2x"></i>
</div>
<div>
<h5>Multiple Locations</h5>
<p class="text-muted">Pick up and drop off at convenient locations</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="d-flex align-items-start">
<div class="feature-icon bg-primary bg-opacity-10 text-primary rounded-circle p-3 me-3">
<i class="fas fa-star fa-2x"></i>
</div>
<div>
<h5>No Hidden Fees</h5>
<p class="text-muted">What you see is what you pay - transparent pricing</p>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Vehicle Categories -->
<section id="categories" class="py-5">
<div class="container">
<h2 class="text-center fw-bold mb-5">Browse by Category</h2>
<div class="row g-4">
<?php foreach ($categories as $category): ?>
<div class="col-lg-3 col-md-4 col-6">
<a href="customer/search.php?category=<?php echo $category['id']; ?>" class="text-decoration-none">
<div class="card h-100 border-0 shadow-sm text-center p-4 category-card">
<div class="category-icon mb-3">
<i class="fas <?php echo $category['icon']; ?> fa-3x text-primary"></i>
</div>
<h6 class="mb-0"><?php echo htmlspecialchars($category['name']); ?></h6>
</div>
</a>
</div>
<?php endforeach; ?>
</div>
</div>
</section>
<!-- Featured Vehicles -->
<section id="vehicles" class="py-5 bg-light">
<div class="container">
<h2 class="text-center fw-bold mb-5">Featured Vehicles</h2>
<div class="row g-4">
<?php foreach ($featuredVehicles as $vehicle): 
$make = $db->getRow("SELECT name FROM vehicle_makes WHERE id = ?", [$vehicle['make_id']]);
$model = $db->getRow("SELECT name FROM vehicle_models WHERE id = ?", [$vehicle['model_id']]);
$images = json_decode($vehicle['images'], true);
$image = $images && !empty($images) ? $images[0] : 'default-car.jpg';
?>
<div class="col-lg-4 col-md-6">
<div class="card h-100 border-0 shadow-sm vehicle-card">
<div class="position-relative">
<img src="uploads/vehicles/<?php echo $image; ?>" 
class="card-img-top" alt="<?php echo $make['name'] . ' ' . $model['name']; ?>"
style="height: 200px; object-fit: cover;">
<?php if ($vehicle['status'] === 'available'): ?>
<span class="badge bg-success position-absolute top-0 end-0 m-3">Available</span>
<?php else: ?>
<span class="badge bg-secondary position-absolute top-0 end-0 m-3">Unavailable</span>
<?php endif; ?>
<?php if ($vehicle['rating'] > 0): ?>
<div class="position-absolute bottom-0 start-0 m-3 bg-dark bg-opacity-75 text-white px-2 py-1 rounded">
<i class="fas fa-star text-warning me-1"></i>
<?php echo number_format($vehicle['rating'], 1); ?> (<?php echo $vehicle['total_reviews']; ?>)
</div>
<?php endif; ?>
</div>
<div class="card-body">
<h5 class="card-title"><?php echo $make['name'] . ' ' . $model['name']; ?></h5>
<div class="d-flex flex-wrap gap-2 mb-2">
<span class="badge bg-light text-dark">
<i class="fas fa-users me-1"></i><?php echo $model['seating_capacity']; ?> seats
</span>
<span class="badge bg-light text-dark">
<i class="fas fa-gas-pump me-1"></i><?php echo ucfirst($model['fuel_type']); ?>
</span>
<span class="badge bg-light text-dark">
<i class="fas fa-cog me-1"></i><?php echo ucfirst($model['transmission']); ?>
</span>
</div>
<p class="card-text text-muted small">
<i class="fas fa-map-marker-alt me-1"></i>
<?php echo htmlspecialchars($vehicle['pickup_location']); ?>
</p>
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="h5 text-primary"><?php echo formatAmount($vehicle['price_per_day']); ?></span>
<small class="text-muted">/day</small>
</div>
<a href="customer/vehicle_details.php?id=<?php echo $vehicle['id']; ?>" 
class="btn btn-outline-primary">
View Details
</a>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="text-center mt-4">
<a href="customer/search.php" class="btn btn-primary btn-lg">
View All Vehicles <i class="fas fa-arrow-right ms-2"></i>
</a>
</div>
</div>
</section>
<!-- Testimonials -->
<section class="py-5">
<div class="container">
<h2 class="text-center fw-bold mb-5">What Our Customers Say</h2>
<div class="row">
<div class="col-md-4 mb-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body p-4">
<div class="mb-3">
<i class="fas fa-star text-warning"></i>
<i class="fas fa-star text-warning"></i>
<i class="fas fa-star text-warning"></i>
<i class="fas fa-star text-warning"></i>
<i class="fas fa-star text-warning"></i>
</div>
<p class="card-text">"Great experience! The booking process was smooth and the car was in excellent condition. Will definitely use again."</p>
<div class="d-flex align-items-center">
<img src="assets/images/avatars/user1.jpg" class="rounded-circle me-3" width="50" alt="User">
<div>
<h6 class="mb-0">Michael Brown</h6>
<small class="text-muted">Business Traveler</small>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body p-4">
<div class="mb-3">
<i class="fas fa-star text-warning"></i>
<i class="fas fa-star text-warning"></i>
<i class="fas fa-star text-warning"></i>
<i class="fas fa-star text-warning"></i>
<i class="fas fa-star text-warning"></i>
</div>
<p class="card-text">"Excellent customer service! Had to change my booking last minute and they were very helpful. Highly recommended!"</p>
<div class="d-flex align-items-center">
<img src="assets/images/avatars/user2.jpg" class="rounded-circle me-3" width="50" alt="User">
<div>
<h6

File: index.php (continued)

                                    <h6 class="mb-0">Sarah Johnson</h6>
<small class="text-muted">Family Vacation</small>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body p-4">
<div class="mb-3">
<i class="fas fa-star text-warning"></i>
<i class="fas fa-star text-warning"></i>
<i class="fas fa-star text-warning"></i>
<i class="fas fa-star text-warning"></i>
<i class="fas fa-star-half-alt text-warning"></i>
</div>
<p class="card-text">"Great selection of vehicles and competitive prices. The mobile app made it easy to manage my booking."</p>
<div class="d-flex align-items-center">
<img src="assets/images/avatars/user3.jpg" class="rounded-circle me-3" width="50" alt="User">
<div>
<h6 class="mb-0">David Chen</h6>
<small class="text-muted">Weekend Getaway</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Partner Agencies -->
<section class="py-5 bg-light">
<div class="container">
<h2 class="text-center fw-bold mb-5">Trusted Partner Agencies</h2>
<div class="row justify-content-center align-items-center">
<?php
$agencies = $db->getRows(
"SELECT * FROM agencies WHERE is_verified = 1 AND status = 'active' ORDER BY rating DESC LIMIT 6"
);
foreach ($agencies as $agency):
?>
<div class="col-lg-2 col-md-4 col-6 mb-4">
<div class="text-center">
<img src="uploads/agencies/<?php echo $agency['logo']; ?>" 
alt="<?php echo htmlspecialchars($agency['agency_name']); ?>"
class="img-fluid" style="max-height: 60px; filter: grayscale(100%); opacity: 0.7;">
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</section>
<!-- Download App Section -->
<section class="py-5">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-6">
<h2 class="fw-bold mb-4">Download Our Mobile App</h2>
<p class="lead mb-4">Manage your bookings, track your rentals, and get exclusive deals on the go.</p>
<div class="d-flex gap-3">
<a href="#" class="btn btn-dark btn-lg">
<i class="fab fa-apple me-2"></i> App Store
</a>
<a href="#" class="btn btn-dark btn-lg">
<i class="fab fa-google-play me-2"></i> Google Play
</a>
</div>
<div class="mt-4">
<div class="d-flex align-items-center mb-2">
<i class="fas fa-check-circle text-success me-2"></i>
<span>Real-time vehicle tracking</span>
</div>
<div class="d-flex align-items-center mb-2">
<i class="fas fa-check-circle text-success me-2"></i>
<span>Instant booking confirmation</span>
</div>
<div class="d-flex align-items-center mb-2">
<i class="fas fa-check-circle text-success me-2"></i>
<span>Secure in-app payments</span>
</div>
<div class="d-flex align-items-center mb-2">
<i class="fas fa-check-circle text-success me-2"></i>
<span>24/7 customer support chat</span>
</div>
</div>
</div>
<div class="col-lg-6 text-center">
<img src="assets/images/mobile-app.png" alt="Mobile App" class="img-fluid" style="max-height: 400px;">
</div>
</div>
</div>
</section>
<!-- Newsletter -->
<section class="py-5 bg-primary text-white">
<div class="container text-center">
<h3 class="fw-bold mb-3">Subscribe to Our Newsletter</h3>
<p class="mb-4">Get the latest deals, promotions, and travel tips straight to your inbox.</p>
<form action="subscribe.php" method="POST" class="row justify-content-center">
<div class="col-md-6">
<div class="input-group">
<input type="email" class="form-control form-control-lg" name="email" 
placeholder="Enter your email address" required>
<button type="submit" class="btn btn-light btn-lg">
Subscribe <i class="fas fa-paper-plane ms-2"></i>
</button>
</div>
</div>
</form>
</div>
</section>
<!-- Footer -->
<footer id="contact" class="bg-dark text-white py-5">
<div class="container">
<div class="row">
<div class="col-md-4 mb-4">
<h5><i class="fas fa-car me-2"></i><?php echo APP_NAME; ?></h5>
<p class="text-white-50">Your trusted partner for car rentals. Wide selection, best prices, and exceptional service.</p>
<div class="social-links">
<a href="#" class="text-white me-2"><i class="fab fa-facebook fa-lg"></i></a>
<a href="#" class="text-white me-2"><i class="fab fa-twitter fa-lg"></i></a>
<a href="#" class="text-white me-2"><i class="fab fa-instagram fa-lg"></i></a>
<a href="#" class="text-white me-2"><i class="fab fa-linkedin fa-lg"></i></a>
</div>
</div>
<div class="col-md-2 mb-4">
<h6>Quick Links</h6>
<ul class="list-unstyled">
<li><a href="index.php" class="text-white-50">Home</a></li>
<li><a href="#how-it-works" class="text-white-50">How It Works</a></li>
<li><a href="#vehicles" class="text-white-50">Vehicles</a></li>
<li><a href="#categories" class="text-white-50">Categories</a></li>
<li><a href="#contact" class="text-white-50">Contact</a></li>
</ul>
</div>
<div class="col-md-3 mb-4">
<h6>For Partners</h6>
<ul class="list-unstyled">
<li><a href="register.php?role=agency" class="text-white-50">List Your Vehicles</a></li>
<li><a href="agency/login.php" class="text-white-50">Agency Login</a></li>
<li><a href="driver/register.php" class="text-white-50">Become a Driver</a></li>
<li><a href="#" class="text-white-50">Partner Support</a></li>
</ul>
</div>
<div class="col-md-3 mb-4">
<h6>Contact Us</h6>
<ul class="list-unstyled text-white-50">
<li><i class="fas fa-map-marker-alt me-2"></i><?php echo SITE_ADDRESS; ?></li>
<li><i class="fas fa-phone me-2"></i><?php echo SITE_PHONE; ?></li>
<li><i class="fas fa-envelope me-2"></i><?php echo SITE_EMAIL; ?></li>
<li><i class="fas fa-clock me-2"></i>24/7 Customer Support</li>
</ul>
</div>
</div>
<hr class="border-secondary">
<div class="row">
<div class="col-md-6">
<p class="text-white-50 mb-0">&copy; <?php echo date('Y'); ?> <?php echo APP_NAME; ?>. All rights reserved.</p>
</div>
<div class="col-md-6 text-md-end">
<a href="terms.php" class="text-white-50 me-3">Terms of Service</a>
<a href="privacy.php" class="text-white-50 me-3">Privacy Policy</a>
<a href="cookies.php" class="text-white-50">Cookie Policy</a>
</div>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"></script>
<script src="assets/js/main.js"></script>
<script>
$(document).ready(function() {
$('.select2').select2({
theme: 'bootstrap-5'
});
// Set min dates for return date based on pickup date
$('#pickup_date').change(function() {
var pickupDate = $(this).val();
if (pickupDate) {
var nextDay = new Date(pickupDate);
nextDay.setDate(nextDay.getDate() + 1);
var minReturn = nextDay.toISOString().split('T')[0];
$('#return_date').attr('min', minReturn);
if ($('#return_date').val() < minReturn) {
$('#return_date').val(minReturn);
}
}
});
});
</script>
<style>
.step-icon {
width: 70px;
height: 70px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.8rem;
}
.category-card {
transition: transform 0.3s ease;
}
.category-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0,0,0,0.1) !important;
}
.vehicle-card {
transition: transform 0.3s ease;
}
.vehicle-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 40px rgba(0,0,0,0.15) !important;
}
.feature-icon {
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
}
.hero-section {
margin-top: 76px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.search-card {
backdrop-filter: blur(10px);
}
.select2-container--bootstrap-5 .select2-selection {
min-height: 45px;
padding: 0.375rem 0.75rem;
}
</style>
</body>
</html>

Login Page

File: login.php

<?php
require_once 'includes/config.php';
// Redirect if already logged in
if ($auth->isLoggedIn()) {
if ($_SESSION['user_role'] == 'admin') {
redirect('/admin/dashboard.php');
} elseif ($_SESSION['user_role'] == 'agency') {
redirect('/agency/dashboard.php');
} elseif ($_SESSION['user_role'] == 'driver') {
redirect('/driver/dashboard.php');
} else {
redirect('/customer/dashboard.php');
}
}
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = sanitize($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
$remember = isset($_POST['remember']);
if (empty($email) || empty($password)) {
$error = 'Please enter email and password';
} else {
$result = $auth->login($email, $password, $remember);
if ($result['success']) {
$user = $result['user'];
// Redirect based on role
if ($user['role'] == 'admin') {
redirect('/admin/dashboard.php');
} elseif ($user['role'] == 'agency') {
redirect('/agency/dashboard.php');
} elseif ($user['role'] == 'driver') {
redirect('/driver/dashboard.php');
} else {
redirect('/customer/dashboard.php');
}
} else {
$error = $result['error'];
}
}
}
// Check for session messages
if (isset($_SESSION['success'])) {
$success = $_SESSION['success'];
unset($_SESSION['success']);
}
if (isset($_SESSION['error'])) {
$error = $_SESSION['error'];
unset($_SESSION['error']);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - <?php echo APP_NAME; ?></title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body class="bg-light">
<div class="container">
<div class="row justify-content-center align-items-center min-vh-100">
<div class="col-md-6 col-lg-5">
<div class="card shadow-lg border-0 rounded-lg">
<div class="card-header bg-primary text-white text-center py-4">
<h3 class="mb-0">
<i class="fas fa-car me-2"></i>
<?php echo APP_NAME; ?>
</h3>
<p class="mb-0 text-white-50">Sign in to your account</p>
</div>
<div class="card-body p-5">
<?php if ($error): ?>
<div class="alert alert-danger alert-dismissible fade show">
<i class="fas fa-exclamation-circle me-2"></i>
<?php echo $error; ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success alert-dismissible fade show">
<i class="fas fa-check-circle me-2"></i>
<?php echo $success; ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form method="POST" action="" onsubmit="return validateLogin()">
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<div class="mb-4">
<label for="email" class="form-label">
<i class="fas fa-envelope me-2"></i>Email Address
</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-envelope"></i></span>
<input type="email" class="form-control" id="email" name="email" 
placeholder="Enter your email" value="<?php echo htmlspecialchars($_POST['email'] ?? ''); ?>" required>
</div>
</div>
<div class="mb-4">
<label for="password" class="form-label">
<i class="fas fa-lock me-2"></i>Password
</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
<input type="password" class="form-control" id="password" name="password" 
placeholder="Enter your password" required>
<button class="btn btn-outline-secondary" type="button" onclick="togglePassword()">
<i class="fas fa-eye" id="togglePasswordIcon"></i>
</button>
</div>
</div>
<div class="mb-4 form-check">
<input type="checkbox" class="form-check-input" id="remember" name="remember">
<label class="form-check-label" for="remember">Remember me</label>
<a href="forgot_password.php" class="float-end text-decoration-none">
Forgot Password?
</a>
</div>
<button type="submit" class="btn btn-primary w-100 py-2 mb-3">
<i class="fas fa-sign-in-alt me-2"></i>Sign In
</button>
</form>
<div class="text-center mb-4">
<p class="mb-0">
Don't have an account? 
<a href="register.php" class="text-decoration-none">Register here</a>
</p>
</div>
<div class="text-center">
<p class="text-muted mb-2">Or sign in as:</p>
<div class="d-flex justify-content-center gap-2">
<a href="demo-login.php?role=customer" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-user me-1"></i>Customer
</a>
<a href="demo-login.php?role=agency" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-building me-1"></i>Agency
</a>
<a href="demo-login.php?role=driver" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-truck me-1"></i>Driver
</a>
</div>
</div>
<hr class="my-4">
<div class="text-center">
<a href="index.php" class="text-decoration-none">
<i class="fas fa-arrow-left me-1"></i>Back to Home
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script>
function togglePassword() {
const password = document.getElementById('password');
const icon = document.getElementById('togglePasswordIcon');
if (password.type === 'password') {
password.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
password.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
}
function validateLogin() {
const email = document.getElementById('email').value.trim();
const password = document.getElementById('password').value;
if (email === '') {
alert('Please enter your email address');
return false;
}
if (password === '') {
alert('Please enter your password');
return false;
}
return true;
}
</script>
</body>
</html>

Registration Page

File: register.php

<?php
require_once 'includes/config.php';
// Redirect if already logged in
if ($auth->isLoggedIn()) {
redirect('/index.php');
}
$error = '';
$success = '';
$role = $_GET['role'] ?? 'customer';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$role = $_POST['role'] ?? 'customer';
// Common validation for all roles
$data = [
'first_name' => sanitize($_POST['first_name'] ?? ''),
'last_name' => sanitize($_POST['last_name'] ?? ''),
'email' => sanitize($_POST['email'] ?? ''),
'phone' => sanitize($_POST['phone'] ?? ''),
'password' => $_POST['password'] ?? '',
'confirm_password' => $_POST['confirm_password'] ?? '',
'role' => $role
];
$errors = [];
// Validate common fields
if (empty($data['first_name'])) {
$errors[] = 'First name is required';
}
if (empty($data['last_name'])) {
$errors[] = 'Last name is required';
}
if (empty($data['email'])) {
$errors[] = 'Email is required';
} elseif (!validateEmail($data['email'])) {
$errors[] = 'Please enter a valid email address';
}
if (empty($data['phone'])) {
$errors[] = 'Phone number is required';
}
if (empty($data['password'])) {
$errors[] = 'Password is required';
} elseif (strlen($data['password']) < 8) {
$errors[] = 'Password must be at least 8 characters';
} elseif (!preg_match('/[A-Z]/', $data['password'])) {
$errors[] = 'Password must contain at least one uppercase letter';
} elseif (!preg_match('/[a-z]/', $data['password'])) {
$errors[] = 'Password must contain at least one lowercase letter';
} elseif (!preg_match('/[0-9]/', $data['password'])) {
$errors[] = 'Password must contain at least one number';
}
if ($data['password'] !== $data['confirm_password']) {
$errors[] = 'Passwords do not match';
}
// Role-specific validation
if ($role === 'agency') {
$agencyData = [
'agency_name' => sanitize($_POST['agency_name'] ?? ''),
'registration_number' => sanitize($_POST['registration_number'] ?? ''),
'tax_number' => sanitize($_POST['tax_number'] ?? ''),
'business_address' => sanitize($_POST['business_address'] ?? ''),
'business_city' => sanitize($_POST['business_city'] ?? ''),
'business_state' => sanitize($_POST['business_state'] ?? ''),
'business_country' => sanitize($_POST['business_country'] ?? ''),
'business_postal_code' => sanitize($_POST['business_postal_code'] ?? ''),
'business_phone' => sanitize($_POST['business_phone'] ?? ''),
'business_email' => sanitize($_POST['business_email'] ?? '')
];
if (empty($agencyData['agency_name'])) {
$errors[] = 'Agency name is required';
}
if (empty($agencyData['business_address'])) {
$errors[] = 'Business address is required';
}
$data = array_merge($data, $agencyData);
}
if ($role === 'driver' && isset($_POST['agency_id'])) {
$driverData = [
'agency_id' => intval($_POST['agency_id']),
'license_number' => sanitize($_POST['license_number'] ?? ''),
'license_expiry' => $_POST['license_expiry'] ?? '',
'experience_years' => intval($_POST['experience_years'] ?? 0)
];
if (empty($driverData['license_number'])) {
$errors[] = 'License number is required';
}
if (empty($driverData['license_expiry'])) {
$errors[] = 'License expiry date is required';
}
$data = array_merge($data, $driverData);
}
// If no errors, attempt registration
if (empty($errors)) {
$result = $auth->register($data);
if ($result['success']) {
$_SESSION['success'] = $result['message'];
redirect('/login.php');
} else {
$error = $result['error'];
}
} else {
$error = implode('<br>', $errors);
}
}
// Get agencies for driver registration
$agencies = [];
if ($role === 'driver') {
$agencies = $db->getRows(
"SELECT id, agency_name FROM agencies WHERE status = 'active' AND is_verified = 1 ORDER BY agency_name"
);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register - <?php echo APP_NAME; ?></title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Select2 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet" />
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body class="bg-light">
<div class="container py-4">
<div class="row justify-content-center">
<div class="col-md-10 col-lg-8">
<div class="card shadow-lg border-0 rounded-lg">
<div class="card-header bg-primary text-white text-center py-4">
<h3 class="mb-0">
<i class="fas fa-user-plus me-2"></i>
Create an Account
</h3>
<p class="mb-0 text-white-50">Join <?php echo APP_NAME; ?> today</p>
</div>
<div class="card-body p-5">
<!-- Role Selection Tabs -->
<ul class="nav nav-pills nav-justified mb-4" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link <?php echo $role === 'customer' ? 'active' : ''; ?>" 
href="?role=customer">
<i class="fas fa-user me-2"></i>Customer
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link <?php echo $role === 'agency' ? 'active' : ''; ?>" 
href="?role=agency">
<i class="fas fa-building me-2"></i>Agency
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link <?php echo $role === 'driver' ? 'active' : ''; ?>" 
href="?role=driver">
<i class="fas fa-truck me-2"></i>Driver
</a>
</li>
</ul>
<?php if ($error): ?>
<div class="alert alert-danger alert-dismissible fade show">
<i class="fas fa-exclamation-circle me-2"></i>
<?php echo $error; ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form method="POST" action="" onsubmit="return validateRegistration()">
<input type="hidden" name="role" value="<?php echo $role; ?>">
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<!-- Personal Information (Common for all roles) -->
<h5 class="mb-3">Personal Information</h5>
<div class="row">
<div class="col-md-6 mb-3">
<label for="first_name" class="form-label">First Name *</label>
<input type="text" class="form-control" id="first_name" name="first_name" 
value="<?php echo htmlspecialchars($_POST['first_name'] ?? ''); ?>" required>
</div>
<div class="col-md-6 mb-3">
<label for="last_name" class="form-label">Last Name *</label>
<input type="text" class="form-control" id="last_name" name="last_name" 
value="<?php echo htmlspecialchars($_POST['last_name'] ?? ''); ?>" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="email" class="form-label">Email Address *</label>
<input type="email" class="form-control" id="email" name="email" 
value="<?php echo htmlspecialchars($_POST['email'] ?? ''); ?>" required>
</div>
<div class="col-md-6 mb-3">
<label for="phone" class="form-label">Phone Number *</label>
<input type="tel" class="form-control" id="phone" name="phone" 
value="<?php echo htmlspecialchars($_POST['phone'] ?? ''); ?>" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="password" class="form-label">Password *</label>
<div class="input-group">
<input type="password" class="form-control" id="password" name="password" required>
<button class="btn btn-outline-secondary" type="button" onclick="togglePassword('password')">
<i class="fas fa-eye" id="togglePasswordIcon1"></i>
</button>
</div>
<small class="text-muted">Min 8 chars, 1 uppercase, 1 lowercase, 1 number</small>
</div>
<div class="col-md-6 mb-3">
<label for="confirm_password" class="form-label">Confirm Password *</label>
<div class="input-group">
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
<button class="btn btn-outline-secondary" type="button" onclick="togglePassword('confirm_password')">
<i class="fas fa-eye" id="togglePasswordIcon2"></i>
</button>
</div>
</div>
</div>
<!-- Agency Specific Fields -->
<?php if ($role === 'agency'): ?>
<hr class="my-4">
<h5 class="mb-3">Agency Information</h5>
<div class="mb-3">
<label for="agency_name" class="form-label">Agency/Business Name *</label>
<input type="text" class="form-control" id="agency_name" name="agency_name" 
value="<?php echo htmlspecialchars($_POST['agency_name'] ?? ''); ?>" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="registration_number" class="form-label">Business Registration Number</label>
<input type="text" class="form-control" id="registration_number" name="registration_number" 
value="<?php echo htmlspecialchars($_POST['registration_number'] ?? ''); ?>">
</div>
<div class="col-md-6 mb-3">
<label for="tax_number" class="form-label">Tax/VAT Number</label>
<input type="text" class="form-control" id="tax_number" name="tax_number" 
value="<?php echo htmlspecialchars($_POST['tax_number'] ?? ''); ?>">
</div>
</div>
<div class="mb-3">
<label for="business_address" class="form-label">Business Address *</label>
<textarea class="form-control" id="business_address" name="business_address" rows="2" required><?php echo htmlspecialchars($_POST['business_address'] ?? ''); ?></textarea>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label for="business_city" class="form-label">City *</label>
<input type="text" class="form-control" id="business_city" name="business_city" 
value="<?php echo htmlspecialchars($_POST['business_city'] ?? ''); ?>" required>
</div>
<div class="col-md-4 mb-3">
<label for="business_state" class="form-label">State/Province *</label>
<input type="text" class="form-control" id="business_state" name="business_state" 
value="<?php echo htmlspecialchars($_POST['business_state'] ?? ''); ?>" required>
</div>
<div class="col-md-4 mb-3">
<label for="business_postal_code" class="form-label">Postal Code *</label>
<input type="text" class="form-control" id="business_postal_code" name="business_postal_code" 
value="<?php echo htmlspecialchars($_POST['business_postal_code'] ?? ''); ?>" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="business_phone" class="form-label">Business Phone *</label>
<input type="tel" class="form-control" id="business_phone" name="business_phone" 
value="<?php echo htmlspecialchars($_POST['business_phone'] ?? ''); ?>" required>
</div>
<div class="col-md-6 mb-3">
<label for="business_email" class="form-label">Business Email *</label>
<input type="email" class="form-control" id="business_email" name="business_email" 
value="<?php echo htmlspecialchars($_POST['business_email'] ?? ''); ?>" required>
</div>
</div>
<?php endif; ?>
<!-- Driver Specific Fields -->
<?php if ($role === 'driver'): ?>
<hr class="my-4">
<h5 class="mb-3">Driver Information</h5>
<div class="mb-3">
<label for="agency_id" class="form-label">Select Agency *</label>
<select class="form-select select2" id="agency_id" name="agency_id" required>
<option value="">Choose an agency</option>
<?php foreach ($agencies as $agency): ?>
<option value="<?php echo $agency['id']; ?>">
<?php echo htmlspecialchars($agency['agency_name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="license_number" class="form-label">Driver's License Number *</label>
<input type="text" class="form-control" id="license_number" name="license_number" 
value="<?php echo htmlspecialchars($_POST['license_number'] ?? ''); ?>" required>
</div>
<div class="col-md-6 mb-3">
<label for="license_expiry" class="form-label">License Expiry Date *</label>
<input type="date" class="form-control" id="license_expiry" name="license_expiry" 
min="<?php echo date('Y-m-d'); ?>" 
value="<?php echo htmlspecialchars($_POST['license_expiry'] ?? ''); ?>" required>
</div>
</div>
<div class="mb-3">
<label for="experience_years" class="form-label">Years of Driving Experience</label>
<input type="number" class="form-control" id="experience_years" name="experience_years" 
min="0" max="50" value="<?php echo htmlspecialchars($_POST['experience_years'] ?? '0'); ?>">
</div>
<?php endif; ?>
<hr class="my-4">
<div class="mb-4 form-check">
<input type="checkbox" class="form-check-input" id="terms" name="terms" required>
<label class="form-check-label" for="terms">
I agree to the <a href="terms.php" target="_blank">Terms of Service</a> and 
<a href="privacy.php" target="_blank">Privacy Policy</a>
</label>
</div>
<button type="submit" class="btn btn-primary w-100 py-2 mb-3">
<i class="fas fa-user-plus me-2"></i>Create Account
</button>
</form>
<div class="text-center">
<p class="mb-0">
Already have an account? 
<a href="login.php" class="text-decoration-none">Sign In</a>
</p>
</div>
<hr class="my-4">
<div class="text-center">
<a href="index.php" class="text-decoration-none">
<i class="fas fa-arrow-left me-1"></i>Back to Home
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"></script>
<script>
$(document).ready(function() {
$('.select2').select2({
theme: 'bootstrap-5',
width: '100%'
});
});
function togglePassword(fieldId) {
const password = document.getElementById(fieldId);
const icon = document.getElementById('togglePasswordIcon' + (fieldId === 'password' ? '1' : '2'));
if (password.type === 'password') {
password.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
password.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
}
function validateRegistration() {
const firstName = document.getElementById('first_name').value.trim();
const lastName = document.getElementById('last_name').value.trim();
const email = document.getElementById('email').value.trim();
const phone = document.getElementById('phone').value.trim();
const password = document.getElementById('password').value;
const confirm = document.getElementById('confirm_password').value;
const terms = document.getElementById('terms').checked;
// Check required fields
if (firstName === '' || lastName === '' || email === '' || phone === '' || password === '') {
alert('Please fill in all required fields.');
return false;
}
// Email validation
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(email)) {
alert('Please enter a valid email address.');
return false;
}
// Password validation
if (password.length < 8) {
alert('Password must be at least 8 characters long.');
return false;
}
if (!/[A-Z]/.test(password)) {
alert('Password must contain at least one uppercase letter.');
return false;
}
if (!/[a-z]/.test(password)) {
alert('Password must contain at least one lowercase letter.');
return false;
}
if (!/[0-9]/.test(password)) {
alert('Password must contain at least one number.');
return false;
}
if (password !== confirm) {
alert('Passwords do not match.');
return false;
}
// Agency specific validation
const role = '<?php echo $role; ?>';
if (role === 'agency') {
const agencyName = document.getElementById('agency_name')?.value.trim();
const businessAddress = document.getElementById('business_address')?.value.trim();
if (!agencyName || !businessAddress) {
alert('Please fill in all agency information.');
return false;
}
}
if (role === 'driver') {
const agencyId = document.getElementById('agency_id')?.value;
const licenseNumber = document.getElementById('license_number')?.value.trim();
const licenseExpiry = document.getElementById('license_expiry')?.value;
if (!agencyId || !licenseNumber || !licenseExpiry) {
alert('Please fill in all driver information.');
return false;
}
}
if (!terms) {
alert('You must agree to the Terms of Service and Privacy Policy.');
return false;
}
return true;
}
</script>
</body>
</html>

Vehicle Class

File: includes/Vehicle.php

<?php
/**
* Vehicle Class
* Handles all vehicle-related operations
*/
class Vehicle {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Add new vehicle
*/
public function add($agencyId, $data) {
try {
// Generate vehicle ID
$vehicleId = generateVehicleId();
// Handle image uploads
$images = [];
if (isset($_FILES['images']) && !empty($_FILES['images']['name'][0])) {
$uploadDir = UPLOAD_DIR . 'vehicles/';
foreach ($_FILES['images']['tmp_name'] as $key => $tmp_name) {
if ($_FILES['images']['error'][$key] == 0) {
$file = [
'name' => $_FILES['images']['name'][$key],
'type' => $_FILES['images']['type'][$key],
'tmp_name' => $tmp_name,
'error' => $_FILES['images']['error'][$key],
'size' => $_FILES['images']['size'][$key]
];
$result = uploadFile($file, $uploadDir);
if ($result['success']) {
$images[] = $result['filename'];
}
}
}
}
// Prepare features as JSON
$features = isset($data['features']) ? json_encode($data['features']) : null;
// Prepare vehicle data
$vehicleData = [
'vehicle_id' => $vehicleId,
'agency_id' => $agencyId,
'category_id' => $data['category_id'],
'make_id' => $data['make_id'],
'model_id' => $data['model_id'],
'registration_number' => $data['registration_number'],
'vin' => $data['vin'] ?? null,
'color' => $data['color'] ?? null,
'mileage' => $data['mileage'] ?? 0,
'features' => $features,
'description' => $data['description'] ?? null,
'images' => json_encode($images),
'price_per_day' => $data['price_per_day'],
'price_per_week' => $data['price_per_week'] ?? null,
'price_per_month' => $data['price_per_month'] ?? null,
'security_deposit' => $data['security_deposit'] ?? 500,
'minimum_rental_days' => $data['minimum_rental_days'] ?? 1,
'maximum_rental_days' => $data['maximum_rental_days'] ?? 30,
'free_mileage_per_day' => $data['free_mileage_per_day'] ?? FREE_MILEAGE_PER_DAY,
'additional_mileage_charge' => $data['additional_mileage_charge'] ?? ADDITIONAL_MILEAGE_CHARGE,
'late_return_fee_per_hour' => $data['late_return_fee_per_hour'] ?? LATE_RETURN_FEE_PER_HOUR,
'cancellation_policy' => $data['cancellation_policy'] ?? 'moderate',
'insurance_included' => isset($data['insurance_included']) ? 1 : 0,
'insurance_details' => $data['insurance_details'] ?? null,
'gps_tracking_enabled' => isset($data['gps_tracking_enabled']) ? 1 : 0,
'pickup_location' => $data['pickup_location'],
'pickup_instructions' => $data['pickup_instructions'] ?? null,
'return_location' => $data['return_location'] ?? $data['pickup_location'],
'return_instructions' => $data['return_instructions'] ?? null,
'latitude' => $data['latitude'] ?? null,
'longitude' => $data['longitude'] ?? null,
'status' => 'available'
];
$vehicleDbId = $this->db->insert('vehicles', $vehicleData);
if ($vehicleDbId) {
return [
'success' => true,
'vehicle_id' => $vehicleId,
'message' => 'Vehicle added successfully'
];
}
return ['success' => false, 'error' => 'Failed to add vehicle'];
} catch (Exception $e) {
logError('Add vehicle error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Failed to add vehicle: ' . $e->getMessage()];
}
}
/**
* Update vehicle
*/
public function update($vehicleId, $agencyId, $data) {
try {
// Get existing vehicle to check images
$existing = $this->getVehicleById($vehicleId, $agencyId);
if (!$existing) {
return ['success' => false, 'error' => 'Vehicle not found'];
}
// Handle image uploads
$images = json_decode($existing['images'], true) ?: [];
if (isset($_FILES['images']) && !empty($_FILES['images']['name'][0])) {
$uploadDir = UPLOAD_DIR . 'vehicles/';
foreach ($_FILES['images']['tmp_name'] as $key => $tmp_name) {
if ($_FILES['images']['error'][$key] == 0) {
$file = [
'name' => $_FILES['images']['name'][$key],
'type' => $_FILES['images']['type'][$key],
'tmp_name' => $tmp_name,
'error' => $_FILES['images']['error'][$key],
'size' => $_FILES['images']['size'][$key]
];
$result = uploadFile($file, $uploadDir);
if ($result['success']) {
$images[] = $result['filename'];
}
}
}
}
// Handle image deletions
if (isset($data['delete_images']) && is_array($data['delete_images'])) {
foreach ($data['delete_images'] as $image) {
$key = array_search($image, $images);
if ($key !== false) {
// Delete file
$filePath = UPLOAD_DIR . 'vehicles/' . $image;
if (file_exists($filePath)) {
unlink($filePath);
}
unset($images[$key]);
}
}
$images = array_values($images);
}
// Prepare features as JSON
$features = isset($data['features']) ? json_encode($data['features']) : $existing['features'];
// Prepare update data
$updateData = [
'category_id' => $data['category_id'] ?? $existing['category_id'],
'make_id' => $data['make_id'] ?? $existing['make_id'],
'model_id' => $data['model_id'] ?? $existing['model_id'],
'registration_number' => $data['registration_number'] ?? $existing['registration_number'],
'vin' => $data['vin'] ?? $existing['vin'],
'color' => $data['color'] ?? $existing['color'],
'mileage' => $data['mileage'] ?? $existing['mileage'],
'features' => $features,
'description' => $data['description'] ?? $existing['description'],
'images' => json_encode($images),
'price_per_day' => $data['price_per_day'] ?? $existing['price_per_day'],
'price_per_week' => $data['price_per_week'] ?? $existing['price_per_week'],
'price_per_month' => $data['price_per_month'] ?? $existing['price_per_month'],
'security_deposit' => $data['security_deposit'] ?? $existing['security_deposit'],
'minimum_rental_days' => $data['minimum_rental_days'] ?? $existing['minimum_rental_days'],
'maximum_rental_days' => $data['maximum_rental_days'] ?? $existing['maximum_rental_days'],
'free_mileage_per_day' => $data['free_mileage_per_day'] ?? $existing['free_mileage_per_day'],
'additional_mileage_charge' => $data['additional_mileage_charge'] ?? $existing['additional_mileage_charge'],
'late_return_fee_per_hour' => $data['late_return_fee_per_hour'] ?? $existing['late_return_fee_per_hour'],
'cancellation_policy' => $data['cancellation_policy'] ?? $existing['cancellation_policy'],
'insurance_included' => isset($data['insurance_included']) ? 1 : 0,
'insurance_details' => $data['insurance_details'] ?? $existing['insurance_details'],
'gps_tracking_enabled' => isset($data['gps_tracking_enabled']) ? 1 : 0,
'pickup_location' => $data['pickup_location'] ?? $existing['pickup_location'],
'pickup_instructions' => $data['pickup_instructions'] ?? $existing['pickup_instructions'],
'return_location' => $data['return_location'] ?? $existing['return_location'],
'return_instructions' => $data['return_instructions'] ?? $existing['return_instructions'],
'latitude' => $data['latitude'] ?? $existing['latitude'],
'longitude' => $data['longitude'] ?? $existing['longitude'],
'status' => $data['status'] ?? $existing['status']
];
$updated = $this->db->update(
'vehicles',
$updateData,
'id = :id AND agency_id = :agency_id',
['id' => $existing['id'], 'agency_id' => $agencyId]
);
if ($updated) {
return ['success' => true, 'message' => 'Vehicle updated successfully'];
}
return ['success' => false, 'error' => 'No changes made'];
} catch (Exception $e) {
logError('Update vehicle error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Failed to update vehicle'];
}
}
/**
* Delete vehicle
*/
public function delete($vehicleId, $agencyId) {
try {
// Check if vehicle has any upcoming bookings
$bookings = $this->db->getRow(
"SELECT id FROM bookings 
WHERE vehicle_id = ? AND booking_status IN ('pending', 'confirmed') 
AND pickup_date > NOW()",
[$vehicleId]
);
if ($bookings) {
return ['success' => false, 'error' => 'Cannot delete vehicle with upcoming bookings'];
}
// Get vehicle images to delete files
$vehicle = $this->getVehicleById($vehicleId, $agencyId);
if ($vehicle && $vehicle['images']) {
$images = json_decode($vehicle['images'], true);
foreach ($images as $image) {
$filePath = UPLOAD_DIR . 'vehicles/' . $image;
if (file_exists($filePath)) {
unlink($filePath);
}
}
}
// Delete vehicle
$deleted = $this->db->delete(
'vehicles',
'id = :id AND agency_id = :agency_id',
['id' => $vehicleId, 'agency_id' => $agencyId]
);
if ($deleted) {
return ['success' => true, 'message' => 'Vehicle deleted successfully'];
}
return ['success' => false, 'error' => 'Vehicle not found'];
} catch (Exception $e) {
logError('Delete vehicle error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to delete vehicle'];
}
}
/**
* Get vehicle by ID
*/
public function getVehicleById($vehicleId, $agencyId = null) {
$sql = "SELECT v.*, 
mk.name as make_name, 
md.name as model_name,
md.seating_capacity, md.transmission, md.fuel_type,
c.name as category_name,
a.agency_name, a.business_phone, a.business_email, a.logo
FROM vehicles v
JOIN vehicle_makes mk ON v.make_id = mk.id
JOIN vehicle_models md ON v.model_id = md.id
JOIN vehicle_categories c ON v.category_id = c.id
JOIN agencies a ON v.agency_id = a.id
WHERE v.id = ?";
$params = [$vehicleId];
if ($agencyId) {
$sql .= " AND v.agency_id = ?";
$params[] = $agencyId;
}
return $this->db->getRow($sql, $params);
}
/**
* Get agency vehicles
*/
public function getAgencyVehicles($agencyId, $filters = []) {
$sql = "SELECT v.*, 
mk.name as make_name, 
md.name as model_name,
md.seating_capacity, md.transmission, md.fuel_type,
c.name as category_name
FROM vehicles v
JOIN vehicle_makes mk ON v.make_id = mk.id
JOIN vehicle_models md ON v.model_id = md.id
JOIN vehicle_categories c ON v.category_id = c.id
WHERE v.agency_id = :agency_id";
$params = ['agency_id' => $agencyId];
// Apply filters
if (!empty($filters['status'])) {
$sql .= " AND v.status = :status";
$params['status'] = $filters['status'];
}
if (!empty($filters['category_id'])) {
$sql .= " AND v.category_id = :category_id";
$params['category_id'] = $filters['category_id'];
}
if (!empty($filters['make_id'])) {
$sql .= " AND v.make_id = :make_id";
$params['make_id'] = $filters['make_id'];
}
if (!empty($filters['search'])) {
$sql .= " AND (v.registration_number LIKE :search OR mk.name LIKE :search OR md.name LIKE :search)";
$params['search'] = '%' . $filters['search'] . '%';
}
$sql .= " ORDER BY v.created_at DESC";
// Pagination
$page = $filters['page'] ?? 1;
$limit = $filters['limit'] ?? ITEMS_PER_PAGE;
$offset = ($page - 1) * $limit;
$sql .= " LIMIT :limit OFFSET :offset";
$params['limit'] = $limit;
$params['offset'] = $offset;
return $this->db->getRows($sql, $params);
}
/**
* Get all vehicles with filters (for customer search)
*/
public function getVehicles($filters = []) {
$sql = "SELECT v.*, 
mk.name as make_name, 
md.name as model_name,
md.seating_capacity, md.transmission, md.fuel_type,
c.name as category_name,
a.agency_name, a.rating as agency_rating,
(SELECT AVG(rating) FROM reviews WHERE vehicle_id = v.id) as avg_rating,
(SELECT COUNT(*) FROM reviews WHERE vehicle_id = v.id) as review_count
FROM vehicles v
JOIN vehicle_makes mk ON v.make_id = mk.id
JOIN vehicle_models md ON v.model_id = md.id
JOIN vehicle_categories c ON v.category_id = c.id
JOIN agencies a ON v.agency_id = a.id
WHERE v.status = 'available' AND v.is_active = 1 AND a.status = 'active'";
$params = [];
// Apply filters
if (!empty($filters['category_id'])) {
$sql .= " AND v.category_id = :category_id";
$params['category_id'] = $filters['category_id'];
}
if (!empty($filters['make_id'])) {
$sql .= " AND v.make_id = :make_id";
$params['make_id'] = $filters['make_id'];
}
if (!empty($filters['model_id'])) {
$sql .= " AND v.model_id = :model_id";
$params['model_id'] = $filters['model_id'];
}
if (!empty($filters['min_price'])) {
$sql .= " AND v.price_per_day >= :min_price";
$params['min_price'] = $filters['min_price'];
}
if (!empty($filters['max_price'])) {
$sql .= " AND v.price_per_day <= :max_price";
$params['max_price'] = $filters['max_price'];
}
if (!empty($filters['transmission'])) {
$sql .= " AND md.transmission = :transmission";
$params['transmission'] = $filters['transmission'];
}
if (!empty($filters['fuel_type'])) {
$sql .= " AND md.fuel_type = :fuel_type";
$params['fuel_type'] = $filters['fuel_type'];
}
if (!empty($filters['seating_capacity'])) {
$sql .= " AND md.seating_capacity >= :seating_capacity";
$params['seating_capacity'] = $filters['seating_capacity'];
}
if (!empty($filters['location'])) {
$sql .= " AND (v.pickup_location LIKE :location OR v.return_location LIKE :location)";
$params['location'] = '%' . $filters['location'] . '%';
}
if (!empty($filters['search'])) {
$sql .= " AND (mk.name LIKE :search OR md.name LIKE :search)";
$params['search'] = '%' . $filters['search'] . '%';
}
// Sorting
$sort = $filters['sort'] ?? 'recommended';
switch ($sort) {
case 'price_low':
$sql .= " ORDER BY v.price_per_day ASC";
break;
case 'price_high':
$sql .= " ORDER BY v.price_per_day DESC";
break;
case 'rating':
$sql .= " ORDER BY avg_rating DESC, review_count DESC";
break;
case 'newest':
$sql .= " ORDER BY v.created_at DESC";
break;
default:
$sql .= " ORDER BY v.views_count DESC, avg_rating DESC";
}
// Pagination
$page = $filters['page'] ?? 1;
$limit = $filters['limit'] ?? ITEMS_PER_PAGE;
$offset = ($page - 1) * $limit;
$sql .= " LIMIT :limit OFFSET :offset";
$params['limit'] = $limit;
$params['offset'] = $offset;
return $this->db->getRows($sql, $params);
}
/**
* Get available vehicles for specific dates
*/
public function getAvailableVehicles($pickupDate, $returnDate, $filters = []) {
// First, get IDs of booked vehicles for these dates
$bookedIds = $this->db->getRows(
"SELECT DISTINCT vehicle_id FROM bookings 
WHERE booking_status NOT IN ('cancelled', 'completed')
AND (
(pickup_date BETWEEN :pickup_date AND :return_date)
OR (return_date BETWEEN :pickup_date AND :return_date)
OR (pickup_date <= :pickup_date AND return_date >= :return_date)
)",
[
'pickup_date' => $pickupDate,
'return_date' => $returnDate
]
);
$excludeIds = [];
foreach ($bookedIds as $booked) {
$excludeIds[] = $booked['vehicle_id'];
}
// Build main query
$sql = "SELECT v.*, 
mk.name as make_name, 
md.name as model_name,
md.seating_capacity, md.transmission, md.fuel_type,
c.name as category_name,
a.agency_name, a.rating as agency_rating,
(SELECT AVG(rating) FROM reviews WHERE vehicle_id = v.id) as avg_rating,
(SELECT COUNT(*) FROM reviews WHERE vehicle_id = v.id) as review_count
FROM vehicles v
JOIN vehicle_makes mk ON v.make_id = mk.id
JOIN vehicle_models md ON v.model_id = md.id
JOIN vehicle_categories c ON v.category_id = c.id
JOIN agencies a ON v.agency_id = a.id
WHERE v.status = 'available' 
AND v.is_active = 1 
AND a.status = 'active'";
$params = [];
if (!empty($excludeIds)) {
$sql .= " AND v.id NOT IN (" . implode(',', array_fill(0, count($excludeIds), '?')) . ")";
$params = array_merge($params, $excludeIds);
}
// Apply same filters as getVehicles()
if (!empty($filters['category_id'])) {
$sql .= " AND v.category_id = ?";
$params[] = $filters['category_id'];
}
if (!empty($filters['make_id'])) {
$sql .= " AND v.make_id = ?";
$params[] = $filters['make_id'];
}
if (!empty($filters['min_price'])) {
$sql .= " AND v.price_per_day >= ?";
$params[] = $filters['min_price'];
}
if (!empty($filters['max_price'])) {
$sql .= " AND v.price_per_day <= ?";
$params[] = $filters['max_price'];
}
if (!empty($filters['transmission'])) {
$sql .= " AND md.transmission = ?";
$params[] = $filters['transmission'];
}
if (!empty($filters['fuel_type'])) {
$sql .= " AND md.fuel_type = ?";
$params[] = $filters['fuel_type'];
}
if (!empty($filters['seating_capacity'])) {
$sql .= " AND md.seating_capacity >= ?";
$params[] = $filters['seating_capacity'];
}
// Sorting
$sort = $filters['sort'] ?? 'recommended';
switch ($sort) {
case 'price_low':
$sql .= " ORDER BY v.price_per_day ASC";
break;
case 'price_high':
$sql .= " ORDER BY v.price_per_day DESC";
break;
case 'rating':
$sql .= " ORDER BY avg_rating DESC, review_count DESC";
break;
default:
$sql .= " ORDER BY v.views_count DESC, avg_rating DESC";
}
// Pagination
$page = $filters['page'] ?? 1;
$limit = $filters['limit'] ?? ITEMS_PER_PAGE;
$offset = ($page - 1) * $limit;
$sql .= " LIMIT ? OFFSET ?";
$params[] = $limit;
$params[] = $offset;
return $this->db->getRows($sql, $params);
}
/**
* Get vehicle availability for calendar
*/
public function getVehicleAvailability($vehicleId, $startDate, $endDate) {
$bookings = $this->db->getRows(
"SELECT pickup_date, return_date, booking_status 
FROM bookings 
WHERE vehicle_id = ? 
AND booking_status NOT IN ('cancelled', 'completed')
AND (
(pickup_date BETWEEN ? AND ?)
OR (return_date BETWEEN ? AND ?)
OR (pickup_date <= ? AND return_date >= ?)
)",
[$vehicleId, $startDate, $endDate, $startDate, $endDate, $startDate, $endDate]
);
$availability = [];
$currentDate = $startDate;
while ($currentDate <= $endDate) {
$isAvailable = true;
foreach ($bookings as $booking) {
if ($currentDate >= $booking['pickup_date'] && $currentDate <= $booking['return_date']) {
$isAvailable = false;
break;
}
}
$availability[] = [
'date' => $currentDate,
'available' => $isAvailable
];
$currentDate = date('Y-m-d', strtotime($currentDate . ' +1 day'));
}
return $availability;
}
/**
* Increment view count
*/
public function incrementViews($vehicleId) {
$this->db->query(
"UPDATE vehicles SET views_count = views_count + 1 WHERE id = ?",
[$vehicleId]
);
}
/**
* Get vehicle makes
*/
public function getMakes() {
return $this->db->getRows(
"SELECT * FROM vehicle_makes WHERE is_active = 1 ORDER BY name"
);
}
/**
* Get vehicle models by make
*/
public function getModelsByMake($makeId) {
return $this->db->getRows(
"SELECT * FROM vehicle_models WHERE make_id = ? AND is_active = 1 ORDER BY name",
[$makeId]
);
}
/**
* Get vehicle categories
*/
public function getCategories() {
return $this->db->getRows(
"SELECT * FROM vehicle_categories WHERE is_active = 1 ORDER BY sort_order"
);
}
/**
* Update vehicle status (for maintenance, etc.)
*/
public function updateStatus($vehicleId, $agencyId, $status, $notes = null) {
$validStatuses = ['available', 'booked', 'maintenance', 'unavailable'];
if (!in_array($status, $validStatuses)) {
return ['success' => false, 'error' => 'Invalid status'];
}
$updated = $this->db->update(
'vehicles',
['status' => $status, 'maintenance_notes' => $notes],
'id = :id AND agency_id = :agency_id',
['id' => $vehicleId, 'agency_id' => $agencyId]
);
if ($updated) {
return ['success' => true, 'message' => 'Vehicle status updated'];
}
return ['success' => false, 'error' => 'Failed to update status'];
}
/**
* Get vehicle statistics for agency
*/
public function getAgencyVehicleStats($agencyId) {
$stats = [];
// Total vehicles
$stats['total'] = $this->db->getValue(
"SELECT COUNT(*) FROM vehicles WHERE agency_id = ?",
[$agencyId]
);
// Available vehicles
$stats['available'] = $this->db->getValue(
"SELECT COUNT(*) FROM vehicles WHERE agency_id = ? AND status = 'available'",
[$agencyId]
);
// In maintenance
$stats['maintenance'] = $this->db->getValue(
"SELECT COUNT(*) FROM vehicles WHERE agency_id = ? AND status = 'maintenance'",
[$agencyId]
);
// Booked
$stats['booked'] = $this->db->getValue(
"SELECT COUNT(*) FROM vehicles WHERE agency_id = ? AND status = 'booked'",
[$agencyId]
);
// Average daily rate
$stats['avg_rate'] = $this->db->getValue(
"SELECT AVG(price_per_day) FROM vehicles WHERE agency_id = ?",
[$agencyId]
);
// Total views
$stats['total_views'] = $this->db->getValue(
"SELECT SUM(views_count) FROM vehicles WHERE agency_id = ?",
[$agencyId]
);
return $stats;
}
}
?>

Environment Configuration

File: .env

# Database Configuration
DB_HOST=localhost
DB_NAME=car_rental_system
DB_USER=root
DB_PASS=
# Application Configuration
APP_NAME=Car Rental System
APP_URL=http://localhost/car-rental-system
APP_VERSION=1.0.0
DEBUG_MODE=true
# Security
SESSION_TIMEOUT=3600
BCRYPT_ROUNDS=12
# Upload Configuration
MAX_FILE_SIZE=5242880  # 5MB in bytes
# Pagination
ITEMS_PER_PAGE=20
# Date/Time
TIMEZONE=America/New_York
DATE_FORMAT=Y-m-d
TIME_FORMAT=H:i
# Currency
CURRENCY=USD
CURRENCY_SYMBOL=$
# Rental Settings
COMMISSION_RATE=10.00
SECURITY_DEPOSIT_PERCENTAGE=20
FREE_MILEAGE_PER_DAY=100
ADDITIONAL_MILEAGE_CHARGE=0.50
LATE_RETURN_FEE_PER_HOUR=10.00
MAX_ADVANCE_BOOKING=180
MIN_ADVANCE_BOOKING=1
CANCELLATION_POLICY=24
# Payment Settings
ENABLE_PAYMENTS=true
PAYMENT_GATEWAY=stripe
STRIPE_KEY=pk_test_your_stripe_key
STRIPE_SECRET=sk_test_your_stripe_secret
PAYPAL_CLIENT_ID=your_paypal_client_id
PAYPAL_SECRET=your_paypal_secret
# Email Settings
ENABLE_EMAIL=true
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
[email protected]
SMTP_PASS=your_app_password
SMTP_ENCRYPTION=tls
[email protected]
SITE_ADDRESS=123 Business Ave, City, State 12345
SITE_PHONE=+1-555-123-4567
# SMS Settings (Twilio)
ENABLE_SMS=false
SMS_PROVIDER=twilio
TWILIO_SID=your_twilio_sid
TWILIO_TOKEN=your_twilio_token
TWILIO_PHONE=+1234567890
# Google Maps
GOOGLE_MAPS_API_KEY=your_google_maps_api_key

File: .gitignore

# Environment variables
.env
# Dependencies
/vendor/
node_modules/
# IDE files
.vscode/
.idea/
*.sublime-*
# OS files
.DS_Store
Thumbs.db
# Logs
/logs/
*.log
# Uploads
/uploads/
!/uploads/.gitkeep
!/uploads/vehicles/.gitkeep
!/uploads/agencies/.gitkeep
!/uploads/drivers/.gitkeep
!/uploads/customers/.gitkeep
!/uploads/contracts/.gitkeep
# Cache
/cache/
!/cache/.gitkeep
# Composer
composer.lock
# Temp files
*.tmp
*.temp
*.swp
*.swo
*~
# Project specific
*.sql
*.bak
*.old

File: composer.json

{
"name": "car-rental-system/application",
"description": "Online Car Rental System with Multi-User Roles",
"type": "project",
"require": {
"php": ">=7.4",
"ext-pdo": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-curl": "*",
"ext-gd": "*",
"phpmailer/phpmailer": "^6.8",
"twilio/sdk": "^7.0",
"stripe/stripe-php": "^13.0",
"tecnickcom/tcpdf": "^6.6"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"autoload": {
"psr-4": {
"CarRental\\": "src/"
}
},
"scripts": {
"test": "phpunit tests",
"post-install-cmd": [
"chmod -R 755 uploads/",
"chmod -R 755 logs/",
"chmod -R 755 cache/"
]
}
}

How to Use the Project (Step-by-Step Guide)

Prerequisites

  1. Web Server: XAMPP, WAMP, MAMP, or any PHP-enabled server (PHP 7.4+)
  2. Database: MySQL 5.7+ or MariaDB
  3. Composer: For dependency management (https://getcomposer.org/)
  4. Node.js: Optional, for frontend asset compilation
  5. Browser: Modern web browser (Chrome, Firefox, Edge, etc.)
  6. API Keys: (Optional) Stripe/PayPal, Google Maps, Twilio

Installation Steps

Step 1: Set Up Local Server

  1. Download and install XAMPP from https://www.apachefriends.org/
  2. Launch XAMPP Control Panel
  3. Start Apache and MySQL services

Step 2: Install Composer Dependencies

  1. Download and install Composer from https://getcomposer.org/
  2. Open terminal/command prompt
  3. Navigate to your project directory
  4. Run: composer install

Step 3: Create Project Folder

  1. Navigate to C:\xampp\htdocs\ (Windows) or /Applications/XAMPP/htdocs/ (Mac)
  2. Create a new folder named car-rental-system
  3. Create all the folders and files as shown in the Project File Structure

Step 4: Set Up Database

  1. Open browser and go to http://localhost/phpmyadmin
  2. Click on "New" to create a new database
  3. Name the database car_rental_system and select utf8_general_ci
  4. Click on "Import" tab
  5. Click "Choose File" and select the database.sql file from the sql folder
  6. Click "Go" to import the database structure and sample data

Step 5: Configure Environment

  1. Rename .env.example to .env in the project root
  2. Update database credentials if different from default:
   DB_HOST=localhost
DB_NAME=car_rental_system
DB_USER=root
DB_PASS=
  1. Update application URL:
   APP_URL=http://localhost/car-rental-system
  1. Configure email settings if using email notifications
  2. Configure payment gateway if using online payments
  3. Add Google Maps API key for location features

Step 6: Set Folder Permissions

Create the following folders and ensure they are writable:

  • uploads/vehicles/
  • uploads/agencies/
  • uploads/drivers/
  • uploads/customers/
  • uploads/contracts/
  • logs/
  • cache/

On Windows, right-click folders → Properties → Security → give Write permission to Users
On Mac/Linux, run: chmod -R 755 uploads/ logs/ cache/

Step 7: Create Admin Password Hash

  1. Go to http://localhost/car-rental-system/register.php
  2. Register a test user (e.g., email: [email protected], password: Admin@123)
  3. Open phpMyAdmin, go to the users table
  4. Find the user and change role to 'admin' and status to 'active'
  5. Copy the password hash for reference

Step 8: Test the Installation

  1. Open browser and go to http://localhost/car-rental-system/
  2. You should see the landing page with search form
  3. Test different user types: Admin Login:
  • Email: [email protected]
  • Password: Admin@123 Agency Registration:
  • Click "Sign Up" and select "Agency" tab
  • Fill in agency details and register
  • Wait for admin approval or manually verify in database Customer Registration:
  • Register as a new customer
  • Browse and search for vehicles
  • Try booking a vehicle Driver Registration:
  • Register as a driver (requires agency selection)
  • Login and view assigned trips

System Walkthrough

For Customers:

  1. Search Vehicles - Enter location, dates, and search for available vehicles
  2. Filter Results - Use filters for price, transmission, fuel type, seating capacity
  3. View Details - Check vehicle specifications, features, and rental terms
  4. Select Vehicle - Choose your preferred vehicle
  5. Add Extras - Optional: add driver, insurance, GPS
  6. Review Booking - Confirm details and price calculation
  7. Make Payment - Complete secure payment
  8. Sign Contract - Digitally sign rental agreement
  9. Manage Bookings - View upcoming rentals, extend or cancel
  10. Leave Review - Rate vehicle and agency after rental

For Agencies:

  1. Dashboard - View statistics, recent bookings, revenue
  2. Manage Vehicles - Add new vehicles with photos and specifications
  3. Set Availability - Mark vehicles as available, booked, or maintenance
  4. View Bookings - See all bookings, confirm or cancel
  5. Manage Drivers - Add and manage drivers for chauffeur services
  6. Earnings - Track revenue, view payouts
  7. Promotions - Create discount codes and special offers
  8. Reviews - Respond to customer reviews
  9. Profile - Update business information
  10. Documents - Upload and manage business documents

For Drivers:

  1. Dashboard - View assigned trips and schedule
  2. Trip Details - Access customer and vehicle information
  3. Start Trip - Begin trip and update status
  4. Complete Trip - Mark trip as completed
  5. Vehicle Inspection - Report any issues before/after trip
  6. Earnings - Track trip earnings and history
  7. Profile - Update personal information and documents

For Admins:

  1. Dashboard - View system-wide statistics and analytics
  2. Manage Agencies - Approve/reject agency applications
  3. Manage Customers - View and manage customer accounts
  4. Manage Vehicles - Overview of all vehicles across agencies
  5. Manage Bookings - Monitor all bookings and disputes
  6. Financial Reports - Generate revenue and commission reports
  7. Disputes - Handle customer-agency disputes
  8. System Settings - Configure global settings and policies

Key Features Explained

Search and Booking Flow

  1. Customer enters location and dates
  2. System checks real-time availability
  3. Available vehicles displayed with filters
  4. Customer selects vehicle and any add-ons
  5. Price calculated dynamically based on duration
  6. Payment processed securely
  7. Digital contract generated and signed
  8. Booking confirmed with email/SMS

Availability Management

  1. Agencies set base availability (working hours)
  2. Individual vehicles can be marked unavailable for maintenance
  3. System prevents double-booking automatically
  4. Calendar view shows booked and available dates
  5. Buffer time between bookings can be configured

Pricing Engine

  • Daily, weekly, and monthly rates
  • Seasonal pricing adjustments
  • Dynamic pricing based on demand
  • Discount codes and promotions
  • Additional fees (late return, extra mileage)
  • Security deposit calculation

Payment Processing

  • Multiple payment methods (card, PayPal, digital wallet)
  • Secure payment gateway integration
  • Automatic commission calculation for agencies
  • Payout system for agencies
  • Refund processing for cancellations
  • Invoice generation

Contract and Legal

  • Digital contract generation
  • E-signature integration
  • Terms and conditions acceptance
  • Insurance documentation
  • Vehicle inspection checklist
  • Damage reporting system

Notification System

  • Booking confirmations (email/SMS)
  • Reminders before pickup
  • Extension approvals
  • Cancellation notices
  • Payment receipts
  • Review requests

Troubleshooting

Common Issues and Solutions

  1. Database Connection Error
  • Check if MySQL is running
  • Verify database credentials in .env
  • Ensure database car_rental_system exists
  • Check if MySQL port is correct (default: 3306)
  1. 404 Page Not Found
  • Check file paths and folder structure
  • Verify APP_URL in .env
  • Ensure .htaccess is properly configured (if using Apache)
  • Check if mod_rewrite is enabled
  1. Email Not Sending
  • Configure SMTP settings in .env
  • For Gmail, use App Password instead of regular password
  • Check spam folder
  • Verify PHP mail() function is enabled
  1. Payment Gateway Errors
  • Verify API keys in .env
  • For Stripe, ensure you're using correct keys (test/live)
  • Check SSL certificate for production
  • Test in sandbox mode first
  1. Image Upload Issues
  • Check folder permissions (755 or 777)
  • Verify MAX_FILE_SIZE in .env
  • Check allowed file extensions
  • Ensure sufficient disk space
  1. Date/Time Issues
  • Check timezone setting in .env
  • Verify server time is correct
  • Check MySQL timezone configuration
  1. Search Not Working
  • Verify Google Maps API key
  • Check if latitude/longitude are stored correctly
  • Ensure location data is populated

Security Best Practices

  1. Change default admin password immediately after installation
  2. Use HTTPS in production with SSL certificate
  3. Regular backups of database and uploads
  4. Input validation on both client and server side
  5. SQL injection prevention using prepared statements
  6. XSS prevention using htmlspecialchars()
  7. CSRF tokens for all forms
  8. Password hashing using bcrypt
  9. Rate limiting for login attempts
  10. Session security with proper timeout
  11. File upload validation and malware scanning
  12. API key protection (store in .env, never expose)
  13. Regular security updates for dependencies

Performance Optimizations

  1. Database indexing on frequently queried columns
  2. Query caching for repeated requests
  3. Image optimization for vehicle photos
  4. Lazy loading for images and content
  5. Pagination for large datasets
  6. Minified CSS and JavaScript for production
  7. CDN for static assets
  8. Browser caching headers
  9. Gzip compression for responses
  10. Database connection pooling

Deployment to Production

  1. Update .env with production settings
  2. Set DEBUG_MODE=false
  3. Configure proper error logging
  4. Set up SSL certificate (Let's Encrypt free)
  5. Configure cron jobs for scheduled tasks:
   # Run every hour to send reminders
0 * * * * php /path/to/project/cron/send_reminders.php
# Run daily to update availability
0 0 * * * php /path/to/project/cron/update_availability.php
# Run weekly to process payouts
0 0 * * 0 php /path/to/project/cron/process_payouts.php
# Run monthly to cleanup old data
0 0 1 * * php /path/to/project/cron/cleanup_old_data.php
  1. Set up database backups:
   0 2 * * * mysqldump -u username -p password car_rental_system > /backups/db_$(date +\%Y\%m\%d).sql
  1. Configure firewall and security rules
  2. Set up monitoring (Uptime Robot, etc.)
  3. Test thoroughly before going live
  4. Create maintenance plan for updates

Scaling Considerations

  1. Database replication for read/write splitting
  2. Redis/Memcached for session and query caching
  3. CDN for static assets
  4. Load balancing for high traffic
  5. Microservices for different features
  6. Queue system for background jobs
  7. Elasticsearch for advanced search
  8. S3/Cloud storage for images
  9. Auto-scaling for peak times

Future Enhancements

  1. Mobile Apps - Native iOS and Android apps
  2. Real-time GPS Tracking - Live vehicle tracking during rental
  3. AI-powered Pricing - Dynamic pricing based on demand
  4. Blockchain Contracts - Smart contracts for rentals
  5. Biometric Verification - Facial recognition for check-in
  6. Voice Search - Voice-enabled search
  7. Multi-language Support - Internationalization
  8. Multi-currency - Automatic currency conversion
  9. Integration with Travel Sites - Expedia, Booking.com
  10. Fleet Management - Advanced analytics for agencies
  11. Maintenance Prediction - AI-based maintenance alerts
  12. Customer Loyalty Program - Points and rewards system
  13. Referral System - Refer friends and earn credits
  14. Social Media Integration - Share rentals on social media
  15. Augmented Reality - Virtual vehicle tours

Conclusion

The Online Car Rental System is a comprehensive, enterprise-grade solution for vehicle rental businesses. This platform provides a complete ecosystem connecting customers, rental agencies, drivers, and administrators in a seamless digital experience.

Key achievements of this system:

  • Complete Multi-User Architecture: Separate interfaces and functionalities for customers, agencies, drivers, and administrators
  • Advanced Booking Engine: Real-time availability checking with intelligent pricing
  • Secure Payment Processing: Integrated with major payment gateways
  • Digital Contract Management: E-signature integration for legal compliance
  • Notification System: Multi-channel notifications (email, SMS, in-app)
  • Review and Rating System: Build trust through verified reviews
  • Location Services: Google Maps integration for pickup/drop-off
  • Scalable Design: Built to handle growth from small agencies to large enterprises

This system demonstrates:

  • MVC Architecture with clean separation of concerns
  • Secure coding practices following OWASP guidelines
  • Responsive design for all devices
  • RESTful API design for future mobile apps
  • Comprehensive error handling and logging
  • Performance optimization techniques
  • Database design with proper relationships and indexing

Whether you're building a startup rental platform or digitizing an existing rental business, this system provides a solid foundation that can be customized and extended to meet specific requirements. With proper deployment, regular maintenance, and continuous improvement, this platform can serve as the backbone of a successful car rental business.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper