Completed
Push — master ( 955d75...4d8fb1 )
by Ingo
36:25 queued 24:15
created

DBFile::getFilename()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Assets\Storage;
4
5
use SilverStripe\Assets\File;
6
use SilverStripe\Assets\Thumbnail;
7
use SilverStripe\Assets\ImageManipulation;
8
use SilverStripe\Core\Injector\Injector;
9
use SilverStripe\Control\Director;
10
use SilverStripe\Forms\AssetField;
11
use SilverStripe\ORM\ValidationResult;
12
use SilverStripe\ORM\ValidationException;
13
use SilverStripe\ORM\FieldType\DBComposite;
14
use SilverStripe\Security\Permission;
15
use SilverStripe\Core\Convert;
16
17
/**
18
 * Represents a file reference stored in a database
19
 *
20
 * @property string $Hash SHA of the file
21
 * @property string $Filename Name of the file, including directory
22
 * @property string $Variant Variant of the file
23
 */
24
class DBFile extends DBComposite implements AssetContainer, Thumbnail {
25
26
	use ImageManipulation;
27
28
	/**
29
	 * List of allowed file categories.
30
	 *
31
	 * {@see File::$app_categories}
32
	 *
33
	 * @var array
34
	 */
35
	protected $allowedCategories = array();
36
37
	/**
38
	 * List of image mime types supported by the image manipulations API
39
	 *
40
	 * {@see File::app_categories} for matching extensions.
41
	 *
42
	 * @config
43
	 * @var array
44
	 */
45
	private static $supported_images = array(
46
		'image/jpeg',
47
		'image/gif',
48
		'image/png'
49
	);
50
51
	/**
52
	 * Create a new image manipulation
53
	 *
54
	 * @param string $name
55
	 * @param array|string $allowed List of allowed file categories (not extensions), as per File::$app_categories
56
	 */
57
	public function __construct($name = null, $allowed = array()) {
58
		parent::__construct($name);
59
		$this->setAllowedCategories($allowed);
60
	}
61
62
	/**
63
	 * Determine if a valid non-empty image exists behind this asset, which is a format
64
	 * compatible with image manipulations
65
	 *
66
	 * @return boolean
67
	 */
68
	public function getIsImage() {
69
		// Check file type
70
		$mime = $this->getMimeType();
71
		return $mime && in_array($mime, $this->config()->supported_images);
0 ignored issues
show
Bug Best Practice introduced by
The expression $mime of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
72
	}
73
74
	/**
75
	 * @return AssetStore
76
	 */
77
	protected function getStore() {
78
		return Injector::inst()->get('AssetStore');
79
	}
80
81
	private static $composite_db = array(
82
		"Hash" => "Varchar(255)", // SHA of the base content
83
		"Filename" => "Varchar(255)", // Path identifier of the base content
84
		"Variant" => "Varchar(255)", // Identifier of the variant to the base, if given
85
	);
86
87
	private static $casting = array(
88
		'URL' => 'Varchar',
89
		'AbsoluteURL' => 'Varchar',
90
		'Basename' => 'Varchar',
91
		'Title' => 'Varchar',
92
		'MimeType' => 'Varchar',
93
		'String' => 'Text',
94
		'Tag' => 'HTMLFragment',
95
		'Size' => 'Varchar'
96
	);
97
98
	public function scaffoldFormField($title = null, $params = null) {
99
		return AssetField::create($this->getName(), $title);
100
	}
101
102
	/**
103
	 * Return a html5 tag of the appropriate for this file (normally img or a)
104
	 *
105
	 * @return string
106
	 */
107
	public function XML() {
108
		return $this->getTag() ?: '';
109
	}
110
111
	/**
112
	 * Return a html5 tag of the appropriate for this file (normally img or a)
113
	 *
114
	 * @return string
115
	 */
116
	public function getTag() {
117
		$template = $this->getFrontendTemplate();
118
		if(empty($template)) {
119
			return '';
120
		}
121
		return (string)$this->renderWith($template);
122
	}
123
124
	/**
125
	 * Determine the template to render as on the frontend
126
	 *
127
	 * @return string Name of template
128
	 */
129
	public function getFrontendTemplate() {
130
		// Check that path is available
131
		$url = $this->getURL();
132
		if(empty($url)) {
133
			return null;
134
		}
135
136
		// Image template for supported images
137
		if($this->getIsImage()) {
138
			return 'DBFile_image';
139
		}
140
141
		// Default download
142
		return 'DBFile_download';
143
	}
144
145
	/**
146
	 * Get trailing part of filename
147
	 *
148
	 * @return string
149
	 */
150
	public function getBasename() {
151
		if(!$this->exists()) {
152
			return null;
153
		}
154
		return basename($this->getSourceURL());
155
	}
156
157
	/**
158
	 * Get file extension
159
	 *
160
	 * @return string
161
	 */
162
	public function getExtension() {
163
		if(!$this->exists()) {
164
			return null;
165
		}
166
		return pathinfo($this->Filename, PATHINFO_EXTENSION);
167
	}
168
169
	/**
170
	 * Alt title for this
171
	 *
172
	 * @return string
173
	 */
174
	public function getTitle() {
175
		// If customised, use the customised title
176
		if($this->failover && ($title = $this->failover->Title)) {
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<SilverStripe\View\ViewableData>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
177
			return $title;
178
		}
179
		// fallback to using base name
180
		return $this->getBasename();
181
	}
182
183
	public function setFromLocalFile($path, $filename = null, $hash = null, $variant = null, $config = array()) {
184
		$this->assertFilenameValid($filename ?: $path);
185
		$result = $this
186
			->getStore()
187
			->setFromLocalFile($path, $filename, $hash, $variant, $config);
188
		// Update from result
189
		if($result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
190
			$this->setValue($result);
191
		}
192
		return $result;
193
	}
194
195
	public function setFromStream($stream, $filename, $hash = null, $variant = null, $config = array()) {
196
		$this->assertFilenameValid($filename);
197
		$result = $this
198
			->getStore()
199
			->setFromStream($stream, $filename, $hash, $variant, $config);
200
		// Update from result
201
		if($result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
202
			$this->setValue($result);
203
		}
204
		return $result;
205
	}
206
207
	public function setFromString($data, $filename, $hash = null, $variant = null, $config = array()) {
208
		$this->assertFilenameValid($filename);
209
		$result = $this
210
			->getStore()
211
			->setFromString($data, $filename, $hash, $variant, $config);
212
		// Update from result
213
		if($result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
214
			$this->setValue($result);
215
		}
216
		return $result;
217
	}
218
219
	public function getStream() {
220
		if(!$this->exists()) {
221
			return null;
222
		}
223
		return $this
224
			->getStore()
225
			->getAsStream($this->Filename, $this->Hash, $this->Variant);
226
	}
227
228
	public function getString() {
229
		if(!$this->exists()) {
230
			return null;
231
		}
232
		return $this
233
			->getStore()
234
			->getAsString($this->Filename, $this->Hash, $this->Variant);
235
	}
236
237
	public function getURL($grant = true) {
238
		if(!$this->exists()) {
239
			return null;
240
		}
241
		$url = $this->getSourceURL($grant);
242
		$this->updateURL($url);
243
		$this->extend('updateURL', $url);
244
		return $url;
245
	}
246
247
	/**
248
	 * Get URL, but without resampling.
249
	 * Note that this will return the url even if the file does not exist.
250
	 *
251
	 * @param bool $grant Ensures that the url for any protected assets is granted for the current user.
252
	 * @return string
253
	 */
254
	public function getSourceURL($grant = true) {
255
		return $this
256
			->getStore()
257
			->getAsURL($this->Filename, $this->Hash, $this->Variant, $grant);
258
	}
259
260
	/**
261
	 * Get the absolute URL to this resource
262
	 *
263
	 * @return string
264
	 */
265
	public function getAbsoluteURL() {
266
		if(!$this->exists()) {
267
			return null;
268
		}
269
		return Director::absoluteURL($this->getURL());
270
	}
271
272
	public function getMetaData() {
273
		if(!$this->exists()) {
274
			return null;
275
		}
276
		return $this
277
			->getStore()
278
			->getMetadata($this->Filename, $this->Hash, $this->Variant);
279
	}
280
281
	public function getMimeType() {
282
		if(!$this->exists()) {
283
			return null;
284
		}
285
		return $this
286
			->getStore()
287
			->getMimeType($this->Filename, $this->Hash, $this->Variant);
288
	}
289
290
	public function getValue() {
291
		if(!$this->exists()) {
292
			return null;
293
		}
294
		return array(
295
			'Filename' => $this->Filename,
296
			'Hash' => $this->Hash,
297
			'Variant' => $this->Variant
298
		);
299
	}
300
301
	public function getVisibility() {
302
		if(empty($this->Filename)) {
303
			return null;
304
		}
305
		return $this
306
			->getStore()
307
			->getVisibility($this->Filename, $this->Hash);
308
	}
309
310
	public function exists() {
311
		if(empty($this->Filename)) {
312
			return false;
313
		}
314
		return $this
315
			->getStore()
316
			->exists($this->Filename, $this->Hash, $this->Variant);
317
	}
318
319
	public function getFilename() {
320
		return $this->getField('Filename');
321
	}
322
323
	public function getHash() {
324
		return $this->getField('Hash');
325
	}
326
327
	public function getVariant() {
328
		return $this->getField('Variant');
329
	}
330
331
	/**
332
	 * Return file size in bytes.
333
	 *
334
	 * @return int
335
	 */
336
	public function getAbsoluteSize() {
337
		$metadata = $this->getMetaData();
338
		if(isset($metadata['size'])) {
339
			return $metadata['size'];
340
		}
341
		return 0;
342
	}
343
344
	/**
345
	 * Customise this object with an "original" record for getting other customised fields
346
	 *
347
	 * @param AssetContainer $original
348
	 * @return $this
349
	 */
350
	public function setOriginal($original) {
351
		$this->failover = $original;
0 ignored issues
show
Documentation Bug introduced by
It seems like $original of type object<SilverStripe\Asse...Storage\AssetContainer> is incompatible with the declared type object<SilverStripe\View\ViewableData> of property $failover.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
352
		return $this;
353
	}
354
355
	/**
356
	 * Get list of allowed file categories
357
	 *
358
	 * @return array
359
	 */
360
	public function getAllowedCategories() {
361
		return $this->allowedCategories;
362
	}
363
364
	/**
365
	 * Assign allowed categories
366
	 *
367
	 * @param array|string $categories
368
	 * @return $this
369
	 */
370
	public function setAllowedCategories($categories) {
371
		if(is_string($categories)) {
372
			$categories = preg_split('/\s*,\s*/', $categories);
373
		}
374
		$this->allowedCategories = (array)$categories;
375
		return $this;
376
	}
377
378
	/**
379
	 * Gets the list of extensions (if limited) for this field. Empty list
380
	 * means there is no restriction on allowed types.
381
	 *
382
	 * @return array
383
	 */
384
	protected function getAllowedExtensions() {
385
		$categories = $this->getAllowedCategories();
386
		return File::get_category_extensions($categories);
387
	}
388
389
	/**
390
	 * Validate that this DBFile accepts this filename as valid
391
	 *
392
	 * @param string $filename
393
	 * @throws ValidationException
394
	 * @return bool
395
	 */
396
	protected function isValidFilename($filename) {
397
		$extension = strtolower(File::get_file_extension($filename));
398
399
		// Validate true if within the list of allowed extensions
400
		$allowed = $this->getAllowedExtensions();
401
		if($allowed) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $allowed of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
402
			return in_array($extension, $allowed);
403
		}
404
405
		// If no extensions are configured, fallback to global list
406
		$globalList = File::config()->allowed_extensions;
0 ignored issues
show
Documentation introduced by
The property allowed_extensions does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
407
		if(in_array($extension, $globalList)) {
408
			return true;
409
		}
410
411
		// Only admins can bypass global rules
412
		return !File::config()->apply_restrictions_to_admin && Permission::check('ADMIN');
413
	}
