Completed
Push — master ( 60247a...406ff5 )
by Ingo
24s
created

File::getAbsoluteSize()   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
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace SilverStripe\Assets;
4
5
use SilverStripe\Admin\AdminRootController;
6
use SilverStripe\Admin\CMSPreviewable;
7
use SilverStripe\Assets\Storage\DBFile;
8
use SilverStripe\Assets\Storage\AssetContainer;
9
use SilverStripe\CMS\Model\SiteTree;
10
use SilverStripe\Core\Convert;
11
use SilverStripe\Control\Director;
12
use SilverStripe\Control\Controller;
13
use SilverStripe\Dev\Deprecation;
14
use SilverStripe\Forms\DatetimeField;
15
use SilverStripe\Forms\FieldList;
16
use SilverStripe\Forms\HeaderField;
17
use SilverStripe\Forms\HiddenField;
18
use SilverStripe\Forms\Tab;
19
use SilverStripe\Forms\TabSet;
20
use SilverStripe\Forms\LiteralField;
21
use SilverStripe\Forms\ReadonlyField;
22
use SilverStripe\Forms\TextField;
23
use SilverStripe\ORM\DataObject;
24
use SilverStripe\ORM\DB;
25
use SilverStripe\ORM\Hierarchy\Hierarchy;
26
use SilverStripe\ORM\ValidationResult;
27
use SilverStripe\ORM\Versioning\Versioned;
28
use SilverStripe\Security\Member;
29
use SilverStripe\Security\Permission;
30
use SilverStripe\View\Parsers\ShortcodeHandler;
31
use SilverStripe\View\Parsers\ShortcodeParser;
32
use InvalidArgumentException;
33
34
/**
35
 * This class handles the representation of a file on the filesystem within the framework.
36
 * Most of the methods also handle the {@link Folder} subclass.
37
 *
38
 * Note: The files are stored in the assets/ directory, but SilverStripe
39
 * looks at the db object to gather information about a file such as URL
40
 * It then uses this for all processing functions (like image manipulation).
41
 *
42
 * <b>Security</b>
43
 *
44
 * Caution: It is recommended to disable any script execution in the "assets/"
45
 * directory in the webserver configuration, to reduce the risk of exploits.
46
 * See http://doc.silverstripe.org/secure-development#filesystem
47
 *
48
 * <b>Asset storage</b>
49
 *
50
 * As asset storage is configured separately to any File DataObject records, this class
51
 * does not make any assumptions about how these records are saved. They could be on
52
 * a local filesystem, remote filesystem, or a virtual record container (such as in local memory).
53
 *
54
 * The File dataobject simply represents an externally facing view of shared resources
55
 * within this asset store.
56
 *
57
 * Internally individual files are referenced by a"Filename" parameter, which represents a File, extension,
58
 * and is optionally prefixed by a list of custom directories. This path is root-agnostic, so it does not
59
 * automatically have a direct url mapping (even to the site's base directory).
60
 *
61
 * Additionally, individual files may have several versions distinguished by sha1 hash,
62
 * of which a File DataObject can point to a single one. Files can also be distinguished by
63
 * variants, which may be resized images or format-shifted documents.
64
 *
65
 * <b>Properties</b>
66
 *
67
 * - "Title": Optional title of the file (for display purposes only).
68
 *   Defaults to "Name". Note that the Title field of Folder (subclass of File)
69
 *   is linked to Name, so Name and Title will always be the same.
70
 * -"File": Physical asset backing this DB record. This is a composite DB field with
71
 *   its own list of properties. {@see DBFile} for more information
72
 * - "Content": Typically unused, but handy for a textual representation of
73
 *   files, e.g. for fulltext indexing of PDF documents.
74
 * - "ParentID": Points to a {@link Folder} record. Should be in sync with
75
 *   "Filename". A ParentID=0 value points to the "assets/" folder, not the webroot.
76
 * -"ShowInSearch": True if this file is searchable
77
 *
78
 * @property string $Name Basename of the file
79
 * @property string $Title Title of the file
80
 * @property DBFile $File asset stored behind this File record
81
 * @property string $Content
82
 * @property string $ShowInSearch Boolean that indicates if file is shown in search. Doesn't apply to Folders
83
 * @property int $ParentID ID of parent File/Folder
84
 * @property int $OwnerID ID of Member who owns the file
85
 *
86
 * @method File Parent() Returns parent File
87
 * @method Member Owner() Returns Member object of file owner.
88
 *
89
 * @mixin Hierarchy
90
 * @mixin Versioned
91
 */
