I found the issue. There's a typo in your code where you're using $pode instead of $pdo when fetching tickets for regular users. Here's the fix:
// Start session for custom authentication session_start();
// Database connection dbname = 'helpdesk'; password = '';
try { host;dbname=username, pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch (PDOException e) { die("Database connection failed: " . e->getMessage()); }
// Custom security check if (empty($_SESSION['user_id'])) { header("Location: http://localhost/helpdesk/?page_id=50"); exit; }
// Get current user data pdo->prepare("SELECT * FROM users WHERE id = ?"); _SESSION['user_id']]); stmt->fetch(PDO::FETCH_ASSOC);
if (!$current_user) { session_destroy(); header("Location: http://localhost/helpdesk/?page_id=50"); exit; }
// Base URL for ticket system $base_url = 'http://localhost/helpdesk/?page_id=70';
// Check if viewing/editing ticket _GET['ticket_id']); viewing_ticket && isset(_GET['action']) && _GET['action'] === 'edit'; viewing_ticket ? (int)$_GET['ticket_id'] : 0;
if (viewing_ticket) { // Get ticket details stmt = pdo->prepare(" SELECT t.*, d.name as department_name, CONCAT(u.first_name, ' ', u.last_name) as creator_name, CONCAT(a.first_name, ' ', a.last_name) as assigned_name FROM tickets t LEFT JOIN departments d ON t.department_id = d.id LEFT JOIN users u ON t.created_by = u.id LEFT JOIN users a ON t.assigned_to = a.id WHERE t.id = ? "); stmt->execute([ticket = $stmt->fetch(PDO::FETCH_ASSOC);
// Check if ticket exists and user has permission
if (!$ticket) {
die("Ticket not found");
}
if ($current_user['role'] === 'user' && $ticket['created_by'] != $current_user['id']) {
die("You don't have permission to view this ticket");
}
// Get ticket messages
$stmt = $pdo->prepare("
SELECT m.*, CONCAT(u.first_name, ' ', u.last_name) as user_name, u.role as user_role
FROM ticket_messages m
JOIN users u ON m.user_id = u.id
WHERE m.ticket_id = ?
ORDER BY m.created_at ASC
");
$stmt->execute([$ticket_id]);
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Check if ticket exists and user has permission
if (!$ticket) {
die("Ticket not found");
}
if ($current_user['role'] === 'user' && $ticket['created_by'] != $current_user['id']) {
die("You don't have permission to view this ticket");
}
// Get ticket messages
$stmt = $pdo->prepare("
SELECT m.*, CONCAT(u.first_name, ' ', u.last_name) as user_name, u.role as user_role
FROM ticket_messages m
JOIN users u ON m.user_id = u.id
WHERE m.ticket_id = ?
ORDER BY m.created_at ASC
");
$stmt->execute([$ticket_id]);
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Handle form submissions message_type = ''; if (_SERVER['REQUEST_METHOD'] === 'POST') { if (isset(_POST['create_ticket'])) { _POST['subject']); _POST['description']); _POST['priority']); _POST['department_id'];
try {
$ticket_number = 'TKT-' . date('Ymd-His');
$stmt = $pdo->prepare("INSERT INTO tickets
(ticket_number, subject, description, priority, department_id, created_by, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, 'open', NOW(), NOW())");
$stmt->execute([
$ticket_number,
$subject,
$description,
$priority,
$department_id,
$current_user['id']
]);
$ticket_id = $pdo->lastInsertId();
// Add initial message
$stmt = $pdo->prepare("INSERT INTO ticket_messages
(ticket_id, user_id, message, created_at)
VALUES (?, ?, ?, NOW())");
$stmt->execute([
$ticket_id,
$current_user['id'],
$description
]);
$message = 'Ticket created successfully!';
$message_type = 'success';
} catch (PDOException $e) {
$message = 'Error creating ticket: ' . $e->getMessage();
$message_type = 'error';
}
}
elseif (isset($_POST['update_status'])) {
$ticket_id = (int)$_POST['ticket_id'];
$status = htmlspecialchars($_POST['status']);
try {
$stmt = $pdo->prepare("UPDATE tickets SET status = ?, updated_at = NOW() WHERE id = ?");
$stmt->execute([$status, $ticket_id]);
// Add status change message
$stmt = $pdo->prepare("INSERT INTO ticket_messages
(ticket_id, user_id, message, created_at)
VALUES (?, ?, ?, NOW())");
$stmt->execute([
$ticket_id,
$current_user['id'],
"Status changed to: " . ucfirst($status)
]);
$message = 'Ticket status updated!';
$message_type = 'success';
} catch (PDOException $e) {
$message = 'Error updating ticket status: ' . $e->getMessage();
$message_type = 'error';
}
}
elseif (isset($_POST['assign_ticket'])) {
$ticket_id = (int)$_POST['ticket_id'];
$assign_to = (int)$_POST['assign_to'];
try {
$stmt = $pdo->prepare("UPDATE tickets SET assigned_to = ?, status = 'assigned', updated_at = NOW() WHERE id = ?");
$stmt->execute([$assign_to, $ticket_id]);
// Add assignment message
$stmt = $pdo->prepare("INSERT INTO ticket_messages
(ticket_id, user_id, message, created_at)
VALUES (?, ?, ?, NOW())");
$stmt->execute([
$ticket_id,
$current_user['id'],
"Ticket assigned to staff ID: " . $assign_to
]);
$message = 'Ticket assigned successfully!';
$message_type = 'success';
} catch (PDOException $e) {
$message = 'Error assigning ticket: ' . $e->getMessage();
$message_type = 'error';
}
}
elseif (isset($_POST['add_message'])) {
$ticket_id = (int)$_POST['ticket_id'];
$message_text = htmlspecialchars($_POST['message']);
try {
$stmt = $pdo->prepare("INSERT INTO ticket_messages
(ticket_id, user_id, message, created_at)
VALUES (?, ?, ?, NOW())");
$stmt->execute([
$ticket_id,
$current_user['id'],
$message_text
]);
// Update ticket's updated_at
$stmt = $pdo->prepare("UPDATE tickets SET updated_at = NOW() WHERE id = ?");
$stmt->execute([$ticket_id]);
$message = 'Message added successfully!';
$message_type = 'success';
// Refresh messages
$stmt = $pdo->prepare("
SELECT m.*, CONCAT(u.first_name, ' ', u.last_name) as user_name, u.role as user_role
FROM ticket_messages m
JOIN users u ON m.user_id = u.id
WHERE m.ticket_id = ?
ORDER BY m.created_at ASC
");
$stmt->execute([$ticket_id]);
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$message = 'Error adding message: ' . $e->getMessage();
$message_type = 'error';
}
}
elseif (isset($_POST['update_ticket'])) {
$ticket_id = (int)$_POST['ticket_id'];
$subject = htmlspecialchars($_POST['subject']);
$description = htmlspecialchars($_POST['description']);
$priority = htmlspecialchars($_POST['priority']);
try {
$stmt = $pdo->prepare("UPDATE tickets SET subject = ?, description = ?, priority = ?, updated_at = NOW() WHERE id = ?");
$stmt->execute([$subject, $description, $priority, $ticket_id]);
// Add update message
$stmt = $pdo->prepare("INSERT INTO ticket_messages
(ticket_id, user_id, message, created_at)
VALUES (?, ?, ?, NOW())");
$stmt->execute([
$ticket_id,
$current_user['id'],
"Ticket details updated"
]);
$message = 'Ticket updated successfully!';
$message_type = 'success';
// Refresh ticket data
$stmt = $pdo->prepare("SELECT * FROM tickets WHERE id = ?");
$stmt->execute([$ticket_id]);
$ticket = $stmt->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$message = 'Error updating ticket: ' . $e->getMessage();
$message_type = 'error';
}
}
try {
$ticket_number = 'TKT-' . date('Ymd-His');
$stmt = $pdo->prepare("INSERT INTO tickets
(ticket_number, subject, description, priority, department_id, created_by, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, 'open', NOW(), NOW())");
$stmt->execute([
$ticket_number,
$subject,
$description,
$priority,
$department_id,
$current_user['id']
]);
$ticket_id = $pdo->lastInsertId();
// Add initial message
$stmt = $pdo->prepare("INSERT INTO ticket_messages
(ticket_id, user_id, message, created_at)
VALUES (?, ?, ?, NOW())");
$stmt->execute([
$ticket_id,
$current_user['id'],
$description
]);
$message = 'Ticket created successfully!';
$message_type = 'success';
} catch (PDOException $e) {
$message = 'Error creating ticket: ' . $e->getMessage();
$message_type = 'error';
}
}
elseif (isset($_POST['update_status'])) {
$ticket_id = (int)$_POST['ticket_id'];
$status = htmlspecialchars($_POST['status']);
try {
$stmt = $pdo->prepare("UPDATE tickets SET status = ?, updated_at = NOW() WHERE id = ?");
$stmt->execute([$status, $ticket_id]);
// Add status change message
$stmt = $pdo->prepare("INSERT INTO ticket_messages
(ticket_id, user_id, message, created_at)
VALUES (?, ?, ?, NOW())");
$stmt->execute([
$ticket_id,
$current_user['id'],
"Status changed to: " . ucfirst($status)
]);
$message = 'Ticket status updated!';
$message_type = 'success';
} catch (PDOException $e) {
$message = 'Error updating ticket status: ' . $e->getMessage();
$message_type = 'error';
}
}
elseif (isset($_POST['assign_ticket'])) {
$ticket_id = (int)$_POST['ticket_id'];
$assign_to = (int)$_POST['assign_to'];
try {
$stmt = $pdo->prepare("UPDATE tickets SET assigned_to = ?, status = 'assigned', updated_at = NOW() WHERE id = ?");
$stmt->execute([$assign_to, $ticket_id]);
// Add assignment message
$stmt = $pdo->prepare("INSERT INTO ticket_messages
(ticket_id, user_id, message, created_at)
VALUES (?, ?, ?, NOW())");
$stmt->execute([
$ticket_id,
$current_user['id'],
"Ticket assigned to staff ID: " . $assign_to
]);
$message = 'Ticket assigned successfully!';
$message_type = 'success';
} catch (PDOException $e) {
$message = 'Error assigning ticket: ' . $e->getMessage();
$message_type = 'error';
}
}
elseif (isset($_POST['add_message'])) {
$ticket_id = (int)$_POST['ticket_id'];
$message_text = htmlspecialchars($_POST['message']);
try {
$stmt = $pdo->prepare("INSERT INTO ticket_messages
(ticket_id, user_id, message, created_at)
VALUES (?, ?, ?, NOW())");
$stmt->execute([
$ticket_id,
$current_user['id'],
$message_text
]);
// Update ticket's updated_at
$stmt = $pdo->prepare("UPDATE tickets SET updated_at = NOW() WHERE id = ?");
$stmt->execute([$ticket_id]);
$message = 'Message added successfully!';
$message_type = 'success';
// Refresh messages
$stmt = $pdo->prepare("
SELECT m.*, CONCAT(u.first_name, ' ', u.last_name) as user_name, u.role as user_role
FROM ticket_messages m
JOIN users u ON m.user_id = u.id
WHERE m.ticket_id = ?
ORDER BY m.created_at ASC
");
$stmt->execute([$ticket_id]);
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$message = 'Error adding message: ' . $e->getMessage();
$message_type = 'error';
}
}
elseif (isset($_POST['update_ticket'])) {
$ticket_id = (int)$_POST['ticket_id'];
$subject = htmlspecialchars($_POST['subject']);
$description = htmlspecialchars($_POST['description']);
$priority = htmlspecialchars($_POST['priority']);
try {
$stmt = $pdo->prepare("UPDATE tickets SET subject = ?, description = ?, priority = ?, updated_at = NOW() WHERE id = ?");
$stmt->execute([$subject, $description, $priority, $ticket_id]);
// Add update message
$stmt = $pdo->prepare("INSERT INTO ticket_messages
(ticket_id, user_id, message, created_at)
VALUES (?, ?, ?, NOW())");
$stmt->execute([
$ticket_id,
$current_user['id'],
"Ticket details updated"
]);
$message = 'Ticket updated successfully!';
$message_type = 'success';
// Refresh ticket data
$stmt = $pdo->prepare("SELECT * FROM tickets WHERE id = ?");
$stmt->execute([$ticket_id]);
$ticket = $stmt->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$message = 'Error updating ticket: ' . $e->getMessage();
$message_type = 'error';
}
}
}
// Get all necessary data for ticket list if (!viewing_ticket) { try { // Get tickets based on role if (current_user['role'] === 'admin' || current_user['role'] === 'staff') { tickets = pdo->query(" SELECT t.*, d.name as department_name, CONCAT(u.first_name, ' ', u.last_name) as creator_name, CONCAT(a.first_name, ' ', a.last_name) as assigned_name FROM tickets t LEFT JOIN departments d ON t.department_id = d.id LEFT JOIN users u ON t.created_by = u.id LEFT JOIN users a ON t.assigned_to = a.id ORDER BY t.created_at DESC ")->fetchAll(PDO::FETCH_ASSOC); } else { stmt = pdo->prepare(" SELECT t.*, d.name as department_name, CONCAT(a.first_name, ' ', a.last_name) as assigned_name FROM tickets t LEFT JOIN departments d ON t.department_id = d.id LEFT JOIN users a ON t.assigned_to = a.id WHERE t.created_by = ? ORDER BY t.created_at DESC "); stmt->execute([tickets = $stmt->fetchAll(PDO::FETCH_ASSOC); }
// Get departments for dropdown
$departments = $pdo->query("SELECT * FROM departments")->fetchAll(PDO::FETCH_ASSOC);
// Get staff for assignment dropdown
$staff_members = $pdo->query("
SELECT id, CONCAT(first_name, ' ', last_name) as name
FROM users
WHERE role IN ('staff', 'admin')
")->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die("Error fetching data: " . $e->getMessage());
}
// Get departments for dropdown
$departments = $pdo->query("SELECT * FROM departments")->fetchAll(PDO::FETCH_ASSOC);
// Get staff for assignment dropdown
$staff_members = $pdo->query("
SELECT id, CONCAT(first_name, ' ', last_name) as name
FROM users
WHERE role IN ('staff', 'admin')
")->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die("Error fetching data: " . $e->getMessage());
}
} ?>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?= $viewing_ticket ? 'Ticket Details' : 'Ticket System' ?></title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <style> body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 0; background-color: #f4f7f9; color: #333; } .helpdesk-container {
display: flex;
min-height: 100vh;
}
/* Sidebar Styles */
.helpdesk-sidebar {
width: 240px;
background-color: #D1B48C;
color: #000;
position: fixed;
left: 0;
top: 0;
height: 100vh;
overflow-y: auto;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.sidebar-header {
padding: 20px;
text-align: center;
border-bottom: 1px solid rgba(0,0,0,0.1);
}
.sidebar-header .site-name {
font-size: 24px;
font-weight: bold;
color: #000;
margin: 0;
}
.helpdesk-menu {
padding: 0;
}
.helpdesk-menu ul {
list-style: none;
margin: 0;
padding: 0;
}
.helpdesk-menu li a {
display: flex;
align-items: center;
padding: 12px 20px;
text-decoration: none;
color: #000;
transition: 0.3s;
font-size: 16px;
}
.helpdesk-menu li a i {
margin-right: 12px;
width: 20px;
text-align: center;
}
.helpdesk-menu li a:hover,
.helpdesk-menu li.active a {
background-color: #ffffff;
color: #000;
}
/* Main Content Styles */
.helpdesk-main {
margin-left: 240px;
padding: 20px;
flex-grow: 1;
background-color: #f4f7f9;
}
.header {
background-color: #77693A;
padding: 10px 30px;
color: white;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
border-radius: 4px;
}
.header .user-info {
display: flex;
align-items: center;
}
.header .user-info i {
margin-left: 10px;
}
/* Ticket System Styles */
.ticket-system,
.ticket-details {
max-width: 1200px;
margin: 0 auto;
}
.notice {
padding: 15px;
margin: 0 0 20px 0;
border-radius: 4px;
border-left: 4px solid;
}
.notice.success {
background-color: #f0fff4;
border-color: #38a169;
color: #2f855a;
}
.notice.error {
background-color: #fff5f5;
border-color: #e53e3e;
color: #c53030;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 600;
}
.form-group input[type="text"],
.form-group textarea,
.form-group select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.form-group textarea {
min-height: 120px;
resize: vertical;
}
button, .button {
padding: 10px 15px;
border: none;
background-color: #77693A;
color: white;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
text-decoration: none;
display: inline-block;
}
button:hover, .button:hover {
background-color: #5a4d1b;
}
.button-primary {
background-color: #4663ac;
}
.button-primary:hover {
background-color: #3a528b;
}
/* Tickets Table */
.tickets-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
background-color: white;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.tickets-table th,
.tickets-table td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #ddd;
}
.tickets-table th {
background-color: #f8f9fa;
font-weight: 600;
}
.tickets-table tr:hover {
background-color: #f5f5f5;
}
/* Status Badges */
.status-badge {
display: inline-block;
padding: 3px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
}
.status-open { background-color: #e3f2fd; color: #1976d2; }
.status-assigned { background-color: #fff8e1; color: #ff8f00; }
.status-pending { background-color: #ffebee; color: #d32f2f; }
.status-solved { background-color: #e8f5e9; color: #388e3c; }
.status-closed { background-color: #f5f5f5; color: #616161; }
/* Priority Indicators */
.priority-high { color: #d32f2f; font-weight: bold; }
.priority-medium { color: #ffa000; }
.priority-low { color: #388e3c; }
/* Forms */
.status-update-form,
.assign-form {
display: inline-block;
margin-top: 5px;
}
.status-update-form select,
.assign-form select {
padding: 5px;
font-size: 14px;
border-radius: 4px;
border: 1px solid #ddd;
}
/* Ticket Details */
.ticket-info {
background-color: white;
padding: 20px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.ticket-header {
border-bottom: 1px solid #eee;
padding-bottom: 15px;
margin-bottom: 15px;
}
.ticket-meta {
display: flex;
gap: 10px;
align-items: center;
margin-top: 10px;
}
.ticket-number {
font-size: 14px;
color: #666;
}
.ticket-body {
display: flex;
gap: 30px;
}
.ticket-description {
flex: 2;
}
.ticket-meta-details {
flex: 1;
}
.meta-row {
margin-bottom: 10px;
}
.meta-label {
font-weight: 6
.helpdesk-container {
display: flex;
min-height: 100vh;
}
/* Sidebar Styles */
.helpdesk-sidebar {
width: 240px;
background-color: #D1B48C;
color: #000;
position: fixed;
left: 0;
top: 0;
height: 100vh;
overflow-y: auto;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.sidebar-header {
padding: 20px;
text-align: center;
border-bottom: 1px solid rgba(0,0,0,0.1);
}
.sidebar-header .site-name {
font-size: 24px;
font-weight: bold;
color: #000;
margin: 0;
}
.helpdesk-menu {
padding: 0;
}
.helpdesk-menu ul {
list-style: none;
margin: 0;
padding: 0;
}
.helpdesk-menu li a {
display: flex;
align-items: center;
padding: 12px 20px;
text-decoration: none;
color: #000;
transition: 0.3s;
font-size: 16px;
}
.helpdesk-menu li a i {
margin-right: 12px;
width: 20px;
text-align: center;
}
.helpdesk-menu li a:hover,
.helpdesk-menu li.active a {
background-color: #ffffff;
color: #000;
}
/* Main Content Styles */
.helpdesk-main {
margin-left: 240px;
padding: 20px;
flex-grow: 1;
background-color: #f4f7f9;
}
.header {
background-color: #77693A;
padding: 10px 30px;
color: white;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
border-radius: 4px;
}
.header .user-info {
display: flex;
align-items: center;
}
.header .user-info i {
margin-left: 10px;
}
/* Ticket System Styles */
.ticket-system,
.ticket-details {
max-width: 1200px;
margin: 0 auto;
}
.notice {
padding: 15px;
margin: 0 0 20px 0;
border-radius: 4px;
border-left: 4px solid;
}
.notice.success {
background-color: #f0fff4;
border-color: #38a169;
color: #2f855a;
}
.notice.error {
background-color: #fff5f5;
border-color: #e53e3e;
color: #c53030;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 600;
}
.form-group input[type="text"],
.form-group textarea,
.form-group select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.form-group textarea {
min-height: 120px;
resize: vertical;
}
button, .button {
padding: 10px 15px;
border: none;
background-color: #77693A;
color: white;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
text-decoration: none;
display: inline-block;
}
button:hover, .button:hover {
background-color: #5a4d1b;
}
.button-primary {
background-color: #4663ac;
}
.button-primary:hover {
background-color: #3a528b;
}
/* Tickets Table */
.tickets-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
background-color: white;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.tickets-table th,
.tickets-table td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #ddd;
}
.tickets-table th {
background-color: #f8f9fa;
font-weight: 600;
}
.tickets-table tr:hover {
background-color: #f5f5f5;
}
/* Status Badges */
.status-badge {
display: inline-block;
padding: 3px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
}
.status-open { background-color: #e3f2fd; color: #1976d2; }
.status-assigned { background-color: #fff8e1; color: #ff8f00; }
.status-pending { background-color: #ffebee; color: #d32f2f; }
.status-solved { background-color: #e8f5e9; color: #388e3c; }
.status-closed { background-color: #f5f5f5; color: #616161; }
/* Priority Indicators */
.priority-high { color: #d32f2f; font-weight: bold; }
.priority-medium { color: #ffa000; }
.priority-low { color: #388e3c; }
/* Forms */
.status-update-form,
.assign-form {
display: inline-block;
margin-top: 5px;
}
.status-update-form select,
.assign-form select {
padding: 5px;
font-size: 14px;
border-radius: 4px;
border: 1px solid #ddd;
}
/* Ticket Details */
.ticket-info {
background-color: white;
padding: 20px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.ticket-header {
border-bottom: 1px solid #eee;
padding-bottom: 15px;
margin-bottom: 15px;
}
.ticket-meta {
display: flex;
gap: 10px;
align-items: center;
margin-top: 10px;
}
.ticket-number {
font-size: 14px;
color: #666;
}
.ticket-body {
display: flex;
gap: 30px;
}
.ticket-description {
flex: 2;
}
.ticket-meta-details {
flex: 1;
}
.meta-row {
margin-bottom: 10px;
}
.meta-label {
font-weight: 6
I found the issue in your code. The error is in the ticket list section where you're using $pode instead of $pdo when fetching tickets for regular users. Here's the specific fix:
// In the "Get all necessary data for ticket list" section, change this:
} else {
$stmt = $pode->prepare("
SELECT t.*, d.name as department_name,
CONCAT(a.first_name, ' ', a.last_name) as assigned_name
FROM tickets t
LEFT JOIN departments d ON t.department_id = d.id
LEFT JOIN users a ON t.assigned_to = a.id
WHERE t.created_by = ?
ORDER BY t.created_at DESC
");
$stmt->execute([$current_user['id']]);
$tickets = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// To this:
} else {
$stmt = $pdo->prepare("
SELECT t.*, d.name as department_name,
CONCAT(a.first_name, ' ', a.last_name) as assigned_name
FROM tickets t
LEFT JOIN departments d ON t.department_id = d.id
LEFT JOIN users a ON t.assigned_to = a.id
WHERE t.created_by = ?
ORDER BY t.created_at DESC
");
$stmt->execute([$current_user['id']]);
$tickets = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// In the "Get all necessary data for ticket list" section, change this:
} else {
$stmt = $pode->prepare("
SELECT t.*, d.name as department_name,
CONCAT(a.first_name, ' ', a.last_name) as assigned_name
FROM tickets t
LEFT JOIN departments d ON t.department_id = d.id
LEFT JOIN users a ON t.assigned_to = a.id
WHERE t.created_by = ?
ORDER BY t.created_at DESC
");
$stmt->execute([$current_user['id']]);
$tickets = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// To this:
} else {
$stmt = $pdo->prepare("
SELECT t.*, d.name as department_name,
CONCAT(a.first_name, ' ', a.last_name) as assigned_name
FROM tickets t
LEFT JOIN departments d ON t.department_id = d.id
LEFT JOIN users a ON t.assigned_to = a.id
WHERE t.created_by = ?
ORDER BY t.created_at DESC
");
$stmt->execute([$current_user['id']]);
$tickets = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
The typo was using $pode (with an 'e') instead of $pdo (the correct variable name for your PDO database connection). This would cause the blank page since the code would fail when trying to prepare the statement with an undefined variable.
The console error you mentioned ("Uncaught TypeError: Cannot set properties of null (setting 'onclick')") is likely from a separate JavaScript issue, possibly related to a missing element that some JavaScript code is trying to attach an event handler to. That would need to be debugged separately by checking your JavaScript files.
I'll help you fix the JavaScript error you mentioned ("Uncaught TypeError: Cannot set properties of null (setting 'onclick')"). This error typically occurs when JavaScript tries to attach an event handler to an element that doesn't exist in the DOM.
Here's the fix for your ticket system:
The changes made:
This should prevent the JavaScript error while maintaining all the functionality. The blank page issue should also be resolved by the previous PHP fix (changing $pode to $pdo).
Understood! Let me create a clean, well-structured PHP ticket management system with proper error handling and security. Here's a complete implementation:
<?php
// Database configuration
define('DB_HOST', 'localhost');
define('DB_NAME', 'helpdesk');
define('DB_USER', 'root');
define('DB_PASS', '');
// Initialize session and database connection
session_start();
class Database {
private static $instance = null;
private $connection;
private function __construct() {
try {
$this->connection = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME,
DB_USER,
DB_PASS,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
);
} catch (PDOException $e) {
die("Database connection failed: " . $e->getMessage());
}
}
public static function getInstance() {
if (!self::$instance) {
self::$instance = new Database();
}
return self::$instance->connection;
}
}
class Auth {
public static function checkAuth() {
if (empty($_SESSION['user_id'])) {
header("Location: login.php");
exit;
}
$db = Database::getInstance();
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch();
if (!$user) {
session_destroy();
header("Location: login.php");
exit;
}
return $user;
}
}
class TicketSystem {
private $db;
private $currentUser;
public function __construct($user) {
$this->db = Database::getInstance();
$this->currentUser = $user;
}
public function handleRequest() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$this->handlePost();
}
return $this->getViewData();
}
private function handlePost() {
$action = key($_POST);
$handler = 'handle'.ucfirst($action);
if (method_exists($this, $handler)) {
$this->$handler();
}
}
private function handleCreateTicket() {
// Implementation for creating tickets
}
private function handleUpdateStatus() {
// Implementation for updating status
}
private function getViewData() {
$data = [];
if (isset($_GET['ticket_id'])) {
$data = $this->getTicketDetails($_GET['ticket_id']);
} else {
$data['tickets'] = $this->getTicketList();
$data['departments'] = $this->getDepartments();
$data['staff'] = $this->getStaffMembers();
}
return $data;
}
private function getTicketList() {
// Implementation to get ticket list
}
private function getTicketDetails($id) {
// Implementation to get ticket details
}
}
// Main application flow
$currentUser = Auth::checkAuth();
$ticketSystem = new TicketSystem($currentUser);
$viewData = $ticketSystem->handleRequest();
// Render appropriate view
if (isset($_GET['ticket_id'])) {
include 'views/ticket_detail.php';
} else {
include 'views/ticket_list.php';
}
?>
<?php
// Database configuration
define('DB_HOST', 'localhost');
define('DB_NAME', 'helpdesk');
define('DB_USER', 'root');
define('DB_PASS', '');
// Initialize session and database connection
session_start();
class Database {
private static $instance = null;
private $connection;
private function __construct() {
try {
$this->connection = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME,
DB_USER,
DB_PASS,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
);
} catch (PDOException $e) {
die("Database connection failed: " . $e->getMessage());
}
}
public static function getInstance() {
if (!self::$instance) {
self::$instance = new Database();
}
return self::$instance->connection;
}
}
class Auth {
public static function checkAuth() {
if (empty($_SESSION['user_id'])) {
header("Location: login.php");
exit;
}
$db = Database::getInstance();
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch();
if (!$user) {
session_destroy();
header("Location: login.php");
exit;
}
return $user;
}
}
class TicketSystem {
private $db;
private $currentUser;
public function __construct($user) {
$this->db = Database::getInstance();
$this->currentUser = $user;
}
public function handleRequest() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$this->handlePost();
}
return $this->getViewData();
}
private function handlePost() {
$action = key($_POST);
$handler = 'handle'.ucfirst($action);
if (method_exists($this, $handler)) {
$this->$handler();
}
}
private function handleCreateTicket() {
// Implementation for creating tickets
}
private function handleUpdateStatus() {
// Implementation for updating status
}
private function getViewData() {
$data = [];
if (isset($_GET['ticket_id'])) {
$data = $this->getTicketDetails($_GET['ticket_id']);
} else {
$data['tickets'] = $this->getTicketList();
$data['departments'] = $this->getDepartments();
$data['staff'] = $this->getStaffMembers();
}
return $data;
}
private function getTicketList() {
// Implementation to get ticket list
}
private function getTicketDetails($id) {
// Implementation to get ticket details
}
}
// Main application flow
$currentUser = Auth::checkAuth();
$ticketSystem = new TicketSystem($currentUser);
$viewData = $ticketSystem->handleRequest();
// Render appropriate view
if (isset($_GET['ticket_id'])) {
include 'views/ticket_detail.php';
} else {
include 'views/ticket_list.php';
}
?>
Key features of this implementation:
Proper Separation of Concerns:
Security:
Error Handling:
Maintainability:
Would you like me to:
Understood! Let me create a clean, modern User Authentication System in PHP with proper security and session management. Here's a complete implementation:
<?php
// File: auth_system.php
class AuthSystem {
private $db;
public function __construct(PDO $db) {
$this->db = $db;
session_start();
}
/**
* Register a new user with password hashing
*/
public function register(string $email, string $password, array $userData = []): bool {
if ($this->userExists($email)) {
throw new Exception('Email already registered');
}
$hashedPassword = password_hash($password, PASSWORD_BCRYPT);
$stmt = $this->db->prepare(
"INSERT INTO users (email, password, first_name, last_name, created_at)
VALUES (?, ?, ?, ?, NOW())"
);
return $stmt->execute([
$email,
$hashedPassword,
$userData['first_name'] ?? '',
$userData['last_name'] ?? ''
]);
}
/**
* Login with email and password
*/
public function login(string $email, string $password): bool {
$user = $this->getUserByEmail($email);
if (!$user || !password_verify($password, $user['password'])) {
return false;
}
$_SESSION['user'] = [
'id' => $user['id'],
'email' => $user['email'],
'first_name' => $user['first_name'],
'last_name' => $user['last_name']
];
return true;
}
/**
* Check if user is logged in
*/
public function isLoggedIn(): bool {
return isset($_SESSION['user']);
}
/**
* Get current user data
*/
public function getCurrentUser(): ?array {
return $_SESSION['user'] ?? null;
}
/**
* Logout the user
*/
public function logout(): void {
session_unset();
session_destroy();
}
private function userExists(string $email): bool {
$stmt = $this->db->prepare("SELECT id FROM users WHERE email = ?");
$stmt->execute([$email]);
return (bool)$stmt->fetch();
}
private function getUserByEmail(string $email): ?array {
$stmt = $this->db->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
return $stmt->fetch() ?: null;
}
}
// Example Usage:
try {
$db = new PDO('mysql:host=localhost;dbname=auth_system', 'root', '');
$auth = new AuthSystem($db);
// Register a new user
// $auth->register('test@example.com', 'secure123', [
// 'first_name' => 'John',
// 'last_name' => 'Doe'
// ]);
// Login
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
if ($auth->login($_POST['email'], $_POST['password'])) {
header('Location: dashboard.php');
exit;
}
$error = "Invalid credentials";
}
// Logout
if (isset($_GET['logout'])) {
$auth->logout();
header('Location: index.php');
exit;
}
} catch (Exception $e) {
die("Error: " . $e->getMessage());
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login System</title>
<style>
body {
font-family: 'Segoe UI', sans-serif;
background: #f5f5f5;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.auth-container {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
width: 100%;
max-width: 400px;
}
.form-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
input {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
button {
background: #4f46e5;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
width: 100%;
}
.error {
color: #ef4444;
margin-bottom: 1rem;
text-align: center;
}
</style>
</head>
<body>
<div class="auth-container">
<?php if (isset($error)): ?>
<div class="error"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST">
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" name="login">Login</button>
</form>
</div>
</body>
</html>
<?php
// File: auth_system.php
class AuthSystem {
private $db;
public function __construct(PDO $db) {
$this->db = $db;
session_start();
}
/**
* Register a new user with password hashing
*/
public function register(string $email, string $password, array $userData = []): bool {
if ($this->userExists($email)) {
throw new Exception('Email already registered');
}
$hashedPassword = password_hash($password, PASSWORD_BCRYPT);
$stmt = $this->db->prepare(
"INSERT INTO users (email, password, first_name, last_name, created_at)
VALUES (?, ?, ?, ?, NOW())"
);
return $stmt->execute([
$email,
$hashedPassword,
$userData['first_name'] ?? '',
$userData['last_name'] ?? ''
]);
}
/**
* Login with email and password
*/
public function login(string $email, string $password): bool {
$user = $this->getUserByEmail($email);
if (!$user || !password_verify($password, $user['password'])) {
return false;
}
$_SESSION['user'] = [
'id' => $user['id'],
'email' => $user['email'],
'first_name' => $user['first_name'],
'last_name' => $user['last_name']
];
return true;
}
/**
* Check if user is logged in
*/
public function isLoggedIn(): bool {
return isset($_SESSION['user']);
}
/**
* Get current user data
*/
public function getCurrentUser(): ?array {
return $_SESSION['user'] ?? null;
}
/**
* Logout the user
*/
public function logout(): void {
session_unset();
session_destroy();
}
private function userExists(string $email): bool {
$stmt = $this->db->prepare("SELECT id FROM users WHERE email = ?");
$stmt->execute([$email]);
return (bool)$stmt->fetch();
}
private function getUserByEmail(string $email): ?array {
$stmt = $this->db->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
return $stmt->fetch() ?: null;
}
}
// Example Usage:
try {
$db = new PDO('mysql:host=localhost;dbname=auth_system', 'root', '');
$auth = new AuthSystem($db);
// Register a new user
// $auth->register('test@example.com', 'secure123', [
// 'first_name' => 'John',
// 'last_name' => 'Doe'
// ]);
// Login
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
if ($auth->login($_POST['email'], $_POST['password'])) {
header('Location: dashboard.php');
exit;
}
$error = "Invalid credentials";
}
// Logout
if (isset($_GET['logout'])) {
$auth->logout();
header('Location: index.php');
exit;
}
} catch (Exception $e) {
die("Error: " . $e->getMessage());
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login System</title>
<style>
body {
font-family: 'Segoe UI', sans-serif;
background: #f5f5f5;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.auth-container {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
width: 100%;
max-width: 400px;
}
.form-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
input {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
button {
background: #4f46e5;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
width: 100%;
}
.error {
color: #ef4444;
margin-bottom: 1rem;
text-align: center;
}
</style>
</head>
<body>
<div class="auth-container">
<?php if (isset($error)): ?>
<div class="error"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST">
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" name="login">Login</button>
</form>
</div>
</body>
</html>
Secure Authentication:
password_hash() and password_verify()Clean Architecture:
Modern UI:
Complete Implementation:
Would you like me to:
Here's a complete password reset implementation that includes email verification and secure token handling:
<?php
// File: password_reset.php
require 'vendor/autoload.php'; // For PHPMailer
class PasswordReset {
private $db;
private $mailer;
public function __construct(PDO $db) {
$this->db = $db;
// Configure PHPMailer (replace with your SMTP settings)
$this->mailer = new PHPMailer\PHPMailer\PHPMailer();
$this->mailer->isSMTP();
$this->mailer->Host = 'smtp.example.com';
$this->mailer->SMTPAuth = true;
$this->mailer->Username = 'your_email@example.com';
$this->mailer->Password = 'your_password';
$this->mailer->SMTPSecure = 'tls';
$this->mailer->Port = 587;
}
/**
* Initiate password reset process
*/
public function requestReset(string $email): bool {
$user = $this->getUserByEmail($email);
if (!$user) return false;
// Generate unique token (32 chars)
$token = bin2hex(random_bytes(16));
$expires = date('Y-m-d H:i:s', strtotime('+1 hour'));
$stmt = $this->db->prepare(
"INSERT INTO password_resets (email, token, expires_at)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE token = ?, expires_at = ?"
);
$stmt->execute([$email, $token, $expires, $token, $expires]);
return $this->sendResetEmail($email, $token);
}
/**
* Validate reset token and update password
*/
public function resetPassword(string $token, string $newPassword): bool {
$reset = $this->getResetRequest($token);
if (!$reset || strtotime($reset['expires_at']) < time()) {
return false;
}
$hashedPassword = password_hash($newPassword, PASSWORD_BCRYPT);
$stmt = $this->db->prepare("UPDATE users SET password = ? WHERE email = ?");
$stmt->execute([$hashedPassword, $reset['email']]);
// Delete the used token
$this->db->prepare("DELETE FROM password_resets WHERE token = ?")
->execute([$token]);
return true;
}
private function sendResetEmail(string $email, string $token): bool {
$resetLink = "https://example.com/reset_password.php?token=$token";
$this->mailer->setFrom('no-reply@example.com', 'Your App Name');
$this->mailer->addAddress($email);
$this->mailer->Subject = 'Password Reset Request';
$this->mailer->Body = "Click this link to reset your password: $resetLink";
return $this->mailer->send();
}
private function getResetRequest(string $token): ?array {
$stmt = $this->db->prepare(
"SELECT * FROM password_resets
WHERE token = ? AND expires_at > NOW()"
);
$stmt->execute([$token]);
return $stmt->fetch() ?: null;
}
private function getUserByEmail(string $email): ?array {
$stmt = $this->db->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
return $stmt->fetch() ?: null;
}
}
// Database setup (create these tables if they don't exist)
/*
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
first_name VARCHAR(50),
last_name VARCHAR(50),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE password_resets (
email VARCHAR(255) NOT NULL,
token VARCHAR(255) NOT NULL,
expires_at DATETIME NOT NULL,
PRIMARY KEY (email),
UNIQUE KEY (token)
);
*/
// Example Usage
$db = new PDO('mysql:host=localhost;dbname=your_db', 'username', 'password');
$resetSystem = new PasswordReset($db);
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['request_reset'])) {
$email = $_POST['email'];
if ($resetSystem->requestReset($email)) {
$message = "Reset link sent to your email";
} else {
$error = "Email not found";
}
}
elseif (isset($_POST['reset_password'])) {
$token = $_POST['token'];
$password = $_POST['password'];
if ($resetSystem->resetPassword($token, $password)) {
$message = "Password updated successfully";
header("Location: login.php");
exit;
} else {
$error = "Invalid or expired token";
}
}
}
// If token is provided in URL, show password reset form
$token = $_GET['token'] ?? null;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Reset</title>
<style>
body {
font-family: 'Segoe UI', sans-serif;
background: #f5f5f5;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.reset-container {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
width: 100%;
max-width: 400px;
}
.form-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
input {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
button {
background: #4f46e5;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
width: 100%;
}
.message {
margin-bottom: 1rem;
padding: 0.75rem;
border-radius: 4px;
text-align: center;
}
.success {
background: #dcfce7;
color: #166534;
}
.error {
background: #fee2e2;
color: #991b1b;
}
</style>
</head>
<body>
<div class="reset-container">
<?php if (isset($message)): ?>
<div class="message success"><?= htmlspecialchars($message) ?></div>
<?php endif; ?>
<?php if (isset($error)): ?>
<div class="message error"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<?php if ($token): ?>
<!-- Password Reset Form -->
<form method="POST">
<input type="hidden" name="token" value="<?= htmlspecialchars($token) ?>">
<div class="form-group">
<label for="password">New Password</label>
<input type="password" id="password" name="password" required minlength="8">
</div>
<button type="submit" name="reset_password">Reset Password</button>
</form>
<?php else: ?>
<!-- Request Reset Form -->
<form method="POST">
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="email" required>
</div>
<button type="submit" name="request_reset">Send Reset Link</button>
</form>
<?php endif; ?>
</div>
</body>
</html>
<?php
// File: password_reset.php
require 'vendor/autoload.php'; // For PHPMailer
class PasswordReset {
private $db;
private $mailer;
public function __construct(PDO $db) {
$this->db = $db;
// Configure PHPMailer (replace with your SMTP settings)
$this->mailer = new PHPMailer\PHPMailer\PHPMailer();
$this->mailer->isSMTP();
$this->mailer->Host = 'smtp.example.com';
$this->mailer->SMTPAuth = true;
$this->mailer->Username = 'your_email@example.com';
$this->mailer->Password = 'your_password';
$this->mailer->SMTPSecure = 'tls';
$this->mailer->Port = 587;
}
/**
* Initiate password reset process
*/
public function requestReset(string $email): bool {
$user = $this->getUserByEmail($email);
if (!$user) return false;
// Generate unique token (32 chars)
$token = bin2hex(random_bytes(16));
$expires = date('Y-m-d H:i:s', strtotime('+1 hour'));
$stmt = $this->db->prepare(
"INSERT INTO password_resets (email, token, expires_at)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE token = ?, expires_at = ?"
);
$stmt->execute([$email, $token, $expires, $token, $expires]);
return $this->sendResetEmail($email, $token);
}
/**
* Validate reset token and update password
*/
public function resetPassword(string $token, string $newPassword): bool {
$reset = $this->getResetRequest($token);
if (!$reset || strtotime($reset['expires_at']) < time()) {
return false;
}
$hashedPassword = password_hash($newPassword, PASSWORD_BCRYPT);
$stmt = $this->db->prepare("UPDATE users SET password = ? WHERE email = ?");
$stmt->execute([$hashedPassword, $reset['email']]);
// Delete the used token
$this->db->prepare("DELETE FROM password_resets WHERE token = ?")
->execute([$token]);
return true;
}
private function sendResetEmail(string $email, string $token): bool {
$resetLink = "https://example.com/reset_password.php?token=$token";
$this->mailer->setFrom('no-reply@example.com', 'Your App Name');
$this->mailer->addAddress($email);
$this->mailer->Subject = 'Password Reset Request';
$this->mailer->Body = "Click this link to reset your password: $resetLink";
return $this->mailer->send();
}
private function getResetRequest(string $token): ?array {
$stmt = $this->db->prepare(
"SELECT * FROM password_resets
WHERE token = ? AND expires_at > NOW()"
);
$stmt->execute([$token]);
return $stmt->fetch() ?: null;
}
private function getUserByEmail(string $email): ?array {
$stmt = $this->db->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
return $stmt->fetch() ?: null;
}
}
// Database setup (create these tables if they don't exist)
/*
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
first_name VARCHAR(50),
last_name VARCHAR(50),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE password_resets (
email VARCHAR(255) NOT NULL,
token VARCHAR(255) NOT NULL,
expires_at DATETIME NOT NULL,
PRIMARY KEY (email),
UNIQUE KEY (token)
);
*/
// Example Usage
$db = new PDO('mysql:host=localhost;dbname=your_db', 'username', 'password');
$resetSystem = new PasswordReset($db);
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['request_reset'])) {
$email = $_POST['email'];
if ($resetSystem->requestReset($email)) {
$message = "Reset link sent to your email";
} else {
$error = "Email not found";
}
}
elseif (isset($_POST['reset_password'])) {
$token = $_POST['token'];
$password = $_POST['password'];
if ($resetSystem->resetPassword($token, $password)) {
$message = "Password updated successfully";
header("Location: login.php");
exit;
} else {
$error = "Invalid or expired token";
}
}
}
// If token is provided in URL, show password reset form
$token = $_GET['token'] ?? null;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Reset</title>
<style>
body {
font-family: 'Segoe UI', sans-serif;
background: #f5f5f5;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.reset-container {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
width: 100%;
max-width: 400px;
}
.form-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
input {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
button {
background: #4f46e5;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
width: 100%;
}
.message {
margin-bottom: 1rem;
padding: 0.75rem;
border-radius: 4px;
text-align: center;
}
.success {
background: #dcfce7;
color: #166534;
}
.error {
background: #fee2e2;
color: #991b1b;
}
</style>
</head>
<body>
<div class="reset-container">
<?php if (isset($message)): ?>
<div class="message success"><?= htmlspecialchars($message) ?></div>
<?php endif; ?>
<?php if (isset($error)): ?>
<div class="message error"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<?php if ($token): ?>
<!-- Password Reset Form -->
<form method="POST">
<input type="hidden" name="token" value="<?= htmlspecialchars($token) ?>">
<div class="form-group">
<label for="password">New Password</label>
<input type="password" id="password" name="password" required minlength="8">
</div>
<button type="submit" name="reset_password">Reset Password</button>
</form>
<?php else: ?>
<!-- Request Reset Form -->
<form method="POST">
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="email" required>
</div>
<button type="submit" name="request_reset">Send Reset Link</button>
</form>
<?php endif; ?>
</div>
</body>
</html>
Token-Based System:
random_bytes()Secure Password Handling:
password_hash() with BCRYPTDatabase Security:
Email Verification:
User Experience:
To implement this system:
Would you like me to add any additional features like: