Completed
Push — master ( 5776a0...605463 )
by Daniel
23s
created

DBFile::XML()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Filesystem\Storage;
4
5
use SilverStripe\Filesystem\Thumbnail;
6
use SilverStripe\Filesystem\ImageManipulation;
7
8
9
use Injector;
10
use AssetField;
11
use File;
12
use Director;
13
14
15
16
use SilverStripe\ORM\ValidationResult;
17
use SilverStripe\ORM\ValidationException;
18
use SilverStripe\ORM\FieldType\DBComposite;
19
use SilverStripe\Security\Permission;
20
21
22
23
/**
24
 * Represents a file reference stored in a database
25
 *
26
 * @property string $Hash SHA of the file
27
 * @property string $Filename Name of the file, including directory
28
 * @property string $Variant Variant of the file
29
 *
30
 * @package framework
31
 * @subpackage filesystem
32
 */
33
class DBFile extends DBComposite implements AssetContainer, Thumbnail {
34
35
	use ImageManipulation;
36
37
	/**
38
	 * List of allowed file categories.
39
	 *
40
	 * {@see File::$app_categories}
41
	 *
42
	 * @var array
43
	 */
44
	protected $allowedCategories = array();
45
46
	/**
47
	 * List of image mime types supported by the image manipulations API
48
	 *
49
	 * {@see File::app_categories} for matching extensions.
50
	 *
51
	 * @config
52
	 * @var array
53
	 */
54
	private static $supported_images = array(
55
		'image/jpeg',
56
		'image/gif',
57
		'image/png'
58
	);
59
60
	/**
61
	 * Create a new image manipulation
62
	 *
63
	 * @param string $name
64
	 * @param array|string $allowed List of allowed file categories (not extensions), as per File::$app_categories
65
	 */
66
	public function __construct($name = null, $allowed = array()) {
67
		parent::__construct($name);
68
		$this->setAllowedCategories($allowed);
69
	}
70
71
	/**
72
	 * Determine if a valid non-empty image exists behind this asset, which is a format
73
	 * compatible with image manipulations
74
	 *
75
	 * @return boolean
76
	 */
77
	public function getIsImage() {
78
		// Check file type
79
		$mime = $this->getMimeType();
80
		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...
81
	}
82
83
	/**
84
	 * @return AssetStore
85
	 */
86
	protected function getStore() {
87
		return Injector::inst()->get('AssetStore');
88
	}
89
90
	private static $composite_db = array(
91
		"Hash" => "Varchar(255)", // SHA of the base content
92
		"Filename" => "Varchar(255)", // Path identifier of the base content
93
		"Variant" => "Varchar(255)", // Identifier of the variant to the base, if given
94
	);
95
96
	private static $casting = array(
97
		'URL' => 'Varchar',
98
		'AbsoluteURL' => 'Varchar',
99
		'Basename' => 'Varchar',
100
		'Title' => 'Varchar',
101
		'MimeType' => 'Varchar',
102
		'String' => 'Text',
103
		'Tag' => 'HTMLFragment',
104
		'Size' => 'Varchar'
105
	);
106
107
	public function scaffoldFormField($title = null, $params = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $params is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
108
		return AssetField::create($this->getName(), $title);
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 XML() {
117
		return $this->getTag() ?: '';
118
	}
119
120
	/**
121
	 * Return a html5 tag of the appropriate for this file (normally img or a)
122
	 *
123
	 * @return string
124
	 */
125
	public function getTag() {
126
		$template = $this->getFrontendTemplate();
127
		if(empty($template)) {
128
			return '';
129
		}
130
		return (string)$this->renderWith($template);
131
	}
132
133
	/**
134
	 * Determine the template to render as on the frontend
135
	 *
136
	 * @return string Name of template
137
	 */
138
	public function getFrontendTemplate() {
139
		// Check that path is available
140
		$url = $this->getURL();
141
		if(empty($url)) {
142
			return null;
143
		}
144
145
		// Image template for supported images
146
		if($this->getIsImage()) {
147
			return 'DBFile_image';
148
		}
149
150
		// Default download
151
		return 'DBFile_download';
152
	}
153
154
	/**
155
	 * Get trailing part of filename
156
	 *
157
	 * @return string
158
	 */
159
	public function getBasename() {
160
		if(!$this->exists()) {
161
			return null;
162
		}
163
		return basename($this->getSourceURL());
164
	}
165
166
	/**
167
	 * Get file extension
168
	 *
169
	 * @return string
170
	 */
171
	public function getExtension() {
172
		if(!$this->exists()) {
173
			return null;
174
		}
175
		return pathinfo($this->Filename, PATHINFO_EXTENSION);
176
	}
177
178
	/**
179
	 * Alt title for this
180
	 *
181
	 * @return string
182
	 */
183
	public function getTitle() {
184
		// If customised, use the customised title
185
		if($this->failover && ($title = $this->failover->Title)) {
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<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...
186
			return $title;
187
		}
188
		// fallback to using base name
189
		return $this->getBasename();
190
	}
191
192
	public function setFromLocalFile($path, $filename = null, $hash = null, $variant = null, $config = array()) {
193
		$this->assertFilenameValid($filename ?: $path);
194
		$result = $this
195
			->getStore()
196
			->setFromLocalFile($path, $filename, $hash, $variant, $config);
197
		// Update from result
198
		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...
199
			$this->setValue($result);
200
		}
201
		return $result;
202
	}
203
204
	public function setFromStream($stream, $filename, $hash = null, $variant = null, $config = array()) {
205
		$this->assertFilenameValid($filename);
206
		$result = $this
207
			->getStore()
208
			->setFromStream($stream, $filename, $hash, $variant, $config);
209
		// Update from result
210
		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...
211
			$this->setValue($result);
212
		}
213
		return $result;
214
	}