92
class File extends DataObject implements ShortcodeHandler, AssetContainer, Thumbnail, CMSPreviewable {
93
94
	use ImageManipulation;
95
96
	private static $default_sort = "\"Name\"";
97
98
	/**
99
	 * @config
100
	 * @var string
101
	 */
102
	private static $singular_name = "File";
103
104
	private static $plural_name = "Files";
105
106
	/**
107
	 * Permissions necessary to view files outside of the live stage (e.g. archive / draft stage).
108
	 *
109
	 * @config
110
	 * @var array
111
	 */
112
	private static $non_live_permissions = array('CMS_ACCESS_LeftAndMain', 'CMS_ACCESS_AssetAdmin', 'VIEW_DRAFT_CONTENT');
113
114
	private static $db = array(
115
		"Name" => "Varchar(255)",
116
		"Title" => "Varchar(255)",
117
		"File" =>"DBFile",
118
		// Only applies to files, doesn't inherit for folder
119
		'ShowInSearch' => 'Boolean(1)',
120
	);
121
122
	private static $has_one = array(
123
		"Parent" => "SilverStripe\\Assets\\File",
124
		"Owner" => "SilverStripe\\Security\\Member"
125
	);
126
127
	private static $defaults = array(
128
		"ShowInSearch" => 1,
129
	);
130
131
	private static $extensions = array(
132
		"SilverStripe\\ORM\\Hierarchy\\Hierarchy",
133
		"SilverStripe\\ORM\\Versioning\\Versioned"
134
	);
135
136
	private static $casting = array (
137
		'TreeTitle' => 'HTMLFragment'
138
	);
139
140
	private static $table_name = 'File';
141
142
	/**
143
	 * @config
144
	 * @var array List of allowed file extensions, enforced through {@link validate()}.
145
	 *
146
	 * Note: if you modify this, you should also change a configuration file in the assets directory.
147
	 * Otherwise, the files will be able to be uploaded but they won't be able to be served by the
148
	 * webserver.
149
	 *
150
	 *  - If you are running Apache you will need to change assets/.htaccess
151
	 *  - If you are running IIS you will need to change assets/web.config
152
	 *
153
	 * Instructions for the change you need to make are included in a comment in the config file.
154
	 */
155
	private static $allowed_extensions = array(
156
		'', 'ace', 'arc', 'arj', 'asf', 'au', 'avi', 'bmp', 'bz2', 'cab', 'cda', 'css', 'csv', 'dmg', 'doc',
157
		'docx', 'dotx', 'dotm', 'flv', 'gif', 'gpx', 'gz', 'hqx', 'ico', 'jar', 'jpeg', 'jpg', 'js', 'kml',
158
		'm4a', 'm4v', 'mid', 'midi', 'mkv', 'mov', 'mp3', 'mp4', 'mpa', 'mpeg', 'mpg', 'ogg', 'ogv', 'pages',
159
		'pcx', 'pdf', 'png', 'pps', 'ppt', 'pptx', 'potx', 'potm', 'ra', 'ram', 'rm', 'rtf', 'sit', 'sitx',
160
		'tar', 'tgz', 'tif', 'tiff', 'txt', 'wav', 'webm', 'wma', 'wmv', 'xls', 'xlsx', 'xltx', 'xltm', 'zip',
161
		'zipx',
162
	);
163
164
	/**
165
	 * @config
166
	 * @var array Category identifiers mapped to commonly used extensions.
167
	 */
168
	private static $app_categories = array(
169
		'archive' => array(
170
			'ace', 'arc', 'arj', 'bz', 'bz2', 'cab', 'dmg', 'gz', 'hqx', 'jar', 'rar', 'sit', 'sitx', 'tar', 'tgz',
171
			'zip', 'zipx',
172
		),
173
		'audio' => array(
174
			'aif', 'aifc', 'aiff', 'apl', 'au', 'avr', 'cda', 'm4a', 'mid', 'midi', 'mp3', 'ogg', 'ra',
175
			'ram', 'rm', 'snd', 'wav', 'wma',
176
		),
177
		'document' => array(
178
			'css', 'csv', 'doc', 'docx', 'dotm', 'dotx', 'htm', 'html', 'gpx', 'js', 'kml', 'pages', 'pdf',
179
			'potm', 'potx', 'pps', 'ppt', 'pptx', 'rtf', 'txt', 'xhtml', 'xls', 'xlsx', 'xltm', 'xltx', 'xml',
180
		),
181
		'image' => array(
182
			'alpha', 'als', 'bmp', 'cel', 'gif', 'ico', 'icon', 'jpeg', 'jpg', 'pcx', 'png', 'ps', 'tif', 'tiff',
183
		),
184
		'image/supported' => array(
185
			'gif', 'jpeg', 'jpg', 'png'
186
		),
187
		'flash' => array(
188
			'fla', 'swf'
189
		),
190
		'video' => array(
191
			'asf', 'avi', 'flv', 'ifo', 'm1v', 'm2v', 'm4v', 'mkv', 'mov', 'mp2', 'mp4', 'mpa', 'mpe', 'mpeg',
192
			'mpg', 'ogv', 'qt', 'vob', 'webm', 'wmv',
193
		),
194
	);
195
196
	/**
197
	 * Map of file extensions to class type
198
	 *
199
	 * @config
200
	 * @var
201
	 */
202
	private static $class_for_file_extension = array(
203
		'*' => 'SilverStripe\\Assets\\File',
204
		'jpg' => 'SilverStripe\\Assets\\Image',
205
		'jpeg' => 'SilverStripe\\Assets\\Image',
206
		'png' => 'SilverStripe\\Assets\\Image',
207
		'gif' => 'SilverStripe\\Assets\\Image',
208
	);
209
210
	/**
211
	 * @config
212
	 * @var bool If this is true, then restrictions set in {@link $allowed_max_file_size} and
213
	 * {@link $allowed_extensions} will be applied to users with admin privileges as
214
	 * well.
215
	 */
216
	private static $apply_restrictions_to_admin = true;
217
218
	/**
219
	 * If enabled, legacy file dataobjects will be automatically imported into the APL
220
	 *
221
	 * @config
222
	 * @var bool
223
	 */
224
	private static $migrate_legacy_file = false;
225
226
	/**
227
	 * @config
228
	 * @var boolean
229
	 */
230
	private static $update_filesystem = true;
231
232
	public static function get_shortcodes() {
233
		return 'file_link';
234
	}
235
236
	/**
237
	 * Replace "[file_link id=n]" shortcode with an anchor tag or link to the file.
238
	 *
239
	 * @param array $arguments Arguments passed to the parser
240
	 * @param string $content Raw shortcode
241
	 * @param ShortcodeParser $parser Parser
242
	 * @param string $shortcode Name of shortcode used to register this handler
243
	 * @param array $extra Extra arguments
244
	 * @return string Result of the handled shortcode
245
	 */
246
	public static function handle_shortcode($arguments, $content, $parser, $shortcode, $extra = array()) {
247
		// Find appropriate record, with fallback for error handlers
248
		$record = static::find_shortcode_record($arguments, $errorCode);
249
		if($errorCode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errorCode of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
250
			$record = static::find_error_record($errorCode);
251
		}
252
		if (!$record) {
253
			return null; // There were no suitable matches at all.
254
		}
255
256
		// build the HTML tag
257
		if($content) {
258
			// build some useful meta-data (file type and size) as data attributes
259
			$attrs = ' ';
260
			if($record instanceof File) {
261
				foreach(array(
262
					'class' => 'file',
263
					'data-type' => $record->getExtension(),
264
					'data-size' => $record->getSize()
265
				) as $name => $value) {
266
					$attrs .= sprintf('%s="%s" ', $name, $value);
267
				}
268
			}
269
270
			return sprintf('<a href="%s"%s>%s</a>', $record->Link(), rtrim($attrs), $parser->parse($content));
271
		} else {
272
			return $record->Link();
273
		}
274
	}
275
276
	/**
277
	 * Find the record to use for a given shortcode.
278
	 *
279
	 * @param array $args Array of input shortcode arguments
280
	 * @param int $errorCode If the file is not found, or is inaccessible, this will be assigned to a HTTP error code.
281
	 * @return File|null The File DataObject, if it can be found.
282
	 */
283
	public static function find_shortcode_record($args, &$errorCode = null) {
284
		// Validate shortcode
285
		if(!isset($args['id']) || !is_numeric($args['id'])) {
286
			return null;
287
		}
288
289
		// Check if the file is found
290
		/** @var File $file */
291
		$file = File::get()->byID($args['id']);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
292
		if (!$file) {
293
			$errorCode = 404;
294
			return null;
295
		}
296
297
		// Check if the file is viewable
298
		if(!$file->canView()) {
299
			$errorCode = 403;
300
			return null;
301
		}
302
303
		// Success
304
		return $file;
305
	}
306
307
	/**
308
	 * Given a HTTP Error, find an appropriate substitute File or SiteTree data object instance.
309
	 *
310
	 * @param int $errorCode HTTP Error value
311
	 * @return File|SiteTree File or SiteTree object to use for the given error
312
	 */
313
	protected static function find_error_record($errorCode) {
314
		$result = static::singleton()->invokeWithExtensions('getErrorRecordFor', $errorCode);
315
		$result = array_filter($result);
316
		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...
317
			return reset($result);
318
		}
319
		return null;
320
	}
