<?php
class FilePage extends BasePage {
private $currentRepo;
private $git;
private $hash;
public function __construct( $allRepos, $currentRepo, $git, $hash ) {
parent::__construct( $allRepos );
$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>';
return;
}
$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->render( $renderer );
}
echo '</div>';
}
private function renderBlob( $targetHash ) {
$repoParam = '&repo=' . urlencode( $this->currentRepo['safe_name'] );
$size = $this->git->getObjectSize( $targetHash );
$buffer = '';
$this->git->stream( $targetHash, function( $d ) use ( &$buffer ) {
if( strlen( $buffer ) < 12 ) $buffer .= $d;
} );
$filename = $_GET['name'] ?? '';
$category = MediaTypeSniffer::isCategory( $buffer, $filename );
$mediaType = MediaTypeSniffer::isMediaType( $buffer, $filename );
$this->renderBreadcrumbs( $targetHash, 'File' );
if( $size === 0 ) {
$this->renderDownloadState( $targetHash, "This file is empty." );
return;
}
$rawUrl = '?action=raw&hash=' . $targetHash . $repoParam . '&name=' . urlencode( $filename );
if( $category === MediaTypeSniffer::CAT_IMAGE ) {
echo '<div class="blob-content blob-content-image"><img src="' . $rawUrl . '"></div>';
} elseif( $category === MediaTypeSniffer::CAT_VIDEO ) {
echo '<div class="blob-content blob-content-video"><video controls><source src="' . $rawUrl . '" type="' . $mediaType . '"></video></div>';
} elseif( $category === MediaTypeSniffer::CAT_AUDIO ) {
echo '<div class="blob-content blob-content-audio"><audio controls><source src="' . $rawUrl . '" type="' . $mediaType . '"></audio></div>';
} elseif( $category === MediaTypeSniffer::CAT_TEXT ) {
if( $size > 524288 ) {
$this->renderDownloadState( $targetHash, "File is too large to display (" . $this->formatSize( $size ) . ")." );
} else {
$content = '';
$this->git->stream( $targetHash, function( $d ) use ( &$content ) { $content .= $d; } );
echo '<div class="blob-content"><pre class="blob-code">' . htmlspecialchars( $content ) . '</pre></div>';
}
} else {
$this->renderDownloadState( $targetHash, "This is a binary file." );
}
}
private function renderDownloadState( $hash, $reason ) {
$url = '?action=raw&hash=' . $hash . '&repo=' . urlencode( $this->currentRepo['safe_name'] );
echo '<div class="empty-state download-state">';
echo '<p>' . htmlspecialchars( $reason ) . '</p>';
echo '<a href="' . $url . '" class="btn-download">Download Raw File</a>';
echo '</div>';
}
private function formatSize( $size ) {
if( $size <= 0 ) return '0 B';
$units = ['B', 'KB', 'MB', 'GB'];
$i = (int)floor( log( $size, 1024 ) );
return round( $size / pow( 1024, $i ), 1 ) . ' ' . $units[$i];
}
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>';
}
}