2007-07-19 12:40:28 +02:00
|
|
|
<?php
|
2016-06-15 06:03:16 +02:00
|
|
|
|
2016-08-19 00:51:35 +02:00
|
|
|
namespace SilverStripe\Forms;
|
|
|
|
|
2017-10-03 17:48:49 +02:00
|
|
|
use SilverStripe\Assets\File;
|
|
|
|
use SilverStripe\Control\HTTP;
|
2017-05-17 07:40:13 +02:00
|
|
|
use SilverStripe\Core\Injector\Injector;
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\ORM\DataObject;
|
2016-06-15 06:03:16 +02:00
|
|
|
use SilverStripe\ORM\DataObjectInterface;
|
2016-08-19 00:51:35 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* Represents a file type which can be added to a form.
|
2008-04-06 10:20:13 +02:00
|
|
|
* Automatically tries to save has_one-relations on the saved
|
|
|
|
* record.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2008-04-06 10:20:13 +02:00
|
|
|
* Please set a validator on the form-object to get feedback
|
|
|
|
* about imposed filesize/extension restrictions.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2010-10-15 05:55:22 +02:00
|
|
|
* <b>Usage</p>
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2010-10-15 05:55:22 +02:00
|
|
|
* If you want to implement a FileField into a form element, you need to pass it an array of source data.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2010-10-15 05:55:22 +02:00
|
|
|
* <code>
|
2016-12-30 00:17:15 +01:00
|
|
|
* class ExampleFormController extends PageController {
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2016-11-29 04:18:48 +01:00
|
|
|
* function Form() {
|
|
|
|
* $fields = new FieldList(
|
|
|
|
* new TextField('MyName'),
|
|
|
|
* new FileField('MyFile')
|
|
|
|
* );
|
|
|
|
* $actions = new FieldList(
|
|
|
|
* new FormAction('doUpload', 'Upload file')
|
|
|
|
* );
|
2020-04-20 09:07:53 +02:00
|
|
|
* $validator = new RequiredFields(['MyName', 'MyFile']);
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2016-11-29 04:18:48 +01:00
|
|
|
* return new Form($this, 'Form', $fields, $actions, $validator);
|
|
|
|
* }
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2016-11-29 04:18:48 +01:00
|
|
|
* function doUpload($data, $form) {
|
|
|
|
* $file = $data['MyFile'];
|
|
|
|
* $content = file_get_contents($file['tmp_name']);
|
|
|
|
* // ... process content
|
|
|
|
* }
|
2010-10-15 05:55:22 +02:00
|
|
|
* }
|
|
|
|
* </code>
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
2017-02-21 02:38:57 +01:00
|
|
|
class FileField extends FormField implements FileHandleField
|
2016-11-29 04:18:48 +01:00
|
|
|
{
|
|
|
|
use UploadReceiver;
|
|
|
|
|
2017-05-08 07:21:51 +02:00
|
|
|
protected $inputType = 'file';
|
|
|
|
|
2016-11-29 04:18:48 +01:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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->constructUploadReceiver();
|
|
|
|
parent::__construct($name, $title, $value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array $properties
|
|
|
|
* @return string
|
|
|
|
*/
|
2020-04-20 19:58:09 +02:00
|
|
|
public function Field($properties = [])
|
2016-11-29 04:18:48 +01:00
|
|
|
{
|
2020-04-20 19:58:09 +02:00
|
|
|
$properties = array_merge($properties, [
|
2016-11-29 04:18:48 +01:00
|
|
|
'MaxFileSize' => $this->getValidator()->getAllowedMaxFileSize()
|
2020-04-20 19:58:09 +02:00
|
|
|
]);
|
2016-11-29 04:18:48 +01:00
|
|
|
|
|
|
|
return parent::Field($properties);
|
|
|
|
}
|
|
|
|
|
2017-10-03 17:48:49 +02:00
|
|
|
public function getAttributes()
|
|
|
|
{
|
|
|
|
$attributes = parent::getAttributes();
|
|
|
|
|
|
|
|
$accept = $this->getAcceptFileTypes();
|
|
|
|
if ($accept) {
|
|
|
|
$attributes = array_merge(['accept' => implode(',', $accept)], $attributes);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $attributes;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of file extensions (and corresponding mime types) that will be accepted
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getAcceptFileTypes()
|
|
|
|
{
|
|
|
|
$extensions = $this->getValidator()->getAllowedExtensions();
|
|
|
|
if (!$extensions) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$accept = [];
|
|
|
|
$mimeTypes = HTTP::config()->uninherited('MimeTypes');
|
|
|
|
foreach ($extensions as $extension) {
|
|
|
|
$accept[] = ".{$extension}";
|
|
|
|
// Check for corresponding mime type
|
|
|
|
if (isset($mimeTypes[$extension])) {
|
|
|
|
$accept[] = $mimeTypes[$extension];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-14 03:12:59 +02:00
|
|
|
return array_unique($accept ?? []);
|
2017-10-03 17:48:49 +02:00
|
|
|
}
|
|
|
|
|
2016-11-29 04:18:48 +01:00
|
|
|
/**
|
|
|
|
* @param DataObject|DataObjectInterface $record
|
|
|
|
*/
|
|
|
|
public function saveInto(DataObjectInterface $record)
|
|
|
|
{
|
2018-01-03 13:01:18 +01:00
|
|
|
if (!isset($_FILES[$this->name]['error']) || $_FILES[$this->name]['error'] == UPLOAD_ERR_NO_FILE) {
|
2016-11-29 04:18:48 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$fileClass = File::get_class_for_file_extension(
|
|
|
|
File::get_file_extension($_FILES[$this->name]['name'])
|
|
|
|
);
|
|
|
|
|
|
|
|
/** @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
|
2017-05-17 07:40:13 +02:00
|
|
|
$file = Injector::inst()->create($fileClass);
|
2016-11-29 04:18:48 +01:00
|
|
|
} else {
|
|
|
|
// try to create a file matching the relation
|
2017-05-17 07:40:13 +02:00
|
|
|
$file = Injector::inst()->create($objectClass);
|
2016-11-29 04:18:48 +01:00
|
|
|
}
|
|
|
|
} elseif ($record instanceof File) {
|
|
|
|
$file = $record;
|
|
|
|
} else {
|
2017-05-17 07:40:13 +02:00
|
|
|
$file = Injector::inst()->create($fileClass);
|
2016-11-29 04:18:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->upload->loadIntoFile($_FILES[$this->name], $file, $this->getFolderName());
|
|
|
|
|
|
|
|
if ($this->upload->isError()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->relationAutoSetting) {
|
|
|
|
if (empty($objectClass)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$file = $this->upload->getFile();
|
|
|
|
|
|
|
|
$record->{$this->name . 'ID'} = $file->ID;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function Value()
|
|
|
|
{
|
|
|
|
return isset($_FILES[$this->getName()]) ? $_FILES[$this->getName()] : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function validate($validator)
|
|
|
|
{
|
2021-02-18 22:28:03 +01:00
|
|
|
// FileField with the name multi_file_syntax[] or multi_file_syntax[key] will have the brackets trimmed in
|
|
|
|
// $_FILES super-global so it will be stored as $_FILES['mutli_file_syntax']
|
|
|
|
// multi-file uploads, which are not officially supported by Silverstripe, though may be
|
|
|
|
// implemented in custom code, so we should still ensure they are at least validated
|
2022-04-14 03:12:59 +02:00
|
|
|
$isMultiFileUpload = strpos($this->name ?? '', '[') !== false;
|
|
|
|
$fieldName = preg_replace('#\[(.*?)\]$#', '', $this->name ?? '');
|
2021-02-18 22:28:03 +01:00
|
|
|
|
|
|
|
if (!isset($_FILES[$fieldName])) {
|
2016-11-29 04:18:48 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:28:03 +01:00
|
|
|
if ($isMultiFileUpload) {
|
|
|
|
$isValid = true;
|
2022-04-14 03:12:59 +02:00
|
|
|
foreach (array_keys($_FILES[$fieldName]['name'] ?? []) as $key) {
|
2021-02-18 22:28:03 +01:00
|
|
|
$fileData = [
|
|
|
|
'name' => $_FILES[$fieldName]['name'][$key],
|
|
|
|
'type' => $_FILES[$fieldName]['type'][$key],
|
|
|
|
'tmp_name' => $_FILES[$fieldName]['tmp_name'][$key],
|
|
|
|
'error' => $_FILES[$fieldName]['error'][$key],
|
|
|
|
'size' => $_FILES[$fieldName]['size'][$key],
|
|
|
|
];
|
|
|
|
if (!$this->validateFileData($validator, $fileData)) {
|
|
|
|
$isValid = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $isValid;
|
|
|
|
}
|
|
|
|
|
|
|
|
// regular single-file upload
|
|
|
|
return $this->validateFileData($validator, $_FILES[$this->name]);
|
|
|
|
}
|
2016-11-29 04:18:48 +01:00
|
|
|
|
2021-02-18 22:28:03 +01:00
|
|
|
/**
|
|
|
|
* @param Validator $validator
|
|
|
|
* @param array $fileData
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
private function validateFileData($validator, array $fileData): bool
|
|
|
|
{
|
|
|
|
$valid = $this->upload->validate($fileData);
|
2016-11-29 04:18:48 +01:00
|
|
|
if (!$valid) {
|
|
|
|
$errors = $this->upload->getErrors();
|
|
|
|
if ($errors) {
|
|
|
|
foreach ($errors as $error) {
|
|
|
|
$validator->validationError($this->name, $error, "validation");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
2012-11-14 23:33:10 +01:00
|
|
|
}
|