321
322
	/**
323
	 * A file only exists if the file_exists() and is in the DB as a record
324
	 *
325
	 * Use $file->isInDB() to only check for a DB record
326
	 * Use $file->File->exists() to only check if the asset exists
327
	 *
328
	 * @return bool
329
	 */
330
	public function exists() {
331
		return parent::exists() && $this->File->exists();
332
	}
333
334
	/**
335
	 * Find a File object by the given filename.
336
	 *
337
	 * @param string $filename Filename to search for, including any custom parent directories.
338
	 * @return File
339
	 */
340
	public static function find($filename) {
341
		// Split to folders and the actual filename, and traverse the structure.
342
		$parts = explode("/", $filename);
343
		$parentID = 0;
344
		/** @var File $item */
345
		$item = null;
346
		foreach($parts as $part) {
347
			$item = File::get()->filter(array(
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
348
				'Name' => $part,
349
				'ParentID' => $parentID
350
			))->first();
351
			if(!$item) break;
352
			$parentID = $item->ID;
353
		}
354
355
		return $item;
356
	}
357
358
	/**
359
	 * Just an alias function to keep a consistent API with SiteTree
360
	 *
361
	 * @return string The link to the file
362
	 */
363
	public function Link() {
364
		return $this->getURL();
365
	}
366
367
	/**
368
	 * @deprecated 4.0
369
	 */
370
	public function RelativeLink() {
371
		Deprecation::notice('4.0', 'Use getURL instead, as not all files will be relative to the site root.');
372
		return Director::makeRelative($this->getURL());
373
	}
374
375
	/**
376
	 * Just an alias function to keep a consistent API with SiteTree
377
	 *
378
	 * @return string The absolute link to the file
379
	 */
380
	public function AbsoluteLink() {
381
		return $this->getAbsoluteURL();
382
	}
383
384
	/**
385
	 * @return string
386
	 */
387
	public function getTreeTitle() {
388
		return Convert::raw2xml($this->Title);
389
	}
390
391
	/**
392
	 * @param Member $member
393
	 * @return bool
394
	 */
395
	public function canView($member = null) {
396
		if(!$member) {
397
			$member = Member::currentUser();
398
		}
399
400
		$result = $this->extendedCan('canView', $member);
0 ignored issues
show
Bug introduced by
It seems like $member can be null; however, extendedCan() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
401
		if($result !== null) {
402
			return $result;
403
	}
404
405
		return true;
406
	}
407
408
	/**
409
	 * Check if this file can be modified
410
	 *
411
	 * @param Member $member
412
	 * @return boolean
413
	 */
