Completed
Push — master ( 1049b7...e6ae53 )
by Damian
09:20
created

File::onBeforeWrite()   C

Complexity

Conditions 9
Paths 64

Size

Total Lines 70
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 35
nc 64
nop 0
dl 0
loc 70
rs 6.1585
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Core\Injector\Injector;
14
use SilverStripe\Dev\Deprecation;
15
use SilverStripe\Forms\DatetimeField;
16
use SilverStripe\Forms\FieldList;
17
use SilverStripe\Forms\HeaderField;
18
use SilverStripe\Forms\HiddenField;
19
use SilverStripe\Forms\Tab;
20
use SilverStripe\Forms\TabSet;
21
use SilverStripe\Forms\LiteralField;
22
use SilverStripe\Forms\ReadonlyField;
23
use SilverStripe\Forms\TextField;
24
use SilverStripe\ORM\DataObject;
25
use SilverStripe\ORM\DB;
26
use SilverStripe\ORM\Hierarchy\Hierarchy;
27
use SilverStripe\ORM\ValidationResult;
28
use SilverStripe\ORM\Versioning\Versioned;
29
use SilverStripe\Security\Member;
30
use SilverStripe\Security\Permission;
31
use SilverStripe\View\Parsers\ShortcodeHandler;
32
use SilverStripe\View\Parsers\ShortcodeParser;
33
use InvalidArgumentException;
34
35
/**
36
 * This class handles the representation of a file on the filesystem within the framework.
37
 * Most of the methods also handle the {@link Folder} subclass.
38
 *
39
 * Note: The files are stored in the assets/ directory, but SilverStripe
40
 * looks at the db object to gather information about a file such as URL
41
 * It then uses this for all processing functions (like image manipulation).
42
 *
43
 * <b>Security</b>
44
 *
45
 * Caution: It is recommended to disable any script execution in the "assets/"
46
 * directory in the webserver configuration, to reduce the risk of exploits.
47
 * See http://doc.silverstripe.org/secure-development#filesystem
48
 *
49
 * <b>Asset storage</b>
50
 *
51
 * As asset storage is configured separately to any File DataObject records, this class
52
 * does not make any assumptions about how these records are saved. They could be on
53
 * a local filesystem, remote filesystem, or a virtual record container (such as in local memory).
54
 *
55
 * The File dataobject simply represents an externally facing view of shared resources
56
 * within this asset store.
57
 *
58
 * Internally individual files are referenced by a"Filename" parameter, which represents a File, extension,
59
 * and is optionally prefixed by a list of custom directories. This path is root-agnostic, so it does not
60
 * automatically have a direct url mapping (even to the site's base directory).
61
 *
62
 * Additionally, individual files may have several versions distinguished by sha1 hash,
63
 * of which a File DataObject can point to a single one. Files can also be distinguished by
64
 * variants, which may be resized images or format-shifted documents.
65
 *
66
 * <b>Properties</b>
67
 *
68
 * - "Title": Optional title of the file (for display purposes only).
69
 *   Defaults to "Name". Note that the Title field of Folder (subclass of File)
70
 *   is linked to Name, so Name and Title will always be the same.
71
 * -"File": Physical asset backing this DB record. This is a composite DB field with
72
 *   its own list of properties. {@see DBFile} for more information
73
 * - "Content": Typically unused, but handy for a textual representation of
74
 *   files, e.g. for fulltext indexing of PDF documents.
75
 * - "ParentID": Points to a {@link Folder} record. Should be in sync with
76
 *   "Filename". A ParentID=0 value points to the "assets/" folder, not the webroot.
77
 * -"ShowInSearch": True if this file is searchable
78
 *
79
 * @property string $Name Basename of the file
80
 * @property string $Title Title of the file
81
 * @property DBFile $File asset stored behind this File record
82
 * @property string $Content
83
 * @property string $ShowInSearch Boolean that indicates if file is shown in search. Doesn't apply to Folders
84
 * @property int $ParentID ID of parent File/Folder
85
 * @property int $OwnerID ID of Member who owns the file
86
 *
87
 * @method File Parent() Returns parent File
88
 * @method Member Owner() Returns Member object of file owner.
89
 *
90
 * @mixin Hierarchy
91
 * @mixin Versioned
92
 */
