Hotel Reservation System – Complete Project HTML CSS JAVASCRIPT WITH PHP AND MY SQL

Introduction to the Project

The Hotel Reservation System is a comprehensive, full-stack web application designed to streamline hotel booking operations for both customers and hotel administrators. This system provides a seamless platform for guests to search for rooms, make reservations, and manage their bookings, while giving hotel staff powerful tools to manage rooms, rates, and reservations efficiently.

The application features role-based access control with three distinct user types: Admin, Hotel Staff, and Guests. The system eliminates double-bookings, automates rate calculations, provides real-time availability, and generates detailed reports for business intelligence.

Key Features

Admin Features

  • Dashboard Overview: Comprehensive statistics on occupancy, revenue, and bookings
  • Hotel Management: Manage multiple hotels/properties (for chains)
  • Room Management: Add, edit, and manage room types and amenities
  • Rate Management: Set dynamic pricing based on seasons, days of week
  • Staff Management: Create and manage hotel staff accounts
  • Booking Oversight: View and manage all reservations
  • Financial Reports: Generate revenue, occupancy, and forecasting reports
  • System Settings: Configure global system parameters

Hotel Staff Features

  • Dashboard: View daily occupancy, check-ins, check-outs
  • Reservation Management: Create, modify, cancel reservations
  • Check-in/Check-out: Process guest arrivals and departures
  • Room Status: Update room status (clean, maintenance, available)
  • Guest Management: View guest history and preferences
  • Housekeeping: Manage room cleaning schedules
  • Billing: Generate invoices and process payments
  • Reports: View occupancy and revenue reports

Guest Features

  • Room Search: Search available rooms by dates, guests, preferences
  • Room Details: View photos, amenities, rates, and availability
  • Online Booking: Make reservations with instant confirmation
  • Booking Management: View, modify, or cancel upcoming stays
  • Booking History: Access past stays and booking details
  • Special Requests: Add special requests to bookings
  • Profile Management: Save preferences and contact information
  • Reviews: Rate and review stays after checkout

General Features

  • Real-time Availability: Check room availability instantly
  • Dynamic Pricing: Automatic rate calculation based on seasons
  • Promo Codes: Apply discount codes to bookings
  • Payment Processing: Online payments via Stripe/PayPal
  • Email Confirmations: Automated booking confirmations and reminders
  • Calendar Integration: Sync bookings with Google Calendar
  • Multi-language: Support for multiple languages
  • Responsive Design: Works on desktop, tablet, and mobile

Technology Stack

  • Frontend: HTML5, CSS3, JavaScript (ES6+), Bootstrap 5
  • Backend: PHP 8.0+ (Core PHP with MVC-like structure)
  • Database: MySQL 5.7+
  • Additional Libraries:
  • PHPMailer for email notifications
  • Stripe/PayPal SDK for payments
  • Bootstrap 5 for responsive UI
  • Font Awesome for icons
  • jQuery for AJAX operations
  • Select2 for enhanced dropdowns
  • DataTables for advanced tables
  • Date Range Picker for date selection
  • Chart.js for analytics
  • TCPDF for invoice generation

Project File Structure

hotel-reservation-system/
│
├── assets/
│   ├── css/
│   │   ├── style.css
│   │   ├── admin.css
│   │   ├── booking.css
│   │   ├── responsive.css
│   │   └── dark-mode.css
│   ├── js/
│   │   ├── main.js
│   │   ├── booking.js
│   │   ├── calendar.js
│   │   ├── validation.js
│   │   ├── payment.js
│   │   └── charts.js
│   ├── images/
│   │   ├── hotels/
│   │   ├── rooms/
│   │   ├── amenities/
│   │   └── avatars/
│   └── plugins/
│       ├── datatables/
│       ├── daterangepicker/
│       └── select2/
│
├── includes/
│   ├── config.php
│   ├── Database.php
│   ├── functions.php
│   ├── auth.php
│   ├── Hotel.php
│   ├── Room.php
│   ├── Reservation.php
│   ├── User.php
│   ├── Payment.php
│   ├── Review.php
│   ├── Report.php
│   └── helpers/
│       ├── DateHelper.php
│       ├── PriceHelper.php
│       └── ValidationHelper.php
│
├── admin/
│   ├── dashboard.php
│   ├── manage_hotels.php
│   ├── add_hotel.php
│   ├── edit_hotel.php
│   ├── manage_rooms.php
│   ├── add_room.php
│   ├── edit_room.php
│   ├── manage_rates.php
│   ├── set_rates.php
│   ├── manage_staff.php
│   ├── add_staff.php
│   ├── all_bookings.php
│   ├── guests.php
│   ├── reports.php
│   ├── analytics.php
│   ├── settings.php
│   └── promotions.php
│
├── staff/
│   ├── dashboard.php
│   ├── calendar.php
│   ├── reservations.php
│   ├── new_booking.php
│   ├── booking_details.php
│   ├── checkin.php
│   ├── checkout.php
│   ├── room_status.php
│   ├── housekeeping.php
│   ├── guests.php
│   ├── guest_details.php
│   ├── billing.php
│   ├── generate_invoice.php
│   └── reports.php
│
├── guest/
│   ├── dashboard.php
│   ├── search.php
│   ├── room_details.php
│   ├── booking.php
│   ├── confirm_booking.php
│   ├── my_bookings.php
│   ├── booking_details.php
│   ├── cancel_booking.php
│   ├── modify_booking.php
│   ├── history.php
│   ├── profile.php
│   ├── favorites.php
│   ├── reviews.php
│   └── write_review.php
│
├── api/
│   ├── check_availability.php
│   ├── get_rates.php
│   ├── search_rooms.php
│   ├── create_booking.php
│   ├── cancel_booking.php
│   ├── process_payment.php
│   ├── apply_promo.php
│   └── get_calendar.php
│
├── includes/
│   └── templates/
│       ├── email/
│       │   ├── booking_confirmation.php
│       │   ├── booking_cancelled.php
│       │   ├── reminder.php
│       │   ├── invoice.php
│       │   └── welcome.php
│       └── invoices/
│
├── uploads/
│   ├── hotels/
│   ├── rooms/
│   └── avatars/
│
├── vendor/
│
├── index.php
├── login.php
├── register.php
├── forgot_password.php
├── reset_password.php
├── logout.php
├── .env
├── .gitignore
├── composer.json
├── cron/
│   ├── send_reminders.php
│   └── update_rates.php
└── sql/
└── database.sql

Database Schema

File: sql/database.sql