414
	public function canEdit($member = null) {
415
		if(!$member) {
416
			$member = Member::currentUser();
417
		}
418
419
		$result = $this->extendedCan('canEdit', $member);
0 ignored issues
show
Bug introduced by
It seems like $member can be null; however, extendedCan() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
420
		if($result !== null) {
421
			return $result;
422
		}
423
424
		return Permission::checkMember($member, array('CMS_ACCESS_AssetAdmin', 'CMS_ACCESS_LeftAndMain'));
425
	}
426
427
	/**
428
	 * Check if a file can be created
429
	 *
430
	 * @param Member $member
431
	 * @param array $context
432
	 * @return boolean
433
	 */
434
	public function canCreate($member = null, $context = array()) {
435
		if(!$member) {
436
			$member = Member::currentUser();
437
		}
438
439
		$result = $this->extendedCan('canCreate', $member, $context);
0 ignored issues
show
Bug introduced by
It seems like $member can be null; however, extendedCan() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
440
		if($result !== null) {
441
			return $result;
442
		}
443
444
		return $this->canEdit($member);
445
	}
446
447
	/**
448
	 * Check if this file can be deleted
449
	 *
450
	 * @param Member $member
451
	 * @return boolean
452
	 */
453
	public function canDelete($member = null) {
454
		if(!$member) {
455
			$member = Member::currentUser();
456
		}
457
458
		$result = $this->extendedCan('canDelete', $member);
0 ignored issues
show
Bug introduced by
It seems like $member can be null; however, extendedCan() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
459
		if($result !== null) {
460
			return $result;
461
		}
462
463
		return $this->canEdit($member);
464
	}
465
466
	/**
467
	 * Returns the fields to power the edit screen of files in the CMS.
468
	 * You can modify this FieldList by subclassing folder, or by creating a {@link DataExtension}
469
	 * and implementing updateCMSFields(FieldList $fields) on that extension.
470
	 *
471
	 * @return FieldList
472
	 */
473
	public function getCMSFields() {
474
		$path = '/' . dirname($this->getFilename());
475
476
		$previewLink = Convert::raw2att($this->PreviewLink());
477
		$image = "<img src=\"{$previewLink}\" class=\"editor__thumbnail\" />";
478
479
		$statusTitle = $this->getStatusTitle();
480
		$statusFlag = ($statusTitle) ? "<span class=\"editor__status-flag\">{$statusTitle}</span>" : '';
481
482
		$content = Tab::create('Main',
483
			HeaderField::create('TitleHeader', $this->Title, 1)
484
				->addExtraClass('editor__heading'),
485
            LiteralField::create('StatusFlag', $statusFlag),
486
			LiteralField::create("IconFull", $image)
487
				->addExtraClass('editor__file-preview'),
488
			TabSet::create('Editor',
489
				Tab::create('Details',
490
					TextField::create("Title", $this->fieldLabel('Title')),
491
					TextField::create("Name", $this->fieldLabel('Filename')),
492
					ReadonlyField::create(
493
						"Path",
494
						_t('AssetTableField.PATH', 'Path'),
495
						(($path !== '/.') ? $path : '') . '/'
496
					)
497
				),
498
				Tab::create('Usage',
499
					DatetimeField::create(
500
						"Created",
501
						_t('AssetTableField.CREATED', 'First uploaded')
502
					)->setReadonly(true),
503
					DatetimeField::create(
504
						"LastEdited",
505
						_t('AssetTableField.LASTEDIT', 'Last changed')
506
					)->setReadonly(true)
507
				)
508
			),
509
			HiddenField::create('ID', $this->ID)
510
		);
511
512
		$fields = FieldList::create(TabSet::create('Root', $content));
513
514
		$this->extend('updateCMSFields', $fields);
515
516
		return $fields;
517
	}
518
519
	/**
520
	 * Get title for current file status
521
	 *
522
	 * @return string
523
	 */
524
	public function getStatusTitle() {
525
        $statusTitle = '';
526
        if ($this->isOnDraftOnly()) {
527
            $statusTitle = _t('File.DRAFT', 'Draft');
528
        } elseif ($this->isModifiedOnDraft()) {
529
            $statusTitle = _t('File.MODIFIED', 'Modified');
530
        }
531
        return $statusTitle;
532
	}
533
534
	/**
535
	 * Returns a category based on the file extension.
536
	 * This can be useful when grouping files by type,
537
	 * showing icons on filelinks, etc.
538
	 * Possible group values are: "audio", "mov", "zip", "image".
539
	 *
540
	 * @param string $ext Extension to check
541
	 * @return string
542
	 */
543
	public static function get_app_category($ext) {
544
		$ext = strtolower($ext);
545
		foreach(static::config()->app_categories as $category => $exts) {
546
			if(in_array($ext, $exts)) return $category;
547
		}
548
		return false;
549
	}
550
551
	/**
552
	 * For a category or list of categories, get the list of file extensions
553
	 *
554
	 * @param array|string $categories List of categories, or single category
555
	 * @return array
556
	 */
557
	public static function get_category_extensions($categories) {
558
		if(empty($categories)) {
559
			return array();
560
	}
561
562
		// Fix arguments into a single array
563
		if(!is_array($categories)) {
564
			$categories = array($categories);
565
		} elseif(count($categories) === 1 && is_array(reset($categories))) {
566
			$categories = reset($categories);
567
		}
568
569
		// Check configured categories
570
		$appCategories = self::config()->app_categories;
0 ignored issues
show
Documentation introduced by
The property app_categories 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...
571
572
		// Merge all categories into list of extensions
573
		$extensions = array();
574
		foreach(array_filter($categories) as $category) {
575
			if(isset($appCategories[$category])) {
576
				$extensions = array_merge($extensions, $appCategories[$category]);
577
			} else {
578
				throw new InvalidArgumentException("Unknown file category: $category");
579
			}
580
		}
581
		$extensions = array_unique($extensions);
582
		sort($extensions);
583
		return $extensions;
584
	}