93
class File extends DataObject implements ShortcodeHandler, AssetContainer, Thumbnail, CMSPreviewable
94
{
95
96
    use ImageManipulation;
97
98
    private static $default_sort = "\"Name\"";
99
100
    /**
101
     * @config
102
     * @var string
103
     */
104
    private static $singular_name = "File";
105
106
    private static $plural_name = "Files";
107
108
    /**
109
     * Permissions necessary to view files outside of the live stage (e.g. archive / draft stage).
110
     *
111
     * @config
112
     * @var array
113
     */
114
    private static $non_live_permissions = array('CMS_ACCESS_LeftAndMain', 'CMS_ACCESS_AssetAdmin', 'VIEW_DRAFT_CONTENT');
115
116
    private static $db = array(
117
        "Name" => "Varchar(255)",
118
        "Title" => "Varchar(255)",
119
        "File" => "DBFile",
120
        // Only applies to files, doesn't inherit for folder
121
        'ShowInSearch' => 'Boolean(1)',
122
    );
123
124
    private static $has_one = array(
125
        "Parent" => "SilverStripe\\Assets\\File",
126
        "Owner" => "SilverStripe\\Security\\Member"
127
    );
128
129
    private static $defaults = array(
130
        "ShowInSearch" => 1,
131
    );
132
133
    private static $extensions = array(
134
        "SilverStripe\\ORM\\Hierarchy\\Hierarchy",
135
        "SilverStripe\\ORM\\Versioning\\Versioned"
136
    );
137
138
    private static $casting = array (
139
        'TreeTitle' => 'HTMLFragment'
140
    );
141
142
    private static $table_name = 'File';
143
144
    /**
145
     * @config
146
     * @var array List of allowed file extensions, enforced through {@link validate()}.
147
     *
148
     * Note: if you modify this, you should also change a configuration file in the assets directory.
149
     * Otherwise, the files will be able to be uploaded but they won't be able to be served by the
150
     * webserver.
151
     *
152
     *  - If you are running Apache you will need to change assets/.htaccess
153
     *  - If you are running IIS you will need to change assets/web.config
154
     *
155
     * Instructions for the change you need to make are included in a comment in the config file.
156
     */
157
    private static $allowed_extensions = array(
158
        '', 'ace', 'arc', 'arj', 'asf', 'au', 'avi', 'bmp', 'bz2', 'cab', 'cda', 'css', 'csv', 'dmg', 'doc',
159
        'docx', 'dotx', 'dotm', 'flv', 'gif', 'gpx', 'gz', 'hqx', 'ico', 'jar', 'jpeg', 'jpg', 'js', 'kml',
160
        'm4a', 'm4v', 'mid', 'midi', 'mkv', 'mov', 'mp3', 'mp4', 'mpa', 'mpeg', 'mpg', 'ogg', 'ogv', 'pages',
161
        'pcx', 'pdf', 'png', 'pps', 'ppt', 'pptx', 'potx', 'potm', 'ra', 'ram', 'rm', 'rtf', 'sit', 'sitx',
162
        'tar', 'tgz', 'tif', 'tiff', 'txt', 'wav', 'webm', 'wma', 'wmv', 'xls', 'xlsx', 'xltx', 'xltm', 'zip',
163
        'zipx',
164
    );
165
166
    /**
167
     * @config
168
     * @var array Category identifiers mapped to commonly used extensions.
169
     */
170
    private static $app_categories = array(
171
        'archive' => array(
172
            'ace', 'arc', 'arj', 'bz', 'bz2', 'cab', 'dmg', 'gz', 'hqx', 'jar', 'rar', 'sit', 'sitx', 'tar', 'tgz',
173
            'zip', 'zipx',
174
        ),
175
        'audio' => array(
176
            'aif', 'aifc', 'aiff', 'apl', 'au', 'avr', 'cda', 'm4a', 'mid', 'midi', 'mp3', 'ogg', 'ra',
177
            'ram', 'rm', 'snd', 'wav', 'wma',
178
        ),
179
        'document' => array(
180
            'css', 'csv', 'doc', 'docx', 'dotm', 'dotx', 'htm', 'html', 'gpx', 'js', 'kml', 'pages', 'pdf',
181
            'potm', 'potx', 'pps', 'ppt', 'pptx', 'rtf', 'txt', 'xhtml', 'xls', 'xlsx', 'xltm', 'xltx', 'xml',
182
        ),
183
        'image' => array(
184
            'alpha', 'als', 'bmp', 'cel', 'gif', 'ico', 'icon', 'jpeg', 'jpg', 'pcx', 'png', 'ps', 'tif', 'tiff',
185
        ),
186
        'image/supported' => array(
187
            'gif', 'jpeg', 'jpg', 'png'
188
        ),
189
        'flash' => array(
190
            'fla', 'swf'
191
        ),
192
        'video' => array(
193
            'asf', 'avi', 'flv', 'ifo', 'm1v', 'm2v', 'm4v', 'mkv', 'mov', 'mp2', 'mp4', 'mpa', 'mpe', 'mpeg',
194
            'mpg', 'ogv', 'qt', 'vob', 'webm', 'wmv',
195
        ),
196
    );
197
198
    /**
199
     * Map of file extensions to class type
200
     *
201
     * @config
202
     * @var
203
     */
204
    private static $class_for_file_extension = array(
205
        '*' => 'SilverStripe\\Assets\\File',
206
        'jpg' => 'SilverStripe\\Assets\\Image',
207
        'jpeg' => 'SilverStripe\\Assets\\Image',
208
        'png' => 'SilverStripe\\Assets\\Image',
209
        'gif' => 'SilverStripe\\Assets\\Image',
210
    );
211
212
    /**
213
     * @config
214
     * @var bool If this is true, then restrictions set in {@link $allowed_max_file_size} and
215
     * {@link $allowed_extensions} will be applied to users with admin privileges as
216
     * well.
217
     */
218
    private static $apply_restrictions_to_admin = true;
219
220
    /**
221
     * If enabled, legacy file dataobjects will be automatically imported into the APL
222
     *
223
     * @config
224
     * @var bool
225
     */
226
    private static $migrate_legacy_file = false;
227
228
    /**
229
     * @config
230
     * @var boolean
231
     */
232
    private static $update_filesystem = true;
233
234
    public static function get_shortcodes()
235
    {
236
        return 'file_link';
237
    }
238
239
    /**
240
     * Replace "[file_link id=n]" shortcode with an anchor tag or link to the file.
241
     *
242
     * @param array $arguments Arguments passed to the parser
243
     * @param string $content Raw shortcode
244
     * @param ShortcodeParser $parser Parser
245
     * @param string $shortcode Name of shortcode used to register this handler
246
     * @param array $extra Extra arguments
247
     * @return string Result of the handled shortcode
248
     */
249
    public static function handle_shortcode($arguments, $content, $parser, $shortcode, $extra = array())
250
    {
251
        // Find appropriate record, with fallback for error handlers
252
        $record = static::find_shortcode_record($arguments, $errorCode);
253
        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...
254
            $record = static::find_error_record($errorCode);
255
        }
256
        if (!$record) {
257
            return null; // There were no suitable matches at all.
258
        }
259
260
        // build the HTML tag
261
        if ($content) {
262
            // build some useful meta-data (file type and size) as data attributes
263
            $attrs = ' ';
264
            if ($record instanceof File) {
265
                foreach (array(
266
                    'class' => 'file',
267
                    'data-type' => $record->getExtension(),
268
                    'data-size' => $record->getSize()
269
                ) as $name => $value) {
270
                    $attrs .= sprintf('%s="%s" ', $name, $value);
271
                }
272
            }
273
274
            return sprintf('<a href="%s"%s>%s</a>', $record->Link(), rtrim($attrs), $parser->parse($content));
275
        } else {
276
            return $record->Link();
277
        }
278
    }
