Test Failed
Push — develop ( 881649...f3b581 )
by nguereza
15:15
created

FileHelper::existPublicImage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 3
dl 0
loc 10
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Platine Framework
5
 *
6
 * Platine Framework is a lightweight, high-performance, simple and elegant
7
 * PHP Web framework
8
 *
9
 * This content is released under the MIT License (MIT)
10
 *
11
 * Copyright (c) 2020 Platine Framework
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
declare(strict_types=1);
33
34
namespace Platine\Framework\Helper;
35
36
use Platine\Config\Config;
37
use Platine\Filesystem\Filesystem;
38
use Platine\Http\UploadedFile;
39
use Platine\Lang\Lang;
40
use Platine\Upload\Exception\UploadException;
41
use Platine\Upload\File\UploadFileInfo;
42
use Platine\Upload\Storage\FileSystem as UploadFileSystem;
43
use Platine\Upload\Upload;
44
use Platine\Upload\Validator\Rule\Extension;
45
use Platine\Upload\Validator\Rule\MimeType;
46
use Platine\Upload\Validator\Rule\Required;
47
use Platine\Upload\Validator\Rule\Size;
48
use Platine\Upload\Validator\RuleInterface;
49
50
/**
51
 * @class FileHelper
52
 * @package Platine\Framework\Helper
53
 * @template T
54
 */
