From 71fefcdbb7ebeb10b7320c49b53aefaa159e629b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?s=C3=B8renpeter?= Date: Tue, 20 Feb 2024 20:40:41 +0100 Subject: [PATCH] thumbs still not working... --- index.php | 1 + libs/Cropper.php | 297 +++++++++++++++++++++++++ libs/Slimdown.php | 1 + style.css | 7 +- {libs => views}/__Thumbnail.php | 9 +- views/__thumb.php | 211 ++++++++++++++++++ libs/thumb.php => views/__thumb_dm.php | 0 views/gallery.php | 42 ++++ 8 files changed, 563 insertions(+), 5 deletions(-) create mode 100644 libs/Cropper.php rename {libs => views}/__Thumbnail.php (95%) create mode 100644 views/__thumb.php rename libs/thumb.php => views/__thumb_dm.php (100%) diff --git a/index.php b/index.php index 03d8371..8af58f6 100644 --- a/index.php +++ b/index.php @@ -39,6 +39,7 @@ $routes = [ //'/profile/([a-zA-Z0-9_-]+)' => 'profile.php', '/conv/([a-zA-Z0-9]{7})' => 'conv.php', // matches only twtHash of exactly 7 alphanumeric characters '/post/([a-zA-Z0-9]{7})' => 'post.php', // matches only twtHash of exactly 7 alphanumeric characters + //'/thumb' => 'thumb.php', '/webmention' => 'webmention_endpoint.php', ]; diff --git a/libs/Cropper.php b/libs/Cropper.php new file mode 100644 index 0000000..59ed6e0 --- /dev/null +++ b/libs/Cropper.php @@ -0,0 +1,297 @@ + + * @package CoffeeCode\Cropper + */ +class Cropper +{ + /** @var string */ + private string $cachePath; + + /** @var string */ + private string $imagePath; + + /** @var string */ + private string $imageName; + + /** @var string */ + private string $imageMime; + + /** @var int */ + private int $quality; + + /** @var int */ + private int $compressor; + + /**@var bool */ + private bool $webP; + + /** + * Allow jpg and png to thumb and cache generate + * @var array allowed media types + */ + private static array $allowedExt = ['image/jpeg', "image/png"]; + + /** @var ConversionFailedException */ + public ConversionFailedException $exception; + + /** + * Cropper constructor. + * compressor must be 1-9 + * quality must be 1-100 + * + * @param string $cachePath + * @param int $quality + * @param int $compressor + * @param bool $webP + * @throws Exception + */ + public function __construct(string $cachePath, int $quality = 75, int $compressor = 5, bool $webP = false) + { + $this->cachePath = $cachePath; + $this->quality = $quality; + $this->compressor = $compressor; + $this->webP = $webP; + + if (!file_exists($this->cachePath) || !is_dir($this->cachePath)) { + if (!mkdir($this->cachePath, 0755, true)) { + throw new Exception("Could not create cache folder"); + } + } + } + + /** + * Make an thumb image + * + * @param string $imagePath + * @param int $width + * @param int|null $height + * @return null|string + */ + public function make(string $imagePath, int $width, int $height = null): ?string + { + if (!file_exists($imagePath)) { + return "Image not found"; + } + + $this->imagePath = $imagePath; + $this->imageName = $this->name($this->imagePath, $width, $height); + $this->imageMime = mime_content_type($this->imagePath); + + if (!in_array($this->imageMime, self::$allowedExt)) { + return "Not a valid JPG or PNG image"; + } + + return $this->image($width, $height); + } + + /** + * @param int $width + * @param int|null $height + * @return string|null + */ + private function image(int $width, int $height = null): ?string + { + $imageWebP = "{$this->cachePath}/{$this->imageName}.webp"; + $imageExt = "{$this->cachePath}/{$this->imageName}." . pathinfo($this->imagePath)['extension']; + + if ($this->webP && file_exists($imageWebP) && is_file($imageWebP)) { + return $imageWebP; + } + + if (file_exists($imageExt) && is_file($imageExt)) { + return $imageExt; + } + + return $this->imageCache($width, $height); + } + + /** + * @param string $name + * @param int|null $width + * @param int|null $height + * @return string + */ + protected function name(string $name, int $width = null, int $height = null): string + { + $filterName = mb_convert_encoding(htmlspecialchars(mb_strtolower(pathinfo($name)["filename"])), 'ISO-8859-1', + 'UTF-8'); + $formats = mb_convert_encoding('ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜüÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûýýþÿRr"!@#$%&*()_-+={[}]/?;:.,\\\'<>°ºª', + 'ISO-8859-1', 'UTF-8'); + $replace = 'aaaaaaaceeeeiiiidnoooooouuuuuybsaaaaaaaceeeeiiiidnoooooouuuyybyrr '; + $trimName = trim(strtr($filterName, $formats, $replace)); + $name = str_replace(["-----", "----", "---", "--"], "-", str_replace(" ", "-", $trimName)); + + $hash = $this->hash($this->imagePath); + $widthName = ($width ? "-{$width}" : ""); + $heightName = ($height ? "x{$height}" : ""); + + return "{$name}{$widthName}{$heightName}-{$hash}"; + } + + /** + * @param string $path + * @return string + */ + protected function hash(string $path): string + { + return hash("crc32", pathinfo($path)['basename']); + } + + /** + * Clear cache + * + * @param string|null $imagePath + * @example $t->flush("images/image.jpg"); clear image name and variations size + * @example $t->flush(); clear all image cache folder + */ + public function flush(string $imagePath = null): void + { + foreach (scandir($this->cachePath) as $file) { + $file = "{$this->cachePath}/{$file}"; + if ($imagePath && strpos($file, $this->hash($imagePath))) { + $this->imageDestroy($file); + } elseif (!$imagePath) { + $this->imageDestroy($file); + } + } + } + + /** + * @param int $width + * @param int|null $height + * @return null|string + */ + private function imageCache(int $width, int $height = null): ?string + { + list($src_w, $src_h) = getimagesize($this->imagePath); + $height = ($height ?? ($width * $src_h) / $src_w); + + $src_x = 0; + $src_y = 0; + + $cmp_x = $src_w / $width; + $cmp_y = $src_h / $height; + + if ($cmp_x > $cmp_y) { + $src_x = round(($src_w - ($src_w / $cmp_x * $cmp_y)) / 2); + $src_w = round($src_w / $cmp_x * $cmp_y); + } elseif ($cmp_y > $cmp_x) { + $src_y = round(($src_h - ($src_h / $cmp_y * $cmp_x)) / 2); + $src_h = round($src_h / $cmp_y * $cmp_x); + } + + $height = (int)$height; + $src_x = (int)$src_x; + $src_y = (int)$src_y; + $src_w = (int)$src_w; + $src_h = (int)$src_h; + + if ($this->imageMime == "image/jpeg") { + return $this->fromJpg($width, $height, $src_x, $src_y, $src_w, $src_h); + } + + if ($this->imageMime == "image/png") { + return $this->fromPng($width, $height, $src_x, $src_y, $src_w, $src_h); + } + + return null; + } + + /** + * @param string $imagePatch + */ + private function imageDestroy(string $imagePatch): void + { + if (file_exists($imagePatch) && is_file($imagePatch)) { + unlink($imagePatch); + } + } + + /** + * @param int $width + * @param int $height + * @param int $src_x + * @param int $src_y + * @param int $src_w + * @param int $src_h + * @return string + */ + private function fromJpg(int $width, int $height, int $src_x, int $src_y, int $src_w, int $src_h): string + { + $thumb = imagecreatetruecolor($width, $height); + $source = imagecreatefromjpeg($this->imagePath); + + imagecopyresampled($thumb, $source, 0, 0, $src_x, $src_y, $width, $height, $src_w, $src_h); + imagejpeg($thumb, "{$this->cachePath}/{$this->imageName}.jpg", $this->quality); + + imagedestroy($thumb); + imagedestroy($source); + + if ($this->webP) { + return $this->toWebP("{$this->cachePath}/{$this->imageName}.jpg"); + } + + return "{$this->cachePath}/{$this->imageName}.jpg"; + } + + /** + * @param int $width + * @param int $height + * @param int $src_x + * @param int $src_y + * @param int $src_w + * @param int $src_h + * @return string + */ + private function fromPng(int $width, int $height, int $src_x, int $src_y, int $src_w, int $src_h): string + { + $thumb = imagecreatetruecolor($width, $height); + $source = imagecreatefrompng($this->imagePath); + + imagealphablending($thumb, false); + imagesavealpha($thumb, true); + imagecopyresampled($thumb, $source, 0, 0, $src_x, $src_y, $width, $height, $src_w, $src_h); + imagepng($thumb, "{$this->cachePath}/{$this->imageName}.png", $this->compressor); + + imagedestroy($thumb); + imagedestroy($source); + + if ($this->webP) { + return $this->toWebP("{$this->cachePath}/{$this->imageName}.png"); + } + + return "{$this->cachePath}/{$this->imageName}.png"; + } + + /** + * @param string $image + * @param bool $unlinkImage + * @return string + */ + public function toWebP(string $image, $unlinkImage = true): string + { + try { + $webPConverted = pathinfo($image)["dirname"] . "/" . pathinfo($image)["filename"] . ".webp"; + WebPConvert::convert($image, $webPConverted, ["default-quality" => $this->quality]); + + if ($unlinkImage) { + unlink($image); + } + + return $webPConverted; + } catch (ConversionFailedException $exception) { + $this->exception = $exception; + return $image; + } + } +} diff --git a/libs/Slimdown.php b/libs/Slimdown.php index 6e2c8d2..60b2080 100644 --- a/libs/Slimdown.php +++ b/libs/Slimdown.php @@ -115,6 +115,7 @@ class Slimdown { // Substitute _ and * in links so they don't break the URLs $link = str_replace (['_', '*'], ['{^^^}', '{~~~}'], $link); return sprintf ('\'%s\'', $link, $text); + //return sprintf ('\'%s\'', $link, $text); // added support for thumbnail generation on the fly } private static function fix_link ($regs) { diff --git a/style.css b/style.css index 8154c09..d00853c 100644 --- a/style.css +++ b/style.css @@ -256,6 +256,11 @@ nav.pagnation { /* === REFRESH === */ +progress, +progress::-moz-progress-bar { +/* border: revert;*/ +} + #refreshInfo { display: block; text-align: center: @@ -297,4 +302,4 @@ footer { border-top: thin solid grey; margin-top: 1rem; text-align: center; -} \ No newline at end of file +} diff --git a/libs/__Thumbnail.php b/views/__Thumbnail.php similarity index 95% rename from libs/__Thumbnail.php rename to views/__Thumbnail.php index bcef53a..38f537f 100644 --- a/libs/__Thumbnail.php +++ b/views/__Thumbnail.php @@ -42,15 +42,16 @@ function createThumbnail($src, $dest, $thumbHeight) { return true; } -$galleryDir = "gallery/"; -$thumbDir = "thumb/"; -$allowedExtensions = array("jpg", "jpeg", "png", "gif", "webp"); // Remove "mp4" from allowed extensions +// $galleryDir = "gallery/"; +$thumbDir = "private/thumb/"; +$allowedExtensions = array("jpg", "jpeg", "png", "gif", "webp"); // Check and create the "thumb" directory if it doesn't exist if (!file_exists($thumbDir)) { mkdir($thumbDir); } +/* $galleryFiles = scandir($galleryDir); foreach ($galleryFiles as $file) { @@ -75,4 +76,4 @@ foreach ($galleryFiles as $file) { } } -?> \ No newline at end of file +*/ diff --git a/views/__thumb.php b/views/__thumb.php new file mode 100644 index 0000000..ce6fba0 --- /dev/null +++ b/views/__thumb.php @@ -0,0 +1,211 @@ + + * + * @see https://gist.github.com/dmkuznetsov/f0038b578c1cb6ccd44d + * @license MIT + */ +define('DEFAULT_WIDTH', 300); +define('DEFAULT_HEIGHT', 300); +define('DEFAULT_QUALITY', 100); +define('CACHE_DIR', rtrim(sys_get_temp_dir(), '/') . '/thumbnails/'); +define('DOCUMENT_ROOT', $_SERVER['DOCUMENT_ROOT']); + +//if (!isset($_GET['image'], $_GET['width'], $_GET['height'])) { +if (!isset($_GET['image'])) { + header('HTTP/1.1 400 Bad Request'); + //echo 'Error: no image/width/height was specified'; + echo 'Error: no image was specified'; + exit(); +} + +// Made width and height optional and define their defaults. +// by: sp@darch.dk (with help from chatGPT, 2024-02-03) +if (!isset($_GET['width'], $_GET['height'])) { + $resizeWidth = $width = DEFAULT_WIDTH; + $resizeHeight = $height = DEFAULT_HEIGHT; +} else { + $resizeWidth = $width = (int)$_GET['width']; + $resizeHeight = $height = (int)$_GET['height']; +} + +$quality = (isset($_GET['quality'])) ? (int)$_GET['quality'] : DEFAULT_QUALITY; +$nocache = isset($_GET['nocache']); + +if (!is_dir(CACHE_DIR)) { + mkdir(CACHE_DIR, 0755, true); +} + +// Make sure we can read and write the cache directory +if (!is_readable(CACHE_DIR)) { + header('HTTP/1.1 500 Internal Server Error'); + echo 'Error: the cache directory is not readable'; + exit(); +} else if (!is_writable(CACHE_DIR)) { + header('HTTP/1.1 500 Internal Server Error'); + echo 'Error: the cache directory is not writable'; + exit(); +} + +$originalImageSource = (string)$_GET['image']; +$image = getImagePath($originalImageSource); + +if ($image[0] != '/' || strpos(dirname($image), ':') || preg_match('/(\.\.|<|>)/', $image)) { + header('HTTP/1.1 400 Bad Request'); + echo 'Error: malformed image path. Image paths must begin with \'/\''; + exit(); +} + +if (!$image || !file_exists($image)) { + header('HTTP/1.1 404 Not Found'); + echo 'Error: image does not exist: ' . $image; + exit(); +} + +$size = getimagesize($image); +$mime = $size['mime']; + +if (substr($mime, 0, 6) != 'image/') { + header('HTTP/1.1 400 Bad Request'); + echo 'Error: requested file is not an accepted type: ' . $image; + exit(); +} + +$originalWidth = $size[0]; +$originalHeight = $size[1]; +$thumbHash = md5($image . $width . $height . $quality); +$thumbPath = CACHE_DIR . $thumbHash; + +if (!$nocache && file_exists($thumbPath)) { + $imageModified = filemtime($image); + $thumbModified = filemtime($thumbPath); + if ($imageModified < $thumbModified) { + $data = file_get_contents($thumbPath); + findBrowserCache(md5($data), gmdate('D, d M Y H:i:s', $thumbModified) . ' GMT'); + header("Content-type: $mime"); + header('Content-Length: ' . strlen($data)); + echo $data; + exit(); + } +} + +$saveAlphaChannel = false; +$imageCreationFunction = 'imagecreatefromjpeg'; +$imageOutputFunction = 'imagejpeg'; +$dst = imagecreatetruecolor($width, $height); +switch ($size['mime']) { + case 'image/gif': + $imageCreationFunction = 'imagecreatefromgif'; + $imageOutputFunction = 'imagepng'; + $quality = round(10 - ($quality / 10)); + $saveAlphaChannel = true; + break; + case 'image/x-png': + case 'image/png': + $imageCreationFunction = 'imagecreatefrompng'; + $imageOutputFunction = 'imagepng'; + $quality = round(10 - ($quality / 10)); + $saveAlphaChannel = true; + break; +} +$src = $imageCreationFunction($image); + +if ($saveAlphaChannel) { + imagealphablending($dst, false); + imagesavealpha($dst, true); +} + +$ratio = $originalWidth / $originalHeight; +if ($width / $height <= $ratio) { + $resizeWidth = $height * $ratio; +} else { + $resizeHeight = $width / $ratio; +} + +// This is where the image is being resized +imagecopyresampled($dst, $src, 0, 0, 0, 0, $resizeWidth, $resizeHeight, $originalWidth, $originalHeight); + +$imageOutputFunction($dst, $thumbPath, $quality); +ob_start(); +$imageOutputFunction($dst, null, $quality); +$data = ob_get_contents(); +ob_end_clean(); + +imagedestroy($src); +imagedestroy($dst); + +if ($nocache && file_exists($image)) { + unlink($image); +} + +findBrowserCache(md5($data), gmdate('D, d M Y H:i:s', filemtime($thumbPath)) . ' GMT'); +header("Content-type: $mime"); +header('Content-Length: ' . strlen($data)); +echo $data; +exit; + +function getImagePath($image) +{ + if (!filter_var($image, FILTER_VALIDATE_URL)) { + $image = DOCUMENT_ROOT . '/' . ltrim($image, '/'); + } else { + $ext = pathinfo(preg_replace('/^(s?f|ht)tps?:\/\/[^\/]+/i', '', $image), PATHINFO_EXTENSION); + $tmpImagePath = CACHE_DIR . md5($image); + if ($ext) { + $tmpImagePath .= '.' . $ext; + } + if (!file_exists($tmpImagePath)) { + $imageContent = file_get_contents($image); + if (!$imageContent) { + header('HTTP/1.1 400 Bad Request'); + echo 'Error: can not download image'; + exit(); + } else { + file_put_contents($tmpImagePath, $imageContent); + } + } + $image = $tmpImagePath; + } + return $image; +} + + +function findBrowserCache($tag, $lastModified) +{ + header("Last-Modified: $lastModified"); + header("ETag: \"{$tag}\""); + + $ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? + stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : + false; + + $ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? + stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : + false; + + if (!$ifModifiedSince && !$ifNoneMatch) { + return; + } + + if ($ifNoneMatch && $ifNoneMatch != $tag && $ifNoneMatch != '"' . $tag . '"') { + return; + } + + if ($ifModifiedSince && $ifModifiedSince != $lastModified) { + return; + } + + header('HTTP/1.1 304 Not Modified'); + exit(); +} \ No newline at end of file diff --git a/libs/thumb.php b/views/__thumb_dm.php similarity index 100% rename from libs/thumb.php rename to views/__thumb_dm.php diff --git a/views/gallery.php b/views/gallery.php index b209712..5c4130c 100644 --- a/views/gallery.php +++ b/views/gallery.php @@ -1,9 +1,15 @@ @@ -41,6 +47,42 @@ foreach ($twts as $twt) { foreach ($img_array as $img) { echo ''.$img[0].''; } + +/* + $doc = new DOMDocument(); + $doc->loadHTML($twt->content); + $img_array = $doc->getElementsByTagName('img'); + + foreach ($img_array as $img) { + $url = $img->getAttribute('src'); + $alt = $img->getAttribute('alt'); + //echo "
Title:" . $img->getAttribute('title'); + + echo "".$alt.""; + } + +/* + foreach ($img_array as $img) { + echo $img[0]; + $extension = pathinfo($img, PATHINFO_EXTENSION); + if (in_array(strtolower($extension), $allowedExtensions)) { + $imageURL = $galleryDir . $img; + $thumbURL = $thumbDir . $img; + + if (!file_exists($thumbURL)) { + createThumbnail($imageURL, $thumbURL, 200); // The thumbnail will have a height of 200 pixels. + } + + echo ' + + '; + } + + + } +*/ + + } ?>