585
586
	/**
587
	 * Returns a category based on the file extension.
588
	 *
589
	 * @return string
590
	 */
591
	public function appCategory() {
592
		return self::get_app_category($this->getExtension());
593
	}
594
595
596
	/**
597
	 * Should be called after the file was uploaded
598
	 */
599
	public function onAfterUpload() {
600
		$this->extend('onAfterUpload');
601
	}
602
603
	/**
604
	 * Make sure the file has a name
605
	 */
606
	protected function onBeforeWrite() {
607
		// Set default owner
608
		if(!$this->isInDB() && !$this->OwnerID) {
609
			$this->OwnerID = Member::currentUserID();
610
		}
611
612
		// Set default name
613
		if(!$this->getField('Name')) {
614
			$this->Name ="new-" . strtolower($this->class);
615
	}
616
617
		// Propegate changes to the AssetStore and update the DBFile field
618
		$this->updateFilesystem();
619
620
		parent::onBeforeWrite();
621
	}
622
623
	/**
624
	 * This will check if the parent record and/or name do not match the name on the underlying
625
	 * DBFile record, and if so, copy this file to the new location, and update the record to
626
	 * point to this new file.
627
	 *
628
	 * This method will update the File {@see DBFile} field value on success, so it must be called
629
	 * before writing to the database
630
	 *
631
	 * @return bool True if changed
632
	 */
633
	public function updateFilesystem() {
634
		if(!$this->config()->update_filesystem) {
635
			return false;
636
		}
637
638
		// Check the file exists
639
		if(!$this->File->exists()) {
640
			return false;
641
				}
642
643
		// Avoid moving files on live; Rely on this being done on stage prior to publish.
644
		if(Versioned::get_stage() !== Versioned::DRAFT) {
645
			return false;
646
			}
647
648
		// Check path updated record will point to
649
		// If no changes necessary, skip
650
		$pathBefore = $this->File->getFilename();
651
		$pathAfter = $this->generateFilename();
652
		if($pathAfter === $pathBefore) {
653
			return false;
654
		}
655
656
		// Copy record to new location via stream
657
		$stream = $this->File->getStream();
658
		$this->File->setFromStream($stream, $pathAfter);
0 ignored issues
show
Bug introduced by
It seems like $stream defined by $this->File->getStream() on line 657 can also be of type null; however, SilverStripe\Assets\Stor...DBFile::setFromStream() does only seem to accept resource, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
659
		return true;
660
	}
661
662
	/**
663
	 * Collate selected descendants of this page.
664
	 * $condition will be evaluated on each descendant, and if it is succeeds, that item will be added
665
	 * to the $collator array.
666
	 *
667
	 * @param string $condition The PHP condition to be evaluated.  The page will be called $item
668
	 * @param array $collator An array, passed by reference, to collect all of the matching descendants.
669
	 * @return true|null
670
	 */
671
	public function collateDescendants($condition, &$collator) {
672
		if($children = $this->Children()) {
673
			foreach($children as $item) {
674
				/** @var File $item */
675
				if(!$condition || eval("return $condition;")) {
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
676
					$collator[] = $item;
677
				}
678
				$item->collateDescendants($condition, $collator);
679
			}
680
			return true;
681
		}
682
		return null;
683
	}
684
685
	/**
686
	 * Setter function for Name. Automatically sets a default title,
687
	 * and removes characters that might be invalid on the filesystem.
688
	 * Also adds a suffix to the name if the filename already exists
689
	 * on the filesystem, and is associated to a different {@link File} database record
690
	 * in the same folder. This means "myfile.jpg" might become "myfile-1.jpg".
691
	 *
692
	 * Does not change the filesystem itself, please use {@link write()} for this.
693
	 *
694
	 * @param string $name
695
	 * @return $this
696
	 */
697
	public function setName($name) {
698
		$oldName = $this->Name;
699
700
		// It can't be blank, default to Title
701
		if(!$name) {
702
			$name = $this->Title;
703
		}
704
705
		// Fix illegal characters
706
		$filter = FileNameFilter::create();
707
		$name = $filter->filter($name);
708
709
		// We might have just turned it blank, so check again.
710
		if(!$name) {
711
			$name = 'new-folder';
712
		}
713
714
		// If it's changed, check for duplicates
715
		if($oldName && $oldName != $name) {
716
			$base = pathinfo($name, PATHINFO_FILENAME);
717
			$ext = self::get_file_extension($name);
718
			$suffix = 1;
719
720
			while(File::get()->filter(array(
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
721
					'Name' => $name,
722
					'ParentID' => (int) $this->ParentID
723
				))->exclude(array(
724
					'ID' => $this->ID
725
				))->first()
726
			) {
727
				$suffix++;
728
				$name = "$base-$suffix.$ext";
729
			}
730
		}
731
732
		// Update actual field value
733
		$this->setField('Name', $name);
734
735
		// Update title
736
		if(!$this->Title) {
737
			$this->Title = str_replace(array('-','_'),' ', preg_replace('/\.[^.]+$/', '', $name));
738
		}
739
740
		return $this;
741
	}
742
743
	/**
744
	 * Gets the URL of this file
745
	 *
746
	 * @return string
747
	 */
748
	public function getAbsoluteURL() {
749
		$url = $this->getURL();
750
		if($url) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $url of type string|null 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...
751
			return Director::absoluteURL($url);
752
		}
