1
0
mirror of https://github.com/typemill/typemill.git synced 2025-08-06 06:07:31 +02:00

Version 1.3.4: Media Library

This commit is contained in:
trendschau
2020-04-05 19:13:10 +02:00
parent 9ab8ade909
commit be6a297b83
40 changed files with 3271 additions and 979 deletions

View File

@@ -30,10 +30,9 @@ class Folder
}
}
}
return $folderContent;
return $folderContent;
}
/*
* scans content of a folder recursively
* vars: folder path as string
@@ -301,6 +300,25 @@ class Folder
return $result;
}
public static function getItemForUrlFrontend($folderContentDetails, $url, $result = NULL)
{
foreach($folderContentDetails as $key => $item)
{
# set item active, needed to move item in navigation
if($item->urlRelWoF === $url)
{
$item->active = true;
$result = $item;
}
elseif($item->elementType === "folder")
{
$result = self::getItemForUrlFrontend($item->folderContent, $url, $result);
}
}
return $result;
}
public static function getPagingForItem($content, $item)
{
$keyPos = count($item->keyPathArray)-1;
@@ -475,6 +493,7 @@ class Folder
while($i < count($searchArray))
{
if(!isset($content[$searchArray[$i]])){ return false; }
$item = $content[$searchArray[$i]];
if($i == count($searchArray)-1)

View File

@@ -24,4 +24,39 @@ class Helpers{
$table .= '</table></body></html>';
echo $table;
}
public static function array_sort($array, $on, $order=SORT_ASC)
{
$new_array = array();
$sortable_array = array();
if (count($array) > 0) {
foreach ($array as $k => $v) {
if (is_array($v)) {
foreach ($v as $k2 => $v2) {
if ($k2 == $on) {
$sortable_array[$k] = $v2;
}
}
} else {
$sortable_array[$k] = $v;
}
}
switch ($order) {
case SORT_ASC:
asort($sortable_array);
break;
case SORT_DESC:
arsort($sortable_array);
break;
}
foreach ($sortable_array as $k => $v) {
$new_array[] = $array[$k];
}
}
return $new_array;
}
}

View File

@@ -0,0 +1,231 @@
<?php
namespace Typemill\Models;
use \URLify;
class ProcessAssets
{
# holds the path to the baseFolder
protected $baseFolder;
# holds the path to the mediaFolder
protected $mediaFolder;
# holds the path to the temporary image folder
protected $tmpFolder;
# holds the path where original images are stored
protected $originalFolder;
# holds the path where images for frontend use are stored
protected $liveFolder;
# holds the folder where the thumbs for the media library are stored
protected $thumbFolder;
# holds the folder where the thumbs for the media library are stored
public $fileFolder;
# holds the desired sizes for image resizing
protected $desiredSizes;
public function __construct($desiredSizes = NULL)
{
$this->baseFolder = getcwd() . DIRECTORY_SEPARATOR;
$this->mediaFolder = $this->baseFolder . 'media' . DIRECTORY_SEPARATOR;
$this->tmpFolder = $this->mediaFolder . 'tmp' . DIRECTORY_SEPARATOR;
$this->originalFolder = $this->mediaFolder . 'original' . DIRECTORY_SEPARATOR;
$this->liveFolder = $this->mediaFolder . 'live' . DIRECTORY_SEPARATOR;
$this->thumbFolder = $this->mediaFolder . 'thumbs' . DIRECTORY_SEPARATOR;
$this->fileFolder = $this->mediaFolder . 'files' . DIRECTORY_SEPARATOR;
$this->desiredSizes = $desiredSizes;
}
public function checkFolders($forassets = null)
{
$folders = [$this->mediaFolder, $this->tmpFolder, $this->fileFolder];
if($forassets == 'images')
{
$folders = [$this->mediaFolder, $this->tmpFolder, $this->originalFolder, $this->liveFolder, $this->thumbFolder];
}
foreach($folders as $folder)
{
if(!file_exists($folder))
{
if(!mkdir($folder, 0774, true))
{
return false;
}
if($folder == $this->thumbFolder)
{
# cleanup old systems
$this->cleanupLiveFolder();
# generate thumbnails from live folder
$this->generateThumbs();
}
}
elseif(!is_writeable($folder))
{
return false;
}
}
return true;
}
public function setFileName($originalname, $type, $overwrite = null)
{
$pathinfo = pathinfo($originalname);
$this->extension = strtolower($pathinfo['extension']);
$this->filename = URLify::filter(iconv(mb_detect_encoding($pathinfo['filename'], mb_detect_order(), true), "UTF-8", $pathinfo['filename']));
$filename = $this->filename;
# check if file name is
if(!$overwrite)
{
$suffix = 1;
$destination = $this->liveFolder;
if($type == 'file')
{
$destination = $this->fileFolder;
}
while(file_exists($destination . $filename . '.' . $this->extension))
{
$filename = $this->filename . '-' . $suffix;
$suffix++;
}
}
$this->filename = $filename;
return true;
}
public function getName()
{
return $this->filename;
}
public function getExtension()
{
return $this->extension;
}
public function getFullName()
{
return $this->filename . '.' . $this->extension;
}
public function clearTempFolder()
{
$files = scandir($this->tmpFolder);
$result = true;
foreach($files as $file)
{
if (!in_array($file, array(".","..")))
{
$filelink = $this->tmpFolder . $file;
if(!unlink($filelink))
{
$success = false;
}
}
}
return $result;
}
public function cleanupLiveFolder()
{
# delete all old thumbs mlibrary in live folder
foreach(glob($this->liveFolder . '*mlibrary*') as $filename)
{
unlink($filename);
}
return true;
}
public function findPagesWithUrl($structure, $url, $result)
{
foreach ($structure as $key => $item)
{
if($item->elementType == 'folder')
{
$result = $this->findPagesWithUrl($item->folderContent, $url, $result);
}
else
{
$live = getcwd() . DIRECTORY_SEPARATOR . 'content' . $item->pathWithoutType . '.md';
$draft = getcwd() . DIRECTORY_SEPARATOR . 'content' . $item->pathWithoutType . '.txt';
# check live first
if(file_exists($live))
{
$content = file_get_contents($live);
if (stripos($content, $url) !== false)
{
$result[] = $item->urlRelWoF;
}
# if not in live, check in draft
elseif(file_exists($draft))
{
$content = file_get_contents($draft);
if (stripos($content, $url) !== false)
{
$result[] = $item->urlRelWoF;
}
}
}
}
}
return $result;
}
public function formatSizeUnits($bytes)
{
if ($bytes >= 1073741824)
{
$bytes = number_format($bytes / 1073741824, 2) . ' GB';
}
elseif ($bytes >= 1048576)
{
$bytes = number_format($bytes / 1048576, 2) . ' MB';
}
elseif ($bytes >= 1024)
{
$bytes = number_format($bytes / 1024, 2) . ' KB';
}
elseif ($bytes > 1)
{
$bytes = $bytes . ' bytes';
}
elseif ($bytes == 1)
{
$bytes = $bytes . ' byte';
}
else
{
$bytes = '0 bytes';
}
return $bytes;
}
}

View File

@@ -0,0 +1,165 @@
<?php
namespace Typemill\Models;
use Slim\Http\UploadedFile;
use Typemill\Models\Helpers;
use \URLify;
class ProcessFile extends ProcessAssets
{
/**
* Moves the uploaded file to the upload directory. Only used for settings / NON VUE.JS uploads
*
* @param string $directory directory to which the file is moved
* @param UploadedFile $uploadedFile file uploaded file to move
* @return string filename of moved file
*/
public function moveUploadedFile(UploadedFile $uploadedFile, $overwrite = false, $name = false)
{
$this->setFileName($uploadedFile->getClientFilename(), 'file');
if($name)
{
$this->setFileName($name . '.' . $this->extension, 'file', $overwrite);
}
$uploadedFile->moveTo($this->fileFolder . $this->getFullName());
return $this->getFullName();
}
public function storeFile($file, $name)
{
$this->setFileName($name, 'file');
$this->clearTempFolder();
$file = $this->decodeFile($file);
$path = $this->tmpFolder . $this->getFullName();
if(file_put_contents($path, $file))
{
$size = filesize($path);
$size = $this->formatSizeUnits($size);
$title = str_replace('-', ' ', $this->filename);
$title = $title . ' (' . strtoupper($this->extension) . ', ' . $size .')';
return ['title' => $title, 'name' => $this->filename, 'extension' => $this->extension, 'size' => $size, 'url' => 'media/files/' . $this->getFullName()];
}
return false;
}
public function publishFile()
{
$files = scandir($this->tmpFolder);
$success = true;
foreach($files as $file)
{
if (!in_array($file, array(".","..")))
{
$success = rename($this->tmpFolder . $file, $this->fileFolder . $file);
}
}
return $success;
}
public function decodeFile(string $file)
{
$fileParts = explode(";base64,", $file);
$fileType = explode("/", $fileParts[0]);
$fileData = base64_decode($fileParts[1]);
if ($fileData !== false)
{
return array("file" => $fileData, "type" => $fileType[1]);
}
return false;
}
public function deleteFile($name)
{
# validate name
$name = basename($name);
if(file_exists($this->fileFolder . $name) && unlink($this->fileFolder . $name))
{
return true;
}
return false;
}
public function deleteFileWithName($name)
{
# e.g. delete $name = 'logo';
$name = basename($name);
if($name != '' && !in_array($name, array(".","..")))
{
foreach(glob($this->fileFolder . $name . '.*') as $file)
{
unlink($file);
}
}
}
/*
* scans content of a folder (without recursion)
* vars: folder path as string
* returns: one-dimensional array with names of folders and files
*/
public function scanFilesFlat()
{
$files = scandir($this->fileFolder);
$filelist = array();
foreach ($files as $key => $name)
{
if (!in_array($name, array(".","..")) && file_exists($this->fileFolder . $name))
{
$filelist[] = [
'name' => $name,
'timestamp' => filemtime($this->fileFolder . $name),
'info' => pathinfo($this->fileFolder . $name),
'url' => 'media/files/' . $name,
];
}
}
$filelist = Helpers::array_sort($filelist, 'timestamp', SORT_DESC);
return $filelist;
}
public function getFileDetails($name, $structure)
{
$name = basename($name);
if (!in_array($name, array(".","..")) && file_exists($this->fileFolder . $name))
{
$filedetails = [
'name' => $name,
'timestamp' => filemtime($this->fileFolder . $name),
'bytes' => filesize($this->fileFolder . $name),
'info' => pathinfo($this->fileFolder . $name),
'url' => 'media/files/' . $name,
'pages' => $this->findPagesWithUrl($structure, $name, $result = [])
];
return $filedetails;
}
return false;
}
}

