Completed
Push — master ( 424078...93da45 )
by Sam
11:49
created

Upload::index()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
use SilverStripe\Filesystem\Storage\AssetContainer;
4
use SilverStripe\Filesystem\Storage\AssetNameGenerator;
5
use SilverStripe\Filesystem\Storage\AssetStore;
6
7
/**
8
 * Manages uploads via HTML forms processed by PHP,
9
 * uploads to Silverstripe's default upload directory,
10
 * and either creates a new or uses an existing File-object
11
 * for syncing with the database.
12
 *
13
 * <b>Validation</b>
14
 *
15
 * By default, a user can upload files without extension limitations,
16
 * which can be a security risk if the webserver is not properly secured.
17
 * Use {@link setAllowedExtensions()} to limit this list,
18
 * and ensure the "assets/" directory does not execute scripts
19
 * (see http://doc.silverstripe.org/secure-development#filesystem).
20
 * {@link File::$allowed_extensions} provides a good start for a list of "safe" extensions.
21
 *
22
 * @package framework
23
 * @subpackage filesystem
24
 *
25
 * @todo Allow for non-database uploads
26
 */
27
class Upload extends Controller {
28
29
	private static $allowed_actions = array(
30
		'index',
31
		'load'
32
	);
33
34
	/**
35
	 * A dataobject (typically {@see File}) which implements {@see AssetContainer}
36
	 *
37
	 * @var AssetContainer
38
	 */
39
	protected $file;
40
41
	/**
42
	 * Validator for this upload field
43
	 *
44
	 * @var Upload_Validator
45
	 */
46
	protected $validator;
47
48
	/**
49
	 * Information about the temporary file produced
50
	 * by the PHP-runtime.
51
	 *
52
	 * @var array
53
	 */
54
	protected $tmpFile;
55
56
	/**
57
	 * Replace an existing file rather than renaming the new one.
58
	 *
59
	 * @var boolean
60
	 */
61
	protected $replaceFile;
62
63
	/**
64
	 * Processing errors that can be evaluated,
65
	 * e.g. by Form-validation.
66
	 *
67
	 * @var array
68
	 */
69
	protected $errors = array();
70
71
	/**
72
	 * Default visibility to assign uploaded files
73
	 *
74
	 * @var string
75
	 */
76
	protected $defaultVisibility = AssetStore::VISIBILITY_PROTECTED;
77
78
	/**
79
	 * A foldername relative to /assets,
80
	 * where all uploaded files are stored by default.
81
	 *
82
	 * @config
83
	 * @var string
84
	 */
85
	private static $uploads_folder = "Uploads";
86
87
	/**
88
	 * A prefix for the version number added to an uploaded file
89
	 * when a file with the same name already exists.
90
	 * Example using no prefix: IMG001.jpg becomes IMG2.jpg
91
	 * Example using '-v' prefix: IMG001.jpg becomes IMG001-v2.jpg
92
	 *
93
	 * @config
94
	 * @var string
95
	 */
96
	private static $version_prefix = '-v';
97
98
	public function __construct() {
99
		parent::__construct();
100
		$this->validator = Injector::inst()->create('Upload_Validator');
101
		$this->replaceFile = self::config()->replaceFile;
0 ignored issues
show
Documentation introduced by
The property replaceFile does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
102
	}
103
104
	public function index() {
105
		return $this->httpError(404); // no-op
106
	}
107
108
	/**
109
	 * Get current validator
110
	 *
111
	 * @return Upload_Validator $validator
112
	 */
113
	public function getValidator() {
114
		return $this->validator;
115
	}
116
117
	/**
118
	 * Set a different instance than {@link Upload_Validator}
119
	 * for this upload session.
120
	 *
121
	 * @param object $validator
122
	 */
123
	public function setValidator($validator) {
124
		$this->validator = $validator;
125
	}
126
127
128
	/**
129
	 * Get an asset renamer for the given filename.
130
	 *
131
	 * @param string $filename Path name
132
	 * @return AssetNameGenerator
133
	 */
134
	protected function getNameGenerator($filename){
135
		return Injector::inst()->createWithArgs('AssetNameGenerator', array($filename));
136
	}
137
138
	/**
139
	 *
140
	 * @return AssetStore
141
	 */
142
	protected function getAssetStore() {
143
		return Injector::inst()->get('AssetStore');
144
	}
145
146
	/**
147
	 * Save an file passed from a form post into the AssetStore directly
148
	 *
149
	 * @param $tmpFile array Indexed array that PHP generated for every file it uploads.
150
	 * @param $folderPath string Folder path relative to /assets
151
	 * @return array|false Either the tuple array, or false if the file could not be saved
152
	 */
153
	public function load($tmpFile, $folderPath = false) {
154
		// Validate filename
155
		$filename = $this->getValidFilename($tmpFile, $folderPath);
156
		if(!$filename) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filename of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false 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...
157
			return false;
158
		}
159
160
		// Save file into backend
161
		$result = $this->storeTempFile($tmpFile, $filename, $this->getAssetStore());
162
163
		//to allow extensions to e.g. create a version after an upload
164
		$this->extend('onAfterLoad', $result, $tmpFile);
165
		return $result;
166
	}