-- Create Database
CREATE DATABASE IF NOT EXISTS `hotel_reservation_system`;
USE `hotel_reservation_system`;
-- Hotels Table
CREATE TABLE `hotels` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`hotel_code` VARCHAR(20) UNIQUE NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` TEXT,
`address` TEXT NOT NULL,
`city` VARCHAR(100) NOT NULL,
`state` VARCHAR(50),
`country` VARCHAR(50) NOT NULL,
`postal_code` VARCHAR(20),
`phone` VARCHAR(20) NOT NULL,
`email` VARCHAR(100) NOT NULL,
`website` VARCHAR(255),
`star_rating` TINYINT CHECK (star_rating BETWEEN 1 AND 5),
`check_in_time` TIME DEFAULT '15:00:00',
`check_out_time` TIME DEFAULT '11:00:00',
`latitude` DECIMAL(10, 8),
`longitude` DECIMAL(11, 8),
`amenities` TEXT,
`policies` TEXT,
`images` TEXT,
`logo` VARCHAR(255),
`status` ENUM('active', 'inactive', 'maintenance') DEFAULT 'active',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_city` (`city`),
INDEX `idx_status` (`status`),
FULLTEXT `idx_search` (`name`, `description`, `city`)
);
-- Room Types Table
CREATE TABLE `room_types` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`hotel_id` INT(11) NOT NULL,
`name` VARCHAR(100) NOT NULL,
`description` TEXT,
`capacity_adults` INT DEFAULT 2,
`capacity_children` INT DEFAULT 0,
`max_occupancy` INT DEFAULT 2,
`size_sqm` DECIMAL(5,2),
`bed_type` VARCHAR(50),
`amenities` TEXT,
`images` TEXT,
`base_price` DECIMAL(10,2) NOT NULL,
`currency` VARCHAR(3) DEFAULT 'USD',
`quantity` INT NOT NULL DEFAULT 1,
`smoking_allowed` BOOLEAN DEFAULT FALSE,
`pet_friendly` BOOLEAN DEFAULT FALSE,
`status` ENUM('active', 'inactive') DEFAULT 'active',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`hotel_id`) REFERENCES `hotels`(`id`) ON DELETE CASCADE,
INDEX `idx_hotel` (`hotel_id`)
);
-- Rooms Table (individual rooms)
CREATE TABLE `rooms` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`hotel_id` INT(11) NOT NULL,
`room_type_id` INT(11) NOT NULL,
`room_number` VARCHAR(20) NOT NULL,
`floor` INT,
`view` VARCHAR(100),
`status` ENUM('available', 'occupied', 'maintenance', 'cleaning') DEFAULT 'available',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`hotel_id`) REFERENCES `hotels`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`room_type_id`) REFERENCES `room_types`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_room` (`hotel_id`, `room_number`)
);
-- Rate Plans Table
CREATE TABLE `rate_plans` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`hotel_id` INT(11) NOT NULL,
`room_type_id` INT(11) NOT NULL,
`name` VARCHAR(100) NOT NULL,
`description` TEXT,
`base_rate` DECIMAL(10,2) NOT NULL,
`currency` VARCHAR(3) DEFAULT 'USD',
`cancellation_policy` TEXT,
`breakfast_included` BOOLEAN DEFAULT FALSE,
`refundable` BOOLEAN DEFAULT TRUE,
`min_stay` INT DEFAULT 1,
`max_stay` INT DEFAULT 30,
`advance_purchase_days` INT DEFAULT 0,
`cut_off_days` INT DEFAULT 0,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`hotel_id`) REFERENCES `hotels`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`room_type_id`) REFERENCES `room_types`(`id`) ON DELETE CASCADE
);
-- Seasonal Rates Table
CREATE TABLE `seasonal_rates` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`rate_plan_id` INT(11) NOT NULL,
`start_date` DATE NOT NULL,
`end_date` DATE NOT NULL,
`rate_multiplier` DECIMAL(3,2) DEFAULT 1.00,
`fixed_rate` DECIMAL(10,2),
`min_stay` INT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`rate_plan_id`) REFERENCES `rate_plans`(`id`) ON DELETE CASCADE,
INDEX `idx_dates` (`start_date`, `end_date`)
);
-- Special Offers/Promotions
CREATE TABLE `promotions` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`hotel_id` INT(11) NOT NULL,
`code` VARCHAR(50) UNIQUE NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` TEXT,
`discount_type` ENUM('percentage', 'fixed') NOT NULL,
`discount_value` DECIMAL(10,2) NOT NULL,
`min_stay` INT,
`min_amount` DECIMAL(10,2),
`start_date` DATE,
`end_date` DATE,
`usage_limit` INT,
`used_count` INT DEFAULT 0,
`is_active` BOOLEAN DEFAULT TRUE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`hotel_id`) REFERENCES `hotels`(`id`) ON DELETE CASCADE,
INDEX `idx_code` (`code`)
);
-- Users Table
CREATE TABLE `users` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_code` 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),
`date_of_birth` DATE,
`gender` ENUM('male', 'female', 'other'),
`address` TEXT,
`city` VARCHAR(100),
`country` VARCHAR(50),
`postal_code` VARCHAR(20),
`id_proof_type` VARCHAR(50),
`id_proof_number` VARCHAR(100),
`preferences` TEXT,
`role` ENUM('admin', 'staff', 'guest') NOT NULL DEFAULT 'guest',
`hotel_id` INT(11), -- for staff users
`status` ENUM('active', 'inactive', 'suspended') DEFAULT 'active',
`email_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`),
FOREIGN KEY (`hotel_id`) REFERENCES `hotels`(`id`),
INDEX `idx_email` (`email`),
INDEX `idx_role` (`role`)
);
-- Reservations Table
CREATE TABLE `reservations` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`booking_code` VARCHAR(20) UNIQUE NOT NULL,
`hotel_id` INT(11) NOT NULL,
`guest_id` INT(11) NOT NULL,
`room_type_id` INT(11) NOT NULL,
`rate_plan_id` INT(11),
`promotion_id` INT(11),
`check_in_date` DATE NOT NULL,
`check_out_date` DATE NOT NULL,
`adults` INT NOT NULL DEFAULT 1,
`children` INT DEFAULT 0,
`total_rooms` INT DEFAULT 1,
`total_nights` INT NOT NULL,
`subtotal` DECIMAL(10,2) NOT NULL,
`tax_amount` DECIMAL(10,2) DEFAULT 0.00,
`discount_amount` DECIMAL(10,2) DEFAULT 0.00,
`total_amount` DECIMAL(10,2) NOT NULL,
`currency` VARCHAR(3) DEFAULT 'USD',
`status` ENUM('pending', 'confirmed', 'checked_in', 'checked_out', 'cancelled', 'no_show') DEFAULT 'pending',
`payment_status` ENUM('pending', 'partial', 'paid', 'refunded') DEFAULT 'pending',
`payment_method` VARCHAR(50),
`special_requests` TEXT,
`guest_notes` TEXT,
`staff_notes` TEXT,
`cancellation_reason` TEXT,
`cancelled_at` DATETIME,
`cancelled_by` INT(11),
`created_by` INT(11), -- staff who created booking (if any)
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`hotel_id`) REFERENCES `hotels`(`id`),
FOREIGN KEY (`guest_id`) REFERENCES `users`(`id`),
FOREIGN KEY (`room_type_id`) REFERENCES `room_types`(`id`),
FOREIGN KEY (`rate_plan_id`) REFERENCES `rate_plans`(`id`),
FOREIGN KEY (`promotion_id`) REFERENCES `promotions`(`id`),
FOREIGN KEY (`cancelled_by`) REFERENCES `users`(`id`),
FOREIGN KEY (`created_by`) REFERENCES `users`(`id`),
INDEX `idx_dates` (`check_in_date`, `check_out_date`),
INDEX `idx_status` (`status`),
INDEX `idx_booking_code` (`booking_code`)
);
-- Assigned Rooms (actual room allocation)
CREATE TABLE `assigned_rooms` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`reservation_id` INT(11) NOT NULL,
`room_id` INT(11) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`reservation_id`) REFERENCES `reservations`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`room_id`) REFERENCES `rooms`(`id`),
UNIQUE KEY `unique_room_date` (`room_id`, `reservation_id`)
);
-- Payments Table
CREATE TABLE `payments` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`payment_code` VARCHAR(50) UNIQUE NOT NULL,
`reservation_id` INT(11) NOT NULL,
`guest_id` INT(11) NOT NULL,
`amount` DECIMAL(10,2) NOT NULL,
`currency` VARCHAR(3) DEFAULT 'USD',
`payment_method` ENUM('credit_card', 'debit_card', 'paypal', 'cash', 'bank_transfer') NOT NULL,
`transaction_id` VARCHAR(255),
`payment_details` JSON,
`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 (`reservation_id`) REFERENCES `reservations`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`guest_id`) REFERENCES `users`(`id`),
INDEX `idx_transaction` (`transaction_id`)
);
-- Invoices Table
CREATE TABLE `invoices` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`invoice_number` VARCHAR(50) UNIQUE NOT NULL,
`reservation_id` INT(11) NOT NULL,
`guest_id` INT(11) NOT NULL,
`issue_date` DATE NOT NULL,
`due_date` DATE NOT NULL,
`subtotal` DECIMAL(10,2) NOT NULL,
`tax_amount` DECIMAL(10,2) DEFAULT 0.00,
`total_amount` DECIMAL(10,2) NOT NULL,
`paid_amount` DECIMAL(10,2) DEFAULT 0.00,
`status` ENUM('draft', 'sent', 'paid', 'overdue', 'cancelled') DEFAULT 'draft',
`pdf_path` VARCHAR(255),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`reservation_id`) REFERENCES `reservations`(`id`),
FOREIGN KEY (`guest_id`) REFERENCES `users`(`id`)
);
-- Invoice Items Table
CREATE TABLE `invoice_items` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`invoice_id` INT(11) NOT NULL,
`description` VARCHAR(255) NOT NULL,
`quantity` INT DEFAULT 1,
`unit_price` DECIMAL(10,2) NOT NULL,
`amount` DECIMAL(10,2) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`invoice_id`) REFERENCES `invoices`(`id`) ON DELETE CASCADE
);
-- Reviews Table
CREATE TABLE `reviews` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`reservation_id` INT(11) NOT NULL,
`guest_id` INT(11) NOT NULL,
`hotel_id` INT(11) NOT NULL,
`rating` TINYINT NOT NULL CHECK (rating BETWEEN 1 AND 5),
`title` VARCHAR(255),
`comment` TEXT,
`staff_response` TEXT,
`response_date` DATETIME,
`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 (`reservation_id`) REFERENCES `reservations`(`id`),
FOREIGN KEY (`guest_id`) REFERENCES `users`(`id`),
FOREIGN KEY (`hotel_id`) REFERENCES `hotels`(`id`),
UNIQUE KEY `unique_review` (`reservation_id`)
);
-- Notifications Table
CREATE TABLE `notifications` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`type` ENUM('booking_confirmation', 'booking_cancelled', 'reminder', 'payment', '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`)
);
-- Amenities Table
CREATE TABLE `amenities` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`icon` VARCHAR(50),
`category` VARCHAR(50),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
-- Room Amenities Junction
CREATE TABLE `room_amenities` (
`room_type_id` INT(11) NOT NULL,
`amenity_id` INT(11) NOT NULL,
PRIMARY KEY (`room_type_id`, `amenity_id`),
FOREIGN KEY (`room_type_id`) REFERENCES `room_types`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`amenity_id`) REFERENCES `amenities`(`id`) ON DELETE CASCADE
);
-- Hotel Amenities Junction
CREATE TABLE `hotel_amenities` (
`hotel_id` INT(11) NOT NULL,
`amenity_id` INT(11) NOT NULL,
PRIMARY KEY (`hotel_id`, `amenity_id`),
FOREIGN KEY (`hotel_id`) REFERENCES `hotels`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`amenity_id`) REFERENCES `amenities`(`id`) ON DELETE CASCADE
);
-- Favorites/Wishlist
CREATE TABLE `favorites` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`guest_id` INT(11) NOT NULL,
`hotel_id` INT(11) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`guest_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`hotel_id`) REFERENCES `hotels`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_favorite` (`guest_id`, `hotel_id`)
);
-- Activity Logs
CREATE TABLE `activity_logs` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11),
`action` VARCHAR(100) NOT NULL,
`description` TEXT,
`ip_address` VARCHAR(45),
`user_agent` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL
);
-- 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 Amenities
INSERT INTO `amenities` (`name`, `icon`, `category`) VALUES
('Free WiFi', 'fa-wifi', 'general'),
('Swimming Pool', 'fa-swimmer', 'general'),
('Fitness Center', 'fa-dumbbell', 'general'),
('Restaurant', 'fa-utensils', 'dining'),
('Room Service', 'fa-concierge-bell', 'dining'),
('Parking', 'fa-parking', 'general'),
('Air Conditioning', 'fa-wind', 'room'),
('TV', 'fa-tv', 'room'),
('Mini Bar', 'fa-glass-cheers', 'room'),
('Safe', 'fa-shield-alt', 'room'),
('Hair Dryer', 'fa-wind', 'bathroom'),
('Coffee Maker', 'fa-mug-hot', 'room'),
('Balcony', 'fa-tree', 'view'),
('Ocean View', 'fa-water', 'view'),
('Mountain View', 'fa-mountain', 'view');
-- Insert System Settings
INSERT INTO `system_settings` (`setting_key`, `setting_value`, `description`) VALUES
('company_name', 'Hotel Reservation System', 'Company/Application Name'),
('company_email', '[email protected]', 'Company Email'),
('company_phone', '+1-555-123-4567', 'Company Phone'),
('company_address', '123 Hotel Street, Suite 100, City, State 12345', 'Company Address'),
('tax_rate', '10.00', 'Tax rate percentage'),
('currency', 'USD', 'Default currency'),
('currency_symbol', '$', 'Currency symbol'),
('date_format', 'Y-m-d', 'Date format'),
('time_format', 'H:i', 'Time format'),
('timezone', 'America/New_York', 'Default timezone'),
('enable_online_payments', '1', 'Enable online payments'),
('payment_gateway', 'stripe', 'Payment gateway (stripe/paypal)'),
('enable_promotions', '1', 'Enable promotion codes'),
('enable_reviews', '1', 'Enable guest reviews'),
('enable_reminders', '1', 'Enable booking reminders'),
('reminder_hours', '24', 'Hours before check-in to send reminder'),
('max_advance_booking', '365', 'Maximum days in advance for booking'),
('min_advance_booking', '0', 'Minimum hours in advance for booking'),
('cancellation_policy', '24', 'Hours before check-in for free cancellation'),
('default_check_in', '15:00', 'Default check-in time'),
('default_check_out', '11:00', 'Default check-out time');
-- Insert Default Admin
INSERT INTO `users` (`user_code`, `email`, `password`, `first_name`, `last_name`, `role`, `email_verified`) 
VALUES ('ADMIN001', '[email protected]', '$2y$10$YourHashedPasswordHere', 'System', 'Administrator', 'admin', TRUE);

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') ?: 'hotel_reservation_system');
define('DB_USER', getenv('DB_USER') ?: 'root');
define('DB_PASS', getenv('DB_PASS') ?: '');
// Application Configuration
define('APP_NAME', getenv('APP_NAME') ?: 'Hotel Reservation System');
define('APP_URL', getenv('APP_URL') ?: 'http://localhost/hotel-reservation-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']);
// 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');
// Business Settings
define('COMPANY_NAME', getenv('COMPANY_NAME') ?: 'Hotel Reservation System');
define('COMPANY_EMAIL', getenv('COMPANY_EMAIL') ?: '[email protected]');
define('COMPANY_PHONE', getenv('COMPANY_PHONE') ?: '+1-555-123-4567');
define('TAX_RATE', getenv('TAX_RATE') ?: 10.00);
define('CURRENCY', getenv('CURRENCY') ?: 'USD');
define('CURRENCY_SYMBOL', getenv('CURRENCY_SYMBOL') ?: '$');
// Booking Settings
define('MAX_ADVANCE_BOOKING', getenv('MAX_ADVANCE_BOOKING') ?: 365); // days
define('MIN_ADVANCE_BOOKING', getenv('MIN_ADVANCE_BOOKING') ?: 0); // hours
define('CANCELLATION_POLICY', getenv('CANCELLATION_POLICY') ?: 24); // hours
define('DEFAULT_CHECK_IN', getenv('DEFAULT_CHECK_IN') ?: '15:00');
define('DEFAULT_CHECK_OUT', getenv('DEFAULT_CHECK_OUT') ?: '11:00');
// Notification Settings
define('ENABLE_REMINDERS', getenv('ENABLE_REMINDERS') === 'true');
define('REMINDER_HOURS', getenv('REMINDER_HOURS') ?: 24);
// Payment Settings
define('ENABLE_ONLINE_PAYMENTS', getenv('ENABLE_ONLINE_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') ?: '');
// 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__ . '/Hotel.php';
require_once __DIR__ . '/Room.php';
require_once __DIR__ . '/Reservation.php';
require_once __DIR__ . '/Payment.php';
require_once __DIR__ . '/Review.php';
require_once __DIR__ . '/Report.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) {
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));
}
/**
* 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 nights between two dates
*/
function calculateNights($checkIn, $checkOut) {
$datetime1 = new DateTime($checkIn);
$datetime2 = new DateTime($checkOut);
$interval = $datetime1->diff($datetime2);
return $interval->days;
}
/**
* Check if dates are valid for booking
*/
function validateBookingDates($checkIn, $checkOut) {
$today = new DateTime();
$today->setTime(0, 0, 0);
$checkInDate = new DateTime($checkIn);
$checkOutDate = new DateTime($checkOut);
// Check if check-in is in the past
if ($checkInDate < $today) {
return ['valid' => false, 'error' => 'Check-in date cannot be in the past'];
}
// Check if check-out is after check-in
if ($checkOutDate <= $checkInDate) {
return ['valid' => false, 'error' => 'Check-out date must be after check-in date'];
}
// Check maximum advance booking
$maxDate = new DateTime('+' . MAX_ADVANCE_BOOKING . ' days');
if ($checkInDate > $maxDate) {
return ['valid' => false, 'error' => 'Cannot book more than ' . MAX_ADVANCE_BOOKING . ' days in advance'];
}
// Check minimum advance booking
if (MIN_ADVANCE_BOOKING > 0) {
$minDateTime = new DateTime('+' . MIN_ADVANCE_BOOKING . ' hours');
if ($checkInDate < $minDateTime) {
return ['valid' => false, 'error' => 'Bookings must be made at least ' . MIN_ADVANCE_BOOKING . ' hours in advance'];
}
}
return ['valid' => true];
}
/**
* Calculate total price for stay
*/
function calculateTotalPrice($basePrice, $checkIn, $checkOut, $rooms = 1, $discount = 0) {
$nights = calculateNights($checkIn, $checkOut);
$subtotal = $basePrice * $nights * $rooms;
$tax = $subtotal * (TAX_RATE / 100);
$discountAmount = $subtotal * ($discount / 100);
$total = $subtotal + $tax - $discountAmount;
return [
'nights' => $nights,
'subtotal' => $subtotal,
'tax' => $tax,
'discount' => $discountAmount,
'total' => $total
];
}
/**
* Generate unique booking code
*/
function generateBookingCode() {
$prefix = 'BK';
$date = date('Ymd');
$random = strtoupper(substr(uniqid(), -6));
return $prefix . $date . $random;
}
/**
* Generate unique invoice number
*/
function generateInvoiceNumber() {
$prefix = 'INV';
$year = date('Y');
$month = date('m');
$random = str_pad(mt_rand(1, 9999), 4, '0', STR_PAD_LEFT);
return $prefix . $year . $month . $random;
}
/**
* Generate unique user code
*/
function generateUserCode($role) {
$prefix = strtoupper(substr($role, 0, 3));
$year = date('Y');
$random = str_pad(mt_rand(1, 9999), 4, '0', STR_PAD_LEFT);
return $prefix . $year . $random;
}
/**
* Check room availability for dates
*/
function checkRoomAvailability($hotelId, $roomTypeId, $checkIn, $checkOut, $roomsNeeded = 1) {
$db = Database::getInstance();
// Get total rooms of this type
$totalRooms = $db->getValue(
"SELECT quantity FROM room_types WHERE id = ?",
[$roomTypeId]
);
// Get booked rooms for these dates
$bookedRooms = $db->getValue(
"SELECT COUNT(DISTINCT ar.room_id) as booked
FROM reservations r
JOIN assigned_rooms ar ON r.id = ar.reservation_id
WHERE r.room_type_id = :room_type_id
AND r.status NOT IN ('cancelled', 'no_show')
AND (
(r.check_in_date < :check_out AND r.check_out_date > :check_in)
)",
[
'room_type_id' => $roomTypeId,
'check_in' => $checkIn,
'check_out' => $checkOut
]
);
$available = $totalRooms - $bookedRooms;
return [
'available' => $available >= $roomsNeeded,
'total_rooms' => $totalRooms,
'booked_rooms' => $bookedRooms,
'available_rooms' => $available
];
}
/**
* Get available room types for dates
*/
function getAvailableRoomTypes($hotelId, $checkIn, $checkOut, $adults = 2, $children = 0) {
$db = Database::getInstance();
$sql = "SELECT rt.*, 
(rt.quantity - COALESCE((
SELECT COUNT(DISTINCT ar.room_id)
FROM reservations r
JOIN assigned_rooms ar ON r.id = ar.reservation_id
WHERE r.room_type_id = rt.id
AND r.status NOT IN ('cancelled', 'no_show')
AND (
(r.check_in_date < :check_out AND r.check_out_date > :check_in)
)
), 0)) as available_rooms
FROM room_types rt
WHERE rt.hotel_id = :hotel_id
AND rt.status = 'active'
AND rt.max_occupancy >= :total_guests
HAVING available_rooms > 0
ORDER BY rt.base_price ASC";
return $db->getRows($sql, [
'hotel_id' => $hotelId,
'check_in' => $checkIn,
'check_out' => $checkOut,
'total_guests' => $adults + $children
]);
}
/**
* 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 = []) {
// Load email template
$templateFile = __DIR__ . "/templates/email/{$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();
// Headers
$headers = [
'MIME-Version: 1.0',
'Content-type: text/html; charset=utf-8',
'From: ' . COMPANY_NAME . ' <' . COMPANY_EMAIL . '>',
'Reply-To: ' . COMPANY_EMAIL,
'X-Mailer: PHP/' . phpversion()
];
return mail($to, $subject, $message, implode("\r\n", $headers));
}
/**
* 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 rating stars HTML
*/
function getRatingStars($rating) {
$html = '<div class="rating-stars">';
for ($i = 1; $i <= 5; $i++) {
if ($i <= $rating) {
$html .= '<i class="fas fa-star text-warning"></i>';
} elseif ($i - 0.5 <= $rating) {
$html .= '<i class="fas fa-star-half-alt text-warning"></i>';
} else {
$html .= '<i class="far fa-star text-warning"></i>';
}
}
$html .= '</div>';
return $html;
}
/**
* Generate pagination
*/
function paginate($currentPage, $totalPages, $url) {
if ($totalPages <= 1) {
return '';
}
$html = '<nav aria-label="Page navigation"><ul class="pagination">';
// 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 code
$userCode = generateUserCode($data['role'] ?? 'guest');
// Generate verification token
$verificationToken = generateRandomString();
// Prepare user data
$userData = [
'user_code' => $userCode,
'email' => $data['email'],
'password' => $hashedPassword,
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'phone' => $data['phone'] ?? null,
'role' => $data['role'] ?? 'guest',
'verification_token' => $verificationToken
];
// Insert user
$newUserId = $this->db->insert('users', $userData);
if ($newUserId) {
// 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()];
}
}
/**
* Login user
*/
public function login($email, $password, $remember = false) {
try {
// Get user
$user = $this->db->getRow(
"SELECT * FROM users WHERE email = ? AND status = 'active'",
[$email]
);
if (!$user) {
return ['success' => false, 'error' => 'Invalid email or password'];
}
// 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_code'] = $user['user_code'];
$_SESSION['user_email'] = $user['email'];
$_SESSION['user_name'] = $user['first_name'] . ' ' . $user['last_name'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['hotel_id'] = $user['hotel_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']);
}
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 database (you would need a remember_tokens table)
// For now, just set cookie
setcookie('remember_token', $token, $expires, '/', '', false, true);
}
/**
* Logout user
*/
public function logout() {
// 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 (is_array($role)) {
if (!in_array($_SESSION['user_role'], $role)) {
$_SESSION['error'] = 'You do not have permission to access this page';
redirect('/index.php');
}
} else {
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('staff')) {
redirect('/staff/dashboard.php');
} else {
redirect('/guest/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,
'address' => $data['address'] ?? null,
'city' => $data['city'] ?? 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 . 'avatars/';
$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'];
}
}
// Initialize Auth
$auth = new Auth();
?>