215
216
	public function setFromString($data, $filename, $hash = null, $variant = null, $config = array()) {
217
		$this->assertFilenameValid($filename);
218
		$result = $this
219
			->getStore()
220
			->setFromString($data, $filename, $hash, $variant, $config);
221
		// Update from result
222
		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...
223
			$this->setValue($result);
224
		}
225
		return $result;
226
	}
227
228
	public function getStream() {
229
		if(!$this->exists()) {
230
			return null;
231
		}
232
		return $this
233
			->getStore()
234
			->getAsStream($this->Filename, $this->Hash, $this->Variant);
235
	}
236
237
	public function getString() {
238
		if(!$this->exists()) {
239
			return null;
240
		}
241
		return $this
242
			->getStore()
243
			->getAsString($this->Filename, $this->Hash, $this->Variant);
244
	}
245
246
	public function getURL($grant = true) {
247
		if(!$this->exists()) {
248
			return null;
249
		}
250
		$url = $this->getSourceURL($grant);
251
		$this->updateURL($url);
252
		$this->extend('updateURL', $url);
253
		return $url;
254
	}
255
256
	/**
257
	 * Get URL, but without resampling.
258
	 * Note that this will return the url even if the file does not exist.
259
	 *
260
	 * @param bool $grant Ensures that the url for any protected assets is granted for the current user.
261
	 * @return string
262
	 */
263
	public function getSourceURL($grant = true) {
264
		return $this
265
			->getStore()
266
			->getAsURL($this->Filename, $this->Hash, $this->Variant, $grant);
267
	}
268
269
	/**
270
	 * Get the absolute URL to this resource
271
	 *
272
	 * @return string
273
	 */
274
	public function getAbsoluteURL() {
275
		if(!$this->exists()) {
276
			return null;
277
		}
278
		return Director::absoluteURL($this->getURL());
279
	}
280
281
	public function getMetaData() {
282
		if(!$this->exists()) {
283
			return null;
284
		}
285
		return $this
286
			->getStore()
287
			->getMetadata($this->Filename, $this->Hash, $this->Variant);
288
	}
289
290
	public function getMimeType() {
291
		if(!$this->exists()) {
292
			return null;
293
		}
294
		return $this
295
			->getStore()
296
			->getMimeType($this->Filename, $this->Hash, $this->Variant);
297
	}
298
299
	public function getValue() {
300
		if(!$this->exists()) {
301
			return null;
302
		}
303
		return array(
304
			'Filename' => $this->Filename,
305
			'Hash' => $this->Hash,
306
			'Variant' => $this->Variant
307
		);
308
	}
309
310
	public function getVisibility() {
311
		if(empty($this->Filename)) {
312
			return null;
313
		}
314
		return $this
315
			->getStore()
316
			->getVisibility($this->Filename, $this->Hash);
317
	}
318
319
	public function exists() {
320
		if(empty($this->Filename)) {
321
			return false;
322
		}
323
		return $this
324
			->getStore()
325
			->exists($this->Filename, $this->Hash, $this->Variant);
326
	}
327
328
	public function getFilename() {
329
		return $this->getField('Filename');
330
	}
331
332
	public function getHash() {
333
		return $this->getField('Hash');
334
	}
335
336
	public function getVariant() {
337
		return $this->getField('Variant');
338
	}
339
340
	/**
341
	 * Return file size in bytes.
342
	 *
343
	 * @return int
344
	 */
345
	public function getAbsoluteSize() {
346
		$metadata = $this->getMetaData();
347
		if(isset($metadata['size'])) {
348
			return $metadata['size'];
349
		}
350
		return 0;
351
	}
352
353
	/**
354
	 * Customise this object with an "original" record for getting other customised fields
355
	 *
356
	 * @param AssetContainer $original
357
	 * @return $this
358
	 */
359
	public function setOriginal($original) {
360
		$this->failover = $original;
0 ignored issues
show
Documentation Bug introduced by
It seems like $original of type object<SilverStripe\File...Storage\AssetContainer> is incompatible with the declared type object<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...
361
		return $this;
362
	}
363
364
	/**
365
	 * Get list of allowed file categories
366
	 *
367
	 * @return array
368
	 */
369
	public function getAllowedCategories() {
370
		return $this->allowedCategories;
371
	}
