Passed
Push — master ( b6337b...c70fa1 )
by Yaro
14:32
created

Image::getEditFormView()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 9
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 9
loc 9
ccs 0
cts 8
cp 0
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 6
1
<?php
2
3
namespace Yaro\Jarboe\Table\Fields;
4
5
use Illuminate\Http\Request;
6
use Illuminate\Http\UploadedFile;
7
use Illuminate\Support\Facades\Storage as IlluminateStorage;
8
use Illuminate\Support\Str;
9
use Intervention\Image\ImageManagerStatic as InterventionImage;
10
use Yaro\Jarboe\Table\Fields\Traits\Filename;
11
use Yaro\Jarboe\Table\Fields\Traits\Nullable;
12
use Yaro\Jarboe\Table\Fields\Traits\Placeholder;
13
use Yaro\Jarboe\Table\Fields\Traits\Storage;
14
15
class Image extends AbstractField
16
{
17
    use Storage;
18
    use Placeholder;
19
    use Nullable;
20
    use Filename;
21
22
    protected $encode = false;
23
    protected $crop = false;
24
    protected $shouldAutoOpen = null;
25
    protected $ratio = [
26
        'width'  => false,
27
        'height' => false,
28
    ];
29
    private $originalImageData;
30
    private $defaultImageDataStructure =  [
31
        'storage' => [
32
            'disk' => null,
33
            'is_encoded' => false,
34
        ],
35
        'crop' => [
36
            'width' => null,
37
            'height' => null,
38
            'x' => null,
39
            'y' => null,
40
            'rotate' => null,
41
            'rotate_background' => null,
42
        ],
43
        'sources' => [
44
            'original' => null,
45
            'cropped' => null,
46
        ],
47
    ];
48
49
    /**
50
     * @var int
51
     */
52
    private $quality = 100;
53
54
    public function __construct()
55
    {
56
        $this->disk = config('filesystems.default');
57
    }
58
59
    public function isEncode()
60
    {
61
        return $this->encode;
62
    }
63
64
    /**
65
     * Encode image as data-url.
66
     *
67
     * @param bool $encode
68
     * @return $this
69
     */
70
    public function encode(bool $encode = true)
71
    {
72
        $this->encode = $encode;
73
74
        return $this;
75
    }
76
77
    protected function storeFile($filepath, $filename, $width = null, $height = null, $x = null, $y = null, $rotate = null, $rotateBackgroundColor = null)
78
    {
79
        $image = InterventionImage::make($filepath);
80
        $rotateBackgroundColor = $rotateBackgroundColor ?: 'rgba(255, 255, 255, 0)';
81
82
        if ($rotate) {
83
            // because js plugin and php library rotating in different directions.
84
            $angle = $rotate * -1;
85
            $image->rotate($angle, $rotateBackgroundColor);
86
        }
87
        $hasCropProperties = !is_null($width) && !is_null($height) && !is_null($x) && !is_null($y);
88
        if ($this->isCrop() && $hasCropProperties) {
89
            $image->crop(round($width), round($height), round($x), round($y));
90
        }
91
92
        if ($this->isEncode()) {
93
            return (string) $image->encode('data-url', $this->getQuality());
94
        }
95
96
        $format = '';
97
        if ($this->isTransparentColor($rotateBackgroundColor)) {
98
            $format = 'png';
99
        }
100
        $path = trim($this->getPath() .'/'. $filename, '/');
101
        IlluminateStorage::disk($this->getDisk())->put(
102
            $path,
103
            (string) $image->encode($format, $this->getQuality())
104
        );
105
106
        return $path;
107
    }
108
109
    public function value(Request $request)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
110
    {
111
        $images = $request->all($this->name());
112
        $images = $images[$this->name()];
113
114
        $data = [];
115
        foreach ($images as $image) {
116
            $imageData = $this->defaultImageDataStructure;
117
            $imageData['storage'] = [
118
                'disk' => $this->getDisk(),
119
                'is_encoded' => $this->isEncode(),
120
            ];
121
122
            $imageData['crop'] = $image['crop'];
123
            $imageData['sources'] = $image['sources'];
124
            /** @var UploadedFile|null $file */
125
            $file = $image['file'] ?? null;
126
            if ($file) {
127
                $imageData['sources']['original'] = $this->storeFile(
128
                    $file->getRealPath(),
129
                    $this->generateFilename(
130
                        $request,
131
                        $file,
132
                        $image,
133
                        $this->isTransparentColor((string) $image['crop']['rotate_background']),
134
                        true
135
                    )
136
                );
137
                $imageData['sources']['cropped'] = $this->storeFile(
138
                    $file->getRealPath(),
139
                    $this->generateFilename(
140
                        $request,
141
                        $file,
142
                        $image,
143
                        $this->isTransparentColor((string) $image['crop']['rotate_background']),
144
                        false
145
                    ),
146
                    $image['crop']['width'],
147
                    $image['crop']['height'],
148
                    $image['crop']['x'],
149
                    $image['crop']['y'],
150
                    $image['crop']['rotate'],
151
                    $image['crop']['rotate_background']
152
                );
153
            } elseif (!$image['sources']['original']) {
154
                continue;
155
            } elseif ($this->isCrop() && !$image['sources']['cropped']) {
156
                $imageData['sources']['cropped'] = $this->storeFile(
157
                    IlluminateStorage::disk($this->getDisk())->path($imageData['sources']['original']),
158
                    $this->generateFilename(
159
                        $request,
160
                        null,
161
                        $image,
162
                        $this->isTransparentColor((string) $image['crop']['rotate_background']),
163
                        false
164
                    ),
165
                    $image['crop']['width'],
166
                    $image['crop']['height'],
167
                    $image['crop']['x'],
168
                    $image['crop']['y'],
169
                    $image['crop']['rotate'],
170
                    $image['crop']['rotate_background']
171
                );
172
            }
173
174
            $data[] = $imageData;
175
        }
176
177
        if (!$this->isMultiple()) {
178
            $data = array_pop($data);
179
        }
180
181
        $data = $data ?: [];
182
        if ($this->isNullable()) {
183
            $data = $data ?: null;
184
        }
185
186
        return $data;
187
    }
188
189
    public function crop(bool $enabled = true)
190
    {
191
        $this->crop = $enabled;
192
193
        return $this;
194
    }
195
196
    public function ratio(int $width, int $height)
197
    {
198
        $this->ratio['width'] = $width;
199
        $this->ratio['height'] = $height;
200
201
        return $this;
202
    }
203
204
    public function isCrop()
205
    {
206
        return $this->crop;
207
    }
208
209
    public function getRatio(string $type)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
210
    {
211
        $type = strtolower($type);
212
213
        switch ($type) {
214
            case 'width':
215
            case 'height':
216
                return $this->ratio[$type];
217
            default:
218
                return false;
219
        }
220
    }
221
222
    public function getListView($model)
223
    {
224
        return view('jarboe::crud.fields.image.list', [
225
            'model' => $model,
226
            'field' => $this,
227
        ]);
228
    }
229
230 View Code Duplication
    public function getEditFormView($model)
231
    {
232
        $template = $this->isReadonly() ? 'readonly' : 'edit';
233
234
        return view('jarboe::crud.fields.image.'. $template, [
235
            'model' => $model,
236
            'field' => $this,
237
        ]);
238
    }
239
240
    public function getCreateFormView()
241
    {
242
        return view('jarboe::crud.fields.image.create', [
243
            'model' => null,
244
            'field' => $this,
245
        ]);
246
    }
247
248
    public function getImage($data = []): \Yaro\Jarboe\Pack\Image
249
    {
250
        return new \Yaro\Jarboe\Pack\Image($data);
251
    }
252
253
    private function isTransparentColor(string $rgbaColor)
254
    {
255
        if (!$rgbaColor) {
256
            return false;
257
        }
258
259
        $segmentsString = preg_replace('~rgba\(|\)~', '', $rgbaColor);
260
        $segments = explode(',', $segmentsString);
261
262
        $opacity = $segments[3] ?? 0;
263
264
        return $opacity < 1;
265
    }
266
267
    /**
268
     * @param Request $request
269
     * @param UploadedFile|null $file
270
     * @param array $imageData
271
     * @param bool $hasTransparentColor
272
     * @param bool $isOriginalImage
273
     * @return string
274
     */
275
    private function generateFilename(Request $request, UploadedFile $file = null, array $imageData, bool $hasTransparentColor, bool $isOriginalImage): string
276
    {
277
        $isRecropFromOriginal = !$file && !$isOriginalImage;
278
        $extension = $isRecropFromOriginal ? pathinfo($imageData['sources']['original'], PATHINFO_EXTENSION) : $file->extension();
0 ignored issues
show
Bug introduced by
It seems like $file is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
279
        $filename = Str::random(40) .'.'. $extension;
280
281
        $closure = $this->filenameClosure;
282
        if (is_callable($closure)) {
283
            $filename = $closure($file, $request, $imageData, $isOriginalImage);
284
        }
285
286
        if ($hasTransparentColor) {
287
            $regexp = '~'. preg_quote(pathinfo($filename, PATHINFO_EXTENSION)) .'$~';
288
            $filename = preg_replace($regexp, 'png', $filename);
289
        }
290
291
        return (string) $filename;
292
    }
293
294
    public function getImagesPack($model): array
295
    {
296
        $defaultImage = $this->getImage();
297
        if (!$model) {
298
            return [$defaultImage];
299
        }
300
301
        $imagesData = $this->getAttribute($model);
302
        if (!is_array($imagesData)) {
303
            $imagesData = [];
304
        }
305
306
        if (!$this->isMultiple()) {
307
            $imagesData = [$imagesData];
308
        }
309
310
311
        $pack = [];
312
        foreach ($imagesData as $imageData) {
313
            $pack[] = $this->getImage($imageData);
314
        }
315
316
        return $pack ?: [$defaultImage];
317
    }
318
319
    public function autoOpen(bool $shouldAutoOpen = true)
320
    {
321
        $this->shouldAutoOpen = $shouldAutoOpen;
322
323
        return $this;
324
    }
325
326
    public function shouldAutoOpenModal(): bool
327
    {
328
        if (!is_null($this->shouldAutoOpen)) {
329
            return $this->shouldAutoOpen;
330
        }
331
332
        return $this->isCrop();
333
    }
334
335
    public function quality(int $quality)
336
    {
337
        if ($quality < 0) {
338
            $quality = 0;
339
        }
340
        if ($quality > 100) {
341
            $quality = 100;
342
        }
343
344
        $this->quality = $quality;
345
346
        return $this;
347
    }
348
349
    /**
350
     * @return int
351
     */
352
    public function getQuality(): int
353
    {
354
        return $this->quality;
355
    }
356
}
357