414
415
	/**
416
	 * Check filename, and raise a ValidationException if invalid
417
	 *
418
	 * @param string $filename
419
	 * @throws ValidationException
420
	 */
421
	protected function assertFilenameValid($filename) {
422
		$result = new ValidationResult();
423
		$this->validate($result, $filename);
424
		if(!$result->valid()) {
425
			throw new ValidationException($result);
426
		}
427
	}
428
429
430
	/**
431
	 * Hook to validate this record against a validation result
432
	 *
433
	 * @param ValidationResult $result
434
	 * @param string $filename Optional filename to validate. If omitted, the current value is validated.
435
	 * @return bool Valid flag
436
	 */
437
	public function validate(ValidationResult $result, $filename = null) {
438
		if(empty($filename)) {
439
			$filename = $this->getFilename();
440
		}
441
		if(empty($filename) || $this->isValidFilename($filename)) {
442
			return true;
443
		}
444
445
		// Check allowed extensions
446
		$extensions = $this->getAllowedExtensions();
447
		if(empty($extensions)) {
448
			$extensions = File::config()->allowed_extensions;
0 ignored issues
show
Documentation introduced by
The property allowed_extensions does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
449
		}
450
		sort($extensions);
451
		$message = _t(
452
			'File.INVALIDEXTENSION',
453
			'Extension is not allowed (valid: {extensions})',
454
			'Argument 1: Comma-separated list of valid extensions',
455
			array('extensions' => wordwrap(implode(', ',$extensions)))
456
		);
457
		$result->error($message);
458
		return false;
459
	}
