NanoGal/app/FileAndDir.php
2024-08-23 16:13:41 +02:00

487 lines
21 KiB
PHP

<?php
namespace App;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\Autolink\AutolinkExtension;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\TaskList\TaskListExtension;
use League\CommonMark\MarkdownConverter;
use Utils\Utils;
class FileAndDir {
private $currentDir;
private $requestedDir;
private $config = [];
private $fileExtensions = [
'deny' => ['sh', 'html', 'js', 'vbs', 'exe', 'md'],
'txt' => ['txt', 'md'],
'img' => ['jpeg', 'jpg', 'jpe', 'bmp', 'webp', 'gif', 'png'],
'pdf' => ['pdf'],
'zip' => ['7z', 'zip', 'gz', 'tar', 'rar', 'r[0-9]{2,}'],
'doc' => ['odt', 'doc', 'docx'],
'pres' => ['odp', 'ppt', 'pptx'],
'spread' => ['ods', 'xls', 'xlsx'],
'video' => ['ogv', 'mp4', 'mpg', 'mpeg', 'mov', 'avi', 'wmv', 'flv', 'webm'],
'audio' => ['aiff', 'aif', 'wma', 'aac', 'flac', 'mp3', 'ogg', 'm4a'],
];
private $appUrl;
/**
* Initializes the object with directory paths and configuration settings.
*
* This constructor initializes the object with the provided directory paths and configuration.
* It verifies that the given paths are authorized using utility methods. If any path is unauthorized,
* the script terminates with an error message.
*
* @param string $dir The current directory path to be set.
* @param string $requestedDir The requested directory path to be set, relative to the 'photos' directory.
* @param array $config Configuration settings to be used by the object.
*
* @throws \Exception If an unauthorized access attempt is detected.
*/
function __construct(string $dir, string $requestedDir, array $config) {
if (!Utils::isPathAuthorized($dir)) {
die("ERROR 02: Unauthorized access!");
}
if (!Utils::isPathAuthorized('photos' . $requestedDir)) {
die("ERROR 03: Unauthorized access!");
}
$this->currentDir = $dir;
$this->requestedDir = $requestedDir;
$this->config = $config;
if ($config['showShareLink'] === true) {
$this->appUrl = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_NAME'];
}
}
/**
* Generates a breadcrumb navigation based on the requested directory.
*
* This method creates an HTML breadcrumb navigation trail from the requested directory path.
* It links each directory level, except for the last one, which is displayed as plain text.
* If the requested directory is empty or set to "photos", it returns an empty string.
*
* @return string The HTML markup for the breadcrumb navigation, or an empty string if no breadcrumb is needed.
*/
public function makeBreadcrumb(): string {
if ($this->requestedDir !== '' && $this->requestedDir !== 'photos') {
$breadcrumb_navigation = '<div id="breadcrumb">';
$breadcrumb_navigation .= '<a href="?dir=">Home</a> > ';
$navitems = explode("/", htmlspecialchars($this->requestedDir));
$path = '';
foreach ($navitems as $index => $item) {
if ($index === 0) continue;
$path .= $item;
if ($index === count($navitems) - 1) {
$breadcrumb_navigation .= htmlspecialchars($item);
} else {
$breadcrumb_navigation .= '<a href="?dir=/' . htmlspecialchars($path) . '">' . htmlspecialchars($item) . '</a> > ';
$path .= '/';
}
}
$breadcrumb_navigation .= '</div>';
return $breadcrumb_navigation;
} else {
return '';
}
}
/**
* Checks if the specified file is readable and updates a global message if it is not.
*
* This function verifies if the given file has read permissions. If the file is not
* readable, it sets a global `$messages` variable with a warning message and a link
* to a guide on how to change file permissions.
*
* @param string $file The path to the file or directory to check.
*
* @global string $messages A global variable used to store the error message if permissions are incorrect.
*/
function checkpermissions(string $file): void {
global $messages;
if (!is_readable($file)) {
$messages = "At least one file or folder has wrong permissions. "
. "Learn how to <a href='http://minigal.dk/faq-reader/items/"
. "how-do-i-change-file-permissions-chmod.html' target='_blank'>"
. "set file permissions</a>";
}
}
/**
* Lists directories and files within the current directory.
*
* This method scans the current directory and generates a list of directories and files.
* Directories and files are categorized, and thumbnails are created or fetched based on
* the file type. The results are sorted and returned in an associative array containing
* two keys: 'dir' for directories and 'file' for files. If the directory cannot be opened,
* the script terminates with an error message.
*
* @return array An associative array with two keys:
* - 'dir': An array of directories with details like name, thumbnail URL, link, type, date, and size.
* - 'file': An array of files with details like name, thumbnail URL, link, type, date, size, data attributes, and image captions.
*
* @throws RuntimeException If the current directory cannot be opened.
*/
function listDirs(): array {
$listDir = $listFile = [];
if (is_dir($this->currentDir) && $handle = opendir($this->currentDir)) {
while (false !== ($file = readdir($handle))) {
if (in_array($file, $this->config['skipObjects'])) {
continue;
}
if (is_dir($this->currentDir . '/' . $file)) {
if ($file != "." && $file != "..") {
if ($this->defineThumbnailDir($file)) {
$thumb = $this->makeFolderThumbUrl($file);
} else {
$thumb = $this->getfirstImage($file);
}
$listDir[] = [
'name' => $file,
'thumb' => $thumb,
'link' => '?dir=' . urlencode($this->requestedDir . '/' . $file),
'type' => 'folder',
'date' => date("Y-m-d", filemtime($this->currentDir . '/' . $file)),
'size' => filesize($this->currentDir . '/' . $file),
'dataAttr' => null,
'imgCaption' => null
];
}
}
if (is_file($this->currentDir . '/' . $file) && $file != "." && $file != ".." && $file != "folder.jpg") {
$extension = $this->getExtension($file);
if ($extension === 'deny') {
continue;
}
if ($extension !== 'img') {
$thumb = '/assets/images/filetype_' . $extension . '.svg';
} else {
$thumb = $this->makeImageThumbUrl($file);
}
$caption = $this->computeCaption($file);
$listFile[] = [
'name' => pathinfo($file, PATHINFO_FILENAME),
'thumb' => $thumb,
'link' => $this->currentDir . '/' . $file,
'type' => $extension,
'date' => date("Y-m-d", filemtime($this->currentDir . '/' . $file)),
'size' => filesize($this->currentDir . '/' . $file),
'dataAttr' => $caption['dataAttr'],
'imgCaption' => $caption['content']
];
}
}
closedir($handle);
$order = $this->config['orderBy'] === 'desc' ? 1 : -1;
usort($listDir, function ($a, $b) use ($order) {
return $order * strcmp(strtolower($a[$this->config['sortBy']]), strtolower($b[$this->config['sortBy']]));
});
usort($listFile, function ($a, $b) use ($order) {
return $order * strcmp(strtolower($a[$this->config['sortBy']]), strtolower($b[$this->config['sortBy']]));
});
return ['dir' => $listDir, 'file' => $listFile];
} else {
die("ERROR: Could not open " . htmlspecialchars(stripslashes($this->currentDir)) . " for reading!");
}
}
/**
* Determines the type of file based on its extension.
*
* This method checks the file extension against a predefined list of known file types
* and returns a category name corresponding to the file type. If the extension does not match
* any known types, it returns 'other'.
*
* @param string $file The name of the file whose extension is to be checked.
*
* @return string The category name of the file type based on its extension. Possible values
* include 'img', 'md', 'pdf', 'zip', 'rar', 'tar', 'gzip', 'doc', 'pres',
* 'ods', 'video', 'audio', or 'other' if the extension is not recognized.
*/
private function getExtension(string $file): string {
if (!is_readable($this->currentDir . '/' . $file) || !is_file($this->currentDir . '/' . $file)) {
return 'broken';
}
$fileExtension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
foreach ($this->fileExtensions as $type => $extensions) {
foreach ($extensions as $ext) {
if (preg_match("/^$ext$/i", $fileExtension)) {
return $type;
}
}
}
return 'other';
}
/**
* Checks if a thumbnail image exists in the specified directory.
*
* This method determines if a `folder.jpg` file exists in the given directory
* within the current directory. It returns `true` if the file is found, indicating
* that the directory has a defined thumbnail; otherwise, it returns `false`.
*
* @param string $dir The name of the directory to check for a thumbnail.
*
* @return bool `true` if the `folder.jpg` file exists in the directory, `false` otherwise.
*/
private function defineThumbnailDir(string $dir): bool {
if (file_exists($this->currentDir . '/' . $dir . '/folder.jpg')) {
return true;
} else {
return false;
}
}
/**
* Generates a URL for the folder thumbnail image.
*
* This method constructs a URL to request the creation of a thumbnail image for a
* specified folder. It uses the `createthumb.php` script with query parameters for
* the thumbnail image file and its size.
*
* @param string $file The name of the folder for which to create a thumbnail.
*
* @return string The URL to request the creation of the folder thumbnail image.
*/
private function makeFolderThumbUrl(string $file): string {
$imgParams = http_build_query(
array(
'filename' => "$this->currentDir/$file/folder.jpg"
),
'',
'&amp;'
);
return 'createthumb.php?' . $imgParams;
}
/**
* Generates a URL for the image thumbnail.
*
* This method constructs a URL to request the creation of a thumbnail image for
* a given file if its extension is recognized as an image format. If the file's
* extension is not a recognized image format, it returns a URL to a default image.
*
* @param string $file The name of the image file for which to create a thumbnail.
*
* @return string The URL to request the creation of the image thumbnail or a default image URL if the extension is not recognized.
*/
private function makeImageThumbUrl(string $file): string {
$pathinfo = pathinfo($file);
$ext = strtolower($pathinfo['extension']);
if (in_array($ext, $this->fileExtensions['img'])) {
$imageName = $file;
$imgParams = 'createthumb.php?' . http_build_query(
array(
'filename' => $this->currentDir . '/' . $imageName
),
'',
'&amp;'
);
} else {
$imgParams = 'assets/images/default.svg';
}
return $imgParams;
}
/**
* Retrieves the URL for the first image found in the specified directory.
*
* This method scans the given directory for image files with recognized extensions.
* It returns a URL for the thumbnail of the first image found. If no image is found
* or if the directory does not exist, it returns a URL to a default image.
*
* @param string $dir The name of the directory to scan for images.
*
* @return string The URL for the thumbnail of the first image found, or a URL to a default image if no image is found or the directory does not exist.
*/
private function getFirstImage(string $dir): string {
$fullPath = $this->currentDir . '/' . $dir;
$imageName = 'assets/images/default.svg';
if (!is_dir($fullPath)) {
return $imageName;
}
if ($handle = opendir($fullPath)) {
while (false !== ($file = readdir($handle))) {
if ($file[0] == '.') {
continue;
}
$pathinfo = pathinfo($file);
if (empty($pathinfo['extension'])) {
continue;
}
$ext = strtolower($pathinfo['extension']);
if (in_array($ext, $this->fileExtensions['img'])) {
$imageName = 'createthumb.php?' . http_build_query(
array(
'filename' => $fullPath . '/' . $file
),
'',
'&amp;'
);
closedir($handle);
return $imageName;
}
}
closedir($handle);
}
return $imageName;
}
/**
* Retrieves and converts the content of a Markdown note in the current directory.
*
* This method checks if a `note.md` file exists in the current directory. If the file
* is found, its content is read and converted from Markdown to HTML using the
* `MarkdownConverter` with specified extensions. If the file does not exist, an
* empty string is returned.
*
* @return string The HTML content of the Markdown file, or an empty string if the file does not exist.
*/
public function getFolderNote(): string {
if (file_exists($this->currentDir . '/note.md')) {
$content = file_get_contents($this->currentDir . '/note.md');
$config = [];
$environment = new Environment($config);
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addExtension(new TaskListExtension());
$environment->addExtension(new AutolinkExtension());
$converter = new MarkdownConverter($environment);
return $converter->convert($content)->getContent();
} else {
return '';
}
}
/**
* Computes the caption for an image by combining Markdown content and EXIF data.
*
* This method checks for the presence of a Markdown file and EXIF data associated with
* the given image. It combines both sources of information, converting the Markdown content
* to HTML and appending EXIF data if available. The result is a caption that can be displayed
* in a Lightbox gallery, with the relevant CSS class and HTML content.
*
* @param string $img The name of the image for which to compute the caption.
*
* @return array An associative array containing:
* - `dataAttr` (string): A data attribute for the caption's CSS class, used by the Lightbox.
* - `content` (string): The HTML content of the caption, including Markdown and EXIF data if available.
*/
private function computeCaption(string $img): array {
$mdContent = $exifData = $shareLink = $listLink = '';
if (file_exists($this->currentDir . '/' . $img . '.md')) {
$mdContent = file_get_contents($this->currentDir . '/' . $img . '.md');
}
if ($this->config['displayExifInfo']) {
$exifData = $this->computeExifData($this->currentDir . '/' . $img);
}
if ($this->config['showShareLink'] === true) {
$shareLink = '## Share link';
$listLink = '
<p>
<ul>
<li><a href="#" onclick="copiedText(this);return false;">Copy thumbnail link</a> : <input class="urlToCopy" value="' . $this->appUrl . '/createthumb.php?filename=' . (urlencode($this->currentDir . '/' . $img)) . '"></li>
<li><a href="#" onclick="copiedText(this);return false;">Copy full link</a> : <input class="urlToCopy" value="' . $this->appUrl . '/' . $this->currentDir . '/' . $img . '"></li>
<li><a href="#" onclick="copiedText(this);return false;">Copy markdown link</a> : <input class="urlToCopy" value="[![](' . $this->appUrl . '/createthumb.php?filename=' . (urlencode($this->currentDir . '/' . $img)) . ')](' . $this->appUrl . '/' . $this->currentDir . '/' . $img . ')"></li>
</ul>
</p>';
}
if (empty($exifData) && empty($mdContent) && empty($shareLink)) {
return [
'dataAttr' => '',
'content' => ''
];
}
$id = uniqid();
$environment = new Environment();
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addExtension(new TaskListExtension());
$environment->addExtension(new AutolinkExtension());
$converter = new MarkdownConverter($environment);
if (!empty($exifData)) {
$exifData = "\n" . '## Exif' . "\n" . $exifData;
}
$mdContent = $mdContent . "\n" . $exifData . "\n" . $shareLink;
$captionData = [
'dataAttr' => 'description: .custom-' . $id,
'content' => '<div class="glightbox-desc custom-' . $id . '">' . $converter->convert($mdContent)->getContent() . $listLink . '</div>'
];
return $captionData;
}
/**
* Computes and formats EXIF data from an image file.
*
* This method reads EXIF data from the specified image file and formats relevant
* pieces of information into a string. It includes details such as the camera model,
* orientation, exposure time, and flash status. If any of these details are missing
* from the EXIF data, they are simply omitted from the resulting string.
*
* @param string $file The path to the image file from which to extract EXIF data.
*
* @return string A formatted string containing the EXIF data of the image, or an empty string if no relevant EXIF data is found.
*/
private function computeExifData(string $file): string {
$returnExif = '';
$exifData = @exif_read_data($file, 'EXIF');
if ($exifData !== false) {
if (isset($exifData['Model'])) {
$returnExif .= ' - Model : ' . $exifData['Model'] . "\n";
}
if (isset($exifData['Orientation'])) {
$returnExif .= ' - Orientation : ' . $exifData['Orientation'] . "\n";
}
if (isset($exifData['ExposureTime'])) {
$returnExif .= ' - Exposure Time : ' . $exifData['ExposureTime'] . "\n";
}
if (isset($exifData['Flash'])) {
$returnExif .= ' - Flash : ' . $exifData['Flash'] . "\n";
}
}
return $returnExif;
}
/**
* Determines the Lightbox class to use based on the resource type.
*
* This static method checks if the given resource type is either 'video' or 'img'.
* If so, it returns the class name 'glightbox', indicating that the resource should
* be added to a Lightbox gallery. Otherwise, it returns `null`.
*
* @param string $ressourceType The type of the resource to check (e.g., 'video' or 'img').
*
* @return string|null The Lightbox class name if the resource type is valid, otherwise `null`.
*/
static function addToLightBox(string $ressourceType): string|null {
if (in_array($ressourceType, ['video', 'img'])) {
return 'glightbox';
}
return null;
}
}