FeatureImageManager::fromConfig()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 11
ccs 9
cts 9
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
4
namespace NovaThinKit\FeatureImage;
5
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Http\UploadedFile;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Facades\Storage;
10
use Illuminate\Support\Str;
11
use NovaThinKit\FeatureImage\Models\WithFeatureImage;
12
use Spatie\Image\Image;
13
use Spatie\Image\Manipulations;
14
use Symfony\Component\HttpFoundation\StreamedResponse;
15
16
class FeatureImageManager implements ImageManager
17
{
18
    /**
19
     * Files storage disk
20
     * @var string|null
21
     */
22
    public ?string $disk;
23
24
    /**
25
     * Predefined formats
26
     * @var array
27
     */
28
    public array $formats;
29
30
    /**
31
     * Deleted formats
32
     * @var array
33
     */
34
    public array $deletedFormats = [];
35
36
    /**
37
     * Generate default responsive formats
38
     * @var bool
39
     */
40
    public bool $responsive;
41
42
    /**
43
     * Filename
44
     * @var WithFeatureImage|null
45
     */
46
    public ?WithFeatureImage $model = null;
47
48
    /**
49
     * Fallback to display specific image if not uploaded.
50
     *
51
     * @var string|null
52
     */
53
    protected ?string $defaultPath = null;
54
55
    protected ?string $tag = null;
56
57 18
    public function __construct(?string $disc = null, array $formats = [], bool $responsive = false, array $options = [])
58
    {
59 18
        $this->disk       = $disc;
60 18
        $this->formats    = $formats;
61 18
        $this->responsive = $responsive;
62
63
        /** @deprecated */
64 18
        if (isset($options['column'])) {
65 1
            $this->tag = $options['column'];
66
        }
67
68 18
        if (array_key_exists('default', $options)) {
69 1
            $this->defaultPath = $options['default'];
70
        }
71
72 18
        if (isset($options['deletedFormats']) && is_array($options['deletedFormats'])) {
73 17
            $this->deletedFormats = $options['deletedFormats'];
74
        }
75
    }
76
77 18
    public static function fromConfig(array $config): static
78
    {
79 18
        return new static(
80 18
            $config['disk']    ?? null,
81 18
            $config['formats'] ?? [],
82 18
            (bool)($config['responsive'] ?? false),
83
            // TODO: $config['options'] should not be present on config anymore
84 18
            array_merge($config['options'] ?? [], Arr::except($config, [
85 18
                'disk',
86 18
                'formats',
87 18
                'responsive',
88 18
            ])),
89 18
        );
90
    }
91
92 1
    public function tag(): ?string
93
    {
94 1
        return $this->tag;
95
    }
96
97 1
    public function withDefaultPath(?string $defaultPath): static
98
    {
99 1
        $this->defaultPath = $defaultPath;
100
101 1
        return $this;
102
    }
103
104 1
    public function withoutDefaultPath(): static
105
    {
106 1
        $this->defaultPath = null;
107
108 1
        return $this;
109
    }
110
111
    /**
112
     * @inheritDoc
113
     */
114 11
    public function storeUploaded(UploadedFile $image, array $options = []): string
115
    {
116 11
        if ($this->filename()) {
117 3
            $this->delete();
118
        }
119
120 11
        $storage     = $this->storage();
121 11
        $fileDir     = $this->directory();
122 11
        $newFileName = Str::random(30);
123 11
        $newFileExt  = '.' . $image->extension();
124
125 11
        $storage->makeDirectory($fileDir);
126
127 11
        $storagePath = "{$fileDir}{$newFileName}{$newFileExt}";
128 11
        $path        = $storage->path($storagePath);
129
130 11
        Image::load($image->path())
131 11
            ->fit(Manipulations::FIT_MAX, 2800, 1800)
132 11
            ->optimize()
133 11
            ->save($path);
134
135 11
        foreach ($this->formats as $format => $configuration) {
136 11
            $formatPath = $storage->path("{$fileDir}{$newFileName}-{$format}{$newFileExt}");
137 11
            $builder    = Image::load($image->path());
138 11
            if (!empty($configuration['methods']) && is_array($configuration['methods'])) {
139 11
                foreach ($configuration['methods'] as $method => $attrs) {
140 11
                    call_user_func_array([$builder, $method], $attrs);
141
                }
142
            }
143 11
            $builder->save($formatPath);
144
        }
145
146 11
        if ($this->responsive) {
147 10
            $width = 1600;
148 10
            while ($width >= 400) {
149 10
                $scalePath = $storage->path("{$fileDir}{$newFileName}-{$width}{$newFileExt}");
150 10
                Image::load($path)
151 10
                    ->width($width)
152 10
                    ->optimize()
153 10
                    ->save($scalePath);
154 10
                $width = $width - 400;
155
            }
156
        }
157
158
159 11
        return $storagePath;
160
    }
161
162
    /**
163
     * @inheritDoc
164
     */
165 6
    public function delete(?string $filename = null): bool
166
    {
167 6
        if ($filename) {
168 1
            return $this->storage()->delete($filename);
169
        }
170 5
        $fileName = $this->filename();
171
172 5
        if (!$fileName) {
173 1
            return false;
174
        }
175
176 5
        $filesToDelete = [
177 5
            $fileName,
178 5
        ];
179
180 5
        [$name, $extension] = $this->explodeFilename($fileName);
181
182 5
        foreach (array_keys($this->formats) as $format) {
183 5
            $filesToDelete[] = "{$name}-{$format}.{$extension}";
184
        }
185
186 5
        foreach ($this->deletedFormats as $format) {
187 4
            $filesToDelete[] = "{$name}-{$format}.{$extension}";
188
        }
189
190 5
        foreach ($this->storage()->files($this->directory()) as $file) {
191
            if (
192 4
                ($suffix = Str::between($file, "{$name}-", ".{$extension}")) &&
193 4
                is_numeric($suffix)
194
            ) {
195 3
                $filesToDelete[] = $file;
196
            }
197
        }
198
199 5
        return $this->storage()->delete(array_unique($filesToDelete));
200
    }
201
202
    /**
203
     * @inheritDoc
204
     */
205 2
    public function exists(?string $format = null, ?string $filename = null): bool
206
    {
207 2
        if ($format && !in_array($format, array_keys($this->formats))) {
208 2
            $format = null;
209
        }
210
211 2
        $path = $filename ?? $this->filename($format);
212 2
        if (!$path) {
213 1
            return false;
214
        }
215
216 2
        return $this->storage()->exists($path);
217
    }
218
219
    /**
220
     * @inheritDoc
221
     */
222 1
    public function path(?string $format = null, ?string $filename = null): ?string
223
    {
224 1
        if ($format && !in_array($format, array_keys($this->formats))) {
225 1
            $format = null;
226
        }
227
228 1
        $filename = $filename ?? ($this->filename($format) ?? $this->defaultPath);
229
230 1
        return $filename ? $this->storage()->path($filename) : null;
231
    }
232
233
    /**
234
     * @inheritDoc
235
     */
236 7
    public function url(?string $format = null, ?string $filename = null): ?string
237
    {
238 7
        if ($format && !in_array($format, array_keys($this->formats))) {
239 1
            $format = null;
240
        }
241
242 7
        $filename = $filename ?? ($this->filename($format) ?? $this->defaultPath);
243
244 7
        if (!$filename) {
245 5
            return null;
246
        }
247
248 4
        return $this->storage()->url($filename);
249
    }
250
251
    /**
252
     * @inheritDoc
253
     */
254 2
    public function download(?string $format = null, ?string $filename = null, ?string $name = null, array $headers = []): ?StreamedResponse
255
    {
256 2
        if ($format && !in_array($format, array_keys($this->formats))) {
257 1
            $format = null;
258
        }
259
260 2
        $filename = $filename ?? ($this->filename($format) ?? $this->defaultPath);
261
262 2
        if (!$filename) {
263 1
            return null;
264
        }
265
266 2
        return $this->storage()->download($filename, $name, $headers);
267
    }
268
269 18
    public function setTag(?string $tag = null): ImageManager
270
    {
271 18
        $this->tag = $tag;
272
273 18
        return $this;
274
    }
275
276 17
    public function setModel(WithFeatureImage $model): ImageManager
277
    {
278 17
        $this->model = $model;
279
280 17
        return $this;
281
    }
282
283 1
    public function getModel(): ?WithFeatureImage
284
    {
285 1
        return $this->model;
286
    }
287
288
    /**
289
     * @return \Illuminate\Filesystem\FilesystemAdapter
290
     */
291 14
    public function storage(): mixed
292
    {
293 14
        return Storage::disk($this->disk);
294
    }
295
296 15
    public function featureImageKey(): string
297
    {
298 15
        return ($this->model && method_exists($this->model, 'featureImageKey')) ? $this->model->featureImageKey($this->tag) : 'image';
299
    }
300
301 16
    public function filename(?string $format = null): ?string
302
    {
303 16
        $filename = $this->model?->{$this->featureImageKey()};
304
305 16
        if ($filename && $format) {
306 3
            [$name, $extension] = $this->explodeFilename($filename);
307 3
            $filename           = $name . "-{$format}." . $extension;
0 ignored issues
show
Bug introduced by
Are you sure $extension of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

307
            $filename           = $name . "-{$format}." . /** @scrutinizer ignore-type */ $extension;
Loading history...
308
        }
309
310 16
        return $filename;
311
    }
312
313 12
    public function directory(): string
314
    {
315 12
        if ($this->model && method_exists($this->model, 'featureImageManagerDirectory')) {
316 12
            $directory = $this->model->featureImageManagerDirectory($this->tag);
317 1
        } elseif ($this->model && ($this->model instanceof Model)) {
318 1
            $directory = base64_encode(Str::slug($this->model->getMorphClass()) . '-' . $this->model->getKey());
319
        } else {
320 1
            $directory = '';
321
        }
322
323 12
        return rtrim($directory, '/') . '/';
324
    }
325
326 7
    protected function explodeFilename(string $fileName): array
327
    {
328 7
        $extension = pathinfo($fileName, PATHINFO_EXTENSION);
329
330 7
        $name = Str::beforeLast($fileName, ".{$extension}");
331
332 7
        return [$name, $extension];
333
    }
334
}
335