372
373
	/**
374
	 * Assign allowed categories
375
	 *
376
	 * @param array|string $categories
377
	 * @return $this
378
	 */
379
	public function setAllowedCategories($categories) {
380
		if(is_string($categories)) {
381
			$categories = preg_split('/\s*,\s*/', $categories);
382
		}
383
		$this->allowedCategories = (array)$categories;
384
		return $this;
385
	}
386
387
	/**
388
	 * Gets the list of extensions (if limited) for this field. Empty list
389
	 * means there is no restriction on allowed types.
390
	 *
391
	 * @return array
392
	 */
393
	protected function getAllowedExtensions() {
394
		$categories = $this->getAllowedCategories();
395
		return File::get_category_extensions($categories);
396
	}
397
398
	/**
399
	 * Validate that this DBFile accepts this filename as valid
400
	 *
401
	 * @param string $filename
402
	 * @throws ValidationException
403
	 * @return bool
404
	 */
405
	protected function isValidFilename($filename) {
406
		$extension = strtolower(File::get_file_extension($filename));
407
408
		// Validate true if within the list of allowed extensions
409
		$allowed = $this->getAllowedExtensions();
410
		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...
411
			return in_array($extension, $allowed);
412
		}
413
414
		// If no extensions are configured, fallback to global list
415
		$globalList = File::config()->allowed_extensions;
0 ignored issues
show
Documentation introduced by
The property allowed_extensions does not exist on object<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...
416
		if(in_array($extension, $globalList)) {
417
			return true;
418
		}
419
420
		// Only admins can bypass global rules
421
		return !File::config()->apply_restrictions_to_admin && Permission::check('ADMIN');
422
	}
423
424
	/**
425
	 * Check filename, and raise a ValidationException if invalid
426
	 *
427
	 * @param string $filename
428
	 * @throws ValidationException
429
	 */
430
	protected function assertFilenameValid($filename) {
431
		$result = new ValidationResult();
432
		$this->validate($result, $filename);
433
		if(!$result->valid()) {
434
			throw new ValidationException($result);
435
		}
436
	}
437
438
439
	/**
440
	 * Hook to validate this record against a validation result
441
	 *
442
	 * @param ValidationResult $result
443
	 * @param string $filename Optional filename to validate. If omitted, the current value is validated.
444
	 * @return bool Valid flag
445
	 */
446
	public function validate(ValidationResult $result, $filename = null) {
447
		if(empty($filename)) {
448
			$filename = $this->getFilename();
449
		}
450
		if(empty($filename) || $this->isValidFilename($filename)) {
451
			return true;
452
		}
453
454
		// Check allowed extensions
455
		$extensions = $this->getAllowedExtensions();
456
		if(empty($extensions)) {
457
			$extensions = File::config()->allowed_extensions;
0 ignored issues
show
Documentation introduced by
The property allowed_extensions does not exist on object<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...
458
		}
459
		sort($extensions);
460
		$message = _t(
461
			'File.INVALIDEXTENSION',
462
			'Extension is not allowed (valid: {extensions})',
463
			'Argument 1: Comma-separated list of valid extensions',
464
			array('extensions' => wordwrap(implode(', ',$extensions)))
465
		);
466
		$result->error($message);
467
		return false;
468
	}
469
470
	public function setField($field, $value, $markChanged = true) {
471
		// Catch filename validation on direct assignment
472
		if($field === 'Filename' && $value) {
473
			$this->assertFilenameValid($value);
474
		}
475
476
		return parent::setField($field, $value, $markChanged);
477
	}
478
479
480
	/**
481
	 * Returns the size of the file type in an appropriate format.
482
	 *
483
	 * @return string|false String value, or false if doesn't exist
484
	 */
485
	public function getSize() {
486
		$size = $this->getAbsoluteSize();
487
		if($size) {
488
			return \File::format_size($size);
489
		}
490
		return false;
491
	}
492
493
	public function deleteFile() {
494
		if(!$this->Filename) {
495
			return false;
496
		}
497
498
		return $this
499
			->getStore()
500
			->delete($this->Filename, $this->Hash);
501
	}
502
503
	public function publishFile() {
504
		if($this->Filename) {
505
			$this
506
				->getStore()
507
				->publish($this->Filename, $this->Hash);
508
		}
509
	}
510
511
	public function protectFile() {
512
		if($this->Filename) {
513
			$this
514
				->getStore()
515
				->protect($this->Filename, $this->Hash);
516
		}
517
	}
518
519
	public function grantFile() {
520
		if($this->Filename) {
521
			$this
522
				->getStore()
523
				->grant($this->Filename, $this->Hash);
524
		}
525
	}
526
527
	public function revokeFile() {
528
		if($this->Filename) {
529
			$this
530
				->getStore()
531
				->revoke($this->Filename, $this->Hash);
532
		}
533
	}
534
535
	public function canViewFile() {
536
		return $this->Filename
537
			&& $this
538
				->getStore()
539
				->canView($this->Filename, $this->Hash);
540
	}
541
}
542