279
280
    /**
281
     * Find the record to use for a given shortcode.
282
     *
283
     * @param array $args Array of input shortcode arguments
284
     * @param int $errorCode If the file is not found, or is inaccessible, this will be assigned to a HTTP error code.
285
     * @return File|null The File DataObject, if it can be found.
286
     */
287
    public static function find_shortcode_record($args, &$errorCode = null)
288
    {
289
        // Validate shortcode
290
        if (!isset($args['id']) || !is_numeric($args['id'])) {
291
            return null;
292
        }
293
294
        // Check if the file is found
295
        /** @var File $file */
296
        $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...
297
        if (!$file) {
298
            $errorCode = 404;
299
            return null;
300
        }
301
302
        // Check if the file is viewable
303
        if (!$file->canView()) {
304
            $errorCode = 403;
305
            return null;
306
        }
307
308
        // Success
309
        return $file;
310
    }
311
312
    /**
313
     * Given a HTTP Error, find an appropriate substitute File or SiteTree data object instance.
314
     *
315
     * @param int $errorCode HTTP Error value
316
     * @return File|SiteTree File or SiteTree object to use for the given error
317
     */
318
    protected static function find_error_record($errorCode)
319
    {
320
        $result = static::singleton()->invokeWithExtensions('getErrorRecordFor', $errorCode);
321
        $result = array_filter($result);
322
        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...
323
            return reset($result);
324
        }
325
        return null;
326
    }
327
328
    /**
329
     * A file only exists if the file_exists() and is in the DB as a record
330
     *
331
     * Use $file->isInDB() to only check for a DB record
332
     * Use $file->File->exists() to only check if the asset exists
333
     *
334
     * @return bool
335
     */
336
    public function exists()
337
    {
338
        return parent::exists() && $this->File->exists();
339
    }
340
341
    /**
342
     * Find a File object by the given filename.
343
     *
344
     * @param string $filename Filename to search for, including any custom parent directories.
345
     * @return File
346
     */
347
    public static function find($filename)
348
    {
349
        // Split to folders and the actual filename, and traverse the structure.
350
        $parts = explode("/", $filename);
351
        $parentID = 0;
352
        /** @var File $item */
353
        $item = null;
354
        foreach ($parts as $part) {
355
            $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...
356
                'Name' => $part,
357
                'ParentID' => $parentID
358
            ))->first();
359
            if (!$item) {
360
                break;
361
            }
362
            $parentID = $item->ID;
363
        }
364
365
        return $item;
366
    }
367
368
    /**
369
     * Just an alias function to keep a consistent API with SiteTree
370
     *
371
     * @return string The link to the file
372
     */
373
    public function Link()
374
    {
375
        return $this->getURL();
376
    }
377
378
    /**
379
     * @deprecated 4.0
380
     */
381
    public function RelativeLink()
382
    {
383
        Deprecation::notice('4.0', 'Use getURL instead, as not all files will be relative to the site root.');
384
        return Director::makeRelative($this->getURL());
385
    }
386
387
    /**
388
     * Just an alias function to keep a consistent API with SiteTree
389
     *
390
     * @return string The absolute link to the file
391
     */
392
    public function AbsoluteLink()
393
    {
394
        return $this->getAbsoluteURL();
395
    }
396
397
    /**
398
     * @return string
399
     */
400
    public function getTreeTitle()
401
    {
402
        return Convert::raw2xml($this->Title);
403
    }
404
405
    /**
406
     * @param Member $member
407
     * @return bool
408
     */
409
    public function canView($member = null)
410
    {
411
        if (!$member) {
412
            $member = Member::currentUser();
413
        }
414
415
        $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...
416
        if ($result !== null) {
417
            return $result;
418
        }
419
420
        return true;
421
    }
422
423
    /**
424
     * Check if this file can be modified
425
     *
426
     * @param Member $member
427
     * @return boolean
428
     */
429
    public function canEdit($member = null)
430
    {
431
        if (!$member) {
432
            $member = Member::currentUser();
433
        }
434
435
        $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...
436
        if ($result !== null) {
437
            return $result;
438
        }
439
440
        return Permission::checkMember($member, array('CMS_ACCESS_AssetAdmin', 'CMS_ACCESS_LeftAndMain'));
441
    }
442
443
    /**
444
     * Check if a file can be created
445
     *
446
     * @param Member $member
447
     * @param array $context
448
     * @return boolean
449
     */
