Add uploads folder

This commit is contained in:
philipp lang 2021-09-06 02:31:27 +02:00
parent fa6a328860
commit 15a4046402
19 changed files with 909 additions and 375 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@
/formwidgets/responsiveimage/assets/css/responsiveimage.build.css.map /formwidgets/responsiveimage/assets/css/responsiveimage.build.css.map
/formwidgets/responsiveimage/assets/js/responsiveimage.build.js.map /formwidgets/responsiveimage/assets/js/responsiveimage.build.js.map
/formwidgets/responsiveimage/assets/js/responsiveimage.build.js.LICENSE.txt /formwidgets/responsiveimage/assets/js/responsiveimage.build.js.LICENSE.txt
/.phpunit.result.cache

View File

@ -1,16 +1,21 @@
<?php namespace Aweos\Resizer; <?php namespace Aweos\Resizer;
use Storage; use Aweos\Resizer\Classes\FileObserver;
use Backend; use Aweos\Resizer\Classes\ImageResizer;
use System\Classes\PluginBase; use Aweos\Resizer\Classes\ResizeJob;
use System\Classes\MediaLibrary; use Aweos\Resizer\Classes\TagGenerator;
use Aweos\Resizer\Models\Setting;
use Aweos\Resizer\Console\ClearOld; use Aweos\Resizer\Console\ClearOld;
use Aweos\Resizer\Console\ResizeMake; use Aweos\Resizer\Console\ResizeMake;
use Illuminate\Support\Facades\Cache;
use Aweos\Resizer\Console\ResizePurge; use Aweos\Resizer\Console\ResizePurge;
use Aweos\Resizer\FormWidgets\Responsiveimage; use Aweos\Resizer\FormWidgets\Responsiveimage;
use Aweos\Resizer\Classes\TagPresenter; use Aweos\Resizer\Models\Setting;
use Event;
use Illuminate\Support\Facades\Cache;
use Queue;
use Storage;
use System\Classes\MediaLibrary;
use System\Classes\PluginBase;
use System\Models\File;
/** /**
* resizer Plugin Information File * resizer Plugin Information File
@ -51,58 +56,29 @@ class Plugin extends PluginBase
*/ */
public function boot() public function boot()
{ {
app()->bind(ImageResizer::class, function() {
$disk = (new File())->getDisk();
$dir = (new File(['is_public' => true]))->getStorageDirectory().'c/';
$media = MediaLibrary::instance();
return new ImageResizer($disk, $dir, $media);
});
Event::listen('media.file.upload', function($widget, $filePath, $uploadedFile) {
if (app(FileObserver::class)->shouldProcessFile($filePath)) {
Queue::push(ResizeJob::class, [$filePath]);
} }
});
Event::listen('media.file.delete', function($widget, $filePath) {
app(FileObserver::class)->delete($filePath);
});
/** Event::listen('media.file.move', function($widget, $old, $new) {
* Registers any front-end components implemented in this plugin. app(FileObserver::class)->rename($old, $new);
* });
* @return array Event::listen('media.file.rename', function($widget, $old, $new) {
*/ app(FileObserver::class)->rename($old, $new);
public function registerComponents() });
{
return []; // Remove this line to activate
return [
'Aweos\Resizer\Components\MyComponent' => 'myComponent',
];
}
/**
* Registers any back-end permissions used by this plugin.
*
* @return array
*/
public function registerPermissions()
{
return []; // Remove this line to activate
return [
'aweos.resizer.some_permission' => [
'tab' => 'resizer',
'label' => 'Some permission'
],
];
}
/**
* Registers back-end navigation items for this plugin.
*
* @return array
*/
public function registerNavigation()
{
return []; // Remove this line to activate
return [
'resizer' => [
'label' => 'resizer',
'url' => Backend::url('aweos/resizer/mycontroller'),
'icon' => 'icon-leaf',
'permissions' => ['aweos.resizer.*'],
'order' => 500,
],
];
} }
public function registerSettings() { public function registerSettings() {
@ -123,75 +99,9 @@ class Plugin extends PluginBase
public function registerMarkupTags() { public function registerMarkupTags() {
return [ return [
'filters' => [ 'filters' => [
'resize' => function($media, $breakpoints = [], $name = '') { 'resize' => function($media, $size = 'original') {
return Cache::remember('resized_image.'.$name.$media, 3600, function() use ($media, $breakpoints) { return app(TagGenerator::class)->generate($media, $size);
ksort($breakpoints);
$l = MediaLibrary::instance();
$folders = Setting::get('folders');
$sizes = Setting::get('srcx');
$media = '/'.ltrim($media, '/');
$filename = basename($media);
$dirname = dirname($media);
$extension = pathinfo($media, PATHINFO_EXTENSION);
if (! $l->folderExists($dirname.'/c')) {
return 'src="'.$l->getPathUrl($media).'"';
}
$sizes = collect($l->listFolderContents($dirname.'/c'))
->filter(function($f) use ($sizes, $filename) {
foreach ($sizes as $size) {
if (pathinfo($f->path, PATHINFO_FILENAME).'.'.pathinfo($filename, PATHINFO_EXTENSION)
== pathinfo($filename, PATHINFO_FILENAME).'-'.$size.'t.'.pathinfo($filename, PATHINFO_EXTENSION)) {
return true;
}
if (pathinfo($f->path, PATHINFO_FILENAME).'.'.pathinfo($filename, PATHINFO_EXTENSION)
== pathinfo($filename, PATHINFO_FILENAME).'-'.$size.'.'.pathinfo($filename, PATHINFO_EXTENSION)) {
return true;
}
}
return false;
})
->map(function($fileVersion) use ($l) {
$sizes = getimagesize(Storage::path('media/'.$fileVersion->path));
return [$l->getPathUrl($fileVersion->path), $sizes[0], $sizes[1]];
})->sortBy(function($size) {
return $size[1];
})
;
if ($sizes->isEmpty()) {
return 'src="'.$l->getPathUrl($media).'"';
}
$srcset = 'srcset="';
$s = '';
foreach($sizes as $size) {
$srcset .= $size[0].' '.$size[1].'w, ';
$s .= '(max-width: '.$size[1].'px) '.$size[1].'px, ';
}
$s = 'sizes="'.substr($s, 0, -2).', 1920px"';
if (!empty($breakpoints)) {
$s = $this->breakpointsToSizes($breakpoints);
}
$srcset = substr($srcset, 0, -2).'"';
$normalSize = 'width="'.$sizes->last()[1].'" height="'.$sizes->last()[2].'"';
return 'src="'.$l->getPathUrl($media).'" '.$srcset.' '.$s.' '.$normalSize;
});
}, },
'responsiveimage' => function($id, $data = []) {
return (new TagPresenter($id, $data))->cacheOutput();
}
] ]
]; ];
} }

70
classes/FileObserver.php Normal file
View File

@ -0,0 +1,70 @@
<?php
namespace Aweos\Resizer\Classes;
use Aweos\Resizer\Models\Setting;
use Storage;
class FileObserver
{
public function versionsPath(string $mediaPath): string
{
return "uploads/public/c/{$this->normalizePath($mediaPath)}";
}
public function normalizePath(string $path): string
{
return preg_replace('|^/*|', '', $path);
}
public function fullMediaPath(string $path): string
{
return "media/{$this->normalizePath($path)}";
}
public function shouldProcessFile($file): bool
{
return collect(Setting::get('folders'))->pluck('folder')->first(
fn ($folder) => starts_with($file, $folder.'/')
) !== null;
}
public function delete($path): void
{
$dir = pathinfo($path, PATHINFO_DIRNAME);
$base = $dir.'/'.pathinfo($path, PATHINFO_FILENAME);
Storage::delete("uploads/public/c{$path}");
collect(Storage::files("uploads/public/c{$dir}"))->filter(
fn ($file) => preg_match('|'.preg_quote("uploads/public/c{$base}", '|').'-[0-9]+x[0-9]+\.[a-zA-Z]+$|', $file)
)->each(function ($path) {
Storage::delete($path);
});
if (empty(Storage::allFiles("uploads/public/c{$dir}"))) {
Storage::deleteDirectory("uploads/public/c{$dir}");
}
}
public function rename(string $old, string $new): void
{
$dir = pathinfo($old, PATHINFO_DIRNAME);
$base = pathinfo($old, PATHINFO_FILENAME);
$newBase = pathinfo($new, PATHINFO_FILENAME);
$newDir = pathinfo($new, PATHINFO_DIRNAME);
foreach (Storage::files("uploads/public/c{$dir}") as $file) {
if (!preg_match_all('|'.preg_quote("uploads/public/c{$dir}/{$base}", '|').'(-[0-9]+x[0-9]+)?(\.[a-zA-Z]+)$|', $file, $matches)) {
continue;
}
$size = $matches[1][0];
$ext = $matches[2][0];
Storage::move($file, "uploads/public/c{$newDir}/{$newBase}{$size}{$ext}");
}
}
}

View File

@ -1,38 +0,0 @@
<?php
namespace Aweos\Resizer\Classes;
class FirstLetterStrategy {
public function sourcePath($fileName) {
return 'source';
}
public function sourceFileBasename($uploadedFile, $data) {
return str_slug($data['title']);
}
public function publicPath($filename) {
return strtolower($filename[0]);
}
public function smallFilename($filename, $width) {
return pathinfo($filename, PATHINFO_FILENAME).'-'.$width.'.'.pathinfo($filename, PATHINFO_EXTENSION);
}
public function versionRegex($filename) {
$ext = preg_quote(pathinfo($filename, PATHINFO_EXTENSION), '/');
$name = preg_quote(pathinfo($filename, PATHINFO_FILENAME), '/');
return "/^{$name}-[0-9]+\.{$ext}$/";
}
public function croppedPath() {
return 'cropped';
}
public function croppedFilename($filename, $crop) {
return $filename;
}
}

115
classes/ImageResizer.php Normal file
View File

@ -0,0 +1,115 @@
<?php
namespace Aweos\Resizer\Classes;
use Aweos\Resizer\Models\Setting;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Collection;
use Intervention\Image\ImageManager;
use Media\Classes\MediaLibrary;
use October\Rain\Resize\Resizer;
use Storage;
class ImageResizer
{
private FilesystemAdapter $disk;
private string $uploadDir;
private MediaLibrary $media;
public function __construct(FilesystemAdapter $disk, string $uploadDir, MediaLibrary $media)
{
$this->disk = $disk;
$this->uploadDir = $uploadDir;
$this->media = $media;
}
public function generate(string $file): void
{
$this->source = app(FileObserver::class)->normalizePath($file);
if ($this->media->findFiles($this->source)[0]->getFileType() !== 'image') {
return;
}
$this->copyOriginalImage();
$this->generateVersions();
}
public function copyOriginalImage()
{
$this->disk->put($this->destinationFilename(), $this->media->get($this->source));
}
private function destinationFilename(): string
{
return $this->uploadDir.$this->source;
}
private function dimensions(): Collection
{
[$width, $height] = getimagesize(Storage::path($this->destinationFilename()));
return collect(compact('width', 'height'));
}
private function sizes(): Collection
{
$sizes = Setting::get('sizes');
return collect(array_column($sizes, 'aspect_ratio'))->map(
fn ($ratio) => collect(array_combine(['width', 'height'], explode('x', $ratio))),
);
}
private function possibleSizes(): Collection
{
$return = collect([]);
$ratios = collect();
$ratios->push($this->dimensions());
$ratios = $ratios->merge($this->sizes());
foreach (collect(Setting::get('breakpoints'))->push($this->dimensions()->get('width'))->unique() as $size) {
foreach ($ratios as $ratio) {
$height = $size * $ratio->get('height') / $ratio->get('width');
$return->push(collect([
'width' => round($size),
'height' => round($height),
]));
}
}
return $return;
}
private function generateVersions(): void
{
foreach ($this->possibleSizes() as $size) {
$temp = microtime().'.jpg';
$this->disk->copy($this->destinationFilename(), $temp);
$fullOriginalPath = $this->disk->path($this->destinationFilename());
$pathinfo = collect(pathinfo($this->destinationFilename()));
$r = app(ImageManager::class)->make($this->disk->path($temp))
->fit($size->get('width'), $size->get('height'), fn ($constraint) => $constraint->upsize())
->save($this->disk->path($temp));
list($destWidth, $destHeight) = getimagesize($this->disk->path($temp));
$versionFilename = $pathinfo->get('dirname').
'/'.
$pathinfo->get('filename').
'-'.
$destWidth.
'x'.
$destHeight.
'.'.
$pathinfo->get('extension');
if ($this->disk->exists($versionFilename)) {
$this->disk->delete($versionFilename);
}
$this->disk->move($temp, $versionFilename);
}
}
}

13
classes/ResizeJob.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace Aweos\Resizer\Classes;
class ResizeJob
{
public function fire($job, $params)
{
list($file) = $params;
app(ImageResizer::class)->generate($file);
}
}

106
classes/TagGenerator.php Normal file
View File

@ -0,0 +1,106 @@
<?php
namespace Aweos\Resizer\Classes;
use Aweos\Resizer\Models\Attachment;
use Aweos\Resizer\Models\Setting;
use Cache;
use Illuminate\Support\Collection;
use Media\Classes\MediaLibrary;
use Storage;
class TagGenerator {
public MediaLibrary $media;
public array $breakpoints;
public $path;
public function __construct()
{
$this->media = MediaLibrary::instance();
$this->breakpoints = Setting::get('breakpoints');
ksort($this->breakpoints);
}
public function cacheOutput() {
return Cache::rememberForever('responsive-image-'.$this->attachment->id, function() {
return $this->output();
}).' '.$this->attrs;
}
public function size(string $name): array
{
$size = collect(Setting::get('sizes'))->filter(fn ($size) => $size['name'] === $name)
->first();
return explode('x', $size['aspect_ratio']);
}
public function possibleFiles(string $ratio): Collection
{
if (Storage::mimeType(app(FileObserver::class)->fullMediaPath($this->path)) !== 'image/jpeg') {
return collect([]);
}
$filename = pathinfo($this->path, PATHINFO_FILENAME);
$basePath = app(FileObserver::class)->versionsPath(pathinfo($this->path, PATHINFO_DIRNAME));
[$originalWidth, $originalHeight] = getimagesize(Storage::path(app(FileObserver::class)->fullMediaPath($this->path)));
$aspectRatio = $ratio === 'original'
? $originalWidth / $originalHeight
: $this->size($ratio)[0] / $this->size($ratio)[1];
$result = collect([]);
foreach (Storage::files($basePath) as $file) {
if (!preg_match('|'.preg_quote($basePath.'/'.$filename, '|').'-([0-9]+x[0-9]+)\.([a-zA-Z]+)$|', $file, $matches)) {
continue;
}
$width = explode('x', $matches[1])[0];
$height = explode('x', $matches[1])[1];
if ($width / ($height+1) > $aspectRatio || $width / ($height-1) < $aspectRatio) {
continue;
}
$result->push(collect([
'url' => Storage::url($file),
'width' => $width,
'height' => $height,
]));
}
return $result->sortBy('width');
}
public function generate(string $path, ?string $ratio = 'original'): string
{
$this->path = $path;
$files = $this->possibleFiles($ratio);
if ($files->isEmpty()) {
return 'src="/storage/app/media/'.$path.'"';
}
$sizes = $files->map(function($file) {
return "(max-width: {$file->get('width')}px) {$file->get('width')}px";
});
$srcset = $files->map(function($file) {
return "{$file->get('url')} {$file->get('width')}w";
});
return $this->htmlAttributes(collect([
'width' => $files->last()->get('width'),
'height' => $files->last()->get('height'),
'sizes' => $sizes->implode(', '),
'srcset' => $srcset->implode(', '),
'src' => $files->last()->get('url'),
]));
}
private function htmlAttributes($attr) {
return $attr->map(function($value, $key) {
return "{$key}=\"{$value}\"";
})->implode(' ');
}
}

View File

@ -1,72 +0,0 @@
<?php
namespace Aweos\Resizer\Classes;
use Cache;
use Storage;
use Aweos\Resizer\Models\Attachment;
use Aweos\Resizer\Models\Setting;
class TagPresenter {
public $attachment;
public $disk = 'uploads';
public $attrs;
public $breakpoints;
public $sizes;
public function __construct($id, $data = []) {
$this->attachment = Attachment::find($id);
$this->attrs = data_get($data, 'attrs');
$this->breakpoints = data_get($data, 'breakpoints', []);
$this->sizes = Setting::get('srcx');
}
public function cacheOutput() {
return Cache::rememberForever('responsive-image-'.$this->attachment->id, function() {
return $this->output();
}).' '.$this->attrs;
}
public function output() {
ksort($this->breakpoints);
$files = collect(Storage::disk($this->disk)->files($this->attachment->path))->filter(function($file) {
return preg_match($this->attachment->versionRegex, $file);
})->map(function($file) {
$sizes = getimagesize(Storage::disk($this->disk)->path($file));
return collect([
'url' => Storage::disk($this->disk)->url($file),
'width' => $sizes[0],
'height' => $sizes[1]
]);
})->sortBy('width');
if ($files->isEmpty()) {
return 'src="'.$this->attachment->url.'"';
}
$sizes = $files->map(function($file) {
return "(max-width: {$file->get('width')}px) {$file->get('width')}px";
});
$srcset = $files->map(function($file) {
return "{$file->get('url')} {$file->get('width')}w";
});
return $this->htmlAttributes(collect([
'width' => $files->last()->get('width'),
'height' => $files->last()->get('height'),
'sizes' => $sizes->implode(', '),
'srcset' => $srcset->implode(', '),
'alt' => $this->attachment->title
]));
}
private function htmlAttributes($attr) {
return $attr->map(function($value, $key) {
return "{$key}=\"{$value}\"";
})->implode(' ');
}
}

View File

@ -2,7 +2,8 @@
"name": "aweos/oc-resizer-plugin", "name": "aweos/oc-resizer-plugin",
"type": "october-plugin", "type": "october-plugin",
"require": { "require": {
"guzzlehttp/guzzle": "^6.3" "guzzlehttp/guzzle": "^6.3",
"intervention/image": "^2.6"
}, },
"authors": [ "authors": [
{ {

89
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "82331c61df9e7f95117f9b82b35b6ce2", "content-hash": "d44b27fd707830b38897f254dc4f9040",
"packages": [ "packages": [
{ {
"name": "guzzlehttp/guzzle", "name": "guzzlehttp/guzzle",
@ -193,6 +193,90 @@
], ],
"time": "2019-07-01T23:21:34+00:00" "time": "2019-07-01T23:21:34+00:00"
}, },
{
"name": "intervention/image",
"version": "2.6.1",
"source": {
"type": "git",
"url": "https://github.com/Intervention/image.git",
"reference": "0925f10b259679b5d8ca58f3a2add9255ffcda45"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Intervention/image/zipball/0925f10b259679b5d8ca58f3a2add9255ffcda45",
"reference": "0925f10b259679b5d8ca58f3a2add9255ffcda45",
"shasum": ""
},
"require": {
"ext-fileinfo": "*",
"guzzlehttp/psr7": "~1.1 || ^2.0",
"php": ">=5.4.0"
},
"require-dev": {
"mockery/mockery": "~0.9.2",
"phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15"
},
"suggest": {
"ext-gd": "to use GD library based image processing.",
"ext-imagick": "to use Imagick based image processing.",
"intervention/imagecache": "Caching extension for the Intervention Image library"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
},
"laravel": {
"providers": [
"Intervention\\Image\\ImageServiceProvider"
],
"aliases": {
"Image": "Intervention\\Image\\Facades\\Image"
}
}
},
"autoload": {
"psr-4": {
"Intervention\\Image\\": "src/Intervention/Image"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Oliver Vogel",
"email": "oliver@olivervogel.com",
"homepage": "http://olivervogel.com/"
}
],
"description": "Image handling and manipulation library with support for Laravel integration",
"homepage": "http://image.intervention.io/",
"keywords": [
"gd",
"image",
"imagick",
"laravel",
"thumbnail",
"watermark"
],
"support": {
"issues": "https://github.com/Intervention/image/issues",
"source": "https://github.com/Intervention/image/tree/2.6.1"
},
"funding": [
{
"url": "https://www.paypal.me/interventionphp",
"type": "custom"
},
{
"url": "https://github.com/Intervention",
"type": "github"
}
],
"time": "2021-07-22T14:31:53+00:00"
},
{ {
"name": "psr/http-message", "name": "psr/http-message",
"version": "1.0.1", "version": "1.0.1",
@ -291,5 +375,6 @@
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": [], "platform": [],
"platform-dev": [] "platform-dev": [],
"plugin-api-version": "2.0.0"
} }

View File

@ -1,20 +1,21 @@
<?php namespace Aweos\Resizer\Console; <?php namespace Aweos\Resizer\Console;
use Storage; use Aweos\Resizer\Classes\ResizeJob;
use GuzzleHttp\Client;
use Illuminate\Console\Command;
use System\Classes\MediaLibrary;
use Aweos\Resizer\Models\Setting; use Aweos\Resizer\Models\Setting;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ClientException;
use Illuminate\Console\Command;
use October\Rain\Database\Attach\Resizer; use October\Rain\Database\Attach\Resizer;
use Symfony\Component\Console\Input\InputOption; use Queue;
use Storage;
use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use System\Classes\MediaLibrary;
class ResizeMake extends Command class ResizeMake extends Command
{ {
public $media = null; public $media = null;
public $http = null;
/** /**
* @var string The console command name. * @var string The console command name.
@ -26,14 +27,14 @@ class ResizeMake extends Command
*/ */
protected $description = 'Resizes configured images'; protected $description = 'Resizes configured images';
public function forAllResizables($callback) { public function resize(string $folder): void
$folders = Setting::get('folders'); {
$sizes = Setting::get('srcx'); foreach ($this->media->listFolderContents($folder) as $item) {
if ($item->type === 'folder') {
sort($sizes); $this->resize($item->path);
} else {
foreach($folders as $folder) { Queue::push(ResizeJob::class, [$item->path]);
$this->start($folder['folder'], $sizes, $callback); }
} }
} }
@ -43,117 +44,12 @@ class ResizeMake extends Command
*/ */
public function handle() public function handle()
{ {
$this->http = new Client(['base_uri' => 'https://api.tinify.com']);
$this->media = MediaLibrary::instance(); $this->media = MediaLibrary::instance();
ProgressBar::setFormatDefinition('custom', '%current%/%max% %bar% -- %message% (%size%)'); ProgressBar::setFormatDefinition('custom', '%current%/%max% %bar% -- %message% (%size%)');
$tinifyKey = Setting::get('tinify') ? Setting::get('tinypngkey') : null; Storage::deleteDirectory('uploads/public/c');
$i = 0; foreach (Setting::get('folders') as $folder) {
$this->forAllResizables(function($file, $w) use (&$i) { $this->resize($folder['folder']);
$i++;
});
$bar = $this->output->createProgressBar($i);
$bar->setFormat('custom');
/**
* @param File $file The current filename
* @param int $w Current width
* @param string $compressed compressed file
* @param string $uncompressed uncompressed file
*/
$this->forAllResizables(function($file, $w, $compressed, $uncompressed) use ($bar, $tinifyKey) {
$bar->setMessage($file->path);
$bar->setMessage($w, 'size');
$bar->advance();
if (Storage::exists($compressed)) {
return;
}
if (!Storage::exists($uncompressed)) {
$r = Resizer::open(Storage::disk('local')->path('media'.$file->path));
$r->resize($w, 0);
$r->save(Storage::disk('local')->path($uncompressed));
$this->info(Storage::url($uncompressed));
}
if (!is_null($tinifyKey)) {
$newUrl = url($this->media->getPathUrl(preg_replace('/^media/', '', $uncompressed)));
try {
$response = $this->http->post('/shrink', [
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Authorization' => 'Basic '.base64_encode('api:'.$tinifyKey)
],
'json' => [
'source' => ['url' => $newUrl]
]
]);
$output = json_decode((string) $response->getBody());
$url = $output->output->url;
$response = $this->http->get($output->output->url, [
'headers' => [
'Authorization' => 'Basic '.base64_encode('api:'.$tinifyKey)
]
]);
$image = (string) $response->getBody();
Storage::put($compressed, $image);
Storage::delete($uncompressed);
} catch(ClientException $e) {
return;
}
}
$bar->advance();
});
$bar->finish();
}
public function getMediaOfType($f, $type) {
return array_filter($this->media->listFolderContents($f, 'title', 'image'), function($item) use ($f, $type) {
return $item->type == $type && $item->path != $f.'/c';
});
}
public function start($f, $sizes, $callback) {
$f = '/'.ltrim(rtrim($f, '/'), '/');
$folders = $this->getMediaOfType($f, 'folder');
foreach ($folders as $folder) {
$this->start($folder->path, $sizes, $callback);
}
$files = $this->getMediaOfType($f, 'file');
// Create C-Folder as current subfolder if it doesnt exist yet
if(count($files) && !$this->media->folderExists($f.'/c')) {
$this->media->makeFolder($f.'/c');
}
foreach ($files as $file) {
foreach ($sizes as $w) {
$imagesize = getimagesize(Storage::path('media'.$file->path));
$uncompressedFilename = pathinfo($file->path, PATHINFO_FILENAME).'-'.$w.'.'.pathinfo($file->path, PATHINFO_EXTENSION);
$compressedFilename = pathinfo($file->path, PATHINFO_FILENAME).'-'.$w.'t.'.pathinfo($file->path, PATHINFO_EXTENSION);
$compressed = 'media'.dirname($file->path).'/c/'.$compressedFilename;
$uncompressed = 'media'.dirname($file->path).'/c/'.$uncompressedFilename;
if ($imagesize[0] < $w) {
continue;
}
call_user_func($callback, $file, $w, $compressed, $uncompressed);
}
} }
} }

View File

@ -5,27 +5,29 @@
fields: fields:
folders: folders:
type: repeater type: repeater
label: Überwachte Ordner
form: form:
fields: fields:
folder: folder:
type: mediafinder type: mediafinder
label: Folder label: Folder
srcx: breakpoints:
type: taglist type: taglist
mode: array mode: array
label: Source X label: Breakpoints
srcy: comment: Von diesen Werten werden Bilder generiert mit entsprechender Breite. Bitte nur die Breite in Pixel angeben. Die Höhe bestimmt sich nach der Zielgröße
type: taglist sizes:
mode: array type: repeater
label: Source Y label: Seitenverhältnisse
form:
fields:
name:
label: Name
span: left
comment: Bitte im Slug Format eingeben - z.B. "big-box"
aspect_ratio:
label: Seitenverhältnis
comment: Bitte im Format [x]x[y] eingeben - z.B. 1280x700
span: right
comment: Ein Bild mit den selben Seitenverhältnissen des Originalbildes wird immer generiert. Gebe hier die Seitenverhältnisse in "x/y" an, die sonst noch generiert werden sollen, z.B. 16/9
tinify:
type: checkbox
label: Tinyfy with tinypng
tinypngkey:
type: text
label: Tinypng API Key
trigger:
action: show
field: tinify
condition: checked

29
phpunit.xml Normal file
View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="../../../modules/system/tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<testsuites>
<testsuite name="Plugin Unit Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing" />
<env name="CACHE_DRIVER" value="array" />
<env name="SESSION_DRIVER" value="array" />
<env name="ACTIVE_THEME" value="test" />
<env name="CONVERT_LINE_ENDINGS" value="true" />
<env name="CMS_ROUTE_CACHE" value="true" />
<env name="CMS_TWIG_CACHE" value="false" />
<env name="ENABLE_CSRF" value="false" />
<env name="DB_CONNECTION" value="sqlite" />
<env name="DB_DATABASE" value=":memory:" />
</php>
</phpunit>

60
tests/DeleteTest.php Normal file
View File

@ -0,0 +1,60 @@
<?php
namespace Aweos\Resizer\Tests\MediaTest;
use Aweos\Resizer\Models\Setting;
use Aweos\Resizer\Tests\TestCase;
use Event;
use Illuminate\Http\UploadedFile;
use Storage;
class DeleteTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
Storage::fake('local');
}
public function testItDeletesAllVersionsIfOriginalImageDeleted()
{
Setting::set('folders', ['pages']);
Setting::set('sizes', []);
Setting::set('breakpoints', []);
UploadedFile::fake()->image(100, 100)->storeAs('uploads/public/c/pages', 'test.jpg', 'local');
Event::fire('media.file.delete', [null, '/pages/test.jpg', null]);
$this->assertFileCount(0, 'pages');
Storage::assertMissing('uploads/public/c/pages');
}
public function testItPreservesDirectoryWhenThereAreOtherFiles()
{
Setting::set('folders', ['pages']);
Setting::set('sizes', []);
Setting::set('breakpoints', []);
UploadedFile::fake()->image(100, 100)->storeAs('uploads/public/c/pages', 'test.jpg', 'local');
UploadedFile::fake()->image(100, 100)->storeAs('uploads/public/c/pages', 'test2.jpg', 'local');
Event::fire('media.file.delete', [null, '/pages/test.jpg', null]);
$this->assertHasFile('pages/test2.jpg');
$this->assertDoesntHaveFile('pages/test.jpg');
}
public function testItDeletesFileVersions()
{
Setting::set('folders', ['pages']);
Setting::set('sizes', []);
Setting::set('breakpoints', []);
UploadedFile::fake()->image(100, 100)->storeAs('uploads/public/c/pages', 'test.jpg', 'local');
UploadedFile::fake()->image(300, 500)->storeAs('uploads/public/c/pages', 'test-300x500.jpg', 'local');
Event::fire('media.file.delete', [null, '/pages/test.jpg', null]);
$this->assertDoesntHaveFile('pages/test-300x500.jpg');
}
}

88
tests/ImageTagTest.php Normal file
View File

@ -0,0 +1,88 @@
<?php
namespace Aweos\Resizer\Tests\MediaTest;
use Aweos\Resizer\Classes\TagGenerator;
use Aweos\Resizer\Models\Setting;
use Aweos\Resizer\Tests\TestCase;
use Event;
use Illuminate\Http\UploadedFile;
use Storage;
class ImageTagTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
Storage::fake('local');
}
public function testItGeneratesAnImageTagWithOriginalSize()
{
Setting::set('folders', ['pages']);
Setting::set('sizes', []);
Setting::set('breakpoints', [100,200]);
$this->media->put('/pages/test.jpg', UploadedFile::fake()->image('test.jpg', 500, 500)->get());
UploadedFile::fake()->image('test.jpg', 500, 500)->storeAs('uploads/public/c/pages', 'test-500x500.jpg', 'local');
UploadedFile::fake()->image('test.jpg', 200, 200)->storeAs('uploads/public/c/pages', 'test-200x200.jpg', 'local');
UploadedFile::fake()->image('test.jpg', 100, 100)->storeAs('uploads/public/c/pages', 'test-100x100.jpg', 'local');
$this->assertEquals(
'width="500" height="500" sizes="(max-width: 100px) 100px, (max-width: 200px) 200px, (max-width: 500px) 500px" srcset="/storage/uploads/public/c/pages/test-100x100.jpg 100w, /storage/uploads/public/c/pages/test-200x200.jpg 200w, /storage/uploads/public/c/pages/test-500x500.jpg 500w" src="/storage/uploads/public/c/pages/test-500x500.jpg"',
app(TagGenerator::class)->generate('pages/test.jpg'),
);
}
public function testItSkipsImagesWithWrongAspectRatio()
{
Setting::set('folders', ['pages']);
Setting::set('sizes', []);
Setting::set('breakpoints', [100,200]);
$this->media->put('/pages/test.jpg', UploadedFile::fake()->image('test.jpg', 500, 500)->get());
UploadedFile::fake()->image('test.jpg', 100, 100)->storeAs('uploads/public/c/pages', 'test-100x100.jpg', 'local');
UploadedFile::fake()->image('test.jpg', 150, 100)->storeAs('uploads/public/c/pages', 'test-150x100.jpg', 'local');
$this->assertFalse(str_contains(
app(TagGenerator::class)->generate('pages/test.jpg'),
'150x100',
));
}
public function testItCanGenerateAAspectRatio()
{
Setting::set('folders', ['pages']);
Setting::set('sizes', [['name' => 'size', 'aspect_ratio' => '1x2']]);
Setting::set('breakpoints', [100,200]);
$this->media->put('/pages/test.jpg', UploadedFile::fake()->image('test.jpg', 500, 500)->get());
UploadedFile::fake()->image('test.jpg', 100, 100)->storeAs('uploads/public/c/pages', 'test-100x100.jpg', 'local');
UploadedFile::fake()->image('test.jpg', 150, 100)->storeAs('uploads/public/c/pages', 'test-150x100.jpg', 'local');
UploadedFile::fake()->image('test.jpg', 250, 500)->storeAs('uploads/public/c/pages', 'test-250x500.jpg', 'local');
UploadedFile::fake()->image('test.jpg', 200, 400)->storeAs('uploads/public/c/pages', 'test-200x400.jpg', 'local');
UploadedFile::fake()->image('test.jpg', 100, 200)->storeAs('uploads/public/c/pages', 'test-100x200.jpg', 'local');
$this->assertFalse(str_contains(
app(TagGenerator::class)->generate('pages/test.jpg', 'size'),
'100x100',
));
$this->assertTrue(str_contains(
app(TagGenerator::class)->generate('pages/test.jpg', 'size'),
'200x400',
));
}
public function testItServesAspectRatiosThatCanBeOnePixelLarger()
{
Setting::set('folders', ['pages']);
Setting::set('sizes', [['name' => 'size', 'aspect_ratio' => '1200x280']]);
Setting::set('breakpoints', [640]);
$this->media->put('/pages/test.jpg', UploadedFile::fake()->image('test.jpg', 1920, 899)->get());
UploadedFile::fake()->image('test.jpg', 640, 149)->storeAs('uploads/public/c/pages', 'test-640x149.jpg', 'local');
$this->assertTrue(str_contains(
app(TagGenerator::class)->generate('pages/test.jpg', 'size'),
'640x149',
));
}
}

69
tests/MoveTest.php Normal file
View File

@ -0,0 +1,69 @@
<?php
namespace Aweos\Resizer\Tests\MediaTest;
use Aweos\Resizer\Models\Setting;
use Aweos\Resizer\Tests\TestCase;
use Event;
use Illuminate\Http\UploadedFile;
use Storage;
class MoveTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
Storage::fake('local');
}
public function testItMovesAllVersionsOfAFile()
{
Setting::set('folders', ['pages']);
Setting::set('sizes', []);
Setting::set('breakpoints', []);
$this->media->put('/pages/alt/test.jpg', UploadedFile::fake()->image(100, 100)->get());
UploadedFile::fake()->image(100, 100)->storeAs('uploads/public/c/pages/alt', 'test.jpg', 'local');
UploadedFile::fake()->image(100, 100)->storeAs('uploads/public/c/pages/alt', 'test-200x300.jpg', 'local');
Event::fire('media.file.move', [null, '/pages/alt/test.jpg', '/pages/neu/test.jpg']);
$this->assertFileCount(0, 'pages/alt');
$this->assertHasFile('pages/neu/test.jpg');
$this->assertHasFile('pages/neu/test-200x300.jpg');
}
public function testItMovesFilesOnRename()
{
Setting::set('folders', ['pages']);
Setting::set('sizes', []);
Setting::set('breakpoints', []);
$this->media->put('/pages/test.jpg', UploadedFile::fake()->image(100, 100)->get());
UploadedFile::fake()->image(100, 100)->storeAs('uploads/public/c/pages', 'test.jpg', 'local');
UploadedFile::fake()->image(100, 100)->storeAs('uploads/public/c/pages', 'test-200x300.jpg', 'local');
Event::fire('media.file.rename', [null, '/pages/test.jpg', '/pages/testneu.jpg']);
$this->assertFileCount(2, 'pages');
$this->assertHasFile('pages/testneu.jpg');
$this->assertHasFile('pages/testneu-200x300.jpg');
}
public function testItDoesntMoveOtherFilesInTheSameDirectory()
{
Setting::set('folders', ['pages']);
Setting::set('sizes', []);
Setting::set('breakpoints', []);
$this->media->put('/pages/test.jpg', UploadedFile::fake()->image(100, 100)->get());
UploadedFile::fake()->image(100, 100)->storeAs('uploads/public/c/pages', 'test.jpg', 'local');
UploadedFile::fake()->image(100, 100)->storeAs('uploads/public/c/pages', 'test-200x300.jpg', 'local');
UploadedFile::fake()->image(100, 100)->storeAs('uploads/public/c/pages', 'testother.jpg', 'local');
Event::fire('media.file.rename', [null, '/pages/test.jpg', '/pages/testneu.jpg']);
$this->assertFileCount(3, 'pages');
$this->assertHasFile('pages/testneu.jpg');
$this->assertHasFile('pages/testneu-200x300.jpg');
}
}

