Completed
Push — master ( d99365...200168 )
by Jan
05:26
created

Attachment::checkIfBuiltin()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * Part-DB Version 0.4+ "nextgen"
6
 * Copyright (C) 2016 - 2019 Jan Böhmer
7
 * https://github.com/jbtronics.
8
 *
9
 * This program is free software; you can redistribute it and/or
10
 * modify it under the terms of the GNU General Public License
11
 * as published by the Free Software Foundation; either version 2
12
 * of the License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
22
 */
23
24
namespace App\Entity\Attachments;
25
26
use App\Entity\Base\NamedDBElement;
27
use App\Validator\Constraints\Selectable;
28
use Doctrine\ORM\Mapping as ORM;
29
30
/**
31
 * Class Attachment.
32
 *
33
 * @ORM\Entity
34
 * @ORM\Table(name="`attachments`")
35
 * @ORM\InheritanceType("SINGLE_TABLE")
36
 * @ORM\DiscriminatorColumn(name="class_name", type="string")
37
 * @ORM\DiscriminatorMap({
38
 *     "PartDB\Part" = "PartAttachment", "Part" = "PartAttachment",
39
 *     "PartDB\Device" = "DeviceAttachment", "Device" = "DeviceAttachment",
40
 *     "AttachmentType" = "AttachmentTypeAttachment", "Category" = "CategoryAttachment",
41
 *     "Footprint" = "FootprintAttachment", "Manufacturer" = "ManufacturerAttachment",
42
 *     "Currency" = "CurrencyAttachment", "Group" = "GroupAttachment",
43
 *     "MeasurementUnit" = "MeasurementUnitAttachment", "Storelocation" = "StorelocationAttachment",
44
 *     "Supplier" = "SupplierAttachment", "User" = "UserAttachment"
45
 * })
46
 * @ORM\EntityListeners({"App\EntityListeners\AttachmentDeleteListener"})
47
 *
48
 */
