Complete Project with HTML, CSS, JavaScript, PHP, and MySQL
TABLE OF CONTENTS
- Project Overview
- System Architecture
- Database Design
- Backend Implementation
- Frontend Implementation
- Algorithm Implementation
- Installation Guide
- API Documentation
- Testing Guide
- Deployment Guide
1. PROJECT OVERVIEW {#overview}
Job Recommendation System using Content-Based Filtering
A sophisticated job recommendation platform that matches job seekers with relevant positions based on their skills, experience, education, and career preferences using content-based filtering algorithms.
Key Features
For Job Seekers:
- User registration and profile management
- Skill assessment and profiling
- Resume parsing and skill extraction
- Personalized job recommendations
- Job application tracking
- Saved jobs and alerts
- Career path suggestions
For Employers:
- Company registration
- Job posting management
- Candidate matching
- Application management
- Interview scheduling
Core Functionality:
- Content-based filtering algorithm
- Skill similarity matching
- Experience level matching
- Location-based filtering
- Salary range matching
- Industry preference matching
- Real-time recommendation updates
Technology Stack
| Component | Technology | Purpose |
|---|---|---|
| Frontend | HTML5, CSS3, JavaScript | User interface |
| Styling | Tailwind CSS | Responsive design |
| Backend | PHP 8.0+ | Business logic |
| Database | MySQL 5.7+ | Data storage |
| Algorithms | PHP | Content-based filtering |
| Security | PHP password_hash() | Authentication |
| API | REST | Data exchange |
| Version Control | Git | Code management |
2. SYSTEM ARCHITECTURE {#architecture}
┌─────────────────────────────────────────────────────────────┐ │ CLIENT LAYER │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ HTML5 │ │ CSS3 │ │ JavaScript │ │ │ │ Structure │ │ Styling │ │ Interaction│ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ API LAYER │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ RESTful API Endpoints │ │ │ │ /api/jobs ● /api/users ● /api/recommendations │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ BUSINESS LOGIC LAYER │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ User │ │ Job │ │Application │ │ │ │ Management │ │ Management │ │ Management │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ Content- │ │ Similarity │ │ Ranking │ │ │ │ Based │ │ Calculator │ │ Algorithm │ │ │ │ Filtering │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ DATA LAYER │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ MySQL Database │ │ │ │ users ● jobs ● skills ● applications ● matches │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘
3. DATABASE DESIGN {#database}
Database Schema
-- Create database
CREATE DATABASE IF NOT EXISTS job_recommendation_system;
USE job_recommendation_system;
-- Users table (Job Seekers)
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
full_name VARCHAR(100) NOT NULL,
phone VARCHAR(20),
location VARCHAR(100),
date_of_birth DATE,
profile_headline VARCHAR(200),
profile_summary TEXT,
profile_picture VARCHAR(255),
resume_url VARCHAR(255),
experience_years DECIMAL(3,1),
current_salary DECIMAL(10,2),
expected_salary DECIMAL(10,2),
notice_period_days INT,
willing_to_relocate BOOLEAN DEFAULT TRUE,
willing_to_remote BOOLEAN DEFAULT TRUE,
preferred_job_type VARCHAR(50), -- 'full-time', 'part-time', 'contract', 'internship'
preferred_industries JSON, -- Array of preferred industries
preferred_locations JSON, -- Array of preferred locations
is_active BOOLEAN DEFAULT TRUE,
last_login TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_location (location),
INDEX idx_experience (experience_years),
INDEX idx_active (is_active)
);
-- Companies table
CREATE TABLE companies (
id INT PRIMARY KEY AUTO_INCREMENT,
company_name VARCHAR(100) NOT NULL,
company_logo VARCHAR(255),
company_website VARCHAR(255),
company_description TEXT,
industry VARCHAR(100),
company_size VARCHAR(50), -- '1-10', '11-50', '51-200', '201-500', '500+'
founded_year YEAR,
headquarters VARCHAR(200),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_industry (industry)
);
-- Employers (Company Representatives)
CREATE TABLE employers (
id INT PRIMARY KEY AUTO_INCREMENT,
company_id INT NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
full_name VARCHAR(100) NOT NULL,
designation VARCHAR(100),
phone VARCHAR(20),
is_primary_contact BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE,
INDEX idx_company (company_id)
);
-- Jobs table
CREATE TABLE jobs (
id INT PRIMARY KEY AUTO_INCREMENT,
company_id INT NOT NULL,
employer_id INT NOT NULL,
job_title VARCHAR(200) NOT NULL,
job_description TEXT NOT NULL,
job_type VARCHAR(50) NOT NULL, -- 'full-time', 'part-time', 'contract', 'internship'
work_mode VARCHAR(50), -- 'remote', 'hybrid', 'onsite'
location VARCHAR(200),
industry VARCHAR(100),
functional_area VARCHAR(100),
role_category VARCHAR(100),
min_experience INT,
max_experience INT,
min_salary DECIMAL(10,2),
max_salary DECIMAL(10,2),
currency VARCHAR(10) DEFAULT 'USD',
skills_required JSON, -- Array of skills with proficiency levels
education_required JSON, -- Array of education requirements
certifications_required JSON, -- Array of certifications
benefits JSON, -- Array of benefits
vacancies INT DEFAULT 1,
application_deadline DATE,
status ENUM('draft', 'published', 'closed', 'filled') DEFAULT 'draft',
views_count INT DEFAULT 0,
applications_count INT DEFAULT 0,
is_featured BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
published_at TIMESTAMP NULL,
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE,
FOREIGN KEY (employer_id) REFERENCES employers(id),
INDEX idx_job_type (job_type),
INDEX idx_location (location),
INDEX idx_industry (industry),
INDEX idx_experience (min_experience, max_experience),
INDEX idx_salary (min_salary, max_salary),
INDEX idx_status (status),
FULLTEXT INDEX ft_job_search (job_title, job_description)
);
-- Skills master table
CREATE TABLE skills (
id INT PRIMARY KEY AUTO_INCREMENT,
skill_name VARCHAR(100) UNIQUE NOT NULL,
skill_category VARCHAR(50),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_category (skill_category)
);
-- User skills
CREATE TABLE user_skills (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
skill_id INT NOT NULL,
proficiency_level ENUM('beginner', 'intermediate', 'advanced', 'expert') DEFAULT 'intermediate',
years_of_experience DECIMAL(3,1),
is_primary BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (skill_id) REFERENCES skills(id) ON DELETE CASCADE,
UNIQUE KEY uk_user_skill (user_id, skill_id),
INDEX idx_proficiency (proficiency_level)
);
-- User education
CREATE TABLE user_education (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
degree VARCHAR(100),
field_of_study VARCHAR(100),
institution VARCHAR(200),
location VARCHAR(100),
graduation_year YEAR,
percentage DECIMAL(5,2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_field (field_of_study)
);
-- User experience
CREATE TABLE user_experience (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
job_title VARCHAR(100),
company_name VARCHAR(100),
location VARCHAR(100),
start_date DATE,
end_date DATE,
is_current BOOLEAN DEFAULT FALSE,
description TEXT,
skills_used JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_company (company_name)
);
-- Job applications
CREATE TABLE job_applications (
id INT PRIMARY KEY AUTO_INCREMENT,
job_id INT NOT NULL,
user_id INT NOT NULL,
application_status ENUM('draft', 'submitted', 'under_review', 'shortlisted', 'rejected', 'hired', 'withdrawn') DEFAULT 'draft',
cover_letter TEXT,
resume_url VARCHAR(255),
expected_salary DECIMAL(10,2),
notice_period_days INT,
employer_notes TEXT,
interview_date DATETIME,
interview_feedback TEXT,
viewed_by_employer BOOLEAN DEFAULT FALSE,
viewed_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (job_id) REFERENCES jobs(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE KEY uk_job_user (job_id, user_id),
INDEX idx_status (application_status),
INDEX idx_created (created_at)
);
-- Saved jobs (for later viewing)
CREATE TABLE saved_jobs (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
job_id INT NOT NULL,
saved_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (job_id) REFERENCES jobs(id) ON DELETE CASCADE,
UNIQUE KEY uk_user_job_saved (user_id, job_id)
);
-- Job alerts
CREATE TABLE job_alerts (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
alert_name VARCHAR(100),
keywords JSON,
industries JSON,
locations JSON,
job_types JSON,
min_salary DECIMAL(10,2),
max_experience INT,
frequency ENUM('daily', 'weekly', 'instant') DEFAULT 'daily',
is_active BOOLEAN DEFAULT TRUE,
last_sent TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_active (is_active)
);
-- Recommendation matches (cached for performance)
CREATE TABLE job_recommendations (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
job_id INT NOT NULL,
match_score DECIMAL(5,2), -- Percentage match
skill_match_score DECIMAL(5,2),
experience_match_score DECIMAL(5,2),
location_match_score DECIMAL(5,2),
salary_match_score DECIMAL(5,2),
industry_match_score DECIMAL(5,2),
education_match_score DECIMAL(5,2),
is_viewed BOOLEAN DEFAULT FALSE,
is_applied BOOLEAN DEFAULT FALSE,
viewed_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (job_id) REFERENCES jobs(id) ON DELETE CASCADE,
UNIQUE KEY uk_user_job_rec (user_id, job_id),
INDEX idx_match_score (match_score),
INDEX idx_created (created_at)
);
-- User activity log for analytics
CREATE TABLE user_activity_log (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
activity_type VARCHAR(50), -- 'search', 'view_job', 'apply', 'save', 'share'
activity_data JSON,
ip_address VARCHAR(45),
user_agent TEXT,
session_id VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL,
INDEX idx_user_activity (user_id, activity_type),
INDEX idx_created (created_at)
);
-- Insert sample skills
INSERT INTO skills (skill_name, skill_category) VALUES
('PHP', 'Programming'),
('JavaScript', 'Programming'),
('Python', 'Programming'),
('Java', 'Programming'),
('HTML', 'Web Development'),
('CSS', 'Web Development'),
('React', 'Frontend'),
('Node.js', 'Backend'),
('MySQL', 'Database'),
('MongoDB', 'Database'),
('AWS', 'Cloud Computing'),
('Docker', 'DevOps'),
('Kubernetes', 'DevOps'),
('Machine Learning', 'Data Science'),
('Data Analysis', 'Data Science'),
('Project Management', 'Management'),
('Agile', 'Methodology'),
('Scrum', 'Methodology'),
('Communication', 'Soft Skills'),
('Leadership', 'Soft Skills'),
('Problem Solving', 'Soft Skills'),
('Team Work', 'Soft Skills'),
('Microsoft Excel', 'Office'),
('Microsoft Word', 'Office'),
('Microsoft PowerPoint', 'Office'),
('Photoshop', 'Design'),
('Illustrator', 'Design'),
('Figma', 'Design'),
('SEO', 'Marketing'),
('Digital Marketing', 'Marketing'),
('Content Writing', 'Writing'),
('Copywriting', 'Writing'),
('Sales', 'Business'),
('Customer Service', 'Business'),
('Negotiation', 'Business');
-- Insert sample companies
INSERT INTO companies (company_name, industry, company_size, headquarters) VALUES
('Tech Corp', 'Information Technology', '201-500', 'San Francisco, CA'),
('Data Systems Inc', 'Data Analytics', '51-200', 'New York, NY'),
('Web Solutions LLC', 'Web Development', '11-50', 'Austin, TX'),
('Cloud Innovators', 'Cloud Computing', '51-200', 'Seattle, WA'),
('Digital Marketing Pro', 'Marketing', '11-50', 'Chicago, IL'),
('Finance Tech Ltd', 'FinTech', '201-500', 'Boston, MA'),
('HealthTech Solutions', 'Healthcare', '51-200', 'San Diego, CA'),
('E-commerce Giants', 'E-commerce', '500+', 'Los Angeles, CA');
-- Insert sample employers
INSERT INTO employers (company_id, email, password_hash, full_name, designation) VALUES
(1, '[email protected]', '$2y$10$YourHashHere', 'John Smith', 'HR Manager'),
(2, '[email protected]', '$2y$10$YourHashHere', 'Jane Doe', 'Talent Acquisition'),
(3, '[email protected]', '$2y$10$YourHashHere', 'Mike Johnson', 'Technical Recruiter'),
(4, '[email protected]', '$2y$10$YourHashHere', 'Sarah Williams', 'HR Director'),
(5, '[email protected]', '$2y$10$YourHashHere', 'Tom Brown', 'Recruitment Lead');
4. BACKEND IMPLEMENTATION {#backend}
Configuration Files
config/database.php
<?php
// Database configuration
class Database {
private $host = 'localhost';
private $db_name = 'job_recommendation_system';
private $username = 'root';
private $password = '';
private $conn;
public function getConnection() {
$this->conn = null;
try {
$this->conn = new PDO(
"mysql:host=" . $this->host . ";dbname=" . $this->db_name,
$this->username,
$this->password
);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->conn->exec("set names utf8");
} catch(PDOException $e) {
echo "Connection error: " . $e->getMessage();
}
return $this->conn;
}
}
?>
config/config.php
<?php
// Application configuration
session_start();
// Error reporting
error_reporting(E_ALL);
ini_set('display_errors', 1);
// Timezone
date_default_timezone_set('UTC');
// Base URL
define('BASE_URL', 'http://localhost/job-recommendation-system');
// Upload paths
define('UPLOAD_PATH', $_SERVER['DOCUMENT_ROOT'] . '/job-recommendation-system/uploads/');
define('RESUME_PATH', UPLOAD_PATH . 'resumes/');
define('PROFILE_PATH', UPLOAD_PATH . 'profiles/');
// Create directories if not exist
if (!file_exists(RESUME_PATH)) {
mkdir(RESUME_PATH, 0777, true);
}
if (!file_exists(PROFILE_PATH)) {
mkdir(PROFILE_PATH, 0777, true);
}
// JWT Secret (for API authentication)
define('JWT_SECRET', 'your-secret-key-change-in-production');
// Include database
require_once 'database.php';
?>
Core Classes
classes/User.php
<?php
class User {
private $conn;
private $table = 'users';
public $id;
public $email;
public $full_name;
public $location;
public $experience_years;
public $skills = [];
public $education = [];
public $experience = [];
public function __construct($db) {
$this->conn = $db;
}
// Register new user
public function register() {
$query = "INSERT INTO " . $this->table . "
SET
email = :email,
password_hash = :password_hash,
full_name = :full_name,
phone = :phone,
location = :location,
date_of_birth = :date_of_birth,
profile_headline = :profile_headline,
profile_summary = :profile_summary,
experience_years = :experience_years,
current_salary = :current_salary,
expected_salary = :expected_salary,
notice_period_days = :notice_period_days,
willing_to_relocate = :willing_to_relocate,
willing_to_remote = :willing_to_remote,
preferred_job_type = :preferred_job_type";
$stmt = $this->conn->prepare($query);
// Sanitize input
$this->email = htmlspecialchars(strip_tags($this->email));
$this->full_name = htmlspecialchars(strip_tags($this->full_name));
$this->location = htmlspecialchars(strip_tags($this->location));
// Hash password
$password_hash = password_hash($this->password, PASSWORD_DEFAULT);
// Bind parameters
$stmt->bindParam(':email', $this->email);
$stmt->bindParam(':password_hash', $password_hash);
$stmt->bindParam(':full_name', $this->full_name);
$stmt->bindParam(':phone', $this->phone);
$stmt->bindParam(':location', $this->location);
$stmt->bindParam(':date_of_birth', $this->date_of_birth);
$stmt->bindParam(':profile_headline', $this->profile_headline);
$stmt->bindParam(':profile_summary', $this->profile_summary);
$stmt->bindParam(':experience_years', $this->experience_years);
$stmt->bindParam(':current_salary', $this->current_salary);
$stmt->bindParam(':expected_salary', $this->expected_salary);
$stmt->bindParam(':notice_period_days', $this->notice_period_days);
$stmt->bindParam(':willing_to_relocate', $this->willing_to_relocate);
$stmt->bindParam(':willing_to_remote', $this->willing_to_remote);
$stmt->bindParam(':preferred_job_type', $this->preferred_job_type);
if ($stmt->execute()) {
$this->id = $this->conn->lastInsertId();
return true;
}
return false;
}
// Login user
public function login($email, $password) {
$query = "SELECT * FROM " . $this->table . " WHERE email = :email LIMIT 1";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':email', $email);
$stmt->execute();
if ($stmt->rowCount() > 0) {
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (password_verify($password, $row['password_hash'])) {
$this->id = $row['id'];
$this->email = $row['email'];
$this->full_name = $row['full_name'];
$this->location = $row['location'];
$this->experience_years = $row['experience_years'];
// Update last login
$updateQuery = "UPDATE " . $this->table . " SET last_login = NOW() WHERE id = :id";
$updateStmt = $this->conn->prepare($updateQuery);
$updateStmt->bindParam(':id', $this->id);
$updateStmt->execute();
return true;
}
}
return false;
}
// Get user profile
public function getProfile() {
$query = "SELECT * FROM " . $this->table . " WHERE id = :id LIMIT 1";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':id', $this->id);
$stmt->execute();
if ($stmt->rowCount() > 0) {
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// Set properties
$this->email = $row['email'];
$this->full_name = $row['full_name'];
$this->phone = $row['phone'];
$this->location = $row['location'];
$this->profile_headline = $row['profile_headline'];
$this->profile_summary = $row['profile_summary'];
$this->experience_years = $row['experience_years'];
$this->current_salary = $row['current_salary'];
$this->expected_salary = $row['expected_salary'];
// Get skills
$this->getUserSkills();
// Get education
$this->getUserEducation();
// Get experience
$this->getUserExperience();
return true;
}
return false;
}
// Get user skills
private function getUserSkills() {
$query = "SELECT s.skill_name, us.proficiency_level, us.years_of_experience
FROM user_skills us
JOIN skills s ON us.skill_id = s.id
WHERE us.user_id = :user_id";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':user_id', $this->id);
$stmt->execute();
$this->skills = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Get user education
private function getUserEducation() {
$query = "SELECT * FROM user_education WHERE user_id = :user_id ORDER BY graduation_year DESC";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':user_id', $this->id);
$stmt->execute();
$this->education = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Get user experience
private function getUserExperience() {
$query = "SELECT * FROM user_experience WHERE user_id = :user_id ORDER BY
CASE WHEN is_current = 1 THEN 0 ELSE 1 END, start_date DESC";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':user_id', $this->id);
$stmt->execute();
$this->experience = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Update user profile
public function updateProfile() {
$query = "UPDATE " . $this->table . "
SET
full_name = :full_name,
phone = :phone,
location = :location,
profile_headline = :profile_headline,
profile_summary = :profile_summary,
experience_years = :experience_years,
current_salary = :current_salary,
expected_salary = :expected_salary,
notice_period_days = :notice_period_days,
willing_to_relocate = :willing_to_relocate,
willing_to_remote = :willing_to_remote,
preferred_job_type = :preferred_job_type
WHERE id = :id";
$stmt = $this->conn->prepare($query);
// Sanitize
$this->full_name = htmlspecialchars(strip_tags($this->full_name));
$this->location = htmlspecialchars(strip_tags($this->location));
// Bind
$stmt->bindParam(':full_name', $this->full_name);
$stmt->bindParam(':phone', $this->phone);
$stmt->bindParam(':location', $this->location);
$stmt->bindParam(':profile_headline', $this->profile_headline);
$stmt->bindParam(':profile_summary', $this->profile_summary);
$stmt->bindParam(':experience_years', $this->experience_years);
$stmt->bindParam(':current_salary', $this->current_salary);
$stmt->bindParam(':expected_salary', $this->expected_salary);
$stmt->bindParam(':notice_period_days', $this->notice_period_days);
$stmt->bindParam(':willing_to_relocate', $this->willing_to_relocate);
$stmt->bindParam(':willing_to_remote', $this->willing_to_remote);
$stmt->bindParam(':preferred_job_type', $this->preferred_job_type);
$stmt->bindParam(':id', $this->id);
return $stmt->execute();
}
// Add skill to user
public function addSkill($skill_id, $proficiency, $years) {
$query = "INSERT INTO user_skills (user_id, skill_id, proficiency_level, years_of_experience)
VALUES (:user_id, :skill_id, :proficiency, :years)
ON DUPLICATE KEY UPDATE
proficiency_level = VALUES(proficiency_level),
years_of_experience = VALUES(years_of_experience)";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':user_id', $this->id);
$stmt->bindParam(':skill_id', $skill_id);
$stmt->bindParam(':proficiency', $proficiency);
$stmt->bindParam(':years', $years);
return $stmt->execute();
}
// Remove skill from user
public function removeSkill($skill_id) {
$query = "DELETE FROM user_skills WHERE user_id = :user_id AND skill_id = :skill_id";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':user_id', $this->id);
$stmt->bindParam(':skill_id', $skill_id);
return $stmt->execute();
}
// Save job for later
public function saveJob($job_id) {
$query = "INSERT INTO saved_jobs (user_id, job_id) VALUES (:user_id, :job_id)";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':user_id', $this->id);
$stmt->bindParam(':job_id', $job_id);
return $stmt->execute();
}
// Remove saved job
public function unsaveJob($job_id) {
$query = "DELETE FROM saved_jobs WHERE user_id = :user_id AND job_id = :job_id";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':user_id', $this->id);
$stmt->bindParam(':job_id', $job_id);
return $stmt->execute();
}
// Get saved jobs
public function getSavedJobs() {
$query = "SELECT j.*, c.company_name, c.company_logo
FROM saved_jobs sj
JOIN jobs j ON sj.job_id = j.id
JOIN companies c ON j.company_id = c.id
WHERE sj.user_id = :user_id
ORDER BY sj.saved_at DESC";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':user_id', $this->id);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
?>
classes/Job.php
<?php
class Job {
private $conn;
private $table = 'jobs';
public $id;
public $company_id;
public $job_title;
public $job_description;
public $location;
public $industry;
public $min_experience;
public $max_experience;
public $min_salary;
public $max_salary;
public $skills_required = [];
public $job_type;
public function __construct($db) {
$this->conn = $db;
}
// Create new job
public function create() {
$query = "INSERT INTO " . $this->table . "
SET
company_id = :company_id,
employer_id = :employer_id,
job_title = :job_title,
job_description = :job_description,
job_type = :job_type,
work_mode = :work_mode,
location = :location,
industry = :industry,
functional_area = :functional_area,
role_category = :role_category,
min_experience = :min_experience,
max_experience = :max_experience,
min_salary = :min_salary,
max_salary = :max_salary,
skills_required = :skills_required,
education_required = :education_required,
certifications_required = :certifications_required,
benefits = :benefits,
vacancies = :vacancies,
application_deadline = :application_deadline,
status = :status";
$stmt = $this->conn->prepare($query);
// Sanitize
$this->job_title = htmlspecialchars(strip_tags($this->job_title));
$this->location = htmlspecialchars(strip_tags($this->location));
// Convert arrays to JSON
$skills_json = json_encode($this->skills_required);
$education_json = json_encode($this->education_required);
$certifications_json = json_encode($this->certifications_required);
$benefits_json = json_encode($this->benefits);
// Bind
$stmt->bindParam(':company_id', $this->company_id);
$stmt->bindParam(':employer_id', $this->employer_id);
$stmt->bindParam(':job_title', $this->job_title);
$stmt->bindParam(':job_description', $this->job_description);
$stmt->bindParam(':job_type', $this->job_type);
$stmt->bindParam(':work_mode', $this->work_mode);
$stmt->bindParam(':location', $this->location);
$stmt->bindParam(':industry', $this->industry);
$stmt->bindParam(':functional_area', $this->functional_area);
$stmt->bindParam(':role_category', $this->role_category);
$stmt->bindParam(':min_experience', $this->min_experience);
$stmt->bindParam(':max_experience', $this->max_experience);
$stmt->bindParam(':min_salary', $this->min_salary);
$stmt->bindParam(':max_salary', $this->max_salary);
$stmt->bindParam(':skills_required', $skills_json);
$stmt->bindParam(':education_required', $education_json);
$stmt->bindParam(':certifications_required', $certifications_json);
$stmt->bindParam(':benefits', $benefits_json);
$stmt->bindParam(':vacancies', $this->vacancies);
$stmt->bindParam(':application_deadline', $this->application_deadline);
$stmt->bindParam(':status', $this->status);
if ($stmt->execute()) {
$this->id = $this->conn->lastInsertId();
return true;
}
return false;
}
// Get job details
public function getJob() {
$query = "SELECT j.*, c.company_name, c.company_logo, c.company_description,
e.full_name as employer_name, e.designation
FROM " . $this->table . " j
JOIN companies c ON j.company_id = c.id
JOIN employers e ON j.employer_id = e.id
WHERE j.id = :id AND j.status = 'published'";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':id', $this->id);
$stmt->execute();
if ($stmt->rowCount() > 0) {
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$this->company_id = $row['company_id'];
$this->job_title = $row['job_title'];
$this->job_description = $row['job_description'];
$this->job_type = $row['job_type'];
$this->location = $row['location'];
$this->industry = $row['industry'];
$this->min_experience = $row['min_experience'];
$this->max_experience = $row['max_experience'];
$this->min_salary = $row['min_salary'];
$this->max_salary = $row['max_salary'];
$this->skills_required = json_decode($row['skills_required'], true);
// Increment view count
$this->incrementViews();
return $row;
}
return false;
}
// Increment view count
private function incrementViews() {
$query = "UPDATE " . $this->table . " SET views_count = views_count + 1 WHERE id = :id";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':id', $this->id);
$stmt->execute();
}
// Search jobs
public function search($filters = [], $limit = 20, $offset = 0) {
$query = "SELECT j.*, c.company_name, c.company_logo,
(SELECT COUNT(*) FROM job_applications WHERE job_id = j.id) as application_count
FROM " . $this->table . " j
JOIN companies c ON j.company_id = c.id
WHERE j.status = 'published'";
$params = [];
// Apply filters
if (!empty($filters['keyword'])) {
$query .= " AND MATCH(j.job_title, j.job_description) AGAINST(:keyword)";
$params[':keyword'] = $filters['keyword'];
}
if (!empty($filters['location'])) {
$query .= " AND j.location LIKE :location";
$params[':location'] = '%' . $filters['location'] . '%';
}
if (!empty($filters['job_type'])) {
$query .= " AND j.job_type = :job_type";
$params[':job_type'] = $filters['job_type'];
}
if (!empty($filters['industry'])) {
$query .= " AND j.industry = :industry";
$params[':industry'] = $filters['industry'];
}
if (!empty($filters['min_experience'])) {
$query .= " AND j.max_experience >= :min_experience";
$params[':min_experience'] = $filters['min_experience'];
}
if (!empty($filters['max_experience'])) {
$query .= " AND j.min_experience <= :max_experience";
$params[':max_experience'] = $filters['max_experience'];
}
if (!empty($filters['min_salary'])) {
$query .= " AND j.max_salary >= :min_salary";
$params[':min_salary'] = $filters['min_salary'];
}
if (!empty($filters['max_salary'])) {
$query .= " AND j.min_salary <= :max_salary";
$params[':max_salary'] = $filters['max_salary'];
}
$query .= " ORDER BY j.is_featured DESC, j.created_at DESC LIMIT :limit OFFSET :offset";
$stmt = $this->conn->prepare($query);
// Bind parameters
foreach ($params as $key => &$value) {
$stmt->bindParam($key, $value);
}
$stmt->bindParam(':limit', $limit, PDO::PARAM_INT);
$stmt->bindParam(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Get similar jobs (for content-based filtering)
public function getSimilarJobs($job_id, $limit = 10) {
// First get the job details
$this->id = $job_id;
$job = $this->getJob();
if (!$job) {
return [];
}
// Extract skills from the job
$skills = json_decode($job['skills_required'], true);
$skillNames = array_column($skills, 'skill_name');
// Find jobs with similar skills
$query = "SELECT j.*, c.company_name, c.company_logo,
MATCH(j.job_title, j.job_description) AGAINST(:job_title) as relevance
FROM " . $this->table . " j
JOIN companies c ON j.company_id = c.id
WHERE j.id != :job_id
AND j.status = 'published'
AND (MATCH(j.job_title, j.job_description) AGAINST(:keywords)
OR j.industry = :industry)
ORDER BY relevance DESC, j.created_at DESC
LIMIT :limit";
$keywords = implode(' ', $skillNames);
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':job_id', $job_id);
$stmt->bindParam(':job_title', $job['job_title']);
$stmt->bindParam(':keywords', $keywords);
$stmt->bindParam(':industry', $job['industry']);
$stmt->bindParam(':limit', $limit, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Apply for job
public function apply($user_id, $application_data) {
$query = "INSERT INTO job_applications
(job_id, user_id, cover_letter, resume_url, expected_salary, notice_period_days)
VALUES (:job_id, :user_id, :cover_letter, :resume_url, :expected_salary, :notice_period_days)";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':job_id', $this->id);
$stmt->bindParam(':user_id', $user_id);
$stmt->bindParam(':cover_letter', $application_data['cover_letter']);
$stmt->bindParam(':resume_url', $application_data['resume_url']);
$stmt->bindParam(':expected_salary', $application_data['expected_salary']);
$stmt->bindParam(':notice_period_days', $application_data['notice_period_days']);
if ($stmt->execute()) {
// Increment application count
$updateQuery = "UPDATE " . $this->table . "
SET applications_count = applications_count + 1
WHERE id = :job_id";
$updateStmt = $this->conn->prepare($updateQuery);
$updateStmt->bindParam(':job_id', $this->id);
$updateStmt->execute();
return true;
}
return false;
}
}
?>
classes/RecommendationEngine.php
<?php
class RecommendationEngine {
private $conn;
private $user;
private $job;
public function __construct($db) {
$this->conn = $db;
$this->user = new User($db);
$this->job = new Job($db);
}
// Main recommendation function - content-based filtering
public function getRecommendations($user_id, $limit = 20, $refresh = false) {
// Check if we have cached recommendations
if (!$refresh) {
$cached = $this->getCachedRecommendations($user_id, $limit);
if (!empty($cached)) {
return $cached;
}
}
// Get user profile
$this->user->id = $user_id;
if (!$this->user->getProfile()) {
return [];
}
// Get all active jobs
$jobs = $this->getAllActiveJobs();
// Calculate scores for each job
$recommendations = [];
foreach ($jobs as $job) {
$score = $this->calculateJobScore($job);
if ($score['total'] > 0) {
$recommendations[] = [
'job' => $job,
'scores' => $score,
'match_percentage' => round($score['total'] * 100, 2)
];
}
}
// Sort by score (highest first)
usort($recommendations, function($a, $b) {
return $b['match_percentage'] <=> $a['match_percentage'];
});
// Take top results
$recommendations = array_slice($recommendations, 0, $limit);
// Cache the recommendations
$this->cacheRecommendations($user_id, $recommendations);
return $recommendations;
}
// Calculate job score based on multiple factors
private function calculateJobScore($job) {
$weights = [
'skills' => 0.40, // 40% weight - most important
'experience' => 0.20, // 20% weight
'location' => 0.15, // 15% weight
'salary' => 0.10, // 10% weight
'industry' => 0.08, // 8% weight
'education' => 0.07 // 7% weight
];
$scores = [
'skills' => $this->calculateSkillMatch($job),
'experience' => $this->calculateExperienceMatch($job),
'location' => $this->calculateLocationMatch($job),
'salary' => $this->calculateSalaryMatch($job),
'industry' => $this->calculateIndustryMatch($job),
'education' => $this->calculateEducationMatch($job)
];
// Calculate weighted total
$total = 0;
foreach ($weights as $factor => $weight) {
$total += $scores[$factor] * $weight;
}
return [
'total' => $total,
'breakdown' => $scores
];
}
// Skill match calculation (Jaccard similarity)
private function calculateSkillMatch($job) {
if (empty($this->user->skills) || empty($job['skills_required'])) {
return 0;
}
$userSkills = array_column($this->user->skills, 'skill_name');
$jobSkills = array_column(json_decode($job['skills_required'], true), 'skill_name');
// Convert to lowercase for case-insensitive comparison
$userSkills = array_map('strtolower', $userSkills);
$jobSkills = array_map('strtolower', $jobSkills);
// Calculate intersection and union
$intersection = array_intersect($userSkills, $jobSkills);
$union = array_unique(array_merge($userSkills, $jobSkills));
if (empty($union)) {
return 0;
}
// Jaccard similarity coefficient
$jaccard = count($intersection) / count($union);
// Bonus for proficiency levels
$proficiencyBonus = 0;
if (!empty($intersection)) {
// Get user's proficiency for matched skills
$matchedSkillDetails = array_filter($this->user->skills, function($skill) use ($intersection) {
return in_array(strtolower($skill['skill_name']), $intersection);
});
foreach ($matchedSkillDetails as $skill) {
$proficiencyBonus += $this->getProficiencyWeight($skill['proficiency_level']);
}
$proficiencyBonus = min($proficiencyBonus / count($intersection), 0.2); // Max 20% bonus
}
return min($jaccard + $proficiencyBonus, 1.0);
}
// Experience match calculation
private function calculateExperienceMatch($job) {
$userExp = floatval($this->user->experience_years);
$minReq = intval($job['min_experience']);
$maxReq = intval($job['max_experience']);
if ($userExp == 0) {
return $minReq == 0 ? 1.0 : 0.3; // Entry level tolerance
}
// Perfect match within range
if ($userExp >= $minReq && $userExp <= $maxReq) {
return 1.0;
}
// Below minimum requirement
if ($userExp < $minReq) {
$gap = $minReq - $userExp;
if ($gap <= 1) {
return 0.8; // Close enough
} elseif ($gap <= 2) {
return 0.6;
} elseif ($gap <= 3) {
return 0.4;
}
return 0.2;
}
// Above maximum requirement
if ($userExp > $maxReq) {
$excess = $userExp - $maxReq;
if ($excess <= 2) {
return 0.9; // Slightly overqualified
} elseif ($excess <= 5) {
return 0.7;
} elseif ($excess <= 10) {
return 0.5;
}
return 0.3; // Too overqualified
}
return 0;
}
// Location match calculation
private function calculateLocationMatch($job) {
if (empty($this->user->location) || empty($job['location'])) {
return 0.5; // Neutral if no location info
}
$userLoc = strtolower(trim($this->user->location));
$jobLoc = strtolower(trim($job['location']));
// Exact match
if ($userLoc == $jobLoc) {
return 1.0;
}
// Check if user is willing to relocate
if ($this->user->willing_to_relocate) {
// Partial match (same city different state, or same state different city)
$userParts = explode(',', $userLoc);
$jobParts = explode(',', $jobLoc);
// Check state/country match
$userLast = trim(end($userParts));
$jobLast = trim(end($jobParts));
if ($userLast == $jobLast) {
return 0.8; // Same state/country
}
return 0.5; // Willing to relocate to different location
}
// Check for remote work
if ($job['work_mode'] == 'remote' && $this->user->willing_to_remote) {
return 0.9; // Remote job
}
// Not willing to relocate and not remote
return 0.1;
}
// Salary match calculation
private function calculateSalaryMatch($job) {
$expected = floatval($this->user->expected_salary);
$minJob = floatval($job['min_salary']);
$maxJob = floatval($job['max_salary']);
if ($expected == 0 || ($minJob == 0 && $maxJob == 0)) {
return 0.5; // No salary info available
}
// Expected salary within job range
if ($expected >= $minJob && $expected <= $maxJob) {
return 1.0;
}
// Expected salary below job range
if ($expected < $minJob) {
$ratio = $expected / $minJob;
if ($ratio >= 0.8) {
return 0.9;
} elseif ($ratio >= 0.6) {
return 0.7;
}
return 0.5;
}
// Expected salary above job range
if ($expected > $maxJob) {
$ratio = $maxJob / $expected;
if ($ratio >= 0.8) {
return 0.8;
} elseif ($ratio >= 0.6) {
return 0.6;
}
return 0.4;
}
return 0.5;
}
// Industry match calculation
private function calculateIndustryMatch($job) {
if (empty($job['industry'])) {
return 0.5;
}
// Check user's preferred industries
$preferred = json_decode($this->user->preferred_industries, true) ?? [];
if (empty($preferred)) {
return 0.5; // No preferences
}
$jobIndustry = strtolower(trim($job['industry']));
foreach ($preferred as $industry) {
if (strtolower(trim($industry)) == $jobIndustry) {
return 1.0;
}
}
// Check if industry is related (partial match)
foreach ($preferred as $industry) {
if (strpos($jobIndustry, strtolower($industry)) !== false ||
strpos(strtolower($industry), $jobIndustry) !== false) {
return 0.7;
}
}
return 0.3;
}
// Education match calculation
private function calculateEducationMatch($job) {
if (empty($this->user->education) || empty($job['education_required'])) {
return 0.5;
}
$userEducation = $this->user->education;
$required = json_decode($job['education_required'], true) ?? [];
if (empty($required)) {
return 0.5; // No education requirements
}
// Get highest degree of user
$userHighest = $this->getHighestEducation($userEducation);
$requiredHighest = $this->getHighestEducation($required);
return $this->compareEducationLevel($userHighest, $requiredHighest);
}
// Helper: Get proficiency weight
private function getProficiencyWeight($level) {
$weights = [
'beginner' => 0.05,
'intermediate' => 0.10,
'advanced' => 0.15,
'expert' => 0.20
];
return $weights[$level] ?? 0.05;
}
// Helper: Get highest education level
private function getHighestEducation($education) {
$levels = [
'high school' => 1,
'associate' => 2,
'bachelor' => 3,
'master' => 4,
'phd' => 5,
'doctorate' => 5
];
$maxLevel = 0;
foreach ($education as $edu) {
$degree = strtolower($edu['degree'] ?? $edu['education_level'] ?? '');
foreach ($levels as $key => $level) {
if (strpos($degree, $key) !== false) {
$maxLevel = max($maxLevel, $level);
}
}
}
return $maxLevel;
}
// Helper: Compare education levels
private function compareEducationLevel($userLevel, $requiredLevel) {
if ($userLevel >= $requiredLevel) {
return 1.0; // Meets or exceeds requirement
}
$gap = $requiredLevel - $userLevel;
if ($gap == 1) {
return 0.7; // One level below
} elseif ($gap == 2) {
return 0.4; // Two levels below
}
return 0.2; // Significantly underqualified
}
// Get all active jobs
private function getAllActiveJobs() {
$query = "SELECT j.*, c.company_name, c.company_logo, c.industry as company_industry
FROM jobs j
JOIN companies c ON j.company_id = c.id
WHERE j.status = 'published'
AND (j.application_deadline IS NULL OR j.application_deadline >= CURDATE())
ORDER BY j.created_at DESC";
$stmt = $this->conn->prepare($query);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Get cached recommendations
private function getCachedRecommendations($user_id, $limit) {
$query = "SELECT j.*, c.company_name, c.company_logo,
jr.match_score, jr.skill_match_score, jr.experience_match_score,
jr.location_match_score, jr.salary_match_score
FROM job_recommendations jr
JOIN jobs j ON jr.job_id = j.id
JOIN companies c ON j.company_id = c.id
WHERE jr.user_id = :user_id
AND jr.expires_at > NOW()
ORDER BY jr.match_score DESC
LIMIT :limit";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':user_id', $user_id);
$stmt->bindParam(':limit', $limit, PDO::PARAM_INT);
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!empty($results)) {
// Format to match recommendation structure
$recommendations = [];
foreach ($results as $result) {
$recommendations[] = [
'job' => $result,
'match_percentage' => $result['match_score'],
'scores' => [
'skills' => $result['skill_match_score'],
'experience' => $result['experience_match_score'],
'location' => $result['location_match_score'],
'salary' => $result['salary_match_score']
]
];
}
return $recommendations;
}
return [];
}
// Cache recommendations
private function cacheRecommendations($user_id, $recommendations) {
// Clear old cache
$clearQuery = "DELETE FROM job_recommendations WHERE user_id = :user_id";
$clearStmt = $this->conn->prepare($clearQuery);
$clearStmt->bindParam(':user_id', $user_id);
$clearStmt->execute();
// Insert new recommendations
$insertQuery = "INSERT INTO job_recommendations
(user_id, job_id, match_score, skill_match_score,
experience_match_score, location_match_score,
salary_match_score, industry_match_score,
education_match_score, expires_at)
VALUES
(:user_id, :job_id, :match_score, :skill_match,
:experience_match, :location_match,
:salary_match, :industry_match,
:education_match, DATE_ADD(NOW(), INTERVAL 7 DAY))";
$insertStmt = $this->conn->prepare($insertQuery);
foreach ($recommendations as $rec) {
$insertStmt->bindParam(':user_id', $user_id);
$insertStmt->bindParam(':job_id', $rec['job']['id']);
$insertStmt->bindParam(':match_score', $rec['match_percentage']);
$insertStmt->bindParam(':skill_match', $rec['scores']['breakdown']['skills']);
$insertStmt->bindParam(':experience_match', $rec['scores']['breakdown']['experience']);
$insertStmt->bindParam(':location_match', $rec['scores']['breakdown']['location']);
$insertStmt->bindParam(':salary_match', $rec['scores']['breakdown']['salary']);
$insertStmt->bindParam(':industry_match', $rec['scores']['breakdown']['industry']);
$insertStmt->bindParam(':education_match', $rec['scores']['breakdown']['education']);
$insertStmt->execute();
}
}
// Log user activity for analytics
public function logActivity($user_id, $activity_type, $activity_data = []) {
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$session_id = session_id();
$query = "INSERT INTO user_activity_log
(user_id, activity_type, activity_data, ip_address, user_agent, session_id)
VALUES
(:user_id, :activity_type, :activity_data, :ip_address, :user_agent, :session_id)";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':user_id', $user_id);
$stmt->bindParam(':activity_type', $activity_type);
$stmt->bindParam(':activity_data', json_encode($activity_data));
$stmt->bindParam(':ip_address', $ip);
$stmt->bindParam(':user_agent', $user_agent);
$stmt->bindParam(':session_id', $session_id);
return $stmt->execute();
}
}
?>
5. FRONTEND IMPLEMENTATION {#frontend}
HTML/CSS Structure
index.html (Landing Page)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JobRecommender - AI-Powered Job Matching</title>
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/style.css">
<!-- Animate.css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<style>
.gradient-bg {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.card-hover {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
}
.job-card {
background: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid #e5e7eb;
}
.skill-tag {
display: inline-block;
padding: 4px 12px;
background: #f3f4f6;
border-radius: 20px;
font-size: 12px;
margin: 2px;
color: #4b5563;
}
.match-badge {
background: #10b981;
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 14px;
font-weight: 600;
}
.loading-spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body class="bg-gray-50">
<!-- Navigation -->
<nav class="bg-white shadow-lg sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<a href="index.html" class="flex items-center space-x-2">
<i class="fas fa-briefcase text-2xl text-purple-600"></i>
<span class="font-bold text-xl text-gray-800">JobRecommender</span>
</a>
</div>
<div class="hidden md:flex items-center space-x-8">
<a href="index.html" class="text-gray-700 hover:text-purple-600 transition">Home</a>
<a href="jobs.php" class="text-gray-700 hover:text-purple-600 transition">Jobs</a>
<a href="companies.php" class="text-gray-700 hover:text-purple-600 transition">Companies</a>
<a href="about.php" class="text-gray-700 hover:text-purple-600 transition">About</a>
</div>
<div class="flex items-center space-x-4">
<a href="login.php" class="text-gray-700 hover:text-purple-600 transition">Login</a>
<a href="register.php" class="bg-purple-600 text-white px-4 py-2 rounded-lg hover:bg-purple-700 transition">Sign Up</a>
</div>
</div>
</div>
</nav>
<!-- Hero Section -->
<section class="gradient-bg text-white py-20">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid md:grid-cols-2 gap-12 items-center">
<div class="animate__animated animate__fadeInLeft">
<h1 class="text-5xl font-bold mb-6">Find Your Dream Job with AI-Powered Matching</h1>
<p class="text-xl mb-8 text-purple-100">Our smart algorithm matches your skills and experience with the perfect job opportunities.</p>
<div class="flex space-x-4">
<a href="register.php" class="bg-white text-purple-600 px-8 py-3 rounded-lg font-semibold hover:bg-gray-100 transition">
Get Started
</a>
<a href="#how-it-works" class="border-2 border-white text-white px-8 py-3 rounded-lg font-semibold hover:bg-white hover:text-purple-600 transition">
Learn More
</a>
</div>
<!-- Stats -->
<div class="flex space-x-8 mt-12">
<div>
<div class="text-3xl font-bold">10K+</div>
<div class="text-purple-200">Jobs Posted</div>
</div>
<div>
<div class="text-3xl font-bold">50K+</div>
<div class="text-purple-200">Job Seekers</div>
</div>
<div>
<div class="text-3xl font-bold">5K+</div>
<div class="text-purple-200">Companies</div>
</div>
</div>
</div>
<div class="animate__animated animate__fadeInRight">
<img src="assets/images/hero-illustration.svg" alt="Job Search" class="w-full">
</div>
</div>
</div>
</section>
<!-- Search Section -->
<section class="py-12 bg-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bg-white rounded-xl shadow-xl p-8 -mt-20 relative z-10">
<form id="searchForm" class="grid md:grid-cols-4 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Job Title or Keyword</label>
<input type="text" id="keyword" name="keyword" placeholder="e.g. PHP Developer"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Location</label>
<input type="text" id="location" name="location" placeholder="City or Remote"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Experience</label>
<select id="experience" name="experience"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
<option value="">Any Experience</option>
<option value="0">Entry Level (0-1 years)</option>
<option value="2">Junior (2-3 years)</option>
<option value="4">Mid Level (4-6 years)</option>
<option value="7">Senior (7+ years)</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2"> </label>
<button type="submit" class="w-full bg-purple-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-purple-700 transition">
<i class="fas fa-search mr-2"></i>Search Jobs
</button>
</div>
</form>
<!-- Popular Searches -->
<div class="mt-4 flex items-center space-x-2 text-sm">
<span class="text-gray-500">Popular:</span>
<a href="#" class="text-purple-600 hover:underline">PHP Developer</a>
<span class="text-gray-300">•</span>
<a href="#" class="text-purple-600 hover:underline">Remote</a>
<span class="text-gray-300">•</span>
<a href="#" class="text-purple-600 hover:underline">Data Scientist</a>
<span class="text-gray-300">•</span>
<a href="#" class="text-purple-600 hover:underline">Frontend Developer</a>
</div>
</div>
</div>
</section>
<!-- How It Works -->
<section id="how-it-works" class="py-16 bg-gray-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 class="text-3xl font-bold text-center mb-12">How It Works</h2>
<div class="grid md:grid-cols-3 gap-8">
<div class="text-center card-hover">
<div class="bg-purple-100 w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fas fa-user-edit text-3xl text-purple-600"></i>
</div>
<h3 class="text-xl font-semibold mb-2">1. Create Profile</h3>
<p class="text-gray-600">Add your skills, experience, and preferences to build your professional profile.</p>
</div>
<div class="text-center card-hover">
<div class="bg-purple-100 w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fas fa-robot text-3xl text-purple-600"></i>
</div>
<h3 class="text-xl font-semibold mb-2">2. AI Matching</h3>
<p class="text-gray-600">Our algorithm analyzes your profile and finds the best matching jobs.</p>
</div>
<div class="text-center card-hover">
<div class="bg-purple-100 w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fas fa-handshake text-3xl text-purple-600"></i>
</div>
<h3 class="text-xl font-semibold mb-2">3. Apply & Get Hired</h3>
<p class="text-gray-600">Apply to matched jobs and connect with employers looking for your skills.</p>
</div>
</div>
</div>
</section>
<!-- Featured Jobs -->
<section class="py-16 bg-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center mb-8">
<h2 class="text-3xl font-bold">Featured Jobs</h2>
<a href="jobs.php" class="text-purple-600 hover:underline">View All Jobs →</a>
</div>
<div id="featuredJobs" class="grid md:grid-cols-2 gap-6">
<!-- Jobs will be loaded here via JavaScript -->
<div class="text-center py-8">
<div class="loading-spinner mx-auto"></div>
<p class="mt-4 text-gray-600">Loading featured jobs...</p>
</div>
</div>
</div>
</section>
<!-- Recommendations Section (for logged in users) -->
<section id="recommendations" class="py-16 bg-gray-50" style="display: none;">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 class="text-3xl font-bold mb-8">Recommended For You</h2>
<p class="text-gray-600 mb-8">Based on your profile and preferences</p>
<div id="recommendationsList" class="space-y-4">
<!-- Recommendations will be loaded here -->
</div>
</div>
</section>
<!-- Testimonials -->
<section class="py-16 bg-purple-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 class="text-3xl font-bold text-center mb-12">Success Stories</h2>
<div class="grid md:grid-cols-3 gap-8">
<div class="bg-white p-6 rounded-xl shadow-lg">
<div class="flex items-center mb-4">
<img src="assets/images/testimonial1.jpg" alt="User" class="w-12 h-12 rounded-full mr-4">
<div>
<h4 class="font-semibold">John Doe</h4>
<p class="text-sm text-gray-600">PHP Developer</p>
</div>
</div>
<p class="text-gray-700">"Found my dream job within 2 weeks! The recommendations were spot-on with my skills."</p>
</div>
<div class="bg-white p-6 rounded-xl shadow-lg">
<div class="flex items-center mb-4">
<img src="assets/images/testimonial2.jpg" alt="User" class="w-12 h-12 rounded-full mr-4">
<div>
<h4 class="font-semibold">Jane Smith</h4>
<p class="text-sm text-gray-600">UX Designer</p>
</div>
</div>
<p class="text-gray-700">"The skill matching algorithm is incredible. Got multiple interview calls from top companies."</p>
</div>
<div class="bg-white p-6 rounded-xl shadow-lg">
<div class="flex items-center mb-4">
<img src="assets/images/testimonial3.jpg" alt="User" class="w-12 h-12 rounded-full mr-4">
<div>
<h4 class="font-semibold">Mike Johnson</h4>
<p class="text-sm text-gray-600">Data Scientist</p>
</div>
</div>
<p class="text-gray-700">"Best job platform for tech professionals. The recommendations are highly relevant."</p>
</div>
</div>
</div>
</section>
<!-- CTA Section -->
<section class="gradient-bg text-white py-16">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h2 class="text-3xl font-bold mb-4">Ready to Find Your Dream Job?</h2>
<p class="text-xl mb-8 text-purple-100">Join thousands of job seekers who found their perfect match</p>
<a href="register.php" class="bg-white text-purple-600 px-8 py-3 rounded-lg font-semibold hover:bg-gray-100 transition inline-block">
Create Free Account
</a>
</div>
</section>
<!-- Footer -->
<footer class="bg-gray-900 text-white py-12">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid md:grid-cols-4 gap-8">
<div>
<h3 class="text-xl font-bold mb-4">JobRecommender</h3>
<p class="text-gray-400">AI-powered job matching platform connecting talented professionals with great companies.</p>
</div>
<div>
<h4 class="font-semibold mb-4">For Job Seekers</h4>
<ul class="space-y-2 text-gray-400">
<li><a href="#" class="hover:text-white transition">Browse Jobs</a></li>
<li><a href="#" class="hover:text-white transition">Create Profile</a></li>
<li><a href="#" class="hover:text-white transition">Job Alerts</a></li>
<li><a href="#" class="hover:text-white transition">Career Advice</a></li>
</ul>
</div>
<div>
<h4 class="font-semibold mb-4">For Employers</h4>
<ul class="space-y-2 text-gray-400">
<li><a href="#" class="hover:text-white transition">Post a Job</a></li>
<li><a href="#" class="hover:text-white transition">Browse Candidates</a></li>
<li><a href="#" class="hover:text-white transition">Pricing</a></li>
<li><a href="#" class="hover:text-white transition">Resources</a></li>
</ul>
</div>
<div>
<h4 class="font-semibold mb-4">Connect With Us</h4>
<div class="flex space-x-4">
<a href="#" class="text-gray-400 hover:text-white transition"><i class="fab fa-facebook-f"></i></a>
<a href="#" class="text-gray-400 hover:text-white transition"><i class="fab fa-twitter"></i></a>
<a href="#" class="text-gray-400 hover:text-white transition"><i class="fab fa-linkedin-in"></i></a>
<a href="#" class="text-gray-400 hover:text-white transition"><i class="fab fa-github"></i></a>
</div>
</div>
</div>
<div class="border-t border-gray-800 mt-8 pt-8 text-center text-gray-400">
<p>© 2024 JobRecommender. All rights reserved.</p>
</div>
</div>
</footer>
<!-- JavaScript -->
<script src="assets/js/main.js"></script>
<script>
// Check if user is logged in
function checkAuth() {
fetch('api/check_auth.php')
.then(response => response.json())
.then(data => {
if (data.logged_in) {
document.getElementById('recommendations').style.display = 'block';
loadRecommendations();
}
});
}
// Load featured jobs
function loadFeaturedJobs() {
fetch('api/featured_jobs.php')
.then(response => response.json())
.then(data => {
if (data.success) {
displayJobs(data.jobs, 'featuredJobs');
}
});
}
// Load recommendations
function loadRecommendations() {
fetch('api/recommendations.php')
.then(response => response.json())
.then(data => {
if (data.success) {
displayRecommendations(data.recommendations);
}
});
}
// Display jobs
function displayJobs(jobs, containerId) {
const container = document.getElementById(containerId);
container.innerHTML = '';
jobs.forEach(job => {
const jobElement = createJobCard(job);
container.appendChild(jobElement);
});
}
// Create job card HTML
function createJobCard(job) {
const div = document.createElement('div');
div.className = 'job-card card-hover';
div.innerHTML = `
<div class="flex items-start justify-between">
<div class="flex items-start space-x-4">
<img src="${job.company_logo || 'assets/images/company-placeholder.png'}"
alt="${job.company_name}" class="w-12 h-12 rounded-lg object-cover">
<div>
<h3 class="text-lg font-semibold">${job.job_title}</h3>
<p class="text-gray-600">${job.company_name}</p>
<div class="flex items-center space-x-4 mt-2 text-sm text-gray-500">
<span><i class="fas fa-map-marker-alt mr-1"></i>${job.location}</span>
<span><i class="fas fa-briefcase mr-1"></i>${job.job_type}</span>
<span><i class="fas fa-money-bill-alt mr-1"></i>$${job.min_salary} - $${job.max_salary}</span>
</div>
<div class="mt-3">
${job.skills_required ? JSON.parse(job.skills_required).slice(0, 5).map(skill =>
`<span class="skill-tag">${skill.skill_name}</span>`
).join('') : ''}
</div>
</div>
</div>
${job.match_percentage ?
`<div class="match-badge">${job.match_percentage}% Match</div>` :
`<a href="job-details.php?id=${job.id}" class="text-purple-600 hover:underline">View Details →</a>`
}
</div>
`;
return div;
}
// Display recommendations
function displayRecommendations(recommendations) {
const container = document.getElementById('recommendationsList');
container.innerHTML = '';
recommendations.forEach(rec => {
const jobElement = createJobCard({
...rec.job,
match_percentage: rec.match_percentage
});
container.appendChild(jobElement);
});
}
// Search form handler
document.getElementById('searchForm').addEventListener('submit', function(e) {
e.preventDefault();
const keyword = document.getElementById('keyword').value;
const location = document.getElementById('location').value;
const experience = document.getElementById('experience').value;
window.location.href = `jobs.php?keyword=${encodeURIComponent(keyword)}&location=${encodeURIComponent(location)}&experience=${experience}`;
});
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
loadFeaturedJobs();
checkAuth();
});
</script>
</body>
</html>
dashboard.php (User Dashboard)
```php
<?php
session_start();
require_once 'config/config.php';
require_once 'classes/User.php';
require_once 'classes/RecommendationEngine.php';
// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit();
}
$database = new Database();
$db = $database->getConnection();
$user = new User($db);
$user->id = $_SESSION['user_id'];
$user->getProfile();
$recommendationEngine = new RecommendationEngine($db);
$recommendations = $recommendationEngine->getRecommendations($_SESSION['user_id'], 10);
// Log activity
$recommendationEngine->logActivity($_SESSION['user_id'], 'view_dashboard');
?>
Dashboard - JobRecommender
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.sidebar {
min-height: calc(100vh - 64px);
}
.stat-card {
transition: transform 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
}
.progress-bar {
transition: width 0.5s ease;
}
</style>
<div class="flex items-center space-x-4"> <div class="relative"> <button class="flex items-center space-x-2 focus:outline-none" onclick="toggleNotifications()"> <i class="fas fa-bell text-gray-600 text-xl"></i> <span class="bg-red-500 text-white text-xs rounded-full px-2 py-1">3</span> </button> <!-- Notifications dropdown --> <div id="notifications" class="hidden absolute right-0 mt-2 w-80 bg-white rounded-lg shadow-xl z-50"> <div class="p-4 border-b"> <h3 class="font-semibold">Notifications</h3> </div> <div class="max-h-96 overflow-y-auto"> <div class="p-4 hover:bg-gray-50 border-b"> <p class="text-sm">New job match: Senior PHP Developer</p> <p class="text-xs text-gray-500 mt-1">5 minutes ago</p> </div> <div class="p-4 hover:bg-gray-50 border-b"> <p class="text-sm">Your application was viewed by Tech Corp</p> <p class="text-xs text-gray-500 mt-1">2 hours ago</p> </div> </div> </div> </div> <div class="relative"> <button class="flex items-center space-x-2 focus:outline-none" onclick="toggleUserMenu()"> <div class="w-8 h-8 bg-purple-600 rounded-full flex items-center justify-center text-white"> <?php echo substr($user->full_name, 0, 1); ?> </div> <span class="text-gray-700"><?php echo $user->full_name; ?></span> <i class="fas fa-chevron-down text-gray-500 text-sm"></i> </button> <!-- User menu dropdown --> <div id="userMenu" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-xl z-50"> <a href="profile.php" class="block px-4 py-2 hover:bg-gray-50">Profile</a> <a href="settings.php" class="block px-4 py-2 hover:bg-gray-50">Settings</a> <a href="saved-jobs.php" class="block px-4 py-2 hover:bg-gray-50">Saved Jobs</a> <hr class="my-2"> <a href="logout.php" class="block px-4 py-2 hover:bg-gray-50 text-red-600">Logout</a> </div> </div> </div> </div> </div> </nav> <div class="flex"> <!-- Sidebar --> <div class="sidebar w-64 bg-white shadow-lg"> <div class="p-6"> <div class="text-center"> <div class="w-24 h-24 bg-purple-600 rounded-full flex items-center justify
Job Recommendation System - Complete Implementation (Continued)
dashboard.php (Continued)
<?php
// Continue from previous part...
?>
<div class="w-24 h-24 bg-purple-600 rounded-full flex items-center justify-center text-white text-3xl mx-auto mb-4">
<?php echo substr($user->full_name, 0, 1); ?>
</div>
<h3 class="font-semibold text-lg"><?php echo $user->full_name; ?></h3>
<p class="text-gray-500 text-sm"><?php echo $user->profile_headline ?? 'Job Seeker'; ?></p>
</div>
<nav class="mt-8">
<a href="dashboard.php" class="flex items-center space-x-3 p-3 bg-purple-50 text-purple-600 rounded-lg mb-2">
<i class="fas fa-home"></i>
<span>Dashboard</span>
</a>
<a href="profile.php" class="flex items-center space-x-3 p-3 text-gray-700 hover:bg-gray-50 rounded-lg mb-2">
<i class="fas fa-user"></i>
<span>My Profile</span>
</a>
<a href="recommendations.php" class="flex items-center space-x-3 p-3 text-gray-700 hover:bg-gray-50 rounded-lg mb-2">
<i class="fas fa-robot"></i>
<span>Recommendations</span>
<span class="bg-purple-600 text-white text-xs px-2 py-1 rounded-full ml-auto"><?php echo count($recommendations); ?></span>
</a>
<a href="applications.php" class="flex items-center space-x-3 p-3 text-gray-700 hover:bg-gray-50 rounded-lg mb-2">
<i class="fas fa-file-alt"></i>
<span>Applications</span>
</a>
<a href="saved-jobs.php" class="flex items-center space-x-3 p-3 text-gray-700 hover:bg-gray-50 rounded-lg mb-2">
<i class="fas fa-bookmark"></i>
<span>Saved Jobs</span>
</a>
<a href="job-alerts.php" class="flex items-center space-x-3 p-3 text-gray-700 hover:bg-gray-50 rounded-lg mb-2">
<i class="fas fa-bell"></i>
<span>Job Alerts</span>
</a>
<hr class="my-4">
<a href="settings.php" class="flex items-center space-x-3 p-3 text-gray-700 hover:bg-gray-50 rounded-lg mb-2">
<i class="fas fa-cog"></i>
<span>Settings</span>
</a>
<a href="help.php" class="flex items-center space-x-3 p-3 text-gray-700 hover:bg-gray-50 rounded-lg mb-2">
<i class="fas fa-question-circle"></i>
<span>Help & Support</span>
</a>
</nav>
</div>
</div>
<!-- Main Content -->
<div class="flex-1 p-8">
<!-- Welcome Section -->
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-800">Welcome back, <?php echo explode(' ', $user->full_name)[0]; ?>!</h1>
<p class="text-gray-600 mt-2">Here's what's happening with your job search today.</p>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<div class="stat-card bg-white p-6 rounded-xl shadow-lg">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">Profile Strength</p>
<p class="text-3xl font-bold text-gray-800">85%</p>
</div>
<div class="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center">
<i class="fas fa-chart-line text-green-600 text-xl"></i>
</div>
</div>
<div class="mt-4">
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="bg-green-600 h-2 rounded-full progress-bar" style="width: 85%"></div>
</div>
<p class="text-xs text-gray-500 mt-2">Complete your profile to get better matches</p>
</div>
</div>
<div class="stat-card bg-white p-6 rounded-xl shadow-lg">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">Applications</p>
<p class="text-3xl font-bold text-gray-800">12</p>
</div>
<div class="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center">
<i class="fas fa-file-alt text-blue-600 text-xl"></i>
</div>
</div>
<p class="text-xs text-gray-500 mt-4">
<span class="text-green-600">3 new</span> responses
</p>
</div>
<div class="stat-card bg-white p-6 rounded-xl shadow-lg">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">Profile Views</p>
<p class="text-3xl font-bold text-gray-800">48</p>
</div>
<div class="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center">
<i class="fas fa-eye text-purple-600 text-xl"></i>
</div>
</div>
<p class="text-xs text-gray-500 mt-4">
<span class="text-green-600">+12</span> from last week
</p>
</div>
<div class="stat-card bg-white p-6 rounded-xl shadow-lg">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">Saved Jobs</p>
<p class="text-3xl font-bold text-gray-800">8</p>
</div>
<div class="w-12 h-12 bg-yellow-100 rounded-full flex items-center justify-center">
<i class="fas fa-bookmark text-yellow-600 text-xl"></i>
</div>
</div>
<p class="text-xs text-gray-500 mt-4">
<span class="text-red-600">2</span> expiring soon
</p>
</div>
</div>
<!-- Charts Section -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div class="bg-white p-6 rounded-xl shadow-lg">
<h3 class="font-semibold text-lg mb-4">Application Status</h3>
<canvas id="applicationChart"></canvas>
</div>
<div class="bg-white p-6 rounded-xl shadow-lg">
<h3 class="font-semibold text-lg mb-4">Profile Views Trend</h3>
<canvas id="viewsChart"></canvas>
</div>
</div>
<!-- Recommendations Section -->
<div class="bg-white rounded-xl shadow-lg p-6 mb-8">
<div class="flex justify-between items-center mb-6">
<h3 class="font-semibold text-lg">Recommended Jobs For You</h3>
<a href="recommendations.php" class="text-purple-600 hover:underline text-sm">View All →</a>
</div>
<div class="space-y-4">
<?php foreach ($recommendations as $rec): ?>
<div class="border rounded-lg p-4 hover:shadow-md transition">
<div class="flex items-start justify-between">
<div class="flex items-start space-x-4">
<img src="<?php echo $rec['job']['company_logo'] ?? 'assets/images/company-placeholder.png'; ?>"
alt="<?php echo $rec['job']['company_name']; ?>"
class="w-12 h-12 rounded-lg object-cover">
<div>
<h4 class="font-semibold"><?php echo $rec['job']['job_title']; ?></h4>
<p class="text-gray-600 text-sm"><?php echo $rec['job']['company_name']; ?></p>
<div class="flex items-center space-x-4 mt-2 text-xs text-gray-500">
<span><i class="fas fa-map-marker-alt mr-1"></i><?php echo $rec['job']['location']; ?></span>
<span><i class="fas fa-briefcase mr-1"></i><?php echo $rec['job']['job_type']; ?></span>
<span><i class="fas fa-money-bill-alt mr-1"></i>$<?php echo number_format($rec['job']['min_salary']); ?> - $<?php echo number_format($rec['job']['max_salary']); ?></span>
</div>
<div class="mt-2">
<span class="text-xs font-semibold text-purple-600">Match Score: <?php echo $rec['match_percentage']; ?>%</span>
<div class="w-32 bg-gray-200 rounded-full h-1.5 mt-1">
<div class="bg-purple-600 h-1.5 rounded-full" style="width: <?php echo $rec['match_percentage']; ?>%"></div>
</div>
</div>
</div>
</div>
<div class="flex space-x-2">
<button onclick="saveJob(<?php echo $rec['job']['id']; ?>)" class="text-gray-400 hover:text-yellow-500 transition">
<i class="far fa-bookmark"></i>
</button>
<a href="job-details.php?id=<?php echo $rec['job']['id']; ?>" class="bg-purple-600 text-white px-4 py-2 rounded-lg text-sm hover:bg-purple-700 transition">
View Job
</a>
</div>
</div>
</div>
<?php endforeach; ?>
<?php if (empty($recommendations)): ?>
<p class="text-center text-gray-500 py-8">No recommendations yet. Complete your profile to get personalized job matches.</p>
<?php endif; ?>
</div>
</div>
<!-- Recent Activity -->
<div class="bg-white rounded-xl shadow-lg p-6">
<h3 class="font-semibold text-lg mb-4">Recent Activity</h3>
<div class="space-y-4">
<div class="flex items-center space-x-4 text-sm">
<div class="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center">
<i class="fas fa-check text-green-600"></i>
</div>
<div class="flex-1">
<p class="text-gray-800">Your application for <span class="font-semibold">Senior PHP Developer</span> at Tech Corp was viewed</p>
<p class="text-gray-500 text-xs">2 hours ago</p>
</div>
</div>
<div class="flex items-center space-x-4 text-sm">
<div class="w-8 h-8 bg-purple-100 rounded-full flex items-center justify-center">
<i class="fas fa-robot text-purple-600"></i>
</div>
<div class="flex-1">
<p class="text-gray-800">New job match: <span class="font-semibold">Full Stack Developer</span> with 95% match</p>
<p class="text-gray-500 text-xs">Yesterday</p>
</div>
</div>
<div class="flex items-center space-x-4 text-sm">
<div class="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
<i class="fas fa-save text-blue-600"></i>
</div>
<div class="flex-1">
<p class="text-gray-800">You saved <span class="font-semibold">Data Scientist</span> position at Data Systems Inc.</p>
<p class="text-gray-500 text-xs">2 days ago</p>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Toggle notifications dropdown
function toggleNotifications() {
const notifications = document.getElementById('notifications');
notifications.classList.toggle('hidden');
}
// Toggle user menu
function toggleUserMenu() {
const userMenu = document.getElementById('userMenu');
userMenu.classList.toggle('hidden');
}
// Close dropdowns when clicking outside
document.addEventListener('click', function(event) {
const notifications = document.getElementById('notifications');
const userMenu = document.getElementById('userMenu');
const isNotificationButton = event.target.closest('.fa-bell') || event.target.closest('button[onclick="toggleNotifications()"]');
const isUserMenuButton = event.target.closest('.fa-chevron-down') || event.target.closest('button[onclick="toggleUserMenu()"]');
if (!isNotificationButton && !notifications.classList.contains('hidden')) {
notifications.classList.add('hidden');
}
if (!isUserMenuButton && !userMenu.classList.contains('hidden')) {
userMenu.classList.add('hidden');
}
});
// Save job function
function saveJob(jobId) {
fetch('api/save_job.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ job_id: jobId })
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Job saved successfully!');
}
});
}
// Initialize charts
document.addEventListener('DOMContentLoaded', function() {
// Application Status Chart
const appCtx = document.getElementById('applicationChart').getContext('2d');
new Chart(appCtx, {
type: 'doughnut',
data: {
labels: ['Under Review', 'Shortlisted', 'Rejected', 'Pending'],
datasets: [{
data: [5, 3, 2, 2],
backgroundColor: ['#f59e0b', '#10b981', '#ef4444', '#6b7280'],
borderWidth: 0
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
// Views Trend Chart
const viewsCtx = document.getElementById('viewsChart').getContext('2d');
new Chart(viewsCtx, {
type: 'line',
data: {
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
datasets: [{
label: 'Profile Views',
data: [5, 8, 12, 15, 10, 7, 9],
borderColor: '#8b5cf6',
backgroundColor: 'rgba(139, 92, 246, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: false
}
}
}
});
});
</script>
</body>
</html>
API Endpoints
api/get_recommendations.php
<?php
session_start();
require_once '../config/config.php';
require_once '../classes/RecommendationEngine.php';
header('Content-Type: application/json');
// Check authentication
if (!isset($_SESSION['user_id'])) {
echo json_encode(['success' => false, 'message' => 'Not authenticated']);
exit();
}
$database = new Database();
$db = $database->getConnection();
$recommendationEngine = new RecommendationEngine($db);
$recommendations = $recommendationEngine->getRecommendations($_SESSION['user_id'], $_GET['limit'] ?? 20);
echo json_encode([
'success' => true,
'recommendations' => $recommendations
]);
?>
api/search_jobs.php
<?php
require_once '../config/config.php';
require_once '../classes/Job.php';
header('Content-Type: application/json');
$database = new Database();
$db = $database->getConnection();
$job = new Job($db);
$filters = [
'keyword' => $_GET['keyword'] ?? '',
'location' => $_GET['location'] ?? '',
'job_type' => $_GET['job_type'] ?? '',
'industry' => $_GET['industry'] ?? '',
'min_experience' => $_GET['min_experience'] ?? '',
'max_experience' => $_GET['max_experience'] ?? '',
'min_salary' => $_GET['min_salary'] ?? '',
'max_salary' => $_GET['max_salary'] ?? ''
];
$page = $_GET['page'] ?? 1;
$limit = $_GET['limit'] ?? 20;
$offset = ($page - 1) * $limit;
$jobs = $job->search($filters, $limit, $offset);
echo json_encode([
'success' => true,
'jobs' => $jobs,
'page' => $page,
'total' => count($jobs)
]);
?>
api/apply_job.php
<?php
session_start();
require_once '../config/config.php';
require_once '../classes/Job.php';
require_once '../classes/RecommendationEngine.php';
header('Content-Type: application/json');
if (!isset($_SESSION['user_id'])) {
echo json_encode(['success' => false, 'message' => 'Not authenticated']);
exit();
}
$data = json_decode(file_get_contents('php://input'), true);
$database = new Database();
$db = $database->getConnection();
$job = new Job($db);
$job->id = $data['job_id'];
$application_data = [
'cover_letter' => $data['cover_letter'] ?? '',
'resume_url' => $data['resume_url'] ?? '',
'expected_salary' => $data['expected_salary'] ?? null,
'notice_period_days' => $data['notice_period_days'] ?? null
];
if ($job->apply($_SESSION['user_id'], $application_data)) {
// Log activity
$recommendationEngine = new RecommendationEngine($db);
$recommendationEngine->logActivity($_SESSION['user_id'], 'apply_job', ['job_id' => $job->id]);
echo json_encode(['success' => true, 'message' => 'Application submitted successfully']);
} else {
echo json_encode(['success' => false, 'message' => 'Failed to submit application']);
}
?>
api/save_job.php
<?php
session_start();
require_once '../config/config.php';
require_once '../classes/User.php';
header('Content-Type: application/json');
if (!isset($_SESSION['user_id'])) {
echo json_encode(['success' => false, 'message' => 'Not authenticated']);
exit();
}
$data = json_decode(file_get_contents('php://input'), true);
$database = new Database();
$db = $database->getConnection();
$user = new User($db);
$user->id = $_SESSION['user_id'];
if ($user->saveJob($data['job_id'])) {
echo json_encode(['success' => true, 'message' => 'Job saved successfully']);
} else {
echo json_encode(['success' => false, 'message' => 'Failed to save job']);
}
?>
api/unsave_job.php
<?php
session_start();
require_once '../config/config.php';
require_once '../classes/User.php';
header('Content-Type: application/json');
if (!isset($_SESSION['user_id'])) {
echo json_encode(['success' => false, 'message' => 'Not authenticated']);
exit();
}
$data = json_decode(file_get_contents('php://input'), true);
$database = new Database();
$db = $database->getConnection();
$user = new User($db);
$user->id = $_SESSION['user_id'];
if ($user->unsaveJob($data['job_id'])) {
echo json_encode(['success' => true, 'message' => 'Job removed from saved']);
} else {
echo json_encode(['success' => false, 'message' => 'Failed to remove job']);
}
?>
api/check_auth.php
<?php
session_start();
header('Content-Type: application/json');
echo json_encode([
'logged_in' => isset($_SESSION['user_id']),
'user_id' => $_SESSION['user_id'] ?? null,
'user_name' => $_SESSION['user_name'] ?? null
]);
?>
api/featured_jobs.php
<?php
require_once '../config/config.php';
require_once '../classes/Job.php';
header('Content-Type: application/json');
$database = new Database();
$db = $database->getConnection();
$job = new Job($db);
// Get featured jobs (most recent or highest rated)
$filters = [];
$jobs = $job->search($filters, 6, 0);
echo json_encode([
'success' => true,
'jobs' => $jobs
]);
?>
Profile Management Pages
profile.php
<?php
session_start();
require_once 'config/config.php';
require_once 'classes/User.php';
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit();
}
$database = new Database();
$db = $database->getConnection();
$user = new User($db);
$user->id = $_SESSION['user_id'];
$user->getProfile();
// Get all skills for dropdown
$skills_query = "SELECT * FROM skills ORDER BY skill_category, skill_name";
$skills_stmt = $db->prepare($skills_query);
$skills_stmt->execute();
$all_skills = $skills_stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Profile - JobRecommender</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Select2 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet" />
</head>
<body class="bg-gray-50">
<!-- Navigation (same as dashboard) -->
<nav class="bg-white shadow-lg">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<a href="dashboard.php" class="flex items-center space-x-2">
<i class="fas fa-briefcase text-2xl text-purple-600"></i>
<span class="font-bold text-xl text-gray-800">JobRecommender</span>
</a>
</div>
<div class="flex items-center space-x-4">
<a href="dashboard.php" class="text-gray-700 hover:text-purple-600">Dashboard</a>
<a href="logout.php" class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600">Logout</a>
</div>
</div>
</div>
</nav>
<div class="max-w-4xl mx-auto py-8 px-4">
<h1 class="text-3xl font-bold mb-8">My Profile</h1>
<!-- Profile Completion Bar -->
<div class="bg-white rounded-lg shadow-lg p-6 mb-8">
<h2 class="font-semibold text-lg mb-4">Profile Completion</h2>
<div class="w-full bg-gray-200 rounded-full h-4">
<div class="bg-green-600 h-4 rounded-full" style="width: 75%"></div>
</div>
<p class="text-sm text-gray-600 mt-2">Complete your profile to get better job matches</p>
</div>
<!-- Personal Information -->
<div class="bg-white rounded-lg shadow-lg p-6 mb-8">
<h2 class="font-semibold text-lg mb-4">Personal Information</h2>
<form id="profileForm" class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Full Name</label>
<input type="text" name="full_name" value="<?php echo htmlspecialchars($user->full_name); ?>"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Email</label>
<input type="email" name="email" value="<?php echo htmlspecialchars($user->email); ?>" readonly
class="w-full px-4 py-2 border border-gray-300 rounded-lg bg-gray-50">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Phone</label>
<input type="tel" name="phone" value="<?php echo htmlspecialchars($user->phone); ?>"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Location</label>
<input type="text" name="location" value="<?php echo htmlspecialchars($user->location); ?>"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Profile Headline</label>
<input type="text" name="profile_headline" value="<?php echo htmlspecialchars($user->profile_headline); ?>"
placeholder="e.g. Senior PHP Developer with 5 years experience"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Professional Summary</label>
<textarea name="profile_summary" rows="4"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"><?php echo htmlspecialchars($user->profile_summary); ?></textarea>
</div>
<div class="grid grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Experience (Years)</label>
<input type="number" name="experience_years" step="0.5" value="<?php echo $user->experience_years; ?>"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Current Salary ($)</label>
<input type="number" name="current_salary" value="<?php echo $user->current_salary; ?>"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Expected Salary ($)</label>
<input type="number" name="expected_salary" value="<?php echo $user->expected_salary; ?>"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500">
</div>
</div>
<div class="flex justify-end">
<button type="submit" class="bg-purple-600 text-white px-6 py-2 rounded-lg hover:bg-purple-700">
Save Changes
</button>
</div>
</form>
</div>
<!-- Skills -->
<div class="bg-white rounded-lg shadow-lg p-6 mb-8">
<h2 class="font-semibold text-lg mb-4">Skills</h2>
<div class="mb-4">
<select id="skillSelect" class="w-full">
<option value="">Select skills to add...</option>
<?php foreach ($all_skills as $skill): ?>
<option value="<?php echo $skill['id']; ?>" data-category="<?php echo $skill['skill_category']; ?>">
<?php echo htmlspecialchars($skill['skill_name']); ?> (<?php echo $skill['skill_category']; ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<div id="skillsList" class="space-y-2">
<?php foreach ($user->skills as $skill): ?>
<div class="flex items-center justify-between bg-gray-50 p-3 rounded-lg">
<div>
<span class="font-medium"><?php echo htmlspecialchars($skill['skill_name']); ?></span>
<span class="text-sm text-gray-500 ml-2">(<?php echo $skill['proficiency_level']; ?>)</span>
</div>
<div class="flex items-center space-x-2">
<select class="proficiency-select text-sm border rounded px-2 py-1" data-skill-id="<?php echo $skill['id']; ?>">
<option value="beginner" <?php echo $skill['proficiency_level'] == 'beginner' ? 'selected' : ''; ?>>Beginner</option>
<option value="intermediate" <?php echo $skill['proficiency_level'] == 'intermediate' ? 'selected' : ''; ?>>Intermediate</option>
<option value="advanced" <?php echo $skill['proficiency_level'] == 'advanced' ? 'selected' : ''; ?>>Advanced</option>
<option value="expert" <?php echo $skill['proficiency_level'] == 'expert' ? 'selected' : ''; ?>>Expert</option>
</select>
<button onclick="removeSkill(<?php echo $skill['id']; ?>)" class="text-red-500 hover:text-red-700">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- Education -->
<div class="bg-white rounded-lg shadow-lg p-6 mb-8">
<h2 class="font-semibold text-lg mb-4">Education</h2>
<div id="educationList" class="space-y-4">
<?php foreach ($user->education as $edu): ?>
<div class="border rounded-lg p-4">
<div class="grid grid-cols-2 gap-4">
<input type="text" value="<?php echo htmlspecialchars($edu['degree']); ?>" placeholder="Degree" class="px-3 py-2 border rounded">
<input type="text" value="<?php echo htmlspecialchars($edu['field_of_study']); ?>" placeholder="Field of Study" class="px-3 py-2 border rounded">
<input type="text" value="<?php echo htmlspecialchars($edu['institution']); ?>" placeholder="Institution" class="px-3 py-2 border rounded">
<input type="number" value="<?php echo $edu['graduation_year']; ?>" placeholder="Graduation Year" class="px-3 py-2 border rounded">
</div>
</div>
<?php endforeach; ?>
</div>
<button onclick="addEducation()" class="mt-4 text-purple-600 hover:text-purple-700">
<i class="fas fa-plus mr-1"></i> Add Education
</button>
</div>
<!-- Experience -->
<div class="bg-white rounded-lg shadow-lg p-6 mb-8">
<h2 class="font-semibold text-lg mb-4">Work Experience</h2>
<div id="experienceList" class="space-y-4">
<?php foreach ($user->experience as $exp): ?>
<div class="border rounded-lg p-4">
<div class="grid grid-cols-2 gap-4">
<input type="text" value="<?php echo htmlspecialchars($exp['job_title']); ?>" placeholder="Job Title" class="px-3 py-2 border rounded">
<input type="text" value="<?php echo htmlspecialchars($exp['company_name']); ?>" placeholder="Company" class="px-3 py-2 border rounded">
<input type="date" value="<?php echo $exp['start_date']; ?>" class="px-3 py-2 border rounded">
<input type="date" value="<?php echo $exp['end_date']; ?>" class="px-3 py-2 border rounded">
</div>
</div>
<?php endforeach; ?>
</div>
<button onclick="addExperience()" class="mt-4 text-purple-600 hover:text-purple-700">
<i class="fas fa-plus mr-1"></i> Add Experience
</button>
</div>
</div>
<!-- jQuery and Select2 -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"></script>
<script>
$(document).ready(function() {
$('#skillSelect').select2({
placeholder: 'Search and select skills...',
allowClear: true
});
$('#skillSelect').on('select2:select', function(e) {
const data = e.params.data;
addSkill(data.id, data.text);
$(this).val(null).trigger('change');
});
});
function addSkill(skillId, skillName) {
// Check if skill already exists
if ($(`#skillsList [data-skill-id="${skillId}"]`).length > 0) {
alert('Skill already added');
return;
}
const skillHtml = `
<div class="flex items-center justify-between bg-gray-50 p-3 rounded-lg">
<div>
<span class="font-medium">${skillName}</span>
<span class="text-sm text-gray-500 ml-2">(intermediate)</span>
</div>
<div class="flex items-center space-x-2">
<select class="proficiency-select text-sm border rounded px-2 py-1" data-skill-id="${skillId}">
<option value="beginner">Beginner</option>
<option value="intermediate" selected>Intermediate</option>
<option value="advanced">Advanced</option>
<option value="expert">Expert</option>
</select>
<button onclick="removeSkill(${skillId})" class="text-red-500 hover:text-red-700">
<i class="fas fa-times"></i>
</button>
</div>
</div>
`;
$('#skillsList').append(skillHtml);
// Save to server
saveSkill(skillId, 'intermediate', 0);
}
function removeSkill(skillId) {
if (confirm('Are you sure you want to remove this skill?')) {
$(`#skillsList [data-skill-id="${skillId}"]`).closest('.flex').remove();
// Remove from server
fetch('api/remove_skill.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ skill_id: skillId })
});
}
}
function saveSkill(skillId, proficiency, years) {
fetch('api/add_skill.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
skill_id: skillId,
proficiency: proficiency,
years: years
})
});
}
// Handle proficiency changes
$(document).on('change', '.proficiency-select', function() {
const skillId = $(this).data('skill-id');
const proficiency = $(this).val();
saveSkill(skillId, proficiency, 0);
});
function addEducation() {
const eduHtml = `
<div class="border rounded-lg p-4">
<div class="grid grid-cols-2 gap-4">
<input type="text" placeholder="Degree" class="px-3 py-2 border rounded">
<input type="text" placeholder="Field of Study" class="px-3 py-2 border rounded">
<input type="text" placeholder="Institution" class="px-3 py-2 border rounded">
<input type="number" placeholder="Graduation Year" class="px-3 py-2 border rounded">
</div>
<button onclick="this.closest('.border').remove()" class="mt-2 text-red-500 text-sm">Remove</button>
</div>
`;
$('#educationList').append(eduHtml);
}
function addExperience() {
const expHtml = `
<div class="border rounded-lg p-4">
<div class="grid grid-cols-2 gap-4">
<input type="text" placeholder="Job Title" class="px-3 py-2 border rounded">
<input type="text" placeholder="Company" class="px-3 py-2 border rounded">
<input type="date" placeholder="Start Date" class="px-3 py-2 border rounded">
<input type="date" placeholder="End Date" class="px-3 py-2 border rounded">
</div>
<button onclick="this.closest('.border').remove()" class="mt-2 text-red-500 text-sm">Remove</button>
</div>
`;
$('#experienceList').append(expHtml);
}
// Handle profile form submission
$('#profileForm').on('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
fetch('api/update_profile.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Profile updated successfully');
} else {
alert('Error updating profile');
}
});
});
</script>
</body>
</html>
Authentication Pages
login.php
<?php
session_start();
require_once 'config/config.php';
require_once 'classes/User.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$database = new Database();
$db = $database->getConnection();
$user = new User($db);
if ($user->login($_POST['email'], $_POST['password'])) {
$_SESSION['user_id'] = $user->id;
$_SESSION['user_name'] = $user->full_name;
header('Location: dashboard.php');
exit();
} else {
$error = "Invalid email or password";
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - JobRecommender</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body class="bg-gray-50">
<div class="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div class="max-w-md w-full space-y-8">
<div>
<div class="flex justify-center">
<i class="fas fa-briefcase text-5xl text-purple-600"></i>
</div>
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
Sign in to your account
</h2>
<p class="mt-2 text-center text-sm text-gray-600">
Or
<a href="register.php" class="font-medium text-purple-600 hover:text-purple-500">
create a new account
</a>
</p>
</div>
<?php if (isset($error)): ?>
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<span class="block sm:inline"><?php echo $error; ?></span>
</div>
<?php endif; ?>
<form class="mt-8 space-y-6" method="POST">
<div class="rounded-md shadow-sm -space-y-px">
<div>
<label for="email" class="sr-only">Email address</label>
<input id="email" name="email" type="email" required
class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-purple-500 focus:border-purple-500 focus:z-10 sm:text-sm"
placeholder="Email address">
</div>
<div>
<label for="password" class="sr-only">Password</label>
<input id="password" name="password" type="password" required
class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-purple-500 focus:border-purple-500 focus:z-10 sm:text-sm"
placeholder="Password">
</div>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<input id="remember_me" name="remember_me" type="checkbox"
class="h-4 w-4 text-purple-600 focus:ring-purple-500 border-gray-300 rounded">
<label for="remember_me" class="ml-2 block text-sm text-gray-900">
Remember me
</label>
</div>
<div class="text-sm">
<a href="forgot-password.php" class="font-medium text-purple-600 hover:text-purple-500">
Forgot your password?
</a>
</div>
</div>
<div>
<button type="submit"
class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500">
<span class="absolute left-0 inset-y-0 flex items-center pl-3">
<i class="fas fa-lock"></i>
</span>
Sign in
</button>
</div>
<!-- Social Login -->
<div class="mt-6">
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-gray-50 text-gray-500">Or continue with</span>
</div>
</div>
<div class="mt-6 grid grid-cols-3 gap-3">
<button type="button" class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
<i class="fab fa-google"></i>
</button>
<button type="button" class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
<i class="fab fa-facebook-f"></i>
</button>
<button type="button" class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
<i class="fab fa-linkedin-in"></i>
</button>
</div>
</div>
</form>
</div>
</div>
</body>
</html>
register.php
<?php
session_start();
require_once 'config/config.php';
require_once 'classes/User.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$database = new Database();
$db = $database->getConnection();
$user = new User($db);
// Set user properties
$user->email = $_POST['email'];
$user->password = $_POST['password'];
$user->full_name = $_POST['full_name'];
$user->phone = $_POST['phone'] ?? '';
$user->location = $_POST['location'] ?? '';
if ($user->register()) {
$_SESSION['user_id'] = $user->id;
$_SESSION['user_name'] = $user->full_name;
header('Location: profile-setup.php');
exit();
} else {
$error = "Registration failed. Email might already exist.";
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register - JobRecommender</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body class="bg-gray-50">
<div class="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div class="max-w-md w-full space-y-8">
<div>
<div class="flex justify-center">
<i class="fas fa-briefcase text-5xl text-purple-600"></i>
</div>
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
Create your account
</h2>
<p class="mt-2 text-center text-sm text-gray-600">
Already have an account?
<a href="login.php" class="font-medium text-purple-600 hover:text-purple-500">
Sign in
</a>
</p>
</div>
<?php if (isset($error)): ?>
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<span class="block sm:inline"><?php echo $error; ?></span>
</div>
<?php endif; ?>
<form class="mt-8 space-y-6" method="POST">
<div class="space-y-4">
<div>
<label for="full_name" class="block text-sm font-medium text-gray-700">Full Name</label>
<input id="full_name" name="full_name" type="text" required
class="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-purple-500 focus:border-purple-500 focus:z-10 sm:text-sm"
placeholder="John Doe">
</div>
<div>
<label for="email" class="block text-sm font-medium text-gray-700">Email address</label>
<input id="email" name="email" type="email" required
class="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-purple-500 focus:border-purple-500 focus:z-10 sm:text-sm"
placeholder="[email protected]">
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700">Password</label>
<input id="password" name="password" type="password" required
class="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-purple-500 focus:border-purple-500 focus:z-10 sm:text-sm"
placeholder="••••••••">
<p class="mt-1 text-xs text-gray-500">Must be at least 8 characters</p>
</div>
<div>
<label for="confirm_password" class="block text-sm font-medium text-gray-700">Confirm Password</label>
<input id="confirm_password" name="confirm_password" type="password" required
class="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-purple-500 focus:border-purple-500 focus:z-10 sm:text-sm"
placeholder="••••••••">
</div>
<div>
<label for="phone" class="block text-sm font-medium text-gray-700">Phone (Optional)</label>
<input id="phone" name="phone" type="tel"
class="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-purple-500 focus:border-purple-500 focus:z-10 sm:text-sm"
placeholder="+1 234 567 8900">
</div>
<div>
<label for="location" class="block text-sm font-medium text-gray-700">Location (Optional)</label>
<input id="location" name="location" type="text"
class="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-purple-500 focus:border-purple-500 focus:z-10 sm:text-sm"
placeholder="New York, NY">
</div>
</div>
<div class="flex items-center">
<input id="terms" name="terms" type="checkbox" required
class="h-4 w-4 text-purple-600 focus:ring-purple-500 border-gray-300 rounded">
<label for="terms" class="ml-2 block text-sm text-gray-900">
I agree to the
<a href="terms.php" class="font-medium text-purple-600 hover:text-purple-500">Terms of Service</a>
and
<a href="privacy.php" class="font-medium text-purple-600 hover:text-purple-500">Privacy Policy</a>
</label>
</div>
<div>
<button type="submit"
class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500">
<span class="absolute left-0 inset-y-0 flex items-center pl-3">
<i class="fas fa-user-plus"></i>
</span>
Create Account
</button>
</div>
</form>
</div>
</div>
</body>
</html>
Installation Guide
1. System Requirements
- PHP 7.4 or higher
- MySQL 5.7 or higher
- Apache/Nginx web server
- Composer (optional)
2. Installation Steps
# 1. Clone or download the project cd /var/www/html/ git clone https://github.com/yourusername/job-recommendation-system.git # 2. Create database mysql -u root -p CREATE DATABASE job_recommendation_system; USE job_recommendation_system; SOURCE database/schema.sql; # 3. Configure database connection # Edit config/database.php with your database credentials # 4. Set permissions chmod -R 755 uploads/ chmod -R 755 assets/ # 5. Configure virtual host (Apache) # Create /etc/apache2/sites-available/job-recommender.conf <VirtualHost *:80> ServerName job-recommender.local DocumentRoot /var/www/html/job-recommendation-system <Directory /var/www/html/job-recommendation-system> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory> </VirtualHost> # 6. Enable site and restart Apache a2ensite job-recommender.conf systemctl restart apache2 # 7. Add to hosts file echo "127.0.0.1 job-recommender.local" >> /etc/hosts
3. Configuration Files
.htaccess
RewriteEngine On
# Redirect to HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
# Remove index.php from URL
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
# Security headers
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "DENY"
Header set X-XSS-Protection "1; mode=block"
# Block access to sensitive files
<FilesMatch "\.(ini|log|sql|sh)$">
Order allow,deny
Deny from all
</FilesMatch>
php.ini recommendations
upload_max_filesize = 10M post_max_size = 10M max_execution_time = 300 memory_limit = 256M
Testing Guide
1. Unit Tests
tests/RecommendationEngineTest.php
<?php
require_once 'classes/RecommendationEngine.php';
class RecommendationEngineTest {
private $engine;
public function setUp() {
$db = new Database();
$this->engine = new RecommendationEngine($db->getConnection());
}
public function testSkillMatch() {
// Mock user skills
$userSkills = ['PHP', 'MySQL', 'JavaScript'];
$jobSkills = ['PHP', 'MySQL', 'Laravel'];
$result = $this->engine->calculateSkillMatch($userSkills, $jobSkills);
assert($result > 0.5, "Skill match should be > 0.5");
echo "✓ Skill match test passed\n";
}
public function testExperienceMatch() {
$userExp = 5;
$minReq = 3;
$maxReq = 7;
$result = $this->engine->calculateExperienceMatch($userExp, $minReq, $maxReq);
assert($result == 1.0, "Experience within range should return 1.0");
echo "✓ Experience match test passed\n";
}
}
$test = new RecommendationEngineTest();
$test->setUp();
$test->testSkillMatch();
$test->testExperienceMatch();
?>
2. API Tests using Postman
{
"info": {
"name": "Job Recommender API Tests",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Get Recommendations",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{base_url}}/api/get_recommendations.php?limit=10",
"host": ["{{base_url}}"],
"path": ["api", "get_recommendations.php"],
"query": [
{
"key": "limit",
"value": "10"
}
]
}
}
},
{
"name": "Search Jobs",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{base_url}}/api/search_jobs.php?keyword=php&location=new york",
"host": ["{{base_url}}"],
"path": ["api", "search_jobs.php"],
"query": [
{
"key": "keyword",
"value": "php"
},
{
"key": "location",
"value": "new york"
}
]
}
}
},
{
"name": "Apply for Job",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"job_id\": 1,\n \"cover_letter\": \"I am very interested in this position...\",\n \"expected_salary\": 80000,\n \"notice_period_days\": 30\n}"
},
"url": {
"raw": "{{base_url}}/api/apply_job.php",
"host": ["{{base_url}}"],
"path": ["api", "apply_job.php"]
}
}
}
]
}
Deployment Checklist
Pre-deployment
- [ ] Update database credentials for production
- [ ] Set appropriate file permissions
- [ ] Enable HTTPS/SSL
- [ ] Configure error logging
- [ ] Set up database backups
- [ ] Test all API endpoints
- [ ] Validate user authentication
- [ ] Check file upload security
- [ ] Test email notifications
- [ ] Verify search functionality
Security Checklist
- [ ] SQL injection prevention (using prepared statements)
- [ ] XSS protection (output escaping)
- [ ] CSRF tokens on forms
- [ ] Password hashing (bcrypt)
- [ ] Session security
- [ ] HTTPS enforcement
- [ ] File upload validation
- [ ] Rate limiting on API
- [ ] Input validation
- [ ] Secure headers configuration
Performance Checklist
- [ ] Enable opcode caching (OPcache)
- [ ] Configure MySQL query cache
- [ ] Implement Redis for session storage
- [ ] Optimize database indexes
- [ ] Enable gzip compression
- [ ] Minify CSS/JavaScript
- [ ] Implement CDN for static assets
- [ ] Configure browser caching
- [ ] Optimize images
- [ ] Enable lazy loading
Conclusion
This complete Job Recommendation System demonstrates:
- Content-Based Filtering Algorithm - Skill matching using Jaccard similarity
- Multi-factor Scoring - Experience, location, salary, industry matching
- User Profile Management - Skills, education, experience tracking
- Real-time Recommendations - Personalized job suggestions
- Application Tracking - Full application lifecycle management
- Analytics - User activity logging and insights
Key Features Implemented:
- ✅ User authentication and profile management
- ✅ Skill-based job matching algorithm
- ✅ Weighted scoring system (40% skills, 20% experience, etc.)
- ✅ Cached recommendations for performance
- ✅ Responsive dashboard with charts
- ✅ Job search with multiple filters
- ✅ Application submission and tracking
- ✅ Saved jobs functionality
- ✅ Activity logging for analytics
- ✅ RESTful API endpoints
Future Enhancements:
- Machine learning model integration
- Real-time notifications (WebSocket)
- Mobile app development
- Advanced analytics dashboard
- Resume parsing using NLP
- Video interview integration
- Social media integration
- AI-powered cover letter generator
This system provides a solid foundation for a job recommendation platform and can be extended based on specific requirements.