<?php require_once __DIR__ . '/BasePage.php'; require_once __DIR__ . '/../render/HtmlFileRenderer.php'; class FilePage extends BasePage { private $currentRepo; private $git; private $hash; public function __construct( array $repositories, array $currentRepo, Git $git, string $hash = '' ) { parent::__construct( $repositories ); $this->currentRepo = $currentRepo; $this->git = $git; $this->hash = $hash; $this->title = $currentRepo['name']; } public function render() { $this->renderLayout( function() { $main = $this->git->getMainBranch(); if( !$main ) { echo '<div class="empty-state"><h3>No branches</h3></div>'; } else { $target = $this->hash ?: $main['hash']; $entries = []; $this->git->walk( $target, function( $file ) use ( &$entries ) { $entries[] = $file; } ); if( !empty( $entries ) ) { $this->renderTree( $main, $target, $entries ); } else { $this->renderBlob( $target ); } } }, $this->currentRepo ); } private function renderTree( $main, $targetHash, $entries ) { $path = $_GET['name'] ?? ''; $this->renderBreadcrumbs( $targetHash, 'Tree' ); echo '<h2>' . htmlspecialchars( $this->currentRepo['name'] ) . ' <span class="branch-badge">' . htmlspecialchars( $main['name'] ) . '</span></h2>'; usort( $entries, function( $a, $b ) { return $a->compare( $b ); } ); echo '<div class="file-list">'; $renderer = new HtmlFileRenderer( $this->currentRepo['safe_name'], $path ); foreach( $entries as $file ) { $file->renderListEntry( $renderer ); } echo '</div>'; } private function renderBlob( $targetHash ) { $repoParam = '&repo=' . urlencode( $this->currentRepo['safe_name'] ); $filename = $_GET['name'] ?? ''; $file = $this->git->readFile( $targetHash, $filename ); $size = $this->git->getObjectSize( $targetHash ); $renderer = new HtmlFileRenderer( $this->currentRepo['safe_name'] ); $this->renderBreadcrumbs( $targetHash, 'File' ); if( $size === 0 ) { $this->renderDownloadState( $targetHash, "This file is empty." ); } else { $rawUrl = '?action=raw&hash=' . $targetHash . $repoParam . '&name=' . urlencode( $filename ); if( !$file->renderMedia( $renderer, $rawUrl ) ) { if( $file->isText() ) { if( $size > 524288 ) { ob_start(); $file->renderSize( $renderer ); $sizeStr = ob_get_clean(); $this->renderDownloadState( $targetHash, "File is too large to display ($sizeStr)." ); } else { $content = ''; $this->git->stream( $targetHash, function( $d ) use ( &$content ) { $content .= $d; } ); echo '<div class="blob-content"><pre class="blob-code">' . $file->highlight( $renderer, $content ) . '</pre></div>'; } } else { $this->renderDownloadState( $targetHash, "This is a binary file." ); } } } } private function renderDownloadState( $hash, $reason ) { $filename = $_GET['name'] ?? ''; $url = '?action=raw&hash=' . $hash . '&repo=' . urlencode( $this->currentRepo['safe_name'] ) . '&name=' . urlencode( $filename ); echo '<div class="empty-state download-state">'; echo '<p>' . htmlspecialchars( $reason ) . '</p>'; echo '<a href="' . $url . '" class="btn-download">Download</a>'; echo '</div>'; } private function renderBreadcrumbs( $hash, $type ) { $repoUrl = '?repo=' . urlencode( $this->currentRepo['safe_name'] ); $path = $_GET['name'] ?? ''; $crumbs = [ '<a href="?">Repositories</a>', '<a href="' . $repoUrl . '">' . htmlspecialchars( $this->currentRepo['name'] ) . '</a>' ]; if( $path ) { $parts = explode( '/', trim( $path, '/' ) ); $acc = ''; foreach( $parts as $idx => $part ) { $acc .= ($idx === 0 ? '' : '/') . $part; if( $idx === count( $parts ) - 1 ) { $crumbs[] = htmlspecialchars( $part ); } else { $crumbs[] = '<a href="' . $repoUrl . '&name=' . urlencode( $acc ) . '">' . htmlspecialchars( $part ) . '</a>'; } } } elseif( $this->hash ) { $crumbs[] = $type . ' ' . substr( $hash, 0, 7 ); } echo '<div class="breadcrumb">' . implode( ' / ', $crumbs ) . '</div>'; } }