123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
<?php
class Img {
private $webPath;
private $cache;
private $allowedImageExtensions = [
'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'avif', 'heic',
'tiff', 'bmp', 'ico', 'psd', 'cur', 'jfif',
'cr2', 'cr3', 'nef', 'nrw', 'arw', 'raf', 'rw2', 'orf', 'pef', 'dng', '3fr', 'x3f',
'ai', 'fig', 'sketch', 'xcf'
];
public function __construct($webPath) {
$this->webPath = $webPath;
$this->cache = initializeCache();
}
public function isImageViewable($extension) {
global $config;
if (empty($extension) || trim($extension) === '') {
return false;
}
$extension = strtolower($extension);
if (!in_array($extension, $this->allowedImageExtensions)) {
return false;
}
$settingKey = getExtensionSetting($extension, 'viewing');
if ($settingKey !== null) {
return isset($config['viewable_files'][$settingKey]) ?
$config['viewable_files'][$settingKey] : false;
}
return false;
}
public function renderImageViewer($fullPath, $filename, $extension, $currentPath, $viewMode = 'default') {
global $router, $config;
$pathParts = explode('/', trim($currentPath, '/'));
$lastPart = end($pathParts);
if ($lastPart === $filename) {
array_pop($pathParts);
$currentPath = implode('/', $pathParts);
}
$relativePath = $currentPath ? $currentPath . '/' . $filename : $filename;
if ($this->cache->hasValidFile($fullPath, 'imageview_' . $viewMode)) {
echo $this->cache->getFile($fullPath, 'imageview_' . $viewMode);
exit;
}
ob_start();
$this->serveImageView($fullPath, $filename, $extension, $currentPath, $viewMode);
$output = ob_get_clean();
$this->cache->setFile($fullPath, $output, 'imageview_' . $viewMode);
echo $output;
exit;
}
private function serveImageView($fullPath, $filename, $extension, $currentPath, $viewMode = 'default') {
global $router, $config;
$disableRawImageViewing = isset($config['main']['disable_raw_image_conversion']) ?
$config['main']['disable_raw_image_conversion'] : true;
$maxResolution = isset($config['main']['max_image_resolution']) ?
$config['main']['max_image_resolution'] : '1920x1080';
list($maxWidth, $maxHeight) = explode('x', $maxResolution);
$maxWidth = (int)$maxWidth;
$maxHeight = (int)$maxHeight;
$fileSize = filesize($fullPath);
$fileSizeFormatted = $this->formatBytes($fileSize);
$fileModified = date('Y-m-d H:i:s', filemtime($fullPath));
$dimensions = $this->getImageDimensions($fullPath, $extension);
$originalDimensionText = $dimensions ? $dimensions['width'] . ' × ' . $dimensions['height'] . ' px' : 'Unknown';
$exceedsResolution = false;
if ($dimensions && ($dimensions['width'] > $maxWidth || $dimensions['height'] > $maxHeight)) {
$exceedsResolution = true;
}
$isTrueScale = ($viewMode === 'truescale');
$isSvgTrueScale = ($extension === 'svg' && $isTrueScale);
$needsSpecialHandling = in_array($extension, [
'tiff', 'psd', 'heic',
'cr2', 'cr3', 'nef', 'nrw', 'arw', 'raf', 'rw2', 'orf', 'pef', 'dng', '3fr', 'x3f',
'ai', 'fig', 'sketch', 'xcf'
]);
$downloadUrl = $router->generateFileURL($currentPath, $filename, 'download');
$viewUrl = $router->generateFileURL($currentPath, $filename, 'view', 'default');
$originalRawUrl = $router->generateFileURL($currentPath, $filename, 'view', 'raw');
$trueScaleUrl = $viewUrl . '&scale=true';
$imageDisplayUrl = $originalRawUrl;
$currentFullUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$backUrl = preg_replace('/\/[^\/]+\/\?.*$/', '/', $currentFullUrl);
if (!preg_match('/\?/', $currentFullUrl)) {
$backUrl = preg_replace('/\/[^\/]+\/$/', '/', $currentFullUrl);
}
$securityStatus = getSecurityStatus();
$lockIcon = $securityStatus['secure']
? $this->webPath . '/.indexer_files/icons/app/green.png'
: $this->webPath . '/.indexer_files/icons/app/red.png';
$convertedDimensions = null;
$dimensionText = $originalDimensionText;
if ($disableRawImageViewing && $needsSpecialHandling) {
$showError = true;
$errorMessage = "Raw image viewing is disabled on this server.<br>Please download the file to view it.";
} else {
$showError = false;
$errorMessage = '';
if (($exceedsResolution && !$isTrueScale) || $needsSpecialHandling) {
$conversionResult = $this->attemptImageConversion($fullPath, $extension, $maxWidth, $maxHeight);
if ($conversionResult) {
$imageDisplayUrl = $conversionResult['data'];
$convertedDimensions = $conversionResult['dimensions'];
if ($convertedDimensions) {
$dimensionText = $originalDimensionText . ' (converted: ' .
$convertedDimensions['width'] . ' × ' .
$convertedDimensions['height'] . ' px)';
}
} elseif ($needsSpecialHandling) {
$showError = true;
$errorMessage = "This image format (" . strtoupper($extension) . ") requires special server extensions to display.<br>Please download the file or view raw to see the image.";
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="application-name" content="5q12's Indexer">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Indexer">
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#2a2a2a">
<link rel="manifest" href="<?php echo $this->webPath; ?>/.indexer_files/local_api/manifest.json">
<title><?php echo $filename; ?></title>
<link rel="icon" type="image/x-icon" href="<?php echo $this->webPath; ?>/.indexer_files/favicon/icon.ico">
<link rel="icon" type="image/png" sizes="16x16" href="<?php echo $this->webPath; ?>/.indexer_files/favicon/16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="<?php echo $this->webPath; ?>/.indexer_files/favicon/32x32.png">
<link rel="icon" type="image/png" sizes="48x48" href="<?php echo $this->webPath; ?>/.indexer_files/favicon/48x48.png">
<link rel="icon" type="image/png" sizes="96x96" href="<?php echo $this->webPath; ?>/.indexer_files/favicon/96x96.png">
<link rel="icon" type="image/png" sizes="144x144" href="<?php echo $this->webPath; ?>/.indexer_files/favicon/144x144.png">
<link rel="icon" type="image/png" sizes="192x192" href="<?php echo $this->webPath; ?>/.indexer_files/favicon/192x192.png">
<link rel="apple-touch-icon" sizes="180x180" href="<?php echo $this->webPath; ?>/.indexer_files/favicon/180x180.png">
<link rel="stylesheet" href="<?php echo $this->webPath; ?>/.indexer_files/local_api/style/base-2.0.0-r0.min.css">
<link rel="stylesheet" href="<?php echo $this->webPath; ?>/.indexer_files/local_api/style/img-2.0.0-r0.min.css">
</head>
<body>
<div class="security-bar">
<span class="security-lock" data-tooltip="<?php echo $securityStatus['secure'] ? 'Connection is secure (HTTPS)' : 'Connection is not secure - Consider using HTTPS'; ?>">
<img src="<?php echo htmlspecialchars($lockIcon); ?>" alt="<?php echo $securityStatus['secure'] ? 'Secure' : 'Not Secure'; ?>">
</span>
<div class="security-bar-filename"><?php echo htmlspecialchars($filename); ?></div>
<div class="security-bar-buttons">
<a href="<?php echo htmlspecialchars($originalRawUrl); ?>" class="security-bar-btn">Raw</a>
<a href="#" class="security-bar-btn active">View</a>
</div>
</div>
<div class="image-container">
<div class="image-wrapper <?php echo ($isTrueScale && !$isSvgTrueScale) ? 'truescale' : 'fitted'; ?>">
<?php if ($showError): ?>
<div class="error-message">
<?php echo $errorMessage; ?>
</div>
<?php elseif ($extension === 'svg'): ?>
<object data="<?php echo htmlspecialchars($rawUrl); ?>" type="image/svg+xml">
<img src="<?php echo htmlspecialchars($imageDisplayUrl); ?>" alt="<?php echo htmlspecialchars($filename); ?>">
</object>
<?php else: ?>
<img src="<?php echo htmlspecialchars($imageDisplayUrl); ?>" alt="<?php echo htmlspecialchars($filename); ?>">
<?php endif; ?>
</div>
</div>
<div class="image-info">
<div class="info-grid">
<div class="info-label">Filename:</div>
<div class="info-value"><?php echo htmlspecialchars($filename); ?></div>
<div class="info-label">Type:</div>
<div class="info-value"><?php echo strtoupper(htmlspecialchars($extension)); ?> Image</div>
<div class="info-label">Dimensions:</div>
<div class="info-value"><?php echo htmlspecialchars($dimensionText); ?></div>
<div class="info-label">File Size:</div>
<div class="info-value"><?php echo htmlspecialchars($fileSizeFormatted); ?></div>
<div class="info-label">Modified:</div>
<div class="info-value"><?php echo htmlspecialchars($fileModified); ?></div>
</div>
<div class="action-buttons">
<a href="<?php echo htmlspecialchars($backUrl); ?>" class="action-btn primary">← Back to Folder</a>
<?php if ($isTrueScale): ?>
<a href="<?php echo htmlspecialchars($viewUrl); ?>" class="action-btn">Fit to Screen</a>
<?php else: ?>
<a href="<?php echo htmlspecialchars($trueScaleUrl); ?>" class="action-btn">View True Scale</a>
<?php endif; ?>
<a href="<?php echo htmlspecialchars($originalRawUrl); ?>" class="action-btn" target="_blank">Open Raw</a>
<a href="<?php echo htmlspecialchars($downloadUrl); ?>" class="action-btn">Download</a>
</div>
</div>
</body>
</html>
<?php
}
private function attemptImageConversion($fullPath, $extension, $maxWidth, $maxHeight) {
if ($this->cache->hasValidImage($fullPath, $maxWidth, $maxHeight)) {
$cachedData = $this->cache->getImageDataUri($fullPath, $maxWidth, $maxHeight);
if ($cachedData) {
return [
'data' => $cachedData,
'dimensions' => $this->getCachedImageDimensions($fullPath, $maxWidth, $maxHeight)
];
}
}
if (extension_loaded('imagick')) {
try {
$imagick = new Imagick($fullPath);
$width = $imagick->getImageWidth();
$height = $imagick->getImageHeight();
$newWidth = $width;
$newHeight = $height;
if ($width > $maxWidth || $height > $maxHeight) {
$ratio = min($maxWidth / $width, $maxHeight / $height);
$newWidth = (int)($width * $ratio);
$newHeight = (int)($height * $ratio);
$imagick->thumbnailImage($newWidth, $newHeight, true);
}
$imagick->setImageFormat('png');
$imageBlob = $imagick->getImageBlob();
$imagick->clear();
$this->cache->setImage($fullPath, $imageBlob, $maxWidth, $maxHeight);
return [
'data' => 'data:image/png;base64,' . base64_encode($imageBlob),
'dimensions' => [
'width' => $newWidth,
'height' => $newHeight
]
];
} catch (Exception $e) {
}
}
if (function_exists('imagecreatefromstring')) {
$imageData = @file_get_contents($fullPath);
if ($imageData !== false) {
$image = @imagecreatefromstring($imageData);
if ($image !== false) {
$width = imagesx($image);
$height = imagesy($image);
$newWidth = $width;
$newHeight = $height;
if ($width > $maxWidth || $height > $maxHeight) {
$ratio = min($maxWidth / $width, $maxHeight / $height);
$newWidth = (int)($width * $ratio);
$newHeight = (int)($height * $ratio);
$resized = imagecreatetruecolor($newWidth, $newHeight);
imagealphablending($resized, false);
imagesavealpha($resized, true);
imagecopyresampled($resized, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
imagedestroy($image);
$image = $resized;
}
ob_start();
imagepng($image);
$pngData = ob_get_clean();
imagedestroy($image);
$this->cache->setImage($fullPath, $pngData, $maxWidth, $maxHeight);
return [
'data' => 'data:image/png;base64,' . base64_encode($pngData),
'dimensions' => [
'width' => $newWidth,
'height' => $newHeight
]
];
}
}
}
return false;
}
private function getCachedImageDimensions($fullPath, $maxWidth, $maxHeight) {
$originalDims = $this->getImageDimensions($fullPath, pathinfo($fullPath, PATHINFO_EXTENSION));
if ($originalDims) {
$width = $originalDims['width'];
$height = $originalDims['height'];
if ($width > $maxWidth || $height > $maxHeight) {
$ratio = min($maxWidth / $width, $maxHeight / $height);
return [
'width' => (int)($width * $ratio),
'height' => (int)($height * $ratio)
];
}
return $originalDims;
}
return null;
}
private function getImageDimensions($fullPath, $extension) {
$extension = strtolower($extension);
if ($extension === 'gif') {
if (extension_loaded('imagick')) {
try {
$imagick = new Imagick($fullPath);
$imagick->setIteratorIndex(0);
$width = $imagick->getImageWidth();
$height = $imagick->getImageHeight();
$imagick->clear();
return [
'width' => $width,
'height' => $height
];
} catch (Exception $e) {
}
}
if (function_exists('imagecreatefromgif')) {
$image = @imagecreatefromgif($fullPath);
if ($image !== false) {
$width = imagesx($image);
$height = imagesy($image);
imagedestroy($image);
return [
'width' => $width,
'height' => $height
];
}
}
}
if (extension_loaded('imagick')) {
try {
$imagick = new Imagick($fullPath);
$width = $imagick->getImageWidth();
$height = $imagick->getImageHeight();
$imagick->clear();
return [
'width' => $width,
'height' => $height
];
} catch (Exception $e) {
}
}
$getimagesizeFormats = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico', 'tiff', 'jfif'];
if (in_array($extension, $getimagesizeFormats)) {
$imageInfo = @getimagesize($fullPath);
if ($imageInfo !== false) {
return [
'width' => $imageInfo[0],
'height' => $imageInfo[1]
];
}
}
if ($extension === 'svg') {
$svgContent = @file_get_contents($fullPath);
if ($svgContent !== false) {
if (preg_match('/width=["\'](\d+(?:\.\d+)?)["\']/', $svgContent, $widthMatch) &&
preg_match('/height=["\'](\d+(?:\.\d+)?)["\']/', $svgContent, $heightMatch)) {
return [
'width' => (int)$widthMatch[1],
'height' => (int)$heightMatch[1]
];
}
if (preg_match('/viewBox=["\'][\d\s]+\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)["\']/', $svgContent, $viewBoxMatch)) {
return [
'width' => (int)$viewBoxMatch[1],
'height' => (int)$viewBoxMatch[2]
];
}
}
}
if ($extension === 'avif' && function_exists('exif_read_data')) {
$exifData = @exif_read_data($fullPath);
if ($exifData !== false && isset($exifData['COMPUTED']['Width']) && isset($exifData['COMPUTED']['Height'])) {
return [
'width' => $exifData['COMPUTED']['Width'],
'height' => $exifData['COMPUTED']['Height']
];
}
}
if (function_exists('imagecreatefromstring')) {
$imageData = @file_get_contents($fullPath);
if ($imageData !== false) {
$image = @imagecreatefromstring($imageData);
if ($image !== false) {
$width = imagesx($image);
$height = imagesy($image);
imagedestroy($image);
return [
'width' => $width,
'height' => $height
];
}
}
}
return null;
}
private function formatBytes($size) {
if ($size === null) return '-';
$units = ['B', 'KB', 'MB', 'GB'];
for ($i = 0; $size > 1024 && $i < count($units) - 1; $i++) {
$size /= 1024;
}
return round($size, 2) . ' ' . $units[$i];
}
}
?>