450
    public function canCreate($member = null, $context = array())
451
    {
452
        if (!$member) {
453
            $member = Member::currentUser();
454
        }
455
456
        $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...
457
        if ($result !== null) {
458
            return $result;
459
        }
460
461
        return $this->canEdit($member);
462
    }
463
464
    /**
465
     * Check if this file can be deleted
466
     *
467
     * @param Member $member
468
     * @return boolean
469
     */
470
    public function canDelete($member = null)
471
    {
472
        if (!$member) {
473
            $member = Member::currentUser();
474
        }
475
476
        $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...
477
        if ($result !== null) {
478
            return $result;
479
        }
480
481
        return $this->canEdit($member);
482
    }
483
484
    /**
485
     * Returns the fields to power the edit screen of files in the CMS.
486
     * You can modify this FieldList by subclassing folder, or by creating a {@link DataExtension}
487
     * and implementing updateCMSFields(FieldList $fields) on that extension.
488
     *
489
     * @return FieldList
490
     */
491
    public function getCMSFields()
492
    {
493
        $path = '/' . dirname($this->getFilename());
494
495
        $previewLink = Convert::raw2att($this->PreviewLink());
496
        $image = "<img src=\"{$previewLink}\" class=\"editor__thumbnail\" />";
497
498
        $statusTitle = $this->getStatusTitle();
499
        $statusFlag = ($statusTitle) ? "<span class=\"editor__status-flag\">{$statusTitle}</span>" : '';
500
501
        $content = Tab::create(
502
            'Main',
503
            HeaderField::create('TitleHeader', $this->Title, 1)
504
                ->addExtraClass('editor__heading'),
505
            LiteralField::create('StatusFlag', $statusFlag),
506
            LiteralField::create("IconFull", $image)
507
                ->addExtraClass('editor__file-preview'),
508
            TabSet::create(
509
                'Editor',
510
                Tab::create(
511
                    'Details',
512
                    TextField::create("Title", $this->fieldLabel('Title')),
513
                    TextField::create("Name", $this->fieldLabel('Filename')),
514
                    ReadonlyField::create(
515
                        "Path",
516
                        _t('AssetTableField.PATH', 'Path'),
517
                        (($path !== '/.') ? $path : '') . '/'
518
                    )
519
                ),
520
                Tab::create(
521
                    'Usage',
522
                    DatetimeField::create(
523
                        "Created",
524
                        _t('AssetTableField.CREATED', 'First uploaded')
525
                    )->setReadonly(true),
526
                    DatetimeField::create(
527
                        "LastEdited",
528
                        _t('AssetTableField.LASTEDIT', 'Last changed')
529
                    )->setReadonly(true)
530
                )
531
            ),
532
            HiddenField::create('ID', $this->ID)
533
        );
534
535
        $fields = FieldList::create(TabSet::create('Root', $content));
536
537
        $this->extend('updateCMSFields', $fields);
538
539
        return $fields;
540
    }
541
542
    /**
543
     * Get title for current file status
544
     *
545
     * @return string
546
     */
547
    public function getStatusTitle()
548
    {
549
        $statusTitle = '';
550
        if ($this->isOnDraftOnly()) {
551
            $statusTitle = _t('File.DRAFT', 'Draft');
552
        } elseif ($this->isModifiedOnDraft()) {
553
            $statusTitle = _t('File.MODIFIED', 'Modified');
554
        }
555
        return $statusTitle;
556
    }
557
558
    /**
559
     * Returns a category based on the file extension.
560
     * This can be useful when grouping files by type,
561
     * showing icons on filelinks, etc.
562
     * Possible group values are: "audio", "mov", "zip", "image".
563
     *
564
     * @param string $ext Extension to check
565
     * @return string
566
     */
567
    public static function get_app_category($ext)
568
    {
569
        $ext = strtolower($ext);
570
        foreach (static::config()->app_categories as $category => $exts) {
571
            if (in_array($ext, $exts)) {
572
                return $category;
573
            }
574
        }
575
        return false;
576
    }
577
578
    /**
579
     * For a category or list of categories, get the list of file extensions
580
     *
581
     * @param array|string $categories List of categories, or single category
582
     * @return array
583
     */
584
    public static function get_category_extensions($categories)
585
    {
586
        if (empty($categories)) {
587
            return array();
588
        }
589
590
        // Fix arguments into a single array
591
        if (!is_array($categories)) {
592
            $categories = array($categories);
593
        } elseif (count($categories) === 1 && is_array(reset($categories))) {
594
            $categories = reset($categories);
595
        }
596
597
        // Check configured categories
598
        $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...
599
600
        // Merge all categories into list of extensions
601
        $extensions = array();
602
        foreach (array_filter($categories) as $category) {
603
            if (isset($appCategories[$category])) {
604
                $extensions = array_merge($extensions, $appCategories[$category]);
605
            } else {
606
                throw new InvalidArgumentException("Unknown file category: $category");
607
            }
608
        }
609
        $extensions = array_unique($extensions);
610
        sort($extensions);
611
        return $extensions;
612
    }
613
614
    /**
615
     * Returns a category based on the file extension.
616
     *
617
     * @return string
618
     */
619
    public function appCategory()
620
    {
621
        return self::get_app_category($this->getExtension());
622
    }
623
624
625
    /**
626
     * Should be called after the file was uploaded
627
     */
628
    public function onAfterUpload()