753
		return null;
754
	}
755
756
	/**
757
	 * Gets the URL of this file
758
	 *
759
	 * @uses Director::baseURL()
760
	 * @param bool $grant Ensures that the url for any protected assets is granted for the current user.
761
	 * @return string
762
	 */
763
	public function getURL($grant = true) {
764
		if($this->File->exists()) {
765
			return $this->File->getURL($grant);
766
		}
767
		return null;
768
	}
769
770
	/**
771
	 * Get URL, but without resampling.
772
	 *
773
	 * @param bool $grant Ensures that the url for any protected assets is granted for the current user.
774
	 * @return string
775
	 */
776
	public function getSourceURL($grant = true) {
777
		if($this->File->exists()) {
778
			return $this->File->getSourceURL($grant);
779
		}
780
		return null;
781
	}
782
783
	/**
784
	 * @todo Coupling with cms module, remove this method.
785
	 *
786
	 * @return string
787
	 */
788
	public function DeleteLink() {
789
		return Controller::join_links(
790
		    Director::absoluteBaseURL(),
791
            AdminRootController::admin_url(),
792
            "assets/removefile/",
793
            $this->ID
794
        );
795
	}
796
797
	/**
798
	 * Get expected value of Filename tuple value. Will be used to trigger
799
	 * a file move on draft stage.
800
	 *
801
	 * @return string
802
	 */
803
	public function generateFilename() {
804
		// Check if this file is nested within a folder
805
		$parent = $this->Parent();
806
		if($parent && $parent->exists()) {
807
			return $this->join_paths($parent->getFilename(), $this->Name);
808
		}
809
		return $this->Name;
810
	}
811
812
	/**
813
	 * Ensure that parent folders are published before this one is published
814
	 *
815
	 * @todo Solve this via triggered publishing / ownership in the future
816
	 */
817
	public function onBeforePublish() {
818
		// Relies on Parent() returning the stage record
819
		$parent = $this->Parent();
820
		if($parent && $parent->exists()) {
821
			$parent->publishRecursive();
822
		}
823
	}
824
825
	/**
826
	 * Update the ParentID and Name for the given filename.
827
	 *
828
	 * On save, the underlying DBFile record will move the underlying file to this location.
829
	 * Thus it will not update the underlying Filename value until this is done.
830
	 *
831
	 * @param string $filename
832
	 * @return $this
833
	 */
834
	public function setFilename($filename) {
835
		// Check existing folder path
836
		$folder = '';
837
		$parent = $this->Parent();
838
		if($parent && $parent->exists()) {
839
			$folder = $parent->Filename;
0 ignored issues
show
Documentation introduced by
The property Filename does not exist on object<SilverStripe\Assets\File>. 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...
840
	}
841
842
		// Detect change in foldername
843
		$newFolder = ltrim(dirname(trim($filename, '/')), '.');
844
		if($folder !== $newFolder) {
845
			if(!$newFolder) {
846
				$this->ParentID = 0;
847
		} else {
848
				$parent = Folder::find_or_make($newFolder);
849
				$this->ParentID = $parent->ID;
850
		}
851
	}
852
853
		// Update base name
854
		$this->Name = basename($filename);
855
		return $this;
856
	}
857
858
	/**
859
	 * Returns the file extension
860
	 *
861
	 * @return string
862
	 */
863
	public function getExtension() {
864
		return self::get_file_extension($this->Name);
865
	}
866
867
	/**
868
	 * Gets the extension of a filepath or filename,
869
	 * by stripping away everything before the last "dot".
870
	 * Caution: Only returns the last extension in "double-barrelled"
871
	 * extensions (e.g. "gz" for "tar.gz").
872
	 *
873
	 * Examples:
874
	 * - "myfile" returns ""
875
	 * - "myfile.txt" returns "txt"
876
	 * - "myfile.tar.gz" returns "gz"
877
	 *
878
	 * @param string $filename
879
	 * @return string
880
	 */
881
	public static function get_file_extension($filename) {
882
		return pathinfo($filename, PATHINFO_EXTENSION);
883
	}
884
885
	/**
886
	 * Given an extension, determine the icon that should be used
887
	 *
888
	 * @param string $extension
889
	 * @return string Icon filename relative to base url
890
	 */
891
	public static function get_icon_for_extension($extension) {
892
		$extension = strtolower($extension);
893
894
		// Check if exact extension has an icon
895
		if(!file_exists(FRAMEWORK_PATH ."/client/dist/images/app_icons/{$extension}_32.png")) {
896
			$extension = static::get_app_category($extension);
897
898
			// Fallback to category specific icon
899
			if(!file_exists(FRAMEWORK_PATH ."/client/dist/images/app_icons/{$extension}_32.png")) {
900
				$extension ="generic";
901
			}
902
		}
903
904
		return FRAMEWORK_DIR ."/client/dist/images/app_icons/{$extension}_32.png";
905
	}
906
907
	/**
908
	 * Return the type of file for the given extension
909
	 * on the current file name.
910
	 *
911
	 * @return string
912
	 */
913
	public function getFileType() {
914
		return self::get_file_type($this->getFilename());
915
	}
916
917
	/**
918
	 * Get descriptive type of file based on filename
919
	 *
920
	 * @param string $filename
921
	 * @return string Description of file
922
	 */
