Files
itflow/client/document.php
johnnyq c6eadf3fa3 Client portal stage 1, look, home and tickets: a new light shell with the company logo or initials on the theme colour, sections shown by what each contact may open (Home, Tickets, Billing for billing contacts, Your IT for technical contacts, custom links), New ticket and an account menu with a dark mode switch kept in a cookie, a bottom tab bar and slide-in menu on phones, and the welcome banner removed; a rebuilt home showing what needs the contact (tickets waiting on their reply or to confirm, approvals, past-due invoices), open tickets, balance with Pay invoices, renewals in the next 45 days, their devices with warranty status and the company phone and email; tickets rebuilt with Open, Waiting on you, Resolved, Closed and All tabs with counts, search, paging and the whole company's tickets for primary and technical contacts with a Mine filter, the old all-tickets page redirecting there; a ticket page with progress steps, the conversation oldest first, approvals, details alongside, replies with drag and drop attachments, It's fixed, close or reopen and rating; New Ticket with priority, category, device and attachments; fixes downloading files from internal notes, files on closed tickets not downloading, new tickets linking to another client's device or a non-ticket category, empty replies and tickets being accepted, PHP warnings on Pay invoices and Recurring invoices with no payment provider, and Bootstrap icons and the editor setup blocked by the portal CSP, which now allows data: images; new js/portal.js and portal helpers in client/functions.php, ticketPortalStatusHtml removed; changelog updated
2026-10-17 01:28:27 -04:00

201 lines
7.7 KiB
PHP

