<?php require_once __DIR__ . '/render/FileRenderer.php'; class File { private const CAT_IMAGE = 'image'; private const CAT_VIDEO = 'video'; private const CAT_AUDIO = 'audio'; private const CAT_TEXT = 'text'; private const CAT_ARCHIVE = 'archive'; private const CAT_BINARY = 'binary'; private const ARCHIVE_EXTENSIONS = [ 'zip', 'tar', 'gz', '7z', 'rar', 'jar', 'lha', 'bz', 'tgz', 'cab', 'iso', 'dmg', 'xz', 'z', 'ar', 'war', 'ear', 'pak', 'hqx', 'arj', 'zoo', 'rpm', 'deb', 'apk' ]; private string $name; private string $sha; private string $mode; private int $timestamp; private int $size; private bool $isDir; private string $mediaType; private string $category; private bool $binary; public function __construct( string $name, string $sha, string $mode, int $timestamp, int $size, string $contents = '' ) { $this->name = $name; $this->sha = $sha; $this->mode = $mode; $this->timestamp = $timestamp; $this->size = $size; $this->isDir = $mode === '40000' || $mode === '040000'; $buffer = $this->isDir ? '' : $contents; $this->mediaType = $this->detectMediaType( $buffer ); $this->category = $this->detectCategory( $name ); $this->binary = $this->detectBinary(); } public function compare( File $other ): int { return $this->isDir !== $other->isDir ? ($this->isDir ? -1 : 1) : strcasecmp( $this->name, $other->name ); } public function renderListEntry( FileRenderer $renderer ): void { $renderer->renderListEntry( $this->name, $this->sha, $this->mode, $this->resolveIcon(), $this->timestamp, $this->size ); } public function renderMedia( FileRenderer $renderer, string $url ): bool { return $renderer->renderMedia( $this, $url, $this->mediaType ); } public function renderSize( FileRenderer $renderer ): void { $renderer->renderSize( $this->size ); } public function highlight( FileRenderer $renderer, string $content ): string { return $renderer->highlight( $this->name, $content, $this->mediaType ); } public function isImage(): bool { return $this->category === self::CAT_IMAGE; } public function isVideo(): bool { return $this->category === self::CAT_VIDEO; } public function isAudio(): bool { return $this->category === self::CAT_AUDIO; } public function isText(): bool { return $this->category === self::CAT_TEXT; } public function isBinary(): bool { return $this->binary; } public function isName( string $name ): bool { return $this->name === $name; } public function emitRawHeaders(): void { header( "Content-Type: " . $this->mediaType ); header( "Content-Length: " . $this->size ); header( "Content-Disposition: attachment; filename=\"" . addslashes( basename( $this->name ) ) . "\"" ); } private function resolveIcon(): string { return $this->isDir ? 'fa-folder' : (str_contains( $this->mediaType, 'application/pdf' ) ? 'fa-file-pdf' : match( $this->category ) { self::CAT_ARCHIVE => 'fa-file-archive', self::CAT_IMAGE => 'fa-file-image', self::CAT_AUDIO => 'fa-file-audio', self::CAT_VIDEO => 'fa-file-video', self::CAT_TEXT => 'fa-file-code', default => 'fa-file', }); } private function detectMediaType( string $buffer ): string { if( $buffer === '' ) return 'application/x-empty'; $finfo = new finfo( FILEINFO_MIME_TYPE ); $mediaType = $finfo->buffer( $buffer ); return $mediaType ?: 'application/octet-stream'; } private function detectCategory( string $filename = '' ): string { $parts = explode( '/', $this->mediaType ); return match( true ) { $parts[0] === 'image' => self::CAT_IMAGE, $parts[0] === 'video' => self::CAT_VIDEO, $parts[0] === 'audio' => self::CAT_AUDIO, $parts[0] === 'text' => self::CAT_TEXT, $this->isArchiveFile( $filename ) => self::CAT_ARCHIVE, str_contains( $this->mediaType, 'compressed' ) => self::CAT_ARCHIVE, default => self::CAT_BINARY, }; } private function detectBinary(): bool { return $this->mediaType !== 'application/x-empty' && !str_starts_with( $this->mediaType, 'text/' ); } private function isArchiveFile( string $filename ): bool { return in_array( strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) ), self::ARCHIVE_EXTENSIONS, true ); } }