49
abstract class Attachment extends NamedDBElement
50
{
51
    /**
52
     * A list of file extensions, that browsers can show directly as image.
53
     * Based on: https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types
54
     * It will be used to determine if a attachment is a picture and therefore will be shown to user as preview.
55
     */
56
    const PICTURE_EXTS = ['apng', 'bmp', 'gif', 'ico', 'cur', 'jpg', 'jpeg', 'jfif', 'pjpeg', 'pjp', 'png',
57
                            'svg', 'webp'];
58
59
    /**
60
     * When the path begins with one of this placeholders
61
     */
62
    const INTERNAL_PLACEHOLDER = ['%BASE%', '%MEDIA%'];
63
64
    /** @var array Placeholders for attachments which using built in files. */
65
    const BUILTIN_PLACEHOLDER = ['%FOOTPRINTS%', '%FOOTPRINTS3D%'];
66
67
    /**
68
     * @var bool
69
     * @ORM\Column(type="boolean")
70
     */
71
    protected $show_in_table = false;
72
73
    /**
74
     * @var string The path to the file relative to a placeholder path like %MEDIA%
75
     * @ORM\Column(type="string", name="path")
76
     */
77
    protected $path = '';
78
79
    /**
80
     * @var string The original filenamethe file had, when the user uploaded it.
81
     * @ORM\Column(type="string", nullable=true)
82
     */
83
    protected $original_filename;
84
85
    /**
86
     * ORM mapping is done in sub classes (like PartAttachment)
87
     */
88
    protected $element;
89
90
    /**
91
     * @var AttachmentType
92
     * @ORM\ManyToOne(targetEntity="AttachmentType", inversedBy="attachments_with_type")
93
     * @ORM\JoinColumn(name="type_id", referencedColumnName="id")
94
     * @Selectable()
95
     */
96
    protected $attachment_type;
97
98
    /***********************************************************
99
     * Various function
100
     ***********************************************************/
101
102
    /**
103
     * Check if this attachement is a picture (analyse the file's extension).
104
     * If the link is external, it is assumed that this is false.
105
     *
106
     * @return bool * true if the file extension is a picture extension
107
     *              * otherwise false
108
     */
109
    public function isPicture(): bool
110
    {
111
        //We can not check if a external link is a picture, so just assume this is false
112
        if ($this->isExternal()) {
113
            return true;
114
        }
115
116
        $extension = pathinfo($this->getPath(), PATHINFO_EXTENSION);
117
118
        return in_array(strtolower($extension), static::PICTURE_EXTS, true);
119
    }
120
121
    /**
122
     * Checks if the attachment file is externally saved (the database saves an URL)
123
     * @return bool true, if the file is saved externally
124
     */
125
    public function isExternal() : bool
126
    {
127
        //After the %PLACEHOLDER% comes a slash, so we can check if we have a placholder via explode
128
        $tmp = explode("/", $this->path);
129
130
        if (empty($tmp)) {
131
            return true;
132
        }
133
134
        return !in_array($tmp[0], array_merge(static::INTERNAL_PLACEHOLDER, static::BUILTIN_PLACEHOLDER), false);
135
    }
136
137
    /**
138
     * Checks if the attachment file is using a builtin file. (see BUILTIN_PLACEHOLDERS const for possible placeholders)
139
     * If a file is built in, the path is shown to user in url field (no sensitive infos are provided)
140
     * @return bool True if the attachment is uning an builtin file.
141
     */
142
    public function isBuiltIn() : bool
143
    {
144
        return static::checkIfBuiltin($this->path);
145
    }
146
147
    /********************************************************************************
148
     *
149
     *   Getters
150
     *
151
     *********************************************************************************/
152
153
    /**
154
     * Returns the extension of the file referenced via the attachment.
155
     * For a path like %BASE/path/foo.bar, bar will be returned.
156
     * If this attachment is external null is returned.
157
     * @return string|null The file extension in lower case.
158
     */
159
    public function getExtension() : ?string
160
    {
161
        if ($this->isExternal()) {
162
            return null;
163
        }
164
165
        if (!empty($this->original_filename)) {
166
            return strtolower(pathinfo($this->original_filename, PATHINFO_EXTENSION));
167
        }
168
169
        return strtolower(pathinfo($this->getPath(), PATHINFO_EXTENSION));
170
    }
171
172
    /**
173
     * Get the element, associated with this Attachement (for example a "Part" object).
174
     *
175
     * @return AttachmentContainingDBElement The associated Element.
176
     */
177
    public function getElement(): ?AttachmentContainingDBElement
178
    {
179
        return $this->element;
180
    }
181
182
    /**
183
     * The URL to the external file, or the path to the built in file.
184
     * Returns null, if the file is not external (and not builtin).
185
     * @return string|null
186
     */
187
    public function getURL(): ?string
188
    {
189
        if (!$this->isExternal() && !$this->isBuiltIn()) {
190
            return null;
191
        }
192
193
        return $this->path;
194
    }
195
196
    /**
197
     * Returns the hostname where the external file is stored.
198
     * Returns null, if the file is not external.
199
     * @return string|null
200
     */
201
    public function getHost(): ?string
202
    {
203
        if (!$this->isExternal()) {
204
            return null;
205
        }
206
207
        return parse_url($this->getURL(), PHP_URL_HOST);
208
    }
209
210
    /**
211
     * Get the filepath, relative to %BASE%.
212
     *
213
     * @return string A string like %BASE/path/foo.bar
214
     */
215
    public function getPath(): string
216
    {
217
        return $this->path;
218
    }
219
220
    /**
221
     * Returns the filename of the attachment.
222
     * For a path like %BASE/path/foo.bar, foo.bar will be returned.
223
     *
224
     * If the path is a URL (can be checked via isExternal()), null will be returned.
225
     *
226
     * @return string|null
227
     */
228
    public function getFilename(): ?string
229
    {
230
        if ($this->isExternal()) {
231
            return null;
232
        }
233
234
        //If we have a stored original filename, then use it
235
        if (!empty($this->original_filename)) {
236
            return $this->original_filename;
237
        }
238
239
        return pathinfo($this->getPath(), PATHINFO_BASENAME);
240
    }
241
242
    /**
243
     * Sets the filename that is shown for this attachment. Useful when the internal path is some generated value.
244
     * @param string|null $new_filename The filename that should be shown.
245
     * Set to null to generate the filename from path.
246
     * @return Attachment
247
     */
248
    public function setFilename(?string $new_filename): Attachment
249
    {
250
        $this->original_filename = $new_filename;
251
        return $this;
252
    }
253
254
    /**
255
     * Get the show_in_table attribute.
256
     *
257
     * @return bool true means, this attachement will be listed in the "Attachements" column of the HTML tables
258
     *              false means, this attachement won't be listed in the "Attachements" column of the HTML tables
259
     */
260
    public function getShowInTable(): bool
261
    {
262
        return (bool) $this->show_in_table;
263
    }
264
265
    /**
266
     *  Get the type of this attachement.
267
     *
268
     * @return AttachmentType the type of this attachement
269
     *
270
     */
271
    public function getAttachmentType(): ?AttachmentType
272
    {
273
        return $this->attachment_type;
274
    }
275
276
    /**
277
     * Returns the ID as an string, defined by the element class.
278
     * This should have a form like P000014, for a part with ID 14.
279
     *
280
     * @return string The ID as a string;
281
     */
282
    public function getIDString(): string
283
    {
284
        return 'A'.sprintf('%09d', $this->getID());
285
    }
286
287
    /*****************************************************************************************************
288
     * Setters
289
     ****************************************************************************************************/
290
291
    /**
292
     * @param bool $show_in_table
293
     *
294
     * @return self
295
     */
296
    public function setShowInTable(bool $show_in_table): self
297
    {
298
        $this->show_in_table = $show_in_table;
299
300
        return $this;
301
    }
302
303
    abstract public function setElement(AttachmentContainingDBElement $element) : Attachment;
304
305
    /**
306
     * @param string $path
307
     * @return Attachment
308
     */
309
    public function setPath(string $path): Attachment
310
    {
311
        $this->path = $path;
312
        return $this;
313
    }
314
315
    /**
316
     * @param AttachmentType $attachement_type
317
     * @return Attachment
318
     */
319
    public function setAttachmentType(AttachmentType $attachement_type): Attachment
320
    {
321
        $this->attachment_type = $attachement_type;
322
        return $this;
323
    }
324
325
    /**
326
     * Sets the url associated with this attachment.
327
     * If the url is empty nothing is changed, to not override the file path.
328
     * @param string|null $url
329
     * @return Attachment
330
     */
331
    public function setURL(?string $url) : Attachment
332
    {
333
        //Only set if the URL is not empty
334
        if (!empty($url)) {
335
            if (strpos($url, '%BASE%') !== false || strpos($url, '%MEDIA%') !== false) {
336
                throw new \InvalidArgumentException('You can not reference internal files via the url field! But nice try!');
337
            }
338
339
            $this->path = $url;
340
        }
341
342
        return $this;
343
    }
344
345
346
    /*****************************************************************************************************
347
     * Static functions
348
     *****************************************************************************************************/
349
350
    /**
351
     * Checks if the given path is a path to a builtin ressource.
352
     * @param string $path The path that should be checked
353
     * @return bool True if the path is pointing to a builtin ressource.
354
     */
355
    public static function checkIfBuiltin(string $path) : bool
356
    {
357
        //After the %PLACEHOLDER% comes a slash, so we can check if we have a placholder via explode
358
        $tmp = explode('/', $path);
359
        //Builtins must have a %PLACEHOLDER% construction
360
        if (empty($tmp)) {
361
            return false;
362
        }
363
        return in_array($tmp[0], static::BUILTIN_PLACEHOLDER, false);
364
    }
365
366
    /**
367
     * Check if a string is a URL and is valid.
368
     * @param $string string The string which should be checked.
369
     * @param bool $path_required If true, the string must contain a path to be valid. (e.g. foo.bar would be invalid, foo.bar/test.php would be valid).
370
     * @param $only_http bool Set this to true, if only HTTPS or HTTP schemata should be allowed.
371
     *  *Caution: When this is set to false, a attacker could use the file:// schema, to get internal server files, like /etc/passwd.*
372
     * @return bool True if the string is a valid URL. False, if the string is not an URL or invalid.
373
     */
374
    public static function isURL(string $string, bool $path_required = true, bool $only_http = true) : bool
375
    {
376
        if ($only_http) {   //Check if scheme is HTTPS or HTTP
377
            $scheme = parse_url($string, PHP_URL_SCHEME);
378
            if ($scheme !== 'http' && $scheme !== 'https') {
379
                return false;   //All other schemes are not valid.
380
            }
381
        }
382
        if ($path_required) {
383
            return (bool) filter_var($string, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED);
384
        }
385
386
        return (bool) filter_var($string, FILTER_VALIDATE_URL);
387
    }
388
}
389