629
    {
630
        $this->extend('onAfterUpload');
631
    }
632
633
    /**
634
     * Make sure the file has a name
635
     */
636
    protected function onBeforeWrite()
637
    {
638
        // Set default owner
639
        if (!$this->isInDB() && !$this->OwnerID) {
640
            $this->OwnerID = Member::currentUserID();
641
        }
642
643
        $name = $this->getField('Name');
644
        $title = $this->getField('Title');
645
646
        $changed = $this->isChanged('Name');
647
648
        // Name can't be blank, default to Title
649
        if (!$name) {
650
            $changed = true;
651
            $name = $title;
652
        }
653
654
        $filter = FileNameFilter::create();
655
        if ($name) {
656
            // Fix illegal characters
657
            $name = $filter->filter($name);
658
        } else {
659
            // Default to file name
660
            $changed = true;
661
            $name = $this->i18n_singular_name();
662
            $name = $filter->filter($name);
663
        }
664
665
        // Check for duplicates when the name has changed (or is set for the first time)
666
        if ($changed) {
667
            $nameGenerator = $this->getNameGenerator($name);
668
            // Defaults to returning the original filename on first iteration
669
            foreach ($nameGenerator as $newName) {
670
                // This logic is also used in the Folder subclass, but we're querying
671
                // for duplicates on the File base class here (including the Folder subclass).
672
673
                // TODO Add read lock to avoid other processes creating files with the same name
674
                // before this process has a chance to persist in the database.
675
                $existingFile = 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...
676
                    'Name' => $newName,
677
                    'ParentID' => (int) $this->ParentID
678
                ))->exclude(array(
679
                    'ID' => $this->ID
680
                ))->first();
681
                if (!$existingFile) {
682
                    $name = $newName;
683
                    break;
684
                }
685
            }
686
        }
687
688
        // Update actual field value
689
        $this->setField('Name', $name);
690
691
        // Update title
692
        if (!$title) {
693
            // Generate a readable title, dashes and underscores replaced by whitespace,
694
            // and any file extensions removed.
695
            $this->setField(
696
                'Title',
697
                str_replace(array('-','_'), ' ', preg_replace('/\.[^.]+$/', '', $name))
698
            );
699
        }
700
701
        // Propagate changes to the AssetStore and update the DBFile field
702
        $this->updateFilesystem();
703
704
        parent::onBeforeWrite();
705
    }
706
707
    /**
708
     * This will check if the parent record and/or name do not match the name on the underlying
709
     * DBFile record, and if so, copy this file to the new location, and update the record to
710
     * point to this new file.
711
     *
712
     * This method will update the File {@see DBFile} field value on success, so it must be called
713
     * before writing to the database
714
     *
715
     * @return bool True if changed
716
     */
717
    public function updateFilesystem()
718
    {
719
        if (!$this->config()->update_filesystem) {
720
            return false;
721
        }
722
723
        // Check the file exists
724
        if (!$this->File->exists()) {
725
            return false;
726
        }
727
728
        // Avoid moving files on live; Rely on this being done on stage prior to publish.
729
        if (Versioned::get_stage() !== Versioned::DRAFT) {
730
            return false;
731
        }
732
733
        // Check path updated record will point to
734
        // If no changes necessary, skip
735
        $pathBefore = $this->File->getFilename();
736
        $pathAfter = $this->generateFilename();
737
        if ($pathAfter === $pathBefore) {
738
            return false;
739
        }
740
741
        // Copy record to new location via stream
742
        $stream = $this->File->getStream();
743
        $this->File->setFromStream($stream, $pathAfter);
0 ignored issues
show
Bug introduced by
It seems like $stream defined by $this->File->getStream() on line 742 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...
744
        return true;
745
    }
746
747
    /**
748
     * Collate selected descendants of this page.
749
     * $condition will be evaluated on each descendant, and if it is succeeds, that item will be added
750
     * to the $collator array.
751
     *
752
     * @param string $condition The PHP condition to be evaluated.  The page will be called $item
753
     * @param array $collator An array, passed by reference, to collect all of the matching descendants.
754
     * @return true|null
755
     */
756
    public function collateDescendants($condition, &$collator)
757
    {
758
        if ($children = $this->Children()) {
759
            foreach ($children as $item) {
760
                /** @var File $item */
761
                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...
762
                    $collator[] = $item;
763
                }
764
                $item->collateDescendants($condition, $collator);
765
            }
766
            return true;
767
        }
768
        return null;
769
    }
770
771
    /**
772
     * Get an asset renamer for the given filename.
773
     *
774
     * @param string $filename Path name
775
     * @return AssetNameGenerator
776
     */
777
    protected function getNameGenerator($filename)
778
    {
779
        return Injector::inst()->createWithArgs('AssetNameGenerator', array($filename));
780
    }
781
782
    /**
783
     * Gets the URL of this file
784
     *
785
     * @return string
786
     */
787
    public function getAbsoluteURL()
788
    {
789
        $url = $this->getURL();
790
        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...
791
            return Director::absoluteURL($url);
792
        }
793
        return null;
794
    }
795
796
    /**
797
     * Gets the URL of this file
798
     *
799
     * @uses Director::baseURL()
800
     * @param bool $grant Ensures that the url for any protected assets is granted for the current user.
801
     * @return string
802
     */
803
    public function getURL($grant = true)
804
    {
805
        if ($this->File->exists()) {
806
            return $this->File->getURL($grant);
807
        }
808
        return null;
809
    }
