Skip to content

Commit 18db5f7

Browse files
le0danielle0daniel
authored andcommitted
Initial commit
0 parents  commit 18db5f7

File tree

14 files changed

+1133
-0
lines changed

14 files changed

+1133
-0
lines changed

.gitignore

Whitespace-only changes.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Laravel Image Engine

composer.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "le0daniel/laravel-image-engine",
3+
"description": "Integration of automatic image manipulation and serving",
4+
"license": "MIT",
5+
"authors": [
6+
{
7+
"name": "Leo Daniel Studer",
8+
"email": "leo.studer@nitrosoft.ch"
9+
}
10+
],
11+
"autoload": {
12+
"psr-4": {
13+
"le0daniel\\Laravel\\ImageEngine\\": "src/"
14+
}
15+
},
16+
"extra": {
17+
"laravel": {
18+
"providers": [
19+
"le0daniel\\Laravel\\ImageEngine\\ImageEngineProvider"
20+
]
21+
}
22+
},
23+
"require": {
24+
"php": "^7.2",
25+
"laravel/framework": "^5.6||^6",
26+
"intervention/image": "^2.5"
27+
}
28+
}

src/Commands/FilesystemClear.php

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
<?php
2+
/**
3+
* Created by PhpStorm.
4+
* User: leodanielstuder
5+
* Date: 27.10.19
6+
* Time: 14:49
7+
*/
8+
9+
namespace le0daniel\Laravel\ImageEngine\Commands;
10+
11+
12+
use Illuminate\Console\Command;
13+
use Symfony\Component\Finder\Exception\DirectoryNotFoundException;
14+
use Symfony\Component\Finder\Finder;
15+
16+
class FilesystemClear extends Command
17+
{
18+
/**
19+
* The name and signature of the console command.
20+
*
21+
* @var string
22+
*/
23+
protected $signature = 'filesystem:clear';
24+
25+
/**
26+
* The console command description.
27+
*
28+
* @var string
29+
*/
30+
protected $description = 'Clears tmp files older than 1 hour';
31+
32+
/**
33+
* Create a new command instance.
34+
*
35+
* @return void
36+
*/
37+
public function __construct()
38+
{
39+
parent::__construct();
40+
}
41+
42+
/**
43+
* Return absolute path of directories to clear with a file max age
44+
*
45+
* @return array
46+
*/
47+
protected function dirsToClean(): array
48+
{
49+
return config('image-engine.dirs_to_clear', []);
50+
}
51+
52+
/**
53+
* @param int $days
54+
* @return int
55+
*/
56+
protected function days(int $days): int
57+
{
58+
return $days * 3600 * 24;
59+
}
60+
61+
/**
62+
* @param int $hours
63+
* @return int
64+
*/
65+
protected function hours(int $hours): int
66+
{
67+
return $hours * 3600;
68+
}
69+
70+
/**
71+
* @param int $months
72+
* @return int
73+
*/
74+
protected function months(int $months): int
75+
{
76+
return $months * 24 * 3600 * 24;
77+
}
78+
79+
/**
80+
* Enforce some directories to always be there
81+
*
82+
* @return array
83+
*/
84+
protected function recreateDirectories(): array
85+
{
86+
$dirs = collect(array_keys($this->dirsToClean()));
87+
return $dirs->map(function (string $dir) {
88+
return array_filter(
89+
explode('/', $dir),
90+
function (string $element) {
91+
return $element !== '*';
92+
});
93+
})->map(function (array $parts): string {
94+
return implode('/', $parts);
95+
})->toArray();
96+
}
97+
98+
protected function extractAgeFromConfig(array $config): int
99+
{
100+
if (isset($config['seconds'])) {
101+
return $config['seconds'];
102+
}
103+
104+
if (isset($config['days'])) {
105+
return $this->days($config['days']);
106+
}
107+
108+
if (isset($config['hours'])) {
109+
return $this->hours($config['hours']);
110+
}
111+
112+
if (isset($config['months'])) {
113+
return $this->months($config['months']);
114+
}
115+
116+
throw new \Exception('Invalid config. Seconds, hours, days or months must be given');
117+
}
118+
119+
/**
120+
* Execute the console command.
121+
*
122+
* @return mixed
123+
*/
124+
public function handle()
125+
{
126+
foreach ($this->dirsToClean() as $absoluteDir => $config) {
127+
try {
128+
$maxAge = $this->extractAgeFromConfig($config);
129+
130+
$finder = (new Finder())
131+
->ignoreUnreadableDirs()
132+
->files()
133+
->in($absoluteDir);
134+
135+
if (isset($config['name'])) {
136+
$finder->name($config['name']);
137+
}
138+
}
139+
catch (DirectoryNotFoundException $exception){
140+
$this->line("Directory <info>{$absoluteDir}</info> empty or not found");
141+
continue;
142+
}
143+
catch (\Exception $error) {
144+
$class = get_class($error);
145+
$this->error("[{$class}]: {$error->getMessage()}");
146+
continue;
147+
}
148+
149+
$this->line('Clear files in <info>' . $absoluteDir . '</info>');
150+
$this->cleanFinder($finder, $maxAge);
151+
}
152+
153+
foreach ($this->recreateDirectories() as $directory) {
154+
if (!file_exists($directory) && !is_dir($directory)) {
155+
mkdir($directory, 0777, true);
156+
$this->line('Made directory: <info>' . $directory . '</info>');
157+
}
158+
}
159+
}
160+
161+
/**
162+
* @param string $dir
163+
*/
164+
protected function clearDirRecursively(string $dir)
165+
{
166+
while ($this->isEmptyDir($dir)) {
167+
rmdir($dir);
168+
$dir = dirname($dir);
169+
}
170+
}
171+
172+
/**
173+
* @param string $dir
174+
* @return bool
175+
*/
176+
protected function isEmptyDir(string $dir): bool
177+
{
178+
if (!file_exists($dir) || !is_dir($dir)) {
179+
return false;
180+
}
181+
182+
return !(new \FilesystemIterator($dir))->valid();
183+
}
184+
185+
/**
186+
* @param Finder $finder
187+
* @param int $maxAgeInSeconds
188+
*/
189+
protected function cleanFinder(Finder $finder, int $maxAgeInSeconds)
190+
{
191+
$index = 0;
192+
// Loop through files
193+
foreach ($finder as $splFileInfo) {
194+
if (($splFileInfo->getMTime() + $maxAgeInSeconds) < time()) {
195+
$index++;
196+
$path = $splFileInfo->getRealPath();
197+
198+
if (file_exists($path)) {
199+
$this->line($path);
200+
unlink($path);
201+
} else {
202+
$this->error("> [{$index}] could not delete {$path}, file does not exist");
203+
continue;
204+
}
205+
206+
$this->clearDirRecursively(dirname($path));
207+
$this->line("> [{$index}] deleted <info>{$path}</info>");
208+
}
209+
}
210+
}
211+
}

