Completed
Push — master ( d9fe77...6645ab )
by Jan
04:48
created

Attachment::is3DModel()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 10
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
    public const PICTURE_EXTS = ['apng', 'bmp', 'gif', 'ico', 'cur', 'jpg', 'jpeg', 'jfif', 'pjpeg', 'pjp', 'png',
57
                            'svg', 'webp'];
58
59
    /**
60
     * A list of extensions that will be treated as a 3D Model that can be shown to user directly in Part-DB.
61
     */
62
    public const MODEL_EXTS = ['x3d'];
63
64
    /**
65
     * When the path begins with one of this placeholders
66
     */
67
    public const INTERNAL_PLACEHOLDER = ['%BASE%', '%MEDIA%'];
68
69
    /** @var array Placeholders for attachments which using built in files. */
70
    public const BUILTIN_PLACEHOLDER = ['%FOOTPRINTS%', '%FOOTPRINTS3D%'];
71
72
    /**
73
     * @var bool
74
     * @ORM\Column(type="boolean")
75
     */
76
    protected $show_in_table = false;
77
78
    /**
79
     * @var string The path to the file relative to a placeholder path like %MEDIA%
80
     * @ORM\Column(type="string", name="path")
81
     */
82
    protected $path = '';
83
84
    /**
85
     * @var string The original filenamethe file had, when the user uploaded it.
86
     * @ORM\Column(type="string", nullable=true)
87
     */
88
    protected $original_filename;
89
90
    /**
91
     * ORM mapping is done in sub classes (like PartAttachment)
92
     */
93
    protected $element;
94
95
    /**
96
     * @var AttachmentType
97
     * @ORM\ManyToOne(targetEntity="AttachmentType", inversedBy="attachments_with_type")
98
     * @ORM\JoinColumn(name="type_id", referencedColumnName="id")
99
     * @Selectable()
100
     */
101
    protected $attachment_type;
102
103
    /***********************************************************
104
     * Various function
105
     ***********************************************************/
106
107
    /**
108
     * Check if this attachement is a picture (analyse the file's extension).
109
     * If the link is external, it is assumed that this is true.
110
     *
111
     * @return bool * true if the file extension is a picture extension
112
     *              * otherwise false
113
     */
114
    public function isPicture(): bool
115
    {
116
        //We can not check if a external link is a picture, so just assume this is false
117
        if ($this->isExternal()) {
118
            return true;
119
        }
120
121
        $extension = pathinfo($this->getPath(), PATHINFO_EXTENSION);
122
123
        return in_array(strtolower($extension), static::PICTURE_EXTS, true);
124
    }
125
126
    /**
127
     * Check if this attachment is a 3D model and therfore can be directly shown to user.
128
     * If the attachment is external, false is returned (3D Models must be internal).
129
     * @return bool
130
     */
131
    public function is3DModel() : bool
132
    {
133
        //We just assume that 3D Models are internally saved, otherwise we get problems loading them.
134
        if ($this->isExternal()) {
135
            return false;
136
        }
137
138
        $extension = pathinfo($this->getPath(), PATHINFO_EXTENSION);
139
140
        return in_array(strtolower($extension), static::MODEL_EXTS, true);
141
    }
142
143
    /**
144
     * Checks if the attachment file is externally saved (the database saves an URL)
145
     * @return bool true, if the file is saved externally
146
     */
147
    public function isExternal() : bool
148
    {
149
        //After the %PLACEHOLDER% comes a slash, so we can check if we have a placholder via explode
150
        $tmp = explode("/", $this->path);
151
152
        if (empty($tmp)) {
153
            return true;
154
        }
155
156
        return !in_array($tmp[0], array_merge(static::INTERNAL_PLACEHOLDER, static::BUILTIN_PLACEHOLDER), false);
157
    }