Hotel Class

File: includes/Hotel.php

<?php
/**
* Hotel Class
* Handles all hotel-related operations
*/
class Hotel {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Get hotel details
*/
public function getHotel($hotelId) {
return $this->db->getRow(
"SELECT * FROM hotels WHERE id = ? AND status = 'active'",
[$hotelId]
);
}
/**
* Get all hotels
*/
public function getHotels($filters = []) {
$sql = "SELECT h.*, 
(SELECT AVG(rating) FROM reviews WHERE hotel_id = h.id) as avg_rating,
(SELECT COUNT(*) FROM reviews WHERE hotel_id = h.id) as review_count
FROM hotels h
WHERE h.status = 'active'";
$params = [];
if (!empty($filters['city'])) {
$sql .= " AND h.city LIKE :city";
$params['city'] = '%' . $filters['city'] . '%';
}
if (!empty($filters['country'])) {
$sql .= " AND h.country = :country";
$params['country'] = $filters['country'];
}
if (!empty($filters['star_rating'])) {
$sql .= " AND h.star_rating = :star_rating";
$params['star_rating'] = $filters['star_rating'];
}
if (!empty($filters['min_rating'])) {
$sql .= " HAVING avg_rating >= :min_rating";
$params['min_rating'] = $filters['min_rating'];
}
if (!empty($filters['search'])) {
$sql .= " AND (h.name LIKE :search OR h.city LIKE :search OR h.description LIKE :search)";
$params['search'] = '%' . $filters['search'] . '%';
}
$sql .= " ORDER BY h.star_rating 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);
}
/**
* Add new hotel
*/
public function addHotel($data) {
try {
// Generate hotel code
$hotelCode = 'HTL' . date('Y') . str_pad(mt_rand(1, 999), 3, '0', STR_PAD_LEFT);
$hotelData = [
'hotel_code' => $hotelCode,
'name' => $data['name'],
'description' => $data['description'] ?? null,
'address' => $data['address'],
'city' => $data['city'],
'state' => $data['state'] ?? null,
'country' => $data['country'],
'postal_code' => $data['postal_code'] ?? null,
'phone' => $data['phone'],
'email' => $data['email'],
'website' => $data['website'] ?? null,
'star_rating' => $data['star_rating'] ?? 3,
'check_in_time' => $data['check_in_time'] ?? DEFAULT_CHECK_IN,
'check_out_time' => $data['check_out_time'] ?? DEFAULT_CHECK_OUT,
'amenities' => $data['amenities'] ?? null,
'policies' => $data['policies'] ?? null
];
// Handle logo upload
if (isset($_FILES['logo']) && $_FILES['logo']['error'] == 0) {
$uploadDir = UPLOAD_DIR . 'hotels/';
$result = uploadFile($_FILES['logo'], $uploadDir);
if ($result['success']) {
$hotelData['logo'] = $result['filename'];
}
}
// Handle images upload
if (isset($_FILES['images'])) {
$images = [];
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'];
}
}
}
if (!empty($images)) {
$hotelData['images'] = json_encode($images);
}
}
$hotelId = $this->db->insert('hotels', $hotelData);
if ($hotelId) {
return ['success' => true, 'hotel_id' => $hotelId, 'message' => 'Hotel added successfully'];
}
return ['success' => false, 'error' => 'Failed to add hotel'];
} catch (Exception $e) {
logError('Add hotel error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to add hotel'];
}
}
/**
* Update hotel
*/
public function updateHotel($hotelId, $data) {
try {
$updateData = [
'name' => $data['name'],
'description' => $data['description'] ?? null,
'address' => $data['address'],
'city' => $data['city'],
'state' => $data['state'] ?? null,
'country' => $data['country'],
'postal_code' => $data['postal_code'] ?? null,
'phone' => $data['phone'],
'email' => $data['email'],
'website' => $data['website'] ?? null,
'star_rating' => $data['star_rating'] ?? 3,
'check_in_time' => $data['check_in_time'] ?? DEFAULT_CHECK_IN,
'check_out_time' => $data['check_out_time'] ?? DEFAULT_CHECK_OUT,
'amenities' => $data['amenities'] ?? null,
'policies' => $data['policies'] ?? null
];
// Handle logo upload
if (isset($_FILES['logo']) && $_FILES['logo']['error'] == 0) {
$uploadDir = UPLOAD_DIR . 'hotels/';
$result = uploadFile($_FILES['logo'], $uploadDir);
if ($result['success']) {
$updateData['logo'] = $result['filename'];
}
}
// Handle images upload
if (isset($_FILES['images'])) {
$currentHotel = $this->getHotel($hotelId);
$existingImages = json_decode($currentHotel['images'] ?? '[]', true);
$images = $existingImages;
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'];
}
}
}
$updateData['images'] = json_encode($images);
}
$this->db->update('hotels', $updateData, 'id = :id', ['id' => $hotelId]);
return ['success' => true, 'message' => 'Hotel updated successfully'];
} catch (Exception $e) {
logError('Update hotel error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to update hotel'];
}
}
/**
* Delete hotel
*/
public function deleteHotel($hotelId) {
try {
// Check if hotel has active bookings
$activeBookings = $this->db->getValue(
"SELECT COUNT(*) FROM reservations 
WHERE hotel_id = ? AND status IN ('confirmed', 'checked_in')",
[$hotelId]
);
if ($activeBookings > 0) {
return ['success' => false, 'error' => 'Cannot delete hotel with active bookings'];
}
// Soft delete - update status
$this->db->update('hotels', ['status' => 'inactive'], 'id = :id', ['id' => $hotelId]);
return ['success' => true, 'message' => 'Hotel deleted successfully'];
} catch (Exception $e) {
logError('Delete hotel error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to delete hotel'];
}
}
/**
* Get hotel amenities
*/
public function getAmenities($hotelId) {
return $this->db->getRows(
"SELECT a.* FROM amenities a
JOIN hotel_amenities ha ON a.id = ha.amenity_id
WHERE ha.hotel_id = ?",
[$hotelId]
);
}
/**
* Get hotel reviews
*/
public function getReviews($hotelId) {
return $this->db->getRows(
"SELECT r.*, u.first_name, u.last_name, u.profile_picture
FROM reviews r
JOIN users u ON r.guest_id = u.id
WHERE r.hotel_id = ?
ORDER BY r.created_at DESC",
[$hotelId]
);
}
/**
* Get hotel statistics
*/
public function getStatistics($hotelId) {
$stats = [];
// Total rooms
$stats['total_rooms'] = $this->db->getValue(
"SELECT SUM(quantity) FROM room_types WHERE hotel_id = ?",
[$hotelId]
);
// Occupied rooms today
$today = date('Y-m-d');
$stats['occupied_today'] = $this->db->getValue(
"SELECT COUNT(DISTINCT ar.room_id)
FROM reservations r
JOIN assigned_rooms ar ON r.id = ar.reservation_id
WHERE r.hotel_id = :hotel_id
AND r.status IN ('confirmed', 'checked_in')
AND r.check_in_date <= :today
AND r.check_out_date > :today",
['hotel_id' => $hotelId, 'today' => $today]
);
// Check-ins today
$stats['check_ins_today'] = $this->db->getValue(
"SELECT COUNT(*) FROM reservations
WHERE hotel_id = ? AND check_in_date = ? AND status = 'confirmed'",
[$hotelId, $today]
);
// Check-outs today
$stats['check_outs_today'] = $this->db->getValue(
"SELECT COUNT(*) FROM reservations
WHERE hotel_id = ? AND check_out_date = ? AND status = 'checked_in'",
[$hotelId, $today]
);
// Average rating
$rating = $this->db->getRow(
"SELECT AVG(rating) as avg_rating, COUNT(*) as total_reviews
FROM reviews WHERE hotel_id = ?",
[$hotelId]
);
$stats['avg_rating'] = round($rating['avg_rating'] ?? 0, 1);
$stats['total_reviews'] = $rating['total_reviews'] ?? 0;
// Monthly revenue
$startOfMonth = date('Y-m-01');
$endOfMonth = date('Y-m-t');
$stats['monthly_revenue'] = $this->db->getValue(
"SELECT SUM(total_amount) FROM reservations
WHERE hotel_id = ? 
AND created_at BETWEEN ? AND ?
AND status IN ('checked_out', 'confirmed')",
[$hotelId, $startOfMonth, $endOfMonth]
) ?: 0;
return $stats;
}
/**
* Search hotels by location
*/
public function searchByLocation($location, $checkIn = null, $checkOut = null) {
$sql = "SELECT h.*,
(SELECT AVG(rating) FROM reviews WHERE hotel_id = h.id) as avg_rating
FROM hotels h
WHERE h.status = 'active'
AND (h.city LIKE :location OR h.country LIKE :location OR h.name LIKE :location)";
$params = ['location' => '%' . $location . '%'];
if ($checkIn && $checkOut) {
// Filter hotels with available rooms for these dates
$sql .= " AND h.id IN (
SELECT DISTINCT rt.hotel_id
FROM room_types rt
WHERE (rt.quantity - COALESCE((
SELECT COUNT(DISTINCT ar.room_id)
FROM reservations r
JOIN assigned_rooms ar ON r.id = ar.reservation_id
WHERE r.room_type_id = rt.id
AND r.status NOT IN ('cancelled', 'no_show')
AND (
(r.check_in_date < :check_out AND r.check_out_date > :check_in)
)
), 0)) > 0
)";
$params['check_in'] = $checkIn;
$params['check_out'] = $checkOut;
}
$sql .= " ORDER BY h.star_rating DESC, avg_rating DESC";
return $this->db->getRows($sql, $params);
}
}
?>