810
811
    /**
812
     * Get URL, but without resampling.
813
     *
814
     * @param bool $grant Ensures that the url for any protected assets is granted for the current user.
815
     * @return string
816
     */
817
    public function getSourceURL($grant = true)
818
    {
819
        if ($this->File->exists()) {
820
            return $this->File->getSourceURL($grant);
821
        }
822
        return null;
823
    }
824
825
    /**
826
     * @todo Coupling with cms module, remove this method.
827
     *
828
     * @return string
829
     */
830
    public function DeleteLink()
831
    {
832
        return Controller::join_links(
833
            Director::absoluteBaseURL(),
834
            AdminRootController::admin_url(),
835
            "assets/removefile/",
836
            $this->ID
837
        );
838
    }
839
840
    /**
841
     * Get expected value of Filename tuple value. Will be used to trigger
842
     * a file move on draft stage.
843
     *
844
     * @return string
845
     */
846
    public function generateFilename()
847
    {
848
        // Check if this file is nested within a folder
849
        $parent = $this->Parent();
850
        if ($parent && $parent->exists()) {
851
            return $this->join_paths($parent->getFilename(), $this->Name);
852
        }
853
        return $this->Name;
854
    }
855
856
    /**
857
     * Ensure that parent folders are published before this one is published
858
     *
859
     * @todo Solve this via triggered publishing / ownership in the future
860
     */
861
    public function onBeforePublish()
862
    {
863
        // Publish all parents from the root up
864
        /** @var Folder $parent */
865
        foreach ($this->getAncestors()->reverse() as $parent) {
866
            $parent->publishSingle();
867
        }
868
    }
869
870
    /**
871
     * Update the ParentID and Name for the given filename.
872
     *
873
     * On save, the underlying DBFile record will move the underlying file to this location.
874
     * Thus it will not update the underlying Filename value until this is done.
875
     *
876
     * @param string $filename
877
     * @return $this
878
     */
879
    public function setFilename($filename)
880
    {
881
        // Check existing folder path
882
        $folder = '';
883
        $parent = $this->Parent();
884
        if ($parent && $parent->exists()) {
885
            $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...
886
        }
887
888
        // Detect change in foldername
889
        $newFolder = ltrim(dirname(trim($filename, '/')), '.');
890
        if ($folder !== $newFolder) {
891
            if (!$newFolder) {
892
                $this->ParentID = 0;
893
            } else {
894
                $parent = Folder::find_or_make($newFolder);
895
                $this->ParentID = $parent->ID;
896
            }
897
        }
898
899
        // Update base name
900
        $this->Name = basename($filename);
901
        return $this;
902
    }
903
904
    /**
905
     * Returns the file extension
906
     *
907
     * @return string
908
     */
909
    public function getExtension()
910
    {
911
        return self::get_file_extension($this->Name);
912
    }
913
914
    /**
915
     * Gets the extension of a filepath or filename,
916
     * by stripping away everything before the last "dot".
917
     * Caution: Only returns the last extension in "double-barrelled"
918
     * extensions (e.g. "gz" for "tar.gz").
919
     *
920
     * Examples:
921
     * - "myfile" returns ""
922
     * - "myfile.txt" returns "txt"
923
     * - "myfile.tar.gz" returns "gz"
924
     *
925
     * @param string $filename
926
     * @return string
927
     */
928
    public static function get_file_extension($filename)
929
    {
930
        return pathinfo($filename, PATHINFO_EXTENSION);
931
    }
932
933
    /**
934
     * Given an extension, determine the icon that should be used
935
     *
936
     * @param string $extension
937
     * @return string Icon filename relative to base url
938
     */
939
    public static function get_icon_for_extension($extension)
940
    {
941
        $extension = strtolower($extension);
942
943
        // Check if exact extension has an icon
944
        if (!file_exists(FRAMEWORK_PATH ."/client/dist/images/app_icons/{$extension}_92.png")) {
945
            $extension = static::get_app_category($extension);
946
947
            // Fallback to category specific icon
948
            if (!file_exists(FRAMEWORK_PATH ."/client/dist/images/app_icons/{$extension}_92.png")) {
949
                $extension ="generic";
950
            }
951
        }
952
953
        return FRAMEWORK_DIR ."/client/dist/images/app_icons/{$extension}_92.png";
954
    }
955
956
    /**
957
     * Return the type of file for the given extension
958
     * on the current file name.
959
     *
960
     * @return string
961
     */
962
    public function getFileType()
963
    {
964
        return self::get_file_type($this->getFilename());
965
    }
966
967
    /**
968
     * Get descriptive type of file based on filename
969
     *
970
     * @param string $filename
971
     * @return string Description of file
972
     */
973
    public static function get_file_type($filename)
