Completed
Push — master ( 2872e3...07dcbc )
by Jan
05:46
created

AttachmentHelper::getHumanFileSize()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 6
nc 2
nop 2
dl 0
loc 14
rs 10
c 1
b 0
f 0
1
<?php
2
/**
3
 *
4
 * part-db version 0.1
5
 * Copyright (C) 2005 Christoph Lechner
6
 * http://www.cl-projects.de/
7
 *
8
 * part-db version 0.2+
9
 * Copyright (C) 2009 K. Jacobs and others (see authors.php)
10
 * http://code.google.com/p/part-db/
11
 *
12
 * Part-DB Version 0.4+
13
 * Copyright (C) 2016 - 2019 Jan Böhmer
14
 * https://github.com/jbtronics
15
 *
16
 * This program is free software; you can redistribute it and/or
17
 * modify it under the terms of the GNU General Public License
18
 * as published by the Free Software Foundation; either version 2
19
 * of the License, or (at your option) any later version.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 * GNU General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU General Public License
27
 * along with this program; if not, write to the Free Software
28
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
29
 *
30
 */
31
32
namespace App\Services;
33
34
35
use App\Entity\Attachments\Attachment;
36
use App\Entity\Attachments\AttachmentContainingDBElement;
37
use App\Entity\Attachments\AttachmentTypeAttachment;
38
use App\Entity\Attachments\CategoryAttachment;
39
use App\Entity\Attachments\CurrencyAttachment;
40
use App\Entity\Attachments\DeviceAttachment;
41
use App\Entity\Attachments\FootprintAttachment;
42
use App\Entity\Attachments\GroupAttachment;
43
use App\Entity\Attachments\ManufacturerAttachment;
44
use App\Entity\Attachments\MeasurementUnitAttachment;
45
use App\Entity\Attachments\PartAttachment;
46
use App\Entity\Attachments\StorelocationAttachment;
47
use App\Entity\Attachments\SupplierAttachment;
48
use App\Entity\Attachments\UserAttachment;
49
use App\Services\Attachments\AttachmentPathResolver;
50
use Symfony\Component\HttpFoundation\File\UploadedFile;
51
52
class AttachmentHelper
53
{
54
55
    protected $pathResolver;
56
57
    public function __construct(AttachmentPathResolver $pathResolver)
58
    {
59
        $this->pathResolver = $pathResolver;
60
    }
61
62
    /**
63
     * Gets an SPLFileInfo object representing the file associated with the attachment.
64
     * @param Attachment $attachment The attachment for which the file should be generated
65
     * @return \SplFileInfo|null The fileinfo for the attachment file. Null, if the attachment is external or has
66
     * invalid file.
67
     */
68
    public function attachmentToFile(Attachment $attachment) : ?\SplFileInfo
69
    {
70
        if ($attachment->isExternal() || !$this->isFileExisting($attachment)) {
71
            return null;
72
        }
73
74
        return new \SplFileInfo($this->toAbsoluteFilePath($attachment));
75
    }
76
77
    /**
78
     * Returns the absolute filepath of the attachment. Null is returned, if the attachment is externally saved.
79
     * @param Attachment $attachment The attachment for which the filepath should be determined
80
     * @return string|null
81
     */
82
    public function toAbsoluteFilePath(Attachment $attachment): ?string
83
    {
84
        if (empty($attachment->getPath())) {
85
            return null;
86
        }
87
88
        if ($attachment->isExternal()) {
89
            return null;
90
        }
91
92
        $path = $this->pathResolver->placeholderToRealPath($attachment->getPath());
93
94
        //realpath does not work with null as argument
95
        if ($path === null) {
96
            return null;
97
        }
98
        return realpath($path);
99
    }
100
101
    /**
102
     * Checks if the file in this attachement is existing. This works for files on the HDD, and for URLs
103
     * (it's not checked if the ressource behind the URL is really existing, so for every external attachment true is returned).
104
     *
105
     * @param Attachment $attachment The attachment for which the existence should be checked
106
     *
107
     * @return bool True if the file is existing.
108
     */
109
    public function isFileExisting(Attachment $attachment): bool
110
    {
111
        if (empty($attachment->getPath())) {
112
            return false;
113
        }
114
115
        return file_exists($this->toAbsoluteFilePath($attachment)) || $attachment->isExternal();
116
    }
117
118
    /**
119
     * Returns the filesize of the attachments in bytes.
120
     * For external attachments or not existing attachments, null is returned.
121
     *
122
     * @param Attachment $attachment The filesize for which the filesize should be calculated.
123
     * @return int|null
124
     */
125
    public function getFileSize(Attachment $attachment): ?int
126
    {
127
        if ($attachment->isExternal()) {
128
            return null;
129
        }
130
131
        if (!$this->isFileExisting($attachment)) {
132
            return null;
133
        }
134
135
        $tmp = filesize($this->toAbsoluteFilePath($attachment));
136
        return  $tmp !== false ? $tmp : null;
137
    }
138
139
    /**
140
     * Returns a human readable version of the attachment file size.
141
     * For external attachments, null is returned.
142
     *
143
     * @param Attachment $attachment
144
     * @param int $decimals The number of decimals numbers that should be printed
145
     * @return string|null A string like 1.3M
146
     */
147
    public function getHumanFileSize(Attachment $attachment, $decimals = 2): ?string
148
    {
149
        $bytes = $this->getFileSize($attachment);
150
151
        if ($bytes == null) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $bytes of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
152
            return null;
153
        }
154
155
        //Format filesize for human reading
156
        //Taken from: https://www.php.net/manual/de/function.filesize.php#106569 and slightly modified
157
158
        $sz = 'BKMGTP';
159
        $factor = (int) floor((strlen($bytes) - 1) / 3);
160
        return sprintf("%.{$decimals}f", $bytes / 1024 ** $factor) . @$sz[$factor];
161
    }
