![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/soundstudiopro.com/private_html/ |
<?php
session_start();
require_once 'config/database.php';
require_once 'includes/event_permissions.php';
$ticket_code = $_GET['code'] ?? null;
if (!$ticket_code) {
http_response_code(400);
die('Ticket code required');
}
$pdo = getDBConnection();
$user_id = $_SESSION['user_id'] ?? null;
ensureEventManagersTable($pdo);
// Get ticket information with creator + check-in details
$stmt = $pdo->prepare("
SELECT
et.*,
e.id as event_id,
e.title as event_title,
e.start_date,
e.end_date,
e.location,
e.venue_name,
e.creator_id,
e.banner_image,
e.cover_image,
u.name as user_name,
u.email as user_email,
manager.name as checked_in_by_name
FROM event_tickets et
JOIN events e ON et.event_id = e.id
JOIN users u ON et.user_id = u.id
LEFT JOIN users manager ON et.checked_in_by = manager.id
WHERE et.ticket_code = ?
");
$stmt->execute([$ticket_code]);
$ticket = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$ticket) {
http_response_code(404);
die('Ticket not found');
}
$can_manage = userCanManageEvent($pdo, $user_id, (int)$ticket['event_id']);
$isCheckedIn = $ticket['status'] === 'used';
$statusLabel = ucfirst($ticket['status']);
$statusClass = 'status-neutral';
switch ($ticket['status']) {
case 'used':
$statusClass = 'status-used';
break;
case 'confirmed':
$statusClass = 'status-confirmed';
break;
case 'pending':
$statusClass = 'status-pending';
break;
case 'cancelled':
$statusClass = 'status-cancelled';
break;
}
$checkedInMeta = null;
if ($isCheckedIn && !empty($ticket['checked_in_at'])) {
$checkedInMeta = [
'time' => date('M j, Y g:i A', strtotime($ticket['checked_in_at'])),
'by' => $ticket['checked_in_by_name'] ?: 'Door staff'
];
}
// Generate QR code using API (client fetch)
$baseUrl = rtrim((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https://' : 'http://') . ($_SERVER['HTTP_HOST'] ?? 'soundstudiopro.com'), '/');
$qr_link = sprintf(
'%s/event_checkin_console.php?event_id=%d&ticket=%s',
$baseUrl,
$ticket['event_id'],
rawurlencode($ticket_code)
);
$qr_url = 'https://api.qrserver.com/v1/create-qr-code/?size=420x420&data=' . urlencode($qr_link);
?><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ticket QR - <?= htmlspecialchars($ticket['event_title']) ?></title>
<style>
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(circle at top, #0f1730, #05060a 70%);
color: #f7fafc;
font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
padding: 2rem;
}
.ticket-wrapper {
background: rgba(12, 16, 34, 0.85);
border: 1px solid rgba(103, 126, 234, 0.3);
border-radius: 26px;
padding: 2.8rem;
width: min(520px, 100%);
text-align: center;
backdrop-filter: blur(14px);
box-shadow: 0 25px 60px rgba(0,0,0,0.45);
position: relative;
}
h1 {
font-size: 2rem;
margin-bottom: 0.5rem;
}
p {
color: #a0aec0;
margin: 0.3rem 0;
}
img {
margin: 2rem auto;
border-radius: 20px;
background: #fff;
padding: 1rem;
box-shadow: 0 10px 35px rgba(79, 172, 254, 0.3);
max-width: 100%;
height: auto;
}
.ticket-meta {
font-size: 0.95rem;
color: #cbd5f5;
line-height: 1.6;
}
.ticket-meta strong {
color: #f7fafc;
font-size: 1.1rem;
letter-spacing: 0.05em;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1rem;
margin-top: 1.5rem;
text-align: left;
}
.info-card {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 16px;
padding: 1rem 1.2rem;
}
.info-card h4 {
margin: 0 0 0.4rem;
font-size: 0.95rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #94a6ff;
}
.info-card p {
margin: 0.15rem 0;
font-size: 0.95rem;
color: #f7fafc;
}
.info-card span {
color: #a0aec0;
font-size: 0.95rem;
letter-spacing: 0.06em;
font-weight: 500;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.35rem 0.9rem;
border-radius: 999px;
font-weight: 600;
margin-top: 0.2rem;
border: 1px solid rgba(255,255,255,0.1);
box-shadow: 0 10px 25px rgba(0,0,0,0.25);
}
.status-badge i {
font-size: 0.95rem;
}
.status-badge.status-used {
color: #fed7d7;
background: rgba(245, 101, 101, 0.25);
border-color: rgba(245, 101, 101, 0.4);
text-transform: uppercase;
letter-spacing: 0.2em;
animation: usedPulse 1.6s infinite;
}
.status-badge.status-confirmed {
color: #c6f6d5;
background: rgba(72, 187, 120, 0.25);
border-color: rgba(72, 187, 120, 0.4);
}
.status-badge.status-pending {
color: #fefcbf;
background: rgba(237, 137, 54, 0.25);
border-color: rgba(237, 137, 54, 0.4);
}
.status-badge.status-cancelled {
color: #feb2b2;
background: rgba(229, 62, 62, 0.25);
border-color: rgba(229, 62, 62, 0.4);
}
@keyframes usedPulse {
0% { box-shadow: 0 0 0 0 rgba(229, 62, 62, 0.35); transform: translateY(0); }
70% { box-shadow: 0 0 35px 10px rgba(229, 62, 62, 0); transform: translateY(-1px); }
100% { box-shadow: 0 0 0 0 rgba(229, 62, 62, 0); transform: translateY(0); }
}
.admin-panel {
margin-top: 1.8rem;
text-align: left;
background: rgba(21, 25, 52, 0.9);
border: 1px solid rgba(102, 126, 234, 0.4);
border-radius: 18px;
padding: 1.5rem;
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.25);
}
.admin-panel h3 {
margin: 0 0 0.8rem;
font-size: 1.1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.status-chip {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.35rem 0.9rem;
border-radius: 999px;
font-size: 0.88rem;
font-weight: 600;
text-transform: capitalize;
}
.status-chip.confirmed {
background: rgba(72, 187, 120, 0.15);
color: #68d391;
border: 1px solid rgba(72, 187, 120, 0.4);
}
.status-chip.used {
background: rgba(236, 201, 75, 0.15);
color: #f6ad55;
border: 1px solid rgba(236, 201, 75, 0.4);
}
.status-chip.cancelled {
background: rgba(245, 101, 101, 0.15);
color: #fc8181;
border: 1px solid rgba(245, 101, 101, 0.4);
}
.admin-meta {
margin-top: 1rem;
font-size: 0.9rem;
color: #cbd5f5;
line-height: 1.5;
}
.admin-actions {
margin-top: 1.2rem;
display: flex;
flex-wrap: wrap;
gap: 0.8rem;
}
.admin-actions button {
flex: 1;
min-width: 180px;
}
.actions {
margin-top: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.btn {
display: inline-flex;
justify-content: center;
align-items: center;
gap: 0.4rem;
padding: 0.9rem 1.2rem;
border-radius: 10px;
font-weight: 600;
text-decoration: none;
border: none;
}
.btn-primary {
background: linear-gradient(135deg, #667eea, #764ba2);
color: #fff;
}
.btn-secondary {
background: rgba(255,255,255,0.08);
color: #e2e8f0;
border: 1px solid rgba(255,255,255,0.15);
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 12px 28px rgba(102, 126, 234, 0.35);
}
@media (max-width: 640px) {
.ticket-wrapper {
padding: 2rem 1.4rem;
border-radius: 20px;
}
.info-grid {
grid-template-columns: 1fr;
}
.admin-actions {
flex-direction: column;
}
}
</style>
</head>
<?php
$eventDate = '';
if (!empty($ticket['start_date'])) {
$eventDate = date('M j, Y g:i A', strtotime($ticket['start_date']));
}
$purchaseDate = date('M j, Y g:i A', strtotime($ticket['purchase_date']));
?>
<body>
<div class="ticket-wrapper">
<h1><?= htmlspecialchars($ticket['event_title']) ?></h1>
<?php if ($eventDate): ?><p><?= $eventDate ?></p><?php endif; ?>
<p><?= htmlspecialchars($ticket['user_name']) ?> · <?= htmlspecialchars($ticket['user_email']) ?></p>
<div class="info-grid">
<div class="info-card">
<h4>Ticket</h4>
<p><span>Code</span><br><strong><?= htmlspecialchars($ticket_code) ?></strong></p>
<p><span>Status</span><br>
<span class="status-badge <?= $statusClass ?>" id="ticketStatusBadge">
<i class="fas <?= $isCheckedIn ? 'fa-ban' : 'fa-check' ?>"></i>
<span id="ticketStatusLabel"><?= $statusLabel ?></span>
</span>
</p>
</div>
<div class="info-card">
<h4>Attendee</h4>
<p><span>Name</span><br><?= htmlspecialchars($ticket['user_name']) ?></p>
<p><span>Email</span><br><?= htmlspecialchars($ticket['user_email']) ?></p>
</div>
</div>
<?php if ($can_manage): ?>
<div class="admin-panel" id="managerPanel">
<h3><i class="fas fa-shield-alt"></i> Door Access</h3>
<div class="status-chip <?= strtolower($ticket['status']) ?>" id="ticketStatusChip">
<i class="fas fa-ticket-alt"></i>
<span id="ticketStatusChipText"><?= $statusLabel ?></span>
</div>
<div class="admin-meta" id="ticketCheckInMeta">
<?php if ($checkedInMeta): ?>
Checked in <?= htmlspecialchars($checkedInMeta['time']) ?> by <?= htmlspecialchars($checkedInMeta['by']) ?>
<?php else: ?>
Ticket ready for check-in. Confirm guest identity before proceeding.
<?php endif; ?>
</div>
<div class="admin-actions">
<?php if (!$isCheckedIn): ?>
<button class="btn btn-primary" id="checkInBtn">
<i class="fas fa-check-circle"></i> Check In Guest
</button>
<?php endif; ?>
<?php if ($isCheckedIn): ?>
<button class="btn btn-secondary" id="undoCheckInBtn">
<i class="fas fa-undo"></i> Undo Check-In
</button>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<img src="<?= $qr_url ?>" alt="Ticket QR Code" loading="lazy">
<div class="ticket-meta">
<strong>Ticket Code:</strong> <?= htmlspecialchars($ticket_code) ?><br>
<strong>Status:</strong> <?= ucfirst($ticket['status']) ?><br>
<strong>Purchased:</strong> <?= $purchaseDate ?>
</div>
<div class="actions">
<a class="btn btn-primary" href="<?= $qr_url ?>" download="ticket-<?= htmlspecialchars($ticket_code) ?>.png">
Download PNG
</a>
<a class="btn btn-secondary" href="/tickets.php">
Back to My Tickets
</a>
</div>
</div>
<?php if ($can_manage): ?>
<script>
(function() {
const checkInBtn = document.getElementById('checkInBtn');
const undoBtn = document.getElementById('undoCheckInBtn');
const statusLabel = document.getElementById('ticketStatusLabel');
const statusChip = document.getElementById('ticketStatusChip');
const statusChipText = document.getElementById('ticketStatusChipText');
const meta = document.getElementById('ticketCheckInMeta');
const ticketCode = <?= json_encode($ticket_code) ?>;
async function performAction(action) {
const btn = action === 'check_in' ? checkInBtn : undoBtn;
if (!btn) return;
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...';
try {
const response = await fetch('/api/ticket_checkin.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ticket_code: ticketCode, action })
});
const result = await response.json();
if (!response.ok || !result.success) {
throw new Error(result.error || 'Failed to update ticket');
}
statusLabel.textContent = result.ticket.status_label;
statusChipText.textContent = result.ticket.status_label;
statusChip.className = 'status-chip ' + result.ticket.status;
meta.textContent = result.ticket.meta;
if (result.ticket.status === 'used') {
if (!undoBtn) {
location.reload();
return;
}
if (checkInBtn) {
checkInBtn.remove();
}
undoBtn.disabled = false;
undoBtn.innerHTML = '<i class="fas fa-undo"></i> Undo Check-In';
} else {
if (!checkInBtn) {
location.reload();
return;
}
if (undoBtn) {
undoBtn.remove();
}
checkInBtn.disabled = false;
checkInBtn.innerHTML = '<i class="fas fa-check-circle"></i> Check In Guest';
}
} catch (error) {
console.error(error);
alert(error.message || 'Unable to update ticket right now.');
if (btn) {
btn.disabled = false;
btn.innerHTML = action === 'check_in'
? '<i class="fas fa-check-circle"></i> Check In Guest'
: '<i class="fas fa-undo"></i> Undo Check-In';
}
}
}
checkInBtn?.addEventListener('click', () => performAction('check_in'));
undoBtn?.addEventListener('click', () => performAction('revert'));
})();
</script>
<?php endif; ?>
</body>
</html>