974
    {
975
        $types = array(
976
            'gif' => _t('File.GifType', 'GIF image - good for diagrams'),
977
            'jpg' => _t('File.JpgType', 'JPEG image - good for photos'),
978
            'jpeg' => _t('File.JpgType', 'JPEG image - good for photos'),
979
            'png' => _t('File.PngType', 'PNG image - good general-purpose format'),
980
            'ico' => _t('File.IcoType', 'Icon image'),
981
            'tiff' => _t('File.TiffType', 'Tagged image format'),
982
            'doc' => _t('File.DocType', 'Word document'),
983
            'xls' => _t('File.XlsType', 'Excel spreadsheet'),
984
            'zip' => _t('File.ZipType', 'ZIP compressed file'),
985
            'gz' => _t('File.GzType', 'GZIP compressed file'),
986
            'dmg' => _t('File.DmgType', 'Apple disk image'),
987
            'pdf' => _t('File.PdfType', 'Adobe Acrobat PDF file'),
988
            'mp3' => _t('File.Mp3Type', 'MP3 audio file'),
989
            'wav' => _t('File.WavType', 'WAV audo file'),
990
            'avi' => _t('File.AviType', 'AVI video file'),
991
            'mpg' => _t('File.MpgType', 'MPEG video file'),
992
            'mpeg' => _t('File.MpgType', 'MPEG video file'),
993
            'js' => _t('File.JsType', 'Javascript file'),
994
            'css' => _t('File.CssType', 'CSS file'),
995
            'html' => _t('File.HtmlType', 'HTML file'),
996
            'htm' => _t('File.HtmlType', 'HTML file')
997
        );
998
999
        // Get extension
1000
        $extension = strtolower(self::get_file_extension($filename));
1001
        return isset($types[$extension]) ? $types[$extension] : 'unknown';
1002
    }
1003
1004
    /**
1005
     * Returns the size of the file type in an appropriate format.
1006
     *
1007
     * @return string|false String value, or false if doesn't exist
1008
     */
1009
    public function getSize()
1010
    {
1011
        $size = $this->getAbsoluteSize();
1012
        if ($size) {
1013
            return static::format_size($size);
1014
        }
1015
        return false;
1016
    }
1017
1018
    /**
1019
     * Formats a file size (eg: (int)42 becomes string '42 bytes')
1020
     *
1021
     * @param int $size
1022
     * @return string
1023
     */
1024
    public static function format_size($size)
1025
    {
1026
        if ($size < 1024) {
1027
            return $size . ' bytes';
1028
        }
1029
        if ($size < 1024*10) {
1030
            return (round($size/1024*10)/10). ' KB';
1031
        }
1032
        if ($size < 1024*1024) {
1033
            return round($size/1024) . ' KB';
1034
        }
1035
        if ($size < 1024*1024*10) {
1036
            return (round(($size/1024)/1024*10)/10) . ' MB';
1037
        }
1038
        if ($size < 1024*1024*1024) {
1039
            return round(($size/1024)/1024) . ' MB';
1040
        }
1041
        return round($size/(1024*1024*1024)*10)/10 . ' GB';
1042
    }
1043
1044
    /**
1045
     * Convert a php.ini value (eg: 512M) to bytes
1046
     *
1047
     * @param  string $iniValue
1048
     * @return int
1049
     */
1050
    public static function ini2bytes($iniValue)
1051
    {
1052
        $iniValues = str_split(trim($iniValue));
1053
        $unit = strtolower(array_pop($iniValues));
1054
        $quantity = (int) implode($iniValues);
1055
        switch ($unit) {
1056
            case 'g':
1057
                $quantity *= 1024;
1058
                // deliberate no break
1059
            case 'm':
1060
                $quantity *= 1024;
1061
                // deliberate no break
1062
            case 'k':
1063
                $quantity *= 1024;
1064
                // deliberate no break
1065
            default:
1066
                // no-op: pre-existing behaviour
1067
                break;
1068
        }
1069
        return $quantity;
1070
    }
1071
1072
    /**
1073
     * Return file size in bytes.
1074
     *
1075
     * @return int
1076
     */
1077
    public function getAbsoluteSize()
1078
    {
1079
        return $this->File->getAbsoluteSize();
1080
    }
1081
1082
    public function validate()
1083
    {
1084
        $result = ValidationResult::create();
1085
        $this->File->validate($result, $this->Name);
1086
        $this->extend('validate', $result);
1087
        return $result;
1088
    }
1089
1090
    /**
1091
     * Maps a {@link File} subclass to a specific extension.
1092
     * By default, files with common image extensions will be created
1093
     * as {@link Image} instead of {@link File} when using
1094
     * {@link Folder::constructChild}, {@link Folder::addUploadToFolder}),
1095
     * and the {@link Upload} class (either directly or through {@link FileField}).
1096
     * For manually instanciated files please use this mapping getter.
1097
     *
1098
     * Caution: Changes to mapping doesn't apply to existing file records in the database.
1099
     * Also doesn't hook into {@link Object::getCustomClass()}.
1100
     *
1101
     * @param String File extension, without dot prefix. Use an asterisk ('*')
1102
     * to specify a generic fallback if no mapping is found for an extension.
1103
     * @return String Classname for a subclass of {@link File}
1104
     */
1105
    public static function get_class_for_file_extension($ext)
1106
    {
1107
        $map = array_change_key_case(self::config()->class_for_file_extension, CASE_LOWER);
1108
        return (array_key_exists(strtolower($ext), $map)) ? $map[strtolower($ext)] : $map['*'];
1109
    }
1110
1111
    /**
1112
     * See {@link get_class_for_file_extension()}.
1113
     *
1114
     * @param String|array
1115
     * @param String
1116
     */
1117
    public static function set_class_for_file_extension($exts, $class)
1118
    {
1119
        if (!is_array($exts)) {
1120
            $exts = array($exts);
1121
        }
1122
        foreach ($exts as $ext) {
1123
            if (!is_subclass_of($class, 'SilverStripe\\Assets\\File')) {
1124
                throw new InvalidArgumentException(
1125
                    sprintf('Class "%s" (for extension "%s") is not a valid subclass of File', $class, $ext)
1126
                );
1127
            }
1128
            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...
1129
        }
1130
    }
1131
1132
    public function getMetaData()