162
163
    /**
164
     * Generate a path to a folder, where this attachment can save its file.
165
     * @param Attachment $attachment The attachment for which the folder should be generated
166
     * @return string The path to the folder (without trailing slash)
167
     */
168
    public function generateFolderForAttachment(Attachment $attachment) : string
169
    {
170
        $mapping = [PartAttachment::class => 'part', AttachmentTypeAttachment::class => 'attachment_type',
171
            CategoryAttachment::class => 'category', CurrencyAttachment::class => 'currency',
172
            DeviceAttachment::class => 'device', FootprintAttachment::class => 'footprint',
173
            GroupAttachment::class => 'group', ManufacturerAttachment::class => 'manufacturer',
174
            MeasurementUnitAttachment::class => 'measurement_unit', StorelocationAttachment::class => 'storelocation',
175
            SupplierAttachment::class => 'supplier', UserAttachment::class => 'user'];
176
177
        $path = $this->pathResolver->getMediaPath() . DIRECTORY_SEPARATOR . $mapping[get_class($attachment)] . DIRECTORY_SEPARATOR . $attachment->getElement()->getID();
178
        return $path;
179
    }
180
181
    /**
182
     * Moves the given uploaded file to a permanent place and saves it into the attachment
183
     * @param Attachment $attachment The attachment in which the file should be saved
184
     * @param UploadedFile|null $file The file which was uploaded
185
     * @param bool $become_preview_if_empty If this is true, the uploaded attachment can become the preview picture
186
     * if the of the element, if no was set already.
187
     * @return Attachment The attachment with the new filepath
188
     */
189
    public function upload(Attachment $attachment, ?UploadedFile $file, bool $become_preview_if_empty = true) : Attachment
190
    {
191
        //If file is null, do nothing (helpful, so we dont have to check if the file was reuploaded in controller)
192
        if (!$file) {
193
            return $attachment;
194
        }
195
196
        $folder = $this->generateFolderForAttachment($attachment);
197
198
        //Sanatize filename
199
        $originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
0 ignored issues
show
Unused Code introduced by
The assignment to $originalFilename is dead and can be removed.
Loading history...
200
        $newFilename = $attachment->getName() . '-' . uniqid('', false) . '.' . $file->getClientOriginalExtension();
201
202
        //Move our temporay attachment to its final location
203
        $file_path = $file->move($folder, $newFilename)->getRealPath();
204
205
        //Make our file path relative to %BASE%
206
        $file_path = $this->pathResolver->realPathToPlaceholder($file_path);
207
208
        //Save the path to the attachment
209
        $attachment->setPath($file_path);
0 ignored issues
show
Bug introduced by
It seems like $file_path can also be of type null; however, parameter $path of App\Entity\Attachments\Attachment::setPath() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

209
        $attachment->setPath(/** @scrutinizer ignore-type */ $file_path);
Loading history...
210
        //And save original filename
211
        $attachment->setFilename($file->getClientOriginalName());
212
213
        //Check if we should assign this to master picture
214
        //this is only possible if the attachment is new (not yet persisted to DB)
215
        if ($become_preview_if_empty && $attachment->getID() === null && $attachment->isPicture()) {
0 ignored issues
show
introduced by
The condition $attachment->getID() === null is always false.
Loading history...
216
            $element = $attachment->getElement();
217
            if ($element instanceof AttachmentContainingDBElement && $element->getMasterPictureAttachment() === null) {
218
                $element->setMasterPictureAttachment($attachment);
219
            }
220
        }
221
222
        return $attachment;
223
    }
224
225
}