460
461
	public function setField($field, $value, $markChanged = true) {
462
		// Catch filename validation on direct assignment
463
		if($field === 'Filename' && $value) {
464
			$this->assertFilenameValid($value);
465
		}
466
467
		return parent::setField($field, $value, $markChanged);
468
	}
469
470
471
	/**
472
	 * Returns the size of the file type in an appropriate format.
473
	 *
474
	 * @return string|false String value, or false if doesn't exist
475
	 */
476
	public function getSize() {
477
		$size = $this->getAbsoluteSize();
478
		if($size) {
479
			return File::format_size($size);
480
		}
481
		return false;
482
	}
483
484
	public function deleteFile() {
485
		if(!$this->Filename) {
486
			return false;
487
		}
488
489
		return $this
490
			->getStore()
491
			->delete($this->Filename, $this->Hash);
492
	}
493
494
	public function publishFile() {
495
		if($this->Filename) {
496
			$this
497
				->getStore()
498
				->publish($this->Filename, $this->Hash);
499
		}
500
	}
501
502
	public function protectFile() {
503
		if($this->Filename) {
504
			$this
505
				->getStore()
506
				->protect($this->Filename, $this->Hash);
507
		}
508
	}
509
510
	public function grantFile() {
511
		if($this->Filename) {
512
			$this
513
				->getStore()
514
				->grant($this->Filename, $this->Hash);
515
		}
516
	}
517
518
	public function revokeFile() {
519
		if($this->Filename) {
520
			$this
521
				->getStore()
522
				->revoke($this->Filename, $this->Hash);
523
		}
524
	}
525
526
	public function canViewFile() {
527
		return $this->Filename
528
			&& $this
529
				->getStore()
530
				->canView($this->Filename, $this->Hash);
531
	}
532
533
	/**
534
	 * Generates the URL for this DBFile preview, this is particularly important for images that
535
	 * have been manipulated e.g. by {@link ImageManipulation}
536
	 * Use the 'updatePreviewLink' extension point to customise the link.
537
	 *
538
	 * @param null $action
539
	 * @return bool|string
540
	 */
541
	public function PreviewLink($action = null) {
542
		// Since AbsoluteURL can whitelist protected assets,
543
		// do permission check first
544
		if (!$this->failover->canView()) {
545
			return false;
546
		}
547
		if ($this->getIsImage()) {
548
			$link = $this->getAbsoluteURL();
549
		} else {
550
			$link = Convert::raw2att($this->failover->getIcon());
551
		}
552
		$this->extend('updatePreviewLink', $link, $action);
553
		return $link;
554
	}
555
}
556