diff --git a/src/Assets/Upload.php b/src/Assets/Upload.php
index 444a344a4..aed1fe12f 100644
--- a/src/Assets/Upload.php
+++ b/src/Assets/Upload.php
@@ -33,405 +33,405 @@ use Exception;
class Upload extends Controller
{
- private static $allowed_actions = array(
- 'index',
- 'load'
- );
+ private static $allowed_actions = array(
+ 'index',
+ 'load'
+ );
- /**
- * A dataobject (typically {@see File}) which implements {@see AssetContainer}
- *
- * @var AssetContainer
- */
- protected $file;
+ /**
+ * A dataobject (typically {@see File}) which implements {@see AssetContainer}
+ *
+ * @var AssetContainer
+ */
+ protected $file;
- /**
- * Validator for this upload field
- *
- * @var Upload_Validator
- */
- protected $validator;
+ /**
+ * Validator for this upload field
+ *
+ * @var Upload_Validator
+ */
+ protected $validator;
- /**
- * Information about the temporary file produced
- * by the PHP-runtime.
- *
- * @var array
- */
- protected $tmpFile;
+ /**
+ * 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;
+ /**
+ * 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 = array();
+ /**
+ * Processing errors that can be evaluated,
+ * e.g. by Form-validation.
+ *
+ * @var array
+ */
+ protected $errors = array();
- /**
- * Default visibility to assign uploaded files
- *
- * @var string
- */
- protected $defaultVisibility = AssetStore::VISIBILITY_PROTECTED;
+ /**
+ * 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 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';
+ /**
+ * 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 = Injector::inst()->create('SilverStripe\\Assets\\Upload_Validator');
- $this->replaceFile = self::config()->replaceFile;
- }
+ parent::__construct();
+ $this->validator = Upload_Validator::create();
+ $this->replaceFile = self::config()->replaceFile;
+ }
public function index()
{
- return $this->httpError(404); // no-op
- }
+ return $this->httpError(404); // no-op
+ }
- /**
- * Get current validator
- *
- * @return Upload_Validator $validator
- */
+ /**
+ * Get current validator
+ *
+ * @return Upload_Validator $validator
+ */
public function getValidator()
{
- return $this->validator;
- }
+ return $this->validator;
+ }
- /**
- * Set a different instance than {@link Upload_Validator}
- * for this upload session.
- *
- * @param object $validator
- */
+ /**
+ * Set a different instance than {@link Upload_Validator}
+ * for this upload session.
+ *
+ * @param object $validator
+ */
public function setValidator($validator)
{
- $this->validator = $validator;
- }
+ $this->validator = $validator;
+ }
- /**
- * Get an asset renamer for the given filename.
- *
- * @param string $filename Path name
- * @return AssetNameGenerator
- */
+ /**
+ * Get an asset renamer for the given filename.
+ *
+ * @param string $filename Path name
+ * @return AssetNameGenerator
+ */
protected function getNameGenerator($filename)
{
- return Injector::inst()->createWithArgs('AssetNameGenerator', array($filename));
- }
+ return Injector::inst()->createWithArgs('AssetNameGenerator', array($filename));
+ }
- /**
- *
- * @return AssetStore
- */
+ /**
+ *
+ * @return AssetStore
+ */
protected function getAssetStore()
{
- return Injector::inst()->get('AssetStore');
- }
+ return Injector::inst()->get('AssetStore');
+ }
- /**
- * 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
- */
+ /**
+ * 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;
- }
+ // Validate filename
+ $filename = $this->getValidFilename($tmpFile, $folderPath);
+ if(!$filename) {
+ return false;
+ }
- // Save file into backend
- $result = $this->storeTempFile($tmpFile, $filename, $this->getAssetStore());
+ // 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;
- }
+ //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
- */
+ /**
+ * 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;
+ $this->file = $file;
- // Validate filename
- $filename = $this->getValidFilename($tmpFile, $folderPath);
- if (!$filename) {
- return false;
- }
- $filename = $this->resolveExistingFile($filename);
+ // 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();
- }
+ // 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;
- }
+ //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
- */
+ /**
+ * 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 = array(
- 'conflict' => $conflictResolution,
- 'visibility' => $this->getDefaultVisibility()
- );
- return $container->setFromLocalFile($tmpFile['tmp_name'], $filename, null, null, $config);
- }
+ // Save file into backend
+ $conflictResolution = $this->replaceFile
+ ? AssetStore::CONFLICT_OVERWRITE
+ : AssetStore::CONFLICT_RENAME;
+ $config = array(
+ '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
- */
+ /**
+ * 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"
- );
- }
+ 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;
- }
+ // 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;
- }
+ // 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
- */
+ /**
+ * 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 = Object::create($fileClass);
- }
+ // 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 = Object::create($fileClass);
+ }
- // Skip this step if not writing File dataobjects
- if (! ($this->file instanceof File)) {
- return $filename;
- }
+ // 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);
+ // 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;
- $this->file->OwnerID = Member::currentUserID();
- }
- // Filename won't change if replacing
- return $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;
+ $this->file->OwnerID = Member::currentUserID();
+ }
+ // 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;
- }
- }
+ // 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");
- }
+ // Fail
+ $tries = $renamer->getMaxTries();
+ throw new Exception("Could not rename {$filename} with {$tries} tries");
+ }
- /**
- * @param bool $replace
- */
+ /**
+ * @param bool $replace
+ */
public function setReplaceFile($replace)
{
- $this->replaceFile = $replace;
- }
+ $this->replaceFile = $replace;
+ }
- /**
- * @return bool
- */
+ /**
+ * @return bool
+ */
public function getReplaceFile()
{
- return $this->replaceFile;
- }
+ 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
- */
+ /**
+ * 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;
- }
+ $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
- */
+ /**
+ * Get file-object, either generated from {load()},
+ * or manually set.
+ *
+ * @return AssetContainer
+ */
public function getFile()
{
- return $this->file;
- }
+ return $this->file;
+ }
- /**
- * Set a file-object (similiar to {loadIntoFile()})
- *
- * @param AssetContainer $file
- */
+ /**
+ * Set a file-object (similiar to {loadIntoFile()})
+ *
+ * @param AssetContainer $file
+ */
public function setFile(AssetContainer $file)
{
- $this->file = $file;
- }
+ $this->file = $file;
+ }
- /**
- * Clear out all errors (mostly set by {loadUploaded()})
- * including the validator's errors
- */
+ /**
+ * Clear out all errors (mostly set by {loadUploaded()})
+ * including the validator's errors
+ */
public function clearErrors()
{
- $this->errors = array();
- $this->validator->clearErrors();
- }
+ $this->errors = array();
+ $this->validator->clearErrors();
+ }
- /**
- * Determines wether previous operations caused an error.
- *
- * @return boolean
- */
+ /**
+ * Determines wether previous operations caused an error.
+ *
+ * @return boolean
+ */
public function isError()
{
- return (count($this->errors));
- }
+ return (count($this->errors));
+ }
- /**
- * Return all errors that occurred while processing so far
- * (mostly set by {loadUploaded()})
- *
- * @return array
- */
+ /**
+ * Return all errors that occurred while processing so far
+ * (mostly set by {loadUploaded()})
+ *
+ * @return array
+ */
public function getErrors()
{
- return $this->errors;
- }
+ return $this->errors;
+ }
- /**
- * Get default visibility for uploaded files. {@see AssetStore}
- * One of the values of AssetStore::VISIBILITY_* constants
- *
- * @return string
- */
+ /**
+ * Get default visibility for uploaded files. {@see AssetStore}
+ * One of the values of AssetStore::VISIBILITY_* constants
+ *
+ * @return string
+ */
public function getDefaultVisibility()
{
- return $this->defaultVisibility;
- }
+ return $this->defaultVisibility;
+ }
- /**
- * Assign default visibility for uploaded files. {@see AssetStore}
- * One of the values of AssetStore::VISIBILITY_* constants
- *
- * @param string $visibility
- * @return $this
- */
+ /**
+ * 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;
- }
+ $this->defaultVisibility = $visibility;
+ return $this;
+ }
}
diff --git a/src/Assets/Upload_Validator.php b/src/Assets/Upload_Validator.php
index 8d8038a4d..4cb13be53 100644
--- a/src/Assets/Upload_Validator.php
+++ b/src/Assets/Upload_Validator.php
@@ -3,10 +3,12 @@
namespace SilverStripe\Assets;
use SilverStripe\Core\Config\Config;
+use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Dev\SapphireTest;
class Upload_Validator
{
+ use Injectable;
/**
* Contains a list of the max file sizes shared by
diff --git a/src/Forms/AssetField.php b/src/Forms/AssetField.php
index e7347a82f..f58891b01 100644
--- a/src/Forms/AssetField.php
+++ b/src/Forms/AssetField.php
@@ -14,7 +14,6 @@ use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
-use SilverStripe\View\Requirements;
use Exception;
/**
@@ -27,784 +26,793 @@ use Exception;
* - Files can't be edited once uploaded.
* - Attached files can only be removed, not deleted.
*/
-class AssetField extends FileField
+class AssetField extends FormField
{
+ use Uploadable;
- /**
- * @var array
- */
- private static $allowed_actions = array(
- 'upload'
- );
+ /**
+ * @var array
+ */
+ private static $allowed_actions = array(
+ 'upload'
+ );
- /**
- * @var array
- */
- private static $url_handlers = array(
- '$Action!' => '$Action',
- );
+ /**
+ * @var array
+ */
+ private static $url_handlers = array(
+ '$Action!' => '$Action',
+ );
- private static $casting = array(
- 'Value' => 'DBFile',
- 'UploadFieldThumbnailURL' => 'Varchar'
- );
+ private static $casting = array(
+ 'Value' => 'DBFile',
+ 'UploadFieldThumbnailURL' => 'Varchar'
+ );
- /**
- * Template to use for the file button widget
- *
- * @var string
- */
- protected $templateFileButtons = null;
+ /**
+ * Template to use for the file button widget
+ *
+ * @var string
+ */
+ protected $templateFileButtons = null;
- /**
- * Parent data record. Will be infered from parent form or controller if blank. The destination
- * DBFile should be a property of the name $name on this object.
- *
- * @var DataObject
- */
- protected $record;
+ /**
+ * Parent data record. Will be infered from parent form or controller if blank. The destination
+ * DBFile should be a property of the name $name on this object.
+ *
+ * @var DataObject
+ */
+ protected $record;
- /**
- * Config for this field used in the front-end javascript
- * (will be merged into the config of the javascript file upload plugin).
- *
- * @var array
- */
- protected $ufConfig = array();
+ /**
+ * Config for this field used in the front-end javascript
+ * (will be merged into the config of the javascript file upload plugin).
+ *
+ * @var array
+ */
+ protected $ufConfig = array();
- /**
- * Front end config defaults
- *
- * @config
- * @var array
- */
- private static $defaultConfig = array(
- /**
- * Automatically upload the file once selected
- *
- * @var boolean
- */
- 'autoUpload' => true,
+ /**
+ * Front end config defaults
+ *
+ * @config
+ * @var array
+ */
+ private static $defaultConfig = array(
+ /**
+ * Automatically upload the file once selected
+ *
+ * @var boolean
+ */
+ 'autoUpload' => true,
- /**
- * Can the user upload new files.
- * String values are interpreted as permission codes.
- *
- * @var boolean|string
- */
- 'canUpload' => true,
+ /**
+ * Can the user upload new files.
+ * String values are interpreted as permission codes.
+ *
+ * @var boolean|string
+ */
+ 'canUpload' => true,
- /**
- * Shows the target folder for new uploads in the field UI.
- * Disable to keep the internal filesystem structure hidden from users.
- *
- * @var boolean|string
- */
- 'canPreviewFolder' => true,
+ /**
+ * Shows the target folder for new uploads in the field UI.
+ * Disable to keep the internal filesystem structure hidden from users.
+ *
+ * @var boolean|string
+ */
+ 'canPreviewFolder' => true,
- /**
- * Indicate a change event to the containing form if an upload
- * or file edit/delete was performed.
- *
- * @var boolean
- */
- 'changeDetection' => true,
+ /**
+ * Indicate a change event to the containing form if an upload
+ * or file edit/delete was performed.
+ *
+ * @var boolean
+ */
+ 'changeDetection' => true,
- /**
- * Maximum width of the preview thumbnail
- *
- * @var integer
- */
- 'previewMaxWidth' => 80,
+ /**
+ * Maximum width of the preview thumbnail
+ *
+ * @var integer
+ */
+ 'previewMaxWidth' => 80,
- /**
- * Maximum height of the preview thumbnail
- *
- * @var integer
- */
- 'previewMaxHeight' => 60,
+ /**
+ * Maximum height of the preview thumbnail
+ *
+ * @var integer
+ */
+ 'previewMaxHeight' => 60,
- /**
- * javascript template used to display uploading files
- *
- * @see javascript/UploadField_uploadtemplate.js
- * @var string
- */
- 'uploadTemplateName' => 'ss-uploadfield-uploadtemplate',
+ /**
+ * javascript template used to display uploading files
+ *
+ * @see javascript/UploadField_uploadtemplate.js
+ * @var string
+ */
+ 'uploadTemplateName' => 'ss-uploadfield-uploadtemplate',
- /**
- * javascript template used to display already uploaded files
- *
- * @see javascript/UploadField_downloadtemplate.js
- * @var string
- */
- 'downloadTemplateName' => 'ss-uploadfield-downloadtemplate'
- );
+ /**
+ * javascript template used to display already uploaded files
+ *
+ * @see javascript/UploadField_downloadtemplate.js
+ * @var string
+ */
+ 'downloadTemplateName' => 'ss-uploadfield-downloadtemplate'
+ );
- /**
- * Folder to display in "Select files" list.
- * Defaults to listing all files regardless of folder.
- * The folder path should be relative to the webroot.
- * See {@link FileField->folderName} to set the upload target instead.
- *
- * @var string
- * @example admin/folder/subfolder
- */
- protected $displayFolderName;
+ /**
+ * Folder to display in "Select files" list.
+ * Defaults to listing all files regardless of folder.
+ * The folder path should be relative to the webroot.
+ * See {@link FileField->folderName} to set the upload target instead.
+ *
+ * @var string
+ * @example admin/folder/subfolder
+ */
+ protected $displayFolderName;
- /**
- * Construct a new UploadField instance
- *
- * @param string $name The internal field name, passed to forms.
- * @param string $title The field label.
- */
+ /**
+ * Construct a new UploadField instance
+ *
+ * @param string $name The internal field name, passed to forms.
+ * @param string $title The field label.
+ */
public function __construct($name, $title = null)
{
- $this->addExtraClass('ss-upload'); // class, used by js
- $this->addExtraClass('ss-uploadfield'); // class, used by css for uploadfield only
+ $this->addExtraClass('ss-upload'); // class, used by js
+ $this->addExtraClass('ss-uploadfield'); // class, used by css for uploadfield only
- $this->ufConfig = array_merge($this->ufConfig, self::config()->defaultConfig);
+ $this->ufConfig = array_merge($this->ufConfig, self::config()->defaultConfig);
- parent::__construct($name, $title);
+ $this->constructUploadable();
+ parent::__construct($name, $title);
- // AssetField always uses rename replacement method
- $this->getUpload()->setReplaceFile(false);
+ // AssetField always uses rename replacement method
+ $this->getUpload()->setReplaceFile(false);
- // filter out '' since this would be a regex problem on JS end
- $this->getValidator()->setAllowedExtensions(
- array_filter(File::config()->allowed_extensions)
- );
+ // filter out '' since this would be a regex problem on JS end
+ $this->getValidator()->setAllowedExtensions(
+ array_filter(File::config()->allowed_extensions)
+ );
- // get the lower max size
- $maxUpload = File::ini2bytes(ini_get('upload_max_filesize'));
- $maxPost = File::ini2bytes(ini_get('post_max_size'));
- $this->getValidator()->setAllowedMaxFileSize(min($maxUpload, $maxPost));
- }
+ // get the lower max size
+ $maxUpload = File::ini2bytes(ini_get('upload_max_filesize'));
+ $maxPost = File::ini2bytes(ini_get('post_max_size'));
+ $this->getValidator()->setAllowedMaxFileSize(min($maxUpload, $maxPost));
+ }
- /**
- * Set name of template used for Buttons on each file (replace, edit, remove, delete) (without path or extension)
- *
- * @param string
- * @return $this
- */
+ /**
+ * Set name of template used for Buttons on each file (replace, edit, remove, delete) (without path or extension)
+ *
+ * @param string
+ * @return $this
+ */
public function setTemplateFileButtons($template)
{
- $this->templateFileButtons = $template;
- return $this;
- }
+ $this->templateFileButtons = $template;
+ return $this;
+ }
- /**
- * @return string
- */
+ /**
+ * @return string
+ */
public function getTemplateFileButtons()
{
- return $this->_templates($this->templateFileButtons, '_FileButtons');
- }
+ return $this->_templates($this->templateFileButtons, '_FileButtons');
+ }
- /**
- * Determine if the target folder for new uploads in is visible the field UI.
- *
- * @return boolean
- */
+ /**
+ * Determine if the target folder for new uploads in is visible the field UI.
+ *
+ * @return boolean
+ */
public function canPreviewFolder()
{
- if (!$this->isActive()) {
- return false;
- }
- $can = $this->getConfig('canPreviewFolder');
- if (is_bool($can)) {
- return $can;
- }
- return Permission::check($can);
- }
+ if(!$this->isActive()) {
+ return false;
+ }
+ $can = $this->getConfig('canPreviewFolder');
+ if(is_bool($can)) {
+ return $can;
+ }
+ return Permission::check($can);
+ }
- /**
- * Determine if the target folder for new uploads in is visible the field UI.
- * Disable to keep the internal filesystem structure hidden from users.
- *
- * @param boolean|string $canPreviewFolder Either a boolean flag, or a
- * required permission code
- * @return $this Self reference
- */
+ /**
+ * Determine if the target folder for new uploads in is visible the field UI.
+ * Disable to keep the internal filesystem structure hidden from users.
+ *
+ * @param boolean|string $canPreviewFolder Either a boolean flag, or a
+ * required permission code
+ * @return $this Self reference
+ */
public function setCanPreviewFolder($canPreviewFolder)
{
- return $this->setConfig('canPreviewFolder', $canPreviewFolder);
- }
+ return $this->setConfig('canPreviewFolder', $canPreviewFolder);
+ }
- /**
- * @param string
- * @return $this
- */
+ /**
+ * @param string
+ * @return $this
+ */
public function setDisplayFolderName($name)
{
- $this->displayFolderName = $name;
- return $this;
- }
+ $this->displayFolderName = $name;
+ return $this;
+ }
- /**
- * @return string
- */
+ /**
+ * @return string
+ */
public function getDisplayFolderName()
{
- return $this->displayFolderName;
- }
+ return $this->displayFolderName;
+ }
- /**
- * Force a record to be used as "Parent" for uploaded Files (eg a Page with a has_one to File)
- *
- * @param DataObject $record
- * @return $this
- */
+ /**
+ * Force a record to be used as "Parent" for uploaded Files (eg a Page with a has_one to File)
+ *
+ * @param DataObject $record
+ * @return $this
+ */
public function setRecord($record)
{
- $this->record = $record;
- return $this;
- }
+ $this->record = $record;
+ return $this;
+ }
- /**
- * Get the record to use as "Parent" for uploaded Files (eg a Page with a has_one to File) If none is set, it will
- * use Form->getRecord().
- *
- * @return DataObject
- */
+ /**
+ * Get the record to use as "Parent" for uploaded Files (eg a Page with a has_one to File) If none is set, it will
+ * use Form->getRecord().
+ *
+ * @return DataObject
+ */
public function getRecord()
{
- if (!$this->record
- && $this->form
- && ($record = $this->form->getRecord())
- && $record instanceof DataObject
- ) {
- $this->record = $record;
- }
- return $this->record;
- }
+ if (!$this->record
+ && $this->form
+ && ($record = $this->form->getRecord())
+ && $record instanceof DataObject
+ ) {
+ $this->record = $record;
+ }
+ return $this->record;
+ }
public function setValue($value, $record = null)
{
- // Extract value from underlying record
- if (empty($value) && $this->getName() && $record instanceof DataObject) {
- $name = $this->getName();
- $value = $record->$name;
- }
+ // Extract value from underlying record
+ if(empty($value) && $this->getName() && $record instanceof DataObject) {
+ $name = $this->getName();
+ $value = $record->$name;
+ }
- // Convert asset container to tuple value
- if ($value instanceof AssetContainer) {
- if ($value->exists()) {
- $value = array(
- 'Filename' => $value->getFilename(),
- 'Hash' => $value->getHash(),
- 'Variant' => $value->getVariant()
- );
- } else {
- $value = null;
- }
- }
+ // Convert asset container to tuple value
+ if($value instanceof AssetContainer) {
+ if($value->exists()) {
+ $value = array(
+ 'Filename' => $value->getFilename(),
+ 'Hash' => $value->getHash(),
+ 'Variant' => $value->getVariant()
+ );
+ } else {
+ $value = null;
+ }
+ }
- // If javascript is disabled, direct file upload (non-html5 style) can
- // trigger a single or multiple file submission. Note that this may be
- // included in addition to re-submitted File IDs as above, so these
- // should be added to the list instead of operated on independently.
- if ($uploadedFile = $this->extractUploadedFileData($value)) {
- $value = $this->saveTemporaryFile($uploadedFile, $error);
- if (!$value) {
- throw new ValidationException($error);
- }
- }
+ // If javascript is disabled, direct file upload (non-html5 style) can
+ // trigger a single or multiple file submission. Note that this may be
+ // included in addition to re-submitted File IDs as above, so these
+ // should be added to the list instead of operated on independently.
+ if($uploadedFile = $this->extractUploadedFileData($value)) {
+ $value = $this->saveTemporaryFile($uploadedFile, $error);
+ if(!$value) {
+ throw new ValidationException($error);
+ }
+ }
- // Set value using parent
- return parent::setValue($value, $record);
- }
+ // Set value using parent
+ return parent::setValue($value, $record);
+ }
public function Value()
{
- // Re-override FileField Value to use data value
- return $this->dataValue();
- }
+ // Re-override FileField Value to use data value
+ return $this->dataValue();
+ }
public function saveInto(DataObjectInterface $record)
{
- // Check required relation details are available
- $name = $this->getName();
- if (!$name) {
- return $this;
- }
- $value = $this->Value();
- foreach (array('Filename', 'Hash', 'Variant') as $part) {
- $partValue = isset($value[$part])
- ? $value[$part]
- : null;
- $record->setField("{$name}{$part}", $partValue);
- }
- return $this;
- }
+ // Check required relation details are available
+ $name = $this->getName();
+ if(!$name) {
+ return $this;
+ }
+ $value = $this->Value();
+ foreach(array('Filename', 'Hash', 'Variant') as $part) {
+ $partValue = isset($value[$part])
+ ? $value[$part]
+ : null;
+ $record->setField("{$name}{$part}", $partValue);
+ }
+ return $this;
+ }
- /**
- * Assign a front-end config variable for the upload field
- *
- * @see https://github.com/blueimp/jQuery-File-Upload/wiki/Options for the list of front end options available
- *
- * @param string $key
- * @param mixed $val
- * @return $this self reference
- */
+ /**
+ * Assign a front-end config variable for the upload field
+ *
+ * @see https://github.com/blueimp/jQuery-File-Upload/wiki/Options for the list of front end options available
+ *
+ * @param string $key
+ * @param mixed $val
+ * @return $this self reference
+ */
public function setConfig($key, $val)
{
- $this->ufConfig[$key] = $val;
- return $this;
- }
+ $this->ufConfig[$key] = $val;
+ return $this;
+ }
- /**
- * Gets a front-end config variable for the upload field
- *
- * @see https://github.com/blueimp/jQuery-File-Upload/wiki/Options for the list of front end options available
- *
- * @param string $key
- * @return mixed
- */
+ /**
+ * Gets a front-end config variable for the upload field
+ *
+ * @see https://github.com/blueimp/jQuery-File-Upload/wiki/Options for the list of front end options available
+ *
+ * @param string $key
+ * @return mixed
+ */
public function getConfig($key)
{
- if (isset($this->ufConfig[$key])) {
- return $this->ufConfig[$key];
- }
- }
+ if(isset($this->ufConfig[$key])) {
+ return $this->ufConfig[$key];
+ }
+ }
- /**
- * Determine if the field should automatically upload the file.
- *
- * @return boolean
- */
+ /**
+ * Determine if the field should automatically upload the file.
+ *
+ * @return boolean
+ */
public function getAutoUpload()
{
- return $this->getConfig('autoUpload');
- }
+ return $this->getConfig('autoUpload');
+ }
- /**
- * Determine if the field should automatically upload the file
- *
- * @param boolean $autoUpload
- * @return $this Self reference
- */
+ /**
+ * Determine if the field should automatically upload the file
+ *
+ * @param boolean $autoUpload
+ * @return $this Self reference
+ */
public function setAutoUpload($autoUpload)
{
- return $this->setConfig('autoUpload', $autoUpload);
- }
+ return $this->setConfig('autoUpload', $autoUpload);
+ }
- /**
- * Determine if the user has permission to upload.
- *
- * @return boolean
- */
+ /**
+ * Determine if the user has permission to upload.
+ *
+ * @return boolean
+ */
public function canUpload()
{
- if (!$this->isActive()) {
- return false;
- }
- $can = $this->getConfig('canUpload');
- if (is_bool($can)) {
- return $can;
- }
- return Permission::check($can);
- }
+ if(!$this->isActive()) {
+ return false;
+ }
+ $can = $this->getConfig('canUpload');
+ if(is_bool($can)) {
+ return $can;
+ }
+ return Permission::check($can);
+ }
- /**
- * Specify whether the user can upload files.
- * String values will be treated as required permission codes
- *
- * @param bool|string $canUpload Either a boolean flag, or a required
- * permission code
- * @return $this Self reference
- */
+ /**
+ * Specify whether the user can upload files.
+ * String values will be treated as required permission codes
+ *
+ * @param bool|string $canUpload Either a boolean flag, or a required
+ * permission code
+ * @return $this Self reference
+ */
public function setCanUpload($canUpload)
{
- return $this->setConfig('canUpload', $canUpload);
- }
+ return $this->setConfig('canUpload', $canUpload);
+ }
- /**
- * Returns true if the field is neither readonly nor disabled
- *
- * @return bool
- */
+ /**
+ * Returns true if the field is neither readonly nor disabled
+ *
+ * @return bool
+ */
public function isActive()
{
- return !$this->isDisabled() && !$this->isReadonly();
- }
+ return !$this->isDisabled() && !$this->isReadonly();
+ }
- /**
- * Gets thumbnail width. Defaults to 80
- *
- * @return int
- */
+ /**
+ * Gets thumbnail width. Defaults to 80
+ *
+ * @return int
+ */
public function getPreviewMaxWidth()
{
- return $this->getConfig('previewMaxWidth');
- }
+ return $this->getConfig('previewMaxWidth');
+ }
- /**
- * Set thumbnail width.
- *
- * @param int $previewMaxWidth
- * @return $this Self reference
- */
+ /**
+ * Set thumbnail width.
+ *
+ * @param int $previewMaxWidth
+ * @return $this Self reference
+ */
public function setPreviewMaxWidth($previewMaxWidth)
{
- return $this->setConfig('previewMaxWidth', $previewMaxWidth);
- }
+ return $this->setConfig('previewMaxWidth', $previewMaxWidth);
+ }
- /**
- * Gets thumbnail height. Defaults to 60
- *
- * @return int
- */
+ /**
+ * Gets thumbnail height. Defaults to 60
+ *
+ * @return int
+ */
public function getPreviewMaxHeight()
{
- return $this->getConfig('previewMaxHeight');
- }
+ return $this->getConfig('previewMaxHeight');
+ }
- /**
- * Set thumbnail height.
- *
- * @param int $previewMaxHeight
- * @return $this Self reference
- */
+ /**
+ * Set thumbnail height.
+ *
+ * @param int $previewMaxHeight
+ * @return $this Self reference
+ */
public function setPreviewMaxHeight($previewMaxHeight)
{
- return $this->setConfig('previewMaxHeight', $previewMaxHeight);
- }
+ return $this->setConfig('previewMaxHeight', $previewMaxHeight);
+ }
- /**
- * javascript template used to display uploading files
- * Defaults to 'ss-uploadfield-uploadtemplate'
- *
- * @see javascript/UploadField_uploadtemplate.js
- * @return string
- */
+ /**
+ * javascript template used to display uploading files
+ * Defaults to 'ss-uploadfield-uploadtemplate'
+ *
+ * @see javascript/UploadField_uploadtemplate.js
+ * @return string
+ */
public function getUploadTemplateName()
{
- return $this->getConfig('uploadTemplateName');
- }
+ return $this->getConfig('uploadTemplateName');
+ }
- /**
- * Set javascript template used to display uploading files
- *
- * @param string $uploadTemplateName
- * @return $this Self reference
- */
+ /**
+ * Set javascript template used to display uploading files
+ *
+ * @param string $uploadTemplateName
+ * @return $this Self reference
+ */
public function setUploadTemplateName($uploadTemplateName)
{
- return $this->setConfig('uploadTemplateName', $uploadTemplateName);
- }
+ return $this->setConfig('uploadTemplateName', $uploadTemplateName);
+ }
- /**
- * javascript template used to display already uploaded files
- * Defaults to 'ss-downloadfield-downloadtemplate'
- *
- * @see javascript/DownloadField_downloadtemplate.js
- * @return string
- */
+ /**
+ * javascript template used to display already uploaded files
+ * Defaults to 'ss-downloadfield-downloadtemplate'
+ *
+ * @see javascript/DownloadField_downloadtemplate.js
+ * @return string
+ */
public function getDownloadTemplateName()
{
- return $this->getConfig('downloadTemplateName');
- }
+ return $this->getConfig('downloadTemplateName');
+ }
- /**
- * Set javascript template used to display already uploaded files
- *
- * @param string $downloadTemplateName
- * @return $this Self reference
- */
+ /**
+ * Set javascript template used to display already uploaded files
+ *
+ * @param string $downloadTemplateName
+ * @return $this Self reference
+ */
public function setDownloadTemplateName($downloadTemplateName)
{
- return $this->setConfig('downloadTemplateName', $downloadTemplateName);
- }
+ return $this->setConfig('downloadTemplateName', $downloadTemplateName);
+ }
public function extraClass()
{
- if ($this->isDisabled()) {
- $this->addExtraClass('disabled');
- }
- if ($this->isReadonly()) {
- $this->addExtraClass('readonly');
- }
+ if($this->isDisabled()) {
+ $this->addExtraClass('disabled');
+ }
+ if($this->isReadonly()) {
+ $this->addExtraClass('readonly');
+ }
- return parent::extraClass();
- }
+ return parent::extraClass();
+ }
public function Field($properties = array())
{
- // Calculated config as per jquery.fileupload-ui.js
- $config = array(
- 'allowedMaxFileNumber' => 1, // Only one file allowed for AssetField
- 'url' => $this->Link('upload'),
- 'urlSelectDialog' => $this->Link('select'),
- 'urlAttach' => $this->Link('attach'),
- 'urlFileExists' => $this->link('fileexists'),
- 'acceptFileTypes' => '.+$',
- // Fileupload treats maxNumberOfFiles as the max number of _additional_ items allowed
- 'maxNumberOfFiles' => $this->Value() ? 0 : 1,
- 'replaceFile' => false, // Should always be false for AssetField
- );
+ // Calculated config as per jquery.fileupload-ui.js
+ $config = array(
+ 'allowedMaxFileNumber' => 1, // Only one file allowed for AssetField
+ 'url' => $this->Link('upload'),
+ 'urlSelectDialog' => $this->Link('select'),
+ 'urlAttach' => $this->Link('attach'),
+ 'urlFileExists' => $this->link('fileexists'),
+ 'acceptFileTypes' => '.+$',
+ // Fileupload treats maxNumberOfFiles as the max number of _additional_ items allowed
+ 'maxNumberOfFiles' => $this->Value() ? 0 : 1,
+ 'replaceFile' => false, // Should always be false for AssetField
+ );
- // Validation: File extensions
- if ($allowedExtensions = $this->getAllowedExtensions()) {
- $config['acceptFileTypes'] = '(\.|\/)(' . implode('|', $allowedExtensions) . ')$';
- $config['errorMessages']['acceptFileTypes'] = _t(
- 'File.INVALIDEXTENSIONSHORT',
- 'Extension is not allowed'
- );
- }
+ // Validation: File extensions
+ if ($allowedExtensions = $this->getAllowedExtensions()) {
+ $config['acceptFileTypes'] = '(\.|\/)(' . implode('|', $allowedExtensions) . ')$';
+ $config['errorMessages']['acceptFileTypes'] = _t(
+ 'File.INVALIDEXTENSIONSHORT',
+ 'Extension is not allowed'
+ );
+ }
- // Validation: File size
- if ($allowedMaxFileSize = $this->getValidator()->getAllowedMaxFileSize()) {
- $config['maxFileSize'] = $allowedMaxFileSize;
- $config['errorMessages']['maxFileSize'] = _t(
- 'File.TOOLARGESHORT',
- 'Filesize exceeds {size}',
- array('size' => File::format_size($config['maxFileSize']))
- );
- }
+ // Validation: File size
+ if ($allowedMaxFileSize = $this->getValidator()->getAllowedMaxFileSize()) {
+ $config['maxFileSize'] = $allowedMaxFileSize;
+ $config['errorMessages']['maxFileSize'] = _t(
+ 'File.TOOLARGESHORT',
+ 'Filesize exceeds {size}',
+ array('size' => File::format_size($config['maxFileSize']))
+ );
+ }
- $mergedConfig = array_merge($config, $this->ufConfig);
- return $this->customise(array(
- 'ConfigString' => Convert::raw2json($mergedConfig),
- 'UploadFieldFileButtons' => $this->renderWith($this->getTemplateFileButtons())
- ))->renderWith($this->getTemplates());
- }
+ $mergedConfig = array_merge($config, $this->ufConfig);
+ return $this->customise(array(
+ 'ConfigString' => Convert::raw2json($mergedConfig),
+ 'UploadFieldFileButtons' => $this->renderWith($this->getTemplateFileButtons())
+ ))->renderWith($this->getTemplates());
+ }
- /**
- * Validation method for this field, called when the entire form is validated
- *
- * @param Validator $validator
- * @return boolean
- */
+ /**
+ * Validation method for this field, called when the entire form is validated
+ *
+ * @param Validator $validator
+ * @return boolean
+ */
public function validate($validator)
{
- $name = $this->getName();
- $value = $this->Value();
+ $name = $this->getName();
+ $value = $this->Value();
- // If there is no file then quit
- if (!$value) {
- return true;
- }
+ // If there is no file then quit
+ if(!$value) {
+ return true;
+ }
- // Revalidate each file against nested validator
- $this->getUpload()->clearErrors();
+ // Revalidate each file against nested validator
+ $this->getUpload()->clearErrors();
- // Generate $_FILES style file attribute array for upload validator
- $store = $this->getAssetStore();
- $mime = $store->getMimeType($value['Filename'], $value['Hash'], $value['Variant']);
- $metadata = $store->getMetadata($value['Filename'], $value['Hash'], $value['Variant']);
- $tmpFile = array(
- 'name' => $value['Filename'],
- 'type' => $mime,
- 'size' => isset($metadata['size']) ? $metadata['size'] : 0,
- 'tmp_name' => null, // Should bypass is_uploaded_file check
- 'error' => UPLOAD_ERR_OK,
- );
- $this->getUpload()->validate($tmpFile);
+ // Generate $_FILES style file attribute array for upload validator
+ $store = $this->getAssetStore();
+ $mime = $store->getMimeType($value['Filename'], $value['Hash'], $value['Variant']);
+ $metadata = $store->getMetadata($value['Filename'], $value['Hash'], $value['Variant']);
+ $tmpFile = array(
+ 'name' => $value['Filename'],
+ 'type' => $mime,
+ 'size' => isset($metadata['size']) ? $metadata['size'] : 0,
+ 'tmp_name' => null, // Should bypass is_uploaded_file check
+ 'error' => UPLOAD_ERR_OK,
+ );
+ $this->getUpload()->validate($tmpFile);
- // Check all errors
- if ($errors = $this->getUpload()->getErrors()) {
- foreach ($errors as $error) {
- $validator->validationError($name, $error, "validation");
- }
- return false;
- }
+ // Check all errors
+ if($errors = $this->getUpload()->getErrors()) {
+ foreach($errors as $error) {
+ $validator->validationError($name, $error, "validation");
+ }
+ return false;
+ }
- return true;
- }
+ return true;
+ }
- /**
- * Given an array of post variables, extract all temporary file data into an array
- *
- * @param array $postVars Array of posted form data
- * @return array data for uploaded file
- */
+ /**
+ * Given an array of post variables, extract all temporary file data into an array
+ *
+ * @param array $postVars Array of posted form data
+ * @return array data for uploaded file
+ */
protected function extractUploadedFileData($postVars)
{
- // Note: Format of posted file parameters in php is a feature of using
- // for multiple file uploads
+ // Note: Format of posted file parameters in php is a feature of using
+ // for multiple file uploads
- // Skip empty file
- if (empty($postVars['tmp_name'])) {
- return null;
- }
+ // Skip empty file
+ if(empty($postVars['tmp_name'])) {
+ return null;
+ }
- // Return single level array for posted file
- /** @skipUpgrade */
- if (empty($postVars['tmp_name']['Upload'])) {
- return $postVars;
- }
+ // Return single level array for posted file
+ /** @skipUpgrade */
+ if(empty($postVars['tmp_name']['Upload'])) {
+ return $postVars;
+ }
- // Extract posted feedback value
- $tmpFile = array();
- foreach (array('name', 'type', 'tmp_name', 'error', 'size') as $field) {
- /** @skipUpgrade */
- $tmpFile[$field] = $postVars[$field]['Upload'];
- }
- return $tmpFile;
- }
+ // Extract posted feedback value
+ $tmpFile = array();
+ foreach(array('name', 'type', 'tmp_name', 'error', 'size') as $field) {
+ /** @skipUpgrade */
+ $tmpFile[$field] = $postVars[$field]['Upload'];
+ }
+ return $tmpFile;
+ }
- /**
- * Loads the temporary file data into the asset store, and return the tuple details
- * for the result.
- *
- * @param array $tmpFile Temporary file data
- * @param string $error Error message
- * @return array Result of saved file, or null if error
- */
+ /**
+ * Loads the temporary file data into the asset store, and return the tuple details
+ * for the result.
+ *
+ * @param array $tmpFile Temporary file data
+ * @param string $error Error message
+ * @return array Result of saved file, or null if error
+ */
protected function saveTemporaryFile($tmpFile, &$error = null)
{
- $error = null;
- if (empty($tmpFile)) {
- $error = _t('UploadField.FIELDNOTSET', 'File information not found');
- return null;
- }
+ $error = null;
+ if (empty($tmpFile)) {
+ $error = _t('UploadField.FIELDNOTSET', 'File information not found');
+ return null;
+ }
- if ($tmpFile['error']) {
- $error = $tmpFile['error'];
- return null;
- }
+ if($tmpFile['error']) {
+ $error = $tmpFile['error'];
+ return null;
+ }
- // Get the uploaded file into a new file object.
- try {
- $result = $this
- ->getUpload()
- ->load($tmpFile, $this->getFolderName());
- } catch (Exception $e) {
- // we shouldn't get an error here, but just in case
- $error = $e->getMessage();
- return null;
- }
+ // Get the uploaded file into a new file object.
+ try {
+ $result = $this
+ ->getUpload()
+ ->load($tmpFile, $this->getFolderName());
+ } catch (Exception $e) {
+ // we shouldn't get an error here, but just in case
+ $error = $e->getMessage();
+ return null;
+ }
- // Check if upload field has an error
- if ($this->getUpload()->isError()) {
- $error = implode(' ' . PHP_EOL, $this->getUpload()->getErrors());
- return null;
- }
+ // Check if upload field has an error
+ if ($this->getUpload()->isError()) {
+ $error = implode(' ' . PHP_EOL, $this->getUpload()->getErrors());
+ return null;
+ }
- // return tuple array of Filename, Hash and Variant
- return $result;
- }
+ // return tuple array of Filename, Hash and Variant
+ return $result;
+ }
- /**
- * Safely encodes the File object with all standard fields required
- * by the front end
- *
- * @param string $filename
- * @param string $hash
- * @param string $variant
- * @return array Encoded list of file attributes
- */
+ /**
+ * Safely encodes the File object with all standard fields required
+ * by the front end
+ *
+ * @param string $filename
+ * @param string $hash
+ * @param string $variant
+ * @return array Encoded list of file attributes
+ */
protected function encodeAssetAttributes($filename, $hash, $variant)
{
- // Force regeneration of file thumbnail for this tuple (without saving into db)
- $object = DBFile::create();
- $object->setValue(array('Filename' => $filename, 'Hash' => $hash, 'Variant' => $variant));
+ // Force regeneration of file thumbnail for this tuple (without saving into db)
+ $object = DBFile::create();
+ $object->setValue(array('Filename' => $filename, 'Hash' => $hash, 'Variant' => $variant));
- return array(
- 'filename' => $filename,
- 'hash' => $hash,
- 'variant' => $variant,
- 'name' => $object->getBasename(),
- 'url' => $object->getURL(),
- 'thumbnail_url' => $object->ThumbnailURL(
- $this->getPreviewMaxWidth(),
- $this->getPreviewMaxHeight()
- ),
- 'size' => $object->getAbsoluteSize(),
- 'type' => File::get_file_type($object->getFilename()),
- 'buttons' => (string)$this->renderWith($this->getTemplateFileButtons()),
- 'fieldname' => $this->getName()
- );
- }
+ return array(
+ 'filename' => $filename,
+ 'hash' => $hash,
+ 'variant' => $variant,
+ 'name' => $object->getBasename(),
+ 'url' => $object->getURL(),
+ 'thumbnail_url' => $object->ThumbnailURL(
+ $this->getPreviewMaxWidth(),
+ $this->getPreviewMaxHeight()
+ ),
+ 'size' => $object->getAbsoluteSize(),
+ 'type' => File::get_file_type($object->getFilename()),
+ 'buttons' => (string)$this->renderWith($this->getTemplateFileButtons()),
+ 'fieldname' => $this->getName()
+ );
+ }
- /**
- * Action to handle upload of a single file
- *
- * @param HTTPRequest $request
- * @return HTTPResponse
- */
+ /**
+ * Action to handle upload of a single file
+ *
+ * @param HTTPRequest $request
+ * @return HTTPResponse
+ */
public function upload(HTTPRequest $request)
{
- if ($this->isDisabled() || $this->isReadonly() || !$this->canUpload()) {
- return $this->httpError(403);
- }
+ if($this->isDisabled() || $this->isReadonly() || !$this->canUpload()) {
+ return $this->httpError(403);
+ }
- // Protect against CSRF on destructive action
- $token = $this
- ->getForm()
- ->getSecurityToken();
- if (!$token->checkRequest($request)) {
- return $this->httpError(400);
- }
+ // Protect against CSRF on destructive action
+ $token = $this
+ ->getForm()
+ ->getSecurityToken();
+ if(!$token->checkRequest($request)) {
+ return $this->httpError(400);
+ }
- // Get form details
- $name = $this->getName();
- $postVars = $request->postVar($name);
+ // Get form details
+ $name = $this->getName();
+ $postVars = $request->postVar($name);
- // Extract uploaded files from Form data
- $uploadedFile = $this->extractUploadedFileData($postVars);
- if (!$uploadedFile) {
- return $this->httpError(400);
- }
+ // Extract uploaded files from Form data
+ $uploadedFile = $this->extractUploadedFileData($postVars);
+ if(!$uploadedFile) {
+ return $this->httpError(400);
+ }
- // Save the temporary files into a File objects
- // and save data/error on a per file basis
- $result = $this->saveTemporaryFile($uploadedFile, $error);
- if (empty($result)) {
- $return = array('error' => $error);
- } else {
- $return = $this->encodeAssetAttributes($result['Filename'], $result['Hash'], $result['Variant']);
- }
- $this
- ->getUpload()
- ->clearErrors();
+ // Save the temporary files into a File objects
+ // and save data/error on a per file basis
+ $result = $this->saveTemporaryFile($uploadedFile, $error);
+ if(empty($result)) {
+ $return = array('error' => $error);
+ } else {
+ $return = $this->encodeAssetAttributes($result['Filename'], $result['Hash'], $result['Variant']);
+ }
+ $this
+ ->getUpload()
+ ->clearErrors();
- // Format response with json
- $response = new HTTPResponse(Convert::raw2json(array($return)));
- $response->addHeader('Content-Type', 'text/plain');
- return $response;
- }
+ // Format response with json
+ $response = new HTTPResponse(Convert::raw2json(array($return)));
+ $response->addHeader('Content-Type', 'text/plain');
+ return $response;
+ }
public function performReadonlyTransformation()
{
- $clone = clone $this;
- $clone->addExtraClass('readonly');
- $clone->setReadonly(true);
- return $clone;
- }
+ $clone = clone $this;
+ $clone->addExtraClass('readonly');
+ $clone->setReadonly(true);
+ return $clone;
+ }
- /**
- * Gets the foreign class that needs to be created, or 'File' as default if there
- * is no relationship, or it cannot be determined.
- *
- * @param string $default Default value to return if no value could be calculated
- * @return string Foreign class name.
- */
+ /**
+ * Gets the foreign class that needs to be created, or 'File' as default if there
+ * is no relationship, or it cannot be determined.
+ *
+ * @param string $default Default value to return if no value could be calculated
+ * @return string Foreign class name.
+ */
public function getRelationAutosetClass($default = 'SilverStripe\\Assets\\File')
{
- // Don't autodetermine relation if no relationship between parent record
+ // Don't autodetermine relation if no relationship between parent record
if (!$this->relationAutoSetting) {
return $default;
}
- // Check record and name
- $name = $this->getName();
- $record = $this->getRecord();
- if (empty($name) || empty($record)) {
- return $default;
- } else {
- $class = $record->getRelationClass($name);
- return empty($class) ? $default : $class;
- }
- }
+ // Check record and name
+ $name = $this->getName();
+ $record = $this->getRecord();
+ if(empty($name) || empty($record)) {
+ return $default;
+ } else {
+ $class = $record->getRelationClass($name);
+ return empty($class) ? $default : $class;
+ }
+ }
- /**
- * @return AssetStore
- */
+ /**
+ * @return AssetStore
+ */
protected function getAssetStore()
{
- return Injector::inst()->get('AssetStore');
- }
+ return Injector::inst()->get('AssetStore');
+ }
+ public function getAttributes() {
+ return array_merge(
+ parent::getAttributes(),
+ ['type' => 'file']
+ );
+ }
+
}
diff --git a/src/Forms/FileField.php b/src/Forms/FileField.php
index b54cbda62..6f61a8759 100644
--- a/src/Forms/FileField.php
+++ b/src/Forms/FileField.php
@@ -2,10 +2,8 @@
namespace SilverStripe\Forms;
-use SilverStripe\Assets\Upload_Validator;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataObjectInterface;
-use SilverStripe\Assets\Upload;
use SilverStripe\Assets\File;
use SilverStripe\Core\Object;
@@ -27,273 +25,156 @@ use SilverStripe\Core\Object;
*
* class ExampleForm_Controller extends Page_Controller {
*
- * function Form() {
- * $fields = new FieldList(
- * new TextField('MyName'),
- * new FileField('MyFile')
- * );
- * $actions = new FieldList(
- * new FormAction('doUpload', 'Upload file')
- * );
+ * function Form() {
+ * $fields = new FieldList(
+ * new TextField('MyName'),
+ * new FileField('MyFile')
+ * );
+ * $actions = new FieldList(
+ * new FormAction('doUpload', 'Upload file')
+ * );
* $validator = new RequiredFields(array('MyName', 'MyFile'));
*
- * return new Form($this, 'Form', $fields, $actions, $validator);
- * }
+ * return new Form($this, 'Form', $fields, $actions, $validator);
+ * }
*
- * function doUpload($data, $form) {
- * $file = $data['MyFile'];
- * $content = file_get_contents($file['tmp_name']);
- * // ... process content
- * }
+ * function doUpload($data, $form) {
+ * $file = $data['MyFile'];
+ * $content = file_get_contents($file['tmp_name']);
+ * // ... process content
+ * }
* }
*
*/
-class FileField extends FormField
-{
+class FileField extends FormField {
+ use Uploadable;
- /**
- * Flag to automatically determine and save a has_one-relationship
- * on the saved record (e.g. a "Player" has_one "PlayerImage" would
- * trigger saving the ID of newly created file into "PlayerImageID"
- * on the record).
- *
- * @var boolean
- */
- public $relationAutoSetting = true;
+ /**
+ * Flag to automatically determine and save a has_one-relationship
+ * on the saved record (e.g. a "Player" has_one "PlayerImage" would
+ * trigger saving the ID of newly created file into "PlayerImageID"
+ * on the record).
+ *
+ * @var boolean
+ */
+ protected $relationAutoSetting = true;
- /**
- * Upload object (needed for validation
- * and actually moving the temporary file
- * created by PHP).
- *
- * @var Upload
- */
- protected $upload;
+ /**
+ * Create a new file field.
+ *
+ * @param string $name The internal field name, passed to forms.
+ * @param string $title The field label.
+ * @param int $value The value of the field.
+ */
+ public function __construct($name, $title = null, $value = null) {
+ $this->constructUploadable();
+ parent::__construct($name, $title, $value);
+ }
- /**
- * Partial filesystem path relative to /assets directory.
- * Defaults to Upload::$uploads_folder.
- *
- * @var string
- */
- protected $folderName = false;
+ /**
+ * @param array $properties
+ * @return string
+ */
+ public function Field($properties = array()) {
+ $properties = array_merge($properties, array(
+ 'MaxFileSize' => $this->getValidator()->getAllowedMaxFileSize()
+ ));
- /**
- * Create a new file field.
- *
- * @param string $name The internal field name, passed to forms.
- * @param string $title The field label.
- * @param int $value The value of the field.
- */
- public function __construct($name, $title = null, $value = null)
- {
- $this->upload = Upload::create();
- parent::__construct($name, $title, $value);
- }
+ return parent::Field($properties);
+ }
- /**
- * @param array $properties
- * @return string
- */
- public function Field($properties = array())
- {
- $properties = array_merge($properties, array(
- 'MaxFileSize' => $this->getValidator()->getAllowedMaxFileSize()
- ));
+ public function getAttributes() {
+ return array_merge(
+ parent::getAttributes(),
+ array('type' => 'file')
+ );
+ }
- return parent::Field($properties);
- }
+ /**
+ * @param DataObject|DataObjectInterface $record
+ */
+ public function saveInto(DataObjectInterface $record) {
+ if(!isset($_FILES[$this->name])) {
+ return;
+ }
- public function getAttributes()
- {
- return array_merge(
- parent::getAttributes(),
- array('type' => 'file')
- );
- }
+ $fileClass = File::get_class_for_file_extension(
+ File::get_file_extension($_FILES[$this->name]['name'])
+ );
- /**
- * @param DataObject|DataObjectInterface $record
- */
- public function saveInto(DataObjectInterface $record)
- {
- if (!isset($_FILES[$this->name])) {
- return;
- }
+ /** @var File $file */
+ if($this->relationAutoSetting) {
+ // assume that the file is connected via a has-one
+ $objectClass = DataObject::getSchema()->hasOneComponent(get_class($record), $this->name);
+ if($objectClass === File::class || empty($objectClass)) {
+ // Create object of the appropriate file class
+ $file = Object::create($fileClass);
+ } else {
+ // try to create a file matching the relation
+ $file = Object::create($objectClass);
+ }
+ } else if($record instanceof File) {
+ $file = $record;
+ } else {
+ $file = Object::create($fileClass);
+ }
- $fileClass = File::get_class_for_file_extension(
- File::get_file_extension($_FILES[$this->name]['name'])
- );
+ $this->upload->loadIntoFile($_FILES[$this->name], $file, $this->getFolderName());
- /** @var File $file */
- if ($this->relationAutoSetting) {
- // assume that the file is connected via a has-one
- $objectClass = DataObject::getSchema()->hasOneComponent(get_class($record), $this->name);
- if ($objectClass === File::class || empty($objectClass)) {
- // Create object of the appropriate file class
- $file = Object::create($fileClass);
- } else {
- // try to create a file matching the relation
- $file = Object::create($objectClass);
- }
- } elseif ($record instanceof File) {
- $file = $record;
- } else {
- $file = Object::create($fileClass);
- }
+ if($this->upload->isError()) {
+ return;
+ }
- $this->upload->loadIntoFile($_FILES[$this->name], $file, $this->getFolderName());
+ if($this->relationAutoSetting) {
+ if (empty($objectClass)) {
+ return;
+ }
- if ($this->upload->isError()) {
- return;
- }
+ $file = $this->upload->getFile();
- if ($this->relationAutoSetting) {
- if (empty($objectClass)) {
- return;
- }
+ $record->{$this->name . 'ID'} = $file->ID;
+ }
+ }
- $file = $this->upload->getFile();
+ public function Value() {
+ return isset($_FILES[$this->getName()]) ? $_FILES[$this->getName()] : null;
+ }
- $record->{$this->name . 'ID'} = $file->ID;
- }
- }
+ public function validate($validator) {
+ if(!isset($_FILES[$this->name])) return true;
- public function Value()
- {
- return isset($_FILES[$this->getName()]) ? $_FILES[$this->getName()] : null;
- }
+ $tmpFile = $_FILES[$this->name];
- /**
- * Get custom validator for this field
- *
- * @return Upload_Validator
- */
- public function getValidator()
- {
- return $this->upload->getValidator();
- }
+ $valid = $this->upload->validate($tmpFile);
+ if(!$valid) {
+ $errors = $this->upload->getErrors();
+ if($errors) foreach($errors as $error) {
+ $validator->validationError($this->name, $error, "validation");
+ }
+ return false;
+ }
- /**
- * Set custom validator for this field
- *
- * @param Upload_Validator $validator
- * @return $this Self reference
- */
- public function setValidator($validator)
- {
- $this->upload->setValidator($validator);
- return $this;
- }
+ return true;
+ }
- /**
- * Sets the upload folder name
- *
- * @param string $folderName
- * @return FileField Self reference
- */
- public function setFolderName($folderName)
- {
- $this->folderName = $folderName;
- return $this;
- }
+ /**
+ * Set if relation can be automatically assigned to the underlying dataobject
+ *
+ * @param bool $auto
+ * @return $this
+ */
+ public function setRelationAutoSetting($auto) {
+ $this->relationAutoSetting = $auto;
+ return $this;
+ }
- /**
- * Gets the upload folder name
- *
- * @return string
- */
- public function getFolderName()
- {
- return ($this->folderName !== false)
- ? $this->folderName
- : Upload::config()->uploads_folder;
- }
+ /**
+ * Check if relation can be automatically assigned to the underlying dataobject
+ *
+ * @return bool
+ */
+ public function getRelationAutoSetting() {
+ return $this->relationAutoSetting;
+ }
- public function validate($validator)
- {
- if (!isset($_FILES[$this->name])) {
- return true;
- }
-
- $tmpFile = $_FILES[$this->name];
-
- $valid = $this->upload->validate($tmpFile);
- if (!$valid) {
- $errors = $this->upload->getErrors();
- if ($errors) {
- foreach ($errors as $error) {
- $validator->validationError($this->name, $error, "validation");
- }
- }
- return false;
- }
-
- return true;
- }
-
- /**
- * Retrieves the Upload handler
- *
- * @return Upload
- */
- public function getUpload()
- {
- return $this->upload;
- }
-
- /**
- * Sets the upload handler
- *
- * @param Upload $upload
- * @return FileField Self reference
- */
- public function setUpload(Upload $upload)
- {
- $this->upload = $upload;
- return $this;
- }
-
- /**
- * Limit allowed file extensions. Empty by default, allowing all extensions.
- * To allow files without an extension, use an empty string.
- * See {@link File::$allowed_extensions} to get a good standard set of
- * extensions that are typically not harmful in a webserver context.
- * See {@link setAllowedMaxFileSize()} to limit file size by extension.
- *
- * @param array $rules List of extensions
- * @return $this
- */
- public function setAllowedExtensions($rules)
- {
- $this->getValidator()->setAllowedExtensions($rules);
- return $this;
- }
-
- /**
- * Limit allowed file extensions by specifying categories of file types.
- * These may be 'image', 'image/supported', 'audio', 'video', 'archive', 'flash', or 'document'
- * See {@link File::$allowed_extensions} for details of allowed extensions
- * for each of these categories
- *
- * @param string $category Category name
- * @param string,... $categories Additional category names
- * @return $this
- */
- public function setAllowedFileCategories($category)
- {
- $extensions = File::get_category_extensions(func_get_args());
- return $this->setAllowedExtensions($extensions);
- }
-
- /**
- * Returns list of extensions allowed by this field, or an empty array
- * if there is no restriction
- *
- * @return array
- */
- public function getAllowedExtensions()
- {
- return $this->getValidator()->getAllowedExtensions();
- }
}
diff --git a/src/Forms/FileUploadable.php b/src/Forms/FileUploadable.php
new file mode 100644
index 000000000..e67803e49
--- /dev/null
+++ b/src/Forms/FileUploadable.php
@@ -0,0 +1,399 @@
+constructUploadable();
+ }
+
+
+ /**
+ * Force a record to be used as "Parent" for uploaded Files (eg a Page with a has_one to File)
+ *
+ * @param DataObject $record
+ * @return $this
+ */
+ public function setRecord($record) {
+ $this->record = $record;
+ return $this;
+ }
+ /**
+ * Get the record to use as "Parent" for uploaded Files (eg a Page with a has_one to File) If none is set, it will
+ * use Form->getRecord() or Form->Controller()->data()
+ *
+ * @return DataObject
+ */
+ public function getRecord() {
+ if ($this->record) {
+ return $this->record;
+ }
+ if (!$this->getForm()) {
+ return null;
+ }
+
+ // Get record from form
+ $record = $this->getForm()->getRecord();
+ if ($record && ($record instanceof DataObject)) {
+ $this->record = $record;
+ return $record;
+ }
+
+ // Get record from controller
+ $controller = $this->getForm()->getController();
+ if ($controller
+ && $controller->hasMethod('data')
+ && ($record = $controller->data())
+ && ($record instanceof DataObject)
+ ) {
+ $this->record = $record;
+ return $record;
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Loads the related record values into this field. This can be uploaded
+ * in one of three ways:
+ *
+ * - By passing in a list of file IDs in the $value parameter (an array with a single
+ * key 'Files', with the value being the actual array of IDs).
+ * - By passing in an explicit list of File objects in the $record parameter, and
+ * leaving $value blank.
+ * - By passing in a dataobject in the $record parameter, from which file objects
+ * will be extracting using the field name as the relation field.
+ *
+ * Each of these methods will update both the items (list of File objects) and the
+ * field value (list of file ID values).
+ *
+ * @param array $value Array of submitted form data, if submitting from a form
+ * @param array|DataObject|SS_List $record Full source record, either as a DataObject,
+ * SS_List of items, or an array of submitted form data
+ * @return $this Self reference
+ * @throws ValidationException
+ */
+ public function setValue($value, $record = null) {
+
+ // If we're not passed a value directly, we can attempt to infer the field
+ // value from the second parameter by inspecting its relations
+ $items = new ArrayList();
+
+ // Determine format of presented data
+ if(empty($value) && $record) {
+ // If a record is given as a second parameter, but no submitted values,
+ // then we should inspect this instead for the form values
+
+ if(($record instanceof DataObject) && $record->hasMethod($this->getName())) {
+ // If given a dataobject use reflection to extract details
+
+ $data = $record->{$this->getName()}();
+ if($data instanceof DataObject) {
+ // If has_one, add sole item to default list
+ $items->push($data);
+ } elseif($data instanceof SS_List) {
+ // For many_many and has_many relations we can use the relation list directly
+ $items = $data;
+ }
+ } elseif($record instanceof SS_List) {
+ // If directly passing a list then save the items directly
+ $items = $record;
+ }
+ } elseif(!empty($value['Files'])) {
+ // If value is given as an array (such as a posted form), extract File IDs from this
+ $class = $this->getRelationAutosetClass();
+ $items = DataObject::get($class)->byIDs($value['Files']);
+ }
+
+ // If javascript is disabled, direct file upload (non-html5 style) can
+ // trigger a single or multiple file submission. Note that this may be
+ // included in addition to re-submitted File IDs as above, so these
+ // should be added to the list instead of operated on independently.
+ if($uploadedFiles = $this->extractUploadedFileData($value)) {
+ foreach($uploadedFiles as $tempFile) {
+ $file = $this->saveTemporaryFile($tempFile, $error);
+ if($file) {
+ $items->add($file);
+ } else {
+ throw new ValidationException($error);
+ }
+ }
+ }
+
+ // Filter items by what's allowed to be viewed
+ $filteredItems = new ArrayList();
+ $fileIDs = array();
+ foreach($items as $file) {
+ if($file->exists() && $file->canView()) {
+ $filteredItems->push($file);
+ $fileIDs[] = $file->ID;
+ }
+ }
+
+ // Filter and cache updated item list
+ $this->items = $filteredItems;
+ // Same format as posted form values for this field. Also ensures that
+ // $this->setValue($this->getValue()); is non-destructive
+ $value = $fileIDs ? array('Files' => $fileIDs) : null;
+
+ // Set value using parent
+ parent::setValue($value, $record);
+ return $this;
+ }
+
+ /**
+ * Sets the items assigned to this field as an SS_List of File objects.
+ * Calling setItems will also update the value of this field, as well as
+ * updating the internal list of File items.
+ *
+ * @param SS_List $items
+ * @return $this self reference
+ */
+ public function setItems(SS_List $items) {
+ return $this->setValue(null, $items);
+ }
+
+ /**
+ * Retrieves the current list of files
+ *
+ * @return SS_List|File[]
+ */
+ public function getItems() {
+ return $this->items ? $this->items : new ArrayList();
+ }
+
+ /**
+ * Retrieves the list of selected file IDs
+ *
+ * @return array
+ */
+ public function getItemIDs() {
+ $value = $this->Value();
+ return empty($value['Files']) ? array() : $value['Files'];
+ }
+
+ public function Value() {
+ // Re-override FileField Value to use data value
+ return $this->dataValue();
+ }
+
+ /**
+ * @param DataObject|DataObjectInterface $record
+ * @return $this
+ */
+ public function saveInto(DataObjectInterface $record) {
+ // Check required relation details are available
+ $fieldname = $this->getName();
+ if(!$fieldname) {
+ return $this;
+ }
+
+ // Get details to save
+ $idList = $this->getItemIDs();
+
+ // Check type of relation
+ $relation = $record->hasMethod($fieldname) ? $record->$fieldname() : null;
+ if($relation && ($relation instanceof RelationList || $relation instanceof UnsavedRelationList)) {
+ // has_many or many_many
+ $relation->setByIDList($idList);
+ } elseif($class = DataObject::getSchema()->hasOneComponent(get_class($record), $fieldname)) {
+ // Assign has_one ID
+ $id = $idList ? reset($idList) : 0;
+ $record->{"{$fieldname}ID"} = $id;
+
+ // Polymorphic asignment
+ if ($class === DataObject::class) {
+ $file = $id ? File::get()->byID($id) : null;
+ $fileClass = $file ? get_class($file) : File::class;
+ $record->{"{$fieldname}Class"} = $id ? $fileClass : null;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Loads the temporary file data into a File object
+ *
+ * @param array $tmpFile Temporary file data
+ * @param string $error Error message
+ * @return AssetContainer File object, or null if error
+ */
+ protected function saveTemporaryFile($tmpFile, &$error = null) {
+ // Determine container object
+ $error = null;
+ $fileObject = null;
+
+ if (empty($tmpFile)) {
+ $error = _t('UploadField.FIELDNOTSET', 'File information not found');
+ return null;
+ }
+
+ if($tmpFile['error']) {
+ $error = $tmpFile['error'];
+ return null;
+ }
+
+ // Search for relations that can hold the uploaded files, but don't fallback
+ // to default if there is no automatic relation
+ if ($relationClass = $this->getRelationAutosetClass(null)) {
+ // Allow File to be subclassed
+ if($relationClass === File::class && isset($tmpFile['name'])) {
+ $relationClass = File::get_class_for_file_extension(
+ File::get_file_extension($tmpFile['name'])
+ );
+ }
+ // Create new object explicitly. Otherwise rely on Upload::load to choose the class.
+ $fileObject = Object::create($relationClass);
+ if(! ($fileObject instanceof DataObject) || !($fileObject instanceof AssetContainer)) {
+ throw new InvalidArgumentException("Invalid asset container $relationClass");
+ }
+ }
+
+ // Get the uploaded file into a new file object.
+ try {
+ $this->getUpload()->loadIntoFile($tmpFile, $fileObject, $this->getFolderName());
+ } catch (Exception $e) {
+ // we shouldn't get an error here, but just in case
+ $error = $e->getMessage();
+ return null;
+ }
+
+ // Check if upload field has an error
+ if ($this->getUpload()->isError()) {
+ $error = implode(' ' . PHP_EOL, $this->getUpload()->getErrors());
+ return null;
+ }
+
+ // return file
+ return $this->getUpload()->getFile();
+ }
+
+ /**
+ * Gets the foreign class that needs to be created, or 'File' as default if there
+ * is no relationship, or it cannot be determined.
+ *
+ * @param string $default Default value to return if no value could be calculated
+ * @return string Foreign class name.
+ */
+ public function getRelationAutosetClass($default = File::class) {
+ // Don't autodetermine relation if no relationship between parent record
+ if(!$this->getRelationAutoSetting()) {
+ return $default;
+ }
+
+ // Check record and name
+ $name = $this->getName();
+ $record = $this->getRecord();
+ if(empty($name) || empty($record)) {
+ return $default;
+ } else {
+ $class = $record->getRelationClass($name);
+ return empty($class) ? $default : $class;
+ }
+ }
+
+ /**
+ * Set if relation can be automatically assigned to the underlying dataobject
+ *
+ * @param bool $auto
+ * @return $this
+ */
+ public function setRelationAutoSetting($auto) {
+ $this->relationAutoSetting = $auto;
+ return $this;
+ }
+
+ /**
+ * Check if relation can be automatically assigned to the underlying dataobject
+ *
+ * @return bool
+ */
+ public function getRelationAutoSetting() {
+ return $this->relationAutoSetting;
+ }
+
+ /**
+ * Given an array of post variables, extract all temporary file data into an array
+ *
+ * @param array $postVars Array of posted form data
+ * @return array List of temporary file data
+ */
+ protected function extractUploadedFileData($postVars) {
+ // Note: Format of posted file parameters in php is a feature of using
+ // for multiple file uploads
+ $tmpFiles = array();
+ if( !empty($postVars['tmp_name'])
+ && is_array($postVars['tmp_name'])
+ && !empty($postVars['tmp_name']['Uploads'])
+ ) {
+ for($i = 0; $i < count($postVars['tmp_name']['Uploads']); $i++) {
+ // Skip if "empty" file
+ if(empty($postVars['tmp_name']['Uploads'][$i])) {
+ continue;
+ }
+ $tmpFile = array();
+ foreach(array('name', 'type', 'tmp_name', 'error', 'size') as $field) {
+ $tmpFile[$field] = $postVars[$field]['Uploads'][$i];
+ }
+ $tmpFiles[] = $tmpFile;
+ }
+ } elseif(!empty($postVars['tmp_name'])) {
+ // Fallback to allow single file uploads (method used by AssetUploadField)
+ $tmpFiles[] = $postVars;
+ }
+
+ return $tmpFiles;
+ }
+}
diff --git a/src/Forms/UploadField.php b/src/Forms/UploadField.php
index cdaefb68d..904e94871 100644
--- a/src/Forms/UploadField.php
+++ b/src/Forms/UploadField.php
@@ -14,11 +14,7 @@ use SilverStripe\ORM\SS_List;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\ValidationException;
-use SilverStripe\ORM\DataObjectInterface;
-use SilverStripe\ORM\RelationList;
-use SilverStripe\ORM\UnsavedRelationList;
use SilverStripe\Security\Permission;
-use SilverStripe\View\Requirements;
use SilverStripe\View\ArrayData;
use SilverStripe\View\ViewableData;
use SilverStripe\View\ViewableData_Customised;
@@ -49,1396 +45,1000 @@ use Exception;
* Caution: The form field does not include any JavaScript or CSS when used outside of the CMS context,
* since the required frontend dependencies are included through CMS bundling.
*/
-class UploadField extends FileField
-{
+class UploadField extends FormField {
+ use FileUploadable;
- /**
- * @var array
- */
- private static $allowed_actions = array(
- 'upload',
- 'attach',
- 'handleItem',
- 'handleSelect',
- 'fileexists'
- );
+ /**
+ * @var array
+ */
+ private static $allowed_actions = array(
+ 'upload',
+ 'attach',
+ 'handleItem',
+ 'handleSelect',
+ 'fileexists'
+ );
- /**
- * @var array
- */
- private static $url_handlers = array(
- 'item/$ID' => 'handleItem',
- 'select' => 'handleSelect',
- '$Action!' => '$Action',
- );
+ /**
+ * @var array
+ */
+ private static $url_handlers = array(
+ 'item/$ID' => 'handleItem',
+ 'select' => 'handleSelect',
+ '$Action!' => '$Action',
+ );
- /**
- * Template to use for the file button widget
- *
- * @var string
- */
- protected $templateFileButtons = null;
+ /**
+ * Template to use for the file button widget
+ *
+ * @var string
+ */
+ protected $templateFileButtons = null;
- /**
- * Template to use for the edit form
- *
- * @var string
- */
- protected $templateFileEdit = null;
+ /**
+ * Template to use for the edit form
+ *
+ * @var string
+ */
+ protected $templateFileEdit = null;
- /**
- * Parent data record. Will be infered from parent form or controller if blank.
- *
- * @var DataObject
- */
- protected $record;
+ /**
+ * Config for this field used in the front-end javascript
+ * (will be merged into the config of the javascript file upload plugin).
+ *
+ * @var array
+ */
+ protected $ufConfig = array();
- /**
- * Items loaded into this field. May be a RelationList, or any other SS_List
- *
- * @var SS_List
- */
- protected $items;
+ /**
+ * Front end config defaults
+ *
+ * @config
+ * @var array
+ */
+ private static $defaultConfig = array(
+ /**
+ * Automatically upload the file once selected
+ *
+ * @var boolean
+ */
+ 'autoUpload' => true,
+ /**
+ * Restriction on number of files that may be set for this field. Set to null to allow
+ * unlimited. If record has a has_one and allowedMaxFileNumber is null, it will be set to 1.
+ * The resulting value will be set to maxNumberOfFiles
+ *
+ * @var integer
+ */
+ 'allowedMaxFileNumber' => null,
+ /**
+ * Can the user upload new files, or just select from existing files.
+ * String values are interpreted as permission codes.
+ *
+ * @var boolean|string
+ */
+ 'canUpload' => true,
+ /**
+ * Can the user attach files from the assets archive on the site?
+ * String values are interpreted as permission codes.
+ *
+ * @var boolean|string
+ */
+ 'canAttachExisting' => "CMS_ACCESS_AssetAdmin",
+ /**
+ * Shows the target folder for new uploads in the field UI.
+ * Disable to keep the internal filesystem structure hidden from users.
+ *
+ * @var boolean|string
+ */
+ 'canPreviewFolder' => true,
+ /**
+ * Indicate a change event to the containing form if an upload
+ * or file edit/delete was performed.
+ *
+ * @var boolean
+ */
+ 'changeDetection' => true,
+ /**
+ * Maximum width of the preview thumbnail
+ *
+ * @var integer
+ */
+ 'previewMaxWidth' => 80,
+ /**
+ * Maximum height of the preview thumbnail
+ *
+ * @var integer
+ */
+ 'previewMaxHeight' => 60,
+ /**
+ * javascript template used to display uploading files
+ *
+ * @see javascript/UploadField_uploadtemplate.js
+ * @var string
+ */
+ 'uploadTemplateName' => 'ss-uploadfield-uploadtemplate',
+ /**
+ * javascript template used to display already uploaded files
+ *
+ * @see javascript/UploadField_downloadtemplate.js
+ * @var string
+ */
+ 'downloadTemplateName' => 'ss-uploadfield-downloadtemplate',
+ /**
+ * Show a warning when overwriting a file.
+ * This requires Upload->replaceFile config to be set to true, otherwise
+ * files will be renamed instead of overwritten
+ *
+ * @see Upload
+ * @var boolean
+ */
+ 'overwriteWarning' => true
+ );
- /**
- * Config for this field used in the front-end javascript
- * (will be merged into the config of the javascript file upload plugin).
- *
- * @var array
- */
- protected $ufConfig = array();
+ /**
+ * @var String Folder to display in "Select files" list.
+ * Defaults to listing all files regardless of folder.
+ * The folder path should be relative to the webroot.
+ * See {@link FileField->folderName} to set the upload target instead.
+ * @example admin/folder/subfolder
+ */
+ protected $displayFolderName;
- /**
- * Front end config defaults
- *
- * @config
- * @var array
- */
- private static $defaultConfig = array(
- /**
- * Automatically upload the file once selected
- *
- * @var boolean
- */
- 'autoUpload' => true,
- /**
- * Restriction on number of files that may be set for this field. Set to null to allow
- * unlimited. If record has a has_one and allowedMaxFileNumber is null, it will be set to 1.
- * The resulting value will be set to maxNumberOfFiles
- *
- * @var integer
- */
- 'allowedMaxFileNumber' => null,
- /**
- * Can the user upload new files, or just select from existing files.
- * String values are interpreted as permission codes.
- *
- * @var boolean|string
- */
- 'canUpload' => true,
- /**
- * Can the user attach files from the assets archive on the site?
- * String values are interpreted as permission codes.
- *
- * @var boolean|string
- */
- 'canAttachExisting' => "CMS_ACCESS_AssetAdmin",
- /**
- * Shows the target folder for new uploads in the field UI.
- * Disable to keep the internal filesystem structure hidden from users.
- *
- * @var boolean|string
- */
- 'canPreviewFolder' => true,
- /**
- * Indicate a change event to the containing form if an upload
- * or file edit/delete was performed.
- *
- * @var boolean
- */
- 'changeDetection' => true,
- /**
- * Maximum width of the preview thumbnail
- *
- * @var integer
- */
- 'previewMaxWidth' => 80,
- /**
- * Maximum height of the preview thumbnail
- *
- * @var integer
- */
- 'previewMaxHeight' => 60,
- /**
- * javascript template used to display uploading files
- *
- * @see javascript/UploadField_uploadtemplate.js
- * @var string
- */
- 'uploadTemplateName' => 'ss-uploadfield-uploadtemplate',
- /**
- * javascript template used to display already uploaded files
- *
- * @see javascript/UploadField_downloadtemplate.js
- * @var string
- */
- 'downloadTemplateName' => 'ss-uploadfield-downloadtemplate',
- /**
- * Show a warning when overwriting a file.
- * This requires Upload->replaceFile config to be set to true, otherwise
- * files will be renamed instead of overwritten
- *
- * @see Upload
- * @var boolean
- */
- 'overwriteWarning' => true
- );
+ /**
+ * FieldList $fields or string $name (of a method on File to provide a fields) for the EditForm
+ * @example 'getCMSFields'
+ *
+ * @var FieldList|string
+ */
+ protected $fileEditFields = null;
- /**
- * @var String Folder to display in "Select files" list.
- * Defaults to listing all files regardless of folder.
- * The folder path should be relative to the webroot.
- * See {@link FileField->folderName} to set the upload target instead.
- * @example admin/folder/subfolder
- */
- protected $displayFolderName;
+ /**
+ * FieldList $actions or string $name (of a method on File to provide a actions) for the EditForm
+ * @example 'getCMSActions'
+ *
+ * @var FieldList|string
+ */
+ protected $fileEditActions = null;
- /**
- * FieldList $fields or string $name (of a method on File to provide a fields) for the EditForm
- * @example 'getCMSFields'
- *
- * @var FieldList|string
- */
- protected $fileEditFields = null;
+ /**
+ * Validator (eg RequiredFields) or string $name (of a method on File to provide a Validator) for the EditForm
+ * @example 'getCMSValidator'
+ *
+ * @var RequiredFields|string
+ */
+ protected $fileEditValidator = null;
- /**
- * FieldList $actions or string $name (of a method on File to provide a actions) for the EditForm
- * @example 'getCMSActions'
- *
- * @var FieldList|string
- */
- protected $fileEditActions = null;
+ /**
+ * Construct a new UploadField instance
+ *
+ * @param string $name The internal field name, passed to forms.
+ * @param string $title The field label.
+ * @param SS_List $items If no items are defined, the field will try to auto-detect an existing relation on
+ * @link $record}, with the same name as the field name.
+ */
+ public function __construct($name, $title = null, SS_List $items = null) {
+ // TODO thats the first thing that came to my head, feel free to change it
+ $this->addExtraClass('ss-upload'); // class, used by js
+ $this->addExtraClass('ss-uploadfield'); // class, used by css for uploadfield only
- /**
- * Validator (eg RequiredFields) or string $name (of a method on File to provide a Validator) for the EditForm
- * @example 'getCMSValidator'
- *
- * @var RequiredFields|string
- */
- protected $fileEditValidator = null;
+ $this->ufConfig = self::config()->defaultConfig;
+ $this->constructFileUploadable();
- /**
- * Construct a new UploadField instance
- *
- * @param string $name The internal field name, passed to forms.
- * @param string $title The field label.
- * @param SS_List $items If no items are defined, the field will try to auto-detect an existing relation on
- * @link $record}, with the same name as the field name.
- */
- public function __construct($name, $title = null, SS_List $items = null)
- {
-
- // TODO thats the first thing that came to my head, feel free to change it
- $this->addExtraClass('ss-upload'); // class, used by js
- $this->addExtraClass('ss-uploadfield'); // class, used by css for uploadfield only
-
- $this->ufConfig = self::config()->defaultConfig;
-
- parent::__construct($name, $title);
+ parent::__construct($name, $title);
if ($items) {
$this->setItems($items);
}
+ }
+
+ /**
+ * Set name of template used for Buttons on each file (replace, edit, remove, delete) (without path or extension)
+ *
+ * @param string $template
+ * @return $this
+ */
+ public function setTemplateFileButtons($template) {
+ $this->templateFileButtons = $template;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTemplateFileButtons() {
+ return $this->_templates($this->templateFileButtons, '_FileButtons');
+ }
+
+ /**
+ * Set name of template used for the edit (inline & popup) of a file file (without path or extension)
+ *
+ * @param string $template
+ * @return $this
+ */
+ public function setTemplateFileEdit($template) {
+ $this->templateFileEdit = $template;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTemplateFileEdit() {
+ return $this->_templates($this->templateFileEdit, '_FileEdit');
+ }
+
+ /**
+ * Determine if the target folder for new uploads in is visible the field UI.
+ *
+ * @return boolean
+ */
+ public function canPreviewFolder() {
+ if(!$this->isActive()) return false;
+ $can = $this->getConfig('canPreviewFolder');
+ return (is_bool($can)) ? $can : Permission::check($can);
+ }
+
+ /**
+ * Determine if the target folder for new uploads in is visible the field UI.
+ * Disable to keep the internal filesystem structure hidden from users.
+ *
+ * @param boolean|string $canPreviewFolder Either a boolean flag, or a
+ * required permission code
+ * @return UploadField Self reference
+ */
+ public function setCanPreviewFolder($canPreviewFolder) {
+ return $this->setConfig('canPreviewFolder', $canPreviewFolder);
+ }
+
+ /**
+ * Determine if the field should show a warning when overwriting a file.
+ * This requires Upload->replaceFile config to be set to true, otherwise
+ * files will be renamed instead of overwritten (although the warning will
+ * still be displayed)
+ *
+ * @return boolean
+ */
+ public function getOverwriteWarning() {
+ return $this->getConfig('overwriteWarning');
+ }
+
+ /**
+ * Determine if the field should show a warning when overwriting a file.
+ * This requires Upload->replaceFile config to be set to true, otherwise
+ * files will be renamed instead of overwritten (although the warning will
+ * still be displayed)
+ *
+ * @param boolean $overwriteWarning
+ * @return UploadField Self reference
+ */
+ public function setOverwriteWarning($overwriteWarning) {
+ return $this->setConfig('overwriteWarning', $overwriteWarning);
+ }
+
+ /**
+ * @param string $name
+ * @return $this
+ */
+ public function setDisplayFolderName($name) {
+ $this->displayFolderName = $name;
+ return $this;
+ }
+
+ /**
+ * @return String
+ */
+ public function getDisplayFolderName() {
+ return $this->displayFolderName;
+ }
+
+
+
+ /**
+ * Retrieves a customised list of all File records to ensure they are
+ * properly viewable when rendered in the field template.
+ *
+ * @return SS_List[ViewableData_Customised]
+ */
+ public function getCustomisedItems() {
+ $customised = new ArrayList();
+ foreach($this->getItems() as $file) {
+ $customised->push($this->customiseFile($file));
+ }
+ return $customised;
+ }
+
+ /**
+ * Customises a file with additional details suitable for rendering in the
+ * UploadField.ss template
+ *
+ * @param ViewableData|AssetContainer $file
+ * @return ViewableData_Customised
+ */
+ protected function customiseFile(AssetContainer $file) {
+ $file = $file->customise(array(
+ 'UploadFieldThumbnailURL' => $this->getThumbnailURLForFile($file),
+ 'UploadFieldDeleteLink' => $this->getItemHandler($file->ID)->DeleteLink(),
+ 'UploadFieldEditLink' => $this->getItemHandler($file->ID)->EditLink(),
+ 'UploadField' => $this
+ ));
+ // we do this in a second customise to have the access to the previous customisations
+ return $file->customise(array(
+ 'UploadFieldFileButtons' => $file->renderWith($this->getTemplateFileButtons())
+ ));
+ }
+
+ /**
+ * Assign a front-end config variable for the upload field
+ *
+ * @see https://github.com/blueimp/jQuery-File-Upload/wiki/Options for the list of front end options available
+ *
+ * @param string $key
+ * @param mixed $val
+ * @return UploadField self reference
+ */
+ public function setConfig($key, $val) {
+ $this->ufConfig[$key] = $val;
+ return $this;
+ }
+
+ /**
+ * Gets a front-end config variable for the upload field
+ *
+ * @see https://github.com/blueimp/jQuery-File-Upload/wiki/Options for the list of front end options available
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function getConfig($key) {
+ if(!isset($this->ufConfig[$key])) return null;
+ return $this->ufConfig[$key];
+ }
+
+ /**
+ * Determine if the field should automatically upload the file.
+ *
+ * @return boolean
+ */
+ public function getAutoUpload() {
+ return $this->getConfig('autoUpload');
+ }
+
+ /**
+ * Determine if the field should automatically upload the file
+ *
+ * @param boolean $autoUpload
+ * @return UploadField Self reference
+ */
+ public function setAutoUpload($autoUpload) {
+ return $this->setConfig('autoUpload', $autoUpload);
+ }
+
+ /**
+ * Determine maximum number of files allowed to be attached
+ * Defaults to 1 for has_one and null (unlimited) for
+ * many_many and has_many relations.
+ *
+ * @return integer|null Maximum limit, or null for no limit
+ */
+ public function getAllowedMaxFileNumber() {
+ $allowedMaxFileNumber = $this->getConfig('allowedMaxFileNumber');
+
+ // if there is a has_one relation with that name on the record and
+ // allowedMaxFileNumber has not been set, it's wanted to be 1
+ if(empty($allowedMaxFileNumber)) {
+ $record = $this->getRecord();
+ $name = $this->getName();
+ if($record && DataObject::getSchema()->hasOneComponent(get_class($record), $name)) {
+ return 1; // Default for has_one
+ } else {
+ return null; // Default for has_many and many_many
+ }
+ } else {
+ return $allowedMaxFileNumber;
+ }
+ }
+
+ /**
+ * Determine maximum number of files allowed to be attached.
+ *
+ * @param integer|null $allowedMaxFileNumber Maximum limit. 0 or null will be treated as unlimited
+ * @return UploadField Self reference
+ */
+ public function setAllowedMaxFileNumber($allowedMaxFileNumber) {
+ return $this->setConfig('allowedMaxFileNumber', $allowedMaxFileNumber);
+ }
+
+ /**
+ * Determine if the user has permission to upload.
+ *
+ * @return boolean
+ */
+ public function canUpload() {
+ if(!$this->isActive()) return false;
+ $can = $this->getConfig('canUpload');
+ return (is_bool($can)) ? $can : Permission::check($can);
+ }
+
+ /**
+ * Specify whether the user can upload files.
+ * String values will be treated as required permission codes
+ *
+ * @param boolean|string $canUpload Either a boolean flag, or a required
+ * permission code
+ * @return UploadField Self reference
+ */
+ public function setCanUpload($canUpload) {
+ return $this->setConfig('canUpload', $canUpload);
+ }
+
+ /**
+ * Determine if the user has permission to attach existing files
+ * By default returns true if the user has the CMS_ACCESS_AssetAdmin permission
+ *
+ * @return boolean
+ */
+ public function canAttachExisting() {
+ if(!$this->isActive()) return false;
+ $can = $this->getConfig('canAttachExisting');
+ return (is_bool($can)) ? $can : Permission::check($can);
+ }
+
+ /**
+ * Returns true if the field is neither readonly nor disabled
+ *
+ * @return boolean
+ */
+ public function isActive() {
+ return !$this->isDisabled() && !$this->isReadonly();
+ }
+
+ /**
+ * Specify whether the user can attach existing files
+ * String values will be treated as required permission codes
+ *
+ * @param boolean|string $canAttachExisting Either a boolean flag, or a
+ * required permission code
+ * @return UploadField Self reference
+ */
+ public function setCanAttachExisting($canAttachExisting) {
+ return $this->setConfig('canAttachExisting', $canAttachExisting);
+ }
+
+ /**
+ * Gets thumbnail width. Defaults to 80
+ *
+ * @return integer
+ */
+ public function getPreviewMaxWidth() {
+ return $this->getConfig('previewMaxWidth');
+ }
+
+ /**
+ * @see UploadField::getPreviewMaxWidth()
+ *
+ * @param integer $previewMaxWidth
+ * @return UploadField Self reference
+ */
+ public function setPreviewMaxWidth($previewMaxWidth) {
+ return $this->setConfig('previewMaxWidth', $previewMaxWidth);
+ }
+
+ /**
+ * Gets thumbnail height. Defaults to 60
+ *
+ * @return integer
+ */
+ public function getPreviewMaxHeight() {
+ return $this->getConfig('previewMaxHeight');
+ }
+
+ /**
+ * @see UploadField::getPreviewMaxHeight()
+ *
+ * @param integer $previewMaxHeight
+ * @return UploadField Self reference
+ */
+ public function setPreviewMaxHeight($previewMaxHeight) {
+ return $this->setConfig('previewMaxHeight', $previewMaxHeight);
+ }
+
+ /**
+ * javascript template used to display uploading files
+ * Defaults to 'ss-uploadfield-uploadtemplate'
+ *
+ * @see javascript/UploadField_uploadtemplate.js
+ * @return string
+ */
+ public function getUploadTemplateName() {
+ return $this->getConfig('uploadTemplateName');
+ }
+
+ /**
+ * @see UploadField::getUploadTemplateName()
+ *
+ * @param string $uploadTemplateName
+ * @return UploadField Self reference
+ */
+ public function setUploadTemplateName($uploadTemplateName) {
+ return $this->setConfig('uploadTemplateName', $uploadTemplateName);
+ }
+
+ /**
+ * javascript template used to display already uploaded files
+ * Defaults to 'ss-downloadfield-downloadtemplate'
+ *
+ * @see javascript/DownloadField_downloadtemplate.js
+ * @return string
+ */
+ public function getDownloadTemplateName() {
+ return $this->getConfig('downloadTemplateName');
+ }
+
+ /**
+ * @see Uploadfield::getDownloadTemplateName()
+ *
+ * @param string $downloadTemplateName
+ * @return Uploadfield Self reference
+ */
+ public function setDownloadTemplateName($downloadTemplateName) {
+ return $this->setConfig('downloadTemplateName', $downloadTemplateName);
+ }
+
+ /**
+ * FieldList $fields for the EditForm
+ * @example 'getCMSFields'
+ *
+ * @param DataObject $file File context to generate fields for
+ * @return FieldList List of form fields
+ */
+ public function getFileEditFields(DataObject $file) {
+ // Empty actions, generate default
+ if(empty($this->fileEditFields)) {
+ $fields = $file->getCMSFields();
+ // Only display main tab, to avoid overly complex interface
+ if($fields->hasTabSet() && ($mainTab = $fields->findOrMakeTab('Root.Main'))) {
+ $fields = $mainTab->Fields();
+ }
+ return $fields;
+ }
+
+ // Fields instance
+ if ($this->fileEditFields instanceof FieldList) {
+ return $this->fileEditFields;
+ }
+
+ // Method to call on the given file
+ if($file->hasMethod($this->fileEditFields)) {
+ return $file->{$this->fileEditFields}();
+ }
+
+ throw new InvalidArgumentException("Invalid value for UploadField::fileEditFields");
+ }
+
+ /**
+ * FieldList $fields or string $name (of a method on File to provide a fields) for the EditForm
+ * @example 'getCMSFields'
+ *
+ * @param FieldList|string
+ * @return Uploadfield Self reference
+ */
+ public function setFileEditFields($fileEditFields) {
+ $this->fileEditFields = $fileEditFields;
+ return $this;
+ }
+
+ /**
+ * FieldList $actions or string $name (of a method on File to provide a actions) for the EditForm
+ * @example 'getCMSActions'
+ *
+ * @param DataObject $file File context to generate form actions for
+ * @return FieldList Field list containing FormAction
+ */
+ public function getFileEditActions(DataObject $file) {
+ // Empty actions, generate default
+ if(empty($this->fileEditActions)) {
+ $actions = new FieldList($saveAction = new FormAction('doEdit', _t('UploadField.DOEDIT', 'Save')));
+ $saveAction->addExtraClass('ss-ui-action-constructive icon-accept');
+ return $actions;
+ }
+
+ // Actions instance
+ if ($this->fileEditActions instanceof FieldList) {
+ return $this->fileEditActions;
+ }
+
+ // Method to call on the given file
+ if($file->hasMethod($this->fileEditActions)) {
+ return $file->{$this->fileEditActions}();
+ }
+
+ throw new InvalidArgumentException("Invalid value for UploadField::fileEditActions");
+ }
+
+ /**
+ * FieldList $actions or string $name (of a method on File to provide a actions) for the EditForm
+ * @example 'getCMSActions'
+ *
+ * @param FieldList|string
+ * @return Uploadfield Self reference
+ */
+ public function setFileEditActions($fileEditActions) {
+ $this->fileEditActions = $fileEditActions;
+ return $this;
+ }
+
+ /**
+ * Determines the validator to use for the edit form
+ * @example 'getCMSValidator'
+ *
+ * @param DataObject $file File context to generate validator from
+ * @return Validator Validator object
+ */
+ public function getFileEditValidator(DataObject $file) {
+ // Empty validator
+ if(empty($this->fileEditValidator)) {
+ return null;
+ }
+
+ // Validator instance
+ if($this->fileEditValidator instanceof Validator) {
+ return $this->fileEditValidator;
+ }
+
+ // Method to call on the given file
+ if($file->hasMethod($this->fileEditValidator)) {
+ return $file->{$this->fileEditValidator}();
+ }
+
+ throw new InvalidArgumentException("Invalid value for UploadField::fileEditValidator");
+ }
+
+ /**
+ * Validator (eg RequiredFields) or string $name (of a method on File to provide a Validator) for the EditForm
+ * @example 'getCMSValidator'
+ *
+ * @param Validator|string
+ * @return Uploadfield Self reference
+ */
+ public function setFileEditValidator($fileEditValidator) {
+ $this->fileEditValidator = $fileEditValidator;
+ return $this;
+ }
+
+ /**
+ *
+ * @param File|AssetContainer $file
+ * @return string URL to thumbnail
+ */
+ protected function getThumbnailURLForFile(AssetContainer $file) {
+ if (!$file->exists()) {
+ return null;
+ }
+
+ // Attempt to generate image at given size
+ $width = $this->getPreviewMaxWidth();
+ $height = $this->getPreviewMaxHeight();
+ if ($file->hasMethod('ThumbnailURL')) {
+ return $file->ThumbnailURL($width, $height);
+ }
+ if ($file->hasMethod('Thumbnail')) {
+ return $file->Thumbnail($width, $height)->getURL();
+ }
+ if ($file->hasMethod('Fit')) {
+ return $file->Fit($width, $height)->getURL();
+ }
+
+ // Check if unsized icon is available
+ if($file->hasMethod('getIcon')) {
+ return $file->getIcon();
+ }
+ return null;
+ }
+
+ public function getAttributes() {
+ return array_merge(
+ parent::getAttributes(),
+ array(
+ 'type' => 'file',
+ 'data-selectdialog-url' => $this->Link('select')
+ )
+ );
+ }
+
+ public function extraClass() {
+ if($this->isDisabled()) {
+ $this->addExtraClass('disabled');
+ }
+ if($this->isReadonly()) {
+ $this->addExtraClass('readonly');
+ }
+
+ return parent::extraClass();
+ }
+
+ public function Field($properties = array()) {
+ // Calculated config as per jquery.fileupload-ui.js
+ $allowedMaxFileNumber = $this->getAllowedMaxFileNumber();
+ $config = array(
+ 'url' => $this->Link('upload'),
+ 'urlSelectDialog' => $this->Link('select'),
+ 'urlAttach' => $this->Link('attach'),
+ 'urlFileExists' => $this->Link('fileexists'),
+ 'acceptFileTypes' => '.+$',
+ // Fileupload treats maxNumberOfFiles as the max number of _additional_ items allowed
+ 'maxNumberOfFiles' => $allowedMaxFileNumber ? ($allowedMaxFileNumber - count($this->getItemIDs())) : null,
+ 'replaceFile' => $this->getUpload()->getReplaceFile(),
+ );
+
+ // Validation: File extensions
+ if ($allowedExtensions = $this->getAllowedExtensions()) {
+ $config['acceptFileTypes'] = '(\.|\/)(' . implode('|', $allowedExtensions) . ')$';
+ $config['errorMessages']['acceptFileTypes'] = _t(
+ 'File.INVALIDEXTENSIONSHORT',
+ 'Extension is not allowed'
+ );
+ }
+
+ // Validation: File size
+ if ($allowedMaxFileSize = $this->getValidator()->getAllowedMaxFileSize()) {
+ $config['maxFileSize'] = $allowedMaxFileSize;
+ $config['errorMessages']['maxFileSize'] = _t(
+ 'File.TOOLARGESHORT',
+ 'File size exceeds {size}',
+ array('size' => File::format_size($config['maxFileSize']))
+ );
+ }
+
+ // Validation: Number of files
+ if ($allowedMaxFileNumber) {
+ if($allowedMaxFileNumber > 1) {
+ $config['errorMessages']['maxNumberOfFiles'] = _t(
+ 'UploadField.MAXNUMBEROFFILESSHORT',
+ 'Can only upload {count} files',
+ array('count' => $allowedMaxFileNumber)
+ );
+ } else {
+ $config['errorMessages']['maxNumberOfFiles'] = _t(
+ 'UploadField.MAXNUMBEROFFILESONE',
+ 'Can only upload one file'
+ );
+ }
+ }
+
+ // add overwrite warning error message to the config object sent to Javascript
+ if ($this->getOverwriteWarning()) {
+ $config['errorMessages']['overwriteWarning'] =
+ _t('UploadField.OVERWRITEWARNING', 'File with the same name already exists');
+ }
+
+ $mergedConfig = array_merge($config, $this->ufConfig);
+ return parent::Field(array(
+ 'configString' => Convert::raw2json($mergedConfig),
+ 'config' => new ArrayData($mergedConfig),
+ 'multiple' => $allowedMaxFileNumber !== 1
+ ));
+ }
+
+ /**
+ * Validation method for this field, called when the entire form is validated
+ *
+ * @param Validator $validator
+ * @return boolean
+ */
+ public function validate($validator) {
+ $name = $this->getName();
+ $files = $this->getItems();
+
+ // If there are no files then quit
+ if($files->count() == 0) return true;
+
+ // Check max number of files
+ $maxFiles = $this->getAllowedMaxFileNumber();
+ if($maxFiles && ($files->count() > $maxFiles)) {
+ $validator->validationError(
+ $name,
+ _t(
+ 'UploadField.MAXNUMBEROFFILES',
+ 'Max number of {count} file(s) exceeded.',
+ array('count' => $maxFiles)
+ ),
+ "validation"
+ );
+ return false;
+ }
+
+ // Revalidate each file against nested validator
+ $this->upload->clearErrors();
+ foreach($files as $file) {
+ // Generate $_FILES style file attribute array for upload validator
+ $tmpFile = array(
+ 'name' => $file->Name,
+ 'type' => null, // Not used for type validation
+ 'size' => $file->AbsoluteSize,
+ 'tmp_name' => null, // Should bypass is_uploaded_file check
+ 'error' => UPLOAD_ERR_OK,
+ );
+ $this->upload->validate($tmpFile);
+ }
+
+ // Check all errors
+ if($errors = $this->upload->getErrors()) {
+ foreach($errors as $error) {
+ $validator->validationError($name, $error, "validation");
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param HTTPRequest $request
+ * @return UploadField_ItemHandler
+ */
+ public function handleItem(HTTPRequest $request) {
+ return $this->getItemHandler($request->param('ID'));
+ }
+
+ /**
+ * @param int $itemID
+ * @return UploadField_ItemHandler
+ */
+ public function getItemHandler($itemID) {
+ return UploadField_ItemHandler::create($this, $itemID);
+ }
+
+ /**
+ * @param HTTPRequest $request
+ * @return UploadField_SelectHandler
+ */
+ public function handleSelect(HTTPRequest $request) {
+ if(!$this->canAttachExisting()) {
+ return $this->httpError(403);
+ }
+ return UploadField_SelectHandler::create($this, $this->getFolderName());
+ }
+
+ /**
+ * Safely encodes the File object with all standard fields required
+ * by the front end
+ *
+ * @param File|AssetContainer $file Object which contains a file
+ * @return array Array encoded list of file attributes
+ */
+ protected function encodeFileAttributes(AssetContainer $file) {
+ // Collect all output data.
+ $customised = $this->customiseFile($file);
+ return array(
+ 'id' => $file->ID,
+ 'name' => basename($file->getFilename()),
+ 'url' => $file->getURL(),
+ 'thumbnail_url' => $customised->UploadFieldThumbnailURL,
+ 'edit_url' => $customised->UploadFieldEditLink,
+ 'size' => $file->getAbsoluteSize(),
+ 'type' => File::get_file_type($file->getFilename()),
+ 'buttons' => (string)$customised->UploadFieldFileButtons,
+ 'fieldname' => $this->getName()
+ );
+ }
+
+ /**
+ * Action to handle upload of a single file
+ *
+ * @param HTTPRequest $request
+ * @return HTTPResponse
+ * @return HTTPResponse
+ */
+ public function upload(HTTPRequest $request) {
+ if($this->isDisabled() || $this->isReadonly() || !$this->canUpload()) {
+ return $this->httpError(403);
+ }
+
+ // Protect against CSRF on destructive action
+ $token = $this->getForm()->getSecurityToken();
+ if(!$token->checkRequest($request)) return $this->httpError(400);
+
+ // Get form details
+ $name = $this->getName();
+ $postVars = $request->postVar($name);
+
+ // Extract uploaded files from Form data
+ $uploadedFiles = $this->extractUploadedFileData($postVars);
+ $return = array();
+
+ // Save the temporary files into a File objects
+ // and save data/error on a per file basis
+ foreach ($uploadedFiles as $tempFile) {
+ $file = $this->saveTemporaryFile($tempFile, $error);
+ if(empty($file)) {
+ array_push($return, array('error' => $error));
+ } else {
+ array_push($return, $this->encodeFileAttributes($file));
+ }
+ $this->upload->clearErrors();
+ }
+
+ // Format response with json
+ $response = new HTTPResponse(Convert::raw2json($return));
+ $response->addHeader('Content-Type', 'text/plain');
+ return $response;
+ }
+
+ /**
+ * Retrieves details for files that this field wishes to attache to the
+ * client-side form
+ *
+ * @param HTTPRequest $request
+ * @return HTTPResponse
+ */
+ public function attach(HTTPRequest $request) {
+ if(!$request->isPOST()) return $this->httpError(403);
+ if(!$this->canAttachExisting()) return $this->httpError(403);
+
+ // Retrieve file attributes required by front end
+ $return = array();
+ $files = File::get()->byIDs($request->postVar('ids'));
+ foreach($files as $file) {
+ $return[] = $this->encodeFileAttributes($file);
+ }
+ $response = new HTTPResponse(Convert::raw2json($return));
+ $response->addHeader('Content-Type', 'application/json');
+ return $response;
+ }
+
+ /**
+ * Check if file exists, both checking filtered filename and exact filename
+ *
+ * @param string $originalFile Filename
+ * @return bool
+ */
+ protected function checkFileExists($originalFile) {
+
+ // Check both original and safely filtered filename
+ $nameFilter = FileNameFilter::create();
+ $filteredFile = $nameFilter->filter($originalFile);
+
+ // Resolve expected folder name
+ $folderName = $this->getFolderName();
+ $folder = Folder::find_or_make($folderName);
+ $parentPath = $folder ? $folder->getFilename() : '';
+
+ // check if either file exists
+ return File::find($parentPath.$originalFile) || File::find($parentPath.$filteredFile);
+ }
+
+ /**
+ * Determines if a specified file exists
+ *
+ * @param HTTPRequest $request
+ * @return HTTPResponse
+ */
+ public function fileexists(HTTPRequest $request) {
+ // Assert that requested filename doesn't attempt to escape the directory
+ $originalFile = $request->requestVar('filename');
+ if($originalFile !== basename($originalFile)) {
+ $return = array(
+ 'error' => _t('File.NOVALIDUPLOAD', 'File is not a valid upload')
+ );
+ } else {
+ $return = array(
+ 'exists' => $this->checkFileExists($originalFile)
+ );
+ }
+
+ // Encode and present response
+ $response = new HTTPResponse(Convert::raw2json($return));
+ $response->addHeader('Content-Type', 'application/json');
+ if (!empty($return['error'])) $response->setStatusCode(400);
+ return $response;
+ }
+
+ public function performReadonlyTransformation() {
+ $clone = clone $this;
+ $clone->addExtraClass('readonly');
+ $clone->setReadonly(true);
+ return $clone;
+ }
- // filter out '' since this would be a regex problem on JS end
- $this->getValidator()->setAllowedExtensions(
- array_filter(File::config()->allowed_extensions)
- );
-
- // get the lower max size
- $maxUpload = File::ini2bytes(ini_get('upload_max_filesize'));
- $maxPost = File::ini2bytes(ini_get('post_max_size'));
- $this->getValidator()->setAllowedMaxFileSize(min($maxUpload, $maxPost));
- }
-
- /**
- * Set name of template used for Buttons on each file (replace, edit, remove, delete) (without path or extension)
- *
- * @param string $template
- * @return $this
- */
- public function setTemplateFileButtons($template)
- {
- $this->templateFileButtons = $template;
- return $this;
- }
-
- /**
- * @return string
- */
- public function getTemplateFileButtons()
- {
- return $this->_templates($this->templateFileButtons, '_FileButtons');
- }
-
- /**
- * Set name of template used for the edit (inline & popup) of a file file (without path or extension)
- *
- * @param string $template
- * @return $this
- */
- public function setTemplateFileEdit($template)
- {
- $this->templateFileEdit = $template;
- return $this;
- }
-
- /**
- * @return string
- */
- public function getTemplateFileEdit()
- {
- return $this->_templates($this->templateFileEdit, '_FileEdit');
- }
-
- /**
- * Determine if the target folder for new uploads in is visible the field UI.
- *
- * @return boolean
- */
- public function canPreviewFolder()
- {
- if (!$this->isActive()) {
- return false;
- }
- $can = $this->getConfig('canPreviewFolder');
- return (is_bool($can)) ? $can : Permission::check($can);
- }
-
- /**
- * Determine if the target folder for new uploads in is visible the field UI.
- * Disable to keep the internal filesystem structure hidden from users.
- *
- * @param boolean|string $canPreviewFolder Either a boolean flag, or a
- * required permission code
- * @return UploadField Self reference
- */
- public function setCanPreviewFolder($canPreviewFolder)
- {
- return $this->setConfig('canPreviewFolder', $canPreviewFolder);
- }
-
- /**
- * Determine if the field should show a warning when overwriting a file.
- * This requires Upload->replaceFile config to be set to true, otherwise
- * files will be renamed instead of overwritten (although the warning will
- * still be displayed)
- *
- * @return boolean
- */
- public function getOverwriteWarning()
- {
- return $this->getConfig('overwriteWarning');
- }
-
- /**
- * Determine if the field should show a warning when overwriting a file.
- * This requires Upload->replaceFile config to be set to true, otherwise
- * files will be renamed instead of overwritten (although the warning will
- * still be displayed)
- *
- * @param boolean $overwriteWarning
- * @return UploadField Self reference
- */
- public function setOverwriteWarning($overwriteWarning)
- {
- return $this->setConfig('overwriteWarning', $overwriteWarning);
- }
-
- /**
- * @param string $name
- * @return $this
- */
- public function setDisplayFolderName($name)
- {
- $this->displayFolderName = $name;
- return $this;
- }
-
- /**
- * @return String
- */
- public function getDisplayFolderName()
- {
- return $this->displayFolderName;
- }
-
- /**
- * Force a record to be used as "Parent" for uploaded Files (eg a Page with a has_one to File)
- *
- * @param DataObject $record
- * @return $this
- */
- public function setRecord($record)
- {
- $this->record = $record;
- return $this;
- }
- /**
- * Get the record to use as "Parent" for uploaded Files (eg a Page with a has_one to File) If none is set, it will
- * use Form->getRecord() or Form->Controller()->data()
- *
- * @return DataObject
- */
- public function getRecord()
- {
- if (!$this->record && $this->form) {
- if (($record = $this->form->getRecord()) && ($record instanceof DataObject)) {
- $this->record = $record;
- } elseif (($controller = $this->form->getController())
- && $controller->hasMethod('data')
- && ($record = $controller->data())
- && ($record instanceof DataObject)
- ) {
- $this->record = $record;
- }
- }
- return $this->record;
- }
-
- /**
- * Loads the related record values into this field. UploadField can be uploaded
- * in one of three ways:
- *
- * - By passing in a list of file IDs in the $value parameter (an array with a single
- * key 'Files', with the value being the actual array of IDs).
- * - By passing in an explicit list of File objects in the $record parameter, and
- * leaving $value blank.
- * - By passing in a dataobject in the $record parameter, from which file objects
- * will be extracting using the field name as the relation field.
- *
- * Each of these methods will update both the items (list of File objects) and the
- * field value (list of file ID values).
- *
- * @param array $value Array of submitted form data, if submitting from a form
- * @param array|DataObject|SS_List $record Full source record, either as a DataObject,
- * SS_List of items, or an array of submitted form data
- * @return $this Self reference
- * @throws ValidationException
- */
- public function setValue($value, $record = null)
- {
-
- // If we're not passed a value directly, we can attempt to infer the field
- // value from the second parameter by inspecting its relations
- $items = new ArrayList();
-
- // Determine format of presented data
- if (empty($value) && $record) {
- // If a record is given as a second parameter, but no submitted values,
- // then we should inspect this instead for the form values
-
- if (($record instanceof DataObject) && $record->hasMethod($this->getName())) {
- // If given a dataobject use reflection to extract details
-
- $data = $record->{$this->getName()}();
- if ($data instanceof DataObject) {
- // If has_one, add sole item to default list
- $items->push($data);
- } elseif ($data instanceof SS_List) {
- // For many_many and has_many relations we can use the relation list directly
- $items = $data;
- }
- } elseif ($record instanceof SS_List) {
- // If directly passing a list then save the items directly
- $items = $record;
- }
- } elseif (!empty($value['Files'])) {
- // If value is given as an array (such as a posted form), extract File IDs from this
- $class = $this->getRelationAutosetClass();
- $items = DataObject::get($class)->byIDs($value['Files']);
- }
-
- // If javascript is disabled, direct file upload (non-html5 style) can
- // trigger a single or multiple file submission. Note that this may be
- // included in addition to re-submitted File IDs as above, so these
- // should be added to the list instead of operated on independently.
- if ($uploadedFiles = $this->extractUploadedFileData($value)) {
- foreach ($uploadedFiles as $tempFile) {
- $file = $this->saveTemporaryFile($tempFile, $error);
- if ($file) {
- $items->add($file);
- } else {
- throw new ValidationException($error);
- }
- }
- }
-
- // Filter items by what's allowed to be viewed
- $filteredItems = new ArrayList();
- $fileIDs = array();
- foreach ($items as $file) {
- if ($file->exists() && $file->canView()) {
- $filteredItems->push($file);
- $fileIDs[] = $file->ID;
- }
- }
-
- // Filter and cache updated item list
- $this->items = $filteredItems;
- // Same format as posted form values for this field. Also ensures that
- // $this->setValue($this->getValue()); is non-destructive
- $value = $fileIDs ? array('Files' => $fileIDs) : null;
-
- // Set value using parent
- parent::setValue($value, $record);
- return $this;
- }
-
- /**
- * Sets the items assigned to this field as an SS_List of File objects.
- * Calling setItems will also update the value of this field, as well as
- * updating the internal list of File items.
- *
- * @param SS_List $items
- * @return UploadField self reference
- */
- public function setItems(SS_List $items)
- {
- return $this->setValue(null, $items);
- }
-
- /**
- * Retrieves the current list of files
- *
- * @return SS_List
- */
- public function getItems()
- {
- return $this->items ? $this->items : new ArrayList();
- }
-
- /**
- * Retrieves a customised list of all File records to ensure they are
- * properly viewable when rendered in the field template.
- *
- * @return SS_List[ViewableData_Customised]
- */
- public function getCustomisedItems()
- {
- $customised = new ArrayList();
- foreach ($this->getItems() as $file) {
- $customised->push($this->customiseFile($file));
- }
- return $customised;
- }
-
- /**
- * Retrieves the list of selected file IDs
- *
- * @return array
- */
- public function getItemIDs()
- {
- $value = $this->Value();
- return empty($value['Files']) ? array() : $value['Files'];
- }
-
- public function Value()
- {
- // Re-override FileField Value to use data value
- return $this->dataValue();
- }
-
- /**
- * @param DataObject|DataObjectInterface $record
- * @return $this
- */
- public function saveInto(DataObjectInterface $record)
- {
- // Check required relation details are available
- $fieldname = $this->getName();
- if (!$fieldname) {
- return $this;
- }
-
- // Get details to save
- $idList = $this->getItemIDs();
-
- // Check type of relation
- $relation = $record->hasMethod($fieldname) ? $record->$fieldname() : null;
- if ($relation && ($relation instanceof RelationList || $relation instanceof UnsavedRelationList)) {
- // has_many or many_many
- $relation->setByIDList($idList);
- } elseif (DataObject::getSchema()->hasOneComponent(get_class($record), $fieldname)) {
- // has_one
- $record->{"{$fieldname}ID"} = $idList ? reset($idList) : 0;
- }
- return $this;
- }
-
- /**
- * Customises a file with additional details suitable for rendering in the
- * UploadField.ss template
- *
- * @param ViewableData|AssetContainer $file
- * @return ViewableData_Customised
- */
- protected function customiseFile(AssetContainer $file)
- {
- $file = $file->customise(array(
- 'UploadFieldThumbnailURL' => $this->getThumbnailURLForFile($file),
- 'UploadFieldDeleteLink' => $this->getItemHandler($file->ID)->DeleteLink(),
- 'UploadFieldEditLink' => $this->getItemHandler($file->ID)->EditLink(),
- 'UploadField' => $this
- ));
- // we do this in a second customise to have the access to the previous customisations
- return $file->customise(array(
- 'UploadFieldFileButtons' => $file->renderWith($this->getTemplateFileButtons())
- ));
- }
-
- /**
- * Assign a front-end config variable for the upload field
- *
- * @see https://github.com/blueimp/jQuery-File-Upload/wiki/Options for the list of front end options available
- *
- * @param string $key
- * @param mixed $val
- * @return UploadField self reference
- */
- public function setConfig($key, $val)
- {
- $this->ufConfig[$key] = $val;
- return $this;
- }
-
- /**
- * Gets a front-end config variable for the upload field
- *
- * @see https://github.com/blueimp/jQuery-File-Upload/wiki/Options for the list of front end options available
- *
- * @param string $key
- * @return mixed
- */
- public function getConfig($key)
- {
- if (!isset($this->ufConfig[$key])) {
- return null;
- }
- return $this->ufConfig[$key];
- }
-
- /**
- * Determine if the field should automatically upload the file.
- *
- * @return boolean
- */
- public function getAutoUpload()
- {
- return $this->getConfig('autoUpload');
- }
-
- /**
- * Determine if the field should automatically upload the file
- *
- * @param boolean $autoUpload
- * @return UploadField Self reference
- */
- public function setAutoUpload($autoUpload)
- {
- return $this->setConfig('autoUpload', $autoUpload);
- }
-
- /**
- * Determine maximum number of files allowed to be attached
- * Defaults to 1 for has_one and null (unlimited) for
- * many_many and has_many relations.
- *
- * @return integer|null Maximum limit, or null for no limit
- */
- public function getAllowedMaxFileNumber()
- {
- $allowedMaxFileNumber = $this->getConfig('allowedMaxFileNumber');
-
- // if there is a has_one relation with that name on the record and
- // allowedMaxFileNumber has not been set, it's wanted to be 1
- if (empty($allowedMaxFileNumber)) {
- $record = $this->getRecord();
- $name = $this->getName();
- if ($record && DataObject::getSchema()->hasOneComponent(get_class($record), $name)) {
- return 1; // Default for has_one
- } else {
- return null; // Default for has_many and many_many
- }
- } else {
- return $allowedMaxFileNumber;
- }
- }
-
- /**
- * Determine maximum number of files allowed to be attached.
- *
- * @param integer|null $allowedMaxFileNumber Maximum limit. 0 or null will be treated as unlimited
- * @return UploadField Self reference
- */
- public function setAllowedMaxFileNumber($allowedMaxFileNumber)
- {
- return $this->setConfig('allowedMaxFileNumber', $allowedMaxFileNumber);
- }
-
- /**
- * Determine if the user has permission to upload.
- *
- * @return boolean
- */
- public function canUpload()
- {
- if (!$this->isActive()) {
- return false;
- }
- $can = $this->getConfig('canUpload');
- return (is_bool($can)) ? $can : Permission::check($can);
- }
-
- /**
- * Specify whether the user can upload files.
- * String values will be treated as required permission codes
- *
- * @param boolean|string $canUpload Either a boolean flag, or a required
- * permission code
- * @return UploadField Self reference
- */
- public function setCanUpload($canUpload)
- {
- return $this->setConfig('canUpload', $canUpload);
- }
-
- /**
- * Determine if the user has permission to attach existing files
- * By default returns true if the user has the CMS_ACCESS_AssetAdmin permission
- *
- * @return boolean
- */
- public function canAttachExisting()
- {
- if (!$this->isActive()) {
- return false;
- }
- $can = $this->getConfig('canAttachExisting');
- return (is_bool($can)) ? $can : Permission::check($can);
- }
-
- /**
- * Returns true if the field is neither readonly nor disabled
- *
- * @return boolean
- */
- public function isActive()
- {
- return !$this->isDisabled() && !$this->isReadonly();
- }
-
- /**
- * Specify whether the user can attach existing files
- * String values will be treated as required permission codes
- *
- * @param boolean|string $canAttachExisting Either a boolean flag, or a
- * required permission code
- * @return UploadField Self reference
- */
- public function setCanAttachExisting($canAttachExisting)
- {
- return $this->setConfig('canAttachExisting', $canAttachExisting);
- }
-
- /**
- * Gets thumbnail width. Defaults to 80
- *
- * @return integer
- */
- public function getPreviewMaxWidth()
- {
- return $this->getConfig('previewMaxWidth');
- }
-
- /**
- * @see UploadField::getPreviewMaxWidth()
- *
- * @param integer $previewMaxWidth
- * @return UploadField Self reference
- */
- public function setPreviewMaxWidth($previewMaxWidth)
- {
- return $this->setConfig('previewMaxWidth', $previewMaxWidth);
- }
-
- /**
- * Gets thumbnail height. Defaults to 60
- *
- * @return integer
- */
- public function getPreviewMaxHeight()
- {
- return $this->getConfig('previewMaxHeight');
- }
-
- /**
- * @see UploadField::getPreviewMaxHeight()
- *
- * @param integer $previewMaxHeight
- * @return UploadField Self reference
- */
- public function setPreviewMaxHeight($previewMaxHeight)
- {
- return $this->setConfig('previewMaxHeight', $previewMaxHeight);
- }
-
- /**
- * javascript template used to display uploading files
- * Defaults to 'ss-uploadfield-uploadtemplate'
- *
- * @see javascript/UploadField_uploadtemplate.js
- * @return string
- */
- public function getUploadTemplateName()
- {
- return $this->getConfig('uploadTemplateName');
- }
-
- /**
- * @see UploadField::getUploadTemplateName()
- *
- * @param string $uploadTemplateName
- * @return UploadField Self reference
- */
- public function setUploadTemplateName($uploadTemplateName)
- {
- return $this->setConfig('uploadTemplateName', $uploadTemplateName);
- }
-
- /**
- * javascript template used to display already uploaded files
- * Defaults to 'ss-downloadfield-downloadtemplate'
- *
- * @see javascript/DownloadField_downloadtemplate.js
- * @return string
- */
- public function getDownloadTemplateName()
- {
- return $this->getConfig('downloadTemplateName');
- }
-
- /**
- * @see Uploadfield::getDownloadTemplateName()
- *
- * @param string $downloadTemplateName
- * @return Uploadfield Self reference
- */
- public function setDownloadTemplateName($downloadTemplateName)
- {
- return $this->setConfig('downloadTemplateName', $downloadTemplateName);
- }
-
- /**
- * FieldList $fields for the EditForm
- * @example 'getCMSFields'
- *
- * @param DataObject $file File context to generate fields for
- * @return FieldList List of form fields
- */
- public function getFileEditFields(DataObject $file)
- {
- // Empty actions, generate default
- if (empty($this->fileEditFields)) {
- $fields = $file->getCMSFields();
- // Only display main tab, to avoid overly complex interface
- if ($fields->hasTabSet() && ($mainTab = $fields->findOrMakeTab('Root.Main'))) {
- $fields = $mainTab->Fields();
- }
- return $fields;
- }
-
- // Fields instance
- if ($this->fileEditFields instanceof FieldList) {
- return $this->fileEditFields;
- }
-
- // Method to call on the given file
- if ($file->hasMethod($this->fileEditFields)) {
- return $file->{$this->fileEditFields}();
- }
-
- throw new InvalidArgumentException("Invalid value for UploadField::fileEditFields");
- }
-
- /**
- * FieldList $fields or string $name (of a method on File to provide a fields) for the EditForm
- * @example 'getCMSFields'
- *
- * @param FieldList|string
- * @return Uploadfield Self reference
- */
- public function setFileEditFields($fileEditFields)
- {
- $this->fileEditFields = $fileEditFields;
- return $this;
- }
-
- /**
- * FieldList $actions or string $name (of a method on File to provide a actions) for the EditForm
- * @example 'getCMSActions'
- *
- * @param DataObject $file File context to generate form actions for
- * @return FieldList Field list containing FormAction
- */
- public function getFileEditActions(DataObject $file)
- {
- // Empty actions, generate default
- if (empty($this->fileEditActions)) {
- $actions = new FieldList($saveAction = new FormAction('doEdit', _t('UploadField.DOEDIT', 'Save')));
- $saveAction->addExtraClass('ss-ui-action-constructive icon-accept');
- return $actions;
- }
-
- // Actions instance
- if ($this->fileEditActions instanceof FieldList) {
- return $this->fileEditActions;
- }
-
- // Method to call on the given file
- if ($file->hasMethod($this->fileEditActions)) {
- return $file->{$this->fileEditActions}();
- }
-
- throw new InvalidArgumentException("Invalid value for UploadField::fileEditActions");
- }
-
- /**
- * FieldList $actions or string $name (of a method on File to provide a actions) for the EditForm
- * @example 'getCMSActions'
- *
- * @param FieldList|string
- * @return Uploadfield Self reference
- */
- public function setFileEditActions($fileEditActions)
- {
- $this->fileEditActions = $fileEditActions;
- return $this;
- }
-
- /**
- * Determines the validator to use for the edit form
- * @example 'getCMSValidator'
- *
- * @param DataObject $file File context to generate validator from
- * @return Validator Validator object
- */
- public function getFileEditValidator(DataObject $file)
- {
- // Empty validator
- if (empty($this->fileEditValidator)) {
- return null;
- }
-
- // Validator instance
- if ($this->fileEditValidator instanceof Validator) {
- return $this->fileEditValidator;
- }
-
- // Method to call on the given file
- if ($file->hasMethod($this->fileEditValidator)) {
- return $file->{$this->fileEditValidator}();
- }
-
- throw new InvalidArgumentException("Invalid value for UploadField::fileEditValidator");
- }
-
- /**
- * Validator (eg RequiredFields) or string $name (of a method on File to provide a Validator) for the EditForm
- * @example 'getCMSValidator'
- *
- * @param Validator|string
- * @return Uploadfield Self reference
- */
- public function setFileEditValidator($fileEditValidator)
- {
- $this->fileEditValidator = $fileEditValidator;
- return $this;
- }
-
- /**
- *
- * @param File|AssetContainer $file
- * @return string URL to thumbnail
- */
- protected function getThumbnailURLForFile(AssetContainer $file)
- {
- if (!$file->exists()) {
- return null;
- }
-
- // Attempt to generate image at given size
- $width = $this->getPreviewMaxWidth();
- $height = $this->getPreviewMaxHeight();
- if ($file->hasMethod('ThumbnailURL')) {
- return $file->ThumbnailURL($width, $height);
- }
- if ($file->hasMethod('Thumbnail')) {
- return $file->Thumbnail($width, $height)->getURL();
- }
- if ($file->hasMethod('Fit')) {
- return $file->Fit($width, $height)->getURL();
- }
-
- // Check if unsized icon is available
- if ($file->hasMethod('getIcon')) {
- return $file->getIcon();
- }
- return null;
- }
-
- public function getAttributes()
- {
- return array_merge(
- parent::getAttributes(),
- array('data-selectdialog-url', $this->Link('select'))
- );
- }
-
- public function extraClass()
- {
- if ($this->isDisabled()) {
- $this->addExtraClass('disabled');
- }
- if ($this->isReadonly()) {
- $this->addExtraClass('readonly');
- }
-
- return parent::extraClass();
- }
-
- public function Field($properties = array())
- {
- // Calculated config as per jquery.fileupload-ui.js
- $allowedMaxFileNumber = $this->getAllowedMaxFileNumber();
- $config = array(
- 'url' => $this->Link('upload'),
- 'urlSelectDialog' => $this->Link('select'),
- 'urlAttach' => $this->Link('attach'),
- 'urlFileExists' => $this->Link('fileexists'),
- 'acceptFileTypes' => '.+$',
- // Fileupload treats maxNumberOfFiles as the max number of _additional_ items allowed
- 'maxNumberOfFiles' => $allowedMaxFileNumber ? ($allowedMaxFileNumber - count($this->getItemIDs())) : null,
- 'replaceFile' => $this->getUpload()->getReplaceFile(),
- );
-
- // Validation: File extensions
- if ($allowedExtensions = $this->getAllowedExtensions()) {
- $config['acceptFileTypes'] = '(\.|\/)(' . implode('|', $allowedExtensions) . ')$';
- $config['errorMessages']['acceptFileTypes'] = _t(
- 'File.INVALIDEXTENSIONSHORT',
- 'Extension is not allowed'
- );
- }
-
- // Validation: File size
- if ($allowedMaxFileSize = $this->getValidator()->getAllowedMaxFileSize()) {
- $config['maxFileSize'] = $allowedMaxFileSize;
- $config['errorMessages']['maxFileSize'] = _t(
- 'File.TOOLARGESHORT',
- 'File size exceeds {size}',
- array('size' => File::format_size($config['maxFileSize']))
- );
- }
-
- // Validation: Number of files
- if ($allowedMaxFileNumber) {
- if ($allowedMaxFileNumber > 1) {
- $config['errorMessages']['maxNumberOfFiles'] = _t(
- 'UploadField.MAXNUMBEROFFILESSHORT',
- 'Can only upload {count} files',
- array('count' => $allowedMaxFileNumber)
- );
- } else {
- $config['errorMessages']['maxNumberOfFiles'] = _t(
- 'UploadField.MAXNUMBEROFFILESONE',
- 'Can only upload one file'
- );
- }
- }
-
- // add overwrite warning error message to the config object sent to Javascript
- if ($this->getOverwriteWarning()) {
- $config['errorMessages']['overwriteWarning'] =
- _t('UploadField.OVERWRITEWARNING', 'File with the same name already exists');
- }
-
- $mergedConfig = array_merge($config, $this->ufConfig);
- return parent::Field(array(
- 'configString' => Convert::raw2json($mergedConfig),
- 'config' => new ArrayData($mergedConfig),
- 'multiple' => $allowedMaxFileNumber !== 1
- ));
- }
-
- /**
- * Validation method for this field, called when the entire form is validated
- *
- * @param Validator $validator
- * @return boolean
- */
- public function validate($validator)
- {
- $name = $this->getName();
- $files = $this->getItems();
-
- // If there are no files then quit
- if ($files->count() == 0) {
- return true;
- }
-
- // Check max number of files
- $maxFiles = $this->getAllowedMaxFileNumber();
- if ($maxFiles && ($files->count() > $maxFiles)) {
- $validator->validationError(
- $name,
- _t(
- 'UploadField.MAXNUMBEROFFILES',
- 'Max number of {count} file(s) exceeded.',
- array('count' => $maxFiles)
- ),
- "validation"
- );
- return false;
- }
-
- // Revalidate each file against nested validator
- $this->upload->clearErrors();
- foreach ($files as $file) {
- // Generate $_FILES style file attribute array for upload validator
- $tmpFile = array(
- 'name' => $file->Name,
- 'type' => null, // Not used for type validation
- 'size' => $file->AbsoluteSize,
- 'tmp_name' => null, // Should bypass is_uploaded_file check
- 'error' => UPLOAD_ERR_OK,
- );
- $this->upload->validate($tmpFile);
- }
-
- // Check all errors
- if ($errors = $this->upload->getErrors()) {
- foreach ($errors as $error) {
- $validator->validationError($name, $error, "validation");
- }
- return false;
- }
-
- return true;
- }
-
- /**
- * @param HTTPRequest $request
- * @return UploadField_ItemHandler
- */
- public function handleItem(HTTPRequest $request)
- {
- return $this->getItemHandler($request->param('ID'));
- }
-
- /**
- * @param int $itemID
- * @return UploadField_ItemHandler
- */
- public function getItemHandler($itemID)
- {
- return UploadField_ItemHandler::create($this, $itemID);
- }
-
- /**
- * @param HTTPRequest $request
- * @return UploadField_SelectHandler
- */
- public function handleSelect(HTTPRequest $request)
- {
- if (!$this->canAttachExisting()) {
- return $this->httpError(403);
- }
- return UploadField_SelectHandler::create($this, $this->getFolderName());
- }
-
- /**
- * Given an array of post variables, extract all temporary file data into an array
- *
- * @param array $postVars Array of posted form data
- * @return array List of temporary file data
- */
- protected function extractUploadedFileData($postVars)
- {
-
- // Note: Format of posted file parameters in php is a feature of using
- // for multiple file uploads
- $tmpFiles = array();
- if (!empty($postVars['tmp_name'])
- && is_array($postVars['tmp_name'])
- && !empty($postVars['tmp_name']['Uploads'])
- ) {
- for ($i = 0; $i < count($postVars['tmp_name']['Uploads']); $i++) {
- // Skip if "empty" file
- if (empty($postVars['tmp_name']['Uploads'][$i])) {
- continue;
- }
- $tmpFile = array();
- foreach (array('name', 'type', 'tmp_name', 'error', 'size') as $field) {
- $tmpFile[$field] = $postVars[$field]['Uploads'][$i];
- }
- $tmpFiles[] = $tmpFile;
- }
- } elseif (!empty($postVars['tmp_name'])) {
- // Fallback to allow single file uploads (method used by AssetUploadField)
- $tmpFiles[] = $postVars;
- }
-
- return $tmpFiles;
- }
-
- /**
- * Loads the temporary file data into a File object
- *
- * @param array $tmpFile Temporary file data
- * @param string $error Error message
- * @return AssetContainer File object, or null if error
- */
- protected function saveTemporaryFile($tmpFile, &$error = null)
- {
- // Determine container object
- $error = null;
- $fileObject = null;
-
- if (empty($tmpFile)) {
- $error = _t('UploadField.FIELDNOTSET', 'File information not found');
- return null;
- }
-
- if ($tmpFile['error']) {
- $error = $tmpFile['error'];
- return null;
- }
-
- // Search for relations that can hold the uploaded files, but don't fallback
- // to default if there is no automatic relation
- if ($relationClass = $this->getRelationAutosetClass(null)) {
- // Allow File to be subclassed
- if ($relationClass === 'SilverStripe\\Assets\\File' && isset($tmpFile['name'])) {
- $relationClass = File::get_class_for_file_extension(
- File::get_file_extension($tmpFile['name'])
- );
- }
- // Create new object explicitly. Otherwise rely on Upload::load to choose the class.
- $fileObject = Object::create($relationClass);
- if (! ($fileObject instanceof DataObject) || !($fileObject instanceof AssetContainer)) {
- throw new InvalidArgumentException("Invalid asset container $relationClass");
- }
- }
-
- // Get the uploaded file into a new file object.
- try {
- $this->upload->loadIntoFile($tmpFile, $fileObject, $this->getFolderName());
- } catch (Exception $e) {
- // we shouldn't get an error here, but just in case
- $error = $e->getMessage();
- return null;
- }
-
- // Check if upload field has an error
- if ($this->upload->isError()) {
- $error = implode(' ' . PHP_EOL, $this->upload->getErrors());
- return null;
- }
-
- // return file
- return $this->upload->getFile();
- }
-
- /**
- * Safely encodes the File object with all standard fields required
- * by the front end
- *
- * @param File|AssetContainer $file Object which contains a file
- * @return array Array encoded list of file attributes
- */
- protected function encodeFileAttributes(AssetContainer $file)
- {
- // Collect all output data.
- $customised = $this->customiseFile($file);
- return array(
- 'id' => $file->ID,
- 'name' => basename($file->getFilename()),
- 'url' => $file->getURL(),
- 'thumbnail_url' => $customised->UploadFieldThumbnailURL,
- 'edit_url' => $customised->UploadFieldEditLink,
- 'size' => $file->getAbsoluteSize(),
- 'type' => File::get_file_type($file->getFilename()),
- 'buttons' => (string)$customised->UploadFieldFileButtons,
- 'fieldname' => $this->getName()
- );
- }
-
- /**
- * Action to handle upload of a single file
- *
- * @param HTTPRequest $request
- * @return HTTPResponse
- * @return HTTPResponse
- */
- public function upload(HTTPRequest $request)
- {
- if ($this->isDisabled() || $this->isReadonly() || !$this->canUpload()) {
- return $this->httpError(403);
- }
-
- // Protect against CSRF on destructive action
- $token = $this->getForm()->getSecurityToken();
- if (!$token->checkRequest($request)) {
- return $this->httpError(400);
- }
-
- // Get form details
- $name = $this->getName();
- $postVars = $request->postVar($name);
-
- // Extract uploaded files from Form data
- $uploadedFiles = $this->extractUploadedFileData($postVars);
- $return = array();
-
- // Save the temporary files into a File objects
- // and save data/error on a per file basis
- foreach ($uploadedFiles as $tempFile) {
- $file = $this->saveTemporaryFile($tempFile, $error);
- if (empty($file)) {
- array_push($return, array('error' => $error));
- } else {
- array_push($return, $this->encodeFileAttributes($file));
- }
- $this->upload->clearErrors();
- }
-
- // Format response with json
- $response = new HTTPResponse(Convert::raw2json($return));
- $response->addHeader('Content-Type', 'text/plain');
- return $response;
- }
-
- /**
- * Retrieves details for files that this field wishes to attache to the
- * client-side form
- *
- * @param HTTPRequest $request
- * @return HTTPResponse
- */
- public function attach(HTTPRequest $request)
- {
- if (!$request->isPOST()) {
- return $this->httpError(403);
- }
- if (!$this->canAttachExisting()) {
- return $this->httpError(403);
- }
-
- // Retrieve file attributes required by front end
- $return = array();
- $files = File::get()->byIDs($request->postVar('ids'));
- foreach ($files as $file) {
- $return[] = $this->encodeFileAttributes($file);
- }
- $response = new HTTPResponse(Convert::raw2json($return));
- $response->addHeader('Content-Type', 'application/json');
- return $response;
- }
-
- /**
- * Check if file exists, both checking filtered filename and exact filename
- *
- * @param string $originalFile Filename
- * @return bool
- */
- protected function checkFileExists($originalFile)
- {
-
- // Check both original and safely filtered filename
- $nameFilter = FileNameFilter::create();
- $filteredFile = $nameFilter->filter($originalFile);
-
- // Resolve expected folder name
- $folderName = $this->getFolderName();
- $folder = Folder::find_or_make($folderName);
- $parentPath = $folder ? $folder->getFilename() : '';
-
- // check if either file exists
- return File::find($parentPath.$originalFile) || File::find($parentPath.$filteredFile);
- }
-
- /**
- * Determines if a specified file exists
- *
- * @param HTTPRequest $request
- * @return HTTPResponse
- */
- public function fileexists(HTTPRequest $request)
- {
- // Assert that requested filename doesn't attempt to escape the directory
- $originalFile = $request->requestVar('filename');
- if ($originalFile !== basename($originalFile)) {
- $return = array(
- 'error' => _t('File.NOVALIDUPLOAD', 'File is not a valid upload')
- );
- } else {
- $return = array(
- 'exists' => $this->checkFileExists($originalFile)
- );
- }
-
- // Encode and present response
- $response = new HTTPResponse(Convert::raw2json($return));
- $response->addHeader('Content-Type', 'application/json');
- if (!empty($return['error'])) {
- $response->setStatusCode(400);
- }
- return $response;
- }
-
- public function performReadonlyTransformation()
- {
- $clone = clone $this;
- $clone->addExtraClass('readonly');
- $clone->setReadonly(true);
- return $clone;
- }
-
- /**
- * Gets the foreign class that needs to be created, or 'File' as default if there
- * is no relationship, or it cannot be determined.
- *
- * @param string $default Default value to return if no value could be calculated
- * @return string Foreign class name.
- */
- public function getRelationAutosetClass($default = 'SilverStripe\\Assets\\File')
- {
-
- // Don't autodetermine relation if no relationship between parent record
- if (!$this->relationAutoSetting) {
- return $default;
- }
-
- // Check record and name
- $name = $this->getName();
- $record = $this->getRecord();
- if (empty($name) || empty($record)) {
- return $default;
- } else {
- $class = $record->getRelationClass($name);
- return empty($class) ? $default : $class;
- }
- }
}
diff --git a/src/Forms/Uploadable.php b/src/Forms/Uploadable.php
new file mode 100644
index 000000000..e6242831f
--- /dev/null
+++ b/src/Forms/Uploadable.php
@@ -0,0 +1,153 @@
+setUpload(Upload::create());
+
+ // filter out '' since this would be a regex problem on JS end
+ $this->getValidator()->setAllowedExtensions(
+ array_filter(File::config()->allowed_extensions)
+ );
+
+ // get the lower max size
+ $maxUpload = File::ini2bytes(ini_get('upload_max_filesize'));
+ $maxPost = File::ini2bytes(ini_get('post_max_size'));
+ $this->getValidator()->setAllowedMaxFileSize(min($maxUpload, $maxPost));
+ }
+
+ /**
+ * Retrieves the Upload handler
+ *
+ * @return Upload
+ */
+ public function getUpload() {
+ return $this->upload;
+ }
+
+ /**
+ * Sets the upload handler
+ *
+ * @param Upload $upload
+ * @return $this Self reference
+ */
+ public function setUpload(Upload $upload) {
+ $this->upload = $upload;
+ return $this;
+ }
+
+ /**
+ * Limit allowed file extensions. Empty by default, allowing all extensions.
+ * To allow files without an extension, use an empty string.
+ * See {@link File::$allowed_extensions} to get a good standard set of
+ * extensions that are typically not harmful in a webserver context.
+ * See {@link setAllowedMaxFileSize()} to limit file size by extension.
+ *
+ * @param array $rules List of extensions
+ * @return $this
+ */
+ public function setAllowedExtensions($rules) {
+ $this->getValidator()->setAllowedExtensions($rules);
+ return $this;
+ }
+
+ /**
+ * Limit allowed file extensions by specifying categories of file types.
+ * These may be 'image', 'image/supported', 'audio', 'video', 'archive', 'flash', or 'document'
+ * See {@link File::$allowed_extensions} for details of allowed extensions
+ * for each of these categories
+ *
+ * @param string $category Category name
+ * @param string,... $categories Additional category names
+ * @return $this
+ */
+ public function setAllowedFileCategories($category) {
+ $extensions = File::get_category_extensions(func_get_args());
+ return $this->setAllowedExtensions($extensions);
+ }
+
+ /**
+ * Returns list of extensions allowed by this field, or an empty array
+ * if there is no restriction
+ *
+ * @return array
+ */
+ public function getAllowedExtensions() {
+ return $this->getValidator()->getAllowedExtensions();
+ }
+
+ /**
+ * Get custom validator for this field
+ *
+ * @return Upload_Validator
+ */
+ public function getValidator() {
+ return $this->getUpload()->getValidator();
+ }
+
+ /**
+ * Set custom validator for this field
+ *
+ * @param Upload_Validator $validator
+ * @return $this Self reference
+ */
+ public function setValidator(Upload_Validator $validator) {
+ $this->getUpload()->setValidator($validator);
+ return $this;
+ }
+
+ /**
+ * Sets the upload folder name
+ *
+ * @param string $folderName
+ * @return $this Self reference
+ */
+ public function setFolderName($folderName) {
+ $this->folderName = $folderName;
+ return $this;
+ }
+
+ /**
+ * Gets the upload folder name
+ *
+ * @return string
+ */
+ public function getFolderName() {
+ return ($this->folderName !== false)
+ ? $this->folderName
+ : Upload::config()->uploads_folder;
+ }
+}