Issues (177)

src/Storage/Image.php (7 issues)

1
<?php
2
3
namespace Mostafaznv\Larupload\Storage;
4
5
use ColorThief\ColorThief;
6
use Exception;
7
use Illuminate\Http\UploadedFile;
8
use Intervention\Image\Interfaces\ImageInterface;
9
use Intervention\Image\ImageManager;
10
use JetBrains\PhpStorm\ArrayShape;
11
use Mostafaznv\Larupload\DTOs\Style\ImageStyle;
12
use Mostafaznv\Larupload\Enums\LaruploadImageLibrary;
13
use Mostafaznv\Larupload\Enums\LaruploadMediaStyle;
14
use Symfony\Component\HttpFoundation\File\File;
15
use Illuminate\Support\Facades\Storage;
16
17
18
class Image
19
{
20
    protected readonly UploadedFile $file;
21
22
    protected readonly ImageInterface $image;
23
24
    protected readonly string $disk;
25
26
    protected readonly bool $driverIsLocal;
27
28
    protected readonly int $dominantColorQuality;
29
30
31
    public function __construct(UploadedFile $file, string $disk, LaruploadImageLibrary $library, int $dominantColorQuality = 10)
32
    {
33
        $this->file = $file;
0 ignored issues
show
The property file is declared read-only in Mostafaznv\Larupload\Storage\Image.
Loading history...
34
        $this->disk = $disk;
0 ignored issues
show
The property disk is declared read-only in Mostafaznv\Larupload\Storage\Image.
Loading history...
35
        $this->driverIsLocal = disk_driver_is_local($this->disk);
0 ignored issues
show
The property driverIsLocal is declared read-only in Mostafaznv\Larupload\Storage\Image.
Loading history...
36
        $this->dominantColorQuality = $dominantColorQuality;
0 ignored issues
show
The property dominantColorQuality is declared read-only in Mostafaznv\Larupload\Storage\Image.
Loading history...
37
38
        $path = $file->getRealPath();
39
40
        $imageManager = $library === LaruploadImageLibrary::GD
41
            ? ImageManager::gd()
42
            : ImageManager::imagick();
43
44
45
        $this->image = $imageManager->read($path);
0 ignored issues
show
The property image is declared read-only in Mostafaznv\Larupload\Storage\Image.
Loading history...
46
    }
47
48
49
    #[ArrayShape(['width' => 'int', 'height' => 'int'])]
50
    public function getMeta(): array
51
    {
52
        return [
53
            'width'  => $this->image->width(),
54
            'height' => $this->image->height(),
55
        ];
56
    }
57
58
    public function resize(string $saveTo, ImageStyle $style): bool
59
    {
60
        $saveTo = Storage::disk($this->disk)->path($saveTo);
61
62
        if ($style->mode === LaruploadMediaStyle::SCALE_HEIGHT and $style->width) {
63
            $this->resizeLandscape($style->width);
64
        }
65
        else if ($style->mode == LaruploadMediaStyle::SCALE_WIDTH and $style->height) {
66
            $this->resizePortrait($style->height);
67
        }
68
        else if ($style->mode == LaruploadMediaStyle::CROP and $style->height and $style->width) {
69
            $this->resizeCrop($style->width, $style->height);
70
        }
71
        else if ($style->mode == LaruploadMediaStyle::FIT and $style->height and $style->width) {
72
            $this->resizeExact($style->width, $style->height);
73
        }
74
        else {
75
            $this->resizeAuto($style->width, $style->height);
76
        }
77
78
79
        if ($this->driverIsLocal) {
80
            $this->save($saveTo);
81
        }
82
        else {
83
            list($path, $name) = split_larupload_path($saveTo);
84
85
            $tempDir = larupload_temp_dir();
86
            $tempName = time() . '-' . $name;
0 ignored issues
show
Are you sure $name 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

86
            $tempName = time() . '-' . /** @scrutinizer ignore-type */ $name;
Loading history...
87
            $temp = "$tempDir/$tempName";
88
89
            $this->save($temp);
90
91
            Storage::disk($this->disk)->putFileAs($path, new File($temp), $name);
92
93
            @unlink($temp);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

93
            /** @scrutinizer ignore-unhandled */ @unlink($temp);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
94
        }
95
96
        return true;
97
    }
98
99
    /**
100
     * Retrieve dominant color from image file.
101
     */
102
    public function getDominantColor($file = null): ?string
103
    {
104
        if (is_null($file)) {
105
            $file = $this->file;
106
        }
107
108
        try {
109
            $path = null;
110
111
            if ($file instanceof UploadedFile) {
112
                $path = $file->getRealPath();
113
            }
114
            else if (file_exists($file)) {
115
                $path = $file;
116
            }
117
118
            if ($path) {
119
                $color = ColorThief::getColor(
120
                    sourceImage: $path,
121
                    quality: $this->dominantColorQuality,
122
                    outputFormat: 'hex'
123
                );
124
125
                if ($color) {
126
                    return $color;
127
                }
128
            }
129
        }
130
        // @codeCoverageIgnoreStart
131
        catch (Exception) {
132
            // do nothing
133
        }
134
        // @codeCoverageIgnoreEnd
135
136
        return null;
137
    }
138
139
140
    /**
141
     * Resize an image as closely as possible to a given
142
     * width and height while still maintaining aspect ratio.
143
     *
144
     * This method is really just a proxy to other resize methods:
145
     * — If the current image is wider, we'll resize landscape.
146
     * — If the current image is taller, we'll resize portrait.
147
     * — If the image is as tall as it is wide (it's a square) then we'll
148
     *   apply the same process using the new dimensions (we'll resize exact if
149
     *   the new dimensions are both equal since at this point we'll have a square
150
     *   image being resized to a square).
151
     */
152
    private function resizeAuto(int $width = null, int $height = null): void
153
    {
154
        $originalWidth = $this->image->width();
155
        $originalHeight = $this->image->height();
156
157
        if ($width === null) {
158
            $width = $originalWidth;
159
        }
160
161
        if ($height === null) {
162
            $height = $originalHeight;
163
        }
164
165
        if ($originalHeight < $originalWidth) {
166
            $this->resizeLandscape($width);
167
            return;
168
        }
169
170
        if ($originalHeight > $originalWidth) {
171
            $this->resizePortrait($height);
172
            return;
173
        }
174
175
        if ($height < $width) {
176
            $this->resizeLandscape($width);
177
            return;
178
        }
179
180
        if ($height > $width) {
181
            $this->resizePortrait($height);
182
            return;
183
        }
184
185
        $this->resizeExact($width, $height);
186
    }
187
188
    /**
189
     * Resize an image and then center crop it
190
     */
191
    private function resizeCrop(int $width, int $height): void
192
    {
193
        $this->image->cover($width, $height);
194
    }
195
196
    /**
197
     * Landscape (width fixed)
198
     * width given, height automatically selected to preserve aspect ratio
199
     */
200
    private function resizeLandscape(int $width): void
201
    {
202
        $this->image->scale(
203
            width: $width
204
        );
205
    }
206
207
    /**
208
     * Portrait (height fixed)
209
     * height given, width automatically selected to preserve aspect ratio
210
     */
211
    private function resizePortrait(int $height): void
212
    {
213
        $this->image->scale(
214
            height: $height
215
        );
216
    }
217
218
    /**
219
     * Resize an image to an exact width and height.
220
     * does not preserve aspect ratio.
221
     */
222
    private function resizeExact(int $width, int $height): void
223
    {
224
        $this->image->resize($width, $height);
225
    }
226
227
    /**
228
     * Save image file
229
     *
230
     * @param string $path
231
     * @return void
232
     */
233
    private function save(string $path): void
234
    {
235
        $isSvg = $this->file->getExtension() === 'svg' || $this->file->getClientOriginalExtension() === 'svg';
236
237
        $isSvg
238
            ? $this->image->toPng()->save($path)
239
            : $this->image->encode()->save($path);
240
    }
241
}
242