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.