FileHelper::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 0
c 1
b 0
f 0
nc 1
nop 3
dl 0
loc 5
rs 10
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 deleteUploadFile(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
     * Delete the given file
125
     * @param string $filename
126
     * @param string|null $folder
127
     * @param bool $useRoot
128
     * @return bool
129
     */
130
    public function delete(string $filename, ?string $folder = null, bool $useRoot = true): bool
131
    {
132
        $configPath = $this->getRootPath(
133
            $this->config->get('platform.data_attachment_path'),
134
            $useRoot
135
        );
136
        $path = $configPath;
137
        if ($folder !== null) {
138
            $path .= DIRECTORY_SEPARATOR . $folder;
139
        }
140
141
        $filepath = sprintf('%s/%s', $path, $filename);
142
        $handle = $this->filesystem->get($filepath);
143
        if ($handle === null) {
144
            return true; //no need to delete if file does not exist
145
        }
146
147
        $handle->delete();
148
149
        return true;
150
    }
151
152
    /**
153
     * Delete the given upload public image file
154
     * @param string $filename
155
     * @param bool $useRoot
156
     * @return bool
157
     */
158
    public function deleteUploadPublicImage(string $filename, bool $useRoot = true): bool
159
    {
160
        return $this->handleDeleteUploadImage(
161
            $filename,
162
            'platform.public_image_path',
163
            $useRoot
164
        );
165
    }
166
167
    /**
168
     * Delete the given upload image file
169
     * @param string $filename
170
     * @param bool $useRoot
171
     * @return bool
172
     */
173
    public function deleteUploadImage(string $filename, bool $useRoot = true): bool
174
    {
175
        return $this->handleDeleteUploadImage(
176
            $filename,
177
            'platform.data_image_path',
178
            $useRoot
179
        );
180
    }
181
182
    /**
183
     * Upload an image
184
     * @param string $name
185
     * @param bool $useRoot
186
     * @return UploadFileInfo
187
     */
188
    public function uploadImage(string $name, bool $useRoot = true): UploadFileInfo
189
    {
190
        return $this->doUploadImage(
191
            $name,
192
            'platform.data_image_path',
193
            $useRoot
194
        );
195
    }
196
197
    /**
198
     * Upload an public image
199
     * @param string $name
200
     * @param bool $useRoot
201
     * @return UploadFileInfo
202
     */
203
    public function uploadPublicImage(string $name, bool $useRoot = true): UploadFileInfo
204
    {
205
        return $this->doUploadImage(
206
            $name,
207
            'platform.public_image_path',
208
            $useRoot
209
        );
210
    }
211
212
    /**
213
     * Upload an attachment
214
     * @param string $name
215
     * @param null|string $folder
216
     * @param bool $useRoot
217
     * @return UploadFileInfo
218
     */
219
    public function uploadAttachment(
220
        string $name,
221
        ?string $folder = null,
222
        bool $useRoot = true
223
    ): UploadFileInfo {
224
        $configPath = $this->getRootPath(
225
            $this->config->get('platform.data_attachment_path'),
226
            $useRoot
227
        );
228
        $path = $configPath;
229
230
        if ($folder !== null) {
231
            $directory = $this->filesystem->directory($configPath);
232
            $path .= DIRECTORY_SEPARATOR . $folder;
233
            // Create the folder if it does not exist
234
            if ($this->filesystem->directory($path)->exists() === false) {
235
                $directory->create($folder, 0775, true);
236
            }
237
        }
238
239
        $rules = [
240
            new Required(),
241
            new Size($this->getAttachmentMaxSize()),
242
        ];
243
244
        $extensions = $this->getAttachmentExtensionRules();
245
        if (count($extensions) === 0) {
246
            $extensions = [
247
                    'png',
248
                    'gif',
249
                    'jpg',
250
                    'jpeg',
251
                    'csv',
252
                    'txt',
253
                    'docx',
254
                    'doc',
255
                    'pdf',
256
                    'xls',
257
                    'xlsx',
258
                    'pptx',
259
                    'ppt',
260
                    'zip',
261
                    'json',
262
                ];
263
        }
264
        $rules[] = new Extension($extensions);
265
266
        $mimes = $this->getAttachmentMimeRules();
267
        if (count($mimes) > 0) {
268
            $rules[] = new MimeType($mimes);
269
        }
270
271
272
        return $this->doUpload(
273
            $name,
274
            $rules,
275
            $path,
276
        );
277
    }
278
279
    /**
280
     * Return the path with the root suffix
281
     * @param string $configPath
282
     * @param bool $useRoot whether to use root path
283
     * @return string
284
     */
285
    public function getRootPath(string $configPath, bool $useRoot = true): string
286
    {
287
        $rootId = '';
288
        if ($useRoot) {
289
            $rootId = $this->getRootPathId();
290
        }
291
292
        $path = sprintf(
293
            '%s%s%s',
294
            $configPath,
295
            DIRECTORY_SEPARATOR,
296
            $rootId
297
        );
298
299
        $directory = $this->filesystem->directory($configPath);
300
301
        // Create the folder if it does not exist
302
        if ($this->filesystem->directory($path)->exists() === false) {
303
            $directory->create($rootId, 0775, true);
304
305
            // if it's public path add index file to prevent directory listing
306
            $this->filesystem->directory($path)->createFile('index.html', 'Access denied');
307
        }
308
309
        return $path;
310
    }
311
312
    /**
313
     * Process the upload
314
     * @param string $name
315
     * @param RuleInterface[] $rules
316
     * @param string $path
317
     *
318
     * @return UploadFileInfo
319
     */
320
    protected function doUpload(
321
        string $name,
322
        array $rules,
323
        string $path,
324
    ): UploadFileInfo {
325
        $uploadedFiles = UploadedFile::createFromGlobals();
326
        /** @var UploadedFile $uploadFile */
327
        $uploadFile = $uploadedFiles[$name] ?? [];
328
329
        $upload = new Upload(
330
            $name,
331
            new UploadFileSystem(
332
                $path,
333
                true
334
            ),
335
            null,
336
            [$name => $uploadFile]
337
        );
338
339
        $upload->setFilename(md5(uniqid() . time()));
340
341
        $upload->addValidations($rules);
342
343
        if ($upload->isUploaded() === false) {
344
            throw new UploadException($this->lang->tr('The file to upload is empty'));
345
        }
346
347
        $isUploaded = $upload->process();
348
        if ($isUploaded === false) {
349
            $errors = $upload->getErrors();
350
            throw new UploadException($errors[0]);
351
        }
352
353
        /** @var UploadFileInfo $info */
354
        $info = $upload->getInfo();
355
356
        return $info;
357
    }
358
359
    /**
360
     * Upload an image
361
     * @param string $name
362
     * @param string $path
363
     * @param bool $useRoot
364
     *
365
     * @return UploadFileInfo
366
     */
367
    protected function doUploadImage(
368
        string $name,
369
        string $path,
370
        bool $useRoot = true
371
    ): UploadFileInfo {
372
        $configPath = $this->getRootPath($this->config->get($path), $useRoot);
373
        return $this->doUpload(
374
            $name,
375
            [
376
                new Size($this->getImageMaxSize()),
377
                new Extension(['png', 'gif', 'jpg', 'jpeg']),
378
                new MimeType([
379
                    'image/png',
380
                    'image/jpg',
381
                    'image/gif',
382
                    'image/jpeg',
383
                ])
384
            ],
385
            $configPath
386
        );
387
    }
388
389
    /**
390
     * Delete the given upload image file
391
     * @param string $filename
392
     * @param string $cfgPath
393
     * @param bool $useRoot
394
     * @return bool
395
     */
396
    protected function handleDeleteUploadImage(
397
        string $filename,
398
        string $cfgPath,
399
        bool $useRoot = true
400
    ): bool {
401
        $configPath = $this->getRootPath($this->config->get($cfgPath), $useRoot);
402
        $path = $configPath;
403
404
        $filepath = sprintf('%s/%s', $path, $filename);
405
        $handle = $this->filesystem->get($filepath);
406
        if ($handle === null) {
407
            return true; //no need to delete if file does not exist
408
        }
409
410
        $handle->delete();
411
412
        return true;
413
    }
414
415
416
    /**
417
     * Return the identifier of root path
418
     * @return string
419
     */
420
    protected function getRootPathId(): string
421
    {
422
        return '';
423
    }
424
425
426
    /**
427
     * Return the validation mime type rules of attachment upload
428
     * @return string[]
429
     */
430
    protected function getAttachmentMimeRules(): array
431
    {
432
        return ['text/plain'];
433
    }
434
435
    /**
436
     * Return the validation extension rules of attachment upload
437
     * @return string[]
438
     */
439
    protected function getAttachmentExtensionRules(): array
440
    {
441
        return [];
442
    }
443
444
    /**
445
     * Return the validation max file size rules of attachment upload
446
     * @return int|string
447
     */
448
    protected function getAttachmentMaxSize(): int|string
449
    {
450
        return '2M';
451
    }
452
453
    /**
454
     * Return the image max file size value
455
     * @return int|string
456
     */
457
    protected function getImageMaxSize(): int|string
458
    {
459
        return '1M';
460
    }
461
}
462