src/Commands/ImageUrl.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
/**
3+
* Created by PhpStorm.
4+
* User: leodanielstuder
5+
* Date: 27.10.19
6+
* Time: 13:44
7+
*/
8+
9+
namespace le0daniel\Laravel\ImageEngine\Commands;
10+
11+
12+
use Carbon\Carbon;
13+
use Illuminate\Console\Command;
14+
use Illuminate\Support\Facades\Storage;
15+
use le0daniel\Laravel\ImageEngine\Image\Image;
16+
17+
class ImageUrl extends Command
18+
{
19+
20+
/**
21+
* Signature of the command
22+
*
23+
* @var string
24+
*/
25+
protected $signature = 'image:url {path} {size} {disk=local} {extension?}';
26+
27+
/**
28+
* @throws \Illuminate\Contracts\Container\BindingResolutionException
29+
*/
30+
public function handle()
31+
{
32+
$path = $this->argument('path');
33+
$size = $this->argument('size');
34+
$disk = $this->argument('disk');
35+
$extension = $this->argument('extension');
36+
37+
$image = new Image(
38+
$path,
39+
$size,
40+
Carbon::now()->addMinutes(10),
41+
$disk
42+
);
43+
44+
if (!Storage::disk($disk)->exists($path)) {
45+
return $this->line('Image not found. Path ' . $path . ' on disk ' . $disk, 'error');
46+
}
47+
48+
if (!$extension) {
49+
$extension = pathinfo($path, PATHINFO_EXTENSION) ?? 'jpg';
50+
$this->line("No extension given, using <info>{$extension}</info>");
51+
}
52+
53+
// Force
54+
$this->line('Generating URL for file: ' . $path, 'info');
55+
$this->line(
56+
image_url($image, $extension)
57+
);
58+
59+
//$this->line('u: '. $disk);
60+
61+
}
62+
63+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
/**
3+
* Created by PhpStorm.
4+
* User: leodanielstuder
5+
* Date: 27.10.19
6+
* Time: 12:42
7+
*/
8+
9+
namespace le0daniel\Laravel\ImageEngine\Http\Controllers;
10+
11+
use Illuminate\Http\Request;
12+
use Illuminate\Routing\Controller as BaseController;
13+
use le0daniel\Laravel\ImageEngine\Image\Image;
14+
use le0daniel\Laravel\ImageEngine\Image\ImageException;
15+
use le0daniel\Laravel\ImageEngine\Image\Renderer;
16+
use le0daniel\Laravel\ImageEngine\Image\Signer;
17+
18+
class ImageController extends BaseController
19+
{
20+
/**
21+
* @var Signer
22+
*/
23+
protected $signer;
24+
25+
/**
26+
* @var Renderer
27+
*/
28+
protected $renderer;
29+
30+
/**
31+
* ImageController constructor.
32+
* @param Signer $signer
33+
* @param Renderer $renderer
34+
*/
35+
public function __construct(Signer $signer, Renderer $renderer)
36+
{
37+
$this->signer = $signer;
38+
$this->renderer = $renderer;
39+
}
40+
41+
42+
/**
43+
* @param string $unverifiedPayload
44+
* @param string $signature
45+
* @param string $extension
46+
* @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse
47+
* @throws \Exception
48+
*/
49+
public function image(string $unverifiedPayload, string $signature, string $extension)
50+
{
51+
try {
52+
$payload = $this->signer->verifyAndUnpack($unverifiedPayload, $signature);
53+
} catch (\Exception $error) {
54+
return response()->json([
55+
'error' => 'Invalid signature.'
56+
], 403);
57+
}
58+
59+
$image = Image::createFromPayload($payload);
60+
61+
// Check if expired
62+
if ($image->isExpired()) {
63+
response()->json([
64+
'error' => 'Expired'
65+
], 403);
66+
}
67+
68+
try {
69+
return response()->file(
70+
$this->renderer->render($image, $extension),
71+
$image->cacheControlHeaders()
72+
);
73+
} catch (ImageException $exception) {
74+
if (config('app.debug') === true) {
75+
return response()->json([
76+
'error' => $exception->getMessage(),
77+
'hint' => $exception->getHint(),
78+
], 500);
79+
}
80+
return response()->json([
81+
'error' => 'Invalid request. Forbidden'
82+
], 403);
83+
}
84+
}
85+
86+
}

0 commit comments

Comments
 (0)