Completed
Push — master ( 7f3b40...87527d )
by Jan
05:23
created

AttachmentHelper::realPathToPlaceholder()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 2
dl 0
loc 11
rs 10
c 0
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\PartAttachment;
37
use Doctrine\ORM\EntityManagerInterface;
38
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
39
use Symfony\Component\Filesystem\Filesystem;
40
use Symfony\Component\HttpFoundation\File\File;
41
use Symfony\Component\HttpFoundation\File\UploadedFile;
42
use Symfony\Component\HttpKernel\KernelInterface;
43
44
class AttachmentHelper
45
{
46
    /**
47
     * @var string The folder where the attachments are saved. By default this is data/media in the project root string
48
     */
49
    protected $base_path;
50
51
    public function __construct(ParameterBagInterface $params, KernelInterface $kernel)
52
    {
53
        $tmp_base_path = $params->get('media_directory');
54
55
        $fs = new Filesystem();
56
57
        //Determine if it is an absolute path, or if we need to create a real absolute one out of it
58
        if ($fs->isAbsolutePath($tmp_base_path)) {
59
            $this->base_path = $tmp_base_path;
60
        } else {
61
            $this->base_path = realpath($kernel->getProjectDir() . DIRECTORY_SEPARATOR . $tmp_base_path);
62
        }
63
    }
64
65
    /**
66
     * Returns the absolute path to the folder where all attachments are saved.
67
     * @return string
68
     */
69
    public function getMediaPath() : string
70
    {
71
        return $this->base_path;
72
    }
73
74
    /**
75
     * Converts an relative placeholder filepath (with %MEDIA% or older %BASE%) to an absolute filepath on disk.
76
     * @param string $placeholder_path The filepath with placeholder for which the real path should be determined.
77
     * @return string The absolute real path of the file
78
     */
79
    public function placeholderToRealPath(string $placeholder_path) : string
80
    {
81
        //The new attachments use %MEDIA% as placeholders, which is the directory set in media_directory
82
        $placeholder_path = str_replace("%MEDIA%", $this->base_path, $placeholder_path);
83
84
        //Older path entries are given via %BASE% which was the project root
85
        $placeholder_path = str_replace("%BASE%/data/media", $this->base_path, $placeholder_path);
86
87
        //Normalize path
88
        $placeholder_path = str_replace('\\', '/', $placeholder_path);
89
90
        return $placeholder_path;
91
    }
92
93
    /**
94
     * Converts an real absolute filepath to a placeholder version.
95
     * @param string $real_path The absolute path, for which the placeholder version should be generated.
96
     * @param bool $old_version By default the %MEDIA% placeholder is used, which is directly replaced with the
97
     * media directory. If set to true, the old version with %BASE% will be used, which is the project directory.
98
     * @return string The placeholder version of the filepath
99
     */
100
    public function realPathToPlaceholder(string $real_path, bool $old_version = false) : string
101
    {
102
        if ($old_version) {
103
            $real_path = str_replace($this->base_path, "%BASE%/data/media", $real_path);
104
        } else {
105
            $real_path = str_replace($this->base_path, "%MEDIA%", $real_path);
106
        }
107
108
        //Normalize path
109
        $real_path = str_replace('\\', '/', $real_path);
110
        return $real_path;
111
    }
112
113
    /**
114
     * Returns the absolute filepath of the attachment. Null is returned, if the attachment is externally saved.
115
     * @param Attachment $attachment The attachment for which the filepath should be determined
116
     * @return string|null
117
     */
118
    public function toAbsoluteFilePath(Attachment $attachment): ?string
119
    {
120
        if (empty($attachment->getPath())) {
121
            return null;
122
        }
123
124
        if ($attachment->isExternal()) {
125
            return null;
126
        }
127
128
        $path = $attachment->getPath();
129
        $path = $this->placeholderToRealPath($path);
130
        return realpath($path);
131
    }
132
133
    /**
134
     * Checks if the file in this attachement is existing. This works for files on the HDD, and for URLs
135
     * (it's not checked if the ressource behind the URL is really existing, so for every external attachment true is returned).
136
     *
137
     * @param Attachment $attachment The attachment for which the existence should be checked
138
     *
139
     * @return bool True if the file is existing.
140
     */
141
    public function isFileExisting(Attachment $attachment): bool
142
    {
143
        if (empty($attachment->getPath())) {
144
            return false;
145
        }
146
147
        return file_exists($this->toAbsoluteFilePath($attachment)) || $attachment->isExternal();
148
    }
149
150
    /**
151
     * Returns the filesize of the attachments in bytes.
152
     * For external attachments, null is returned.
153
     *
154
     * @param Attachment $attachment The filesize for which the filesize should be calculated.
155
     * @return int|null
156
     */
157
    public function getFileSize(Attachment $attachment): ?int
158
    {
159
        if ($attachment->isExternal()) {
160
            return null;
161
        }
162
163
        return filesize($this->toAbsoluteFilePath($attachment));
164
    }
165
166
    /**
167
     * Returns a human readable version of the attachment file size.
168
     * For external attachments, null is returned.
169
     *
170
     * @param Attachment $attachment
171
     * @param int $decimals The number of decimals numbers that should be printed
172
     * @return string|null A string like 1.3M
173
     */
174
    public function getHumanFileSize(Attachment $attachment, $decimals = 2): ?string
175
    {
176
        $bytes = $this->getFileSize($attachment);
177
178
        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...
179
            return null;
180
        }
181
182
        //Format filesize for human reading
183
        //Taken from: https://www.php.net/manual/de/function.filesize.php#106569 and slightly modified
184
185
        $sz = 'BKMGTP';
186
        $factor = (int) floor((strlen($bytes) - 1) / 3);
187
        return sprintf("%.{$decimals}f", $bytes / 1024 ** $factor) . @$sz[$factor];
188
    }
