<?php
$folderPath = isset($_GET['p']) ? $_GET['p'] : null;
$rawFile = isset($_GET['file']) ? $_GET['file'] : null;
$rootPath = realpath('piths/source');
$filePath = ($folderPath && $rawFile) ? $folderPath . DIRECTORY_SEPARATOR . $rawFile : $rawFile;
// "hide" source/.htaccess
if ($filePath && basename($filePath) === '.htaccess') {
$filePath = null;
$rawFile = null;
}
// ?p=
$topFolder = $folderPath ?: basename($rootPath);
if (!$folderPath && $filePath) {
$partsPath = explode(DIRECTORY_SEPARATOR, ltrim($filePath, DIRECTORY_SEPARATOR));
$topFolder = count($partsPath) > 1 ? $partsPath[0] : basename($rootPath);
}
// ?p=[folder]?file=README.md
if ($folderPath && !$rawFile) {
$readmePath = $folderPath . DIRECTORY_SEPARATOR . 'README.md';
if (is_file($rootPath . DIRECTORY_SEPARATOR . $readmePath)) {
$filePath = $readmePath;
}
}
// download button
if (isset($_GET['download']) && $filePath) {
$safePath = getSafePath($rootPath . DIRECTORY_SEPARATOR . $filePath, $rootPath);
if ($safePath && is_file($safePath)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($safePath) . '"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($safePath));
readfile($safePath);
exit;
}
}
function getSafePath($path, $root) {
$realPath = realpath($path);
if ($realPath && strpos($realPath . DIRECTORY_SEPARATOR, $root . DIRECTORY_SEPARATOR) === 0) {
return $realPath;
}
return null;
}
// tree build
function renderTree($dir, $root, $filePath = null, $folderPath = null, $isRoot = true) {
$items = scandir($dir);
echo "<ul>";
foreach ($items as $item) {
if ($item == "." || $item == ".." || strpos($item, '.') === 0) continue;
$fullPath = $dir . DIRECTORY_SEPARATOR . $item;
$relPath = str_replace($root . DIRECTORY_SEPARATOR, '', $fullPath);
if ($folderPath) {
if ($relPath !== $folderPath && strpos($relPath, $folderPath . DIRECTORY_SEPARATOR) !== 0 && strpos($folderPath, $relPath . DIRECTORY_SEPARATOR) !== 0) {
continue;
}
}
if (is_dir($fullPath)) {
$isOpen = "";
if ($folderPath && ($relPath === $folderPath || strpos($relPath, $folderPath . DIRECTORY_SEPARATOR) === 0 || strpos($folderPath, $relPath . DIRECTORY_SEPARATOR) === 0)) {
$isOpen = "open";
}
echo "<li class='folder'><details $isOpen data-path='" . htmlspecialchars($relPath) . "'><summary>" . htmlspecialchars($item) . "</summary>";
renderTree($fullPath, $root, $filePath, $folderPath, false);
echo "</details></li>";
} else {
$activeClass = ($filePath === $relPath) ? "active-file" : "";
$parts = explode(DIRECTORY_SEPARATOR, $relPath);
$fileTopFolder = count($parts) > 1 ? $parts[0] : null;
if ($fileTopFolder) {
$urlFile = substr($relPath, strlen($fileTopFolder) + 1);
$url = "?p=" . urlencode($fileTopFolder) . "&file=" . urlencode($urlFile);
} else {
$url = "?p=&file=" . urlencode($relPath);
}
echo "<li class='file $activeClass'><a href='$url'>" . htmlspecialchars($item) . "</a></li>";
}
}
echo "</ul>";
}
function get_otc_date($timestamp = 'now', $tz_input = null) {
$tz_input = $tz_input ?? ($_GET['tz'] ?? 'UTC');
$timezone = in_array($tz_input, timezone_identifiers_list())
? new DateTimeZone($tz_input)
: new DateTimeZone('UTC');
$dt = ($timestamp === 'now')
? new DateTimeImmutable('now', $timezone)
: (new DateTimeImmutable('@' . $timestamp))->setTimezone($timezone);
$year = (int) $dt->format('Y');
$hour = $dt->format('H');
$min = $dt->format('i');
$sec = $dt->format('s');
$new_year = new DateTimeImmutable("{$year}-03-20", $timezone);
if ($dt < $new_year) {
$year--;
$new_year = new DateTimeImmutable("{$year}-03-20", $timezone);
}
$final_year = $year - 2000;
$final_day = (int) $new_year->diff($dt)->days + 1;
$zero_year = str_pad((string) $final_year, 3, '0', STR_PAD_LEFT);
$zero_final_day = str_pad((string) $final_day, 3, '0', STR_PAD_LEFT);
return [
'o_date' => ($hour . ":" . $min . ":" . $sec . " " . $final_day . " " . $zero_year),
];
}
$otc_current = get_otc_date('now');
$o_date = $otc_current['o_date'];
?>
<div class="tenon_header">
<div class="tenon_header_td_left">
<span class="title">Pith <?php
if ($folderPath) {
echo "- ";
$is_mobile = function_exists('isMobile') ? isMobile() : false;
$is_logged_in = ($login ?? false) === true;
if ($is_mobile) {
$pith_title_trim = $is_logged_in ? 20 : 45;
} else {
$pith_title_trim = $is_logged_in ? 35 : 45;
}
$safe_pith_title = htmlspecialchars($title ?? '', ENT_QUOTES, 'UTF-8');
echo substr($safe_pith_title, 0, $pith_title_trim) . " ";
}
?></span>
</div>
<div class="tenon_header_td_right">
</div>
</div>
<div class="pith_table">
<div class="pith_td">
<div class='pith_sidebar'>
<?php renderTree($rootPath, $rootPath, $filePath, $folderPath); ?>
</div>
<div class='main_content'>
<div class='toolbar'>
<div class='tool_path'>
<span id="filename_display"><?php echo $filePath ? htmlspecialchars($filePath) : "No File Selected"; ?></span>
<?php if ($filePath && ($safePath = getSafePath($rootPath . DIRECTORY_SEPARATOR . $filePath, $rootPath)) && is_file($safePath)): ?>
<span style="color: gray; margin-left: 4px;">[<?php
$size = filesize($safePath);
echo ($size >= 1048576) ? number_format($size / 1048576, 1) . ' mb' : number_format($size / 1024, 1) . ' kb';
?>]</span>
<?php endif; ?>
</div>
<div class='file_data'>
<div>
<?php if (isset($safePath) && $safePath && is_file($safePath)): ?>
<?php
$file_mtime = filemtime($safePath);
$daysSince = floor((time() - $file_mtime) / 86400);
$file_otc = get_otc_date($file_mtime);
?>
<span class="file_meta">Modified: <?php echo $file_otc['o_date'] . " (" . date("d M 0y", $file_mtime) . ")"; ?><br><?php echo $daysSince; ?> Days Ago</span>
<?php endif; ?>
</div>
<?php if ($filePath): ?>
<div class='actions' style='flex-shrink: 0;'>
<button class='copy_btn' id='copyBtn' onclick='copyCode()'>Copy</button>
</div>
<?php endif; ?>
</div>
</div>
<div class='pith_viewer'>
<div class='pith_viewer_inner'>
<?php
if ($filePath) {
$safePath = getSafePath($rootPath . DIRECTORY_SEPARATOR . $filePath, $rootPath);
if ($safePath && is_file($safePath)) {
$ext = strtolower(pathinfo($safePath, PATHINFO_EXTENSION));
$imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico'];
if (in_array($ext, $imageExts)) {
echo "<div class='image_viewport'><img src='piths/source/" . htmlspecialchars($filePath) . "' alt=''></div>";
} else {
echo "<pre><code id='file_content'>" . htmlspecialchars(file_get_contents($safePath)) . "</code></pre>";
}
} else {
echo "<div class='empty_state'>Error: File not found or access denied.</div>";
}
}
?>
</div>
</div>
</div>
<a name="panels"></a>
</div>
</div>
<script src='piths/pith/highlight.min.js'></script>
<script src='piths/pith/highlightjs-line-numbers.js'></script>
<script>
hljs.highlightAll();
hljs.initLineNumbersOnLoad();
function copyCode() {
const codeElement = document.getElementById('file_content');
const btn = document.getElementById('copyBtn');
if (!codeElement) return;
const codeCells = codeElement.querySelectorAll('.hljs-ln-code');
let text = "";
if (codeCells.length > 0) {
text = Array.from(codeCells).map(cell => cell.textContent).join('\n');
} else {
text = codeElement.textContent;
}
navigator.clipboard.writeText(text).then(() => {
const originalText = btn.innerText;
btn.innerText = "Copied";
btn.classList.add('success');
setTimeout(() => {
btn.innerText = originalText;
btn.classList.remove('success');
}, 2000);
}).catch(err => {
console.error('Failed to copy: ', err);
alert("Failed to copy text.");
});
}
</script>
<script>
const detailsElements = document.querySelectorAll('details[data-path]');
detailsElements.forEach(details => {
const path = details.getAttribute('data-path');
if (details.open) {
sessionStorage.setItem('p-' + path, 'open');
}
else if (sessionStorage.getItem('p-' + path) === 'open') {
details.open = true;
}
details.addEventListener('toggle', (e) => {
const path = details.getAttribute('data-path');
if (details.open) {
sessionStorage.setItem('p-' + path, 'open');
} else {
sessionStorage.removeItem('p-' + path);
}
});
const summary = details.querySelector('summary');
if (summary) {
summary.addEventListener('click', (e) => {
if (!details.open) {
e.preventDefault();
window.location.href = '?p=' + encodeURIComponent(path);
}
});
}
});
</script>