Reservation Class

File: includes/Reservation.php

<?php
/**
* Reservation Class
* Handles all reservation-related operations
*/
class Reservation {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Create new reservation
*/
public function create($guestId, $data) {
try {
$this->db->beginTransaction();
// Validate dates
$dateValidation = validateBookingDates($data['check_in'], $data['check_out']);
if (!$dateValidation['valid']) {
return ['success' => false, 'error' => $dateValidation['error']];
}
// Check room availability
$availability = checkRoomAvailability(
$data['hotel_id'],
$data['room_type_id'],
$data['check_in'],
$data['check_out'],
$data['rooms'] ?? 1
);
if (!$availability['available']) {
return ['success' => false, 'error' => 'Selected room type is not available for these dates'];
}
// Get room type details
$roomType = $this->db->getRow(
"SELECT * FROM room_types WHERE id = ?",
[$data['room_type_id']]
);
// Calculate price
$nights = calculateNights($data['check_in'], $data['check_out']);
$subtotal = $roomType['base_price'] * $nights * ($data['rooms'] ?? 1);
$tax = $subtotal * (TAX_RATE / 100);
$discount = 0;
// Apply promotion if provided
if (!empty($data['promo_code'])) {
$promoResult = $this->applyPromoCode($data['promo_code'], $subtotal);
if ($promoResult['valid']) {
$discount = $promoResult['discount'];
}
}
$total = $subtotal + $tax - $discount;
// Generate booking code
$bookingCode = generateBookingCode();
// Create reservation
$reservationData = [
'booking_code' => $bookingCode,
'hotel_id' => $data['hotel_id'],
'guest_id' => $guestId,
'room_type_id' => $data['room_type_id'],
'promotion_id' => $promoResult['promotion_id'] ?? null,
'check_in_date' => $data['check_in'],
'check_out_date' => $data['check_out'],
'adults' => $data['adults'] ?? 1,
'children' => $data['children'] ?? 0,
'total_rooms' => $data['rooms'] ?? 1,
'total_nights' => $nights,
'subtotal' => $subtotal,
'tax_amount' => $tax,
'discount_amount' => $discount,
'total_amount' => $total,
'currency' => $roomType['currency'],
'special_requests' => $data['special_requests'] ?? null,
'status' => 'pending',
'payment_status' => 'pending'
];
$reservationId = $this->db->insert('reservations', $reservationData);
// Assign specific rooms if available
if ($reservationId) {
$this->assignRooms($reservationId, $data['room_type_id'], $data['check_in'], $data['check_out'], $data['rooms'] ?? 1);
}
// Create invoice
$this->createInvoice($reservationId);
$this->db->commit();
// Send confirmation email
$this->sendConfirmationEmail($reservationId);
return [
'success' => true,
'reservation_id' => $reservationId,
'booking_code' => $bookingCode,
'message' => 'Reservation created successfully'
];
} catch (Exception $e) {
$this->db->rollback();
logError('Create reservation error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Failed to create reservation: ' . $e->getMessage()];
}
}
/**
* Assign specific rooms to reservation
*/
private function assignRooms($reservationId, $roomTypeId, $checkIn, $checkOut, $roomsNeeded) {
// Get available rooms of this type for these dates
$availableRooms = $this->db->getRows(
"SELECT r.id FROM rooms r
WHERE r.room_type_id = :room_type_id
AND r.status = 'available'
AND r.id NOT IN (
SELECT ar.room_id
FROM assigned_rooms ar
JOIN reservations res ON ar.reservation_id = res.id
WHERE res.room_type_id = :room_type_id
AND res.status NOT IN ('cancelled', 'no_show')
AND (
(res.check_in_date < :check_out AND res.check_out_date > :check_in)
)
)
LIMIT :limit",
[
'room_type_id' => $roomTypeId,
'check_in' => $checkIn,
'check_out' => $checkOut,
'limit' => $roomsNeeded
]
);
foreach ($availableRooms as $room) {
$this->db->insert('assigned_rooms', [
'reservation_id' => $reservationId,
'room_id' => $room['id']
]);
}
}
/**
* Get reservation by ID
*/
public function getReservation($reservationId) {
return $this->db->getRow(
"SELECT r.*, 
h.name as hotel_name, h.address, h.city, h.country,
rt.name as room_type_name, rt.description as room_description,
u.first_name, u.last_name, u.email, u.phone
FROM reservations r
JOIN hotels h ON r.hotel_id = h.id
JOIN room_types rt ON r.room_type_id = rt.id
JOIN users u ON r.guest_id = u.id
WHERE r.id = ? OR r.booking_code = ?",
[$reservationId, $reservationId]
);
}
/**
* Get guest reservations
*/
public function getGuestReservations($guestId, $status = null) {
$sql = "SELECT r.*, h.name as hotel_name, h.city
FROM reservations r
JOIN hotels h ON r.hotel_id = h.id
WHERE r.guest_id = :guest_id";
$params = ['guest_id' => $guestId];
if ($status) {
if (is_array($status)) {
$placeholders = implode(',', array_fill(0, count($status), '?'));
$sql .= " AND r.status IN ($placeholders)";
$params = array_merge([$guestId], $status);
} else {
$sql .= " AND r.status = :status";
$params['status'] = $status;
}
}
$sql .= " ORDER BY r.created_at DESC";
return $this->db->getRows($sql, $params);
}
/**
* Get hotel reservations
*/
public function getHotelReservations($hotelId, $date = null) {
$sql = "SELECT r.*, u.first_name, u.last_name, u.email, u.phone,
rt.name as room_type_name
FROM reservations r
JOIN users u ON r.guest_id = u.id
JOIN room_types rt ON r.room_type_id = rt.id
WHERE r.hotel_id = :hotel_id";
$params = ['hotel_id' => $hotelId];
if ($date) {
$sql .= " AND (r.check_in_date = :date OR r.check_out_date = :date)";
$params['date'] = $date;
}
$sql .= " ORDER BY r.check_in_date ASC";
return $this->db->getRows($sql, $params);
}
/**
* Get today's reservations
*/
public function getTodaysReservations($hotelId = null) {
$today = date('Y-m-d');
$sql = "SELECT r.*, u.first_name, u.last_name, u.phone,
rt.name as room_type_name,
GROUP_CONCAT(rm.room_number) as room_numbers
FROM reservations r
JOIN users u ON r.guest_id = u.id
JOIN room_types rt ON r.room_type_id = rt.id
LEFT JOIN assigned_rooms ar ON r.id = ar.reservation_id
LEFT JOIN rooms rm ON ar.room_id = rm.id
WHERE (r.check_in_date = :today OR r.check_out_date = :today)
AND r.status NOT IN ('cancelled', 'no_show')";
$params = ['today' => $today];
if ($hotelId) {
$sql .= " AND r.hotel_id = :hotel_id";
$params['hotel_id'] = $hotelId;
}
$sql .= " GROUP BY r.id ORDER BY r.check_in_date ASC";
return $this->db->getRows($sql, $params);
}
/**
* Update reservation status
*/
public function updateStatus($reservationId, $status, $notes = null) {
$updateData = ['status' => $status];
if ($notes) {
$updateData['staff_notes'] = $notes;
}
if ($status == 'checked_in') {
$updateData['status'] = 'checked_in';
} elseif ($status == 'checked_out') {
$updateData['status'] = 'checked_out';
} elseif ($status == 'cancelled') {
$updateData['cancelled_at'] = date('Y-m-d H:i:s');
$updateData['cancelled_by'] = $_SESSION['user_id'] ?? null;
}
$this->db->update('reservations', $updateData, 'id = :id', ['id' => $reservationId]);
return ['success' => true, 'message' => 'Reservation status updated'];
}
/**
* Cancel reservation
*/
public function cancel($reservationId, $reason = null) {
try {
$reservation = $this->getReservation($reservationId);
if (!$reservation) {
return ['success' => false, 'error' => 'Reservation not found'];
}
// Check if cancellation is allowed
if ($reservation['status'] == 'checked_in') {
return ['success' => false, 'error' => 'Cannot cancel checked-in reservation'];
}
if ($reservation['status'] == 'checked_out') {
return ['success' => false, 'error' => 'Cannot cancel checked-out reservation'];
}
// Calculate refund if applicable
$refundAmount = 0;
$checkIn = new DateTime($reservation['check_in_date']);
$now = new DateTime();
$hoursUntilCheckIn = ($checkIn->getTimestamp() - $now->getTimestamp()) / 3600;
if ($hoursUntilCheckIn >= CANCELLATION_POLICY) {
$refundAmount = $reservation['total_amount'];
}
$this->db->beginTransaction();
// Update reservation
$this->db->update(
'reservations',
[
'status' => 'cancelled',
'cancellation_reason' => $reason,
'cancelled_at' => date('Y-m-d H:i:s'),
'cancelled_by' => $_SESSION['user_id'] ?? null
],
'id = :id',
['id' => $reservationId]
);
// Process refund if applicable
if ($refundAmount > 0 && $reservation['payment_status'] == 'paid') {
$payment = new Payment();
$payment->refund($reservationId, $refundAmount, 'Booking cancelled');
}
// Release assigned rooms
$this->db->delete('assigned_rooms', 'reservation_id = ?', [$reservationId]);
$this->db->commit();
// Send cancellation email
$this->sendCancellationEmail($reservationId, $reason);
return ['success' => true, 'message' => 'Reservation cancelled successfully'];
} catch (Exception $e) {
$this->db->rollback();
logError('Cancel reservation error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to cancel reservation'];
}
}
/**
* Apply promo code
*/
private function applyPromoCode($code, $amount) {
$promo = $this->db->getRow(
"SELECT * FROM promotions 
WHERE code = ? AND is_active = 1 
AND (start_date IS NULL OR start_date <= CURDATE())
AND (end_date IS NULL OR end_date >= CURDATE())
AND (usage_limit IS NULL OR used_count < usage_limit)",
[$code]
);
if (!$promo) {
return ['valid' => false];
}
// Check minimum amount
if ($promo['min_amount'] && $amount < $promo['min_amount']) {
return ['valid' => false, 'error' => 'Minimum amount requirement not met'];
}
// Calculate discount
if ($promo['discount_type'] == 'percentage') {
$discount = $amount * ($promo['discount_value'] / 100);
} else {
$discount = min($promo['discount_value'], $amount);
}
// Update usage count
$this->db->update(
'promotions',
['used_count' => $promo['used_count'] + 1],
'id = :id',
['id' => $promo['id']]
);
return [
'valid' => true,
'promotion_id' => $promo['id'],
'discount' => $discount
];
}
/**
* Create invoice for reservation
*/
private function createInvoice($reservationId) {
$reservation = $this->getReservation($reservationId);
$invoiceNumber = generateInvoiceNumber();
$invoiceId = $this->db->insert('invoices', [
'invoice_number' => $invoiceNumber,
'reservation_id' => $reservationId,
'guest_id' => $reservation['guest_id'],
'issue_date' => date('Y-m-d'),
'due_date' => date('Y-m-d', strtotime('+7 days')),
'subtotal' => $reservation['subtotal'],
'tax_amount' => $reservation['tax_amount'],
'total_amount' => $reservation['total_amount'],
'status' => 'draft'
]);
// Add invoice items
$this->db->insert('invoice_items', [
'invoice_id' => $invoiceId,
'description' => 'Room charges - ' . $reservation['room_type_name'] . ' for ' . $reservation['total_nights'] . ' nights',
'quantity' => $reservation['total_nights'],
'unit_price' => $reservation['subtotal'] / $reservation['total_nights'],
'amount' => $reservation['subtotal']
]);
if ($reservation['tax_amount'] > 0) {
$this->db->insert('invoice_items', [
'invoice_id' => $invoiceId,
'description' => 'Tax (' . TAX_RATE . '%)',
'quantity' => 1,
'unit_price' => $reservation['tax_amount'],
'amount' => $reservation['tax_amount']
]);
}
return $invoiceId;
}
/**
* Send confirmation email
*/
private function sendConfirmationEmail($reservationId) {
$reservation = $this->getReservation($reservationId);
$guest = $this->db->getRow("SELECT * FROM users WHERE id = ?", [$reservation['guest_id']]);
$data = [
'guest_name' => $guest['first_name'] . ' ' . $guest['last_name'],
'booking_code' => $reservation['booking_code'],
'hotel_name' => $reservation['hotel_name'],
'room_type' => $reservation['room_type_name'],
'check_in' => formatDate($reservation['check_in_date']),
'check_out' => formatDate($reservation['check_out_date']),
'nights' => $reservation['total_nights'],
'guests' => $reservation['adults'] . ' Adults, ' . $reservation['children'] . ' Children',
'total' => formatAmount($reservation['total_amount']),
'special_requests' => $reservation['special_requests']
];
sendEmail($guest['email'], 'Booking Confirmation - ' . APP_NAME, 'booking_confirmation', $data);
}
/**
* Send cancellation email
*/
private function sendCancellationEmail($reservationId, $reason) {
$reservation = $this->getReservation($reservationId);
$guest = $this->db->getRow("SELECT * FROM users WHERE id = ?", [$reservation['guest_id']]);
$data = [
'guest_name' => $guest['first_name'] . ' ' . $guest['last_name'],
'booking_code' => $reservation['booking_code'],
'hotel_name' => $reservation['hotel_name'],
'reason' => $reason
];
sendEmail($guest['email'], 'Booking Cancelled - ' . APP_NAME, 'booking_cancelled', $data);
}
/**
* Get reservation statistics
*/
public function getStatistics($hotelId = null, $startDate = null, $endDate = null) {
if (!$startDate) {
$startDate = date('Y-m-01');
}
if (!$endDate) {
$endDate = date('Y-m-t');
}
$sql = "SELECT 
COUNT(*) as total_reservations,
SUM(CASE WHEN status = 'confirmed' THEN 1 ELSE 0 END) as confirmed,
SUM(CASE WHEN status = 'checked_in' THEN 1 ELSE 0 END) as checked_in,
SUM(CASE WHEN status = 'checked_out' THEN 1 ELSE 0 END) as checked_out,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled,
SUM(total_amount) as total_revenue,
AVG(total_nights) as avg_stay
FROM reservations
WHERE check_in_date BETWEEN :start_date AND :end_date";
$params = [
'start_date' => $startDate,
'end_date' => $endDate
];
if ($hotelId) {
$sql .= " AND hotel_id = :hotel_id";
$params['hotel_id'] = $hotelId;
}
return $this->db->getRow($sql, $params);
}
/**
* Check-in guest
*/
public function checkIn($reservationId) {
$result = $this->updateStatus($reservationId, 'checked_in');
if ($result['success']) {
// Update room status to occupied
$assignedRooms = $this->db->getRows(
"SELECT room_id FROM assigned_rooms WHERE reservation_id = ?",
[$reservationId]
);
foreach ($assignedRooms as $room) {
$this->db->update(
'rooms',
['status' => 'occupied'],
'id = :id',
['id' => $room['room_id']]
);
}
}
return $result;
}
/**
* Check-out guest
*/
public function checkOut($reservationId) {
$result = $this->updateStatus($reservationId, 'checked_out');
if ($result['success']) {
// Update room status to cleaning
$assignedRooms = $this->db->getRows(
"SELECT room_id FROM assigned_rooms WHERE reservation_id = ?",
[$reservationId]
);
foreach ($assignedRooms as $room) {
$this->db->update(
'rooms',
['status' => 'cleaning'],
'id = :id',
['id' => $room['room_id']]
);
}
// Update invoice status
$this->db->update(
'invoices',
['status' => 'paid'],
'reservation_id = :id',
['id' => $reservationId]
);
}
return $result;
}
}
?>