<?php
/*
* Client Portal
* Docs for PTC / technical contacts
*/
header("Content-Security-Policy: default-src 'self'; img-src 'self' data:");
require_once "includes/inc_all.php";
enforceContactCan('itdoc');
//Initialize the HTML Purifier to prevent XSS
require_once "../libs/htmlpurifier/HTMLPurifier.standalone.php";
$purifier_config = HTMLPurifier_Config::createDefault();
$purifier_config->set('Cache.DefinitionImpl', null); // Disable cache by setting a non-existent directory or an invalid one
$purifier_config->set('URI.AllowedSchemes', ['data' => true, 'src' => true, 'http' => true, 'https' => true]);
$purifier = new HTMLPurifier($purifier_config);
// Check for a document ID
if (!isset($_GET['id']) && !intval($_GET['id'])) {
header("Location: documents.php");
exit();
}
$document_id = intval($_GET['id']);
$sql_document = mysqli_query($mysqli,
"SELECT document_id, document_name, document_content, document_description
FROM documents
WHERE document_id = $document_id AND document_client_visible = 1 AND document_client_id = $session_client_id AND document_archived_at IS NULL
LIMIT 1"
);
$row = mysqli_fetch_assoc($sql_document);
if ($row) {
$document_id = intval($row['document_id']);
$document_name = escapeHtml($row['document_name']);
$document_content = $purifier->purify($row['document_content']);
$document_description = escapeHtml($row['document_description']);
} else {
header("Location: post.php?logout");
exit();
}
// Check for associated files
$sql_files = mysqli_query($mysqli,
"SELECT f.file_id, f.file_name, f.file_reference_name, f.file_ext, f.file_size, f.file_mime_type
FROM files f
INNER JOIN document_files df ON f.file_id = df.file_id
WHERE df.document_id = $document_id AND f.file_client_id = $session_client_id
ORDER BY f.file_name ASC"
);
?>
<ol class="breadcrumb d-print-none">
<li class="breadcrumb-item">
<a href="index.php">Home</a>
</li>
<li class="breadcrumb-item">
<a href="documents.php">Documents</a>
</li>
<li class="breadcrumb-item active">
Document
</li>
</ol>
<?php
// Check if this document has attached files and handle accordingly
if (mysqli_num_rows($sql_files) > 0) {
$file_row = mysqli_fetch_assoc($sql_files);
$file_id = intval($file_row['file_id']);
$file_name = escapeHtml($file_row['file_name']);
$file_ext = strtolower($file_row['file_ext']);
$file_size = intval($file_row['file_size']);
$file_mime_type = escapeHtml($file_row['file_mime_type']);
$file_size_formatted = formatBytes($file_size);
$file_view_url = "file.php?file_id=$file_id&action=view";
$file_download_url = "file.php?file_id=$file_id";
// For PDF files, display them inline
if ($file_ext == 'pdf') {
?>
<div class="card">
<div class="card-header">
<div class="row align-items-center">
<div class="col">
<h3 class="mb-0"><i class="fas fa-file-pdf text-danger me-2"></i><?= $document_name ?></h3>
<?php if (!empty($document_description)) { ?>
<small class="text-muted"><?= $document_description ?></small>
<?php } ?>
</div>
<div class="col-auto">
<a href="<?= $file_view_url ?>" target="_blank" class="btn btn-primary">
<i class="fas fa-external-link-alt me-2"></i>Open in New Tab
</a>
<a href="<?= $file_download_url ?>" class="btn btn-secondary">
<i class="fas fa-download me-2"></i>Download
</a>
</div>
</div>
</div>
<div class="card-body p-0">
<embed src="<?= $file_view_url ?>" type="application/pdf" width="100%" height="800px" />
</div>
</div>
<?php
}
// For images, display them inline
elseif (in_array($file_ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
?>
<div class="card">
<div class="card-header">
<div class="row align-items-center">
<div class="col">
<h3 class="mb-0"><i class="fas fa-image text-primary me-2"></i><?= $document_name ?></h3>
<?php if (!empty($document_description)) { ?>
<small class="text-muted"><?= $document_description ?></small>
<?php } ?>
</div>
<div class="col-auto">
<a href="<?= $file_view_url ?>" target="_blank" class="btn btn-primary">
<i class="fas fa-external-link-alt me-2"></i>View Full Size
</a>
<a href="<?= $file_download_url ?>" class="btn btn-secondary">
<i class="fas fa-download me-2"></i>Download
</a>
</div>
</div>
</div>
<div class="card-body text-center">
<img src="<?= $file_view_url ?>" alt="<?= $file_name ?>" class="img-fluid itflow-portal-preview-image">
</div>
</div>
<?php
}
// For other file types, show download option with preview of content
else {
$file_icon = getFileIcon($file_ext);
?>
<div class="card">
<div class="card-header">
<div class="row align-items-center">
<div class="col">
<h3 class="mb-0"><i class="fas fa-<?= $file_icon ?> me-2"></i><?= $document_name ?></h3>
<?php if (!empty($document_description)) { ?>
<small class="text-muted"><?= $document_description ?></small>
<?php } ?>
</div>
<div class="col-auto">
<a href="<?= $file_download_url ?>" class="btn btn-primary">
<i class="fas fa-external-link-alt me-2"></i>Open File
</a>
<a href="<?= $file_download_url ?>" class="btn btn-secondary">
<i class="fas fa-download me-2"></i>Download
</a>
</div>
</div>
</div>
<div class="card-body">
<div class="row align-items-center">
<div class="col-auto">
<i class="fas fa-<?= $file_icon ?> fa-3x text-secondary"></i>
</div>
<div class="col">
<h5><?= $file_name ?></h5>
<p class="text-muted mb-2">
<strong>Type:</strong> <?= strtoupper($file_ext) ?> File<br>
<strong>Size:</strong> <?= $file_size_formatted ?>
</p>
<?php if (!empty($document_content) && $document_content != "<p>Uploaded file: <strong>$file_name</strong></p><p>$document_description</p>") { ?>
<div class="mt-3">
<?= $document_content ?>
</div>
<?php } ?>
</div>
</div>
</div>
</div>
<?php
}
} else {
// Regular text-based document (no files attached)
?>
<div class="card">
<div class="card-body prettyContent">
<h3><?= $document_name ?></h3>
<?= $document_content ?>
</div>
</div>
<?php
}
?>
<?php
require_once "includes/footer.php";