923
	public static function get_file_type($filename) {
924
		$types = array(
925
			'gif' => _t('File.GifType', 'GIF image - good for diagrams'),
926
			'jpg' => _t('File.JpgType', 'JPEG image - good for photos'),
927
			'jpeg' => _t('File.JpgType', 'JPEG image - good for photos'),
928
			'png' => _t('File.PngType', 'PNG image - good general-purpose format'),
929
			'ico' => _t('File.IcoType', 'Icon image'),
930
			'tiff' => _t('File.TiffType', 'Tagged image format'),
931
			'doc' => _t('File.DocType', 'Word document'),
932
			'xls' => _t('File.XlsType', 'Excel spreadsheet'),
933
			'zip' => _t('File.ZipType', 'ZIP compressed file'),
934
			'gz' => _t('File.GzType', 'GZIP compressed file'),
935
			'dmg' => _t('File.DmgType', 'Apple disk image'),
936
			'pdf' => _t('File.PdfType', 'Adobe Acrobat PDF file'),
937
			'mp3' => _t('File.Mp3Type', 'MP3 audio file'),
938
			'wav' => _t('File.WavType', 'WAV audo file'),
939
			'avi' => _t('File.AviType', 'AVI video file'),
940
			'mpg' => _t('File.MpgType', 'MPEG video file'),
941
			'mpeg' => _t('File.MpgType', 'MPEG video file'),
942
			'js' => _t('File.JsType', 'Javascript file'),
943
			'css' => _t('File.CssType', 'CSS file'),
944
			'html' => _t('File.HtmlType', 'HTML file'),
945
			'htm' => _t('File.HtmlType', 'HTML file')
946
		);
947
948
		// Get extension
949
		$extension = strtolower(self::get_file_extension($filename));
950
		return isset($types[$extension]) ? $types[$extension] : 'unknown';
951
	}
952
953
	/**
954
	 * Returns the size of the file type in an appropriate format.
955
	 *
956
	 * @return string|false String value, or false if doesn't exist
957
	 */
958
	public function getSize() {
959
		$size = $this->getAbsoluteSize();
960
		if($size) {
961
			return static::format_size($size);
962
	}
963
		return false;
964
	}
965
966
	/**
967
	 * Formats a file size (eg: (int)42 becomes string '42 bytes')
968
	 *
969
	 * @todo unit tests
970
	 *
971
	 * @param int $size
972
	 * @return string
973
	 */
974
	public static function format_size($size) {
975
		if($size < 1024) {
976
			return $size . ' bytes';
977
		}
978
		if($size < 1024*10) {
979
			return (round($size/1024*10)/10). ' KB';
980
		}
981
		if($size < 1024*1024) {
982
			return round($size/1024) . ' KB';
983
		}
984
		if($size < 1024*1024*10) {
985
			return (round(($size/1024)/1024*10)/10) . ' MB';
986
		}
987
		if($size < 1024*1024*1024) {
988
			return round(($size/1024)/1024) . ' MB';
989
		}
990
		return round($size/(1024*1024*1024)*10)/10 . ' GB';
991
	}
992
993
	/**
994
	 * Convert a php.ini value (eg: 512M) to bytes
995
	 *
996
	 * @todo unit tests
997
	 *
998
	 * @param string $iniValue
999
	 * @return int
1000
	 */
1001
	public static function ini2bytes($iniValue) {
1002
		switch(strtolower(substr(trim($iniValue), -1))) {
1003
			case 'g':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
1004
				$iniValue *= 1024;
1005
			case 'm':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
1006
				$iniValue *= 1024;
1007
			case 'k':
1008
				$iniValue *= 1024;
1009
		}
1010
		return $iniValue;
1011
	}
1012
1013
	/**
1014
	 * Return file size in bytes.
1015
	 *
1016
	 * @return int
1017
	 */
1018
	public function getAbsoluteSize(){
1019
		return $this->File->getAbsoluteSize();
1020
		}
1021
1022
	public function validate() {
1023
		$result = ValidationResult::create();
1024
		$this->File->validate($result, $this->Name);
1025
		$this->extend('validate', $result);
1026
		return $result;
1027
	}
1028
1029
	/**
1030
	 * Maps a {@link File} subclass to a specific extension.
1031
	 * By default, files with common image extensions will be created
1032
	 * as {@link Image} instead of {@link File} when using
1033
	 * {@link Folder::constructChild}, {@link Folder::addUploadToFolder}),
1034
	 * and the {@link Upload} class (either directly or through {@link FileField}).
1035
	 * For manually instanciated files please use this mapping getter.
1036
	 *
1037
	 * Caution: Changes to mapping doesn't apply to existing file records in the database.
1038
	 * Also doesn't hook into {@link Object::getCustomClass()}.
1039
	 *
1040
	 * @param String File extension, without dot prefix. Use an asterisk ('*')
1041
	 * to specify a generic fallback if no mapping is found for an extension.
1042
	 * @return String Classname for a subclass of {@link File}
1043
	 */
1044
	public static function get_class_for_file_extension($ext) {
1045
		$map = array_change_key_case(self::config()->class_for_file_extension, CASE_LOWER);
1046
		return (array_key_exists(strtolower($ext), $map)) ? $map[strtolower($ext)] : $map['*'];
1047
	}
1048
1049
	/**
1050
	 * See {@link get_class_for_file_extension()}.
1051
	 *
1052
	 * @param String|array
1053
	 * @param String
1054
	 */
1055
	public static function set_class_for_file_extension($exts, $class) {
1056
		if(!is_array($exts)) {
1057
			$exts = array($exts);
1058
		}
1059
		foreach($exts as $ext) {
1060
			if(!is_subclass_of($class, 'SilverStripe\\Assets\\File')) {
1061
				throw new InvalidArgumentException(
1062
					sprintf('Class "%s" (for extension "%s") is not a valid subclass of File', $class, $ext)
1063
				);
1064
			}
1065
			self::config()->class_for_file_extension = array($ext => $class);
0 ignored issues
show
Documentation introduced by
The property class_for_file_extension 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...
1066
		}
1067
	}