Frontend Pages

Main Landing Page

File: index.php

<?php
require_once 'includes/config.php';
// Get featured hotels
$hotel = new Hotel();
$featuredHotels = $hotel->getHotels(['limit' => 6]);
// Get popular destinations
$destinations = $db->getRows(
"SELECT city, country, COUNT(*) as hotel_count 
FROM hotels 
WHERE status = 'active' 
GROUP BY city, country 
ORDER BY hotel_count DESC 
LIMIT 6"
);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo APP_NAME; ?> - Book Your Stay</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">
<!-- Date Range Picker -->
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css" />
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm fixed-top">
<div class="container">
<a class="navbar-brand" href="index.php">
<i class="fas fa-hotel text-primary me-2"></i>
<?php echo APP_NAME; ?>
</a>
<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="#destinations">Destinations</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#hotels">Hotels</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#offers">Offers</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'] == 'staff' ? 'staff/dashboard.php' : 'guest/dashboard.php'); 
?>">
<i class="fas fa-tachometer-alt me-2"></i>Dashboard
</a>
</li>
<?php if ($_SESSION['user_role'] == 'guest'): ?>
<li>
<a class="dropdown-item" href="guest/my_bookings.php">
<i class="fas fa-calendar-check me-2"></i>My Bookings
</a>
</li>
<?php endif; ?>
<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 justify-content-center">
<div class="col-lg-8 text-center">
<h1 class="display-4 fw-bold mb-4">Find Your Perfect Stay</h1>
<p class="lead mb-5">Discover amazing hotels at the best prices. Book with confidence.</p>
<!-- Search Form -->
<div class="search-box bg-white p-4 rounded-4 shadow-lg">
<form action="guest/search.php" method="GET" id="searchForm">
<div class="row g-3">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text bg-light border-0">
<i class="fas fa-map-marker-alt text-primary"></i>
</span>
<input type="text" class="form-control form-control-lg border-0 bg-light" 
name="location" placeholder="Where are you going?" required>
</div>
</div>
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text bg-light border-0">
<i class="fas fa-calendar-alt text-primary"></i>
</span>
<input type="text" class="form-control form-control-lg border-0 bg-light" 
name="dates" id="dateRange" placeholder="Check-in - Check-out">
</div>
</div>
<div class="col-md-4">
<div class="input-group">
<span class="input-group-text bg-light border-0">
<i class="fas fa-user text-primary"></i>
</span>
<select class="form-select form-select-lg border-0 bg-light" name="adults">
<option value="1">1 Adult</option>
<option value="2" selected>2 Adults</option>
<option value="3">3 Adults</option>
<option value="4">4 Adults</option>
</select>
</div>
</div>
<div class="col-md-4">
<div class="input-group">
<span class="input-group-text bg-light border-0">
<i class="fas fa-child text-primary"></i>
</span>
<select class="form-select form-select-lg border-0 bg-light" name="children">
<option value="0">0 Children</option>
<option value="1">1 Child</option>
<option value="2">2 Children</option>
<option value="3">3 Children</option>
</select>
</div>
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-primary btn-lg w-100">
<i class="fas fa-search me-2"></i>Search
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
<!-- Popular Destinations -->
<section id="destinations" class="py-5">
<div class="container">
<h2 class="text-center fw-bold mb-5">Popular Destinations</h2>
<div class="row g-4">
<?php foreach ($destinations as $dest): ?>
<div class="col-lg-4 col-md-6">
<a href="guest/search.php?location=<?php echo urlencode($dest['city']); ?>" class="text-decoration-none">
<div class="destination-card position-relative rounded-4 overflow-hidden">
<img src="assets/images/destinations/<?php echo strtolower($dest['city']); ?>.jpg" 
class="img-fluid" alt="<?php echo $dest['city']; ?>">
<div class="destination-overlay position-absolute bottom-0 start-0 w-100 p-4 text-white">
<h4 class="mb-1"><?php echo $dest['city']; ?></h4>
<p class="mb-0"><?php echo $dest['hotel_count']; ?> Hotels</p>
</div>
</div>
</a>
</div>
<?php endforeach; ?>
</div>
</div>
</section>
<!-- Featured Hotels -->
<section id="hotels" class="py-5 bg-light">
<div class="container">
<h2 class="text-center fw-bold mb-5">Featured Hotels</h2>
<div class="row g-4">
<?php foreach ($featuredHotels as $hotel): ?>
<div class="col-lg-4 col-md-6">
<div class="card h-100 border-0 shadow-sm hotel-card">
<div class="position-relative">
<img src="uploads/hotels/<?php echo $hotel['images'] ? json_decode($hotel['images'])[0] : 'default-hotel.jpg'; ?>" 
class="card-img-top" alt="<?php echo $hotel['name']; ?>" style="height: 200px; object-fit: cover;">
<span class="badge bg-primary position-absolute top-0 end-0 m-3">
<i class="fas fa-star me-1"></i><?php echo $hotel['star_rating']; ?>
</span>
</div>
<div class="card-body">
<h5 class="card-title"><?php echo $hotel['name']; ?></h5>
<p class="card-text text-muted small">
<i class="fas fa-map-marker-alt me-1"></i><?php echo $hotel['city'] . ', ' . $hotel['country']; ?>
</p>
<div class="d-flex align-items-center mb-2">
<div class="rating me-2">
<?php for ($i = 1; $i <= 5; $i++): ?>
<i class="fas fa-star <?php echo $i <= round($hotel['avg_rating']) ? 'text-warning' : 'text-muted'; ?>"></i>
<?php endfor; ?>
</div>
<span class="text-muted small">(<?php echo $hotel['review_count']; ?> reviews)</span>
</div>
<p class="card-text text-muted small">
<?php echo substr($hotel['description'] ?? '', 0, 100) . '...'; ?>
</p>
</div>
<div class="card-footer bg-white border-0 pb-3">
<div class="d-flex justify-content-between align-items-center">
<div>
<small class="text-muted">Starting from</small>
<h5 class="mb-0 text-primary"><?php echo formatAmount($hotel['min_price'] ?? 99); ?></h5>
<small class="text-muted">per night</small>
</div>
<a href="guest/hotel_details.php?id=<?php echo $hotel['id']; ?>" class="btn btn-outline-primary">
View Hotel
</a>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="text-center mt-4">
<a href="guest/search.php" class="btn btn-outline-primary btn-lg">
View All Hotels <i class="fas fa-arrow-right ms-2"></i>
</a>
</div>
</div>
</section>
<!-- Why Choose Us -->
<section class="py-5">
<div class="container">
<h2 class="text-center fw-bold mb-5">Why Choose Us</h2>
<div class="row g-4">
<div class="col-md-3">
<div class="text-center">
<div class="feature-icon bg-primary text-white rounded-circle mx-auto mb-3">
<i class="fas fa-tag"></i>
</div>
<h5>Best Price Guarantee</h5>
<p class="text-muted">We match any lower price found elsewhere</p>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<div class="feature-icon bg-primary text-white rounded-circle mx-auto mb-3">
<i class="fas fa-calendar-check"></i>
</div>
<h5>Free Cancellation</h5>
<p class="text-muted">Cancel up to 24 hours before check-in</p>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<div class="feature-icon bg-primary text-white rounded-circle mx-auto mb-3">
<i class="fas fa-headset"></i>
</div>
<h5>24/7 Customer Support</h5>
<p class="text-muted">We're here to help anytime</p>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<div class="feature-icon bg-primary text-white rounded-circle mx-auto mb-3">
<i class="fas fa-shield-alt"></i>
</div>
<h5>Secure Booking</h5>
<p class="text-muted">Your information is always safe</p>
</div>
</div>
</div>
</div>
</section>
<!-- Special Offers -->
<section id="offers" class="py-5 bg-light">
<div class="container">
<h2 class="text-center fw-bold mb-5">Special Offers</h2>
<div class="row">
<?php
$promotions = $db->getRows(
"SELECT p.*, h.name as hotel_name, h.city 
FROM promotions p
JOIN hotels h ON p.hotel_id = h.id
WHERE p.is_active = 1 
AND (p.start_date IS NULL OR p.start_date <= CURDATE())
AND (p.end_date IS NULL OR p.end_date >= CURDATE())
ORDER BY p.created_at DESC
LIMIT 3"
);
foreach ($promotions as $promo):
?>
<div class="col-md-4 mb-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<h5 class="card-title text-primary"><?php echo $promo['name']; ?></h5>
<h6 class="card-subtitle mb-2 text-muted"><?php echo $promo['hotel_name'] . ', ' . $promo['city']; ?></h6>
<div class="offer-badge bg-success text-white p-2 rounded mb-3">
<?php if ($promo['discount_type'] == 'percentage'): ?>
Save <?php echo $promo['discount_value']; ?>% on your stay
<?php else: ?>
Save <?php echo formatAmount($promo['discount_value']); ?> on your stay
<?php endif; ?>
</div>
<p class="card-text"><?php echo $promo['description']; ?></p>
<?php if ($promo['min_stay']): ?>
<small class="text-muted d-block">Minimum stay: <?php echo $promo['min_stay']; ?> nights</small>
<?php endif; ?>
<?php if ($promo['end_date']): ?>
<small class="text-muted d-block">Valid until: <?php echo formatDate($promo['end_date']); ?></small>
<?php endif; ?>
<a href="guest/search.php?hotel=<?php echo $promo['hotel_id']; ?>" class="btn btn-outline-primary w-100 mt-3">
Book Now <i class="fas fa-arrow-right ms-2"></i>
</a>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</section>
<!-- Testimonials -->
<section class="py-5">
<div class="container">
<h2 class="text-center fw-bold mb-5">What Our Guests Say</h2>
<div class="row">
<?php
$reviews = $db->getRows(
"SELECT r.*, u.first_name, u.last_name, u.profile_picture, h.name as hotel_name
FROM reviews r
JOIN users u ON r.guest_id = u.id
JOIN hotels h ON r.hotel_id = h.id
ORDER BY r.created_at DESC
LIMIT 3"
);
foreach ($reviews as $review):
?>
<div class="col-md-4 mb-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<div class="mb-3">
<?php echo getRatingStars($review['rating']); ?>
</div>
<h6 class="card-title"><?php echo $review['title'] ?? 'Great Experience'; ?></h6>
<p class="card-text">"<?php echo substr($review['comment'], 0, 100) . '...'; ?>"</p>
<div class="d-flex align-items-center mt-3">
<img src="uploads/avatars/<?php echo $review['profile_picture']; ?>" 
class="rounded-circle me-3" width="40" height="40" alt="User">
<div>
<h6 class="mb-0"><?php echo $review['first_name'] . ' ' . $review['last_name']; ?></h6>
<small class="text-muted">Stayed at <?php echo $review['hotel_name']; ?></small>
</div>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</section>
<!-- Call to Action -->
<section class="py-5 bg-primary text-white">
<div class="container text-center py-4">
<h2 class="fw-bold mb-4">Ready for Your Next Getaway?</h2>
<p class="lead mb-4">Join thousands of happy travelers who book with us.</p>
<?php if (!$auth->isLoggedIn()): ?>
<a href="register.php" class="btn btn-light btn-lg px-5 me-2">Sign Up Now</a>
<a href="guest/search.php" class="btn btn-outline-light btn-lg px-5">Browse Hotels</a>
<?php else: ?>
<a href="guest/search.php" class="btn btn-light btn-lg px-5">Find Your Stay</a>
<?php endif; ?>
</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-hotel me-2"></i><?php echo APP_NAME; ?></h5>
<p class="text-white-50">Your trusted partner for hotel reservations worldwide. Find the perfect stay at the best price.</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="#destinations" class="text-white-50">Destinations</a></li>
<li><a href="#hotels" class="text-white-50">Hotels</a></li>
<li><a href="#offers" class="text-white-50">Offers</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="partner/register.php" class="text-white-50">List Your Property</a></li>
<li><a href="partner/login.php" class="text-white-50">Partner Login</a></li>
<li><a href="#" class="text-white-50">Partner Resources</a></li>
<li><a href="#" class="text-white-50">FAQs</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 COMPANY_ADDRESS; ?></li>
<li><i class="fas fa-phone me-2"></i><?php echo COMPANY_PHONE; ?></li>
<li><i class="fas fa-envelope me-2"></i><?php echo COMPANY_EMAIL; ?></li>
</ul>
</div>
</div>
<hr class="border-secondary">
<div class="row">
<div class="col-md-6">
<p class="text-white-50 mb-0">&copy; <?php echo date('Y'); ?> <?php echo APP_NAME; ?>. All rights reserved.</p>
</div>
<div class="col-md-6 text-md-end">
<a href="#" class="text-white-50 me-3">Privacy Policy</a>
<a href="#" class="text-white-50 me-3">Terms of Service</a>
<a href="#" 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]/min/moment.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
<script>
$(document).ready(function() {
// Date Range Picker
$('#dateRange').daterangepicker({
minDate: moment(),
startDate: moment().add(1, 'days'),
endDate: moment().add(2, 'days'),
locale: {
format: 'YYYY-MM-DD'
}
});
});
</script>
<style>
.feature-icon {
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
}
.destination-card {
height: 250px;
cursor: pointer;
transition: transform 0.3s ease;
}
.destination-card:hover {
transform: scale(1.05);
}
.destination-card img {
width: 100%;
height: 100%;
object-fit: cover;
}
.destination-overlay {
background: linear-gradient(transparent, rgba(0,0,0,0.7));
}
.hotel-card {
transition: transform 0.3s ease;
}
.hotel-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 40px rgba(0,0,0,0.15) !important;
}
.offer-badge {
display: inline-block;
font-size: 0.9rem;
font-weight: 600;
}
.navbar {
padding: 1rem 0;
}
.hero-section {
margin-top: 76px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.search-box {
max-width: 800px;
margin: 0 auto;
}
.rating i {
font-size: 0.9rem;
}
</style>
</body>
</html>

Environment Configuration

File: .env

# Database Configuration
DB_HOST=localhost
DB_NAME=hotel_reservation_system
DB_USER=root
DB_PASS=
# Application Configuration
APP_NAME=Hotel Reservation System
APP_URL=http://localhost/hotel-reservation-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
# Business Settings
COMPANY_NAME=Hotel Reservation System
[email protected]
COMPANY_PHONE=+1-555-123-4567
COMPANY_ADDRESS=123 Hotel Street, Suite 100, City, State 12345
TAX_RATE=10.00
CURRENCY=USD
CURRENCY_SYMBOL=$
# Booking Settings
MAX_ADVANCE_BOOKING=365
MIN_ADVANCE_BOOKING=0
CANCELLATION_POLICY=24
DEFAULT_CHECK_IN=15:00
DEFAULT_CHECK_OUT=11:00
# Notification Settings
ENABLE_REMINDERS=true
REMINDER_HOURS=24
# Payment Settings
ENABLE_ONLINE_PAYMENTS=true
PAYMENT_GATEWAY=stripe
STRIPE_KEY=your_stripe_key
STRIPE_SECRET=your_stripe_secret
PAYPAL_CLIENT_ID=your_paypal_client_id
PAYPAL_SECRET=your_paypal_secret

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
# Cache
/cache/
!/cache/.gitkeep
# Composer
composer.lock
# Temp files
*.tmp
*.temp

File: composer.json

{
"name": "hotel-reservation-system/application",
"description": "Complete Hotel Reservation System with Online Booking",
"type": "project",
"require": {
"php": ">=7.4",
"ext-pdo": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-curl": "*",
"ext-gd": "*",
"phpmailer/phpmailer": "^6.8",
"tecnickcom/tcpdf": "^6.6",
"stripe/stripe-php": "^13.0",
"paypal/rest-api-sdk-php": "^1.14"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"autoload": {
"psr-4": {
"HotelSystem\\": "src/"
}
},
"scripts": {
"test": "phpunit tests",
"post-install-cmd": [
"chmod -R 755 uploads/",
"chmod -R 755 logs/",
"chmod -R 755 cache/"
]
}
}

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

Prerequisites

  1. Web Server: XAMPP, WAMP, MAMP, or any PHP-enabled server (PHP 7.4+)
  2. Database: MySQL 5.7+ or MariaDB
  3. Composer: For dependency management
  4. Browser: Modern web browser (Chrome, Firefox, Edge, etc.)

Installation Steps

Step 1: Set Up Local Server

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

Step 2: Install Composer Dependencies

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

Step 3: Create Project Folder

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

Step 4: Set Up Database

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

Step 5: Configure Environment

  1. Rename .env.example to .env in the project root
  2. Update database credentials if different from default:
   DB_HOST=localhost
DB_NAME=hotel_reservation_system
DB_USER=root
DB_PASS=
  1. Update application URL:
   APP_URL=http://localhost/hotel-reservation-system
  1. Configure email settings if using email notifications
  2. Configure payment gateway if using online payments

Step 6: Set Folder Permissions

Create the following folders and ensure they are writable:

  • uploads/hotels/
  • uploads/rooms/
  • uploads/avatars/
  • logs/
  • cache/

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

Step 7: Create Admin Password Hash

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

Step 8: Test the Installation

  1. Open browser and go to http://localhost/hotel-reservation-system/
  2. You should see the landing page with search form
  3. Test different user types: Admin Login:
  • Email: [email protected]
  • Password: Admin@123 (or the password you set) Staff Registration:
  • Register a new user
  • Admin can promote to staff in admin panel Guest Registration:
  • Register as a new guest
  • Search and book rooms

System Walkthrough

For Guests:

  1. Search Hotels - Enter destination, dates, and guests
  2. Browse Results - View available hotels with prices
  3. View Hotel Details - Check amenities, photos, reviews
  4. Select Room - Choose room type and view rates
  5. Make Booking - Enter guest details and special requests
  6. Apply Promo Code - Add discount codes if available
  7. Payment - Pay online or choose pay at hotel
  8. Confirmation - Receive booking confirmation via email
  9. Manage Bookings - View, modify, or cancel bookings
  10. Leave Review - Rate and review after stay

For Staff:

  1. Dashboard - View today's check-ins, check-outs, occupancy
  2. Calendar View - See all reservations on calendar
  3. Manage Reservations - Create, modify, cancel bookings
  4. Check-in Process - Verify guest details and assign rooms
  5. Check-out Process - Process payments and generate invoices
  6. Room Status - Update room status (clean, maintenance)
  7. Guest Management - View guest history and preferences
  8. Reports - Generate occupancy and revenue reports

For Admins:

  1. Dashboard - View system-wide statistics
  2. Hotel Management - Add/edit hotels and properties
  3. Room Management - Manage room types and inventory
  4. Rate Management - Set seasonal rates and promotions
  5. Staff Management - Add/edit staff accounts
  6. Booking Oversight - View all reservations across hotels
  7. Financial Reports - Revenue analysis and forecasting
  8. System Settings - Configure global parameters

Key Features Explained

Search and Booking Flow

  1. Guest enters destination, dates, and number of guests
  2. System shows available hotels with real-time pricing
  3. Guest selects hotel and room type
  4. System calculates total price including taxes
  5. Guest provides personal details and special requests
  6. Optional promo code application
  7. Payment processing (online or pay at hotel)
  8. Instant confirmation email with booking details

Inventory Management

  1. Each room type has total quantity
  2. System tracks booked rooms per date
  3. Real-time availability checking
  4. Automatic room assignment at booking
  5. Maintenance and cleaning status tracking
  6. Overbooking prevention

Dynamic Pricing

  1. Base rates per room type
  2. Seasonal rate multipliers
  3. Special promotions and discounts
  4. Minimum stay requirements
  5. Advance purchase discounts
  6. Last-minute deals

Calendar Integration

  1. Visual calendar view of bookings
  2. Drag-and-drop rescheduling
  3. Color-coded by status
  4. Quick check-in/check-out actions
  5. Export to Google/Outlook calendar

Troubleshooting

Common Issues and Solutions

  1. Database Connection Error
  • Check if MySQL is running
  • Verify database credentials in .env
  • Ensure database hotel_reservation_system exists
  1. 404 Page Not Found
  • Check file paths and folder structure
  • Verify APP_URL in .env
  • Ensure .htaccess is properly configured (if using Apache)
  1. Email Not Sending
  • Configure SMTP settings in PHPMailer
  • Check spam folder
  • Verify PHP mail() function is enabled
  1. Payment Gateway Errors
  • Verify API keys in .env
  • Check SSL certificate (for production)
  • Test in sandbox mode first
  1. Room Availability Issues
  • Check room type quantities
  • Verify no conflicting bookings
  • Check maintenance status
  1. Date Calculation Errors
  • Verify timezone settings
  • Check date format in .env
  • Ensure MySQL timezone is set correctly

Security Best Practices

  1. Change default admin password immediately after installation
  2. Use HTTPS in production with SSL certificate
  3. Regular backups of database and uploads
  4. Input validation on both client and server side
  5. SQL injection prevention using prepared statements
  6. XSS prevention using htmlspecialchars()
  7. CSRF tokens for all forms
  8. Password hashing using bcrypt
  9. Rate limiting for login attempts
  10. Session security with proper timeout
  11. PCI compliance for payment processing

Performance Optimizations

  1. Database indexing on frequently queried columns
  2. Query caching for repeated requests
  3. Image optimization for hotel photos
  4. Lazy loading for images and content
  5. Pagination for large datasets
  6. Minified CSS and JavaScript for production
  7. CDN for static assets
  8. Browser caching headers
  9. Redis/Memcached for session storage (optional)

Cron Jobs Setup

Create the following cron jobs for automated tasks:

# Send booking reminders (run daily at 8 AM)
0 8 * * * php /path/to/project/cron/send_reminders.php
# Update seasonal rates (run daily at midnight)
0 0 * * * php /path/to/project/cron/update_rates.php
# Generate daily reports (run at 11:59 PM)
59 23 * * * php /path/to/project/cron/generate_reports.php
# Clean up expired sessions (run hourly)
0 * * * * php /path/to/project/cron/clean_sessions.php

Conclusion

The Hotel Reservation System is a comprehensive, feature-rich platform for managing hotel bookings online. With its intuitive interface, real-time availability, and powerful management tools, it provides everything needed for both guests to book rooms and hotel staff to manage reservations efficiently.

This application demonstrates:

  • Secure user authentication with role-based access
  • Real-time availability checking with conflict prevention
  • Dynamic pricing with seasonal rates and promotions
  • Payment processing integration with multiple gateways
  • Email notifications for booking confirmations and reminders
  • Invoice generation with PDF export
  • Responsive design for all devices
  • Modular code structure following OOP principles
  • Database design with proper relationships and indexing
  • Error handling and logging
  • Security best practices throughout

The system is built to be scalable, allowing easy addition of new features such as:

  • Multiple property management for hotel chains
  • Channel manager integration with OTAs
  • Loyalty program and reward points
  • Mobile check-in and digital keys
  • Revenue management system
  • Advanced reporting and analytics
  • Multi-language and multi-currency support

With proper deployment, regular backups, and security updates, this system can serve as a reliable platform for hotels of any size, from boutique properties to large chains.

Leave a Reply

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


Macro Nepal Helper