View File

@@ -1,9 +1,11 @@
<?php
namespace Typemill\Models;
class ProcessImage
use Typemill\Models\Helpers;
class ProcessImage extends ProcessAssets
{
public function createImage(string $image, array $desiredSizes)
public function createImage(string $image, string $name, array $desiredSizes)
{
# fix error from jpeg-library
ini_set ('gd.jpeg_ignore_warning', 1);
@@ -11,7 +13,10 @@ class ProcessImage
# clear temporary folder
$this->clearTempFolder();
# set the name of the image
$this->setFileName($name, 'image');
# decode the image from base64-string
$imageDecoded = $this->decodeImage($image);
$imageData = $imageDecoded["image"];
@@ -28,25 +33,24 @@ class ProcessImage
# resize the images
$resizedImages = $this->imageResize($image, $imageSize, $desiredSizes, $imageType);
$basePath = getcwd() . DIRECTORY_SEPARATOR . 'media';
$tmpFolder = $basePath . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
$this->saveOriginal($tmpFolder, $imageData, 'original', $imageType);
if($imageType == "gif" && $this->detectAnimatedGif($imageData))
{
$this->saveOriginal($tmpFolder, $imageData, 'live', $imageType);
# store the original name as txt-file
$tmpname = fopen($this->tmpFolder . $this->getName() . '.' . $imageType . ".txt", "w");
$this->saveOriginal($this->tmpFolder, $imageData, $name = 'original', $imageType);
return true;
}
# temporary store resized images
foreach($resizedImages as $key => $resizedImage)
{
$this->saveImage($tmpFolder, $resizedImage, $key, $imageType);
$this->saveImage($this->tmpFolder, $resizedImage, $key, $imageType);
}
# if the image is an animated gif, then overwrite the resized version for live use with the original version
if($imageType == "gif" && $this->detectAnimatedGif($imageData))
{
$this->saveOriginal($this->tmpFolder, $imageData, $name = 'live', $imageType);
}
return true;
}
@@ -60,42 +64,54 @@ class ProcessImage
return false;
}
public function publishImage(array $desiredSizes, $name = false)
public function publishImage()
{
/* get images from tmp folder */
$basePath = getcwd() . DIRECTORY_SEPARATOR . 'media';
$tmpFolder = $basePath . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
$originalFolder = $basePath . DIRECTORY_SEPARATOR . 'original' . DIRECTORY_SEPARATOR;
$liveFolder = $basePath . DIRECTORY_SEPARATOR . 'live' . DIRECTORY_SEPARATOR;
# name is stored in temporary folder as name of the .txt-file
foreach(glob($this->tmpFolder . '*.txt') as $imagename)
{
$tmpname = str_replace('.txt', '', basename($imagename));
if(!file_exists($originalFolder)){ mkdir($originalFolder, 0774, true); }
if(!file_exists($liveFolder)){ mkdir($liveFolder, 0774, true); }
# set extension and sanitize name
$this->setFileName($tmpname, 'image');
$name = $name ? $name : uniqid();
$files = scandir($tmpFolder);
unlink($imagename);
}
$name = uniqid();
if($this->filename && $this->extension)
{
$name = $this->filename;
}
$files = scandir($this->tmpFolder);
$success = true;
foreach($files as $file)
{
if (!in_array($file, array(".","..")))
{
{
$tmpfilename = explode(".", $file);
if($tmpfilename[0] == 'original')
{
$success = rename($tmpFolder . $file, $originalFolder . $name . '-' . $file);
$success = rename($this->tmpFolder . $file, $this->originalFolder . $name . '.' . $tmpfilename[1]);
}
else
if($tmpfilename[0] == 'live')
{
$success = rename($tmpFolder . $file, $liveFolder . $name . '-' . $file);
$success = rename($this->tmpFolder . $file, $this->liveFolder . $name . '.' . $tmpfilename[1]);
}
if($tmpfilename[0] == 'thumbs')
{
$success = rename($this->tmpFolder . $file, $this->thumbFolder . $name . '.' . $tmpfilename[1]);
}
}
}
if($success)
{
return 'media/live/' . $name . '-live.' . $tmpfilename[1];
return true;
return 'media/live/' . $name . '.' . $tmpfilename[1];
}
return false;
@@ -126,6 +142,7 @@ class ProcessImage
{
foreach($desiredSizes as $key => $desiredSize)
{
# if desired size is bigger than the actual image, then drop the desired sizes and use the actual image size instead
if($desiredSize['width'] > $imageSize['width'])
{
$desiredSizes[$key] = $imageSize;
@@ -141,129 +158,61 @@ class ProcessImage
return $desiredSizes;
}
public function imageResize($imageData, array $imageSize, array $desiredSizes, $imageType)
public function imageResize($imageData, array $source, array $desiredSizes, $imageType)
{
$copiedImages = array();
$source_aspect_ratio = $imageSize['width'] / $imageSize['height'];
foreach($desiredSizes as $key => $desiredSize)
foreach($desiredSizes as $key => $desired)
{
$desired_aspect_ratio = $desiredSize['width'] / $desiredSize['height'];
// resize
$ratio = max($desired['width']/$source['width'], $desired['height']/$source['height']);
$h = $desired['height'] / $ratio;
$x = ($source['width'] - $desired['width'] / $ratio) / 2;
$y = ($source['height'] - $desired['height'] / $ratio) / 2;
$w = $desired['width'] / $ratio;
if ( $source_aspect_ratio > $desired_aspect_ratio )
{
# when source image is wider
$temp_height = $desiredSize['height'];
$temp_width = ( int ) ($desiredSize['height'] * $source_aspect_ratio);
$temp_width = round($temp_width, 0);
}
else
{
# when source image is similar or taller
$temp_width = $desiredSize['width'];
$temp_height = ( int ) ($desiredSize['width'] / $source_aspect_ratio);
$temp_height = round($temp_height, 0);
}
$new = imagecreatetruecolor($desired['width'], $desired['height']);
# Create a temporary GD image with desired size
$temp_gdim = imagecreatetruecolor( $temp_width, $temp_height );
// preserve transparency
if($imageType == "gif" or $imageType == "png")
{
imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127));
imagealphablending($new, false);
imagesavealpha($new, true);
}
if ($imageType == "gif")
{
$transparent_index = imagecolortransparent($imageData);
imagepalettecopy($imageData, $temp_gdim);
imagefill($temp_gdim, 0, 0, $transparent_index);
imagecolortransparent($temp_gdim, $transparent_index);
imagetruecolortopalette($temp_gdim, true, 256);
}
elseif($imageType == "png")
{
imagealphablending($temp_gdim, false);
imagesavealpha($temp_gdim, true);
$transparent = imagecolorallocatealpha($temp_gdim, 255, 255, 255, 127);
imagefilledrectangle($temp_gdim, 0, 0, $temp_width, $temp_height, $transparent);
}
# resize image
imagecopyresampled(
$temp_gdim,
$imageData,
0, 0,
0, 0,
$temp_width, $temp_height,
$imageSize['width'], $imageSize['height']
);
imagecopyresampled($new, $imageData, 0, 0, $x, $y, $desired['width'], $desired['height'], $w, $h);
$copiedImages[$key] = $temp_gdim;
/*
# Copy cropped region from temporary image into the desired GD image
$x0 = ( $temp_width - $desiredSize['width'] ) / 2;
$y0 = ( $temp_height - $desiredSize['height'] ) / 2;
$desired_gdim = imagecreatetruecolor( $desiredSize['width'], $desiredSize['height'] );
if ($imageType == "gif")
{
imagepalettecopy($temp_gdim, $desired_gdim);
imagefill($desired_gdim, 0, 0, $transparent_index);
imagecolortransparent($desired_gdim, $transparent_index);
imagetruecolortopalette($desired_gdim, true, 256);
}
elseif($imageType == "png")
{
imagealphablending($desired_gdim, false);
imagesavealpha($desired_gdim,true);
$transparent = imagecolorallocatealpha($desired_gdim, 255, 255, 255, 127);
imagefilledrectangle($desired_gdim, 0, 0, $desired_size['with'], $desired_size['height'], $transparent);
}
imagecopyresampled(
$desired_gdim,
$temp_gdim,
0, 0,
0, 0,
$x0, $y0,
$desiredSize['width'], $desiredSize['height']
);
$copiedImages[$key] = $desired_gdim;
*/
$copiedImages[$key] = $new;
}
return $copiedImages;
}
# save original in temporary folder
public function saveOriginal($folder, $image, $name, $type)
{
if(!file_exists($folder))
{
mkdir($folder, 0774, true);
}
{
$path = $folder . $name . '.' . $type;
file_put_contents($path, $image);
}
# save resized images in temporary folder
public function saveImage($folder, $image, $name, $type)
{
if(!file_exists($folder))
{
mkdir($folder, 0774, true);
}
{
if($type == "png")
{
$result = imagepng( $image, $folder . '/' . $name . '.png' );
$result = imagepng( $image, $folder . $name . '.png' );
}
elseif($type == "gif")
{
$result = imagegif( $image, $folder . '/' . $name . '.gif' );
$result = imagegif( $image, $folder . $name . '.gif' );
}
else
{
$result = imagejpeg( $image, $folder . '/' . $name . '.jpeg' );
$result = imagejpeg( $image, $folder . $name . '.jpeg' );
$type = 'jpeg';
}
@@ -277,60 +226,156 @@ class ProcessImage
return false;
}
public function clearTempFolder()
{
$folder = getcwd() . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
if(!file_exists($folder))
{
mkdir($folder, 0774, true);
return true;
}
$files = scandir($folder);
$result = true;
foreach($files as $file)
{
if (!in_array($file, array(".","..")))
{
$filelink = $folder . $file;
if(!unlink($filelink))
{
$success = false;
}
}
}
return $result;
}
public function deleteImage($name)
{
$baseFolder = getcwd() . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR;
$original = $baseFolder . 'original' . DIRECTORY_SEPARATOR . $name . '*';
$live = $baseFolder . 'live' . DIRECTORY_SEPARATOR . $name . '*';
$success = true;
foreach(glob($original) as $image)
# validate name
$name = basename($name);
$result = true;
if(!file_exists($this->originalFolder . $name) OR !unlink($this->originalFolder . $name))
{
$result = false;
}
if(!file_exists($this->liveFolder . $name) OR !unlink($this->liveFolder . $name))
{
$result = false;
}
if(!file_exists($this->thumbFolder . $name) OR !unlink($this->thumbFolder . $name))
{
$result = false;
}
# you should not use glob but exact name with ending
/*
foreach(glob($this->originalFolder . $name) as $image)
{
if(!unlink($image))
{
$success = false;
}
}
*/
foreach(glob($live) as $image)
{
if(!unlink($image))
{
$success = false;
}
}
return $success;
# array_map('unlink', glob("some/dir/*.txt"));
return $result;
}
/*
* scans content of a folder (without recursion)
* vars: folder path as string
* returns: one-dimensional array with names of folders and files
*/
public function scanMediaFlat()
{
$thumbs = array_diff(scandir($this->thumbFolder), array('..', '.'));
$imagelist = array();
foreach ($thumbs as $key => $name)
{
if (file_exists($this->liveFolder . $name))
{
$imagelist[] = [
'name' => $name,
'timestamp' => filemtime($this->liveFolder . $name),
'src_thumb' => 'media/thumbs/' . $name,
'src_live' => 'media/live/' . $name,
];
}
}
$imagelist = Helpers::array_sort($imagelist, 'timestamp', SORT_DESC);
return $imagelist;
}
}
?>
public function getImageDetails($name, $structure)
{
$name = basename($name);
if (!in_array($name, array(".","..")) && file_exists($this->liveFolder . $name))
{
$imageinfo = getimagesize($this->liveFolder . $name);
$imagedetails = [
'name' => $name,
'timestamp' => filemtime($this->liveFolder . $name),
'bytes' => filesize($this->liveFolder . $name),
'width' => $imageinfo[0],
'height' => $imageinfo[1],
'type' => $imageinfo['mime'],
'src_thumb' => 'media/thumbs/' . $name,
'src_live' => 'media/live/' . $name,
'pages' => $this->findPagesWithUrl($structure, $name, $result = [])
];
return $imagedetails;
}
return false;
}
public function generateThumbs()
{
# generate images from live folder to 'tmthumbs'
$liveImages = scandir($this->liveFolder);
foreach ($liveImages as $key => $name)
{
if (!in_array($name, array(".","..")))
{
$this->generateThumbFromImageFile($name);
}
}
}
public function generateThumbFromImageFile($filename)
{
$this->setFileName($filename, 'image', $overwrite = true);
if($this->extension == 'jpeg') $this->extension = 'jpg';
switch($this->extension)
{
case 'gif': $image = imagecreatefromgif($this->liveFolder . $filename); break;
case 'jpg': $image = imagecreatefromjpeg($this->liveFolder . $filename); break;
case 'png': $image = imagecreatefrompng($this->liveFolder . $filename); break;
default: return 'image type not supported';
}
$originalSize = $this->getImageSize($image);
$thumbSize = $this->desiredSizes['thumbs'];
$thumb = $this->imageResize($image, $originalSize, ['thumbs' => $thumbSize ], $this->extension);
$this->saveImage($this->thumbFolder, $thumb['thumbs'], $this->filename, $this->extension);
}
public function generateSizesFromImageFile($filename, $image)
{
$this->setFileName($filename, 'image');
if($this->extension == 'jpeg') $this->extension = 'jpg';
switch($this->extension)
{
case 'gif': $image = imagecreatefromgif($image); break;
case 'jpg': $image = imagecreatefromjpeg($image); break;
case 'png': $image = imagecreatefrompng($image); break;
default: return 'image type not supported';
}
$originalSize = $this->getImageSize($image);
$resizedImages = $this->imageResize($image, $originalSize, $this->desiredSizes, $this->extension);
return $resizedImages;
}
}