1068
1069
	public function getMetaData() {
1070
		if (!$this->File->exists()) {
1071
			return null;
1072
		}
1073
			return $this->File->getMetaData();
1074
		}
1075
1076
	public function getMimeType() {
1077
		if(!$this->File->exists()) {
1078
			return null;
1079
		}
1080
			return $this->File->getMimeType();
1081
		}
1082
1083
	public function getStream() {
1084
		if(!$this->File->exists()) {
1085
			return null;
1086
		}
1087
		return $this->File->getStream();
1088
	}
1089
1090
	public function getString() {
1091
		if(!$this->File->exists()) {
1092
			return null;
1093
		}
1094
		return $this->File->getString();
1095
	}
1096
1097
	public function setFromLocalFile($path, $filename = null, $hash = null, $variant = null, $config = array()) {
1098
		$result = $this->File->setFromLocalFile($path, $filename, $hash, $variant, $config);
1099
1100
		// Update File record to name of the uploaded asset
1101
		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...
1102
			$this->setFilename($result['Filename']);
1103
		}
1104
		return $result;
1105
	}
1106
1107
	public function setFromStream($stream, $filename, $hash = null, $variant = null, $config = array()) {
1108
		$result = $this->File->setFromStream($stream, $filename, $hash, $variant, $config);
1109
1110
		// Update File record to name of the uploaded asset
1111
		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...
1112
			$this->setFilename($result['Filename']);
1113
		}
1114
		return $result;
1115
	}
1116
1117
	public function setFromString($data, $filename, $hash = null, $variant = null, $config = array()) {
1118
		$result = $this->File->setFromString($data, $filename, $hash, $variant, $config);
1119
1120
		// Update File record to name of the uploaded asset
1121
		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...
1122
			$this->setFilename($result['Filename']);
1123
		}
1124
		return $result;
1125
	}
1126
1127
	public function getIsImage() {
1128
		return false;
1129
	}
1130
1131
	public function getFilename() {
1132
		return $this->File->Filename;
1133
	}
1134
1135
	public function getHash() {
1136
		return $this->File->Hash;
1137
	}
1138
1139
	public function getVariant() {
1140
		return $this->File->Variant;
1141
	}
1142
1143
	/**
1144
	 * Return a html5 tag of the appropriate for this file (normally img or a)
1145
	 *
1146
	 * @return string
1147
	 */
1148
	public function forTemplate() {
1149
		return $this->getTag() ?: '';
1150
	}
1151
1152
	/**
1153
	 * Return a html5 tag of the appropriate for this file (normally img or a)
1154
	 *
1155
	 * @return string
1156
	 */
1157
	public function getTag() {
1158
		$template = $this->File->getFrontendTemplate();
1159
		if(empty($template)) {
1160
			return '';
1161
		}
1162
		return (string)$this->renderWith($template);
1163
	}
1164
1165
	public function requireDefaultRecords() {
1166
		parent::requireDefaultRecords();
1167
1168
		// Check if old file records should be migrated
1169
		if(!$this->config()->migrate_legacy_file) {
1170
			return;
1171
		}
1172
1173
		$migrated = FileMigrationHelper::singleton()->run();
1174
		if($migrated) {
1175
			DB::alteration_message("{$migrated} File DataObjects upgraded","changed");
1176
		}
1177
	}
1178
1179
	/**
1180
	 * Joins one or more segments together to build a Filename identifier.
1181
	 *
1182
	 * Note that the result will not have a leading slash, and should not be used
1183
	 * with local file paths.
1184
	 *
1185
	 * @param string $part,... Parts
0 ignored issues
show
Bug introduced by
There is no parameter named $part,.... Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1186
	 * @return string
1187
	 */
1188
	public static function join_paths($part = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $part 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...
1189
		$args = func_get_args();
1190
		if(count($args) === 1 && is_array($args[0])) {
1191
			$args = $args[0];
1192
		}
1193
1194
		$parts = array();
1195
		foreach($args as $arg) {
1196
			$part = trim($arg, ' \\/');
1197
			if($part) {
1198
				$parts[] = $part;
1199
			}
1200
		}
1201
1202
		return implode('/', $parts);
1203
	}
1204
1205
	public function deleteFile() {
1206
		return $this->File->deleteFile();
1207
	}
1208
1209
	public function getVisibility() {
1210
		return $this->File->getVisibility();
1211
	}
1212
1213
	public function publishFile() {
1214
		$this->File->publishFile();
1215
	}
1216
1217
	public function protectFile() {
1218
		$this->File->protectFile();
1219
	}
1220
1221
	public function grantFile() {
1222
		$this->File->grantFile();
1223
	}
1224
1225
	public function revokeFile() {
1226
		$this->File->revokeFile();
1227
	}
1228
1229
	public function canViewFile() {
1230
		return $this->File->canViewFile();
1231
	}
1232
1233
	public function CMSEditLink() {
1234
		$link = null;
1235
		$this->extend('updateCMSEditLink', $link);
1236
		return $link;
1237
	}
1238
1239
	public function PreviewLink($action = null) {
1240
		// Since AbsoluteURL can whitelist protected assets,
1241
		// do permission check first
1242
		if (!$this->canView()) {
1243
			return null;
1244
		}
1245
		$link = $this->getIcon();
1246
		$this->extend('updatePreviewLink', $link, $action);
1247
		return $link;
1248
	}
1249
}
1250