167
168
	/**
169
	 * Save an file passed from a form post into this object.
170
	 * File names are filtered through {@link FileNameFilter}, see class documentation
171
	 * on how to influence this behaviour.
172
	 *
173
	 * @param array $tmpFile
174
	 * @param AssetContainer $file
175
	 * @return bool True if the file was successfully saved into this record
176
	 */
177
	public function loadIntoFile($tmpFile, $file = null, $folderPath = false) {
178
		$this->file = $file;
179
180
		// Validate filename
181
		$filename = $this->getValidFilename($tmpFile, $folderPath);
182
		if(!$filename) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filename of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false 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...
183
			return false;
184
		}
185
		$filename = $this->resolveExistingFile($filename);
186
187
		// Save changes to underlying record (if it's a DataObject)
188
		$this->storeTempFile($tmpFile, $filename, $this->file);
0 ignored issues
show
Bug introduced by
It seems like $this->file can be null; however, storeTempFile() 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...
189
		if($this->file instanceof DataObject) {
190
			$this->file->write();
191
		}
192
193
		//to allow extensions to e.g. create a version after an upload
194
		$this->file->extend('onAfterUpload');
195
		$this->extend('onAfterLoadIntoFile', $this->file);
196
		return true;
197
	}
198
199
	/**
200
	 * Assign this temporary file into the given destination
201
	 *
202
	 * @param array $tmpFile
203
	 * @param string $filename
204
	 * @param AssetContainer|AssetStore $container
205
	 * @return array
206
	 */
207
	protected function storeTempFile($tmpFile, $filename, $container) {
208
		// Save file into backend
209
		$conflictResolution = $this->replaceFile
210
			? AssetStore::CONFLICT_OVERWRITE
211
			: AssetStore::CONFLICT_RENAME;
212
		$config = array(
213
			'conflict' => $conflictResolution,
214
			'visibility' => $this->getDefaultVisibility()
215
		);
216
		return $container->setFromLocalFile($tmpFile['tmp_name'], $filename, null, null, $config);
217
	}
218
219
	/**
220
	 * Given a temporary file and upload path, validate the file and determine the
221
	 * value of the 'Filename' tuple that should be used to store this asset.
222
	 *
223
	 * @param array $tmpFile
224
	 * @param string $folderPath
225
	 * @return string|false Value of filename tuple, or false if invalid
226
	 */
227
	protected function getValidFilename($tmpFile, $folderPath = null) {
228
		if(!is_array($tmpFile)) {
229
			throw new InvalidArgumentException(
230
				"Upload::load() Not passed an array.  Most likely, the form hasn't got the right enctype"
231
			);
232
		}
233
234
		// Validate
235
		$this->clearErrors();
236
		$valid = $this->validate($tmpFile);
237
		if(!$valid) {
238
			return false;
239
		}
240
241
		// Clean filename
242
		if(!$folderPath) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $folderPath of type string|null is loosely compared to false; 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...
243
			$folderPath = $this->config()->uploads_folder;
0 ignored issues
show
Documentation introduced by
The property uploads_folder does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
244
		}
245
		$nameFilter = FileNameFilter::create();
246
		$file = $nameFilter->filter($tmpFile['name']);
247
		$filename = basename($file);
248
		if($folderPath) {
249
			$filename = File::join_paths($folderPath, $filename);
250
		}
251
		return $filename;
252
	}
253
254
	/**
255
	 * Given a file and filename, ensure that file renaming / replacing rules are satisfied
256
	 *
257
	 * If replacing, this method may replace $this->file with an existing record to overwrite.
258
	 * If renaming, a new value for $filename may be returned
259
	 *
260
	 * @param string $filename
261
	 * @return string $filename A filename safe to write to
262
	 * @throws Exception
263
	 */