189
190
    /**
191
     * Generate a path to a folder, where this attachment can save its file.
192
     * @param Attachment $attachment The attachment for which the folder should be generated
193
     * @return string The path to the folder (without trailing slash)
194
     */
195
    public function generateFolderForAttachment(Attachment $attachment) : string
196
    {
197
        $mapping = [PartAttachment::class => 'part'];
198
199
        $path = $this->base_path . DIRECTORY_SEPARATOR . $mapping[get_class($attachment)] . DIRECTORY_SEPARATOR . $attachment->getElement()->getID();
200
        return $path;
201
    }
202
203
    /**
204
     * Moves the given uploaded file to a permanent place and saves it into the attachment
205
     * @param Attachment $attachment The attachment in which the file should be saved
206
     * @param UploadedFile|null $file The file which was uploaded
207
     * @return Attachment The attachment with the new filepath
208
     */
209
    public function upload(Attachment $attachment, ?UploadedFile $file) : Attachment
210
    {
211
        //If file is null, do nothing (helpful, so we dont have to check if the file was reuploaded in controller)
212
        if (!$file) {
213
            return $attachment;
214
        }
215
216
        $folder = $this->generateFolderForAttachment($attachment);
217
218
        //Sanatize filename
219
        $originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
220
        $safeFilename = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
221
        $newFilename = $safeFilename . '.' . $file->getClientOriginalExtension();
222
223
        //If a file with this name is already existing add a number to the filename
224
        if (file_exists($folder . DIRECTORY_SEPARATOR . $newFilename)) {
225
            $bak = $newFilename;
0 ignored issues
show
Unused Code introduced by
The assignment to $bak is dead and can be removed.
Loading history...
226
227
            $number = 1;
228
            $newFilename = $folder . DIRECTORY_SEPARATOR . $safeFilename . '-' . $number . '.' . $file->getClientOriginalExtension();
229
            while (file_exists($newFilename)) {
230
                $number++;
231
                $newFilename = $folder . DIRECTORY_SEPARATOR . $safeFilename . '-' . $number . '.' . $file->getClientOriginalExtension();
232
            }
233
        }
234
235
        //Move our temporay attachment to its final location
236
        $file_path = $file->move($folder, $newFilename)->getRealPath();
237
238
        //Make our file path relative to %BASE%
239
        $file_path = $this->realPathToPlaceholder($file_path);
240
241
        //Save the path to the attachment
242
        $attachment->setPath($file_path);
243
244
        return $attachment;
245
    }
246
247
}