158
159
    /**
160
     * Checks if the attachment file is using a builtin file. (see BUILTIN_PLACEHOLDERS const for possible placeholders)
161
     * If a file is built in, the path is shown to user in url field (no sensitive infos are provided)
162
     * @return bool True if the attachment is uning an builtin file.
163
     */
164
    public function isBuiltIn() : bool
165
    {
166
        return static::checkIfBuiltin($this->path);
167
    }
168
169
    /********************************************************************************
170
     *
171
     *   Getters
172
     *
173
     *********************************************************************************/
174
175
    /**
176
     * Returns the extension of the file referenced via the attachment.
177
     * For a path like %BASE/path/foo.bar, bar will be returned.
178
     * If this attachment is external null is returned.
179
     * @return string|null The file extension in lower case.
180
     */
181
    public function getExtension() : ?string
182
    {
183
        if ($this->isExternal()) {
184
            return null;
185
        }
186
187
        if (!empty($this->original_filename)) {
188
            return strtolower(pathinfo($this->original_filename, PATHINFO_EXTENSION));
189
        }
190
191
        return strtolower(pathinfo($this->getPath(), PATHINFO_EXTENSION));
192
    }
193
194
    /**
195
     * Get the element, associated with this Attachement (for example a "Part" object).
196
     *
197
     * @return AttachmentContainingDBElement The associated Element.
198
     */
199
    public function getElement(): ?AttachmentContainingDBElement
200
    {
201
        return $this->element;
202
    }
203
204
    /**
205
     * The URL to the external file, or the path to the built in file.
206
     * Returns null, if the file is not external (and not builtin).
207
     * @return string|null
208
     */
209
    public function getURL(): ?string
210
    {
211
        if (!$this->isExternal() && !$this->isBuiltIn()) {
212
            return null;
213
        }
214
215
        return $this->path;
216
    }
217
218
    /**
219
     * Returns the hostname where the external file is stored.
220
     * Returns null, if the file is not external.
221
     * @return string|null
222
     */
223
    public function getHost(): ?string
224
    {
225
        if (!$this->isExternal()) {
226
            return null;
227
        }
228
229
        return parse_url($this->getURL(), PHP_URL_HOST);
230
    }
231
232
    /**
233
     * Get the filepath, relative to %BASE%.
234
     *
235
     * @return string A string like %BASE/path/foo.bar
236
     */
237
    public function getPath(): string
238
    {
239
        return $this->path;
240
    }
241
242
    /**
243
     * Returns the filename of the attachment.
244
     * For a path like %BASE/path/foo.bar, foo.bar will be returned.
245
     *
246
     * If the path is a URL (can be checked via isExternal()), null will be returned.
247
     *
248
     * @return string|null
249
     */
250
    public function getFilename(): ?string
251
    {
252
        if ($this->isExternal()) {
253
            return null;
254
        }
255
256
        //If we have a stored original filename, then use it
257
        if (!empty($this->original_filename)) {
258
            return $this->original_filename;
259
        }
260
261
        return pathinfo($this->getPath(), PATHINFO_BASENAME);
262
    }
263
264
    /**
265
     * Sets the filename that is shown for this attachment. Useful when the internal path is some generated value.
266
     * @param string|null $new_filename The filename that should be shown.
267
     * Set to null to generate the filename from path.
268
     * @return Attachment
269
     */
270
    public function setFilename(?string $new_filename): Attachment
271
    {
272
        $this->original_filename = $new_filename;
273
        return $this;
274
    }
275
276
    /**
277
     * Get the show_in_table attribute.
278
     *
279
     * @return bool true means, this attachement will be listed in the "Attachements" column of the HTML tables
280
     *              false means, this attachement won't be listed in the "Attachements" column of the HTML tables
281
     */
282
    public function getShowInTable(): bool
283
    {
284
        return (bool) $this->show_in_table;
285
    }
286
287
    /**
288
     *  Get the type of this attachement.
289
     *
290
     * @return AttachmentType the type of this attachement
291
     *
292
     */