264
	protected function resolveExistingFile($filename) {
265
		// Create a new file record (or try to retrieve an existing one)
266
		if(!$this->file) {
267
			$fileClass = File::get_class_for_file_extension(
268
				File::get_file_extension($filename)
269
			);
270
			$this->file = Object::create($fileClass);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Object::create($fileClass) of type this<Upload> is incompatible with the declared type object<SilverStripe\File...Storage\AssetContainer> of property $file.

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

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

Loading history...
271
		}
272
273
		// Skip this step if not writing File dataobjects
274
		if(! ($this->file instanceof File) ) {
275
			return $filename;
276
		}
277
278
		// Check there is if existing file
279
		$existing = File::find($filename);
280
281
		// If replacing (or no file exists) confirm this filename is safe
282
		if($this->replaceFile || !$existing) {
283
			// If replacing files, make sure to update the OwnerID
284
			if(!$this->file->ID && $this->replaceFile && $existing) {
285
				$this->file = $existing;
286
				$this->file->OwnerID = Member::currentUserID();
287
			}
288
			// Filename won't change if replacing
289
			return $filename;
290
		}
291
292
		// if filename already exists, version the filename (e.g. test.gif to test-v2.gif, test-v2.gif to test-v3.gif)
293
		$renamer = $this->getNameGenerator($filename);
294
		foreach($renamer as $newName) {
295
			if(!File::find($newName)) {
296
				return $newName;
297
			}
298
		}
299
300
		// Fail
301
		$tries = $renamer->getMaxTries();
302
		throw new Exception("Could not rename {$filename} with {$tries} tries");
303
	}
304
305
	/**
306
	 * @param bool $replace
307
	 */
308
	public function setReplaceFile($replace) {
309
		$this->replaceFile = $replace;
310
	}
311
312
	/**
313
	 * @return bool
314
	 */
315
	public function getReplaceFile() {
316
		return $this->replaceFile;
317
	}
318
319
	/**
320
	 * Container for all validation on the file
321
	 * (e.g. size and extension restrictions).
322
	 * Is NOT connected to the {Validator} classes,
323
	 * please have a look at {FileField->validate()}
324
	 * for an example implementation of external validation.
325
	 *
326
	 * @param array $tmpFile
327
	 * @return boolean
328
	 */
329
	public function validate($tmpFile) {
330
		$validator = $this->validator;
331
		$validator->setTmpFile($tmpFile);
332
		$isValid = $validator->validate();
333
		if($validator->getErrors()) {
334
			$this->errors = array_merge($this->errors, $validator->getErrors());
335
		}
336
		return $isValid;
337
	}
338
339
	/**
340
	 * Get file-object, either generated from {load()},
341
	 * or manually set.
342
	 *
343
	 * @return AssetContainer
344
	 */
345
	public function getFile() {
346
		return $this->file;
347
	}
348
349
	/**
350
	 * Set a file-object (similiar to {loadIntoFile()})
351
	 *
352
	 * @param AssetContainer $file
353
	 */
354
	public function setFile(AssetContainer $file) {
355
		$this->file = $file;
356
	}
357
358
	/**
359
	 * Clear out all errors (mostly set by {loadUploaded()})
360
	 * including the validator's errors
361
	 */
362
	public function clearErrors() {
363
		$this->errors = array();
364
		$this->validator->clearErrors();
365
	}
366
367
	/**
368
	 * Determines wether previous operations caused an error.
369
	 *
370
	 * @return boolean
371
	 */
372
	public function isError() {
373
		return (count($this->errors));
374
	}
375
376
	/**
377
	 * Return all errors that occurred while processing so far
378
	 * (mostly set by {loadUploaded()})
379
	 *
380
	 * @return array
381
	 */
382
	public function getErrors() {
383
		return $this->errors;
384
	}
385
386
	/**
387
	 * Get default visibility for uploaded files. {@see AssetStore}
388
	 * One of the values of AssetStore::VISIBILITY_* constants
389
	 *
390
	 * @return string
391
	 */
392
	public function getDefaultVisibility() {
393
		return $this->defaultVisibility;
394
	}
395
396
	/**
397
	 * Assign default visibility for uploaded files. {@see AssetStore}
398
	 * One of the values of AssetStore::VISIBILITY_* constants
399
	 *
400
	 * @param string $visibility
401
	 * @return $this
402
	 */
403
	public function setDefaultVisibility($visibility) {
404
		$this->defaultVisibility = $visibility;
405
		return $this;
406
	}
407
408
}
409
410
/**
411
 * @package framework
412
 * @subpackage filesystem
413
 */
