Source of file Upload.php
Size: 12,299 Bytes - Last Modified: 2021-12-23T10:27:40+00:00
/var/www/docs.ssmods.com/process/src/src/Upload.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440 | <?php namespace SilverStripe\Assets; use Exception; use InvalidArgumentException; use SilverStripe\Assets\Storage\AssetContainer; use SilverStripe\Assets\Storage\AssetNameGenerator; use SilverStripe\Assets\Storage\AssetStore; use SilverStripe\Control\Controller; use SilverStripe\Core\Injector\Injector; use SilverStripe\ORM\DataObject; use SilverStripe\Security\Security; /** * Manages uploads via HTML forms processed by PHP, * uploads to Silverstripe's default upload directory, * and either creates a new or uses an existing File-object * for syncing with the database. * * <b>Validation</b> * * By default, a user can upload files without extension limitations, * which can be a security risk if the webserver is not properly secured. * Use {@link setAllowedExtensions()} to limit this list, * and ensure the "assets/" directory does not execute scripts * (see http://doc.silverstripe.org/secure-development#filesystem). * {@link File::$allowed_extensions} provides a good start for a list of "safe" extensions. * * @todo Allow for non-database uploads */ class Upload extends Controller { private static $allowed_actions = [ 'index', 'load' ]; /** * A dataobject (typically {@see File}) which implements {@see AssetContainer} * * @var AssetContainer */ protected $file; /** * Validator for this upload field * * @var Upload_Validator */ protected $validator; /** * Information about the temporary file produced * by the PHP-runtime. * * @var array */ protected $tmpFile; /** * Replace an existing file rather than renaming the new one. * * @var boolean */ protected $replaceFile = false; /** * Processing errors that can be evaluated, * e.g. by Form-validation. * * @var array */ protected $errors = []; /** * Default visibility to assign uploaded files * * @var string */ protected $defaultVisibility = AssetStore::VISIBILITY_PROTECTED; /** * A foldername relative to /assets, * where all uploaded files are stored by default. * * @config * @var string */ private static $uploads_folder = "Uploads"; /** * A prefix for the version number added to an uploaded file * when a file with the same name already exists. * Example using no prefix: IMG001.jpg becomes IMG2.jpg * Example using '-v' prefix: IMG001.jpg becomes IMG001-v2.jpg * * @config * @var string */ private static $version_prefix = '-v'; public function __construct() { parent::__construct(); $this->validator = Upload_Validator::create(); $this->replaceFile = self::config()->replaceFile; } public function index() { return $this->httpError(404); // no-op } /** * Get current validator * * @return Upload_Validator $validator */ public function getValidator() { return $this->validator; } /** * Set a different instance than {@link Upload_Validator} * for this upload session. * * @param object $validator */ public function setValidator($validator) { $this->validator = $validator; } /** * Get an asset renamer for the given filename. * * @param string $filename Path name * @return AssetNameGenerator */ protected function getNameGenerator($filename) { return Injector::inst()->createWithArgs(AssetNameGenerator::class, [$filename]); } /** * * @return AssetStore */ protected function getAssetStore() { return Injector::inst()->get(AssetStore::class); } /** * Save an file passed from a form post into the AssetStore directly * * @param array $tmpFile Indexed array that PHP generated for every file it uploads. * @param string|bool $folderPath Folder path relative to /assets * @return array|false Either the tuple array, or false if the file could not be saved */ public function load($tmpFile, $folderPath = false) { // Validate filename $filename = $this->getValidFilename($tmpFile, $folderPath); if (!$filename) { return false; } // Save file into backend $result = $this->storeTempFile($tmpFile, $filename, $this->getAssetStore()); //to allow extensions to e.g. create a version after an upload $this->extend('onAfterLoad', $result, $tmpFile); return $result; } /** * Save an file passed from a form post into this object. * File names are filtered through {@link FileNameFilter}, see class documentation * on how to influence this behaviour. * * @param array $tmpFile * @param AssetContainer $file * @param string|bool $folderPath * @return bool True if the file was successfully saved into this record * @throws Exception */ public function loadIntoFile($tmpFile, $file = null, $folderPath = false) { $this->file = $file; // Validate filename $filename = $this->getValidFilename($tmpFile, $folderPath); if (!$filename) { return false; } $filename = $this->resolveExistingFile($filename); // Save changes to underlying record (if it's a DataObject) $this->storeTempFile($tmpFile, $filename, $this->file); if ($this->file instanceof DataObject) { $this->file->write(); } //to allow extensions to e.g. create a version after an upload $this->file->extend('onAfterUpload'); $this->extend('onAfterLoadIntoFile', $this->file); return true; } /** * Assign this temporary file into the given destination * * @param array $tmpFile * @param string $filename * @param AssetContainer|AssetStore $container * @return array */ protected function storeTempFile($tmpFile, $filename, $container) { // Save file into backend $conflictResolution = $this->replaceFile ? AssetStore::CONFLICT_OVERWRITE : AssetStore::CONFLICT_RENAME; $config = [ 'conflict' => $conflictResolution, 'visibility' => $this->getDefaultVisibility() ]; return $container->setFromLocalFile($tmpFile['tmp_name'], $filename, null, null, $config); } /** * Given a temporary file and upload path, validate the file and determine the * value of the 'Filename' tuple that should be used to store this asset. * * @param array $tmpFile * @param string $folderPath * @return string|false Value of filename tuple, or false if invalid */ protected function getValidFilename($tmpFile, $folderPath = null) { if (!is_array($tmpFile)) { throw new InvalidArgumentException( "Upload::load() Not passed an array. Most likely, the form hasn't got the right enctype" ); } // Validate $this->clearErrors(); $valid = $this->validate($tmpFile); if (!$valid) { return false; } // Clean filename if (!$folderPath) { $folderPath = $this->config()->uploads_folder; } $nameFilter = FileNameFilter::create(); $file = $nameFilter->filter($tmpFile['name']); $filename = basename($file); if ($folderPath) { $filename = File::join_paths($folderPath, $filename); } return $filename; } /** * Given a file and filename, ensure that file renaming / replacing rules are satisfied * * If replacing, this method may replace $this->file with an existing record to overwrite. * If renaming, a new value for $filename may be returned * * @param string $filename * @return string $filename A filename safe to write to * @throws Exception */ protected function resolveExistingFile($filename) { // Create a new file record (or try to retrieve an existing one) if (!$this->file) { $fileClass = File::get_class_for_file_extension( File::get_file_extension($filename) ); $this->file = Injector::inst()->create($fileClass); } // Skip this step if not writing File dataobjects if (! ($this->file instanceof File)) { return $filename; } // Check there is if existing file $existing = File::find($filename); // If replacing (or no file exists) confirm this filename is safe if ($this->replaceFile || !$existing) { // If replacing files, make sure to update the OwnerID if (!$this->file->ID && $this->replaceFile && $existing) { $this->file = $existing; $user = Security::getCurrentUser(); if ($user) { $this->file->OwnerID = $user->ID; } } // Filename won't change if replacing return $filename; } // if filename already exists, version the filename (e.g. test.gif to test-v2.gif, test-v2.gif to test-v3.gif) $renamer = $this->getNameGenerator($filename); foreach ($renamer as $newName) { if (!File::find($newName)) { return $newName; } } // Fail $tries = $renamer->getMaxTries(); throw new Exception("Could not rename {$filename} with {$tries} tries"); } /** * @param bool $replace */ public function setReplaceFile($replace) { $this->replaceFile = $replace; } /** * @return bool */ public function getReplaceFile() { return $this->replaceFile; } /** * Container for all validation on the file * (e.g. size and extension restrictions). * Is NOT connected to the {Validator} classes, * please have a look at {FileField->validate()} * for an example implementation of external validation. * * @param array $tmpFile * @return boolean */ public function validate($tmpFile) { $validator = $this->validator; $validator->setTmpFile($tmpFile); $isValid = $validator->validate(); if ($validator->getErrors()) { $this->errors = array_merge($this->errors, $validator->getErrors()); } return $isValid; } /** * Get file-object, either generated from {load()}, * or manually set. * * @return AssetContainer */ public function getFile() { return $this->file; } /** * Set a file-object (similiar to {loadIntoFile()}) * * @param AssetContainer $file */ public function setFile(AssetContainer $file) { $this->file = $file; } /** * Clear out all errors (mostly set by {loadUploaded()}) * including the validator's errors */ public function clearErrors() { $this->errors = []; $this->validator->clearErrors(); } /** * Determines wether previous operations caused an error. * * @return boolean */ public function isError() { return (count($this->errors)); } /** * Return all errors that occurred while processing so far * (mostly set by {loadUploaded()}) * * @return array */ public function getErrors() { return $this->errors; } /** * Get default visibility for uploaded files. {@see AssetStore} * One of the values of AssetStore::VISIBILITY_* constants * * @return string */ public function getDefaultVisibility() { return $this->defaultVisibility; } /** * Assign default visibility for uploaded files. {@see AssetStore} * One of the values of AssetStore::VISIBILITY_* constants * * @param string $visibility * @return $this */ public function setDefaultVisibility($visibility) { $this->defaultVisibility = $visibility; return $this; } } |