55
class FileHelper
56
{
57
    /**
58
     *
59
     * @param Config<T> $config
60
     * @param Filesystem $filesystem
61
     * @param Lang $lang
62
     */
63
    public function __construct(
64
        protected Config $config,
65
        protected Filesystem $filesystem,
66
        protected Lang $lang,
67
    ) {
68
    }
69
70
    /**
71
     * Whether the file is uploaded
72
     * @param string $name
73
     * @return bool
74
     */
75
    public function isUploaded(string $name): bool
76
    {
77
        $uploadedFiles = UploadedFile::createFromGlobals();
78
        $uploadFile = $uploadedFiles[$name] ?? null;
79
80
        return $uploadFile instanceof UploadedFile
81
                && $uploadFile->getClientMediaType() !== null;
82
    }
83
84
    /**
85
     * Delete the given uploaded file in some situation, like after upload error
86
     * or can not save the information in database, etc.
87
     * @param UploadFileInfo $info
88
     * @return void
89
     */
90
    public function deleteFile(UploadFileInfo $info): void
91
    {
92
        $file = $this->filesystem->file($info->getPath());
93
        if ($file->exists()) {
94
            $file->delete();
95
        }
96
    }
97
98
    /**
99
     * Whether the given file exist
100
     * @param string $filename
101
     * @param string|null $folder
102
     * @param bool $useRoot
103
     * @return bool
104
     */
105
    public function exists(string $filename, ?string $folder = null, bool $useRoot = true): bool
106
    {
107
        $configPath = $this->getRootPath(
108
            $this->config->get('platform.data_attachment_path'),
109
            $useRoot
110
        );
111
112
        $path = $configPath;
113
        if ($folder !== null) {
114
            $path .= DIRECTORY_SEPARATOR . $folder;
115
        }
116
117
        $filepath = sprintf('%s/%s', $path, $filename);
118
        $handle = $this->filesystem->get($filepath);
119
120
        return $handle !== null && $handle->exists();
121
    }
122
123
    /**
124
     * Whether the given image file exist
125
     * @param string $filename
126
     * @param string|null $folder
127
     * @param bool $useRoot
128
     * @return bool
129
     */
130
    public function existImage(
131
        string $filename,
132
        ?string $folder = null,
133
        bool $useRoot = true
134
    ): bool {
135
        return $this->isImageExists(
136
            $filename,
137
            'platform.data_image_path',
138
            $folder,
139
            $useRoot
140
        );
141
    }
142
143
    /**
144
     * Whether the given public image file exist
145
     * @param string $filename
146
     * @param string|null $folder
147
     * @param bool $useRoot
148
     * @return bool
149
     */
150
    public function existPublicImage(
151
        string $filename,
152
        ?string $folder = null,
153
        bool $useRoot = true
154
    ): bool {
155
        return $this->isImageExists(
156
            $filename,
157
            'platform.public_image_path',
158
            $folder,
159
            $useRoot
160
        );
161
    }
162
163
    /**
164
     * Delete the given file
165
     * @param string $filename
166
     * @param string|null $folder
167
     * @param bool $useRoot
168
     * @return bool
169
     */
170
    public function delete(
171
        string $filename,
172
        ?string $folder = null,
173
        bool $useRoot = true
174
    ): bool {
175
        $configPath = $this->getRootPath(
176
            $this->config->get('platform.data_attachment_path'),
177
            $useRoot
178
        );
179
        $path = $configPath;
180
        if ($folder !== null) {
181
            $path .= DIRECTORY_SEPARATOR . $folder;
182
        }
183
184
        $filepath = sprintf('%s/%s', $path, $filename);
185
        $handle = $this->filesystem->get($filepath);
186
        if ($handle === null) {
187
            return true; //no need to delete if file does not exist
188
        }
189
190
        $handle->delete();
191
192
        return true;
193
    }
194
195
    /**
196
     * Delete the given upload public image file
197
     * @param string $filename
198
     * @param string|null $folder
199
     * @param bool $useRoot
200
     * @return bool
201
     */
202
    public function deletePublicImage(
203
        string $filename,
204
        ?string $folder = null,
205
        bool $useRoot = true
206
    ): bool {
207
        return $this->handleDeleteImage(
208
            $filename,
209
            'platform.public_image_path',
210
            $folder,
211
            $useRoot
212
        );
213
    }
214
215
    /**
216
     * Delete the given upload image file
217
     * @param string $filename
218
     * @param string|null $folder
219
     * @param bool $useRoot
220
     * @return bool
221
     */
222
    public function deleteImage(
223
        string $filename,
224
        ?string $folder = null,
225
        bool $useRoot = true
226
    ): bool {
227
        return $this->handleDeleteImage(
228
            $filename,
229
            'platform.data_image_path',
230
            $folder,
231
            $useRoot
232
        );
233
    }
234
235
    /**
236
     * Upload an image
237
     * @param string $name
238
     * @param string|null $folder
239
     * @param bool $useRoot
240
     * @return UploadFileInfo
241
     */
242
    public function uploadImage(
243
        string $name,
244
        ?string $folder = null,
245
        bool $useRoot = true
246
    ): UploadFileInfo {
247
        return $this->doUploadImage(
248
            $name,
249
            'platform.data_image_path',
250
            $folder,
251
            $useRoot
252
        );
253
    }
254
255
    /**
256
     * Upload an public image
257
     * @param string $name
258
     * @param string|null $folder
259
     * @param bool $useRoot
260
     * @return UploadFileInfo
261
     */
262
    public function uploadPublicImage(
263
        string $name,
264
        ?string $folder = null,
265
        bool $useRoot = true
266
    ): UploadFileInfo {
267
        return $this->doUploadImage(
268
            $name,
269
            'platform.public_image_path',
270
            $folder,
271
            $useRoot
272
        );
273
    }
274
275
    /**
276
     * Upload an attachment
277
     * @param string $name
278
     * @param string|null $folder
279
     * @param string[] $mimes the allowed mime types to use on fly
280
     * @param string[] $extensions the allowed extensions to use on fly
281
     * @param bool $useRoot
282
     * @return UploadFileInfo
283
     */
284
    public function uploadAttachment(
285
        string $name,
286
        ?string $folder = null,
287
        array $mimes = [],
288
        array $extensions = [],
289
        bool $useRoot = true
290
    ): UploadFileInfo {
291
        $configPath = $this->getRootPath(
292
            $this->config->get('platform.data_attachment_path'),
293
            $useRoot
294
        );
295
        $path = $configPath;
296
297
        if ($folder !== null) {
298
            $directory = $this->filesystem->directory($configPath);
299
            $path .= DIRECTORY_SEPARATOR . $folder;
300
            // Create the folder if it does not exist
301
            if ($this->filesystem->directory($path)->exists() === false) {
302
                $directory->create($folder, 0775, true);
303
            }
304
        }
305
306
        $rules = [
307
            new Required(),
308
            new Size($this->getAttachmentMaxSize()),
309
        ];
310
311
        if (count($extensions) === 0) {
312
            $extensions = $this->getAttachmentExtensionRules();
313
        }
314
315
        if (count($extensions) === 0) {
316
            $extensions = [
317
                    'png',
318
                    'gif',
319
                    'jpg',
320
                    'jpeg',
321
                    'csv',
322
                    'txt',
323
                    'docx',
324
                    'doc',
325
                    'pdf',
326
                    'xls',
327
                    'xlsx',
328
                    'pptx',
329
                    'ppt',
330
                    'zip',
331
                    'json',
332
                ];
333
        }
334
        $rules[] = new Extension($extensions);
335
        if (count($mimes) === 0) {
336
            $mimes = $this->getAttachmentMimeRules();
337
        }
338
339
        if (count($mimes) > 0) {
340
            $rules[] = new MimeType($mimes);
341
        }
342
343
        return $this->doUpload(
344
            $name,
345
            $rules,
346
            $path,
347
        );
348
    }
349
350
    /**
351
     * Return the path with the root suffix
352
     * @param string $configPath
353
     * @param bool $useRoot whether to use root path
354
     * @return string
355
     */
356
    public function getRootPath(string $configPath, bool $useRoot = true): string
357
    {
358
        $rootId = '';
359
        if ($useRoot) {
360
            $rootId = $this->getRootPathId();
361
        }
362
363
        $path = sprintf(
364
            '%s%s%s',
365
            $configPath,
366
            DIRECTORY_SEPARATOR,
367
            $rootId
368
        );
369
370
        $directory = $this->filesystem->directory($configPath);
371
372
        // Create the folder if it does not exist
373
        if ($this->filesystem->directory($path)->exists() === false) {
374
            $directory->create($rootId, 0775, true);
375
376
            // if it's public path add index file to prevent directory listing
377
            $this->filesystem->directory($path)->createFile('index.html', 'Access denied');
378
        }
379
380
        return $path;
381
    }
382
383
    /**
384
     * Whether the given image file exist
385
     * @param string $filename
386
     * @param string $configPathKey
387
     * @param string|null $folder
388
     * @param bool $useRoot
389
     * @return bool
390
     */
391
    protected function isImageExists(
392
        string $filename,
393
        string $configPathKey,
394
        ?string $folder = null,
395
        bool $useRoot = true
396
    ): bool {
397
        $configPath = $this->getRootPath(
398
            $this->config->get($configPathKey),
399
            $useRoot
400
        );
401
402
        $path = $configPath;
403
        if ($folder !== null) {
404
            $path .= DIRECTORY_SEPARATOR . $folder;
405
        }
406
407
        $filepath = sprintf('%s/%s', $path, $filename);
408
        $handle = $this->filesystem->get($filepath);
409
410
        return $handle !== null && $handle->exists();
411
    }
412
413
    /**
414
     * Process the upload
415
     * @param string $name
416
     * @param RuleInterface[] $rules
417
     * @param string $path
418
     *
419
     * @return UploadFileInfo
420
     */
421
    protected function doUpload(
422
        string $name,
423
        array $rules,
424
        string $path,
425
    ): UploadFileInfo {
426
        $uploadedFiles = UploadedFile::createFromGlobals();
427
        /** @var UploadedFile $uploadFile */
428
        $uploadFile = $uploadedFiles[$name] ?? [];
429
430
        $upload = new Upload(
431
            $name,
432
            new UploadFileSystem(
433
                $path,
434
                true
435
            ),
436
            null,
437
            [$name => $uploadFile]
438
        );
439
440
        $upload->setFilename(md5(uniqid() . time()));
441
442
        $upload->addValidations($rules);
443
444
        if ($upload->isUploaded() === false) {
445
            throw new UploadException($this->lang->tr('The file to upload is empty'));
446
        }
447
448
        $isUploaded = $upload->process();
449
        if ($isUploaded === false) {
450
            $errors = $upload->getErrors();
451
            throw new UploadException($errors[0]);
452
        }
453
454
        /** @var UploadFileInfo $info */
455
        $info = $upload->getInfo();
456
457
        return $info;
458
    }
459
460
    /**
461
     * Upload an image
462
     * @param string $name
463
     * @param string $path
464
     * @param string|null $folder
465
     * @param bool $useRoot
466
     *
467
     * @return UploadFileInfo
468
     */
469
    protected function doUploadImage(
470
        string $name,
471
        string $path,
472
        ?string $folder = null,
473
        bool $useRoot = true
474
    ): UploadFileInfo {
475
        $configPath = $this->getRootPath($this->config->get($path), $useRoot);
476
477
        $imagePath = $configPath;
478
479
        if ($folder !== null) {
480
            $directory = $this->filesystem->directory($configPath);
481
            $imagePath .= DIRECTORY_SEPARATOR . $folder;
482
            // Create the folder if it does not exist
483
            if ($this->filesystem->directory($imagePath)->exists() === false) {
484
                $directory->create($folder, 0775, true);
485
            }
486
        }
487
488
        return $this->doUpload(
489
            $name,
490
            [
491
                new Size($this->getImageMaxSize()),
492
                new Extension(['png', 'gif', 'jpg', 'jpeg']),
493
                new MimeType([
494
                    'image/png',
495
                    'image/jpg',
496
                    'image/gif',
497
                    'image/jpeg',
498
                ])
499
            ],
500
            $imagePath
501
        );
502
    }
503
504
    /**
505
     * Delete the given upload image file
506
     * @param string $filename
507
     * @param string $cfgPath
508
     * @param string|null $folder
509
     * @param bool $useRoot
510
     * @return bool
511
     */
512
    protected function handleDeleteImage(
513
        string $filename,
514
        string $cfgPath,
515
        ?string $folder = null,
516
        bool $useRoot = true
517
    ): bool {
518
        $configPath = $this->getRootPath($this->config->get($cfgPath), $useRoot);
519
        $path = $configPath;
520
        if ($folder !== null) {
521
            $path .= DIRECTORY_SEPARATOR . $folder;
522
        }
523
524
        $filepath = sprintf('%s/%s', $path, $filename);
525
        $handle = $this->filesystem->get($filepath);
526
        if ($handle === null) {
527
            return true; //no need to delete if file does not exist
528
        }
529
530
        $handle->delete();
531
532
        return true;
533
    }
534
535
536
    /**
537
     * Return the identifier of root path
538
     * @return string
539
     */
540
    protected function getRootPathId(): string
541
    {
542
        return '';
543
    }
544
545
546
    /**
547
     * Return the validation mime type rules of attachment upload
548
     * @return string[]
549
     */
550
    protected function getAttachmentMimeRules(): array
551
    {
552
        return ['text/plain'];
553
    }
554
555
    /**
556
     * Return the validation extension rules of attachment upload
557
     * @return string[]
558
     */
559
    protected function getAttachmentExtensionRules(): array
560
    {
561
        return [];
562
    }
563
564
    /**
565
     * Return the validation max file size rules of attachment upload
566
     * @return int|string
567
     */
568
    protected function getAttachmentMaxSize(): int|string
569
    {
570
        return '2M';
571
    }
572
573
    /**
574
     * Return the image max file size value
575
     * @return int|string
576
     */
577
    protected function getImageMaxSize(): int|string
578
    {
579
        return '1M';
580
    }
581
}
582