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">© <?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
- Web Server: XAMPP, WAMP, MAMP, or any PHP-enabled server (PHP 7.4+)
- Database: MySQL 5.7+ or MariaDB
- Composer: For dependency management (https://getcomposer.org/)
- Node.js: Optional, for frontend asset compilation
- Browser: Modern web browser (Chrome, Firefox, Edge, etc.)
- API Keys: (Optional) Stripe/PayPal, Google Maps, Twilio
Installation Steps
Step 1: Set Up Local Server
- Download and install XAMPP from https://www.apachefriends.org/
- Launch XAMPP Control Panel
- Start Apache and MySQL services
Step 2: Install Composer Dependencies
- Download and install Composer from https://getcomposer.org/
- Open terminal/command prompt
- Navigate to your project directory
- Run:
composer install
Step 3: Create Project Folder
- Navigate to
C:\xampp\htdocs\(Windows) or/Applications/XAMPP/htdocs/(Mac) - Create a new folder named
car-rental-system - Create all the folders and files as shown in the Project File Structure
Step 4: Set Up Database
- Open browser and go to
http://localhost/phpmyadmin - Click on "New" to create a new database
- Name the database
car_rental_systemand selectutf8_general_ci - Click on "Import" tab
- Click "Choose File" and select the
database.sqlfile from thesqlfolder - Click "Go" to import the database structure and sample data
Step 5: Configure Environment
- Rename
.env.exampleto.envin the project root - Update database credentials if different from default:
DB_HOST=localhost DB_NAME=car_rental_system DB_USER=root DB_PASS=
- Update application URL:
APP_URL=http://localhost/car-rental-system
- Configure email settings if using email notifications
- Configure payment gateway if using online payments
- 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
- Go to
http://localhost/car-rental-system/register.php - Register a test user (e.g., email:
[email protected], password:Admin@123) - Open phpMyAdmin, go to the
userstable - Find the user and change role to 'admin' and status to 'active'
- Copy the password hash for reference
Step 8: Test the Installation
- Open browser and go to
http://localhost/car-rental-system/ - You should see the landing page with search form
- Test different user types: Admin Login:
- Email:
[email protected] - Password:
Admin@123Agency 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:
- Search Vehicles - Enter location, dates, and search for available vehicles
- Filter Results - Use filters for price, transmission, fuel type, seating capacity
- View Details - Check vehicle specifications, features, and rental terms
- Select Vehicle - Choose your preferred vehicle
- Add Extras - Optional: add driver, insurance, GPS
- Review Booking - Confirm details and price calculation
- Make Payment - Complete secure payment
- Sign Contract - Digitally sign rental agreement
- Manage Bookings - View upcoming rentals, extend or cancel
- Leave Review - Rate vehicle and agency after rental
For Agencies:
- Dashboard - View statistics, recent bookings, revenue
- Manage Vehicles - Add new vehicles with photos and specifications
- Set Availability - Mark vehicles as available, booked, or maintenance
- View Bookings - See all bookings, confirm or cancel
- Manage Drivers - Add and manage drivers for chauffeur services
- Earnings - Track revenue, view payouts
- Promotions - Create discount codes and special offers
- Reviews - Respond to customer reviews
- Profile - Update business information
- Documents - Upload and manage business documents
For Drivers:
- Dashboard - View assigned trips and schedule
- Trip Details - Access customer and vehicle information
- Start Trip - Begin trip and update status
- Complete Trip - Mark trip as completed
- Vehicle Inspection - Report any issues before/after trip
- Earnings - Track trip earnings and history
- Profile - Update personal information and documents
For Admins:
- Dashboard - View system-wide statistics and analytics
- Manage Agencies - Approve/reject agency applications
- Manage Customers - View and manage customer accounts
- Manage Vehicles - Overview of all vehicles across agencies
- Manage Bookings - Monitor all bookings and disputes
- Financial Reports - Generate revenue and commission reports
- Disputes - Handle customer-agency disputes
- System Settings - Configure global settings and policies
Key Features Explained
Search and Booking Flow
- Customer enters location and dates
- System checks real-time availability
- Available vehicles displayed with filters
- Customer selects vehicle and any add-ons
- Price calculated dynamically based on duration
- Payment processed securely
- Digital contract generated and signed
- Booking confirmed with email/SMS
Availability Management
- Agencies set base availability (working hours)
- Individual vehicles can be marked unavailable for maintenance
- System prevents double-booking automatically
- Calendar view shows booked and available dates
- 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
- Database Connection Error
- Check if MySQL is running
- Verify database credentials in
.env - Ensure database
car_rental_systemexists - Check if MySQL port is correct (default: 3306)
- 404 Page Not Found
- Check file paths and folder structure
- Verify
APP_URLin.env - Ensure
.htaccessis properly configured (if using Apache) - Check if mod_rewrite is enabled
- 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
- 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
- Image Upload Issues
- Check folder permissions (755 or 777)
- Verify
MAX_FILE_SIZEin.env - Check allowed file extensions
- Ensure sufficient disk space
- Date/Time Issues
- Check timezone setting in
.env - Verify server time is correct
- Check MySQL timezone configuration
- Search Not Working
- Verify Google Maps API key
- Check if latitude/longitude are stored correctly
- Ensure location data is populated
Security Best Practices
- Change default admin password immediately after installation
- Use HTTPS in production with SSL certificate
- Regular backups of database and uploads
- Input validation on both client and server side
- SQL injection prevention using prepared statements
- XSS prevention using
htmlspecialchars() - CSRF tokens for all forms
- Password hashing using bcrypt
- Rate limiting for login attempts
- Session security with proper timeout
- File upload validation and malware scanning
- API key protection (store in .env, never expose)
- Regular security updates for dependencies
Performance Optimizations
- Database indexing on frequently queried columns
- Query caching for repeated requests
- Image optimization for vehicle photos
- Lazy loading for images and content
- Pagination for large datasets
- Minified CSS and JavaScript for production
- CDN for static assets
- Browser caching headers
- Gzip compression for responses
- Database connection pooling
Deployment to Production
- Update
.envwith production settings - Set DEBUG_MODE=false
- Configure proper error logging
- Set up SSL certificate (Let's Encrypt free)
- 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
- Set up database backups:
0 2 * * * mysqldump -u username -p password car_rental_system > /backups/db_$(date +\%Y\%m\%d).sql
- Configure firewall and security rules
- Set up monitoring (Uptime Robot, etc.)
- Test thoroughly before going live
- Create maintenance plan for updates
Scaling Considerations
- Database replication for read/write splitting
- Redis/Memcached for session and query caching
- CDN for static assets
- Load balancing for high traffic
- Microservices for different features
- Queue system for background jobs
- Elasticsearch for advanced search
- S3/Cloud storage for images
- Auto-scaling for peak times
Future Enhancements
- Mobile Apps - Native iOS and Android apps
- Real-time GPS Tracking - Live vehicle tracking during rental
- AI-powered Pricing - Dynamic pricing based on demand
- Blockchain Contracts - Smart contracts for rentals
- Biometric Verification - Facial recognition for check-in
- Voice Search - Voice-enabled search
- Multi-language Support - Internationalization
- Multi-currency - Automatic currency conversion
- Integration with Travel Sites - Expedia, Booking.com
- Fleet Management - Advanced analytics for agencies
- Maintenance Prediction - AI-based maintenance alerts
- Customer Loyalty Program - Points and rewards system
- Referral System - Refer friends and earn credits
- Social Media Integration - Share rentals on social media
- 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.