• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

plank / laravel-mediable / 23991546572

05 Apr 2026 01:22AM UTC coverage: 94.27% (-0.5%) from 94.72%
23991546572

push

github

web-flow
Add support for intervention/image:4.0 (#388)

- Added support for intervention/image:4.0 (#388)
- Dropped support for intervention/image:2.X
- Added ImageManipulation::setOutputOptions(), to support various intervention/image output format settings
- Deprecated ImageManipulation::setOutputQuality(). Use the 'quality' key in setOutputOptions() instead

29 of 38 new or added lines in 2 files covered. (76.32%)

3 existing lines in 1 file now uncovered.

1497 of 1588 relevant lines covered (94.27%)

152.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.29
/src/ImageManipulation.php
1
<?php
2

3
namespace Plank\Mediable;
4

5
use Plank\Mediable\Exceptions\MediaUpload\ConfigurationException;
6
use Plank\Mediable\Helpers\File;
7
use Spatie\ImageOptimizer\Optimizer;
8
use Spatie\ImageOptimizer\OptimizerChain;
9

10
class ImageManipulation
11
{
12
    public const FORMAT_BMP = 'bmp';
13
    public const FORMAT_GIF = 'gif';
14
    public const FORMAT_HEIC = 'heic';
15
    public const FORMAT_JPG = 'jpg';
16
    public const FORMAT_PNG = 'png';
17
    public const FORMAT_TIFF = 'tif';
18
    public const FORMAT_WEBP = 'webp';
19

20
    public const VALID_IMAGE_FORMATS = [
21
        self::FORMAT_BMP,
22
        self::FORMAT_GIF,
23
        self::FORMAT_HEIC,
24
        self::FORMAT_JPG,
25
        self::FORMAT_PNG,
26
        self::FORMAT_TIFF,
27
    ];
28

29
    public const MIME_TYPE_MAP = [
30
        self::FORMAT_BMP => 'image/bmp',
31
        self::FORMAT_GIF => 'image/gif',
32
        self::FORMAT_HEIC => 'image/heic',
33
        self::FORMAT_JPG => 'image/jpeg',
34
        self::FORMAT_PNG => 'image/png',
35
        self::FORMAT_TIFF => 'image/tiff',
36
        self::FORMAT_WEBP => 'image/webp'
37
    ];
38

39
    public const ON_DUPLICATE_INCREMENT = 'increment';
40
    public const ON_DUPLICATE_ERROR = 'error';
41

42
    /** @var callable */
43
    private $callback;
44

45
    private ?string $outputFormat = null;
46

47
    private ?int $outputQuality = null;
48

49
    private array $outputOptions = [];
50

51
    private ?string $disk = null;
52

53
    private ?string $directory = null;
54

55
    private ?string $filename = null;
56

57
    private ?string $hashFilenameAlgo = null;
58

59
    private string $onDuplicateBehaviour = self::ON_DUPLICATE_INCREMENT;
60

61
    /** @var string|null */
62
    private ?string $visibility = null;
63

64
    /** @var callable|null */
65
    private $beforeSave;
66

67
    private bool $shouldOptimize;
68

69
    /** @var array<class-string<Optimizer>,string[]> */
70
    private array $optimizers;
71

72
    public function __construct(callable $callback)
73
    {
74
        $this->callback = $callback;
228✔
75
        $this->shouldOptimize = config('mediable.image_optimization.enabled', true);
228✔
76
        $this->setOptimizers(config('mediable.image_optimization.optimizers', []));
228✔
77
    }
78

79
    public static function make(callable $callback): self
80
    {
81
        return new self($callback);
156✔
82
    }
83

84
    /**
85
     * @return callable
86
     */
87
    public function getCallback(): callable
88
    {
89
        return $this->callback;
144✔
90
    }
91

92
    /**
93
     * @return int
94
     * @deprecated use getOutputOptions() instead and check for the 'quality' key. Output quality is not supported by all encoders and may be ignored depending on the output format and encoders used.
95
     */
96
    public function getOutputQuality(): ?int
97
    {
98
        return $this->outputQuality;
6✔
99
    }
100

101
    /**
102
     * @param int $outputQuality
103
     * @return $this
104
     * @deprecated use setOutputOptions() instead with the appropriate options for the encoder you are using. Output quality is not supported by all encoders and may be ignored depending on the output format and encoders used.
105
     */
106
    public function setOutputQuality(int $outputQuality): self
107
    {
108
        $this->outputQuality = min(100, max(0, $outputQuality));
24✔
109

110
        return $this;
24✔
111
    }
112

113
    /**
114
     * @return string|null
115
     */
116
    public function getOutputFormat(): ?string
117
    {
118
        return $this->outputFormat;
150✔
119
    }
120

121
    /**
122
     * Get the options to be passed to the encoder when saving the image.
123
     * The options may vary depending on the output format and encoders used.
124
     *
125
     * @return array
126
     */
127
    public function getOutputOptions(): array
128
    {
129
        $options = $this->outputOptions;
138✔
130
        if ($this->outputQuality !== null) {
138✔
131
            $options['quality'] = $this->outputQuality;
18✔
132
        }
133
        return $options;
138✔
134
    }
135

136
    /**
137
     * @param string|null $outputFormat
138
     * @return $this
139
     */
140
    public function setOutputFormat(?string $outputFormat): self
141
    {
142
        $this->outputFormat = $outputFormat;
36✔
143

144
        return $this;
36✔
145
    }
146

147
    /**
148
     * @return $this
149
     */
150
    public function outputJpegFormat(): self
151
    {
152
        $this->setOutputFormat(self::FORMAT_JPG);
12✔
153

154
        return $this;
12✔
155
    }
156

157
    /**
158
     * @return $this
159
     */
160
    public function outputPngFormat(): self
161
    {
162
        $this->setOutputFormat(self::FORMAT_PNG);
12✔
163

164
        return $this;
12✔
165
    }
166

167
    /**
168
     * @return $this
169
     */
170
    public function outputGifFormat(): self
171
    {
172
        $this->setOutputFormat(self::FORMAT_GIF);
6✔
173

174
        return $this;
6✔
175
    }
176

177
    /**
178
     * @return $this
179
     */
180
    public function outputTiffFormat(): self
181
    {
182
        $this->setOutputFormat(self::FORMAT_TIFF);
6✔
183

184
        return $this;
6✔
185
    }
186

187
    /**
188
     * @return $this
189
     */
190
    public function outputBmpFormat(): self
191
    {
192
        $this->setOutputFormat(self::FORMAT_BMP);
6✔
193

194
        return $this;
6✔
195
    }
196

197
    /**
198
     * @return $this
199
     */
200
    public function outputWebpFormat(): self
201
    {
202
        $this->setOutputFormat(self::FORMAT_WEBP);
6✔
203

204
        return $this;
6✔
205
    }
206

207
    public function outputHeicFormat(): self
208
    {
209
        $this->setOutputFormat(self::FORMAT_HEIC);
6✔
210

211
        return $this;
6✔
212
    }
213

214
    /**
215
     * @param array $options The options to be passed to the encoder when saving the image. The options may vary depending on the output format and encoders used.
216
     * @return $this
217
     */
218
    public function setOutputOptions(array $options): self
219
    {
NEW
220
        $this->outputOptions = $options;
×
NEW
221
        return $this;
×
222
    }
223

224
    /**
225
     * @return callable
226
     */
227
    public function getBeforeSave(): ?callable
228
    {
229
        return $this->beforeSave;
132✔
230
    }
231

232
    /**
233
     * Set the filesystem disk and relative directory where the file will be saved.
234
     *
235
     * @param  string $disk
236
     * @param  string $directory
237
     *
238
     * @return $this
239
     */
240
    public function toDestination(string $disk, string $directory): self
241
    {
242
        return $this->toDisk($disk)->toDirectory($directory);
18✔
243
    }
244

245
    /**
246
     * Set the filesystem disk on which the file will be saved.
247
     *
248
     * @param string $disk
249
     *
250
     * @return $this
251
     */
252
    public function toDisk(string $disk): self
253
    {
254
        if (!array_key_exists($disk, config('filesystems.disks', []))) {
72✔
255
            throw ConfigurationException::diskNotFound($disk);
×
256
        }
257
        $this->disk = $disk;
72✔
258

259
        return $this;
72✔
260
    }
261

262
    /**
263
     * @return string|null
264
     */
265
    public function getDisk(): ?string
266
    {
267
        return $this->disk;
138✔
268
    }
269

270
    /**
271
     * Set the directory relative to the filesystem disk at which the file will be saved.
272
     * @param string $directory
273
     * @return $this
274
     */
275
    public function toDirectory(string $directory): self
276
    {
277
        $this->directory = File::sanitizePath($directory);
24✔
278

279
        return $this;
24✔
280
    }
281

282
    /**
283
     * @return string|null
284
     */
285
    public function getDirectory(): ?string
286
    {
287
        return $this->directory;
138✔
288
    }
289

290
    /**
291
     * Specify the filename to copy to the file to.
292
     * @param string $filename
293
     * @return $this
294
     */
295
    public function useFilename(string $filename): self
296
    {
297
        $this->filename = File::sanitizeFilename($filename);
12✔
298
        $this->hashFilenameAlgo = null;
12✔
299

300
        return $this;
12✔
301
    }
302

303
    /**
304
     * Indicates to the uploader to generate a filename using the file's MD5 hash.
305
     * @return $this
306
     */
307
    public function useHashForFilename(string $algo = 'md5'): self
308
    {
309
        $this->hashFilenameAlgo = $algo;
12✔
310
        $this->filename = null;
12✔
311

312
        return $this;
12✔
313
    }
314

315
    /**
316
     * Restore the default behaviour of using the source file's filename.
317
     * @return $this
318
     */
319
    public function useOriginalFilename(): self
320
    {
321
        $this->filename = null;
6✔
322
        $this->hashFilenameAlgo = null;
6✔
323

324
        return $this;
6✔
325
    }
326

327
    public function getFilename(): ?string
328
    {
329
        return $this->filename;
138✔
330
    }
331

332
    public function isUsingHashForFilename(): bool
333
    {
334
        return $this->hashFilenameAlgo !== null;
132✔
335
    }
336

337
    public function getHashFilenameAlgo(): ?string
338
    {
339
        return $this->hashFilenameAlgo;
12✔
340
    }
341

342
    /**
343
     * @return $this
344
     */
345
    public function onDuplicateIncrement(): self
346
    {
347
        $this->onDuplicateBehaviour = self::ON_DUPLICATE_INCREMENT;
18✔
348
        return $this;
18✔
349
    }
350

351
    /**
352
     * @return $this
353
     */
354
    public function onDuplicateError(): self
355
    {
356
        $this->onDuplicateBehaviour = self::ON_DUPLICATE_ERROR;
18✔
357
        return $this;
18✔
358
    }
359

360
    /**
361
     * @return string
362
     */
363
    public function getOnDuplicateBehaviour(): string
364
    {
365
        return $this->onDuplicateBehaviour;
30✔
366
    }
367

368
    public function makePrivate(): self
369
    {
370
        $this->visibility = 'private';
6✔
371
        return $this;
6✔
372
    }
373

374
    public function makePublic(): self
375
    {
376
        $this->visibility = 'public';
6✔
377
        return $this;
6✔
378
    }
379

380
    public function matchOriginalVisibility(): self
381
    {
382
        $this->visibility = 'match';
6✔
383
        return $this;
6✔
384
    }
385

386
    public function setVisibility(?string $visibility): self
387
    {
388
        $this->visibility = $visibility;
54✔
389
        return $this;
54✔
390
    }
391

392
    public function getVisibility(): ?string
393
    {
394
        return $this->visibility;
126✔
395
    }
396

397
    /**
398
     * @param callable $beforeSave
399
     * @return $this
400
     */
401
    public function beforeSave(callable $beforeSave): self
402
    {
403
        $this->beforeSave = $beforeSave;
24✔
404

405
        return $this;
24✔
406
    }
407

408
    /**
409
     * Disable image optimization.
410
     * @return $this
411
     */
412
    public function noOptimization(): self
413
    {
414
        $this->shouldOptimize = false;
12✔
415

416
        return $this;
12✔
417
    }
418

419
    /**
420
     * Enable image optimization.
421
     * @param array<class-string<Optimizer>,string[]> $customOptimizers Override default optimizers.
422
     *     The array keys should be the fully qualified class names of the optimizers to use.
423
     *     The array values should be arrays of command line arguments to pass to the optimizer.
424
     *     DO NOT PASS UNTRUSTED USER INPUT AS COMMAND LINE ARGUMENTS
425
     * @return $this
426
     * @throws ConfigurationException
427
     */
428
    public function optimize(?array $customOptimizers = null): self
429
    {
430
        if ($customOptimizers !== null) {
12✔
431
            $this->setOptimizers($customOptimizers);
12✔
432
        }
433
        $this->shouldOptimize = true;
12✔
434

435
        return $this;
12✔
436
    }
437

438
    public function shouldOptimize(): bool
439
    {
440
        return $this->shouldOptimize && !empty($this->optimizers);
144✔
441
    }
442

443
    public function getOptimizerChain(): OptimizerChain
444
    {
445
        $chain = new OptimizerChain();
12✔
446
        foreach ($this->optimizers as $optimizerClass => $args) {
12✔
447
            $optimizer = new $optimizerClass($args);
12✔
448
            $chain->addOptimizer($optimizer);
12✔
449
        }
450
        return $chain;
12✔
451
    }
452

453
    private function setOptimizers(array $customOptimizers): void
454
    {
455
        foreach ($customOptimizers as $optimizerClass => $args) {
228✔
456
            if (!is_a($optimizerClass, Optimizer::class, true)) {
228✔
457
                throw ConfigurationException::invalidOptimizer($optimizerClass);
×
458
            }
459
        }
460
        $this->optimizers = $customOptimizers;
228✔
461
    }
462
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc