Introduction to the Project
The Task Management System is a comprehensive, full-stack web application designed to help individuals and teams organize, track, and manage their tasks and projects efficiently. This system provides a powerful platform for creating tasks, setting priorities, tracking progress, collaborating with team members, and meeting deadlines.
The application features role-based access control with multiple user types: Admin, Project Managers, Team Members, and Clients. The system includes kanban boards, Gantt charts, time tracking, notifications, and detailed reporting for project insights.
Key Features
Admin Features
- Dashboard Overview: System-wide statistics on users, projects, and tasks
- User Management: Create, edit, and manage user accounts and permissions
- Team Management: Create teams and assign members
- Project Oversight: View all projects across the organization
- System Settings: Configure global parameters and notification settings
- Audit Logs: Track all system activities
- Backup & Restore: Database backup management
Project Manager Features
- Project Dashboard: Overview of all managed projects
- Project Creation: Create new projects with timelines and budgets
- Task Assignment: Assign tasks to team members with deadlines
- Progress Tracking: Monitor project progress with Gantt charts
- Resource Management: Allocate resources and track workload
- Milestone Management: Set and track project milestones
- Budget Tracking: Monitor project budgets and expenses
- Report Generation: Generate project status reports
Team Member Features
- Personal Dashboard: View assigned tasks and deadlines
- Task Management: Update task status, add comments, attach files
- Time Tracking: Log time spent on tasks
- Calendar View: See tasks and deadlines in calendar format
- Notifications: Receive updates on task assignments and changes
- Collaboration: Comment on tasks, mention team members
- File Sharing: Upload and share files related to tasks
Client Features
- Project View: See project progress and milestones
- Task Tracking: Monitor tasks relevant to client
- File Access: View and download shared documents
- Feedback: Provide feedback on deliverables
- Communication: Message project team members
General Features
- Kanban Boards: Visual task management with drag-and-drop
- Gantt Charts: Timeline view of projects and dependencies
- Priority Levels: High, medium, low priority settings
- Task Categories: Categorize tasks by type
- Tags & Labels: Organize tasks with custom tags
- Search & Filters: Advanced search and filtering options
- File Attachments: Upload files to tasks
- Comments & Discussions: Threaded comments on tasks
- Mentions: @mention team members in comments
- Notifications: Email and in-app notifications
- Activity Logs: Track all changes and activities
- Export Options: Export data to PDF, Excel, CSV
Technology Stack
- Frontend: HTML5, CSS3, JavaScript (ES6+), Bootstrap 5
- Backend: PHP 8.0+ (Core PHP with MVC-like structure)
- Database: MySQL 5.7+
- Additional Libraries:
- PHPMailer for email notifications
- Bootstrap 5 for responsive UI
- Font Awesome for icons
- jQuery for AJAX operations
- Select2 for enhanced dropdowns
- DataTables for advanced tables
- Chart.js for analytics
- FullCalendar.js for calendar view
- Dragula for drag-and-drop kanban
- Moment.js for date handling
- TCPDF for PDF generation
- PhpSpreadsheet for Excel export
Project File Structure
task-management-system/ │ ├── assets/ │ ├── css/ │ │ ├── style.css │ │ ├── dashboard.css │ │ ├── kanban.css │ │ ├── calendar.css │ │ ├── responsive.css │ │ └── dark-mode.css │ ├── js/ │ │ ├── main.js │ │ ├── dashboard.js │ │ ├── kanban.js │ │ ├── calendar.js │ │ ├── gantt.js │ │ ├── task.js │ │ ├── validation.js │ │ └── charts.js │ ├── images/ │ │ ├── avatars/ │ │ └── icons/ │ └── plugins/ │ ├── datatables/ │ ├── fullcalendar/ │ ├── select2/ │ └── dragula/ │ ├── includes/ │ ├── config.php │ ├── Database.php │ ├── functions.php │ ├── auth.php │ ├── User.php │ ├── Project.php │ ├── Task.php │ ├── Team.php │ ├── Comment.php │ ├── Notification.php │ ├── ActivityLog.php │ ├── Report.php │ └── helpers/ │ ├── DateHelper.php │ ├── FileHelper.php │ └── ValidationHelper.php │ ├── admin/ │ ├── dashboard.php │ ├── manage_users.php │ ├── add_user.php │ ├── edit_user.php │ ├── manage_teams.php │ ├── create_team.php │ ├── edit_team.php │ ├── projects.php │ ├── settings.php │ ├── audit_logs.php │ └── backup.php │ ├── manager/ │ ├── dashboard.php │ ├── projects.php │ ├── create_project.php │ ├── edit_project.php │ ├── project_details.php │ ├── tasks.php │ ├── create_task.php │ ├── task_details.php │ ├── gantt.php │ ├── team.php │ ├── team_members.php │ ├── reports.php │ └── budget.php │ ├── member/ │ ├── dashboard.php │ ├── my_tasks.php │ ├── task_details.php │ ├── update_task.php │ ├── calendar.php │ ├── time_tracking.php │ ├── files.php │ ├── notifications.php │ └── profile.php │ ├── client/ │ ├── dashboard.php │ ├── projects.php │ ├── project_details.php │ ├── tasks.php │ ├── files.php │ ├── feedback.php │ └── profile.php │ ├── api/ │ ├── tasks.php │ ├── update_task_status.php │ ├── add_comment.php │ ├── upload_file.php │ ├── search_users.php │ ├── get_notifications.php │ └── time_log.php │ ├── uploads/ │ ├── tasks/ │ ├── projects/ │ └── avatars/ │ ├── vendor/ │ ├── index.php ├── login.php ├── register.php ├── forgot_password.php ├── reset_password.php ├── logout.php ├── .env ├── .gitignore ├── composer.json ├── cron/ │ ├── send_reminders.php │ └── update_task_status.php └── sql/ └── database.sql
Database Schema
File: sql/database.sql
-- Create Database
CREATE DATABASE IF NOT EXISTS `task_management_system`;
USE `task_management_system`;
-- Users Table
CREATE TABLE `users` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_code` VARCHAR(20) UNIQUE NOT NULL,
`email` VARCHAR(100) UNIQUE NOT NULL,
`password` VARCHAR(255) NOT NULL,
`first_name` VARCHAR(50) NOT NULL,
`last_name` VARCHAR(50) NOT NULL,
`phone` VARCHAR(20),
`profile_picture` VARCHAR(255) DEFAULT 'default.png',
`role` ENUM('admin', 'manager', 'member', 'client') NOT NULL DEFAULT 'member',
`department` VARCHAR(100),
`position` VARCHAR(100),
`skills` TEXT,
`bio` TEXT,
`timezone` VARCHAR(50) DEFAULT 'UTC',
`email_notifications` BOOLEAN DEFAULT TRUE,
`push_notifications` BOOLEAN DEFAULT TRUE,
`status` ENUM('active', 'inactive', 'suspended') DEFAULT 'active',
`email_verified` BOOLEAN DEFAULT FALSE,
`verification_token` VARCHAR(255),
`reset_token` VARCHAR(255),
`reset_expires` DATETIME,
`last_login` DATETIME,
`last_activity` DATETIME,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_email` (`email`),
INDEX `idx_role` (`role`),
INDEX `idx_status` (`status`),
FULLTEXT `idx_search` (`first_name`, `last_name`, `email`)
);
-- Teams Table
CREATE TABLE `teams` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`team_code` VARCHAR(20) UNIQUE NOT NULL,
`name` VARCHAR(100) NOT NULL,
`description` TEXT,
`department` VARCHAR(100),
`team_lead_id` INT(11),
`created_by` INT(11),
`status` ENUM('active', 'inactive') DEFAULT 'active',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`team_lead_id`) REFERENCES `users`(`id`),
FOREIGN KEY (`created_by`) REFERENCES `users`(`id`),
INDEX `idx_name` (`name`)
);
-- Team Members Junction
CREATE TABLE `team_members` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`team_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`role_in_team` VARCHAR(50),
`joined_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`team_id`) REFERENCES `teams`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_team_member` (`team_id`, `user_id`)
);
-- Projects Table
CREATE TABLE `projects` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`project_code` VARCHAR(20) UNIQUE NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` TEXT,
`objective` TEXT,
`scope` TEXT,
`manager_id` INT(11) NOT NULL,
`team_id` INT(11),
`client_id` INT(11),
`status` ENUM('planning', 'active', 'on_hold', 'completed', 'cancelled') DEFAULT 'planning',
`priority` ENUM('low', 'medium', 'high', 'urgent') DEFAULT 'medium',
`start_date` DATE,
`end_date` DATE,
`estimated_hours` DECIMAL(10,2),
`actual_hours` DECIMAL(10,2) DEFAULT 0,
`budget` DECIMAL(10,2),
`currency` VARCHAR(3) DEFAULT 'USD',
`progress` INT DEFAULT 0,
`color` VARCHAR(7) DEFAULT '#3498db',
`created_by` INT(11),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`manager_id`) REFERENCES `users`(`id`),
FOREIGN KEY (`team_id`) REFERENCES `teams`(`id`),
FOREIGN KEY (`client_id`) REFERENCES `users`(`id`),
FOREIGN KEY (`created_by`) REFERENCES `users`(`id`),
INDEX `idx_status` (`status`),
INDEX `idx_dates` (`start_date`, `end_date`),
FULLTEXT `idx_search` (`name`, `description`)
);
-- Project Members Junction
CREATE TABLE `project_members` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`project_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`role_in_project` VARCHAR(50),
`assigned_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_project_member` (`project_id`, `user_id`)
);
-- Categories/Tags Table
CREATE TABLE `categories` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
`color` VARCHAR(7) DEFAULT '#6c757d',
`icon` VARCHAR(50),
`created_by` INT(11),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_name` (`name`)
);
-- Tasks Table
CREATE TABLE `tasks` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`task_code` VARCHAR(20) UNIQUE NOT NULL,
`title` VARCHAR(255) NOT NULL,
`description` TEXT,
`project_id` INT(11),
`category_id` INT(11),
`parent_task_id` INT(11),
`created_by` INT(11) NOT NULL,
`assigned_to` INT(11),
`status` ENUM('todo', 'in_progress', 'review', 'done', 'blocked') DEFAULT 'todo',
`priority` ENUM('low', 'medium', 'high', 'urgent') DEFAULT 'medium',
`type` ENUM('task', 'bug', 'feature', 'improvement') DEFAULT 'task',
`estimated_hours` DECIMAL(10,2),
`actual_hours` DECIMAL(10,2) DEFAULT 0,
`start_date` DATE,
`due_date` DATE,
`completed_at` DATETIME,
`progress` INT DEFAULT 0,
`order_position` INT DEFAULT 0,
`tags` TEXT,
`attachments` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`category_id`) REFERENCES `categories`(`id`),
FOREIGN KEY (`parent_task_id`) REFERENCES `tasks`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`created_by`) REFERENCES `users`(`id`),
FOREIGN KEY (`assigned_to`) REFERENCES `users`(`id`),
INDEX `idx_status` (`status`),
INDEX `idx_assigned` (`assigned_to`),
INDEX `idx_dates` (`start_date`, `due_date`),
FULLTEXT `idx_search` (`title`, `description`, `tags`)
);
-- Task Dependencies
CREATE TABLE `task_dependencies` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`task_id` INT(11) NOT NULL,
`depends_on` INT(11) NOT NULL,
`dependency_type` ENUM('finish_to_start', 'start_to_start', 'finish_to_finish', 'start_to_finish') DEFAULT 'finish_to_start',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`task_id`) REFERENCES `tasks`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`depends_on`) REFERENCES `tasks`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_dependency` (`task_id`, `depends_on`)
);
-- Comments Table
CREATE TABLE `comments` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`task_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`parent_id` INT(11),
`comment` TEXT NOT NULL,
`mentions` TEXT,
`attachments` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`task_id`) REFERENCES `tasks`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`),
FOREIGN KEY (`parent_id`) REFERENCES `comments`(`id`) ON DELETE CASCADE,
INDEX `idx_task` (`task_id`)
);
-- Time Tracking Table
CREATE TABLE `time_logs` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`task_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`start_time` DATETIME NOT NULL,
`end_time` DATETIME,
`duration` INT, -- in minutes
`description` TEXT,
`billable` BOOLEAN DEFAULT TRUE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`task_id`) REFERENCES `tasks`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`),
INDEX `idx_user_date` (`user_id`, `start_time`)
);
-- Files/Attachments Table
CREATE TABLE `files` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`task_id` INT(11),
`project_id` INT(11),
`user_id` INT(11) NOT NULL,
`filename` VARCHAR(255) NOT NULL,
`original_name` VARCHAR(255) NOT NULL,
`file_path` VARCHAR(500) NOT NULL,
`file_size` INT,
`file_type` VARCHAR(100),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`task_id`) REFERENCES `tasks`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`),
INDEX `idx_task` (`task_id`),
INDEX `idx_project` (`project_id`)
);
-- Notifications Table
CREATE TABLE `notifications` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`type` ENUM('task_assigned', 'task_updated', 'comment_added', 'mention', 'deadline_reminder', 'status_change', 'project_update') NOT NULL,
`title` VARCHAR(255) NOT NULL,
`message` TEXT NOT NULL,
`data` JSON,
`link` VARCHAR(500),
`is_read` BOOLEAN DEFAULT FALSE,
`read_at` DATETIME,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
INDEX `idx_user_read` (`user_id`, `is_read`),
INDEX `idx_created` (`created_at`)
);
-- Activity Logs Table
CREATE TABLE `activity_logs` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11),
`action` VARCHAR(100) NOT NULL,
`entity_type` ENUM('project', 'task', 'user', 'team', 'comment') NOT NULL,
`entity_id` INT(11),
`old_values` JSON,
`new_values` JSON,
`description` TEXT,
`ip_address` VARCHAR(45),
`user_agent` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
INDEX `idx_entity` (`entity_type`, `entity_id`),
INDEX `idx_created` (`created_at`)
);
-- Milestones Table
CREATE TABLE `milestones` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`project_id` INT(11) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` TEXT,
`due_date` DATE NOT NULL,
`status` ENUM('pending', 'achieved', 'missed') DEFAULT 'pending',
`achieved_at` DATETIME,
`created_by` INT(11),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`created_by`) REFERENCES `users`(`id`),
INDEX `idx_project` (`project_id`)
);
-- Budget Items Table
CREATE TABLE `budget_items` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`project_id` INT(11) NOT NULL,
`description` VARCHAR(255) NOT NULL,
`category` VARCHAR(100),
`estimated_amount` DECIMAL(10,2) NOT NULL,
`actual_amount` DECIMAL(10,2) DEFAULT 0,
`currency` VARCHAR(3) DEFAULT 'USD',
`date` DATE,
`notes` TEXT,
`created_by` INT(11),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`created_by`) REFERENCES `users`(`id`)
);
-- User Settings Table
CREATE TABLE `user_settings` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`setting_key` VARCHAR(100) NOT NULL,
`setting_value` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_user_setting` (`user_id`, `setting_key`)
);
-- System Settings Table
CREATE TABLE `system_settings` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`setting_key` VARCHAR(100) UNIQUE NOT NULL,
`setting_value` TEXT,
`description` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
-- Insert Default Categories
INSERT INTO `categories` (`name`, `color`, `icon`) VALUES
('Development', '#3498db', 'fa-code'),
('Design', '#9b59b6', 'fa-paint-brush'),
('Testing', '#e74c3c', 'fa-bug'),
('Documentation', '#2ecc71', 'fa-file-alt'),
('Meeting', '#f39c12', 'fa-users'),
('Research', '#1abc9c', 'fa-search'),
('Planning', '#34495e', 'fa-tasks'),
('Support', '#e67e22', 'fa-headset');
-- Insert System Settings
INSERT INTO `system_settings` (`setting_key`, `setting_value`, `description`) VALUES
('company_name', 'Task Management System', 'Company/Organization Name'),
('company_email', '[email protected]', 'System Email'),
('company_phone', '+1-555-123-4567', 'Contact Phone'),
('date_format', 'Y-m-d', 'Date format'),
('time_format', 'H:i', 'Time format'),
('timezone', 'UTC', 'Default timezone'),
('items_per_page', '20', 'Items per page in tables'),
('enable_notifications', '1', 'Enable email notifications'),
('default_task_view', 'list', 'Default task view (list/kanban/calendar)'),
('allow_attachments', '1', 'Allow file attachments'),
('max_file_size', '10', 'Maximum file size in MB'),
('allowed_file_types', 'jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,txt', 'Allowed file types'),
('session_timeout', '3600', 'Session timeout in seconds');
-- Insert Default Admin
INSERT INTO `users` (`user_code`, `email`, `password`, `first_name`, `last_name`, `role`, `email_verified`)
VALUES ('ADMIN001', '[email protected]', '$2y$10$YourHashedPasswordHere', 'System', 'Administrator', 'admin', TRUE);
Core PHP Classes
Database Class
File: includes/Database.php
<?php
/**
* Database Class
* Handles all database connections and operations using PDO with singleton pattern
*/
class Database {
private static $instance = null;
private $connection;
private $statement;
private $host;
private $dbname;
private $username;
private $password;
/**
* Private constructor for singleton pattern
*/
private function __construct() {
$this->host = DB_HOST;
$this->dbname = DB_NAME;
$this->username = DB_USER;
$this->password = DB_PASS;
try {
$this->connection = new PDO(
"mysql:host={$this->host};dbname={$this->dbname};charset=utf8mb4",
$this->username,
$this->password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
} catch (PDOException $e) {
die("Database connection failed: " . $e->getMessage());
}
}
/**
* Get database instance (Singleton)
*/
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Prepare and execute query with parameters
*/
public function query($sql, $params = []) {
try {
$this->statement = $this->connection->prepare($sql);
$this->statement->execute($params);
return $this->statement;
} catch (PDOException $e) {
$this->logError($e->getMessage(), $sql, $params);
throw new Exception("Database query failed: " . $e->getMessage());
}
}
/**
* Get single row
*/
public function getRow($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetch();
}
/**
* Get multiple rows
*/
public function getRows($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetchAll();
}
/**
* Get single value
*/
public function getValue($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetchColumn();
}
/**
* Insert data and return last insert ID
*/
public function insert($table, $data) {
$columns = implode(', ', array_keys($data));
$placeholders = ':' . implode(', :', array_keys($data));
$sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";
$this->query($sql, $data);
return $this->connection->lastInsertId();
}
/**
* Update data
*/
public function update($table, $data, $where, $whereParams = []) {
$set = [];
foreach (array_keys($data) as $column) {
$set[] = "{$column} = :{$column}";
}
$sql = "UPDATE {$table} SET " . implode(', ', $set) . " WHERE {$where}";
$params = array_merge($data, $whereParams);
return $this->query($sql, $params)->rowCount();
}
/**
* Delete data
*/
public function delete($table, $where, $params = []) {
$sql = "DELETE FROM {$table} WHERE {$where}";
return $this->query($sql, $params)->rowCount();
}
/**
* Begin transaction
*/
public function beginTransaction() {
return $this->connection->beginTransaction();
}
/**
* Commit transaction
*/
public function commit() {
return $this->connection->commit();
}
/**
* Rollback transaction
*/
public function rollback() {
return $this->connection->rollBack();
}
/**
* Get last insert ID
*/
public function lastInsertId() {
return $this->connection->lastInsertId();
}
/**
* Log database errors
*/
private function logError($message, $sql, $params) {
$logFile = __DIR__ . '/../logs/database.log';
$logDir = dirname($logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
$timestamp = date('Y-m-d H:i:s');
$logMessage = "[{$timestamp}] Error: {$message}\n";
$logMessage .= "SQL: {$sql}\n";
$logMessage .= "Params: " . json_encode($params) . "\n";
$logMessage .= "------------------------\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
}
/**
* Prevent cloning of the instance
*/
private function __clone() {}
/**
* Prevent unserializing of the instance
*/
public function __wakeup() {}
}
?>
Configuration File
File: includes/config.php
<?php
/**
* Configuration File
* Loads environment variables and sets up constants
*/
// Start session if not started
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Load environment variables from .env file
function loadEnv($path) {
if (!file_exists($path)) {
return false;
}
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (strpos(trim($line), '#') === 0) {
continue;
}
list($name, $value) = explode('=', $line, 2);
$name = trim($name);
$value = trim($value);
if (!array_key_exists($name, $_ENV)) {
$_ENV[$name] = $value;
putenv(sprintf('%s=%s', $name, $value));
}
}
return true;
}
// Load environment variables
loadEnv(__DIR__ . '/../.env');
// Database Configuration
define('DB_HOST', getenv('DB_HOST') ?: 'localhost');
define('DB_NAME', getenv('DB_NAME') ?: 'task_management_system');
define('DB_USER', getenv('DB_USER') ?: 'root');
define('DB_PASS', getenv('DB_PASS') ?: '');
// Application Configuration
define('APP_NAME', getenv('APP_NAME') ?: 'Task Management System');
define('APP_URL', getenv('APP_URL') ?: 'http://localhost/task-management-system');
define('APP_VERSION', getenv('APP_VERSION') ?: '1.0.0');
define('DEBUG_MODE', getenv('DEBUG_MODE') === 'true');
// Security Configuration
define('SESSION_TIMEOUT', getenv('SESSION_TIMEOUT') ?: 3600); // 1 hour
define('BCRYPT_ROUNDS', 12);
define('CSRF_TOKEN_NAME', 'csrf_token');
// Upload Configuration
define('UPLOAD_DIR', __DIR__ . '/../uploads/');
define('MAX_FILE_SIZE', getenv('MAX_FILE_SIZE') ?: 10 * 1024 * 1024); // 10MB
define('ALLOWED_EXTENSIONS', explode(',', getenv('ALLOWED_FILE_TYPES') ?: 'jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,txt'));
// Pagination
define('ITEMS_PER_PAGE', getenv('ITEMS_PER_PAGE') ?: 20);
// Date/Time Configuration
date_default_timezone_set(getenv('TIMEZONE') ?: 'UTC');
define('DATE_FORMAT', 'Y-m-d');
define('TIME_FORMAT', 'H:i');
define('DATETIME_FORMAT', 'Y-m-d H:i:s');
// Company Settings
define('COMPANY_NAME', getenv('COMPANY_NAME') ?: 'Task Management System');
define('COMPANY_EMAIL', getenv('COMPANY_EMAIL') ?: '[email protected]');
define('COMPANY_PHONE', getenv('COMPANY_PHONE') ?: '+1-555-123-4567');
// Notification Settings
define('ENABLE_NOTIFICATIONS', getenv('ENABLE_NOTIFICATIONS') === 'true');
// Error Reporting
if (DEBUG_MODE) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
} else {
error_reporting(0);
ini_set('display_errors', 0);
}
// Include required files
require_once __DIR__ . '/Database.php';
require_once __DIR__ . '/functions.php';
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/User.php';
require_once __DIR__ . '/Project.php';
require_once __DIR__ . '/Task.php';
require_once __DIR__ . '/Team.php';
require_once __DIR__ . '/Comment.php';
require_once __DIR__ . '/Notification.php';
require_once __DIR__ . '/ActivityLog.php';
require_once __DIR__ . '/Report.php';
// Initialize database connection
$db = Database::getInstance();
// Load system settings
$settings = $db->getRows("SELECT setting_key, setting_value FROM system_settings");
foreach ($settings as $setting) {
define(strtoupper($setting['setting_key']), $setting['setting_value']);
}
// Set timezone for MySQL
$db->query("SET time_zone = ?", [date('P')]);
?>
Helper Functions
File: includes/functions.php
<?php
/**
* Helper Functions
* Common utility functions used throughout the application
*/
/**
* Sanitize input data
*/
function sanitize($input) {
if (is_array($input)) {
return array_map('sanitize', $input);
}
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
/**
* Generate CSRF token
*/
function generateCSRFToken() {
if (!isset($_SESSION[CSRF_TOKEN_NAME])) {
$_SESSION[CSRF_TOKEN_NAME] = bin2hex(random_bytes(32));
}
return $_SESSION[CSRF_TOKEN_NAME];
}
/**
* Verify CSRF token
*/
function verifyCSRFToken($token) {
if (!isset($_SESSION[CSRF_TOKEN_NAME]) || $token !== $_SESSION[CSRF_TOKEN_NAME]) {
return false;
}
return true;
}
/**
* Redirect to URL
*/
function redirect($url) {
header("Location: " . APP_URL . $url);
exit();
}
/**
* Format date
*/
function formatDate($date, $format = null) {
if ($format === null) {
$format = DATE_FORMAT;
}
if ($date instanceof DateTime) {
return $date->format($format);
}
if (empty($date) || $date == '0000-00-00') {
return 'N/A';
}
return date($format, strtotime($date));
}
/**
* Format datetime
*/
function formatDateTime($datetime, $format = null) {
if ($format === null) {
$format = DATETIME_FORMAT;
}
if (empty($datetime) || $datetime == '0000-00-00 00:00:00') {
return 'N/A';
}
return date($format, strtotime($datetime));
}
/**
* Format time
*/
function formatTime($time, $format = null) {
if ($format === null) {
$format = TIME_FORMAT;
}
return date($format, strtotime($time));
}
/**
* Get time ago string
*/
function timeAgo($datetime) {
if (empty($datetime)) {
return 'N/A';
}
$time = strtotime($datetime);
$now = time();
$diff = $now - $time;
if ($diff < 60) {
return $diff . ' seconds ago';
} elseif ($diff < 3600) {
return floor($diff / 60) . ' minutes ago';
} elseif ($diff < 86400) {
return floor($diff / 3600) . ' hours ago';
} elseif ($diff < 2592000) {
return floor($diff / 86400) . ' days ago';
} elseif ($diff < 31536000) {
return floor($diff / 2592000) . ' months ago';
} else {
return floor($diff / 31536000) . ' years ago';
}
}
/**
* Generate unique code
*/
function generateCode($prefix, $length = 8) {
return $prefix . strtoupper(substr(uniqid(), -$length));
}
/**
* Get status badge HTML
*/
function getStatusBadge($status) {
$badges = [
'todo' => 'secondary',
'in_progress' => 'primary',
'review' => 'warning',
'done' => 'success',
'blocked' => 'danger',
'planning' => 'info',
'active' => 'success',
'on_hold' => 'warning',
'completed' => 'success',
'cancelled' => 'danger',
'pending' => 'warning',
'achieved' => 'success',
'missed' => 'danger',
'low' => 'info',
'medium' => 'warning',
'high' => 'danger',
'urgent' => 'dark'
];
$statusText = str_replace('_', ' ', ucfirst($status));
$badgeClass = $badges[$status] ?? 'secondary';
return '<span class="badge bg-' . $badgeClass . '">' . $statusText . '</span>';
}
/**
* Get priority badge HTML
*/
function getPriorityBadge($priority) {
$badges = [
'low' => 'info',
'medium' => 'warning',
'high' => 'danger',
'urgent' => 'dark'
];
$priorityText = ucfirst($priority);
$badgeClass = $badges[$priority] ?? 'secondary';
return '<span class="badge bg-' . $badgeClass . '">' . $priorityText . '</span>';
}
/**
* Calculate progress percentage
*/
function calculateProgress($completed, $total) {
if ($total == 0) {
return 0;
}
return round(($completed / $total) * 100);
}
/**
* Format file size
*/
function formatFileSize($bytes, $precision = 2) {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}
/**
* Upload file
*/
function uploadFile($file, $targetDir, $allowedTypes = null) {
if ($allowedTypes === null) {
$allowedTypes = ALLOWED_EXTENSIONS;
}
// Check for errors
if ($file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'error' => 'Upload failed with error code: ' . $file['error']];
}
// Check file size
if ($file['size'] > MAX_FILE_SIZE) {
return ['success' => false, 'error' => 'File size exceeds limit of ' . (MAX_FILE_SIZE / 1024 / 1024) . 'MB'];
}
// Check file type
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $allowedTypes)) {
return ['success' => false, 'error' => 'File type not allowed. Allowed types: ' . implode(', ', $allowedTypes)];
}
// Generate unique filename
$filename = uniqid() . '_' . time() . '.' . $extension;
$targetPath = $targetDir . '/' . $filename;
// Create directory if not exists
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
// Upload file
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
return [
'success' => true,
'filename' => $filename,
'original_name' => $file['name'],
'path' => $targetPath,
'size' => $file['size'],
'type' => $file['type']
];
}
return ['success' => false, 'error' => 'Failed to move uploaded file'];
}
/**
* Delete file
*/
function deleteFile($path) {
if (file_exists($path)) {
return unlink($path);
}
return false;
}
/**
* Send email notification
*/
function sendEmail($to, $subject, $message) {
// For production, use PHPMailer with SMTP
// This is a simple mail function for development
$headers = [
'MIME-Version: 1.0',
'Content-type: text/html; charset=utf-8',
'From: ' . COMPANY_NAME . ' <' . COMPANY_EMAIL . '>',
'Reply-To: ' . COMPANY_EMAIL,
'X-Mailer: PHP/' . phpversion()
];
return mail($to, $subject, $message, implode("\r\n", $headers));
}
/**
* Log error
*/
function logError($message, $context = []) {
$logFile = __DIR__ . '/../logs/error.log';
$logDir = dirname($logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
$timestamp = date('Y-m-d H:i:s');
$contextStr = !empty($context) ? ' ' . json_encode($context) : '';
$logMessage = "[{$timestamp}] {$message}{$contextStr}\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
}
/**
* Get user IP address
*/
function getUserIP() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
return $_SERVER['REMOTE_ADDR'];
}
}
/**
* Generate random string
*/
function generateRandomString($length = 32) {
return bin2hex(random_bytes($length / 2));
}
/**
* Validate email
*/
function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
/**
* Get user avatar URL
*/
function getAvatarUrl($avatar) {
if ($avatar && file_exists(UPLOAD_DIR . 'avatars/' . $avatar)) {
return APP_URL . '/uploads/avatars/' . $avatar;
}
return APP_URL . '/assets/images/default-avatar.png';
}
/**
* Get user full name
*/
function getUserFullName($user) {
return $user['first_name'] . ' ' . $user['last_name'];
}
/**
* Get initials from name
*/
function getInitials($name) {
$words = explode(' ', $name);
$initials = '';
foreach ($words as $word) {
$initials .= strtoupper(substr($word, 0, 1));
}
return substr($initials, 0, 2);
}
/**
* Generate pagination
*/
function paginate($currentPage, $totalPages, $url) {
if ($totalPages <= 1) {
return '';
}
$html = '<nav aria-label="Page navigation"><ul class="pagination">';
// Previous button
if ($currentPage > 1) {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '?page=' . ($currentPage - 1) . '">Previous</a></li>';
} else {
$html .= '<li class="page-item disabled"><span class="page-link">Previous</span></li>';
}
// Page numbers
for ($i = 1; $i <= $totalPages; $i++) {
if ($i == $currentPage) {
$html .= '<li class="page-item active"><span class="page-link">' . $i . '</span></li>';
} else {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '?page=' . $i . '">' . $i . '</a></li>';
}
}
// Next button
if ($currentPage < $totalPages) {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '?page=' . ($currentPage + 1) . '">Next</a></li>';
} else {
$html .= '<li class="page-item disabled"><span class="page-link">Next</span></li>';
}
$html .= '</ul></nav>';
return $html;
}
/**
* Check if user has permission
*/
function hasPermission($user, $requiredRole) {
$roles = [
'admin' => 4,
'manager' => 3,
'member' => 2,
'client' => 1
];
$userLevel = $roles[$user['role']] ?? 0;
$requiredLevel = $roles[$requiredRole] ?? 0;
return $userLevel >= $requiredLevel;
}
/**
* Get task status options
*/
function getTaskStatusOptions() {
return [
'todo' => 'To Do',
'in_progress' => 'In Progress',
'review' => 'Review',
'done' => 'Done',
'blocked' => 'Blocked'
];
}
/**
* Get priority options
*/
function getPriorityOptions() {
return [
'low' => 'Low',
'medium' => 'Medium',
'high' => 'High',
'urgent' => 'Urgent'
];
}
/**
* Get task type options
*/
function getTaskTypeOptions() {
return [
'task' => 'Task',
'bug' => 'Bug',
'feature' => 'Feature',
'improvement' => 'Improvement'
];
}
/**
* Calculate days remaining until deadline
*/
function daysRemaining($dueDate) {
if (empty($dueDate)) {
return null;
}
$now = new DateTime();
$due = new DateTime($dueDate);
$interval = $now->diff($due);
if ($due < $now) {
return -$interval->days;
}
return $interval->days;
}
/**
* Get overdue status
*/
function isOverdue($dueDate) {
if (empty($dueDate)) {
return false;
}
$now = new DateTime();
$due = new DateTime($dueDate);
return $due < $now;
}
/**
* Format duration (minutes to hours and minutes)
*/
function formatDuration($minutes) {
if ($minutes < 60) {
return $minutes . ' min';
}
$hours = floor($minutes / 60);
$mins = $minutes % 60;
if ($mins == 0) {
return $hours . ' hr' . ($hours > 1 ? 's' : '');
}
return $hours . ' hr ' . $mins . ' min';
}
/**
* Get contrast color for background
*/
function getContrastColor($hexColor) {
// Convert hex to RGB
$hexColor = str_replace('#', '', $hexColor);
$r = hexdec(substr($hexColor, 0, 2));
$g = hexdec(substr($hexColor, 2, 2));
$b = hexdec(substr($hexColor, 4, 2));
// Calculate luminance
$luminance = (0.299 * $r + 0.587 * $g + 0.114 * $b) / 255;
return $luminance > 0.5 ? '#000000' : '#ffffff';
}
?>
Authentication Class
File: includes/auth.php
<?php
/**
* Authentication Class
* Handles user authentication, registration, and session management
*/
class Auth {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Register new user
*/
public function register($data) {
try {
// Check if email already exists
$existing = $this->db->getRow(
"SELECT id FROM users WHERE email = ?",
[$data['email']]
);
if ($existing) {
return ['success' => false, 'error' => 'Email already registered'];
}
// Hash password
$hashedPassword = password_hash($data['password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
// Generate user code
$userCode = generateCode('USR');
// Generate verification token
$verificationToken = generateRandomString();
// Prepare user data
$userData = [
'user_code' => $userCode,
'email' => $data['email'],
'password' => $hashedPassword,
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'phone' => $data['phone'] ?? null,
'role' => $data['role'] ?? 'member',
'verification_token' => $verificationToken
];
// Insert user
$newUserId = $this->db->insert('users', $userData);
if ($newUserId) {
// Send verification email
$this->sendVerificationEmail($data['email'], $verificationToken);
// Log activity
$this->logActivity($newUserId, 'user_registered', 'user', $newUserId, null, null, 'New user registered');
return [
'success' => true,
'user_id' => $newUserId,
'message' => 'Registration successful. Please check your email to verify your account.'
];
}
return ['success' => false, 'error' => 'Registration failed'];
} catch (Exception $e) {
logError('Registration error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Registration failed: ' . $e->getMessage()];
}
}
/**
* Login user
*/
public function login($email, $password, $remember = false) {
try {
// Get user
$user = $this->db->getRow(
"SELECT * FROM users WHERE email = ? AND status = 'active'",
[$email]
);
if (!$user) {
return ['success' => false, 'error' => 'Invalid email or password'];
}
// Check if email verified
if (!$user['email_verified']) {
return ['success' => false, 'error' => 'Please verify your email before logging in'];
}
// Verify password
if (!password_verify($password, $user['password'])) {
return ['success' => false, 'error' => 'Invalid email or password'];
}
// Check if password needs rehash
if (password_needs_rehash($user['password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS])) {
$newHash = password_hash($password, PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
$this->db->update('users', ['password' => $newHash], 'id = :id', ['id' => $user['id']]);
}
// Set session
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_code'] = $user['user_code'];
$_SESSION['user_email'] = $user['email'];
$_SESSION['user_name'] = $user['first_name'] . ' ' . $user['last_name'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['logged_in'] = true;
$_SESSION['login_time'] = time();
// Update last login
$this->db->update(
'users',
['last_login' => date('Y-m-d H:i:s'), 'last_activity' => date('Y-m-d H:i:s')],
'id = :id',
['id' => $user['id']]
);
// Log activity
$this->logActivity($user['id'], 'user_login', 'user', $user['id'], null, null, 'User logged in');
// Set remember me cookie
if ($remember) {
$this->setRememberMe($user['id']);
}
return ['success' => true, 'user' => $user];
} catch (Exception $e) {
logError('Login error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Login failed'];
}
}
/**
* Set remember me cookie
*/
private function setRememberMe($userId) {
$token = generateRandomString(64);
$expires = time() + (86400 * 30); // 30 days
// Store token in database (you would need a remember_tokens table)
// For now, just set cookie
setcookie('remember_token', $token, $expires, '/', '', false, true);
}
/**
* Logout user
*/
public function logout() {
if (isset($_SESSION['user_id'])) {
// Log activity
$this->logActivity($_SESSION['user_id'], 'user_logout', 'user', $_SESSION['user_id'], null, null, 'User logged out');
}
// Clear session
$_SESSION = array();
// Clear session cookie
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
}
// Clear remember me cookie
setcookie('remember_token', '', time() - 3600, '/');
// Destroy session
session_destroy();
}
/**
* Check if user is logged in
*/
public function isLoggedIn() {
return isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true;
}
/**
* Get current user
*/
public function getCurrentUser() {
if (!$this->isLoggedIn()) {
return null;
}
return $this->db->getRow(
"SELECT * FROM users WHERE id = ?",
[$_SESSION['user_id']]
);
}
/**
* Check if user has role
*/
public function hasRole($role) {
return isset($_SESSION['user_role']) && $_SESSION['user_role'] === $role;
}
/**
* Check if user has any of the roles
*/
public function hasAnyRole($roles) {
return isset($_SESSION['user_role']) && in_array($_SESSION['user_role'], $roles);
}
/**
* Require login
*/
public function requireLogin() {
if (!$this->isLoggedIn()) {
$_SESSION['error'] = 'Please login to access this page';
redirect('/login.php');
}
}
/**
* Require role
*/
public function requireRole($role) {
$this->requireLogin();
if (is_array($role)) {
if (!in_array($_SESSION['user_role'], $role)) {
$_SESSION['error'] = 'You do not have permission to access this page';
redirect('/dashboard.php');
}
} else {
if (!$this->hasRole($role)) {
$_SESSION['error'] = 'You do not have permission to access this page';
// Redirect based on role
if ($this->hasRole('admin')) {
redirect('/admin/dashboard.php');
} elseif ($this->hasRole('manager')) {
redirect('/manager/dashboard.php');
} elseif ($this->hasRole('member')) {
redirect('/member/dashboard.php');
} else {
redirect('/client/dashboard.php');
}
}
}
}
/**
* Verify email
*/
public function verifyEmail($token) {
$user = $this->db->getRow(
"SELECT id FROM users WHERE verification_token = ?",
[$token]
);
if ($user) {
$this->db->update(
'users',
['email_verified' => true, 'verification_token' => null],
'id = :id',
['id' => $user['id']]
);
// Log activity
$this->logActivity($user['id'], 'email_verified', 'user', $user['id'], null, null, 'Email verified');
return true;
}
return false;
}
/**
* Send verification email
*/
private function sendVerificationEmail($email, $token) {
$subject = "Verify your email - " . APP_NAME;
$message = "<h1>Email Verification</h1>";
$message .= "<p>Click the link below to verify your email address:</p>";
$message .= "<p><a href='" . APP_URL . "/verify.php?token=" . $token . "'>Verify Email</a></p>";
$message .= "<p>If you didn't create an account, you can ignore this email.</p>";
return sendEmail($email, $subject, $message);
}
/**
* Forgot password
*/
public function forgotPassword($email) {
$user = $this->db->getRow(
"SELECT id, first_name FROM users WHERE email = ?",
[$email]
);
if ($user) {
$token = generateRandomString();
$expires = date('Y-m-d H:i:s', strtotime('+1 hour'));
$this->db->update(
'users',
['reset_token' => $token, 'reset_expires' => $expires],
'id = :id',
['id' => $user['id']]
);
// Send reset email
$subject = "Password Reset - " . APP_NAME;
$message = "<h1>Password Reset Request</h1>";
$message .= "<p>Hello " . $user['first_name'] . ",</p>";
$message .= "<p>Click the link below to reset your password (valid for 1 hour):</p>";
$message .= "<p><a href='" . APP_URL . "/reset_password.php?token=" . $token . "'>Reset Password</a></p>";
$message .= "<p>If you didn't request this, you can ignore this email.</p>";
return sendEmail($email, $subject, $message);
}
return false;
}
/**
* Reset password
*/
public function resetPassword($token, $password) {
$user = $this->db->getRow(
"SELECT id FROM users WHERE reset_token = ? AND reset_expires > NOW()",
[$token]
);
if ($user) {
$hashedPassword = password_hash($password, PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
$this->db->update(
'users',
['password' => $hashedPassword, 'reset_token' => null, 'reset_expires' => null],
'id = :id',
['id' => $user['id']]
);
// Log activity
$this->logActivity($user['id'], 'password_reset', 'user', $user['id'], null, null, 'Password reset');
return true;
}
return false;
}
/**
* Update user profile
*/
public function updateProfile($userId, $data) {
try {
$updateData = [
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'phone' => $data['phone'] ?? null,
'department' => $data['department'] ?? null,
'position' => $data['position'] ?? null,
'bio' => $data['bio'] ?? null,
'timezone' => $data['timezone'] ?? 'UTC',
'email_notifications' => isset($data['email_notifications']) ? 1 : 0,
'push_notifications' => isset($data['push_notifications']) ? 1 : 0
];
// Handle profile picture upload
if (isset($_FILES['profile_picture']) && $_FILES['profile_picture']['error'] == 0) {
$uploadDir = UPLOAD_DIR . 'avatars/';
$result = uploadFile($_FILES['profile_picture'], $uploadDir);
if ($result['success']) {
// Delete old picture
$oldUser = $this->db->getRow("SELECT profile_picture FROM users WHERE id = ?", [$userId]);
if ($oldUser && $oldUser['profile_picture'] != 'default.png') {
deleteFile(UPLOAD_DIR . 'avatars/' . $oldUser['profile_picture']);
}
$updateData['profile_picture'] = $result['filename'];
}
}
$this->db->update('users', $updateData, 'id = :id', ['id' => $userId]);
// Log activity
$this->logActivity($userId, 'profile_updated', 'user', $userId, null, null, 'Profile updated');
return ['success' => true, 'message' => 'Profile updated successfully'];
} catch (Exception $e) {
logError('Profile update error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to update profile'];
}
}
/**
* Change password
*/
public function changePassword($userId, $currentPassword, $newPassword) {
$user = $this->db->getRow("SELECT password FROM users WHERE id = ?", [$userId]);
if (!password_verify($currentPassword, $user['password'])) {
return ['success' => false, 'error' => 'Current password is incorrect'];
}
$hashedPassword = password_hash($newPassword, PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
$this->db->update('users', ['password' => $hashedPassword], 'id = :id', ['id' => $userId]);
// Log activity
$this->logActivity($userId, 'password_changed', 'user', $userId, null, null, 'Password changed');
return ['success' => true, 'message' => 'Password changed successfully'];
}
/**
* Log activity
*/
private function logActivity($userId, $action, $entityType, $entityId, $oldValues, $newValues, $description) {
$log = new ActivityLog();
$log->create($userId, $action, $entityType, $entityId, $oldValues, $newValues, $description);
}
/**
* Update last activity
*/
public function updateLastActivity() {
if ($this->isLoggedIn()) {
$this->db->update(
'users',
['last_activity' => date('Y-m-d H:i:s')],
'id = :id',
['id' => $_SESSION['user_id']]
);
}
}
}
// Initialize Auth
$auth = new Auth();
// Update last activity
$auth->updateLastActivity();
?>
User Class
File: includes/User.php
<?php
/**
* User Class
* Handles all user-related operations
*/
class User {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Get user by ID
*/
public function getUser($userId) {
return $this->db->getRow(
"SELECT * FROM users WHERE id = ?",
[$userId]
);
}
/**
* Get user by email
*/
public function getUserByEmail($email) {
return $this->db->getRow(
"SELECT * FROM users WHERE email = ?",
[$email]
);
}
/**
* Get all users with filters
*/
public function getUsers($filters = []) {
$sql = "SELECT * FROM users WHERE 1=1";
$params = [];
if (!empty($filters['role'])) {
$sql .= " AND role = :role";
$params['role'] = $filters['role'];
}
if (!empty($filters['status'])) {
$sql .= " AND status = :status";
$params['status'] = $filters['status'];
}
if (!empty($filters['search'])) {
$sql .= " AND (first_name LIKE :search OR last_name LIKE :search OR email LIKE :search)";
$params['search'] = '%' . $filters['search'] . '%';
}
if (!empty($filters['team_id'])) {
$sql .= " AND id IN (SELECT user_id FROM team_members WHERE team_id = :team_id)";
$params['team_id'] = $filters['team_id'];
}
if (!empty($filters['project_id'])) {
$sql .= " AND id IN (SELECT user_id FROM project_members WHERE project_id = :project_id)";
$params['project_id'] = $filters['project_id'];
}
$sql .= " ORDER BY first_name ASC, last_name ASC";
// Pagination
$page = $filters['page'] ?? 1;
$limit = $filters['limit'] ?? ITEMS_PER_PAGE;
$offset = ($page - 1) * $limit;
$sql .= " LIMIT :limit OFFSET :offset";
$params['limit'] = $limit;
$params['offset'] = $offset;
return $this->db->getRows($sql, $params);
}
/**
* Get user count
*/
public function getUserCount($filters = []) {
$sql = "SELECT COUNT(*) as count FROM users WHERE 1=1";
$params = [];
if (!empty($filters['role'])) {
$sql .= " AND role = :role";
$params['role'] = $filters['role'];
}
if (!empty($filters['status'])) {
$sql .= " AND status = :status";
$params['status'] = $filters['status'];
}
$result = $this->db->getRow($sql, $params);
return $result['count'];
}
/**
* Create new user (admin only)
*/
public function createUser($data) {
try {
// Check if email already exists
$existing = $this->db->getRow(
"SELECT id FROM users WHERE email = ?",
[$data['email']]
);
if ($existing) {
return ['success' => false, 'error' => 'Email already exists'];
}
// Hash password
$hashedPassword = password_hash($data['password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
// Generate user code
$userCode = generateCode('USR');
$userData = [
'user_code' => $userCode,
'email' => $data['email'],
'password' => $hashedPassword,
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'phone' => $data['phone'] ?? null,
'role' => $data['role'] ?? 'member',
'department' => $data['department'] ?? null,
'position' => $data['position'] ?? null,
'email_verified' => 1, // Admin created users are automatically verified
'status' => $data['status'] ?? 'active'
];
$userId = $this->db->insert('users', $userData);
if ($userId) {
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'user_created',
'user',
$userId,
null,
$userData,
'User created by admin'
);
return ['success' => true, 'user_id' => $userId, 'message' => 'User created successfully'];
}
return ['success' => false, 'error' => 'Failed to create user'];
} catch (Exception $e) {
logError('Create user error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Failed to create user'];
}
}
/**
* Update user (admin only)
*/
public function updateUser($userId, $data) {
try {
$user = $this->getUser($userId);
if (!$user) {
return ['success' => false, 'error' => 'User not found'];
}
$updateData = [
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'phone' => $data['phone'] ?? null,
'role' => $data['role'] ?? $user['role'],
'department' => $data['department'] ?? null,
'position' => $data['position'] ?? null,
'status' => $data['status'] ?? $user['status']
];
// Update password if provided
if (!empty($data['password'])) {
$updateData['password'] = password_hash($data['password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
}
$this->db->update('users', $updateData, 'id = :id', ['id' => $userId]);
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'user_updated',
'user',
$userId,
$user,
$updateData,
'User updated by admin'
);
return ['success' => true, 'message' => 'User updated successfully'];
} catch (Exception $e) {
logError('Update user error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Failed to update user'];
}
}
/**
* Delete user (admin only)
*/
public function deleteUser($userId) {
try {
$user = $this->getUser($userId);
if (!$user) {
return ['success' => false, 'error' => 'User not found'];
}
// Check if user has active tasks
$activeTasks = $this->db->getValue(
"SELECT COUNT(*) FROM tasks WHERE assigned_to = ? AND status != 'done'",
[$userId]
);
if ($activeTasks > 0) {
return ['success' => false, 'error' => 'Cannot delete user with active tasks'];
}
// Soft delete - update status
$this->db->update('users', ['status' => 'inactive'], 'id = :id', ['id' => $userId]);
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'user_deleted',
'user',
$userId,
$user,
null,
'User deactivated by admin'
);
return ['success' => true, 'message' => 'User deactivated successfully'];
} catch (Exception $e) {
logError('Delete user error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to delete user'];
}
}
/**
* Get user teams
*/
public function getUserTeams($userId) {
return $this->db->getRows(
"SELECT t.*, tm.role_in_team
FROM teams t
JOIN team_members tm ON t.id = tm.team_id
WHERE tm.user_id = ? AND t.status = 'active'",
[$userId]
);
}
/**
* Get user projects
*/
public function getUserProjects($userId) {
return $this->db->getRows(
"SELECT p.*, pm.role_in_project
FROM projects p
JOIN project_members pm ON p.id = pm.project_id
WHERE pm.user_id = ? AND p.status NOT IN ('completed', 'cancelled')",
[$userId]
);
}
/**
* Get user tasks
*/
public function getUserTasks($userId, $status = null) {
$sql = "SELECT t.*, p.name as project_name
FROM tasks t
LEFT JOIN projects p ON t.project_id = p.id
WHERE t.assigned_to = :user_id";
$params = ['user_id' => $userId];
if ($status) {
$sql .= " AND t.status = :status";
$params['status'] = $status;
}
$sql .= " ORDER BY t.due_date ASC, t.priority DESC";
return $this->db->getRows($sql, $params);
}
/**
* Get user dashboard stats
*/
public function getUserStats($userId) {
$stats = [];
// Total tasks assigned
$stats['total_tasks'] = $this->db->getValue(
"SELECT COUNT(*) FROM tasks WHERE assigned_to = ?",
[$userId]
);
// Tasks by status
$stats['todo_tasks'] = $this->db->getValue(
"SELECT COUNT(*) FROM tasks WHERE assigned_to = ? AND status = 'todo'",
[$userId]
);
$stats['in_progress_tasks'] = $this->db->getValue(
"SELECT COUNT(*) FROM tasks WHERE assigned_to = ? AND status = 'in_progress'",
[$userId]
);
$stats['review_tasks'] = $this->db->getValue(
"SELECT COUNT(*) FROM tasks WHERE assigned_to = ? AND status = 'review'",
[$userId]
);
$stats['done_tasks'] = $this->db->getValue(
"SELECT COUNT(*) FROM tasks WHERE assigned_to = ? AND status = 'done'",
[$userId]
);
// Overdue tasks
$stats['overdue_tasks'] = $this->db->getValue(
"SELECT COUNT(*) FROM tasks
WHERE assigned_to = ?
AND status NOT IN ('done', 'cancelled')
AND due_date < CURDATE()",
[$userId]
);
// Upcoming deadlines (next 7 days)
$stats['upcoming_deadlines'] = $this->db->getValue(
"SELECT COUNT(*) FROM tasks
WHERE assigned_to = ?
AND status NOT IN ('done', 'cancelled')
AND due_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY)",
[$userId]
);
// Total projects
$stats['total_projects'] = $this->db->getValue(
"SELECT COUNT(DISTINCT project_id) FROM project_members WHERE user_id = ?",
[$userId]
);
// Total time logged (current month)
$stats['time_logged_month'] = $this->db->getValue(
"SELECT SUM(duration) FROM time_logs
WHERE user_id = ?
AND MONTH(start_time) = MONTH(CURDATE())
AND YEAR(start_time) = YEAR(CURDATE())",
[$userId]
) ?: 0;
return $stats;
}
/**
* Search users for mentions
*/
public function searchUsers($query, $projectId = null, $limit = 10) {
$sql = "SELECT id, first_name, last_name, email, profile_picture
FROM users
WHERE status = 'active'
AND (first_name LIKE :query OR last_name LIKE :query OR email LIKE :query)";
$params = ['query' => '%' . $query . '%'];
if ($projectId) {
$sql .= " AND id IN (SELECT user_id FROM project_members WHERE project_id = :project_id)";
$params['project_id'] = $projectId;
}
$sql .= " LIMIT :limit";
$params['limit'] = $limit;
return $this->db->getRows($sql, $params);
}
/**
* Get user notifications
*/
public function getNotifications($userId, $unreadOnly = false, $limit = null) {
$sql = "SELECT * FROM notifications WHERE user_id = :user_id";
$params = ['user_id' => $userId];
if ($unreadOnly) {
$sql .= " AND is_read = 0";
}
$sql .= " ORDER BY created_at DESC";
if ($limit) {
$sql .= " LIMIT :limit";
$params['limit'] = $limit;
}
return $this->db->getRows($sql, $params);
}
/**
* Mark notification as read
*/
public function markNotificationRead($notificationId, $userId) {
return $this->db->update(
'notifications',
['is_read' => true, 'read_at' => date('Y-m-d H:i:s')],
'id = :id AND user_id = :user_id',
['id' => $notificationId, 'user_id' => $userId]
);
}
/**
* Mark all notifications as read
*/
public function markAllNotificationsRead($userId) {
return $this->db->update(
'notifications',
['is_read' => true, 'read_at' => date('Y-m-d H:i:s')],
'user_id = :user_id AND is_read = 0',
['user_id' => $userId]
);
}
}
?>
Task Class
File: includes/Task.php
<?php
/**
* Task Class
* Handles all task-related operations
*/
class Task {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Get task by ID
*/
public function getTask($taskId) {
$task = $this->db->getRow(
"SELECT t.*,
p.name as project_name,
c.name as category_name, c.color as category_color,
creator.first_name as creator_first_name, creator.last_name as creator_last_name,
assignee.first_name as assignee_first_name, assignee.last_name as assignee_last_name
FROM tasks t
LEFT JOIN projects p ON t.project_id = p.id
LEFT JOIN categories c ON t.category_id = c.id
LEFT JOIN users creator ON t.created_by = creator.id
LEFT JOIN users assignee ON t.assigned_to = assignee.id
WHERE t.id = ? OR t.task_code = ?",
[$taskId, $taskId]
);
if ($task) {
// Get tags as array
$task['tags_array'] = $task['tags'] ? explode(',', $task['tags']) : [];
// Get attachments as array
$task['attachments_array'] = $task['attachments'] ? json_decode($task['attachments'], true) : [];
// Get subtasks
$task['subtasks'] = $this->getSubtasks($task['id']);
// Get dependencies
$task['dependencies'] = $this->getDependencies($task['id']);
// Get comments count
$task['comments_count'] = $this->db->getValue(
"SELECT COUNT(*) FROM comments WHERE task_id = ?",
[$task['id']]
);
// Get time logged
$task['time_logged'] = $this->db->getValue(
"SELECT SUM(duration) FROM time_logs WHERE task_id = ?",
[$task['id']]
) ?: 0;
}
return $task;
}
/**
* Get tasks with filters
*/
public function getTasks($filters = []) {
$sql = "SELECT t.*,
p.name as project_name,
c.name as category_name, c.color as category_color,
assignee.first_name as assignee_first_name,
assignee.last_name as assignee_last_name
FROM tasks t
LEFT JOIN projects p ON t.project_id = p.id
LEFT JOIN categories c ON t.category_id = c.id
LEFT JOIN users assignee ON t.assigned_to = assignee.id
WHERE 1=1";
$params = [];
if (!empty($filters['project_id'])) {
$sql .= " AND t.project_id = :project_id";
$params['project_id'] = $filters['project_id'];
}
if (!empty($filters['assigned_to'])) {
$sql .= " AND t.assigned_to = :assigned_to";
$params['assigned_to'] = $filters['assigned_to'];
}
if (!empty($filters['created_by'])) {
$sql .= " AND t.created_by = :created_by";
$params['created_by'] = $filters['created_by'];
}
if (!empty($filters['status'])) {
if (is_array($filters['status'])) {
$placeholders = implode(',', array_fill(0, count($filters['status']), '?'));
$sql .= " AND t.status IN ($placeholders)";
$params = array_merge($params, $filters['status']);
} else {
$sql .= " AND t.status = :status";
$params['status'] = $filters['status'];
}
}
if (!empty($filters['priority'])) {
$sql .= " AND t.priority = :priority";
$params['priority'] = $filters['priority'];
}
if (!empty($filters['category_id'])) {
$sql .= " AND t.category_id = :category_id";
$params['category_id'] = $filters['category_id'];
}
if (!empty($filters['due_date_from'])) {
$sql .= " AND t.due_date >= :due_date_from";
$params['due_date_from'] = $filters['due_date_from'];
}
if (!empty($filters['due_date_to'])) {
$sql .= " AND t.due_date <= :due_date_to";
$params['due_date_to'] = $filters['due_date_to'];
}
if (!empty($filters['overdue'])) {
$sql .= " AND t.due_date < CURDATE() AND t.status NOT IN ('done', 'cancelled')";
}
if (!empty($filters['search'])) {
$sql .= " AND (t.title LIKE :search OR t.description LIKE :search OR t.tags LIKE :search)";
$params['search'] = '%' . $filters['search'] . '%';
}
if (!empty($filters['tags'])) {
$tags = explode(',', $filters['tags']);
foreach ($tags as $index => $tag) {
$sql .= " AND t.tags LIKE :tag_$index";
$params['tag_' . $index] = '%' . trim($tag) . '%';
}
}
$sql .= " ORDER BY " . ($filters['order_by'] ?? 't.due_date ASC, t.priority DESC');
// Pagination
$page = $filters['page'] ?? 1;
$limit = $filters['limit'] ?? ITEMS_PER_PAGE;
$offset = ($page - 1) * $limit;
$sql .= " LIMIT :limit OFFSET :offset";
$params['limit'] = $limit;
$params['offset'] = $offset;
return $this->db->getRows($sql, $params);
}
/**
* Create new task
*/
public function createTask($data) {
try {
$this->db->beginTransaction();
// Generate task code
$taskCode = generateCode('TSK');
// Handle tags
$tags = '';
if (!empty($data['tags'])) {
if (is_array($data['tags'])) {
$tags = implode(',', $data['tags']);
} else {
$tags = $data['tags'];
}
}
$taskData = [
'task_code' => $taskCode,
'title' => $data['title'],
'description' => $data['description'] ?? null,
'project_id' => $data['project_id'] ?? null,
'category_id' => $data['category_id'] ?? null,
'parent_task_id' => $data['parent_task_id'] ?? null,
'created_by' => $_SESSION['user_id'],
'assigned_to' => $data['assigned_to'] ?? null,
'status' => $data['status'] ?? 'todo',
'priority' => $data['priority'] ?? 'medium',
'type' => $data['type'] ?? 'task',
'estimated_hours' => $data['estimated_hours'] ?? null,
'start_date' => $data['start_date'] ?? null,
'due_date' => $data['due_date'] ?? null,
'tags' => $tags
];
$taskId = $this->db->insert('tasks', $taskData);
if ($taskId) {
// Handle dependencies
if (!empty($data['dependencies'])) {
foreach ($data['dependencies'] as $depId) {
$this->addDependency($taskId, $depId);
}
}
// Add to project members if not already
if (!empty($data['project_id']) && !empty($data['assigned_to'])) {
$this->ensureProjectMember($data['project_id'], $data['assigned_to']);
}
// Create notification for assignee
if (!empty($data['assigned_to'])) {
$this->sendAssignmentNotification($taskId, $data['assigned_to']);
}
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'task_created',
'task',
$taskId,
null,
$taskData,
'Task created'
);
}
$this->db->commit();
return ['success' => true, 'task_id' => $taskId, 'task_code' => $taskCode, 'message' => 'Task created successfully'];
} catch (Exception $e) {
$this->db->rollback();
logError('Create task error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Failed to create task'];
}
}
/**
* Update task
*/
public function updateTask($taskId, $data) {
try {
$task = $this->getTask($taskId);
if (!$task) {
return ['success' => false, 'error' => 'Task not found'];
}
$oldAssignedTo = $task['assigned_to'];
// Handle tags
$tags = $task['tags'];
if (isset($data['tags'])) {
if (is_array($data['tags'])) {
$tags = implode(',', $data['tags']);
} else {
$tags = $data['tags'];
}
}
$updateData = [
'title' => $data['title'] ?? $task['title'],
'description' => $data['description'] ?? $task['description'],
'project_id' => $data['project_id'] ?? $task['project_id'],
'category_id' => $data['category_id'] ?? $task['category_id'],
'assigned_to' => $data['assigned_to'] ?? $task['assigned_to'],
'priority' => $data['priority'] ?? $task['priority'],
'type' => $data['type'] ?? $task['type'],
'estimated_hours' => $data['estimated_hours'] ?? $task['estimated_hours'],
'start_date' => $data['start_date'] ?? $task['start_date'],
'due_date' => $data['due_date'] ?? $task['due_date'],
'tags' => $tags
];
// Handle status change
if (isset($data['status']) && $data['status'] != $task['status']) {
$updateData['status'] = $data['status'];
if ($data['status'] == 'done' && $task['status'] != 'done') {
$updateData['completed_at'] = date('Y-m-d H:i:s');
$updateData['progress'] = 100;
}
}
$this->db->update('tasks', $updateData, 'id = :id', ['id' => $taskId]);
// Send notification if assignee changed
if ($updateData['assigned_to'] != $oldAssignedTo && !empty($updateData['assigned_to'])) {
$this->sendAssignmentNotification($taskId, $updateData['assigned_to']);
}
// Add to project members if not already
if (!empty($updateData['project_id']) && !empty($updateData['assigned_to'])) {
$this->ensureProjectMember($updateData['project_id'], $updateData['assigned_to']);
}
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'task_updated',
'task',
$taskId,
$task,
$updateData,
'Task updated'
);
return ['success' => true, 'message' => 'Task updated successfully'];
} catch (Exception $e) {
logError('Update task error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Failed to update task'];
}
}
/**
* Update task status
*/
public function updateStatus($taskId, $status) {
$task = $this->getTask($taskId);
if (!$task) {
return ['success' => false, 'error' => 'Task not found'];
}
$oldStatus = $task['status'];
$updateData = ['status' => $status];
if ($status == 'done' && $oldStatus != 'done') {
$updateData['completed_at'] = date('Y-m-d H:i:s');
$updateData['progress'] = 100;
}
$this->db->update('tasks', $updateData, 'id = :id', ['id' => $taskId]);
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'task_status_changed',
'task',
$taskId,
['status' => $oldStatus],
['status' => $status],
'Task status changed from ' . $oldStatus . ' to ' . $status
);
return ['success' => true, 'message' => 'Task status updated'];
}
/**
* Update task progress
*/
public function updateProgress($taskId, $progress) {
$task = $this->getTask($taskId);
if (!$task) {
return ['success' => false, 'error' => 'Task not found'];
}
$oldProgress = $task['progress'];
$this->db->update(
'tasks',
['progress' => $progress],
'id = :id',
['id' => $taskId]
);
// If progress is 100%, mark as done
if ($progress == 100 && $task['status'] != 'done') {
$this->updateStatus($taskId, 'done');
}
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'task_progress_updated',
'task',
$taskId,
['progress' => $oldProgress],
['progress' => $progress],
'Task progress updated'
);
return ['success' => true, 'message' => 'Progress updated'];
}
/**
* Delete task
*/
public function deleteTask($taskId) {
try {
$task = $this->getTask($taskId);
if (!$task) {
return ['success' => false, 'error' => 'Task not found'];
}
// Check if task has subtasks
$subtasks = $this->db->getValue(
"SELECT COUNT(*) FROM tasks WHERE parent_task_id = ?",
[$taskId]
);
if ($subtasks > 0) {
return ['success' => false, 'error' => 'Cannot delete task with subtasks'];
}
$this->db->beginTransaction();
// Delete dependencies
$this->db->delete('task_dependencies', 'task_id = ? OR depends_on = ?', [$taskId, $taskId]);
// Delete comments
$this->db->delete('comments', 'task_id = ?', [$taskId]);
// Delete time logs
$this->db->delete('time_logs', 'task_id = ?', [$taskId]);
// Delete task
$this->db->delete('tasks', 'id = ?', [$taskId]);
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'task_deleted',
'task',
$taskId,
$task,
null,
'Task deleted'
);
$this->db->commit();
return ['success' => true, 'message' => 'Task deleted successfully'];
} catch (Exception $e) {
$this->db->rollback();
logError('Delete task error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to delete task'];
}
}
/**
* Get subtasks
*/
public function getSubtasks($parentTaskId) {
return $this->db->getRows(
"SELECT t.*, assignee.first_name, assignee.last_name
FROM tasks t
LEFT JOIN users assignee ON t.assigned_to = assignee.id
WHERE t.parent_task_id = ?
ORDER BY t.order_position ASC",
[$parentTaskId]
);
}
/**
* Add dependency
*/
public function addDependency($taskId, $dependsOn) {
// Check for circular dependency
if ($this->wouldCreateCircularDependency($taskId, $dependsOn)) {
return false;
}
return $this->db->insert('task_dependencies', [
'task_id' => $taskId,
'depends_on' => $dependsOn
]);
}
/**
* Check for circular dependency
*/
private function wouldCreateCircularDependency($taskId, $dependsOn) {
if ($taskId == $dependsOn) {
return true;
}
// Check if depends_on already depends on taskId
$deps = $this->db->getRows(
"SELECT task_id FROM task_dependencies WHERE depends_on = ?",
[$dependsOn]
);
foreach ($deps as $dep) {
if ($this->wouldCreateCircularDependency($taskId, $dep['task_id'])) {
return true;
}
}
return false;
}
/**
* Get dependencies
*/
public function getDependencies($taskId) {
return $this->db->getRows(
"SELECT t.*, td.dependency_type
FROM tasks t
JOIN task_dependencies td ON t.id = td.depends_on
WHERE td.task_id = ?",
[$taskId]
);
}
/**
* Get dependent tasks
*/
public function getDependentTasks($taskId) {
return $this->db->getRows(
"SELECT t.*, td.dependency_type
FROM tasks t
JOIN task_dependencies td ON t.id = td.task_id
WHERE td.depends_on = ?",
[$taskId]
);
}
/**
* Ensure user is project member
*/
private function ensureProjectMember($projectId, $userId) {
$exists = $this->db->getValue(
"SELECT id FROM project_members WHERE project_id = ? AND user_id = ?",
[$projectId, $userId]
);
if (!$exists) {
$this->db->insert('project_members', [
'project_id' => $projectId,
'user_id' => $userId
]);
}
}
/**
* Send assignment notification
*/
private function sendAssignmentNotification($taskId, $userId) {
$task = $this->getTask($taskId);
$notification = new Notification();
$notification->create(
$userId,
'task_assigned',
'New Task Assigned',
"You have been assigned to task: " . $task['title'],
['task_id' => $taskId],
'/member/task_details.php?id=' . $taskId
);
}
/**
* Get task statistics
*/
public function getTaskStats($projectId = null) {
$sql = "SELECT
COUNT(*) as total_tasks,
SUM(CASE WHEN status = 'todo' THEN 1 ELSE 0 END) as todo,
SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) as in_progress,
SUM(CASE WHEN status = 'review' THEN 1 ELSE 0 END) as review,
SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) as done,
SUM(CASE WHEN status = 'blocked' THEN 1 ELSE 0 END) as blocked,
SUM(CASE WHEN due_date < CURDATE() AND status NOT IN ('done', 'cancelled') THEN 1 ELSE 0 END) as overdue,
SUM(CASE WHEN priority = 'urgent' THEN 1 ELSE 0 END) as urgent
FROM tasks";
$params = [];
if ($projectId) {
$sql .= " WHERE project_id = :project_id";
$params['project_id'] = $projectId;
}
return $this->db->getRow($sql, $params);
}
/**
* Get tasks for Gantt chart
*/
public function getGanttData($projectId) {
$tasks = $this->db->getRows(
"SELECT t.id, t.title, t.start_date, t.due_date as end_date, t.progress,
t.duration, t.dependencies
FROM tasks t
WHERE t.project_id = ? AND t.start_date IS NOT NULL AND t.due_date IS NOT NULL
ORDER BY t.start_date ASC",
[$projectId]
);
$ganttData = [];
foreach ($tasks as $task) {
$deps = $this->getDependencies($task['id']);
$depIds = [];
foreach ($deps as $dep) {
$depIds[] = $dep['id'];
}
$ganttData[] = [
'id' => $task['id'],
'name' => $task['title'],
'start' => $task['start_date'],
'end' => $task['end_date'],
'progress' => $task['progress'],
'dependencies' => implode(',', $depIds)
];
}
return $ganttData;
}
/**
* Log time on task
*/
public function logTime($taskId, $userId, $startTime, $endTime, $description = null) {
$start = new DateTime($startTime);
$end = new DateTime($endTime);
$interval = $start->diff($end);
$duration = ($interval->days * 24 * 60) + ($interval->h * 60) + $interval->i;
$logId = $this->db->insert('time_logs', [
'task_id' => $taskId,
'user_id' => $userId,
'start_time' => $startTime,
'end_time' => $endTime,
'duration' => $duration,
'description' => $description
]);
if ($logId) {
// Update task actual hours
$totalTime = $this->db->getValue(
"SELECT SUM(duration) FROM time_logs WHERE task_id = ?",
[$taskId]
);
$this->db->update(
'tasks',
['actual_hours' => $totalTime / 60],
'id = :id',
['id' => $taskId]
);
return ['success' => true, 'log_id' => $logId];
}
return ['success' => false];
}
}
?>
Frontend Pages
Login Page
File: login.php
<?php
require_once 'includes/config.php';
// Redirect if already logged in
if ($auth->isLoggedIn()) {
$role = $_SESSION['user_role'];
if ($role == 'admin') {
redirect('/admin/dashboard.php');
} elseif ($role == 'manager') {
redirect('/manager/dashboard.php');
} elseif ($role == 'member') {
redirect('/member/dashboard.php');
} else {
redirect('/client/dashboard.php');
}
}
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = sanitize($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
$remember = isset($_POST['remember']);
if (empty($email) || empty($password)) {
$error = 'Please enter email and password';
} else {
$result = $auth->login($email, $password, $remember);
if ($result['success']) {
$user = $result['user'];
// Redirect based on role
if ($user['role'] === 'admin') {
redirect('/admin/dashboard.php');
} elseif ($user['role'] === 'manager') {
redirect('/manager/dashboard.php');
} elseif ($user['role'] === 'member') {
redirect('/member/dashboard.php');
} else {
redirect('/client/dashboard.php');
}
} else {
$error = $result['error'];
}
}
}
// Check for session messages
if (isset($_SESSION['success'])) {
$success = $_SESSION['success'];
unset($_SESSION['success']);
}
if (isset($_SESSION['error'])) {
$error = $_SESSION['error'];
unset($_SESSION['error']);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - <?php echo APP_NAME; ?></title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body class="bg-light">
<div class="container">
<div class="row justify-content-center align-items-center min-vh-100">
<div class="col-md-6 col-lg-5">
<div class="card shadow-lg border-0 rounded-lg">
<div class="card-header bg-primary text-white text-center py-4">
<h3 class="mb-0">
<i class="fas fa-tasks me-2"></i>
<?php echo APP_NAME; ?>
</h3>
<p class="mb-0 text-white-50">Manage your tasks efficiently</p>
</div>
<div class="card-body p-5">
<h4 class="text-center mb-4">Welcome Back!</h4>
<?php if ($error): ?>
<div class="alert alert-danger alert-dismissible fade show">
<i class="fas fa-exclamation-circle me-2"></i>
<?php echo $error; ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success alert-dismissible fade show">
<i class="fas fa-check-circle me-2"></i>
<?php echo $success; ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form method="POST" action="" onsubmit="return validateLogin()">
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<div class="mb-4">
<label for="email" class="form-label">
<i class="fas fa-envelope me-2"></i>Email Address
</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-envelope"></i></span>
<input type="email" class="form-control" id="email" name="email"
placeholder="Enter your email" value="<?php echo htmlspecialchars($_POST['email'] ?? ''); ?>" required>
</div>
</div>
<div class="mb-4">
<label for="password" class="form-label">
<i class="fas fa-lock me-2"></i>Password
</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
<input type="password" class="form-control" id="password" name="password"
placeholder="Enter your password" required>
<button class="btn btn-outline-secondary" type="button" onclick="togglePassword()">
<i class="fas fa-eye" id="togglePasswordIcon"></i>
</button>
</div>
</div>
<div class="mb-4 form-check">
<input type="checkbox" class="form-check-input" id="remember" name="remember">
<label class="form-check-label" for="remember">Remember me</label>
<a href="forgot_password.php" class="float-end text-decoration-none">
Forgot Password?
</a>
</div>
<button type="submit" class="btn btn-primary w-100 py-2 mb-3">
<i class="fas fa-sign-in-alt me-2"></i>Login
</button>
</form>
<div class="text-center">
<p class="mb-0">
Don't have an account?
<a href="register.php" class="text-decoration-none">Register here</a>
</p>
</div>
<hr class="my-4">
<div class="text-center">
<a href="index.php" class="text-decoration-none">
<i class="fas fa-arrow-left me-1"></i>Back to Home
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script>
function togglePassword() {
const password = document.getElementById('password');
const icon = document.getElementById('togglePasswordIcon');
if (password.type === 'password') {
password.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
password.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
}
function validateLogin() {
const email = document.getElementById('email').value.trim();
const password = document.getElementById('password').value;
if (email === '') {
alert('Please enter your email');
return false;
}
if (password === '') {
alert('Please enter your password');
return false;
}
return true;
}
</script>
</body>
</html>
Member Dashboard
File: member/dashboard.php
<?php require_once '../includes/config.php'; require_once '../includes/auth.php'; // Require login and member role $auth->requireRole(['member', 'manager', 'admin']); // Get current user $user = $auth->getCurrentUser(); // Initialize classes $taskObj = new Task(); $userObj = new User(); // Get user stats $stats = $userObj->getUserStats($user['id']); // Get assigned tasks $tasks = $taskObj->getTasks([ 'assigned_to' => $user['id'], 'status' => ['todo', 'in_progress', 'review'], 'limit' => 10 ]); // Get overdue tasks $overdueTasks = $taskObj->getTasks([ 'assigned_to' => $user['id'], 'overdue' => true, 'limit' => 5 ]); // Get recent activities $activityLog = new ActivityLog(); $recentActivities = $activityLog->getUserActivities($user['id'], 10); // Get notifications $notifications = $userObj->getNotifications($user['id'], true, 5); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Dashboard - <?php echo APP_NAME; ?></title> <!-- Bootstrap 5 CSS --> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Font Awesome --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <!-- Chart.js --> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <!-- Custom CSS --> <link rel="stylesheet" href="../assets/css/dashboard.css"> </head> <body> <div class="d-flex"> <!-- Sidebar --> <div class="sidebar bg-dark text-white"> <div class="sidebar-header p-4"> <a href="dashboard.php" class="text-white text-decoration-none"> <i class="fas fa-tasks fa-2x mb-3"></i> <h5 class="mb-0"><?php echo APP_NAME; ?></h5> <small>Task Management</small> </a> </div> <div class="user-info p-4 border-top border-secondary"> <div class="d-flex align-items-center"> <div class="avatar bg-primary rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 50px; height: 50px; font-size: 1.2rem; font-weight: bold;"> <?php echo getInitials($user['first_name'] . ' ' . $user['last_name']); ?> </div> <div> <h6 class="mb-0"><?php echo htmlspecialchars($user['first_name'] . ' ' . $user['last_name']); ?></h6> <small class="text-secondary"><?php echo ucfirst($user['role']); ?></small> </div> </div> </div> <nav class="nav flex-column p-3"> <a href="dashboard.php" class="nav-link text-white active"> <i class="fas fa-tachometer-alt me-3"></i>Dashboard </a> <a href="my_tasks.php" class="nav-link text-white"> <i class="fas fa-tasks me-3"></i>My Tasks <?php if ($stats['todo_tasks'] > 0): ?> <span class="badge bg-primary ms-auto"><?php echo $stats['todo_tasks']; ?></span> <?php endif; ?> </a> <a href="calendar.php" class="nav-link text-white"> <i class="fas fa-calendar-alt me-3"></i>Calendar </a> <a href="time_tracking.php" class="nav-link text-white"> <i class="fas fa-clock me-3"></i>Time Tracking </a> <a href="files.php" class="nav-link text-white"> <i class="fas fa-file-alt me-3"></i>Files </a> <a href="notifications.php" class="nav-link text-white"> <i class="fas fa-bell me-3"></i>Notifications <?php if (count($notifications) > 0): ?> <span class="badge bg-danger ms-auto"><?php echo count($notifications); ?></span> <?php endif; ?> </a> <a href="profile.php" class="nav-link text-white"> <i class="fas fa-user-circle me-3"></i>Profile </a> <a href="../logout.php" class="nav-link text-white mt-4"> <i class="fas fa-sign-out-alt me-3"></i>Logout </a> </nav> </div> <!-- Main Content --> <div class="main-content flex-grow-1 bg-light"> <header class="bg-white shadow-sm p-3"> <div class="d-flex justify-content-between align-items-center"> <h5 class="mb-0"> <i class="fas fa-tachometer-alt me-2 text-primary"></i> Dashboard </h5> <div class="d-flex align-items-center"> <!-- Notifications Dropdown --> <div class="dropdown me-3"> <button class="btn btn-light position-relative" type="button" data-bs-toggle="dropdown"> <i class="fas fa-bell"></i> <?php if (count($notifications) > 0): ?> <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger"> <?php echo count($notifications); ?> </span> <?php endif; ?> </button> <div class="dropdown-menu dropdown-menu-end notification-dropdown" style="width: 300px;"> <?php if (count($notifications) > 0): ?> <?php foreach ($notifications as $note): ?> <a class="dropdown-item" href="<?php echo $note['link']; ?>"> <small class="text-muted d-block"><?php echo timeAgo($note['created_at']); ?></small> <strong><?php echo htmlspecialchars($note['title']); ?></strong> <p class="mb-0 small text-muted"><?php echo htmlspecialchars($note['message']); ?></p> </a> <div class="dropdown-divider"></div> <?php endforeach; ?> <a class="dropdown-item text-center" href="notifications.php"> View all notifications </a> <?php else: ?> <span class="dropdown-item text-center text-muted"> No new notifications </span> <?php endif; ?> </div> </div> <!-- Date --> <span class="text-muted"> <i class="fas fa-calendar me-1"></i> <?php echo date('F j, Y'); ?> </span> </div> </div> </header> <div class="p-4"> <!-- Welcome Banner --> <div class="row mb-4"> <div class="col-12"> <div class="card bg-primary text-white"> <div class="card-body"> <div class="row align-items-center"> <div class="col-md-8"> <h4>Welcome back, <?php echo htmlspecialchars($user['first_name']); ?>!</h4> <p class="mb-0"> Here's your task summary for today. You have <strong><?php echo $stats['todo_tasks']; ?> tasks</strong> to do and <strong><?php echo $stats['in_progress_tasks']; ?> in progress</strong>. </p> </div> <div class="col-md-4 text-md-end"> <a href="my_tasks.php" class="btn btn-light"> <i class="fas fa-tasks me-2"></i>View All Tasks </a> </div> </div> </div> </div> </div> </div> <!-- Stats Cards --> <div class="row g-4 mb-4"> <div class="col-md-3"> <div class="card border-0 shadow-sm h-100"> <div class="card-body"> <div class="d-flex justify-content-between align-items-center"> <div> <h6 class="text-muted mb-2">To Do</h6> <h3 class="mb-0 text-primary"><?php echo $stats['todo_tasks']; ?></h3> </div> <div class="icon-circle bg-primary bg-opacity-10 text-primary"> <i class="fas fa-clipboard-list"></i> </div> </div> </div> </div> </div> <div class="col-md-3"> <div class="card border-0 shadow-sm h-100"> <div class="card-body"> <div class="d-flex justify-content-between align-items-center"> <div> <h6 class="text-muted mb-2">In Progress</h6> <h3 class="mb-0 text-warning"><?php echo $stats['in_progress_tasks']; ?></h3> </div> <div class="icon-circle bg-warning bg-opacity-10 text-warning"> <i class="fas fa-spinner"></i> </div> </div> </div> </div> </div> <div class="col-md-3"> <div class="card border-0 shadow-sm h-100"> <div class="card-body"> <div class="d-flex justify-content-between align-items-center"> <div> <h6 class="text-muted mb-2">Review</h6> <h3 class="mb-0 text-info"><?php echo $stats['review_tasks']; ?></h3> </div> <div class="icon-circle bg-info bg-opacity-10 text-info"> <i class="fas fa-eye"></i> </div> </div> </div> </div> </div> <div class="col-md-3"> <div class="card border-0 shadow-sm h-100"> <div class="card-body"> <div class="d-flex justify-content-between align-items-center"> <div> <h6 class="text-muted mb-2">Completed</h6> <h3 class="mb-0 text-success"><?php echo $stats['done_tasks']; ?></h3> </div> <div class="icon-circle bg-success bg-opacity-10 text-success"> <i class="fas fa-check-circle"></i> </div> </div> </div> </div> </div> </div> <div class="row"> <!-- Overdue Tasks Alert --> <?php if ($stats['overdue_tasks'] > 0): ?> <div class="col-12 mb-4"> <div class="alert alert-danger alert-dismissible fade show"> <i class="fas fa-exclamation-triangle me-2"></i> <strong>Overdue Tasks!</strong> You have <?php echo $stats['overdue_tasks']; ?> overdue task(s). <a href="my_tasks.php?filter=overdue" class="alert-link">View now</a> <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> </div> <?php endif; ?> <!-- Upcoming Deadlines --> <?php if ($stats['upcoming_deadlines'] > 0): ?> <div class="col-12 mb-4"> <div class="alert alert-warning alert-dismissible fade show"> <i class="fas fa-clock me-2"></i> <strong>Upcoming Deadlines!</strong> You have <?php echo $stats['upcoming_deadlines']; ?> task(s) due in the next 7 days. <a href="my_tasks.php?filter=upcoming" class="alert-link">View now</a> <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> </div> <?php endif; ?> </div> <div class="row"> <!-- My Tasks --> <div class="col-lg-8 mb-4"> <div class="card border-0 shadow-sm h-100"> <div class="card-header bg-white d-flex justify-content-between align-items-center"> <h6 class="mb-0"> <i class="fas fa-tasks me-2 text-primary"></i> My Tasks </h6> <a href="my_tasks.php" class="btn btn-sm btn-outline-primary"> View All </a> </div> <div class="card-body p-0"> <div class="list-group list-group-flush"> <?php if (count($tasks) > 0): ?> <?php foreach ($tasks as $task): ?> <div class="list-group-item"> <div class="d-flex justify-content-between align-items-center"> <div> <div class="d-flex align-items-center"> <div class="me-3"> <?php if ($task['priority'] == 'urgent'): ?> <span class="badge bg-danger">Urgent</span> <?php elseif ($task['priority'] == 'high'): ?> <span class="badge bg-warning">High</span> <?php elseif ($task['priority'] == 'medium'): ?> <span class="badge bg-info">Medium</span> <?php else: ?> <span class="badge bg-secondary">Low</span> <?php endif; ?> </div> <div> <h6 class="mb-1"> <a href="task_details.php?id=<?php echo $task['id']; ?>" class="text-decoration-none"> <?php echo htmlspecialchars($task['title']); ?> </a> </h6> <small class="text-muted"> <?php if ($task['project_name']): ?> <i class="fas fa-folder me-1"></i><?php echo htmlspecialchars($task['project_name']); ?> • <?php endif; ?> <i class="fas fa-calendar me-1"></i> Due: <?php echo $task['due_date'] ? formatDate($task['due_date']) : 'No deadline'; ?> <?php if (isOverdue($task['due_date']) && $task['status'] != 'done'): ?> <span class="badge bg-danger ms-2">Overdue</span> <?php endif; ?> </small> </div> </div> </div> <div> <?php echo getStatusBadge($task['status']); ?> </div> </div> </div> <?php endforeach; ?> <?php else: ?> <div class="text-center py-5"> <i class="fas fa-check-circle fa-3x text-success mb-3"></i> <p class="text-muted">No tasks assigned yet. Great job!</p> </div> <?php endif; ?> </div> </div> </div> </div> <!-- Recent Activity --> <div class="col-lg-4 mb-4"> <div class="card border-0 shadow-sm h-100"> <div class="card-header bg-white"> <h6 class="mb-0"> <i class="fas fa-history me-2 text-primary"></i> Recent Activity </h6> </div> <div class="card-body p-0"> <div class="list-group list-group-flush"> <?php if (count($recentActivities) > 0): ?> <?php foreach ($recentActivities as $activity): ?> <div class="list-group-item"> <small class="text-muted d-block"><?php echo timeAgo($activity['created_at']); ?></small> <p class="mb-0 small"><?php echo htmlspecialchars($activity['description']); ?></p> </div> <?php endforeach; ?> <?php else: ?> <div class="text-center py-4"> <p class="text-muted small mb-0">No recent activity</p> </div> <?php endif; ?> </div> </div> </div> </div> </div> <!-- Task Progress Chart --> <div class="row"> <div class="col-md-6 mb-4"> <div class="card border-0 shadow-sm"> <div class="card-header bg-white"> <h6 class="mb-0"> <i class="fas fa-chart-pie me-2 text-primary"></i> Task Status Distribution </h6> </div> <div class="card-body"> <canvas id="taskChart" style="height: 300px;"></canvas> </div> </div> </div> <div class="col-md-6 mb-4"> <div class="card border-0 shadow-sm"> <div class="card-header bg-white"> <h6 class="mb-0"> <i class="fas fa-clock me-2 text-primary"></i> Time Logged This Month </h6> </div> <div class="card-body text-center"> <h1 class="display-3 text-primary"><?php echo formatDuration($stats['time_logged_month']); ?></h1> <p class="text-muted">Total time logged in <?php echo date('F Y'); ?></p> </div> </div> </div> </div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script> <script> // Task Status Chart const ctx = document.getElementById('taskChart').getContext('2d'); new Chart(ctx, { type: 'doughnut', data: { labels: ['To Do', 'In Progress', 'Review', 'Done'], datasets: [{ data: [ <?php echo $stats['todo_tasks']; ?>, <?php echo $stats['in_progress_tasks']; ?>, <?php echo $stats['review_tasks']; ?>, <?php echo $stats['done_tasks']; ?> ], backgroundColor: ['#3498db', '#f39c12', '#9b59b6', '#2ecc71'], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } } }); </script> <style> .sidebar { width: 280px; min-height: 100vh; } .main-content { margin-left: 280px; } .nav-link { color: rgba(255,255,255,0.8) !important; padding: 12px 20px; border-radius: 5px; margin: 2px 10px; transition: all 0.3s; } .nav-link:hover, .nav-link.active { background: rgba(255,255,255,0.1) !important; color: white !important; } .nav-link i { width: 20px; } .icon-circle { width: 50px; height: 50px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; } .avatar { width: 50px; height: 50px; } .notification-dropdown { max-height: 400px; overflow-y: auto; } @media (max-width: 768px) { .sidebar { transform: translateX(-100%); position: fixed; z-index: 1000; } .main-content { margin-left: 0; } } </style> </body> </html>
Environment Configuration
File: .env
# Database Configuration DB_HOST=localhost DB_NAME=task_management_system DB_USER=root DB_PASS= # Application Configuration APP_NAME=Task Management System APP_URL=http://localhost/task-management-system APP_VERSION=1.0.0 DEBUG_MODE=true # Security SESSION_TIMEOUT=3600 BCRYPT_ROUNDS=12 # Upload Configuration MAX_FILE_SIZE=10485760 # 10MB in bytes ALLOWED_FILE_TYPES=jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,txt # Pagination ITEMS_PER_PAGE=20 # Date/Time TIMEZONE=UTC DATE_FORMAT=Y-m-d TIME_FORMAT=H:i # Company Settings COMPANY_NAME=Task Management System [email protected] COMPANY_PHONE=+1-555-123-4567 # Notification Settings ENABLE_NOTIFICATIONS=true
File: .gitignore
# Environment variables .env # Dependencies /vendor/ node_modules/ # IDE files .vscode/ .idea/ *.sublime-* # OS files .DS_Store Thumbs.db # Logs /logs/ *.log # Uploads /uploads/ !/uploads/.gitkeep # Composer composer.lock # Temp files *.tmp *.temp
File: composer.json
{
"name": "task-management-system/application",
"description": "Complete Task Management System for Teams",
"type": "project",
"require": {
"php": ">=7.4",
"ext-pdo": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-curl": "*",
"ext-gd": "*",
"phpmailer/phpmailer": "^6.8",
"tecnickcom/tcpdf": "^6.6"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"autoload": {
"psr-4": {
"TaskSystem\\": "src/"
}
},
"scripts": {
"test": "phpunit tests",
"post-install-cmd": [
"chmod -R 755 uploads/",
"chmod -R 755 logs/"
]
}
}
How to Use the Project (Step-by-Step Guide)
Prerequisites
- Web Server: XAMPP, WAMP, MAMP, or any PHP-enabled server (PHP 7.4+)
- Database: MySQL 5.7+ or MariaDB
- Composer: For dependency management
- Browser: Modern web browser (Chrome, Firefox, Edge, etc.)
Installation Steps
Step 1: Set Up Local Server
- Download and install XAMPP from https://www.apachefriends.org/
- Launch XAMPP Control Panel
- Start Apache and MySQL services
Step 2: Install Composer Dependencies
- Download and install Composer from https://getcomposer.org/
- Navigate to your project directory in terminal
- Run:
composer install
Step 3: Create Project Folder
- Navigate to
C:\xampp\htdocs\(Windows) or/Applications/XAMPP/htdocs/(Mac) - Create a new folder named
task-management-system - Create all the folders and files as shown in the Project File Structure
Step 4: Set Up Database
- Open browser and go to
http://localhost/phpmyadmin - Click on "New" to create a new database
- Name the database
task_management_systemand selectutf8_general_ci - Click on "Import" tab
- Click "Choose File" and select the
database.sqlfile from thesqlfolder - Click "Go" to import the database structure and sample data
Step 5: Configure Environment
- Rename
.env.exampleto.envin the project root - Update database credentials if different from default:
DB_HOST=localhost DB_NAME=task_management_system DB_USER=root DB_PASS=
- Update application URL:
APP_URL=http://localhost/task-management-system
- Configure email settings if using email notifications
Step 6: Set Folder Permissions
Create the following folders and ensure they are writable:
uploads/avatars/uploads/tasks/uploads/projects/logs/
On Windows, right-click folders → Properties → Security → give Write permission to Users
On Mac/Linux, run: chmod -R 755 uploads/ logs/
Step 7: Create Admin Password Hash
- Go to
http://localhost/task-management-system/register.php - Register a test user (e.g., email:
[email protected], password:Admin@123) - Open phpMyAdmin, go to the
userstable - Find the user and change role to 'admin'
- Set
email_verifiedto 1
Step 8: Test the Installation
- Open browser and go to
http://localhost/task-management-system/ - You should see the login page
- Test different user types: Admin Login:
- Email:
[email protected] - Password:
Admin@123Manager Registration: - Register a new user
- Admin can promote to manager in admin panel Member Registration:
- Register as a new member
- Login and view dashboard
System Walkthrough
For Members:
- Dashboard - View task summary, assigned tasks, and recent activity
- My Tasks - View all assigned tasks with filters
- Task Details - View task information, add comments, update status
- Calendar - View tasks on calendar by due date
- Time Tracking - Log time spent on tasks
- Files - Access shared files
- Notifications - View system notifications
- Profile - Update personal information and preferences
For Managers:
- Project Dashboard - Overview of all managed projects
- Project Management - Create and manage projects
- Task Management - Create, assign, and track tasks
- Team Management - Manage team members and assignments
- Gantt Chart - Visual timeline of project tasks
- Reports - Generate project status reports
- Budget Tracking - Monitor project budgets
For Admins:
- System Dashboard - Overview of system statistics
- User Management - Create, edit, and manage users
- Team Management - Create teams and assign members
- Project Oversight - View all projects
- System Settings - Configure global parameters
- Audit Logs - Track all system activities
- Backup - Manage database backups
Key Features Explained
Task Management
- Create tasks with title, description, priority, and due date
- Assign tasks to team members
- Set task dependencies (e.g., Task B depends on Task A)
- Track task status (To Do, In Progress, Review, Done)
- Add comments and mentions
- Attach files to tasks
- Log time spent on tasks
- View task history and changes
Project Management
- Create projects with timelines and budgets
- Set project milestones
- Track project progress with Gantt charts
- Manage project team members
- Monitor project budgets and expenses
- Generate project reports
Collaboration Features
- Comment on tasks with @mentions
- Share files within tasks
- Receive notifications for assignments and updates
- View activity logs
- Team calendar for deadline visibility
Cron Jobs Setup
Create the following cron jobs for automated tasks:
# Send deadline reminders (run daily at 8 AM) 0 8 * * * php /path/to/project/cron/send_reminders.php # Update task status for overdue tasks (run daily at midnight) 0 0 * * * php /path/to/project/cron/update_task_status.php # Clean up old logs (run weekly on Sunday at 1 AM) 0 1 * * 0 php /path/to/project/cron/clean_logs.php
Troubleshooting
Common Issues and Solutions
- Database Connection Error
- Check if MySQL is running
- Verify database credentials in
.env - Ensure database
task_management_systemexists
- 404 Page Not Found
- Check file paths and folder structure
- Verify
APP_URLin.env - Ensure
.htaccessis properly configured (if using Apache)
- Email Not Sending
- Configure SMTP settings in PHPMailer
- Check spam folder
- Verify PHP mail() function is enabled
- File Upload Issues
- Check folder permissions
- Verify
MAX_FILE_SIZEin.env - Check allowed file types
- Session/Login Issues
- Clear browser cookies and cache
- Check
SESSION_TIMEOUTin.env - Verify session save path is writable
- Task Dependency Issues
- Check for circular dependencies
- Verify task IDs exist
- Ensure proper dependency type
Conclusion
The Task Management System is a comprehensive, feature-rich platform for managing tasks, projects, and team collaboration. With its intuitive interface, powerful features, and flexible role-based access control, it provides everything needed for efficient task and project management.
This application demonstrates:
- Secure user authentication with role-based access
- Complete CRUD operations for tasks, projects, and users
- Task dependencies and subtask management
- Gantt charts for project timeline visualization
- Comment system with mentions
- File attachments for tasks
- Time tracking for tasks
- Notification system for updates and reminders
- Activity logging for audit trails
- Responsive design for all devices
- Modular code structure following OOP principles
- Database design with proper relationships
- Error handling and logging
- Security best practices throughout
The system is built to be scalable, allowing easy addition of new features such as:
- API integration with external tools
- Mobile apps for iOS and Android
- Advanced reporting and analytics
- Resource allocation and workload management
- Integration with calendar applications
- Export to various formats (PDF, Excel)
- Multi-language support
With proper deployment, regular backups, and security updates, this system can serve as a reliable platform for teams and organizations of any size to manage their tasks and projects efficiently.