1133
    {
1134
        if (!$this->File->exists()) {
1135
            return null;
1136
        }
1137
            return $this->File->getMetaData();
1138
    }
1139
1140
    public function getMimeType()
1141
    {
1142
        if (!$this->File->exists()) {
1143
            return null;
1144
        }
1145
            return $this->File->getMimeType();
1146
    }
1147
1148
    public function getStream()
1149
    {
1150
        if (!$this->File->exists()) {
1151
            return null;
1152
        }
1153
        return $this->File->getStream();
1154
    }
1155
1156
    public function getString()
1157
    {
1158
        if (!$this->File->exists()) {
1159
            return null;
1160
        }
1161
        return $this->File->getString();
1162
    }
1163
1164
    public function setFromLocalFile($path, $filename = null, $hash = null, $variant = null, $config = array())
1165
    {
1166
        $result = $this->File->setFromLocalFile($path, $filename, $hash, $variant, $config);
1167
1168
        // Update File record to name of the uploaded asset
1169
        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...
1170
            $this->setFilename($result['Filename']);
1171
        }
1172
        return $result;
1173
    }
1174
1175
    public function setFromStream($stream, $filename, $hash = null, $variant = null, $config = array())
1176
    {
1177
        $result = $this->File->setFromStream($stream, $filename, $hash, $variant, $config);
1178
1179
        // Update File record to name of the uploaded asset
1180
        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...
1181
            $this->setFilename($result['Filename']);
1182
        }
1183
        return $result;
1184
    }
1185
1186
    public function setFromString($data, $filename, $hash = null, $variant = null, $config = array())
1187
    {
1188
        $result = $this->File->setFromString($data, $filename, $hash, $variant, $config);
1189
1190
        // Update File record to name of the uploaded asset
1191
        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...
1192
            $this->setFilename($result['Filename']);
1193
        }
1194
        return $result;
1195
    }
1196
1197
    public function getIsImage()
1198
    {
1199
        return false;
1200
    }
1201
1202
    public function getFilename()
1203
    {
1204
        return $this->File->Filename;
1205
    }
1206
1207
    public function getHash()
1208
    {
1209
        return $this->File->Hash;
1210
    }
1211
1212
    public function getVariant()
1213
    {
1214
        return $this->File->Variant;
1215
    }
1216
1217
    /**
1218
     * Return a html5 tag of the appropriate for this file (normally img or a)
1219
     *
1220
     * @return string
1221
     */
1222
    public function forTemplate()
1223
    {
1224
        return $this->getTag() ?: '';
1225
    }
1226
1227
    /**
1228
     * Return a html5 tag of the appropriate for this file (normally img or a)
1229
     *
1230
     * @return string
1231
     */
1232
    public function getTag()
1233
    {
1234
        $template = $this->File->getFrontendTemplate();
1235
        if (empty($template)) {
1236
            return '';
1237
        }
1238
        return (string)$this->renderWith($template);
1239
    }
1240
1241
    public function requireDefaultRecords()
1242
    {
1243
        parent::requireDefaultRecords();
1244
1245
        // Check if old file records should be migrated
1246
        if (!$this->config()->migrate_legacy_file) {
1247
            return;
1248
        }
1249
1250
        $migrated = FileMigrationHelper::singleton()->run();
1251
        if ($migrated) {
1252
            DB::alteration_message("{$migrated} File DataObjects upgraded", "changed");
1253
        }
1254
    }
1255
1256
    /**
1257
     * Joins one or more segments together to build a Filename identifier.
1258
     *
1259
     * Note that the result will not have a leading slash, and should not be used
1260
     * with local file paths.
1261
     *
1262
     * @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...
1263
     * @return string
1264
     */
1265
    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...
1266
    {
1267
        $args = func_get_args();
1268
        if (count($args) === 1 && is_array($args[0])) {
1269
            $args = $args[0];
1270
        }
1271
1272
        $parts = array();
1273
        foreach ($args as $arg) {
1274
            $part = trim($arg, ' \\/');
1275
            if ($part) {
1276
                $parts[] = $part;
1277
            }
1278
        }
1279
1280
        return implode('/', $parts);
1281
    }
1282
1283
    public function deleteFile()
1284
    {
1285
        return $this->File->deleteFile();
1286
    }
1287
1288
    public function getVisibility()
1289
    {
1290
        return $this->File->getVisibility();
1291
    }
1292
1293
    public function publishFile()
1294
    {
1295
        $this->File->publishFile();
1296
    }
1297
1298
    public function protectFile()
1299
    {
1300
        $this->File->protectFile();
1301
    }
1302
1303
    public function grantFile()
1304
    {
1305
        $this->File->grantFile();
1306
    }
1307
1308
    public function revokeFile()
1309
    {
1310
        $this->File->revokeFile();
1311
    }
1312
1313
    public function canViewFile()
1314
    {
1315
        return $this->File->canViewFile();
1316
    }
1317
1318
    public function CMSEditLink()
1319
    {
1320
        $link = null;
1321
        $this->extend('updateCMSEditLink', $link);
1322
        return $link;
1323
    }
1324
1325
    public function PreviewLink($action = null)
1326
    {
1327
        // Since AbsoluteURL can whitelist protected assets,
1328
        // do permission check first
1329
        if (!$this->canView()) {
1330
            return null;
1331
        }
1332
        $link = $this->getIcon();
1333
        $this->extend('updatePreviewLink', $link, $action);
1334
        return $link;
1335
    }
1336
}
1337