<?php
require_once __DIR__ . '/config.php';
if (!IS_INSTALLED) { header('Location: setup.php'); exit; }
require_once ROOT_PATH . '/includes/db.php';
require_once ROOT_PATH . '/includes/functions.php';
require_once ROOT_PATH . '/includes/auth.php';
require_once ROOT_PATH . '/includes/markdown.php';
$topicSlug = trim($_GET['topic'] ?? '');
$docSlug = trim($_GET['slug'] ?? '');
$urlToken = trim($_GET['token'] ?? '');
$topic = $topicSlug ? get_topic($topicSlug) : null;
if (!$topic) { http_response_code(404); die('Topic not found.'); }
$doc = $docSlug ? get_document($topic['id'], $docSlug) : null;
if (!$doc || ($doc['status'] !== 'published' && !has_role('contributor'))) {
http_response_code(404); die('Document not found.');
}
// ── Access control ────────────────────────────────────────────────────────────
$accessType = $doc['access_type'] ?? 'public';
$accessOk = true;
$passwordError = '';
if (!has_role('contributor')) {
if ($accessType === 'link') {
if (empty($doc['access_token']) || $urlToken !== $doc['access_token']) {
$accessOk = false;
}
} elseif ($accessType === 'password') {
$sessionKey = 'doc_unlocked_' . $doc['id'];
if (empty($_SESSION[$sessionKey])) {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['doc_password'])) {
verify_csrf();
if (!empty($doc['access_password']) && password_verify($_POST['doc_password'], $doc['access_password'])) {
$_SESSION[$sessionKey] = true;
$redir = base_url('topic/' . rawurlencode($topic['slug']) . '/' . rawurlencode($doc['slug']));
header('Location: ' . $redir); exit;
} else {
$passwordError = 'Incorrect password. Please try again.';
}
}
$accessOk = false;
}
}
}
$tags = get_document_tags($doc['id']);
$revisions = get_revisions($doc['id']);
$ogImage = $doc['og_image'] ?: $topic['og_image'] ?: get_setting('og_image');
// Sibling document navigation within this topic
$siblingDocs = get_documents($topic['id']); // published, ordered by updated_at DESC
$siblingIdx = array_search($doc['slug'], array_column($siblingDocs, 'slug'));
$prevSibling = ($siblingIdx !== false && $siblingIdx > 0) ? $siblingDocs[$siblingIdx - 1] : null;
$nextSibling = ($siblingIdx !== false && $siblingIdx < count($siblingDocs) - 1) ? $siblingDocs[$siblingIdx + 1] : null;
// Build the canonical URL (include token for link-type so OG links work)
$canonicalUrl = doc_url($topic['slug'], $doc['slug']);
if ($accessType === 'link' && !empty($doc['access_token'])) {
$canonicalUrl .= '?token=' . rawurlencode($doc['access_token']);
}
$meta = build_meta([
'title' => $doc['title'] . ' — ' . $topic['title'],
'og_title' => $doc['title'],
'og_desc' => $doc['meta_description'] ?: get_setting('og_description'),
'og_image' => $ogImage,
'og_type' => 'article',
'og_url' => $canonicalUrl,
]);
include ROOT_PATH . '/includes/header.php';
// ── Access denied: password form ──────────────────────────────────────────────
if (!$accessOk && $accessType === 'password'):
?>
<div class="window access-gate" style="max-width:400px;margin:40px auto">
<div class="win-titlebar">🔒 Password Required</div>
<div class="win-body">
<p style="margin:0 0 12px">This document is password protected.</p>
<?php if ($passwordError): ?>
<div class="flash flash-error"><?= h($passwordError) ?></div>
<?php endif; ?>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= csrf_token() ?>">
<div class="form-group">
<label for="doc_password">Password</label>
<input type="password" id="doc_password" name="doc_password" class="input-full" required autofocus>
</div>
<div class="form-actions">
<button type="submit" class="button">Unlock Document</button>
<a href="<?= h(topic_url($topic['slug'])) ?>" class="button" style="margin-left:8px">← Back</a>
</div>
</form>
</div>
</div>
<?php include ROOT_PATH . '/includes/footer.php'; exit; endif; ?>
<?php
// ── Access denied: link only ──────────────────────────────────────────────────
if (!$accessOk && $accessType === 'link'):
?>
<div class="window access-gate" style="max-width:440px;margin:40px auto">
<div class="win-titlebar">🔗 Access Restricted</div>
<div class="win-body">
<p>This document is only accessible via a special private link.</p>
<p style="font-size:13px;color:#555">If you have received the link, please use it to access this document. Generic URL access is not permitted.</p>
<a href="<?= h(topic_url($topic['slug'])) ?>" class="button">← Back to Topic</a>
</div>
</div>
<?php include ROOT_PATH . '/includes/footer.php'; exit; endif; ?>
<?php record_page_view((int)$doc['id']); ?>
<div class="breadcrumb">
<a href="<?= h(base_url()) ?>">Home</a> ›
<?php foreach (get_topic_ancestors((int)$topic['id']) as $anc): ?>
<a href="<?= h(topic_url($anc['slug'])) ?>"><?= h($anc['title']) ?></a> ›
<?php endforeach; ?>
<a href="<?= h(topic_url($topic['slug'])) ?>"><?= h($topic['title']) ?></a> ›
<?= h($doc['title']) ?>
</div>
<?php if ($accessType === 'password' && !has_role('contributor')): ?>
<div class="flash flash-info" style="margin-bottom:8px">🔒 This document is password protected. Your access expires when your session ends.</div>
<?php endif; ?>
<div class="doc-layout">
<!-- Main document window -->
<div class="window doc-window">
<div class="win-titlebar">
📄 <?= h($doc['title']) ?>
<div class="win-actions">
<?php
$printLink = print_url($topic['slug'], $doc['slug']);
if ($accessType === 'link' && !empty($doc['access_token'])) {
$printLink .= '?token=' . rawurlencode($doc['access_token']);
}
?>
<a href="<?= h($printLink) ?>" target="_blank" class="button btn-sm" title="Print">🖨 Print</a>
<?php if (has_role('contributor')): ?>
<a href="<?= h(base_url('admin/document-edit.php?id=' . $doc['id'])) ?>" class="button btn-sm">✎ Edit</a>
<?php endif; ?>
</div>
</div>
<div class="win-body">
<?php if ($doc['status'] === 'draft'): ?>
<div class="flash flash-info">⚠ This document is a draft and not publicly visible.</div>
<?php endif; ?>
<?php if ($doc['og_image']): ?>
<img src="<?= h(base_url('uploads/og/' . $doc['og_image'])) ?>" alt="" class="doc-og-image">
<?php endif; ?>
<div class="markdown-body" id="doc-content">
<?= md($doc['content']) ?>
</div>
</div>
</div>
<!-- Sidebar -->
<aside class="doc-sidebar">
<div class="window sidebar-section">
<div class="win-titlebar">Document Info</div>
<div class="win-body">
<dl class="doc-meta-list">
<dt>Topic</dt><dd><a href="<?= h(topic_url($topic['slug'])) ?>"><?= h($topic['title']) ?></a></dd>
<?php if ($doc['author_name']): ?>
<dt>Author</dt><dd><?= h($doc['author_name']) ?></dd>
<?php endif; ?>
<dt>Created</dt><dd><?= h(fmt_date($doc['created_at'])) ?></dd>
<dt>Modified</dt><dd><?= h(fmt_date($doc['updated_at'])) ?></dd>
<?php if ($accessType !== 'public'): ?>
<dt>Access</dt>
<dd>
<?php if ($accessType === 'password'): ?>
<span style="color:#800000">🔒 Password</span>
<?php else: ?>
<span style="color:#007000">🔗 Link Only</span>
<?php endif; ?>
</dd>
<?php endif; ?>
</dl>
</div>
</div>
<?php if (!empty($tags)): ?>
<div class="window sidebar-section">
<div class="win-titlebar">Audience Tags</div>
<div class="win-body">
<?php foreach ($tags as $tg): ?>
<a href="<?= h(topic_url($topic['slug'])) ?>?tag=<?= $tg['id'] ?>"
class="tag-badge" style="background:<?= h($tg['color']) ?>;color:#FFF"
title="<?= h($tg['description']) ?>"><?= h($tg['name']) ?></a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<div class="window sidebar-section">
<div class="win-titlebar">Actions</div>
<div class="win-body">
<a href="<?= h($printLink) ?>" target="_blank" class="button full-width">🖨 Print Document</a>
<a href="<?= h(rss_url($topic['slug'])) ?>" class="button full-width" style="margin-top:6px">⁕ Topic RSS Feed</a>
<?php if ($accessType === 'link' && !empty($doc['access_token'])): ?>
<div style="margin-top:8px">
<label style="font-size:11px;font-weight:bold">Private Link:</label>
<div style="display:flex;gap:4px;margin-top:3px">
<input type="text" value="<?= h($canonicalUrl) ?>" id="share-link-pub" class="input-full" readonly style="font-size:11px;padding:2px 4px">
<button type="button" class="button btn-sm" onclick="copyShareLink()" title="Copy to clipboard">📋</button>
</div>
</div>
<?php endif; ?>
</div>
</div>
</aside>
</div>
<!-- Revision History -->
<?php if (!empty($revisions)): ?>
<div class="window revisions-window">
<div class="win-titlebar">📄 Revision History</div>
<div class="win-body">
<div class="table-responsive">
<table class="doc-table">
<thead><tr><th>Date</th><th>Changed By</th><th>Note</th></tr></thead>
<tbody>
<?php foreach ($revisions as $rev): ?>
<tr>
<td><?= h(fmt_date($rev['created_at'], true)) ?></td>
<td><?= h($rev['username'] ?? '—') ?></td>
<td><?= h($rev['change_note'] ?: '—') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php endif; ?>
<script>
function copyShareLink() {
var inp = document.getElementById('share-link-pub');
if (!inp) return;
if (navigator.clipboard) {
navigator.clipboard.writeText(inp.value).then(function(){
var btn = inp.nextElementSibling;
var old = btn.innerHTML;
btn.innerHTML = '✓';
setTimeout(function(){ btn.innerHTML = old; }, 2000);
});
} else {
inp.select(); document.execCommand('copy');
}
}
</script>
<?php if ($prevSibling || $nextSibling): ?>
<style>
.doc-nav-arrow {
position: fixed;
top: 50%;
transform: translateY(-50%);
z-index: 800;
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 64px;
background: rgba(0,0,0,0.55);
color: #FFF;
font-size: 20px;
text-decoration: none;
border: 1px solid rgba(255,255,255,0.25);
transition: background 0.15s;
cursor: pointer;
user-select: none;
}
.doc-nav-arrow:hover { background: rgba(0,0,100,0.75); color: #FFF; border-color: rgba(255,255,255,0.5); }
.doc-nav-arrow.doc-nav-prev { left: 0; border-left: none; border-radius: 0 4px 4px 0; }
.doc-nav-arrow.doc-nav-next { right: 0; border-right: none; border-radius: 4px 0 0 4px; }
@media (max-width: 640px) {
.doc-nav-arrow { width: 28px; height: 52px; font-size: 16px; opacity: 0.7; }
}
</style>
<?php if ($prevSibling): ?>
<a href="<?= h(doc_url($topic['slug'], $prevSibling['slug'])) ?>"
class="doc-nav-arrow doc-nav-prev"
title="← <?= h($prevSibling['title']) ?>">◀</a>
<?php endif; ?>
<?php if ($nextSibling): ?>
<a href="<?= h(doc_url($topic['slug'], $nextSibling['slug'])) ?>"
class="doc-nav-arrow doc-nav-next"
title="→ <?= h($nextSibling['title']) ?>">▶</a>
<?php endif; ?>
<?php endif; ?>
<script>
window.docNav = {
prev: <?= $prevSibling ? json_encode(doc_url($topic['slug'], $prevSibling['slug'])) : 'null' ?>,
next: <?= $nextSibling ? json_encode(doc_url($topic['slug'], $nextSibling['slug'])) : 'null' ?>
};
</script>
<?php include ROOT_PATH . '/includes/footer.php'; ?>