414
class Upload_Validator {
415
416
	/**
417
	* Contains a list of the max file sizes shared by
418
	* all upload fields. This is then duplicated into the
419
	* "allowedMaxFileSize" instance property on construct.
420
	*
421
	* @config
422
	* @var array
423
	*/
424
	private static $default_max_file_size = array();
425
426
	/**
427
	 * Information about the temporary file produced
428
	 * by the PHP-runtime.
429
	 *
430
	 * @var array
431
	 */
432
	protected $tmpFile;
433
434
	protected $errors = array();
435
436
	/**
437
	 * Restrict filesize for either all filetypes
438
	 * or a specific extension, with extension-name
439
	 * as array-key and the size-restriction in bytes as array-value.
440
	 *
441
	 * @var array
442
	 */
443
	public $allowedMaxFileSize = array();
444
445
	/**
446
	 * @var array Collection of extensions.
447
	 * Extension-names are treated case-insensitive.
448
	 *
449
	 * Example:
450
	 * <code>
451
	 * 	array("jpg","GIF")
452
	 * </code>
453
	 */
454
	public $allowedExtensions = array();
455
456
	/**
457
	 * Return all errors that occurred while validating
458
	 * the temporary file.
459
	 *
460
	 * @return array
461
	 */
462
	public function getErrors() {
463
		return $this->errors;
464
	}
465
466
	/**
467
	 * Clear out all errors
468
	 */
469
	public function clearErrors() {
470
		$this->errors = array();
471
	}
472
473
	/**
474
	 * Set information about temporary file produced by PHP.
475
	 * @param array $tmpFile
476
	 */
477
	public function setTmpFile($tmpFile) {
478
		$this->tmpFile = $tmpFile;
479
	}
480
481
	/**
482
	 * Get maximum file size for all or specified file extension.
483
	 *
484
	 * @param string $ext
485
	 * @return int Filesize in bytes
486
	 */
487
	public function getAllowedMaxFileSize($ext = null) {
488
489
		// Check if there is any defined instance max file sizes
490
		if (empty($this->allowedMaxFileSize)) {
491
			// Set default max file sizes if there isn't
492
			$fileSize = Config::inst()->get('Upload_Validator', 'default_max_file_size');
493
			if (isset($fileSize)) {
494
				$this->setAllowedMaxFileSize($fileSize);
495
			} else {
496
				// When no default is present, use maximum set by PHP
497
				$maxUpload = File::ini2bytes(ini_get('upload_max_filesize'));
498
				$maxPost = File::ini2bytes(ini_get('post_max_size'));
499
				$this->setAllowedMaxFileSize(min($maxUpload, $maxPost));
500
			}
501
		}
502
503
		$ext = strtolower($ext);
504
		if ($ext) {
505
			if (isset($this->allowedMaxFileSize[$ext])) {
506
				return $this->allowedMaxFileSize[$ext];
507
			}
508
509
			$category = File::get_app_category($ext);
510
			if ($category && isset($this->allowedMaxFileSize['[' . $category . ']'])) {
511
				return $this->allowedMaxFileSize['[' . $category . ']'];
512
			}
513
514
			return false;
515
		} else {
516
			return (isset($this->allowedMaxFileSize['*'])) ? $this->allowedMaxFileSize['*'] : false;
517
		}
518
	}
519
520
	/**
521
	 * Set filesize maximums (in bytes or INI format).
522
	 * Automatically converts extensions to lowercase
523
	 * for easier matching.
524
	 *
525
	 * Example:
526
	 * <code>
527
	 * array('*' => 200, 'jpg' => 1000, '[doc]' => '5m')
528
	 * </code>
529
	 *
530
	 * @param array|int $rules
531
	 */
532
	public function setAllowedMaxFileSize($rules) {
533
		if(is_array($rules) && count($rules)) {
534
			// make sure all extensions are lowercase
535
			$rules = array_change_key_case($rules, CASE_LOWER);
536
			$finalRules = array();
537
538
			foreach ($rules as $rule => $value) {
539
				if (is_numeric($value)) {
540
					$tmpSize = $value;
541
				} else {
542
					$tmpSize = File::ini2bytes($value);
543
				}
544
545
				$finalRules[$rule] = (int)$tmpSize;
546
			}
547
548
			$this->allowedMaxFileSize = $finalRules;
549
		} elseif(is_string($rules)) {
550
			$this->allowedMaxFileSize['*'] = File::ini2bytes($rules);
551
		} elseif((int) $rules > 0) {
552
			$this->allowedMaxFileSize['*'] = (int)$rules;
553
		}
554
	}
555
556
	/**
557
	 * @return array
558
	 */
559
	public function getAllowedExtensions() {
560
		return $this->allowedExtensions;
561
	}
562
563
	/**
564
	 * Limit allowed file extensions. Empty by default, allowing all extensions.
565
	 * To allow files without an extension, use an empty string.
566
	 * See {@link File::$allowed_extensions} to get a good standard set of
567
	 * extensions that are typically not harmful in a webserver context.
568
	 * See {@link setAllowedMaxFileSize()} to limit file size by extension.
569
	 *
570
	 * @param array $rules List of extensions
571
	 */
572
	public function setAllowedExtensions($rules) {
573
		if(!is_array($rules)) {
574
			return;
575
		}
576
577
		// make sure all rules are lowercase
578
		foreach($rules as &$rule) $rule = strtolower($rule);
579
580
		$this->allowedExtensions = $rules;
581
	}
582
583
	/**
584
	 * Determines if the bytesize of an uploaded
585
	 * file is valid - can be defined on an
586
	 * extension-by-extension basis in {@link $allowedMaxFileSize}
587
	 *
588
	 * @return boolean
589
	 */
590
	public function isValidSize() {
591
		$pathInfo = pathinfo($this->tmpFile['name']);
592
		$extension = isset($pathInfo['extension']) ? strtolower($pathInfo['extension']) : null;
593
		$maxSize = $this->getAllowedMaxFileSize($extension);
594
		return (!$this->tmpFile['size'] || !$maxSize || (int) $this->tmpFile['size'] < $maxSize);
595
	}
596
597
	/**
598
	 * Determines if the temporary file has a valid extension
599
	 * An empty string in the validation map indicates files without an extension.
600
	 * @return boolean
601
	 */
602
	public function isValidExtension() {
603
		$pathInfo = pathinfo($this->tmpFile['name']);
604
605
		// Special case for filenames without an extension
606
		if(!isset($pathInfo['extension'])) {
607
			return in_array('', $this->allowedExtensions, true);
608
		} else {
609
			return (!count($this->allowedExtensions)
610
				|| in_array(strtolower($pathInfo['extension']), $this->allowedExtensions));
611
		}
612
	}
613
614
	/**
615
	 * Run through the rules for this validator checking against
616
	 * the temporary file set by {@link setTmpFile()} to see if
617
	 * the file is deemed valid or not.
618
	 *
619
	 * @return boolean
620
	 */
621
	public function validate() {
622
		// we don't validate for empty upload fields yet
623
		if(empty($this->tmpFile['name']) || empty($this->tmpFile['tmp_name'])) {
624
			return true;
625
		}
626
627
		$isRunningTests = (class_exists('SapphireTest', false) && SapphireTest::is_running_test());
628
		if(isset($this->tmpFile['tmp_name']) && !is_uploaded_file($this->tmpFile['tmp_name']) && !$isRunningTests) {
629
			$this->errors[] = _t('File.NOVALIDUPLOAD', 'File is not a valid upload');
630
			return false;
631
		}
632
633
		// Check file isn't empty
634
		if(empty($this->tmpFile['size']) || !filesize($this->tmpFile['tmp_name'])) {
635
			$this->errors[] = _t('File.NOFILESIZE', 'Filesize is zero bytes.');
636
			return false;
637
		}
638
639
		$pathInfo = pathinfo($this->tmpFile['name']);
640
		// filesize validation
641
		if(!$this->isValidSize()) {
642
			$ext = (isset($pathInfo['extension'])) ? $pathInfo['extension'] : '';
643
			$arg = File::format_size($this->getAllowedMaxFileSize($ext));
644
			$this->errors[] = _t(
645
				'File.TOOLARGE',
646
				'Filesize is too large, maximum {size} allowed',
647
				'Argument 1: Filesize (e.g. 1MB)',
648
				array('size' => $arg)
649
			);
650
			return false;
651
		}
652
653
		// extension validation
654
		if(!$this->isValidExtension()) {
655
			$this->errors[] = _t(
656
				'File.INVALIDEXTENSION',
657
				'Extension is not allowed (valid: {extensions})',
658
				'Argument 1: Comma-separated list of valid extensions',
659
				array('extensions' => wordwrap(implode(', ', $this->allowedExtensions)))
660
			);
661
			return false;
662
		}
663
664
		return true;
665
	}
666
667
}
668