Test Failed
Branch master (df1c42)
by Mostafa
15:23
created

Image::__construct()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 8
c 1
b 0
f 0
nc 1
nop 4
dl 0
loc 14
rs 10
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\Image as InterventionImage;
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 InterventionImage $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
Bug introduced by
The property file is declared read-only in Mostafaznv\Larupload\Storage\Image.
Loading history...
34
        $this->disk = $disk;
0 ignored issues
show
Bug introduced by
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
Bug introduced by
The property driverIsLocal is declared read-only in Mostafaznv\Larupload\Storage\Image.
Loading history...
36
        $this->dominantColorQuality = $dominantColorQuality;
0 ignored issues
show
Bug introduced by
The property dominantColorQuality is declared read-only in Mostafaznv\Larupload\Storage\Image.
Loading history...
37
38
        $path = $file->getRealPath();
39
40
        $imageManager = new ImageManager([
41
            'driver' => $library === LaruploadImageLibrary::GD ? 'gd' : 'imagick',
42
        ]);
43
44
        $this->image = $imageManager->make($path);
0 ignored issues
show
Bug introduced by
The property image is declared read-only in Mostafaznv\Larupload\Storage\Image.
Loading history...
45
    }
46
47
48
    #[ArrayShape(['width' => 'int', 'height' => 'int'])]
49
    public function getMeta(): array
50
    {
51
        return [
52
            'width'  => $this->image->width(),
53
            'height' => $this->image->height(),
54
        ];
55
    }
56
57
    public function resize(string $saveTo, ImageStyle $style): bool
58
    {
59
        $saveTo = Storage::disk($this->disk)->path($saveTo);
60
61
        if ($style->mode === LaruploadMediaStyle::SCALE_HEIGHT and $style->width) {
62
            $this->resizeLandscape($style->width);
63
        }
64
        else if ($style->mode == LaruploadMediaStyle::SCALE_WIDTH and $style->height) {
65
            $this->resizePortrait($style->height);
66
        }
67
        else if ($style->mode == LaruploadMediaStyle::CROP and $style->height and $style->width) {
68
            $this->resizeCrop($style->width, $style->height);
69
        }
70
        else if ($style->mode == LaruploadMediaStyle::FIT and $style->height and $style->width) {
71
            $this->resizeExact($style->width, $style->height);
72
        }
73
        else {
74
            $this->resizeAuto($style->width, $style->height);
75
        }
76
77
78
        if ($this->driverIsLocal) {
79
            $this->image->save($saveTo);
80
        }
81
        else {
82
            list($path, $name) = split_larupload_path($saveTo);
83
84
            $tempDir = larupload_temp_dir();
85
            $tempName = time() . '-' . $name;
0 ignored issues
show
Bug introduced by
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

85
            $tempName = time() . '-' . /** @scrutinizer ignore-type */ $name;
Loading history...
86
            $temp = "$tempDir/$tempName";
87
88
            $this->image->save($temp);
89
90
            Storage::disk($this->disk)->putFileAs($path, new File($temp), $name);
91
92
            @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

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