293
    public function getAttachmentType(): ?AttachmentType
294
    {
295
        return $this->attachment_type;
296
    }
297
298
    /**
299
     * Returns the ID as an string, defined by the element class.
300
     * This should have a form like P000014, for a part with ID 14.
301
     *
302
     * @return string The ID as a string;
303
     */
304
    public function getIDString(): string
305
    {
306
        return 'A'.sprintf('%09d', $this->getID());
307
    }
308
309
    /*****************************************************************************************************
310
     * Setters
311
     ****************************************************************************************************/
312
313
    /**
314
     * @param bool $show_in_table
315
     *
316
     * @return self
317
     */
318
    public function setShowInTable(bool $show_in_table): self
319
    {
320
        $this->show_in_table = $show_in_table;
321
322
        return $this;
323
    }
324
325
    abstract public function setElement(AttachmentContainingDBElement $element) : Attachment;
326
327
    /**
328
     * @param string $path
329
     * @return Attachment
330
     */
331
    public function setPath(string $path): Attachment
332
    {
333
        $this->path = $path;
334
        return $this;
335
    }
336
337
    /**
338
     * @param AttachmentType $attachement_type
339
     * @return Attachment
340
     */
341
    public function setAttachmentType(AttachmentType $attachement_type): Attachment
342
    {
343
        $this->attachment_type = $attachement_type;
344
        return $this;
345
    }
346
347
    /**
348
     * Sets the url associated with this attachment.
349
     * If the url is empty nothing is changed, to not override the file path.
350
     * @param string|null $url
351
     * @return Attachment
352
     */
353
    public function setURL(?string $url) : Attachment
354
    {
355
        //Only set if the URL is not empty
356
        if (!empty($url)) {
357
            if (strpos($url, '%BASE%') !== false || strpos($url, '%MEDIA%') !== false) {
358
                throw new \InvalidArgumentException('You can not reference internal files via the url field! But nice try!');
359
            }
360
361
            $this->path = $url;
362
            //Reset internal filename
363
            $this->original_filename = null;
364
        }
365
366
        return $this;
367
    }
368
369
370
    /*****************************************************************************************************
371
     * Static functions
372
     *****************************************************************************************************/
373
374
    /**
375
     * Checks if the given path is a path to a builtin ressource.
376
     * @param string $path The path that should be checked
377
     * @return bool True if the path is pointing to a builtin ressource.
378
     */
379
    public static function checkIfBuiltin(string $path) : bool
380
    {
381
        //After the %PLACEHOLDER% comes a slash, so we can check if we have a placholder via explode
382
        $tmp = explode('/', $path);
383
        //Builtins must have a %PLACEHOLDER% construction
384
        if (empty($tmp)) {
385
            return false;
386
        }
387
        return in_array($tmp[0], static::BUILTIN_PLACEHOLDER, false);
388
    }
389
390
    /**
391
     * Check if a string is a URL and is valid.
392
     * @param $string string The string which should be checked.
393
     * @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).
394
     * @param $only_http bool Set this to true, if only HTTPS or HTTP schemata should be allowed.
395
     *  *Caution: When this is set to false, a attacker could use the file:// schema, to get internal server files, like /etc/passwd.*
396
     * @return bool True if the string is a valid URL. False, if the string is not an URL or invalid.
397
     */
398
    public static function isURL(string $string, bool $path_required = true, bool $only_http = true) : bool
399
    {
400
        if ($only_http) {   //Check if scheme is HTTPS or HTTP
401
            $scheme = parse_url($string, PHP_URL_SCHEME);
402
            if ($scheme !== 'http' && $scheme !== 'https') {
403
                return false;   //All other schemes are not valid.
404
            }
405
        }
406
        if ($path_required) {
407
            return (bool) filter_var($string, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED);
408
        }
409
410
        return (bool) filter_var($string, FILTER_VALIDATE_URL);
411
    }
412
}
413