158
tests/ResizerTest.php Normal file
View File

@ -0,0 +1,158 @@
<?php
namespace Aweos\Resizer\Tests\MediaTest;
use Aweos\Resizer\Models\Setting;
use Aweos\Resizer\Tests\TestCase;
use Event;
use Illuminate\Http\UploadedFile;
use Media\Classes\MediaLibrary;
use Storage;
class ResizerTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
Storage::fake('local');
}
public function testDontCopyOriginalFileWhenSettingsAreNotSet(): void
{
Setting::set('folders', []);
Setting::set('sizes', []);
Setting::set('breakpoints', []);
$file = UploadedFile::fake()->image('test.jpg', 500, 600);
$media = MediaLibrary::instance();
$media->put('/pages/test.jpg', $file);
Event::fire('media.file.upload', [null, '/pages/test.jpg', null]);
$this->assertFileCount(0, '');
}
public function testCopyOriginalFileWithoutSizesWhenNoSizesAreSet(): void
{
Setting::set('folders', [['folder' => '/pages']]);
Setting::set('sizes', []);
Setting::set('breakpoints', []);
$file = UploadedFile::fake()->image('test.jpg', 500, 600);
$media = MediaLibrary::instance();
$media->put('/pages/test.jpg', $file->get());
Event::fire('media.file.upload', [null, '/pages/test.jpg', null]);
$this->assertFileCount(2, 'pages');
$this->assertHasFile('pages/test.jpg');
$this->assertHasFile('pages/test-500x600.jpg');
}
public function testCopyTwoDirectoriesDeepButNotAnotherDirectory(): void
{
Setting::set('folders', [['folder' => '/pages']]);
Setting::set('sizes', []);
Setting::set('breakpoints', []);
$file = UploadedFile::fake()->image('test.jpg', 500, 600);
$media = MediaLibrary::instance();
$media->put('/pages-neu/test.jpg', $file->get());
Event::fire('media.file.upload', [null, '/pages-neu/test.jpg', null]);
$this->assertFileCount(0, 'pages');
$this->assertFileCount(0, 'pages-neu');
}
public function testCopySubdirectoriesOfSelectedPath(): void
{
Setting::set('folders', [['folder' => '/pages']]);
Setting::set('sizes', []);
Setting::set('breakpoints', []);
$file = UploadedFile::fake()->image('test.jpg', 500, 600);
$media = MediaLibrary::instance();
$media->put('/pages/neu/test.jpg', $file->get());
Event::fire('media.file.upload', [null, '/pages/neu/test.jpg', null]);
$this->assertFileCount(2, 'pages/neu');
}
public function testGenerateSizeIfSizeIsSmallerWithSameAspectRatio(): void
{
Setting::set('folders', [['folder' => '/pages']]);
Setting::set('sizes', []);
Setting::set('breakpoints', ['250']);
$file = UploadedFile::fake()->image('test.jpg', 500, 600);
$media = MediaLibrary::instance();
$media->put('/pages/neu/test.jpg', $file->get());
Event::fire('media.file.upload', [null, '/pages/neu/test.jpg', null]);
$this->assertFileCount(3, 'pages/neu');
$this->assertHasFile('pages/neu/test-250x300.jpg');
$this->assertHasFile('pages/neu/test-500x600.jpg');
$this->assertHasFile('pages/neu/test.jpg');
}
public function testGenerateSizeIfSizeIsSmallerWithDifferentAspectRatio(): void
{
Setting::set('folders', [['folder' => '/pages']]);
Setting::set('sizes', []);
Setting::set('breakpoints', ['250']);
$file = UploadedFile::fake()->image('test.jpg', 500, 100);
$media = MediaLibrary::instance();
$media->put('/pages/neu/test.jpg', $file->get());
Event::fire('media.file.upload', [null, '/pages/neu/test.jpg', null]);
$this->assertHasFile('pages/neu/test-250x50.jpg');
}
public function testDontGenerateSizeIfOriginalFileIsSmaller(): void
{
Setting::set('folders', [['folder' => '/pages']]);
Setting::set('sizes', []);
Setting::set('breakpoints', ['250']);
$file = UploadedFile::fake()->image('test.jpg', 250, 1000);
$media = MediaLibrary::instance();
$media->put('/pages/neu/test.jpg', $file->get());
Event::fire('media.file.upload', [null, '/pages/neu/test.jpg', null]);
$this->assertFileCount(2, 'pages/neu');
}
public function testDontGenerateSizeIfImageWouldBeLarger(): void
{
Setting::set('folders', [['folder' => '/pages']]);
Setting::set('sizes', []);
Setting::set('breakpoints', ['250']);
$file = UploadedFile::fake()->image('test.jpg', 249, 1000);
$media = MediaLibrary::instance();
$media->put('/pages/neu/test.jpg', $file->get());
Event::fire('media.file.upload', [null, '/pages/neu/test.jpg', null]);
$this->assertFileCount(2, 'pages/neu');
}
public function testGenerateBreakpointImage(): void
{
Setting::set('folders', [['folder' => '/pages']]);
Setting::set('sizes', [['name' => 'testas', 'aspect_ratio' => '5x8']]);
Setting::set('breakpoints', ['100']);
$file = UploadedFile::fake()->image('test.jpg', 500, 400);
$media = MediaLibrary::instance();
$media->put('/pages/neu/test.jpg', $file->get());
Event::fire('media.file.upload', [null, '/pages/neu/test.jpg', null]);
$this->assertFileCount(5, 'pages/neu');
$this->assertHasFile('pages/neu/test.jpg');
$this->assertHasFile('pages/neu/test-500x400.jpg');
$this->assertHasFile('pages/neu/test-250x400.jpg');
$this->assertHasFile('pages/neu/test-100x160.jpg');
$this->assertHasFile('pages/neu/test-100x80.jpg');
}
}

41
tests/TestCase.php Normal file
View File

@ -0,0 +1,41 @@
<?php
namespace Aweos\Resizer\Tests;
use Media\Classes\MediaLibrary;
use PluginTestCase;
use Storage;
use System\Classes\PluginManager;
class TestCase extends PluginTestCase
{
public MediaLibrary $media;
public function setUp(): void
{
parent::setUp();
$this->media = MediaLibrary::instance();
}
public function assertHasFile($file): void
{
Storage::disk('local')->assertExists("uploads/public/c/{$this->normalizeFilePath($file)}");
}
public function assertDoesntHaveFile($file): void
{
Storage::disk('local')->assertMissing("uploads/public/c/{$this->normalizeFilePath($file)}");
}
public function assertFileCount($count, $dir): void
{
$this->assertCount($count, Storage::disk('local')->files("uploads/public/c/{$this->normalizeFilePath($dir)}"));
}
public function normalizeFilePath(string $path): string
{
return preg_replace('|^/*|', '', $path);
}
}

BIN
tests/stub/img.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB