Skip to content

Commit 50e65d4

Browse files
author
Bradie Tilley
authored
Add permalinks for images (#32)
Add permalink option for images resizing to ensure that the resized image will always yield the same URL and thus the same image.
1 parent f0c12d5 commit 50e65d4

File tree

8 files changed

+363
-8
lines changed

8 files changed

+363
-8
lines changed

Plugin.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use ABWebDevelopers\ImageResize\Classes\Resizer;
66
use ABWebDevelopers\ImageResize\Commands\ImageResizeClear;
77
use ABWebDevelopers\ImageResize\Commands\ImageResizeGc;
8+
use ABWebDevelopers\ImageResize\Commands\ImageResizeResetPermalinks;
9+
use ABWebDevelopers\ImageResize\Models\ImagePermalink;
810
use ABWebDevelopers\ImageResize\Models\Settings;
911
use ABWebDevelopers\ImageResize\ReportWidgets\ImageResizeClearWidget;
1012
use App;
@@ -46,6 +48,11 @@ public function registerMarkupTags()
4648
$height = ($height !== null) ? (int) $height : null;
4749
$options = ($options instanceof Arrayable) ? $options->toArray() : (array) $options;
4850

51+
// If the given configuration has a permalink identifier then resize using it
52+
if (isset($options['permalink']) && strlen($options['permalink'])) {
53+
return $resizer->resizePermalink($options['permalink'], $width, $height, $options)->permalink_url;
54+
}
55+
4956
return $resizer->resize($width, $height, $options);
5057
},
5158
'modify' => function ($image, $options = []) {
@@ -55,6 +62,11 @@ public function registerMarkupTags()
5562
$height = null;
5663
$options = ($options instanceof Arrayable) ? $options->toArray() : (array) $options;
5764

65+
// If the given configuration has a permalink identifier then resize using it
66+
if (isset($options['permalink']) && strlen($options['permalink'])) {
67+
return $resizer->resizePermalink($options['permalink'], $width, $height, $options)->permalink_url;
68+
}
69+
5870
return $resizer->resize($width, $height, $options);
5971
},
6072
'filterHtmlImageResize' => function ($html, $width, $height = null, $options = []) {
@@ -146,6 +158,7 @@ public function register()
146158
{
147159
$this->registerConsoleCommand('imageresize:gc', ImageResizeGc::class);
148160
$this->registerConsoleCommand('imageresize:clear', ImageResizeClear::class);
161+
$this->registerConsoleCommand('imageresize:reset-permalink', ImageResizeResetPermalinks::class);
149162
}
150163

151164
/**
@@ -172,7 +185,7 @@ public function registerReportWidgets()
172185

173186
/**
174187
* Run the callback only if/when the database exists (and system_settings table exists).
175-
*
188+
*
176189
* @param \Closure $callback
177190
* @return mixed
178191
*/

classes/Resizer.php

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace ABWebDevelopers\ImageResize\Classes;
44

5+
use ABWebDevelopers\ImageResize\Models\ImagePermalink;
56
use ABWebDevelopers\ImageResize\Models\Settings;
67
use Cache;
78
use Carbon\Carbon;
@@ -333,6 +334,16 @@ private function initOptions(array $options = null)
333334
return $this;
334335
}
335336

337+
/**
338+
* Get the options defined in this resizer
339+
*
340+
* @return array
341+
*/
342+
public function getOptions(): array
343+
{
344+
return $this->options;
345+
}
346+
336347
/**
337348
* Get the absolute physical path of the image
338349
*
@@ -358,7 +369,7 @@ public function outputExtension(): string
358369
*
359370
* @return string
360371
*/
361-
private function getRelativePath(): string
372+
public function getRelativePath(): string
362373
{
363374
$rel = '/' . substr($this->hash, 0, 3) .
364375
'/' . substr($this->hash, 3, 3) .
@@ -466,6 +477,33 @@ public function resize(int $width = null, int $height = null, array $options = n
466477
return $this->storeCacheAndgetFirstTimeUrl();
467478
}
468479

480+
/**
481+
* Resize - Optionally resize the image, and/or modify the image with options.
482+
*
483+
* This implements a permalink class and does not resize until first accessed
484+
*
485+
* @param int $width
486+
* @param int $height
487+
* @param array $options
488+
* @return ImagePermalink
489+
*/
490+
public function resizePermalink(string $identifier, int $width = null, int $height = null, array $options = []): ImagePermalink
491+
{
492+
$identifier = trim($identifier, '/');
493+
494+
$width = ($width > 0) ? $width : null;
495+
$height = ($height > 0) ? $height : null;
496+
497+
// Fill these keys in, as it'll be used to help identify the cache
498+
$options['width'] = $width;
499+
$options['height'] = $height;
500+
501+
// Set options, set hash for cache
502+
$this->initOptions($options);
503+
504+
return ImagePermalink::fromResizer($identifier, $this);
505+
}
506+
469507
/**
470508
* Does the current file exist in the filesystem?
471509
*
@@ -677,7 +715,7 @@ public function doResize()
677715
* @param array $options
678716
* @return array
679717
*/
680-
private function detectFormat(bool $useNewFormat = false): array
718+
public function detectFormat(bool $useNewFormat = false): array
681719
{
682720
// Convert standard boolean to numeric boolean (for array access)
683721
$useNewFormat = ($useNewFormat) ? 1 : 0;
@@ -934,19 +972,19 @@ public static function clearFiles(Carbon $minAge = null, string $directory = nul
934972

935973
/**
936974
* Parse a given HTML string for images and replace them with the resized copies as per the given modifications.
937-
*
975+
*
938976
* CAUTION: Experimental
939977
*
940978
* This uses regex to find and replace HTML content which is often frowned upon. You may supply your own custom
941979
* regexes, or you may rely on the defaults (which may change in future versions of this plugin soo beware).
942-
*
980+
*
943981
* By default this will search img elements with a src, data-src or lazy-src attribute, as well as any "style"
944982
* attribute with a background or background-image CSS rule (of which contains a "url()" to an image)
945-
*
983+
*
946984
* Example Usage (a richeditor field that contains custom embedded images that require OTF optimisation or resizing):
947985
* {{ service.description | filterHtmlImageResize(600, 600, { mode: 'contain' }) }}
948986
* {{ service.description | filterHtmlImageModifiy({ quality: 60 }) }}
949-
*
987+
*
950988
* @param string $html The HTML to find/replace images
951989
* @param int|null $width
952990
* @param int|null $height
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace ABWebDevelopers\ImageResize\Commands;
4+
5+
use ABWebDevelopers\ImageResize\Classes\Resizer;
6+
use ABWebDevelopers\ImageResize\Models\ImagePermalink;
7+
use Illuminate\Console\Command;
8+
use Illuminate\Support\Str;
9+
10+
class ImageResizeResetPermalinks extends Command
11+
{
12+
protected $name = 'imageresize:reset-permalinks';
13+
14+
protected $description = 'Delete all permalink configurations in case of needing to regenerate all images using new modifications. If all identifiers are the same, this should have no major affect on the website.';
15+
16+
public function handle()
17+
{
18+
$deleted = ImagePermalink::count();
19+
20+
// Delete all permalinks
21+
ImagePermalink::query()->delete();
22+
23+
$this->info('Successfully deleted ' . $deleted . ' ' . Str::plural('permalinks', $deleted));
24+
}
25+
}

models/ImagePermalink.php

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
<?php
2+
3+
namespace ABWebDevelopers\ImageResize\Models;
4+
5+
use ABWebDevelopers\ImageResize\Classes\Resizer;
6+
use Illuminate\Contracts\Support\Arrayable;
7+
use Illuminate\Database\Eloquent\Builder;
8+
use Model;
9+
10+
class ImagePermalink extends Model
11+
{
12+
public $table = 'abweb_imageresize_permalinks';
13+
14+
/**
15+
* Define cast types for each modifier type
16+
*
17+
* @var array
18+
*/
19+
protected $castModifiers = [
20+
'identifier' => 'string',
21+
'image' => 'string',
22+
'path' => 'string',
23+
];
24+
25+
public $jsonable = [
26+
'options',
27+
];
28+
29+
/**
30+
* When casting this object to string, retrieve the Permalink URL
31+
*
32+
* @return string
33+
*/
34+
public function __toString()
35+
{
36+
return $this->permalink_url;
37+
}
38+
39+
/**
40+
* Get an ImagePermalink class by the given identifier (and provide
41+
* defaults for when not resized yet: width, height, options)
42+
*
43+
* @param string $identifier
44+
* @param string $image
45+
* @param integer $width
46+
* @param integer $height
47+
* @param array $options
48+
* @return ImagePermalink
49+
*/
50+
public static function getPermalink(string $identifier, string $image, int $width = null, int $height = null, array $options = []): ImagePermalink
51+
{
52+
$resizer = new Resizer((string) $image);
53+
54+
$width = ($width !== null) ? (int) $width : null;
55+
$height = ($height !== null) ? (int) $height : null;
56+
$options = ($options instanceof Arrayable) ? $options->toArray() : (array) $options;
57+
58+
return $resizer->resizePermalink($identifier, $width, $height, $options)->permalink_url;
59+
}
60+
61+
/**
62+
* Scope all results by those matching the identifier (should always be only one)
63+
*
64+
* @param Builder $query
65+
* @param string $identifier
66+
* @return void
67+
*/
68+
public function scopeByIdentifier(Builder $query, string $identifier): void
69+
{
70+
$query->where('identifier', $identifier);
71+
}
72+
73+
/**
74+
* Get the Image Permalink with this identifier if it exists
75+
*
76+
* @param string $identifier
77+
* @return ImagePermalink|null
78+
*/
79+
public static function withIdentifer(string $identifier): ?ImagePermalink
80+
{
81+
return static::byIdentifier($identifier)->first();
82+
}
83+
84+
/**
85+
* Does the resized version of this image exist?
86+
*
87+
* @return boolean
88+
*/
89+
public function resizeExists(): bool
90+
{
91+
return !empty($this->path) && file_exists($this->absolute_path);
92+
}
93+
94+
/**
95+
* Generate a short life hash for this file.
96+
* This will be where the image is stored in the storage path
97+
*
98+
* @return string
99+
*/
100+
public function generateShortLifeHash(): string
101+
{
102+
return hash('sha256', $this->identifier . ':permalink:' . json_encode($this->options));
103+
}
104+
105+
/**
106+
* Resize the image, if not already resized
107+
*
108+
* @return void
109+
*/
110+
public function resize()
111+
{
112+
if ($this->resizeExists() === false) {
113+
$config = $this->options ?? [];
114+
115+
$resizer = Resizer::using($this->image)
116+
->setHash($hash = $this->generateShortLifeHash())
117+
->setOptions($config)
118+
->doResize();
119+
120+
$path = $resizer->getRelativePath();
121+
122+
$this->path = $path;
123+
$this->resized_at = now();
124+
$this->save();
125+
}
126+
127+
return $this;
128+
}
129+
130+
/**
131+
* Get the absolute path to the resized image
132+
*
133+
* @return string
134+
*/
135+
public function getAbsolutePathAttribute(): string
136+
{
137+
return base_path($this->path);
138+
}
139+
140+
/**
141+
* Get the permalink URL for this image
142+
*
143+
* @return string
144+
*/
145+
public function getPermalinkUrlAttribute(): string
146+
{
147+
return url('/imageresizestatic/' . $this->identifier . '.' . $this->extension);
148+
}
149+
150+
/**
151+
* Render this image to screen, now.
152+
*
153+
* @return void
154+
*/
155+
public function render()
156+
{
157+
$this->resize(); // if not resized
158+
159+
header('Content-Type: ' . $this->mime);
160+
header('Content-Length: ' . filesize($this->absolute_path));
161+
echo file_get_contents($this->absolute_path);
162+
exit();
163+
}
164+
165+
/**
166+
* Get a default 404 image not found mock permalink
167+
*
168+
* @return ImagePermalink
169+
*/
170+
public static function defaultNotFound(): ImagePermalink
171+
{
172+
$identifier = '404';
173+
$resizer = Resizer::using('');
174+
175+
return static::fromResizer($identifier, $resizer);
176+
}
177+
178+
/**
179+
* Initialise an ImagePermalink from the given Resizer instance
180+
*
181+
* @param string $identifier
182+
* @param Resizer $resizer
183+
* @return ImagePermalink
184+
*/
185+
public static function fromResizer(string $identifier, Resizer $resizer): ImagePermalink
186+
{
187+
$that = static::withIdentifer($identifier);
188+
189+
if ($that === null) {
190+
$that = new static();
191+
192+
$that->identifier = $identifier;
193+
$that->image = $resizer->getImagePath();
194+
195+
list($mime, $format) = $resizer->detectFormat(true);
196+
197+
$that->mime_type = 'image/' . $mime;
198+
$that->extension = $format;
199+
$that->options = $resizer->getOptions();
200+
201+
$that->save();
202+
}
203+
204+
return $that;
205+
}
206+
}

0 commit comments

Comments
 (0)