API UploadField functions on new records

Fixed regression from 1e5d40474d (UploadField::canPreviewFolder).
Merged in pull request #2009 - (6018bdd631).
Merged pull request #1259 (34bfc862ee).
This commit is contained in:
Damian Mooyman 2013-05-27 15:22:59 +12:00
parent 2b9a44ef67
commit 7f057ce343
20 changed files with 1783 additions and 1015 deletions

View File

@ -6,12 +6,9 @@ UploadField:
allowedMaxFileNumber:
canUpload: true
canAttachExisting: 'CMS_ACCESS_AssetAdmin'
replaceExistingFile: false
canPreviewFolder: true
previewMaxWidth: 80
previewMaxHeight: 60
uploadTemplateName: 'ss-uploadfield-uploadtemplate'
downloadTemplateName: 'ss-uploadfield-downloadtemplate'
fileEditFields:
fileEditActions:
fileEditValidator:
overwriteWarning: true # Warning before overwriting existing file (only relevant when Upload: replaceFile is true)
overwriteWarning: true # Warning before overwriting existing file (only relevant when Upload: replaceFile is true)

View File

@ -44,11 +44,11 @@ Used in side panels and action tabs
.cms table.ss-gridfield-table tbody td.col-listChildrenLink .list-children-link { background: transparent url(../images/sitetree_ss_default_icons.png) no-repeat 3px -4px; display: block; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.item { color: #0073c1; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge { clear: both; text-transform: uppercase; display: inline-block; padding: 0px 3px; font-size: 0.75em; line-height: 1em; margin-left: 10px; margin-right: 6px; margin-top: -1px; -webkit-border-radius: 2px 2px; -moz-border-radius: 2px / 2px; border-radius: 2px / 2px; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge.modified { color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge.addedtodraft { color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge.deletedonlive { color: #636363; border: 1px solid #E49393; background-color: #F2DADB; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge.removedfromdraft { color: #636363; border: 1px solid #E49393; background-color: #F2DADB; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge.workflow-approval { color: #56660C; border: 1px solid #7C8816; background-color: #DAE79A; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge.status-modified { color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge.status-addedtodraft { color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge.status-deletedonlive { color: #636363; border: 1px solid #E49393; background-color: #F2DADB; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge.status-removedfromdraft { color: #636363; border: 1px solid #E49393; background-color: #F2DADB; }
.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge.status-workflow-approval { color: #56660C; border: 1px solid #7C8816; background-color: #DAE79A; }
.cms table.ss-gridfield-table tbody td button { border: none; background: none; margin: 0 0 0 2px; padding: 1px 0; width: auto; text-shadow: none; }
.cms table.ss-gridfield-table tbody td button.ui-state-hover { background: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; }
.cms table.ss-gridfield-table tbody td button.ui-state-active { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; }

View File

@ -18,26 +18,26 @@ Used in side panels and action tabs
.ss-uploadfield .ss-uploadfield-item { margin: 0; padding: 15px; overflow: auto; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-preview { height: 60px; line-height: 60px; width: 80px; text-align: center; font-weight: bold; float: left; overflow: hidden; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-preview.ss-uploadfield-dropzone { -webkit-box-shadow: gray 0 0 4px 0 inset; -moz-box-shadow: gray 0 0 4px 0 inset; box-shadow: gray 0 0 4px 0 inset; border: 2px dashed gray; background: #d0d3d5; display: none; margin-right: 15px; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info { float: left; margin-left: 15px; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info { margin-left: 95px; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name { display: block; line-height: 13px; height: 26px; margin: 0; text-align: left; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name b { font-weight: bold; padding: 0 5px 0 0; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name .name { font-size: 11px; color: #848484; width: 290px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; -o-text-overflow: ellipsis; display: inline; float: left; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name .ss-uploadfield-item-status { float: right; padding: 0 0 0 5px; width: 100px; text-align: right; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name .ss-uploadfield-item-status.ui-state-error-text { color: red; font-weight: bold; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name .name { max-width: 240px; font-weight: bold; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; -o-text-overflow: ellipsis; display: inline; float: left; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name .size { color: #848484; padding: 0 0 0 5px; display: inline; float: left; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name .ss-uploadfield-item-status { float: right; padding: 0 0 0 5px; text-align: right; max-width: 75%; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name .ss-uploadfield-item-status.ui-state-error-text { color: red; font-weight: bold; width: 150px; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name .ss-uploadfield-item-status.ui-state-warning-text { color: #b7a403; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name .ss-uploadfield-item-status.ui-state-success-text { color: #1f9433; }
.ss-uploadfield .ss-ui-button { display: block; float: left; margin: 0 10px 0 0; }
.ss-uploadfield .ss-ui-button { display: block; float: left; margin: 0 10px 6px 0; }
.ss-uploadfield .ss-ui-button.ss-uploadfield-fromcomputer { position: relative; overflow: hidden; }
.ss-uploadfield .ss-uploadfield-files { margin: 0; padding: 0; overflow: auto; position: relative; }
.ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item, .ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item.ui-state-error { border: 0; border-bottom: 1px solid #b3b3b3; background: none; color: #444444; }
.ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item:last-child, .ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item.ui-state-error:last-child { border-bottom: 0; }
.ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-actions { height: 28px; margin: 6px 0 0; position: relative; }
.ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-actions { min-height: 28px; overflow: hidden; margin: 6px 0 -6px 0; position: relative; }
.ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-progress { position: absolute; left: 0; right: 42px; width: auto; margin: 11px 0 0; height: 15px; }
.ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-progress div { -webkit-border-radius: 25px; -moz-border-radius: 25px; -ms-border-radius: 25px; -o-border-radius: 25px; border-radius: 25px; height: 13px; padding: 0; margin: 0; overflow: hidden; }
.ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-progressbar { border: 1px solid gray; background-color: #92a6b3; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #92a6b3), color-stop(11%, #90aab8), color-stop(22%, #96b1bf), color-stop(33%, #9eb4c1), color-stop(44%, #a7bac7), color-stop(100%, #c1d5dc)); background-image: -webkit-linear-gradient(top, #92a6b3 0%, #90aab8 11%, #96b1bf 22%, #9eb4c1 33%, #a7bac7 44%, #c1d5dc 100%); background-image: -moz-linear-gradient(top, #92a6b3 0%, #90aab8 11%, #96b1bf 22%, #9eb4c1 33%, #a7bac7 44%, #c1d5dc 100%); background-image: -o-linear-gradient(top, #92a6b3 0%, #90aab8 11%, #96b1bf 22%, #9eb4c1 33%, #a7bac7 44%, #c1d5dc 100%); background-image: linear-gradient(top, #92a6b3 0%, #90aab8 11%, #96b1bf 22%, #9eb4c1 33%, #a7bac7 44%, #c1d5dc 100%); }
.ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-progressbarvalue { border: 0; width: 0%; background: #60b3dd url(../images/progressbar_blue.gif) repeat-x left center; }
.ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-cancel, .ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-start { position: absolute; top: 10px; right: 0; }
.ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-cancel button, .ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-start button { display: block; overflow: hidden; text-indent: -9999px; padding: 0; margin: 0; border: 0; width: 16px; height: 16px; cursor: pointer; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; background: none; position: relative; }
.ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-cancel button, .ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-start button { display: block; overflow: hidden; text-indent: -9999px; padding: 0; margin: 0; border: 0; width: 16px; height: 16px; cursor: pointer; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; position: relative; }
.ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-cancel button span, .ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-start button span { position: absolute; left: 0; top: 0; margin: 0; }
.ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-cancel button span.ui-button-text, .ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-start button span.ui-button-text { display: none; }
.ss-uploadfield .ss-uploadfield-files .ss-uploadfield-item-start { right: 20px; }

View File

@ -415,6 +415,7 @@ you can enable those warnings and future-proof your code already.
* Removed `SQLMap::map()`: Use DataList::("Member")->map()
* Removed `SQLMap::mapInGroups()`: Use Member::map_in_groups()
* Removed `PasswordEncryptor::register()/unregister()`: Use config system instead
* `UploadField::setConfig()` and `UploadField::getConfig()` are no longer public: Use set{Property} or get{Property} to configure an `UploadField` instance
* Methods on DataList and ArrayList that used to both modify the existing list & return a new version now just return a new version. Make sure you change statements like `$list->filter(...)` to $`list = $list->filter(...)` for these methods:
- `ArrayList#reverse`
- `ArrayList#sort`

View File

@ -10,8 +10,19 @@ like for instance in creating and managing a simple gallery.
## Usage
The field can be used in two ways: To upload a single file into a `has_one` relationship,
or allow multiple files into a fixed folder (or relationship).
The field can be used in three ways: To upload a single file into a `has_one` relationship,
or allow multiple files into a `has_many` or `many_many` relationship, or to act as a stand
alone uploader into a folder with no underlying relation.
## Validation
Although images are uploaded and stored on the filesystem immediately after selection,
the value (or values) of this field will not be written to any related record until
the record is saved and successfully validated. However, any invalid records will still
persist across form submissions until explicitly removed or replaced by the user.
Care should be taken as invalid files may remain within the filesystem until explicitly
removed.
### Single fileupload
@ -42,13 +53,13 @@ based on a has_one relation:
The UploadField will autodetect the relation based on it's `name` property, and
save it into the GalleyPages' `SingleImageID` field. Setting the
`allowedMaxFileNumber` to 1 will make sure that only one image can ever be
`setAllowedMaxFileNumber` to 1 will make sure that only one image can ever be
uploaded and linked to the relation.
### Multiple fileupload
Enable multiple fileuploads by using a many_many relation. Again, the
UploadField will detect the relation based on its $name property value:
Enable multiple fileuploads by using a many_many (or has_many) relation. Again,
the `UploadField` will detect the relation based on its $name property value:
:::php
class GalleryPage extends Page {
@ -68,22 +79,33 @@ UploadField will detect the relation based on its $name property value:
$title = 'Upload one or more images (max 10 in total)'
)
);
$uploadField->setConfig('allowedMaxFileNumber', 10);
$uploadField->setAllowedMaxFileNumber(10);
return $fields;
}
}
class GalleryPage_Controller extends Page_Controller {
}
WARNING: Currently the UploadField doesn't fully support has_many relations, so use a many_many relation instead!
class GalleryImageExtension extends DataExtension {
private static $belongs_many_many = array('Galleries' => 'GalleryPage);
}
Image::add_extension('GalleryImageExtension');
<div class="notice" markdown='1'>
In order to link both ends of the relationship together it's usually advisable to
extend Image with the necessary $has_one, $belongs_to, $has_many or $belongs_many_many.
In particular, a DataObject with $has_many Images will not work without this specified explicitly.
</div>
## Configuration
### Overview
The field can either be configured on an instance level through `setConfig(<key>, <value>)`,
or globally by overriding the YAML defaults. See the [Configuration Reference](uploadfield#configuration-reference) section for possible values.
The field can either be configured on an instance level with the various
getProperty and setProperty functions, or globally by overriding the YAML defaults.
See the [Configuration Reference](uploadfield#configuration-reference) section for possible values.
Example: mysite/_config/uploadfield.yml
@ -93,7 +115,6 @@ Example: mysite/_config/uploadfield.yml
defaultConfig:
canUpload: false
### Set a custom folder
This example will save all uploads in the `/assets/customfolder/` folder. If
@ -108,13 +129,22 @@ the folder doesn't exist, it will be created.
);
$uploadField->setFolderName('customfolder');
## Limit the allowed filetypes
### Limit the allowed filetypes
`AllowedExtensions` defaults to the `File.allowed_extensions` configuration setting,
but can be overwritten for each UploadField:
:::php
$uploadField->getValidator()->setAllowedExtensions(array('jpg', 'jpeg', 'png', 'gif'));
$uploadField->setAllowedExtensions(array('jpg', 'jpeg', 'png', 'gif'));
Entire groups of file extensions can be specified in order to quickly limit types
to known file categories.
:::php
// This will limit files to the following extensions:
// "bmp" ,"gif" ,"jpg" ,"jpeg" ,"pcx" ,"tif" ,"png" ,"alpha","als" ,"cel" ,"icon" ,"ico" ,"ps"
// 'doc','docx','txt','rtf','xls','xlsx','pages', 'ppt','pptx','pps','csv', 'html','htm','xhtml', 'xml','pdf'
$uploadField->setAllowedFileCategories('image', 'doc');
### Limit the maximum file size
@ -135,8 +165,24 @@ Set the dimensions of the image preview. By default the max width is set to 80
and the max height is set to 60.
:::php
$uploadField->setConfig('previewMaxWidth', 100);
$uploadField->setConfig('previewMaxHeight', 100);
$uploadField->setPreviewMaxWidth(100);
$uploadField->setPreviewMaxHeight(100);
### Disable attachment of existing files
This can force the user to upload a new file, rather than link to the already
existing file librarry
:::php
$uploadField->setCanAttachExisting(false);
### Disable uploading of new files
Alternatively, you can force the user to only specify already existing files
in the file library
:::php
$uploadField->setCanUpload(false);
### Automatic or manual upload
@ -145,8 +191,8 @@ Setting the `autoUpload` property to false, will present you with a list of
selected files that you can then upload manually one by one:
:::php
$uploadField->setConfig('autoUpload', false);
$uploadField->setAutoUpload(false);
### Build a simple gallery
A gallery most times needs more then simple images. You might want to add a
@ -171,8 +217,14 @@ Now register the DataExtension for the Image class in your _config.php:
:::php
Image::add_extension('GalleryImage');
NOTE: although you can subclass the Image class instead of using a DataExtension, this is not advisable. For instance: when using a subclass, the 'From files' button will only return files that were uploaded for that subclass, it won't recognize any other images!
<div class="notice" markdown='1'>
Note: Although you can subclass the Image class instead of using a DataExtension,
this is not advisable. For instance: when using a subclass, the 'From files'
button will only return files that were uploaded for that subclass, it won't
recognize any other images!
</div>
### Edit uploaded images
By default the UploadField will let you edit the following fields: *Title,
@ -195,31 +247,62 @@ you you alter these settings. One way to go about this is create a
Then, in your GalleryPage, tell the UploadField to use this function:
:::php
$uploadField->setConfig('fileEditFields', 'getCustomFields');
$uploadField->setFileEditFields('getCustomFields');
In a similar fashion you can use 'fileEditActions' to set the actions for the
In a similar fashion you can use 'setFileEditActions' to set the actions for the
editform, or 'fileEditValidator' to determine the validator (eg RequiredFields).
### Configuration Reference
- `autoUpload`: (boolean)
- `allowedMaxFileNumber`: (int) php validation of allowedMaxFileNumber
- `setAllowedMaxFileNumber`: (int) php validation of allowedMaxFileNumber
only works when a db relation is available, set to null to allow
unlimited if record has a has_one and allowedMaxFileNumber is null, it will be set to 1
- `canUpload`: (boolean) Can the user upload new files, or just select from existing files.
- `setAllowedFileExtensions`: (array) List of file extensions allowed
- `setAllowedFileCategories`: (array|string) List of types of files allowed.
May be any of 'image', 'audio', 'mov', 'zip', 'flash', or 'doc'
- `setAutoUpload`: (boolean) Should the field automatically trigger an upload once
a file is selected?
- `setCanAttachExisting`: (boolean|string) Can the user attach existing files from the library.
String values are interpreted as permission codes.
- `previewMaxWidth`: (int)
- `previewMaxHeight`: (int)
- `uploadTemplateName`: (string) javascript template used to display uploading
files, see javascript/UploadField_uploadtemplate.js
- `downloadTemplateName`: (string) javascript template used to display already
- `setCanPreviewFolder`: (boolean|string) Can the user preview the folder files will be saved into?
String values are interpreted as permission codes.
- `setCanUpload`: (boolean|string) Can the user upload new files, or just select from existing files.
String values are interpreted as permission codes.
- `setDownloadTemplateName`: (string) javascript template used to display already
uploaded files, see javascript/UploadField_downloadtemplate.js
- `fileEditFields`: (FieldList|string) FieldList $fields or string $name
- `setFileEditFields`: (FieldList|string) FieldList $fields or string $name
(of a method on File to provide a fields) for the EditForm (Example: 'getCMSFields')
- `fileEditActions`: (FieldList|string) FieldList $actions or string $name
- `setFileEditActions`: (FieldList|string) FieldList $actions or string $name
(of a method on File to provide a actions) for the EditForm (Example: 'getCMSActions')
- `fileEditValidator`: (string) Validator (eg RequiredFields) or string $name
- `setFileEditValidator`: (string) Validator (eg RequiredFields) or string $name
(of a method on File to provide a Validator) for the EditForm (Example: 'getCMSValidator')
- `setOverwriteWarning`: (boolean) Show a warning when overwriting a file.
- `setPreviewMaxWidth`: (int)
- `setPreviewMaxHeight`: (int)
- `setTemplateFileButtons`: (string) Template name to use for the file buttons
- `setTemplateFileEdit`: (string) Template name to use for the file edit form
- `setUploadTemplateName`: (string) javascript template used to display uploading
files, see javascript/UploadField_uploadtemplate.js
- `setCanPreviewFolder`: (boolean|string) Is the upload folder visible to uploading users?
String values are interpreted as permission codes.
Certain default values for the above can be configured using the YAML config system.
:::yaml
UploadField:
defaultConfig:
autoUpload: true
allowedMaxFileNumber:
canUpload: true
canAttachExisting: 'CMS_ACCESS_AssetAdmin'
canPreviewFolder: true
previewMaxWidth: 80
previewMaxHeight: 60
uploadTemplateName: 'ss-uploadfield-uploadtemplate'
downloadTemplateName: 'ss-uploadfield-downloadtemplate'
overwriteWarning: true # Warning before overwriting existing file (only relevant when Upload: replaceFile is true)
The above settings can also be set on a per-instance basis by using `setConfig` with the appropriate key.
You can also configure the underlying `[api:Upload]` class, by using the YAML config system.
@ -227,8 +310,65 @@ You can also configure the underlying `[api:Upload]` class, by using the YAML co
Upload:
# Globally disables automatic renaming of files and displays a warning before overwriting an existing file
replaceFile: true
uploads_folder: 'Uploads'
## Using the UploadField in a frontend form
## TODO: Using the UploadField in a frontend form
The UploadField can be used in a frontend form, given that sufficient attention is given
to the permissions granted to non-authorised users.
*At this moment the UploadField not yet fully supports being used on a frontend
form.*
By default Image::canDelete and Image::canEdit do not require admin privileges, so
make sure you override the methods in your Image extension class.
For instance, to generate an upload form suitable for saving images into a user-defined
gallery the below code could be used:
:::php
// In GalleryPage.php
class GalleryPage extends Page {}
class GalleryPage_Controller extends Page_Controller {
public function Form() {
$fields = new FieldList(
new TextField('Title', 'Title', null, 255),
$field = new UploadField('Images', 'Upload Images')
);
$field->setCanAttachExisting(false); // Block access to Silverstripe assets library
$field->setCanPreviewFolder(false); // Don't show target filesystem folder on upload field
$field->relationAutoSetting = false; // Prevents the form thinking the GalleryPage is the underlying object
$actions = new FieldList(new FormAction('submit', 'Save Images'));
return new Form($this, 'Form', $fields, $actions, null);
}
public function submit($data, Form $form) {
$gallery = new Gallery();
$form->saveInto($gallery);
$gallery->write();
return $this;
}
}
// In Gallery.php
class Gallery extends DataObject {
private static $db = array(
'Title' => 'Varchar(255)'
);
private static $many_many = array(
'Images' => 'Image'
);
}
// In ImageExtension.php
class ImageExtension extends DataExtension {
private static $belongs_many_many = array(
'Gallery' => 'Gallery'
);
function canEdit($member) {
// This part is important!
return Permission::check('ADMIN');
}
}
Image::add_extension('ImageExtension');

View File

@ -340,10 +340,10 @@ class File extends DataObject {
}
// Upload
$uploadField = new UploadField('UploadField','Upload Field');
$uploadField->setConfig('previewMaxWidth', 40);
$uploadField->setConfig('previewMaxHeight', 30);
$uploadField->setConfig('allowedMaxFileNumber', 1);
$uploadField = UploadField::create('UploadField','Upload Field')
->setPreviewMaxWidth(40)
->setPreviewMaxHeight(30)
->setAllowedMaxFileNumber(1);
//$uploadField->setTemplate('FileEditUploadField');
if ($this->ParentID) {
$parent = $this->Parent();

View File

@ -28,12 +28,14 @@ class Upload extends Controller {
/**
* A File object
*
* @var File
*/
protected $file;
/**
* An instance of Upload_Validator
* Validator for this upload field
*
* @var Upload_Validator
*/
protected $validator;
@ -48,7 +50,8 @@ class Upload extends Controller {
/**
* Replace an existing file rather than renaming the new one.
* @var Boolean
*
* @var boolean
*/
protected $replaceFile;
@ -72,13 +75,13 @@ class Upload extends Controller {
public function __construct() {
parent::__construct();
$this->validator = new Upload_Validator();
$this->replaceFile = $this->config()->replaceFile;
$this->replaceFile = self::config()->replaceFile;
}
/**
* Get current validator
*
* @return object $validator
* @return Upload_Validator $validator
*/
public function getValidator() {
return $this->validator;
@ -277,7 +280,7 @@ class Upload extends Controller {
* @return array
*/
public function getErrors() {
return $this->errors;
return $this->errors;
}
}

View File

@ -132,7 +132,7 @@ class FileField extends FormField {
/**
* Get custom validator for this field
*
* @param object $validator
* @param Upload_Validator $validator
*/
public function getValidator() {
return $this->upload->getValidator();
@ -141,7 +141,8 @@ class FileField extends FormField {
/**
* Set custom validator for this field
*
* @param object $validator
* @param Upload_Validator $validator
* @return FileField Self reference
*/
public function setValidator($validator) {
$this->upload->setValidator($validator);
@ -149,7 +150,10 @@ class FileField extends FormField {
}
/**
* Sets the upload folder name
*
* @param string $folderName
* @return FileField Self reference
*/
public function setFolderName($folderName) {
$this->folderName = $folderName;
@ -157,6 +161,8 @@ class FileField extends FormField {
}
/**
* Gets the upload folder name
*
* @return string
*/
public function getFolderName() {
@ -181,14 +187,23 @@ class FileField extends FormField {
}
/**
* 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;
}
}

View File

@ -208,6 +208,8 @@ class FormField extends RequestHandler {
/**
* Method to save this form field into the given data object.
* By default, makes use of $this->dataValue()
*
* @param DataObjectInterface $record DataObject to save data into
*/
public function saveInto(DataObjectInterface $record) {
if($this->name) {
@ -403,7 +405,9 @@ class FormField extends RequestHandler {
/**
* Set the field value.
* Returns $this.
*
* @param mixed $value
* @return FormField Self reference
*/
public function setValue($value) {
$this->value = $value;

View File

@ -2,14 +2,13 @@
/**
* Field for uploading single or multiple files of all types, including images.
* <b>NOTE: this Field will call write() on the supplied record</b>
*
* <b>Features (some might not be available to old browsers):</b>
*
* - File Drag&Drop support
* - Progressbar
* - Image thumbnail/file icons even before upload finished
* - Saving into relations
* - Saving into relations on form submit
* - Edit file
* - allowedExtensions is by default File::$allowed_extensions<li>maxFileSize the value of min(upload_max_filesize,
* post_max_size) from php.ini
@ -17,9 +16,9 @@
* <>Usage</b>
*
* @example <code>
* $UploadField = new UploadField('myFiles', 'Please upload some images <span>(max. 5 files)</span>');
* $UploadField->getValidator()->setAllowedExtensions(array('jpg', 'jpeg', 'png', 'gif'));
* $UploadField->setConfig('allowedMaxFileNumber', 5);
* $UploadField = new UploadField('AttachedImages', 'Please upload some images <span>(max. 5 files)</span>');
* $UploadField->setAllowedFileCategories('image');
* $UploadField->setAllowedMaxFileNumber(5);
* </code>
*
* @author Zauberfisch
@ -36,6 +35,7 @@ class UploadField extends FileField {
'attach',
'handleItem',
'handleSelect',
'fileexists'
);
/**
@ -48,107 +48,141 @@ class UploadField extends FileField {
);
/**
* @var String
* Template to use for the file button widget
*
* @var string
*/
protected $templateFileButtons = 'UploadField_FileButtons';
/**
* @var String
* Template to use for the edit form
*
* @var string
*/
protected $templateFileEdit = 'UploadField_FileEdit';
/**
* Parent data record. Will be infered from parent form or controller if blank.
*
* @var DataObject
*/
protected $record;
/**
* Items loaded into this field. May be a RelationList, or any other SS_List
*
* @var SS_List
*/
protected $items;
/**
* @var array Config for this field used in both, php and javascript
* Config for this field used in the front-end javascript
* (will be merged into the config of the javascript file upload plugin).
* See framework/_config/uploadfield.yml for configuration defaults and documentation.
*
* @var array
*/
protected $ufConfig = array(
/**
* Automatically upload the file once selected
*
* @var boolean
*/
'autoUpload' => true,
/**
* php validation of allowedMaxFileNumber only works when a db relation is available, set to null to allow
* unlimited if record has a has_one and allowedMaxFileNumber is null, it will be set to 1
* @var int
* 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,
/**
* @var boolean|string Can the user upload new files, or just select from existing files.
* Can the user upload new files, or just select from existing files.
* String values are interpreted as permission codes.
*
* @var boolean|string
*/
'canUpload' => true,
/**
* @var boolean|string Can the user attach files from the assets archive on the site?
* 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",
/**
* @var boolean Shows the target folder for new uploads in the field UI.
* 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,
/**
* @var boolean If a second file is uploaded, should it replace the existing one rather than throwing an errror?
* This only applies for has_one relationships, and only replaces the association
* rather than the actual file database record or filesystem entry.
*/
'replaceExistingFile' => false,
/**
* @var int
* Maximum width of the preview thumbnail
*
* @var integer
*/
'previewMaxWidth' => 80,
/**
* @var int
* 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',
/**
* FieldList $fields or string $name (of a method on File to provide a fields) for the EditForm
* @example 'getCMSFields'
* @var FieldList|string
* 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)
*
* @see Upload
* @var boolean
*/
'fileEditFields' => null,
/**
* FieldList $actions or string $name (of a method on File to provide a actions) for the EditForm
* @example 'getCMSActions'
* @var FieldList|string
*/
'fileEditActions' => null,
/**
* Validator (eg RequiredFields) or string $name (of a method on File to provide a Validator) for the EditForm
* @example 'getCMSValidator'
* @var string
*/
'fileEditValidator' => null,
/**
* Show a warning when overwriting a file
*/
'overwriteWarning' => true,
'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;
/**
* 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;
/**
* 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;
/**
* 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
@ -156,11 +190,12 @@ class UploadField extends FileField {
* @param Form $form Reference to the container form
*/
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 = array_merge($this->ufConfig, Config::inst()->get('UploadField', 'defaultConfig'));
$this->ufConfig = array_merge($this->ufConfig, self::config()->defaultConfig);
parent::__construct($name, $title);
@ -170,15 +205,17 @@ class UploadField extends FileField {
$this->getValidator()->setAllowedExtensions(
array_filter(Config::inst()->get('File', 'allowed_extensions'))
);
// get the lower max size
$this->getValidator()->setAllowedMaxFileSize(min(File::ini2bytes(ini_get('upload_max_filesize')),
File::ini2bytes(ini_get('post_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
* @param string
*/
public function setTemplateFileButtons($template) {
$this->templateFileButtons = $template;
@ -186,7 +223,7 @@ class UploadField extends FileField {
}
/**
* @return String
* @return string
*/
public function getTemplateFileButtons() {
return $this->templateFileButtons;
@ -195,7 +232,7 @@ class UploadField extends FileField {
/**
* Set name of template used for the edit (inline & popup) of a file file (without path or extension)
*
* @param String
* @param string
*/
public function setTemplateFileEdit($template) {
$this->templateFileEdit = $template;
@ -203,12 +240,60 @@ class UploadField extends FileField {
}
/**
* @return String
* @return string
*/
public function getTemplateFileEdit() {
return $this->templateFileEdit;
}
/**
* 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);
}
/**
* Force a record to be used as "Parent" for uploaded Files (eg a Page with a has_one to File)
* @param DataObject $record
@ -220,71 +305,190 @@ class UploadField extends FileField {
/**
* 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 ($this->form->getRecord() && is_a($this->form->getRecord(), 'DataObject')) {
$this->record = $this->form->getRecord();
} elseif ($this->form->Controller() && $this->form->Controller()->hasMethod('data')
&& $this->form->Controller()->data() && is_a($this->form->Controller()->data(), 'DataObject')) {
$this->record = $this->form->Controller()->data();
if (($record = $this->form->getRecord()) && ($record instanceof DataObject)) {
$this->record = $record;
} elseif (($controller = $this->form->Controller())
&& $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 UploadField Self reference
*/
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
return parent::setValue($value, $record);
}
/**
* 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) {
$this->items = $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();
}
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($record->has_one($fieldname)) {
// has_one
$record->{"{$fieldname}ID"} = $idList ? reset($idList) : 0;
}
return $this;
}
/**
* @return SS_List
*/
public function getItems() {
$name = $this->getName();
if (!$this->items || !$this->items->exists()) {
$record = $this->getRecord();
$this->items = array();
// Try to auto-detect relationship
if ($record && $record->exists()) {
if ($record->has_many($name) || $record->many_many($name)) {
// Ensure relationship is cast to an array, as we can't alter the items of a DataList/RelationList
// (see below)
$this->items = $record->{$name}()->toArray();
} elseif($record->has_one($name)) {
$item = $record->{$name}();
if ($item && $item->exists())
$this->items = array($record->{$name}());
}
}
$this->items = new ArrayList($this->items);
// hack to provide $UploadFieldThumbnailURL, $hasRelation and $UploadFieldEditLink in template for each
// file
if ($this->items->exists()) {
foreach ($this->items as $i=>$file) {
$this->items[$i] = $this->customiseFile($file);
if(!$file->canView()) unset($this->items[$i]); // Respect model permissions
}
}
}
return $this->items;
}
/**
* Hack to add some Variables and a dynamic template to a File
* Customises a file with additional details suitable for rendering in the
* UploadField.ss template
*
* @param File $file
* @return ViewableData_Customised
*/
protected function customiseFile(File $file) {
$file = $file->customise(array(
'UploadFieldHasRelation' => $this->managesRelation(),
'UploadFieldThumbnailURL' => $this->getThumbnailURLForFile($file),
'UploadFieldRemoveLink' => $this->getItemHandler($file->ID)->RemoveLink(),
'UploadFieldDeleteLink' => $this->getItemHandler($file->ID)->DeleteLink(),
'UploadFieldEditLink' => $this->getItemHandler($file->ID)->EditLink(),
'UploadField' => $this
@ -296,44 +500,409 @@ class UploadField extends FileField {
}
/**
* Assign a front-end config variable for the upload field
*
* @param string $key
* @param mixed $val
* @return UploadField self reference
*/
public function setConfig($key, $val) {
if(!array_key_exists($key, $this->ufConfig)) {
user_error("UploadField->setConfig called with invalid option: '$key'", E_USER_ERROR);
}
$this->ufConfig[$key] = $val;
return $this;
}
/**
* Gets a front-end config variable for the upload field
*
* @param string $key
* @return mixed
*/
public function getConfig($key) {
if(!array_key_exists($key, $this->ufConfig)) {
user_error("UploadField->getConfig called with invalid option: '$key'", E_USER_ERROR);
}
return $this->ufConfig[$key];
}
/**
* Used to get config in the template
* 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 && $record->has_one($name)) {
return 1; // Default for has_one
} else {
return null; // Default for has_many and many_many
}
} else {
return $allowedMaxFileNumber;
}
}
/**
* 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 UploadField Self reference
*/
public function setAllowedExtensions($rules) {
$this->getValidator()->setAllowedExtensions($rules);
return $this;
}
/**
* Limit allowed file extensions by specifying categories of file types.
* These may be 'image', 'audio', 'mov', 'zip', 'flash', or 'doc'
* 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 UploadField Self reference
*/
public function setAllowedFileCategories($category) {
$extensions = array();
$knownCategories = File::config()->app_categories;
// Parse arguments
$categories = func_get_args();
if(func_num_args() === 1 && is_array(reset($categories))) {
$categories = reset($categories);
}
// Merge all categories into list of extensions
foreach(array_filter($categories) as $category) {
if(isset($knownCategories[$category])) {
$extensions = array_merge($extensions, $knownCategories[$category]);
} else {
user_error("Unknown file category: $category", E_USER_ERROR);
}
}
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();
}
/**
* 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
* @var 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
* @var 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 File $file File context to generate fields for
* @return FieldList List of form fields
*/
public function getFileEditFields(File $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}();
}
user_error("Invalid value for UploadField::fileEditFields", E_USER_ERROR);
}
/**
* 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 File $file File context to generate form actions for
* @return FieldList Field list containing FormAction
*/
public function getFileEditActions(File $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}();
}
user_error("Invalid value for UploadField::fileEditActions", E_USER_ERROR);
}
/**
* 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 File $file File context to generate validator from
* @return Validator Validator object
*/
public function getFileEditValidator(File $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}();
}
user_error("Invalid value for UploadField::fileEditValidator", E_USER_ERROR);
}
/**
* 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 $file
* @return string
*/
protected function getThumbnailURLForFile(File $file) {
if ($file && $file->exists() && file_exists(Director::baseFolder() . '/' . $file->getFilename())) {
if ($file->exists() && file_exists(Director::baseFolder() . '/' . $file->getFilename())) {
$width = $this->getPreviewMaxWidth();
$height = $this->getPreviewMaxHeight();
if ($file->hasMethod('getThumbnail')) {
return $file->getThumbnail($this->getConfig('previewMaxWidth'),
$this->getConfig('previewMaxHeight'))->getURL();
return $file->getThumbnail($width, $height)->getURL();
} elseif ($file->hasMethod('getThumbnailURL')) {
return $file->getThumbnailURL($this->getConfig('previewMaxWidth'),
$this->getConfig('previewMaxHeight'));
return $file->getThumbnailURL($width, $height);
} elseif ($file->hasMethod('SetRatioSize')) {
return $file->SetRatioSize($this->getConfig('previewMaxWidth'),
$this->getConfig('previewMaxHeight'))->getURL();
return $file->SetRatioSize($width, $height)->getURL();
} else {
return $file->Icon();
}
@ -355,18 +924,6 @@ class UploadField extends FileField {
}
public function Field($properties = array()) {
$record = $this->getRecord();
$name = $this->getName();
// 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(
$record && $record->exists()
&& $record->has_one($name) && !$this->getConfig('allowedMaxFileNumber')
) {
$this->setConfig('allowedMaxFileNumber', 1);
}
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
Requirements::javascript(THIRDPARTY_DIR . '/jquery-ui/jquery-ui.js');
Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
@ -374,7 +931,8 @@ class UploadField extends FileField {
Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/ssui.core.js');
Requirements::combine_files('uploadfield.js', array(
THIRDPARTY_DIR . '/javascript-templates/tmpl.js',
// @todo jquery templates is a project no longer maintained and should be retired at some point.
THIRDPARTY_DIR . '/javascript-templates/tmpl.js',
THIRDPARTY_DIR . '/javascript-loadimage/load-image.js',
THIRDPARTY_DIR . '/jquery-fileupload/jquery.iframe-transport.js',
THIRDPARTY_DIR . '/jquery-fileupload/cors/jquery.xdr-transport.js',
@ -387,69 +945,120 @@ class UploadField extends FileField {
Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css'); // TODO hmmm, remove it?
Requirements::css(FRAMEWORK_DIR . '/css/UploadField.css');
// 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' => '.+$',
'maxNumberOfFiles' => $this->getConfig('allowedMaxFileNumber')
// Fileupload treats maxNumberOfFiles as the max number of _additional_ items allowed
'maxNumberOfFiles' => $allowedMaxFileNumber ? ($allowedMaxFileNumber - count($this->getItemIDs())) : null
);
if (count($this->getValidator()->getAllowedExtensions())) {
$allowedExtensions = $this->getValidator()->getAllowedExtensions();
// Validation: File extensions
if ($allowedExtensions = $this->getAllowedExtensions()) {
$config['acceptFileTypes'] = '(\.|\/)(' . implode('|', $allowedExtensions) . ')$';
$config['errorMessages']['acceptFileTypes'] = _t(
'File.INVALIDEXTENSIONSHORT',
'Extension is not allowed'
);
}
if ($this->getValidator()->getAllowedMaxFileSize()) {
$config['maxFileSize'] = $this->getValidator()->getAllowedMaxFileSize();
// 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']))
);
}
if ($config['maxNumberOfFiles'] > 1) {
$config['errorMessages']['maxNumberOfFiles'] = _t(
'UploadField.MAXNUMBEROFFILESSHORT',
'Can only upload {count} files',
array('count' => $config['maxNumberOfFiles'])
);
}
$configOverwrite = array();
if (is_numeric($config['maxNumberOfFiles']) && $this->getItems()->count()) {
$configOverwrite['maxNumberOfFiles'] = $config['maxNumberOfFiles'] - $this->getItems()->count();
// 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'
);
}
}
//get all the existing files in the current folder
if ($this->getConfig('overwriteWarning')) {
$folder = Folder::find_or_make($this->getFolderName());
$files = glob( $folder->getFullPath() . '/*' );
$config['existingFiles'] = array_map("basename", $files);;
if ($this->getOverwriteWarning()) {
//add overwrite warning error message to the config object sent to Javascript
$config['errorMessages']['overwriteWarning'] =
_t('UploadField.OVERWRITEWARNING','File with the same name already exists');
_t('UploadField.OVERWRITEWARNING', 'File with the same name already exists');
}
$config = array_merge($config, $this->ufConfig, $configOverwrite);
$mergedConfig = array_merge($config, $this->ufConfig);
return $this->customise(array(
'configString' => str_replace('"', "'", Convert::raw2json($config)),
'config' => new ArrayData($config),
'multiple' => $config['maxNumberOfFiles'] !== 1,
'displayInput' => (!isset($configOverwrite['maxNumberOfFiles']) || $configOverwrite['maxNumberOfFiles'])
'configString' => str_replace('"', "'", Convert::raw2json($mergedConfig)),
'config' => new ArrayData($mergedConfig),
'multiple' => $allowedMaxFileNumber !== 1
))->renderWith($this->getTemplates());
}
/**
* Validation method for this field, called when the entire form is validated
*
* @param $validator
* @return Boolean
* @param Validator $validator
* @return boolean
*/
public function validate($validator) {
// @todo Test compatibility with RequiredFields
$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;
}
@ -476,12 +1085,117 @@ class UploadField extends FileField {
public function handleSelect(SS_HTTPRequest $request) {
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
// <input name='{$Name}[Uploads][]' /> 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 File 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)) {
// Create new object explicitly. Otherwise rely on Upload::load to choose the class.
$fileObject = Object::create($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 $file
* @return array Array encoded list of file attributes
*/
protected function encodeFileAttributes(File $file) {
// Collect all output data.
$file = $this->customiseFile($file);
return array(
'id' => $file->ID,
'name' => $file->Name,
'url' => $file->URL,
'thumbnail_url' => $file->UploadFieldThumbnailURL,
'edit_url' => $file->UploadFieldEditLink,
'size' => $file->AbsoluteSize,
'type' => $file->FileType,
'buttons' => $file->UploadFieldFileButtons,
'fieldname' => $this->getName()
);
}
/**
* Action to handle upload of a single file
*
* @param SS_HTTPRequest $request
* @return string json
* @return SS_HTTPResponse
*/
public function upload(SS_HTTPRequest $request) {
if($this->isDisabled() || $this->isReadonly() || !$this->canUpload()) {
@ -491,148 +1205,75 @@ class UploadField extends FileField {
// Protect against CSRF on destructive action
$token = $this->getForm()->getSecurityToken();
if(!$token->checkRequest($request)) return $this->httpError(400);
$name = $this->getName();
$tmpfile = $request->postVar($name);
$record = $this->getRecord();
// Check if the file has been uploaded into the temporary storage.
if (!$tmpfile) {
$return = array('error' => _t('UploadField.FIELDNOTSET', 'File information not found'));
// Get form details
$name = $this->getName();
$postVars = $request->postVar($name);
// Save the temporary file into a File object
$uploadedFiles = $this->extractUploadedFileData($postVars);
$firstFile = reset($uploadedFiles);
$file = $this->saveTemporaryFile($firstFile, $error);
if(empty($file)) {
$return = array('error' => $error);
} else {
$return = array(
'name' => $tmpfile['name'],
'size' => $tmpfile['size'],
'type' => $tmpfile['type'],
'error' => $tmpfile['error']
);
}
// Check for constraints on the record to which the file will be attached.
if (!$return['error'] && $this->relationAutoSetting && $record && $record->exists()) {
$tooManyFiles = false;
// Some relationships allow many files to be attached.
if ($this->getConfig('allowedMaxFileNumber') && ($record->has_many($name) || $record->many_many($name))) {
if(!$record->isInDB()) $record->write();
$tooManyFiles = $record->{$name}()->count() >= $this->getConfig('allowedMaxFileNumber');
// has_one only allows one file at any given time.
} elseif($record->has_one($name)) {
// If we're allowed to replace an existing file, clear out the old one
if($record->$name && $this->getConfig('replaceExistingFile')) {
$record->$name = null;
}
$tooManyFiles = $record->{$name}() && $record->{$name}()->exists();
}
// Report the constraint violation.
if ($tooManyFiles) {
if(!$this->getConfig('allowedMaxFileNumber')) $this->setConfig('allowedMaxFileNumber', 1);
$return['error'] = _t(
'UploadField.MAXNUMBEROFFILES',
'Max number of {count} file(s) exceeded.',
array('count' => $this->getConfig('allowedMaxFileNumber'))
);
}
}
// Process the uploaded file
if (!$return['error']) {
$fileObject = null;
if ($this->relationAutoSetting) {
// Search for relations that can hold the uploaded files.
if ($relationClass = $this->getRelationAutosetClass()) {
// Create new object explicitly. Otherwise rely on Upload::load to choose the class.
$fileObject = Object::create($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
$return['error'] = $e->getMessage();
}
if (!$return['error']) {
if ($this->upload->isError()) {
$return['error'] = implode(' '.PHP_EOL, $this->upload->getErrors());
} else {
$file = $this->upload->getFile();
// Attach the file to the related record.
if ($this->relationAutoSetting) {
$this->attachFile($file);
}
// Collect all output data.
$file = $this->customiseFile($file);
$return = array_merge($return, array(
'id' => $file->ID,
'name' => $file->getTitle() . '.' . $file->getExtension(),
'url' => $file->getURL(),
'thumbnail_url' => $file->UploadFieldThumbnailURL,
'edit_url' => $file->UploadFieldEditLink,
'size' => $file->getAbsoluteSize(),
'buttons' => $file->UploadFieldFileButtons
));
}
}
$return = $this->encodeFileAttributes($file);
}
// Format response with json
$response = new SS_HTTPResponse(Convert::raw2json(array($return)));
$response->addHeader('Content-Type', 'text/plain');
return $response;
}
/**
* Add existing {@link File} records to the relationship.
* Retrieves details for files that this field wishes to attache to the
* client-side form
*
* @param SS_HTTPRequest $request
* @return SS_HTTPResponse
*/
public function attach($request) {
public function attach(SS_HTTPRequest $request) {
if(!$request->isPOST()) return $this->httpError(403);
if(!$this->managesRelation()) 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) {
$this->attachFile($file);
$file = $this->customiseFile($file);
$return[] = array(
'id' => $file->ID,
'name' => $file->getTitle() . '.' . $file->getExtension(),
'url' => $file->getURL(),
'thumbnail_url' => $file->UploadFieldThumbnailURL,
'edit_url' => $file->UploadFieldEditLink,
'size' => $file->getAbsoluteSize(),
'buttons' => $file->UploadFieldFileButtons
);
$return[] = $this->encodeFileAttributes($file);
}
$response = new SS_HTTPResponse(Convert::raw2json($return));
$response->addHeader('Content-Type', 'application/json');
return $response;
}
/**
* @param File
* Determines if a specified file exists
*
* @param SS_HTTPRequest $request
*/
protected function attachFile($file) {
$replaceFileID = $this->getRequest()->requestVar('ReplaceFileID');
$record = $this->getRecord();
$name = $this->getName();
if ($record && $record->exists()) {
if (($record->has_many($name) || $record->many_many($name))) {
if(!$record->isInDB()) $record->write();
if ($replaceFileID){
$record->{$name}()->removebyId($replaceFileID);
}
$record->{$name}()->add($file);
} elseif($record->has_one($name)) {
$record->{$name . 'ID'} = $file->ID;
$record->write();
public function fileexists(SS_HTTPRequest $request) {
// Check both original and safely filtered filename
$originalFile = $request->requestVar('filename');
$nameFilter = FileNameFilter::create();
$filteredFile = basename($nameFilter->filter($originalFile));
// check if either file exists
$folder = $this->getFolderName();
$exists = false;
foreach(array($originalFile, $filteredFile) as $file) {
if(file_exists(ASSETS_PATH."/$folder/$file")) {
$exists = true;
break;
}
}
// Encode and present response
$response = new SS_HTTPResponse(Convert::raw2json(array('exists' => $exists)));
$response->addHeader('Content-Type', 'application/json');
return $response;
}
public function performReadonlyTransformation() {
@ -643,55 +1284,26 @@ class UploadField extends FileField {
}
/**
* Determines if the underlying record (if any) has a relationship
* matching the field name. Important for permission control.
*
* @return boolean
*/
public function managesRelation() {
$record = $this->getRecord();
$fieldName = $this->getName();
return (
$record
&& ($record->has_one($fieldName) || $record->has_many($fieldName) || $record->many_many($fieldName))
);
}
/**
* Gets the foreign class that needs to be created.
* Gets the foreign class that needs to be created, or 'File' as default if there
* is no relationship, or it cannot be determined.
*
* @param $default Default value to return if no value could be calculated
* @return string Foreign class name.
*/
public function getRelationAutosetClass() {
public function getRelationAutosetClass($default = '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 (isset($name) && isset($record)) return $record->getRelationClass($name);
}
public function isDisabled() {
return (parent::isDisabled() || !$this->isSaveable());
}
/**
* Determines if the field can be saved into a database record.
*
* @return boolean
*/
public function isSaveable() {
$record = $this->getRecord();
// Don't allow upload or edit of a relation when the underlying record hasn't been persisted yet
return (!$record || !$this->managesRelation() || $record->exists());
}
public function canUpload() {
$can = $this->getConfig('canUpload');
return (is_bool($can)) ? $can : Permission::check($can);
}
public function canAttachExisting() {
$can = $this->getConfig('canAttachExisting');
return (is_bool($can)) ? $can : Permission::check($can);
if(empty($name) || empty($record)) {
return $default;
} else {
$class = $record->getRelationClass($name);
return empty($class) ? $default : $class;
}
}
}
@ -746,14 +1358,6 @@ class UploadField_ItemHandler extends RequestHandler {
return Controller::join_links($this->parent->Link(), '/item/', $this->itemID, $action);
}
/**
* @return string
*/
public function RemoveLink() {
$token = $this->parent->getForm()->getSecurityToken();
return $token->addToUrl($this->Link('remove'));
}
/**
* @return string
*/
@ -769,42 +1373,6 @@ class UploadField_ItemHandler extends RequestHandler {
return $this->Link('edit');
}
/**
* Action to handle removing a single file from the db relation
*
* @param SS_HTTPRequest $request
* @return SS_HTTPResponse
*/
public function remove(SS_HTTPRequest $request) {
// Check form field state
if($this->parent->isDisabled() || $this->parent->isReadonly()) return $this->httpError(403);
// Protect against CSRF on destructive action
$token = $this->parent->getForm()->getSecurityToken();
if(!$token->checkRequest($request)) return $this->httpError(400);
$response = new SS_HTTPResponse();
$response->setStatusCode(500);
$fieldName = $this->parent->getName();
$record = $this->parent->getRecord();
$id = $this->getItem()->ID;
if ($id && $record && $record->exists()) {
if (($record->has_many($fieldName) || $record->many_many($fieldName))
&& $file = $record->{$fieldName}()->byID($id)) {
$record->{$fieldName}()->remove($file);
$response->setStatusCode(200);
} elseif($record->has_one($fieldName) && $record->{$fieldName . 'ID'} == $id) {
$record->{$fieldName . 'ID'} = 0;
$record->write();
$response->setStatusCode(200);
}
}
if ($response->getStatusCode() != 200)
$response->setStatusDescription(_t('UploadField.REMOVEERROR', 'Error removing file'));
return $response;
}
/**
* Action to handle deleting of a single file
*
@ -824,14 +1392,9 @@ class UploadField_ItemHandler extends RequestHandler {
if(!$item) return $this->httpError(404);
if(!$item->canDelete()) return $this->httpError(403);
// Only allow actions on files in the managed relation (if one exists)
$items = $this->parent->getItems();
if($this->parent->managesRelation() && !$items->byID($item->ID)) return $this->httpError(403);
// First remove the file from the current relationship
$this->remove($request);
// Then delete the file from the filesystem
// Delete the file from the filesystem. The file will be removed
// from the relation on save
// @todo Investigate if references to deleted files (if unsaved) is dangerous
$item->delete();
}
@ -850,10 +1413,6 @@ class UploadField_ItemHandler extends RequestHandler {
if(!$item) return $this->httpError(404);
if(!$item->canEdit()) return $this->httpError(403);
// Only allow actions on files in the managed relation (if one exists)
$items = $this->parent->getItems();
if($this->parent->managesRelation() && !$items->byID($item->ID)) return $this->httpError(403);
Requirements::css(FRAMEWORK_DIR . '/css/UploadField.css');
return $this->customise(array(
@ -866,30 +1425,10 @@ class UploadField_ItemHandler extends RequestHandler {
*/
public function EditForm() {
$file = $this->getItem();
if (is_a($this->parent->getConfig('fileEditFields'), 'FieldList')) {
$fields = $this->parent->getConfig('fileEditFields');
} elseif ($file->hasMethod($this->parent->getConfig('fileEditFields'))) {
$fields = $file->{$this->parent->getConfig('fileEditFields')}();
} else {
$fields = $file->getCMSFields();
// Only display main tab, to avoid overly complex interface
if($fields->hasTabSet() && $mainTab = $fields->findOrMakeTab('Root.Main')) $fields = $mainTab->Fields();
}
if (is_a($this->parent->getConfig('fileEditActions'), 'FieldList')) {
$actions = $this->parent->getConfig('fileEditActions');
} elseif ($file->hasMethod($this->parent->getConfig('fileEditActions'))) {
$actions = $file->{$this->parent->getConfig('fileEditActions')}();
} else {
$actions = new FieldList($saveAction = new FormAction('doEdit', _t('UploadField.DOEDIT', 'Save')));
$saveAction->addExtraClass('ss-ui-action-constructive icon-accept');
}
if (is_a($this->parent->getConfig('fileEditValidator'), 'Validator')) {
$validator = $this->parent->getConfig('fileEditValidator');
} elseif ($file->hasMethod($this->parent->getConfig('fileEditValidator'))) {
$validator = $file->{$this->parent->getConfig('fileEditValidator')}();
} else {
$validator = null;
}
// Get form components
$fields = $this->parent->getFileEditFields($file);
$actions = $this->parent->getFileEditActions($file);
$validator = $this->parent->getFileEditValidator($file);
$form = new Form(
$this,
__FUNCTION__,
@ -916,10 +1455,6 @@ class UploadField_ItemHandler extends RequestHandler {
$item = $this->getItem();
if(!$item) return $this->httpError(404);
// Only allow actions on files in the managed relation (if one exists)
$items = $this->parent->getItems();
if($this->parent->managesRelation() && !$items->byID($item->ID)) return $this->httpError(403);
$form->saveInto($item);
$item->write();
@ -941,7 +1476,7 @@ class UploadField_SelectHandler extends RequestHandler {
protected $parent;
/**
* @var String
* @var string
*/
protected $folderName;
@ -981,7 +1516,7 @@ class UploadField_SelectHandler extends RequestHandler {
$folderID = $this->parent->getRequest()->requestVar('ParentID');
if (!isset($folderID)) {
$folder = Folder::find_or_make($this->folderName);
$folderID = $folder->ID;
$folderID = $folder ? $folder->ID : 0;
}
// Construct the form
@ -1009,8 +1544,6 @@ class UploadField_SelectHandler extends RequestHandler {
$folderField = new TreeDropdownField('ParentID', _t('HtmlEditorField.FOLDER', 'Folder'), 'Folder');
$folderField->setValue($folderID);
// Generate the file list field.
$config = GridFieldConfig::create();
$config->addComponent(new GridFieldSortableHeader());
@ -1019,36 +1552,27 @@ class UploadField_SelectHandler extends RequestHandler {
$config->addComponent(new GridFieldPaginator(10));
// If relation is to be autoset, we need to make sure we only list compatible objects.
$baseClass = null;
if ($this->parent->relationAutoSetting) {
$baseClass = $this->parent->getRelationAutosetClass();
}
// By default we can attach anything that is a file, or derives from file.
if (!$baseClass) $baseClass = 'File';
$baseClass = $this->parent->getRelationAutosetClass();
// Create the data source for the list of files within the current directory.
$files = DataList::create($baseClass)->filter('ParentID', $folderID);
$fileField = new GridField('Files', false, $files, $config);
$fileField->setAttribute('data-selectable', true);
if($this->parent->getConfig('allowedMaxFileNumber') > 1) $fileField->setAttribute('data-multiselect', true);
if($this->parent->getAllowedMaxFileNumber() !== 1) {
$fileField->setAttribute('data-multiselect', true);
}
$selectComposite = new CompositeField(
$folderField,
$fileField
);
//Existing file to replace
if ($replaceFileID = $this->parent->getRequest()->requestVar('ReplaceFileID')) {
$selectComposite->push(new HiddenField('ReplaceFileID','ReplaceFileID', $replaceFileID));
}
return $selectComposite;
}
public function doAttach($data, $form) {
// TODO Only implemented via JS for now
// Popup-window attach does not require server side action, as it is implemented via JS
}
}

View File

@ -1,12 +1,12 @@
(function($) {
$.widget('blueimpUIX.fileupload', $.blueimpUI.fileupload, {
_initTemplates: function() {
this.options.templateContainer = document.createElement(
this._files.prop('nodeName')
);
this.options.uploadTemplate = window.tmpl(this.options.uploadTemplateName);
this.options.downloadTemplate = window.tmpl(this.options.downloadTemplateName);
},
this.options.templateContainer = document.createElement(
this._files.prop('nodeName')
);
this.options.uploadTemplate = window.tmpl(this.options.uploadTemplateName);
this.options.downloadTemplate = window.tmpl(this.options.downloadTemplateName);
},
_enableFileInputButton: function() {
$.blueimpUI.fileupload.prototype._enableFileInputButton.call(this);
this.element.find('.ss-uploadfield-addfile').show();
@ -51,33 +51,33 @@
_onSend: function (e, data) {
//check the array of existing files to see if we are trying to upload a file that already exists
var that = this;
var config = $('div.ss-upload').entwine('ss').getConfig();
var existingFiles = [];
if (typeof (config.existingFiles) !== "undefined") {
existingFiles = config.existingFiles;
}
var fileExists = false;
jQuery.each(existingFiles,function(){
if ($(this)[0].toLowerCase() === data.files[0].name.toLowerCase()) {
fileExists = true;
return;
}
});
if (fileExists) {
//display the dialogs with the question to overwrite or not
data.context.find('.ss-uploadfield-item-status').text(config.errorMessages.overwriteWarning).css('max-width','75%');
data.context.find('.ss-uploadfield-item-progress').hide();
data.context.find('.ss-uploadfield-item-overwrite').show();
data.context.find('.ss-uploadfield-item-overwrite-warning').on('click', function(){
data.context.find('.ss-uploadfield-item-progress').show();
data.context.find('.ss-uploadfield-item-overwrite').hide();
//upload only if the "overwrite" button is clicked
$.blueimpUI.fileupload.prototype._onSend.call(that, e, data);
});
} else { //regular file upload
var config = this.options;
if (config.overwriteWarning) {
$.get(
config['urlFileExists'],
{'filename': data.files[0].name},
function(response, status, xhr) {
if(response.exists) {
//display the dialogs with the question to overwrite or not
data.context.find('.ss-uploadfield-item-status')
.text(config.errorMessages.overwriteWarning)
.addClass('ui-state-warning-text');
data.context.find('.ss-uploadfield-item-progress').hide();
data.context.find('.ss-uploadfield-item-overwrite').show();
data.context.find('.ss-uploadfield-item-overwrite-warning').on('click', function(){
data.context.find('.ss-uploadfield-item-progress').show();
data.context.find('.ss-uploadfield-item-overwrite').hide();
data.context.find('.ss-uploadfield-item-status')
.removeClass('ui-state-warning-text');
//upload only if the "overwrite" button is clicked
$.blueimpUI.fileupload.prototype._onSend.call(that, e, data);
});
} else { //regular file upload
return $.blueimpUI.fileupload.prototype._onSend.call(that, e, data);
}
}
);
} else {
return $.blueimpUI.fileupload.prototype._onSend.call(that, e, data);
}
},
@ -88,7 +88,53 @@
$('.ss-uploadfield-item-edit-all').show();
$('.fileOverview .uploadStatus').addClass("good").removeClass("notice").removeClass("bad");
}
}
},
_create: function() {
$.blueimpUI.fileupload.prototype._create.call(this);
// Ensures that the visibility of the fileupload dialog is set correctly at initialisation
this._adjustMaxNumberOfFiles(0);
},
attach: function(data) {
// Handles attachment of already uploaded files, similar to add
var self = this,
files = data.files,
replaceFileID = data.replaceFileID,
valid = true;
// If replacing an element (and it exists), adjust max number of files at this point
var replacedElement = null;
if(replaceFileID) {
replacedElement = $(".ss-uploadfield-item[data-fileid='"+replaceFileID+"']");
if(replacedElement.length === 0) {
replacedElement = null;
} else {
self._adjustMaxNumberOfFiles(1);
}
}
// Validate each file
$.each(files, function (index, file) {
self._adjustMaxNumberOfFiles(-1);
error = self._validate([file]);
valid = error && valid;
});
data.isAdjusted = true;
data.files.valid = data.isValidated = valid;
// Generate new file HTMl, and either append or replace (if replacing
// an already uploaded file).
data.context = this._renderDownload(files);
if(replacedElement) {
replacedElement.replaceWith(data.context);
} else {
data.context.appendTo(this._files);
}
data.context.data('data', data);
// Force reflow:
this._reflow = this._transition && data.context[0].offsetWidth;
data.context.addClass('in');
}
});
@ -102,7 +148,7 @@
if(this.is('.readonly,.disabled')) return;
var fileInput = this.find('input');
var fileInput = this.find('input[type=file]');
var dropZone = this.find('.ss-uploadfield-dropzone');
var config = $.parseJSON(fileInput.data('config').replace(/'/g,'"'));
@ -148,7 +194,7 @@
];
},
errorMessages: {
// errorMessages for all error codes suggested from the plugin author, some will be overwritten by the config comming from php
// errorMessages for all error codes suggested from the plugin author, some will be overwritten by the config coming from php
1: ss.i18n._t('UploadField.PHP_MAXFILESIZE'),
2: ss.i18n._t('UploadField.HTML_MAXFILESIZE'),
3: ss.i18n._t('UploadField.ONLYPARTIALUPLOADED'),
@ -212,7 +258,6 @@
var uploadedFileId = null;
if (uploadedFile && uploadedFile.attr('data-fileid') > 0){
uploadedFileId = uploadedFile.attr('data-fileid');
iframeUrl = iframeUrl + '?ReplaceFileID=' + uploadedFileId;
}
// Show dialog
@ -244,23 +289,13 @@
var self = this, config = this.getConfig();
$.post(
config['urlAttach'],
{'ids': ids, 'ReplaceFileID': uploadedFileId},
{'ids': ids},
function(data, status, xhr) {
var fn = self.fileupload('option', 'downloadTemplate');
var container = self.find('.ss-uploadfield-files');
if (config['allowedMaxFileNumber'] == 1){
container.empty();
}
container.append(fn({
self.fileupload('attach', {
files: data,
formatFileSize: function (bytes) {
if (typeof bytes !== 'number') return '';
if (bytes >= 1000000000) return (bytes / 1000000000).toFixed(2) + ' GB';
if (bytes >= 1000000) return (bytes / 1000000).toFixed(2) + ' MB';
return (bytes / 1000).toFixed(2) + ' KB';
},
options: self.fileupload('option')
}));
options: self.fileupload('option'),
replaceFileID: uploadedFileId
});
}
);
}
@ -300,14 +335,18 @@
var fileupload = this.closest('div.ss-upload').data('fileupload'),
item = this.closest('.ss-uploadfield-item'), msg = '';
if(this.is('.ss-uploadfield-item-delete')) msg = ss.i18n._t('UploadField.ConfirmDelete');
if(!msg || confirm(msg)) {
fileupload._trigger('destroy', e, {
context: item,
url: this.data('href'),
type: 'get',
dataType: fileupload.options.dataType
});
if(this.is('.ss-uploadfield-item-delete')) {
if(confirm(ss.i18n._t('UploadField.ConfirmDelete'))) {
fileupload._trigger('destroy', e, {
context: item,
url: this.data('href'),
type: 'get',
dataType: fileupload.options.dataType
});
}
} else {
// Removed files will be applied to object on save
fileupload._trigger('destroy', e, {context: item});
}
return false;
@ -334,7 +373,7 @@
e.preventDefault(); // Avoid a form submit
}
});
$('div.ss-upload .ss-uploadfield-item-edit, div.ss-upload .ss-uploadfield-item-name').entwine({
$( 'div.ss-upload:not(.disabled):not(.readonly) .ss-uploadfield-item-edit').entwine({
onclick: function(e) {
var editform = this.closest('.ss-uploadfield-item').find('.ss-uploadfield-item-editform');
var disabled;

View File

@ -5,8 +5,12 @@ window.tmpl.cache['ss-uploadfield-downloadtemplate'] = tmpl(
'<img src="{%=file.thumbnail_url%}" alt="" />' +
'</span></div>' +
'<div class="ss-uploadfield-item-info">' +
'{% if (!file.error) { %}' +
'<input type="hidden" name="{%=file.fieldname%}[Files][]" value="{%=file.id%}" />' +
'{% } %}' +
'<label class="ss-uploadfield-item-name">' +
'<span class="name" title="{%=file.name%}">{%=file.name%}</span> ' +
'<span class="size">{%=o.formatFileSize(file.size)%}</span>' +
'{% if (!file.error) { %}' +
'<div class="ss-uploadfield-item-status ui-state-success-text" title="'+ss.i18n._t('UploadField.Uploaded', 'Uploaded')+'">'+ss.i18n._t('UploadField.Uploaded', 'Uploaded')+'</div>' +
'{% } else { %}' +
@ -16,7 +20,7 @@ window.tmpl.cache['ss-uploadfield-downloadtemplate'] = tmpl(
'</label>' +
'{% if (file.error) { %}' +
'<div class="ss-uploadfield-item-actions">' +
'<div class="ss-uploadfield-item-cancel ss-uploadfield-item-cancelfailed"><button class="icon icon-16">' + ss.i18n._t('UploadField.CANCEL', 'Cancel') + '</button></div>' +
'<div class="ss-uploadfield-item-cancel ss-uploadfield-item-cancelfailed delete"><button class="icon icon-16" data-icon="delete" title="' + ss.i18n._t('UploadField.CANCELREMOVE', 'Cancel/Remove') + '">' + ss.i18n._t('UploadField.CANCELREMOVE', 'Cancel/Remove') + '</button></div>' +
'</div>' +
'{% } else { %}' +
'<div class="ss-uploadfield-item-actions">{% print(file.buttons, true); %}</div>' +
@ -27,4 +31,4 @@ window.tmpl.cache['ss-uploadfield-downloadtemplate'] = tmpl(
'{% } %}' +
'</li>' +
'{% } %}'
);
);

View File

@ -20,11 +20,14 @@ window.tmpl.cache['ss-uploadfield-uploadtemplate'] = tmpl(
'<div class="ss-uploadfield-item-start start"><button class="icon icon-16" data-icon="navigation">' + ss.i18n._t('UploadField.START', 'Start') + '</button></div>' +
'{% } %}' +
'{% } %}' +
'<div class="ss-uploadfield-item-cancel cancel"><button data-icon="deleteLight" class="ss-uploadfield-item-cancel" title="' + ss.i18n._t('UploadField.CANCELREMOVE', 'Cancel/Remove') + '">' + ss.i18n._t('UploadField.CANCEL', 'Cancel') + '</button></div>' +
'<div class="ss-uploadfield-item-cancel cancel">' +
'<button class="icon icon-16" data-icon="minus-circle" title="' + ss.i18n._t('UploadField.CANCELREMOVE', 'Cancel/Remove') + '">' + ss.i18n._t('UploadField.CANCELREMOVE', 'Cancel/Remove') + '</button>' +
'</div>' +
'<div class="ss-uploadfield-item-overwrite hide ">'+
'<button data-icon="drive-upload" class="ss-uploadfield-item-overwrite-warning" title="' + ss.i18n._t('UploadField.OVERWRITE', 'Overwrite') + '">' + ss.i18n._t('UploadField.OVERWRITE', 'Overwrite') +
'<button data-icon="drive-upload" class="ss-uploadfield-item-overwrite-warning" title="' + ss.i18n._t('UploadField.OVERWRITE', 'Overwrite') + '">' + ss.i18n._t('UploadField.OVERWRITE', 'Overwrite') + '</button>' +
'</div>' +
'</div>' +
'</div>' +
'</li>' +
'{% } %}'
);
);

View File

@ -567,6 +567,7 @@ en:
HOTLINKINFO: 'Info: This image will be hotlinked. Please ensure you have permissions from the original site creator to do so.'
MAXNUMBEROFFILES: 'Max number of {count} file(s) exceeded.'
MAXNUMBEROFFILESSHORT: 'Can only upload {count} files'
MAXNUMBEROFFILESONE: 'Can only upload one file'
REMOVE: Remove
REMOVEERROR: 'Error removing file'
REMOVEINFO: 'Remove this file from here, but do not delete it from the file store'

View File

@ -566,6 +566,7 @@ en_GB:
HOTLINKINFO: 'Info: This image will be hotlinked. Please ensure you have permissions from the original site creator to do so.'
MAXNUMBEROFFILES: 'Max number of {count} file(s) exceeded.'
MAXNUMBEROFFILESSHORT: 'Can only upload {count} files'
MAXNUMBEROFFILESONE: 'Can only upload one file'
REMOVE: Remove
REMOVEERROR: 'Error removing file'
REMOVEINFO: 'Remove this file from here, but do not delete it from the file store'

View File

@ -56,39 +56,43 @@
}
}
.ss-uploadfield-item-info {
float: left;
margin-left: 15px;
margin-left: 95px;
.ss-uploadfield-item-name {
display: block;
line-height: 13px;
height: 26px;
margin: 0;
text-align: left;
b {
font-weight: bold;
padding: 0 5px 0 0;
}
text-align: left;
.name {
font-size: $font-base-size - 1;
color: lighten($color-text, 25%);
width:290px; //Ensures the title doesn't interfer with the status message
max-width: 240px;
font-weight: bold;
@include hide-text-overflow;
display:inline;
display:inline;
float:left;
}
.size {
color: lighten($color-text, 25%);
padding: 0 0 0 5px;
display:inline;
float:left;
}
.ss-uploadfield-item-status {
float: right;
padding: 0 0 0 5px;
width:100px; //Allocates the status message enough room to be useful. Will wrap if it is longer
text-align:right;
text-align:right;
max-width: 75%;
&.ui-state-error-text {
color: $color-button-destructive;
font-weight: bold;
width:150px; //Allocates the status message enough room to be useful. Will wrap if it is longer
}
&.ui-state-warning-text {
color: darken($color-warning, 10%);
}
&.ui-state-success-text {
color: $color-button-constructive;
}
@ -99,7 +103,7 @@
.ss-ui-button {
display: block;
float: left;
margin: 0 10px 0 0;
margin: 0 10px 6px 0;
&.ss-uploadfield-fromcomputer {
position: relative;
@ -124,8 +128,9 @@
}
}
.ss-uploadfield-item-actions {
height: 28px;
margin: 6px 0 0;
min-height: 28px;
overflow: hidden;
margin: 6px 0 -6px 0;
position: relative;
}
.ss-uploadfield-item-progress {
@ -171,7 +176,6 @@
height: 16px;
cursor: pointer;
@include single-box-shadow(none);
background: none;
position: relative;
// background: sprite($sprites16, cross-circle) no-repeat;

View File

@ -6,15 +6,12 @@
</span>
</button>
<% end_if %>
<% if UploadFieldHasRelation %>
<button data-href="$UploadFieldRemoveLink" class="ss-uploadfield-item-remove ss-ui-button ui-corner-all" title="<% _t('UploadField.REMOVEINFO', 'Remove this file from here, but do not delete it from the file store') %>" data-icon="plug-disconnect-prohibition">
<% _t('UploadField.REMOVE', 'Remove') %></button>
<% end_if %>
<button class="ss-uploadfield-item-remove ss-ui-button ui-corner-all" title="<% _t('UploadField.REMOVEINFO', 'Remove this file from here, but do not delete it from the file store') %>" data-icon="plug-disconnect-prohibition">
<% _t('UploadField.REMOVE', 'Remove') %></button>
<% if canDelete %>
<button data-href="$UploadFieldDeleteLink" class="ss-uploadfield-item-delete ss-ui-button ui-corner-all" title="<% _t('UploadField.DELETEINFO', 'Permanently delete this file from the file store') %>" data-icon="minus-circle"><% _t('UploadField.DELETE', 'Delete from files') %></button>
<% end_if %>
<% if UploadFieldHasRelation && UploadField.canAttachExisting %>
<% if UploadField.canAttachExisting %>
<button class="ss-uploadfield-item-choose-another ss-uploadfield-fromfiles ss-ui-button ui-corner-all" title="<% _t('UploadField.CHOOSEANOTHERINFO', 'Replace this file with another one from the file store') %>" data-icon="network-cloud">
<% _t('UploadField.CHOOSEANOTHERFILE', 'Choose another file') %></button>
<% end_if %>

View File

@ -1,19 +1,19 @@
<ul class="ss-uploadfield-files files">
<% if $Items %>
<% loop $Items %>
<% if $CustomisedItems %>
<% loop $CustomisedItems %>
<li class="ss-uploadfield-item template-download" data-fileid="$ID">
<div class="ss-uploadfield-item-preview preview"><span>
<img alt="$hasRelation" src="$UploadFieldThumbnailURL" />
</span></div>
<div class="ss-uploadfield-item-info">
<input type='hidden' value='$ID' name='{$Top.Name}[Files][]' />
<label class="ss-uploadfield-item-name">
<b>{$Title}.{$Extension}</b>
<span>$Size</span>
<span class="name">$Name.XML</span>
<span class="size">$Size</span>
<div class="clear"><!-- --></div>
</label>
<div class="ss-uploadfield-item-actions">
<% if Top.isDisabled || Top.isReadonly %>
<% else %>
<% if Top.isActive %>
$UploadFieldFileButtons
<% end_if %>
</div>
@ -25,15 +25,8 @@
<% end_loop %>
<% end_if %>
</ul>
<% if isDisabled || isReadonly %>
<% if isSaveable %>
<% else %>
<div class="ss-uploadfield-item">
<em><% _t('FileIFrameField.ATTACHONCESAVED2', 'Files can be attached once you have saved the record for the first time.') %></em>
</div>
<% end_if %>
<% else %>
<div class="ss-uploadfield-item ss-uploadfield-addfile<% if $Items && $displayInput %> borderTop<% end_if %>" <% if not $displayInput %>style="display: none;"<% end_if %>>
<% if canUpload || canAttachExisting %>
<div class="ss-uploadfield-item ss-uploadfield-addfile<% if $CustomisedItems %> borderTop<% end_if %>">
<% if canUpload %>
<div class="ss-uploadfield-item-preview ss-uploadfield-dropzone ui-corner-all">
<% if $multiple %>
@ -50,24 +43,24 @@
<% else %>
<b><% _t('UploadField.ATTACHFILE', 'Attach a file') %></b>
<% end_if %>
<% if getConfig('canPreviewFolder') %>
<% if canPreviewFolder %>
<small>(<%t UploadField.UPLOADSINTO 'saves into /{path}' path=$FolderName %>)</small>
<% end_if %>
</label>
<% if canUpload %>
<label class="ss-uploadfield-fromcomputer ss-ui-button ui-corner-all" title="<% _t('UploadField.FROMCOMPUTERINFO', 'Upload from your computer') %>" data-icon="drive-upload">
<% _t('UploadField.FROMCOMPUTER', 'From your computer') %>
<input id="$id" name="$getName" class="$extraClass ss-uploadfield-fromcomputer-fileinput" data-config="$configString" type="file"<% if $multiple %> multiple="multiple"<% end_if %> />
</label>
<% else %>
<input style="display: none" id="$id" name="$getName" class="$extraClass ss-uploadfield-fromcomputer-fileinput" data-config="$configString" type="file"<% if $multiple %> multiple="multiple"<% end_if %> />
<label class="ss-uploadfield-fromcomputer ss-ui-button ui-corner-all" title="<% _t('UploadField.FROMCOMPUTERINFO', 'Upload from your computer') %>" data-icon="drive-upload">
<% _t('UploadField.FROMCOMPUTER', 'From your computer') %>
<input id="$id" name="{$Name}[Uploads][]" class="$extraClass ss-uploadfield-fromcomputer-fileinput" data-config="$configString" type="file"<% if $multiple %> multiple="multiple"<% end_if %> />
</label>
<% end_if %>
<% if canAttachExisting %>
<button class="ss-uploadfield-fromfiles ss-ui-button ui-corner-all" title="<% _t('UploadField.FROMCOMPUTERINFO', 'Select from files') %>" data-icon="network-cloud"><% _t('UploadField.FROMFILES', 'From files') %></button>
<button class="ss-uploadfield-fromfiles ss-ui-button ui-corner-all" title="<% _t('UploadField.FROMCOMPUTERINFO', 'Select from files') %>" data-icon="network-cloud"><% _t('UploadField.FROMFILES', 'From files') %></button>
<% end_if %>
<% if not $autoUpload %>
<button class="ss-uploadfield-startall ss-ui-button ui-corner-all" data-icon="navigation"><% _t('UploadField.STARTALL', 'Start all') %></button>
<% if canUpload %>
<% if not $autoUpload %>
<button class="ss-uploadfield-startall ss-ui-button ui-corner-all" data-icon="navigation"><% _t('UploadField.STARTALL', 'Start all') %></button>
<% end_if %>
<% end_if %>
<div class="clear"><!-- --></div>
</div>

View File

@ -14,23 +14,25 @@ class UploadFieldTest extends FunctionalTest {
'File' => array('UploadFieldTest_FileExtension')
);
/**
* Test that files can be uploaded against an object with no relation
*/
public function testUploadNoRelation() {
$this->loginWithPermission('ADMIN');
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$tmpFileName = 'testUploadBasic.txt';
$_FILES = array('NoRelationField' => $this->getUploadFile($tmpFileName));
$response = $this->post(
'UploadFieldTest_Controller/Form/field/NoRelationField/upload',
array('NoRelationField' => $this->getUploadFile($tmpFileName))
);
$response = $this->mockFileUpload('NoRelationField', $tmpFileName);
$this->assertFalse($response->isError());
$this->assertFileExists(ASSETS_PATH . "/UploadFieldTest/$tmpFileName");
$uploadedFile = DataObject::get_one('File', sprintf('"Name" = \'%s\'', $tmpFileName));
$this->assertTrue(is_object($uploadedFile), 'The file object is created');
}
/**
* Test that an object can be uploaded against an object with a has_one relation
*/
public function testUploadHasOneRelation() {
$this->loginWithPermission('ADMIN');
@ -39,22 +41,29 @@ class UploadFieldTest extends FunctionalTest {
$record->HasOneFileID = null;
$record->write();
// Firstly, ensure the file can be uploaded
$tmpFileName = 'testUploadHasOneRelation.txt';
$_FILES = array('HasOneFile' => $this->getUploadFile($tmpFileName));
$response = $this->post(
'UploadFieldTest_Controller/Form/field/HasOneFile/upload',
array('HasOneFile' => $this->getUploadFile($tmpFileName))
);
$response = $this->mockFileUpload('HasOneFile', $tmpFileName);
$this->assertFalse($response->isError());
$this->assertFileExists(ASSETS_PATH . "/UploadFieldTest/$tmpFileName");
$uploadedFile = DataObject::get_one('File', sprintf('"Name" = \'%s\'', $tmpFileName));
$this->assertTrue(is_object($uploadedFile), 'The file object is created');
// Secondly, ensure that simply uploading an object does not save the file against the relation
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertFalse($record->HasOneFile()->exists());
// Thirdly, test submitting the form with the encoded data
$response = $this->mockUploadFileIDs('HasOneFile', array($uploadedFile->ID));
$this->assertEmpty($response['errors']);
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertTrue($record->HasOneFile()->exists());
$this->assertEquals($record->HasOneFile()->Name, $tmpFileName);
}
/**
* Tests that has_one relations work with subclasses of File
*/
public function testUploadHasOneRelationWithExtendedFile() {
$this->loginWithPermission('ADMIN');
@ -63,98 +72,123 @@ class UploadFieldTest extends FunctionalTest {
$record->HasOneExtendedFileID = null;
$record->write();
// Test that the file can be safely uploaded
$tmpFileName = 'testUploadHasOneRelationWithExtendedFile.txt';
$_FILES = array('HasOneExtendedFile' => $this->getUploadFile($tmpFileName));
$response = $this->post(
'UploadFieldTest_Controller/Form/field/HasOneExtendedFile/upload',
array('HasOneExtendedFile' => $this->getUploadFile($tmpFileName))
);
$response = $this->mockFileUpload('HasOneExtendedFile', $tmpFileName);
$this->assertFalse($response->isError());
$this->assertFileExists(ASSETS_PATH . "/UploadFieldTest/$tmpFileName");
$uploadedFile = DataObject::get_one('UploadFieldTest_ExtendedFile', sprintf('"Name" = \'%s\'', $tmpFileName));
$this->assertTrue(is_object($uploadedFile), 'The file object is created');
// Test that the record isn't written to automatically
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertTrue($record->HasOneExtendedFile()->exists(), 'The extended file is attached to the class');
$this->assertEquals($record->HasOneExtendedFile()->Name, $tmpFileName, 'Proper file has been attached');
$this->assertFalse($record->HasOneExtendedFile()->exists());
// Test that saving the form writes the record
$response = $this->mockUploadFileIDs('HasOneExtendedFile', array($uploadedFile->ID));
$this->assertEmpty($response['errors']);
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertTrue($record->HasOneExtendedFile()->exists());
$this->assertEquals($record->HasOneExtendedFile()->Name, $tmpFileName);
}
/**
* Test that has_many relations work with files
*/
public function testUploadHasManyRelation() {
$this->loginWithPermission('ADMIN');
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
// Test that uploaded files can be posted to a has_many relation
$tmpFileName = 'testUploadHasManyRelation.txt';
$_FILES = array('HasManyFiles' => $this->getUploadFile($tmpFileName));
$response = $this->post(
'UploadFieldTest_Controller/Form/field/HasManyFiles/upload',
array('HasManyFiles' => $this->getUploadFile($tmpFileName))
);
$response = $this->mockFileUpload('HasManyFiles', $tmpFileName);
$this->assertFalse($response->isError());
$this->assertFileExists(ASSETS_PATH . "/UploadFieldTest/$tmpFileName");
$uploadedFile = DataObject::get_one('File', sprintf('"Name" = \'%s\'', $tmpFileName));
$this->assertTrue(is_object($uploadedFile), 'The file object is created');
// Test that the record isn't written to automatically
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertEquals(3, $record->HasManyFiles()->Count());
$this->assertEquals($record->HasManyFiles()->Last()->Name, $tmpFileName);
$this->assertEquals(2, $record->HasManyFiles()->Count()); // Existing two files should be retained
// Test that saving the form writes the record
$ids = array_merge($record->HasManyFiles()->getIDList(), array($uploadedFile->ID));
$response = $this->mockUploadFileIDs('HasManyFiles', $ids);
$this->assertEmpty($response['errors']);
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertEquals(3, $record->HasManyFiles()->Count()); // New record should appear here now
}
/**
* Test that many_many relationships work with files
*/
public function testUploadManyManyRelation() {
$this->loginWithPermission('ADMIN');
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$relationCount = $record->ManyManyFiles()->Count();
// Test that uploaded files can be posted to a many_many relation
$tmpFileName = 'testUploadManyManyRelation.txt';
$_FILES = array('ManyManyFiles' => $this->getUploadFile($tmpFileName));
$response = $this->post(
'UploadFieldTest_Controller/Form/field/ManyManyFiles/upload',
array('ManyManyFiles' => $this->getUploadFile($tmpFileName))
);
$response = $this->mockFileUpload('ManyManyFiles', $tmpFileName);
$this->assertFalse($response->isError());
$this->assertFileExists(ASSETS_PATH . "/UploadFieldTest/$tmpFileName");
$uploadedFile = DataObject::get_one('File', sprintf('"Name" = \'%s\'', $tmpFileName));
$this->assertTrue(is_object($uploadedFile), 'The file object is created');
// Test that the record isn't written to automatically
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertEquals($relationCount+1, $record->ManyManyFiles()->Count());
$this->assertEquals($record->ManyManyFiles()->Last()->Name, $tmpFileName);
$this->assertEquals($relationCount, $record->ManyManyFiles()->Count()); // Existing file count should be retained
// Test that saving the form writes the record
$ids = array_merge($record->ManyManyFiles()->getIDList(), array($uploadedFile->ID));
$response = $this->mockUploadFileIDs('ManyManyFiles', $ids);
$this->assertEmpty($response['errors']);
$record = DataObject::get_by_id($record->class, $record->ID, false);
$record->flushCache();
$this->assertEquals($relationCount + 1, $record->ManyManyFiles()->Count()); // New record should appear here now
}
/**
* Test that has_one relations do not support multiple files
*/
public function testAllowedMaxFileNumberWithHasOne() {
$this->loginWithPermission('ADMIN');
// Get references for each file to upload
$file1 = $this->objFromFixture('File', 'file1');
$file2 = $this->objFromFixture('File', 'file2');
$fileIDs = array($file1->ID, $file2->ID);
// Test each of the three cases - has one with no max filel limit, has one with a limit of
// one, has one with a limit of more than one (makes no sense, but should test it anyway).
// Each of them should public function in the same way - attaching the first file should work, the
// second should cause an error.
foreach (array('HasOneFile', 'HasOneFileMaxOne', 'HasOneFileMaxTwo') as $recordName) {
// Unset existing has_one relation before re-uploading
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$record->{$recordName . 'ID'} = null;
$record->{"{$recordName}ID"} = null;
$record->write();
$tmpFileName = 'testUploadHasOneRelation.txt';
$_FILES = array($recordName => $this->getUploadFile($tmpFileName));
$response = $this->post(
"UploadFieldTest_Controller/Form/field/$recordName/upload",
array($recordName => $this->getUploadFile($tmpFileName))
);
$body = json_decode($response->getBody());
$this->assertEquals(0, $body[0]->error);
// Write to it again, should result in an error.
$response = $this->post(
"UploadFieldTest_Controller/Form/field/$recordName/upload",
array($recordName => $this->getUploadFile($tmpFileName))
);
$body = json_decode($response->getBody());
$this->assertNotEquals(0, $body[0]->error);
// Post form with two files for this field, should result in an error
$response = $this->mockUploadFileIDs($recordName, $fileIDs);
$isError = !empty($response['errors']);
// Strictly, a has_one should not allow two files, but this is overridden
// by the setAllowedMaxFileNumber(2) call
$maxFiles = ($recordName === 'HasOneFileMaxTwo') ? 2 : 1;
// Assert that the form fails if the maximum number of files is exceeded
$this->assertTrue((count($fileIDs) > $maxFiles) == $isError);
}
}
/**
* Test that max number of items on has_many is validated
*/
public function testAllowedMaxFileNumberWithHasMany() {
$this->loginWithPermission('ADMIN');
@ -164,100 +198,124 @@ class UploadFieldTest extends FunctionalTest {
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$record->HasManyFilesMaxTwo()->removeAll();
$tmpFileName = 'testUploadHasManyRelation.txt';
$_FILES = array('HasManyFilesMaxTwo' => $this->getUploadFile($tmpFileName));
// Get references for each file to upload
$file1 = $this->objFromFixture('File', 'file1');
$file2 = $this->objFromFixture('File', 'file2');
$file3 = $this->objFromFixture('File', 'file3');
// Write the first element, should be okay.
$response = $this->post(
'UploadFieldTest_Controller/Form/field/HasManyFilesMaxTwo/upload',
array('HasManyFilesMaxTwo' => $this->getUploadFile($tmpFileName))
);
$body = json_decode($response->getBody());
$this->assertEquals(0, $body[0]->error);
$response = $this->mockUploadFileIDs('HasManyFilesMaxTwo', array($file1->ID));
$this->assertEmpty($response['errors']);
// Write the second element, should be okay.
$response = $this->post(
'UploadFieldTest_Controller/Form/field/HasManyFilesMaxTwo/upload',
array('HasManyFilesMaxTwo' => $this->getUploadFile($tmpFileName))
);
$body = json_decode($response->getBody());
$this->assertEquals(0, $body[0]->error);
$response = $this->mockUploadFileIDs('HasManyFilesMaxTwo', array($file1->ID, $file2->ID));
$this->assertEmpty($response['errors']);
// Write the third element, should result in error.
$response = $this->post(
'UploadFieldTest_Controller/Form/field/HasManyFilesMaxTwo/upload',
array('HasManyFilesMaxTwo' => $this->getUploadFile($tmpFileName))
);
$body = json_decode($response->getBody());
$this->assertNotEquals(0, $body[0]->error);
$response = $this->mockUploadFileIDs('HasManyFilesMaxTwo', array($file1->ID, $file2->ID, $file3->ID));
$this->assertNotEmpty($response['errors']);
}
/**
* Test that files can be removed from has_one relations
*/
public function testRemoveFromHasOne() {
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$file1 = $this->objFromFixture('File', 'file1');
// Check record exists
$this->assertTrue($record->HasOneFile()->exists());
$response = $this->post(
'UploadFieldTest_Controller/Form/field/HasOneFile/item/' . $file1->ID . '/remove',
array()
);
$this->assertFalse($response->isError());
// Remove from record
$response = $this->mockUploadFileIDs('HasOneFile', array());
$this->assertEmpty($response['errors']);
// Check file is removed
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertFalse($record->HasOneFile()->exists());
// Check file object itself exists
$this->assertFileExists($file1->FullPath, 'File is only detached, not deleted from filesystem');
}
/**
* Test that items can be removed from has_many
*/
public function testRemoveFromHasMany() {
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$file2 = $this->objFromFixture('File', 'file2');
$file3 = $this->objFromFixture('File', 'file3');
// Check record has two files attached
$this->assertEquals(array('File2', 'File3'), $record->HasManyFiles()->column('Title'));
$response = $this->post(
'UploadFieldTest_Controller/Form/field/HasManyFiles/item/' . $file2->ID . '/remove',
array()
);
$this->assertFalse($response->isError());
// Remove file 2
$response = $this->mockUploadFileIDs('HasManyFiles', array($file3->ID));
$this->assertEmpty($response['errors']);
// check only file 3 is left
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertEquals(array('File3'), $record->HasManyFiles()->column('Title'));
// Check file 2 object itself exists
$this->assertFileExists($file3->FullPath, 'File is only detached, not deleted from filesystem');
}
/**
* Test that items can be removed from many_many
*/
public function testRemoveFromManyMany() {
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$file4 = $this->objFromFixture('File', 'file4');
$file5 = $this->objFromFixture('File', 'file5');
// Check that both files are currently set
$this->assertContains('File4', $record->ManyManyFiles()->column('Title'));
$this->assertContains('File5', $record->ManyManyFiles()->column('Title'));
$response = $this->post(
'UploadFieldTest_Controller/Form/field/ManyManyFiles/item/' . $file4->ID . '/remove',
array()
);
$this->assertFalse($response->isError());
// Remove file 4
$response = $this->mockUploadFileIDs('ManyManyFiles', array($file5->ID));
$this->assertEmpty($response['errors']);
// check only file 5 is left
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertNotContains('File4', $record->ManyManyFiles()->column('Title'));
$this->assertContains('File5', $record->ManyManyFiles()->column('Title'));
// check file 4 object exists
$this->assertFileExists($file4->FullPath, 'File is only detached, not deleted from filesystem');
}
/**
* Test that files can be deleted from has_one and the filesystem
*/
public function testDeleteFromHasOne() {
$this->loginWithPermission('ADMIN');
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$file1 = $this->objFromFixture('File', 'file1');
// Check that file initially exists
$this->assertTrue($record->HasOneFile()->exists());
$response = $this->post(
'UploadFieldTest_Controller/Form/field/HasOneFile/item/' . $file1->ID . '/delete',
array()
);
$this->assertFileExists($file1->FullPath);
// Delete physical file and update record
$response = $this->mockFileDelete('HasOneFile', $file1->ID);
$this->assertFalse($response->isError());
$response = $this->mockUploadFileIDs('HasOneFile', array());
$this->assertEmpty($response['errors']);
// Check that file is not set against record
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertFalse($record->HasOneFile()->exists());
// Check that the physical file is deleted
$this->assertFileNotExists($file1->FullPath, 'File is also removed from filesystem');
}
/**
* Test that files can be deleted from has_many and the filesystem
*/
public function testDeleteFromHasMany() {
$this->loginWithPermission('ADMIN');
@ -265,25 +323,28 @@ class UploadFieldTest extends FunctionalTest {
$file2 = $this->objFromFixture('File', 'file2');
$file3 = $this->objFromFixture('File', 'file3');
// Check that files initially exists
$this->assertEquals(array('File2', 'File3'), $record->HasManyFiles()->column('Title'));
$response = $this->post(
'UploadFieldTest_Controller/Form/field/HasManyFiles/item/' . $file2->ID . '/delete',
array()
);
$this->assertFileExists($file2->FullPath);
$this->assertFileExists($file3->FullPath);
// Delete physical file and update record without file 2
$response = $this->mockFileDelete('HasManyFiles', $file2->ID);
$this->assertFalse($response->isError());
$response = $this->mockUploadFileIDs('HasManyFiles', array($file3->ID));
$this->assertEmpty($response['errors']);
// Test that file is removed from record
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertEquals(array('File3'), $record->HasManyFiles()->column('Title'));
// Test that physical file is removed
$this->assertFileNotExists($file2->FullPath, 'File is also removed from filesystem');
$fileNotOnRelationship = $this->objFromFixture('File', 'file1');
$response = $this->post(
'UploadFieldTest_Controller/Form/field/HasManyFiles/item/' . $fileNotOnRelationship->ID . '/delete',
array()
);
$this->assertEquals(403, $response->getStatusCode(),
"Denies deleting files if they're not on the current relationship");
}
/**
* Test that files can be deleted from many_many and the filesystem
*/
public function testDeleteFromManyMany() {
$this->loginWithPermission('ADMIN');
@ -292,26 +353,35 @@ class UploadFieldTest extends FunctionalTest {
$file5 = $this->objFromFixture('File', 'file5');
$fileNoDelete = $this->objFromFixture('File', 'file-nodelete');
$this->assertContains('File4', $record->ManyManyFiles()->column('Title'));
$this->assertContains('File5', $record->ManyManyFiles()->column('Title'));
$response = $this->post(
'UploadFieldTest_Controller/Form/field/ManyManyFiles/item/' . $file4->ID . '/delete',
array()
);
// Test that files initially exist
$setFiles = $record->ManyManyFiles()->column('Title');
$this->assertContains('File4', $setFiles);
$this->assertContains('File5', $setFiles);
$this->assertContains('nodelete.txt', $setFiles);
$this->assertFileExists($file4->FullPath);
$this->assertFileExists($file5->FullPath);
$this->assertFileExists($fileNoDelete->FullPath);
// Delete physical file and update record without file 4
$response = $this->mockFileDelete('ManyManyFiles', $file4->ID);
$this->assertFalse($response->isError());
// Check file is removed from record
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertNotContains('File4', $record->ManyManyFiles()->column('Title'));
$this->assertContains('File5', $record->ManyManyFiles()->column('Title'));
// Check physical file is removed from filesystem
$this->assertFileNotExists($file4->FullPath, 'File is also removed from filesystem');
// Test record-based permissions
$response = $this->post(
'UploadFieldTest_Controller/Form/field/ManyManyFiles/item/' . $fileNoDelete->ID . '/delete',
array()
);
$response = $this->mockFileDelete('ManyManyFiles/', $fileNoDelete->ID);
$this->assertEquals(403, $response->getStatusCode());
}
/**
* Test control output html
*/
public function testView() {
$this->loginWithPermission('ADMIN');
@ -326,7 +396,7 @@ class UploadFieldTest extends FunctionalTest {
$this->assertFalse($response->isError());
$parser = new CSSContentParser($response->getBody());
$items = $parser->getBySelector('#ManyManyFiles .ss-uploadfield-files .ss-uploadfield-item');
$items = $parser->getBySelector('#HasManyNoViewFiles .ss-uploadfield-files .ss-uploadfield-item');
$ids = array();
foreach($items as $item) $ids[] = (int)$item['data-fileid'];
@ -368,16 +438,16 @@ class UploadFieldTest extends FunctionalTest {
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$form = $this->getMockForm();
$field = new UploadField('MyField');
$field = UploadField::create('MyField');
$field->setForm($form);
$this->assertNull($field->getRecord(), 'Returns no record by default');
$field = new UploadField('MyField');
$field = UploadField::create('MyField');
$field->setForm($form);
$form->loadDataFrom($record);
$this->assertEquals($record, $field->getRecord(), 'Returns record from form if available');
$field = new UploadField('MyField');
$field = UploadField::create('MyField');
$field->setForm($form);
$field->setRecord($record);
$this->assertEquals($record, $field->getRecord(), 'Returns record when set explicitly');
@ -385,22 +455,24 @@ class UploadFieldTest extends FunctionalTest {
public function testSetItems() {
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$form = $this->getMockForm();
$items = new ArrayList(array(
$this->objFromFixture('File', 'file1'),
$this->objFromFixture('File', 'file2')
));
// Field with no record attached
$field = UploadField::create('DummyField');
$field->setItems($items);
$this->assertEquals(array('File1', 'File2'), $field->getItems()->column('Title'));
// Anonymous field
$field = new UploadField('MyField');
$field->setForm($form);
$field = UploadField::create('MyField');
$field->setRecord($record);
$field->setItems($items);
$this->assertEquals(array('File1', 'File2'), $field->getItems()->column('Title'));
// Field with has_one auto-detected
$field = new UploadField('HasOneFile');
$field->setForm($form);
$field = UploadField::create('HasOneFile');
$field->setRecord($record);
$field->setItems($items);
$this->assertEquals(array('File1', 'File2'), $field->getItems()->column('Title'),
@ -410,30 +482,25 @@ class UploadFieldTest extends FunctionalTest {
public function testGetItems() {
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$form = $this->getMockForm();
// Anonymous field
$field = new UploadField('MyField');
$field->setForm($form);
$field->setRecord($record);
$field = UploadField::create('MyField');
$field->setValue(null, $record);
$this->assertEquals(array(), $field->getItems()->column('Title'));
// Field with has_one auto-detected
$field = new UploadField('HasOneFile');
$field->setForm($form);
$field->setRecord($record);
$field = UploadField::create('HasOneFile');
$field->setValue(null, $record);
$this->assertEquals(array('File1'), $field->getItems()->column('Title'));
// Field with has_many auto-detected
$field = new UploadField('HasManyFiles');
$field->setForm($form);
$field->setRecord($record);
$field = UploadField::create('HasManyFiles');
$field->setValue(null, $record);
$this->assertEquals(array('File2', 'File3'), $field->getItems()->column('Title'));
// Field with many_many auto-detected
$field = new UploadField('ManyManyFiles');
$field->setForm($form);
$field->setRecord($record);
$field = UploadField::create('ManyManyFiles');
$field->setValue(null, $record);
$this->assertNotContains('File1',$field->getItems()->column('Title'));
$this->assertNotContains('File2',$field->getItems()->column('Title'));
$this->assertNotContains('File3',$field->getItems()->column('Title'));
@ -448,14 +515,17 @@ class UploadFieldTest extends FunctionalTest {
$this->assertFalse($response->isError());
$parser = new CSSContentParser($response->getBody());
$this->assertFalse((bool)$parser->getBySelector(
'#ReadonlyField .ss-uploadfield-files .ss-uploadfield-item .ss-ui-button'),
$this->assertFalse(
(bool)$parser->getBySelector('#ReadonlyField .ss-uploadfield-files .ss-uploadfield-item .ss-ui-button'),
'Removes all buttons on items');
$this->assertFalse((bool)$parser->getBySelector('#ReadonlyField .ss-uploadfield-dropzone'),
'Removes dropzone');
$this->assertFalse((bool)$parser->getBySelector(
'#ReadonlyField .ss-uploadfield-addfile .ss-ui-button'),
'Removes all buttons from "add" area');
$this->assertFalse(
(bool)$parser->getBySelector('#ReadonlyField .ss-uploadfield-dropzone'),
'Removes dropzone'
);
$this->assertFalse((bool)$parser->getBySelector('#ReadonlyField .ss-uploadfield-addfile'),
'Entire "add" area'
);
}
public function testDisabled() {
@ -471,9 +541,9 @@ class UploadFieldTest extends FunctionalTest {
$this->assertFalse((bool)$parser->getBySelector('#DisabledField .ss-uploadfield-dropzone'),
'Removes dropzone');
$this->assertFalse(
(bool)$parser->getBySelector('#DisabledField .ss-uploadfield-addfile .ss-ui-button'),
'Removes all buttons from "add" area');
(bool)$parser->getBySelector('#DisabledField .ss-uploadfield-addfile'),
'Entire "add" area'
);
}
public function testCanUpload() {
@ -491,20 +561,20 @@ class UploadFieldTest extends FunctionalTest {
}
public function testCanUploadWithPermissionCode() {
$field = new UploadField('MyField');
$field = UploadField::create('MyField');
$field->setConfig('canUpload', true);
$field->setCanUpload(true);
$this->assertTrue($field->canUpload());
$field->setConfig('canUpload', false);
$field->setCanUpload(false);
$this->assertFalse($field->canUpload());
$this->loginWithPermission('ADMIN');
$field->setConfig('canUpload', false);
$field->setCanUpload(false);
$this->assertFalse($field->canUpload());
$field->setConfig('canUpload', 'ADMIN');
$field->setCanUpload('ADMIN');
$this->assertTrue($field->canUpload());
}
@ -522,28 +592,6 @@ class UploadFieldTest extends FunctionalTest {
(bool)$parser->getBySelector('#CanAttachExistingFalseField .ss-uploadfield-fromfiles'),
'Removes "From files" button'
);
}
public function testIsSaveable() {
$form = $this->getMockForm();
$field = new UploadField('MyField');
$this->assertTrue($field->isSaveable(), 'Field without relation is always marked as saveable');
$field = new UploadField('HasOneFile');
$this->assertTrue($field->isSaveable(), 'Field with has_one relation is saveable without record on form');
$field = new UploadField('HasOneFile');
$newRecord = new UploadFieldTest_Record();
$form->loadDataFrom($newRecord);
$field->setForm($form);
$this->assertFalse($field->isSaveable(), 'Field with has_one relation not saveable with new record on form');
$field = new UploadField('HasOneFile');
$existingRecord = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$form->loadDataFrom($existingRecord);
$field->setForm($form);
$this->assertTrue($field->isSaveable(), 'Field with has_one relation saveable with saved record on form');
}
public function testSelect() {
@ -566,93 +614,6 @@ class UploadFieldTest extends FunctionalTest {
$this->assertNotContains($fileSubfolder->ID, $itemIDs, 'Does not contain file in subfolder');
}
public function testAttachHasOne() {
$this->loginWithPermission('ADMIN');
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$file1 = $this->objFromFixture('File', 'file1');
$file2 = $this->objFromFixture('File', 'file2');
$file3AlreadyAttached = $this->objFromFixture('File', 'file3');
$response = $this->post(
'UploadFieldTest_Controller/Form/field/HasOneFile/attach',
array('ids' => array($file1->ID/* first file should be ignored */, $file2->ID))
);
$this->assertFalse($response->isError());
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertEquals($file2->ID, $record->HasOneFileID, 'Attaches new relations');
}
public function testAttachHasMany() {
$this->loginWithPermission('ADMIN');
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$file1 = $this->objFromFixture('File', 'file1');
$file2 = $this->objFromFixture('File', 'file2');
$file3AlreadyAttached = $this->objFromFixture('File', 'file3');
$response = $this->post(
'UploadFieldTest_Controller/Form/field/HasManyFiles/attach',
array('ids' => array($file1->ID, $file2->ID))
);
$this->assertFalse($response->isError());
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertContains($file1->ID, $record->HasManyFiles()->column('ID'),
'Attaches new relations');
$this->assertContains($file2->ID, $record->HasManyFiles()->column('ID'),
'Attaches new relations');
$this->assertContains($file3AlreadyAttached->ID, $record->HasManyFiles()->column('ID'),
'Does not detach existing relations');
}
public function testAttachManyMany() {
$this->loginWithPermission('ADMIN');
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$file1 = $this->objFromFixture('File', 'file1');
$file2 = $this->objFromFixture('File', 'file2');
$file5AlreadyAttached = $this->objFromFixture('File', 'file5');
$response = $this->post(
'UploadFieldTest_Controller/Form/field/ManyManyFiles/attach',
array('ids' => array($file1->ID, $file2->ID))
);
$this->assertFalse($response->isError());
$record = DataObject::get_by_id($record->class, $record->ID, false);
$this->assertContains($file1->ID, $record->ManyManyFiles()->column('ID'),
'Attaches new relations');
$this->assertContains($file2->ID, $record->ManyManyFiles()->column('ID'),
'Attaches new relations');
$this->assertContains($file5AlreadyAttached->ID, $record->ManyManyFiles()->column('ID'),
'Does not detach existing relations');
}
public function testManagesRelation() {
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
$field = new UploadField('ManyManyFiles');
$this->assertFalse($field->managesRelation(), 'False if no record is set');
$field = new UploadField('NoRelationField');
$field->setRecord($record);
$this->assertFalse($field->managesRelation(), 'False if no relation found by name');
$field = new UploadField('HasOneFile');
$field->setRecord($record);
$this->assertTrue($field->managesRelation(), 'True for has_one');
$field = new UploadField('HasManyFiles');
$field->setRecord($record);
$this->assertTrue($field->managesRelation(), 'True for has_many');
$field = new UploadField('ManyManyFiles');
$field->setRecord($record);
$this->assertTrue($field->managesRelation(), 'True for many_many');
}
protected function getMockForm() {
return new Form(new Controller(), 'Form', new FieldList(), new FieldList());
}
@ -668,12 +629,76 @@ class UploadFieldTest extends FunctionalTest {
// emulates the $_FILES array
return array(
'name' => $tmpFileName,
'type' => 'text/plaintext',
'size' => filesize($tmpFilePath),
'tmp_name' => $tmpFilePath,
'extension' => 'txt',
'error' => UPLOAD_ERR_OK,
'name' => array('Uploads' => array($tmpFileName)),
'type' => array('Uploads' => array('text/plaintext')),
'size' => array('Uploads' => array(filesize($tmpFilePath))),
'tmp_name' => array('Uploads' => array($tmpFilePath)),
'error' => array('Uploads' => array(UPLOAD_ERR_OK)),
);
}
/**
* Simulates a form post to the test controller with the specified file IDs
*
* @param string $fileField Name of field to assign ids to
* @param array $ids list of file IDs
* @return boolean Array with key 'errors'
*/
protected function mockUploadFileIDs($fileField, $ids) {
// collate file ids
$files = array();
foreach($ids as $id) {
$files[$id] = $id;
}
$data = array(
'action_submit' => 1
);
if($files) {
// Normal post requests can't submit empty array values for fields
$data[$fileField] = array('Files' => $files);
}
$form = new UploadFieldTestForm();
$form->loadDataFrom($data, true);
if($form->validate()) {
$record = $form->getRecord();
$form->saveInto($record);
$record->write();
return array('errors' => null);
} else {
return array('errors' => $form->getValidator()->getErrors());
}
}
/**
* Simulates a file upload
*
* @param string $fileField Name of the field to mock upload for
* @param array $tmpFileName Name of temporary file to upload
* @return SS_HTTPResponse form response
*/
protected function mockFileUpload($fileField, $tmpFileName) {
$upload = $this->getUploadFile($tmpFileName);
$_FILES = array($fileField => $upload);
return $this->post(
"UploadFieldTest_Controller/Form/field/{$fileField}/upload",
array($fileField => $upload)
);
}
/**
* Simulates a physical file deletion
*
* @param string $fileField Name of the field
* @param integer $fileID ID of the file to delete
* @return SS_HTTPResponse form response
*/
protected function mockFileDelete($fileField, $fileID) {
return $this->post(
"UploadFieldTest_Controller/Form/field/HasOneFile/item/{$fileID}/delete",
array()
);
}
@ -738,12 +763,14 @@ class UploadFieldTest_Record extends DataObject implements TestOnly {
);
private static $has_many = array(
'HasManyFiles' => 'File',
'HasManyFilesMaxTwo' => 'File',
'HasManyFiles' => 'File.HasManyRecord',
'HasManyFilesMaxTwo' => 'File.HasManyMaxTwoRecord',
'HasManyNoViewFiles' => 'File.HasManyNoViewRecord',
'ReadonlyField' => 'File.ReadonlyRecord'
);
private static $many_many = array(
'ManyManyFiles' => 'File',
'ManyManyFiles' => 'File'
);
}
@ -751,7 +778,20 @@ class UploadFieldTest_Record extends DataObject implements TestOnly {
class UploadFieldTest_FileExtension extends DataExtension implements TestOnly {
private static $has_one = array(
'Record' => 'UploadFieldTest_Record'
'HasManyRecord' => 'UploadFieldTest_Record',
'HasManyMaxTwoRecord' => 'UploadFieldTest_Record',
'HasManyNoViewRecord' => 'UploadFieldTest_Record',
'ReadonlyRecord' => 'UploadFieldTest_Record'
);
private static $has_many = array(
'HasOneRecords' => 'UploadFieldTest_Record.HasOneFile',
'HasOneMaxOneRecords' => 'UploadFieldTest_Record.HasOneFileMaxOne',
'HasOneMaxTwoRecords' => 'UploadFieldTest_Record.HasOneFileMaxTwo',
);
private static $belongs_many_many = array(
'ManyManyRecords' => 'UploadFieldTest_Record'
);
public function canDelete($member = null) {
@ -767,119 +807,119 @@ class UploadFieldTest_FileExtension extends DataExtension implements TestOnly {
}
}
class UploadFieldTest_Controller extends Controller implements TestOnly {
protected $template = 'BlankPage';
public function Form() {
$record = DataObject::get_one('UploadFieldTest_Record', '"Title" = \'Record 1\'');
$fieldNoRelation = new UploadField('NoRelationField');
$fieldNoRelation->setFolderName('UploadFieldTest');
$fieldNoRelation->setRecord($record);
$fieldHasOne = new UploadField('HasOneFile');
$fieldHasOne->setFolderName('UploadFieldTest');
$fieldHasOne->setRecord($record);
$fieldHasOneExtendedFile = new UploadField('HasOneExtendedFile');
$fieldHasOneExtendedFile->setFolderName('UploadFieldTest');
$fieldHasOneExtendedFile->setRecord($record);
$fieldHasOneMaxOne = new UploadField('HasOneFileMaxOne');
$fieldHasOneMaxOne->setFolderName('UploadFieldTest');
$fieldHasOneMaxOne->setConfig('allowedMaxFileNumber', 1);
$fieldHasOneMaxOne->setRecord($record);
$fieldHasOneMaxTwo = new UploadField('HasOneFileMaxTwo');
$fieldHasOneMaxTwo->setFolderName('UploadFieldTest');
$fieldHasOneMaxTwo->setConfig('allowedMaxFileNumber', 2);
$fieldHasOneMaxTwo->setRecord($record);
$fieldHasMany = new UploadField('HasManyFiles');
$fieldHasMany->setFolderName('UploadFieldTest');
$fieldHasMany->setRecord($record);
$fieldHasManyMaxTwo = new UploadField('HasManyFilesMaxTwo');
$fieldHasManyMaxTwo->setFolderName('UploadFieldTest');
$fieldHasManyMaxTwo->setConfig('allowedMaxFileNumber', 2);
$fieldHasManyMaxTwo->setRecord($record);
$fieldManyMany = new UploadField('ManyManyFiles');
$fieldManyMany->setFolderName('UploadFieldTest');
$fieldManyMany->setRecord($record);
$fieldReadonly = new UploadField('ReadonlyField');
$fieldReadonly->setFolderName('UploadFieldTest');
$fieldReadonly->setRecord($record);
$fieldReadonly = $fieldReadonly->performReadonlyTransformation();
$fieldDisabled = new UploadField('DisabledField');
$fieldDisabled->setFolderName('UploadFieldTest');
$fieldDisabled->setRecord($record);
$fieldDisabled = $fieldDisabled->performDisabledTransformation();
$fieldSubfolder = new UploadField('SubfolderField');
$fieldSubfolder->setFolderName('UploadFieldTest/subfolder1');
$fieldSubfolder->setRecord($record);
$fieldCanUploadFalse = new UploadField('CanUploadFalseField');
$fieldCanUploadFalse->setConfig('canUpload', false);
$fieldCanUploadFalse->setRecord($record);
$fieldCanAttachExisting = new UploadField('CanAttachExistingFalseField');
$fieldCanAttachExisting->setConfig('canAttachExisting', false);
$fieldCanAttachExisting->setRecord($record);
$form = new Form(
$this,
'Form',
new FieldList(
$fieldNoRelation,
$fieldHasOne,
$fieldHasOneMaxOne,
$fieldHasOneMaxTwo,
$fieldHasOneExtendedFile,
$fieldHasMany,
$fieldHasManyMaxTwo,
$fieldManyMany,
$fieldReadonly,
$fieldDisabled,
$fieldSubfolder,
$fieldCanUploadFalse,
$fieldCanAttachExisting
),
new FieldList(
new FormAction('submit')
),
new RequiredFields(
'NoRelationField',
'HasOneFile',
'HasOneFileMaxOne',
'HasOneFileMaxTwo',
'HasOneExtendedFile',
'HasManyFiles',
'HasManyFilesMaxTwo',
'ManyManyFiles',
'ReadonlyField',
'DisabledField',
'SubfolderField',
'CanUploadFalseField',
'CanAttachExistingField'
)
);
return $form;
}
public function submit($data, $form) {
}
}
/**
* Used for testing the create-on-upload
*/
class UploadFieldTest_ExtendedFile extends File implements TestOnly {
private static $has_many = array(
'HasOneExtendedRecords' => 'UploadFieldTest_Record.HasOneExtendedFile'
);
}
class UploadFieldTestForm extends Form implements TestOnly {
public function getRecord() {
if(empty($this->record)) {
$this->record = DataObject::get_one('UploadFieldTest_Record', '"Title" = \'Record 1\'');
}
return $this->record;
}
function __construct($controller = null, $name = 'Form') {
if(empty($controller)) {
$controller = new UploadFieldTest_Controller();
}
$fieldNoRelation = UploadField::create('NoRelationField')
->setFolderName('UploadFieldTest');
$fieldHasOne = UploadField::create('HasOneFile')
->setFolderName('UploadFieldTest');
$fieldHasOneExtendedFile = UploadField::create('HasOneExtendedFile')
->setFolderName('UploadFieldTest');
$fieldHasOneMaxOne = UploadField::create('HasOneFileMaxOne')
->setFolderName('UploadFieldTest')
->setAllowedMaxFileNumber(1);
$fieldHasOneMaxTwo = UploadField::create('HasOneFileMaxTwo')
->setFolderName('UploadFieldTest')
->setAllowedMaxFileNumber(2);
$fieldHasMany = UploadField::create('HasManyFiles')
->setFolderName('UploadFieldTest');
$fieldHasManyMaxTwo = UploadField::create('HasManyFilesMaxTwo')
->setFolderName('UploadFieldTest')
->setAllowedMaxFileNumber(2);
$fieldManyMany = UploadField::create('ManyManyFiles')
->setFolderName('UploadFieldTest');
$fieldHasManyNoView = UploadField::create('HasManyNoViewFiles')
->setFolderName('UploadFieldTest');
$fieldReadonly = UploadField::create('ReadonlyField')
->setFolderName('UploadFieldTest')
->performReadonlyTransformation();
$fieldDisabled = UploadField::create('DisabledField')
->setFolderName('UploadFieldTest')
->performDisabledTransformation();
$fieldSubfolder = UploadField::create('SubfolderField')
->setFolderName('UploadFieldTest/subfolder1');
$fieldCanUploadFalse = UploadField::create('CanUploadFalseField')
->setCanUpload(false);
$fieldCanAttachExisting = UploadField::create('CanAttachExistingFalseField')
->setCanAttachExisting(false);
$fields = new FieldList(
$fieldNoRelation,
$fieldHasOne,
$fieldHasOneMaxOne,
$fieldHasOneMaxTwo,
$fieldHasOneExtendedFile,
$fieldHasMany,
$fieldHasManyMaxTwo,
$fieldManyMany,
$fieldHasManyNoView,
$fieldReadonly,
$fieldDisabled,
$fieldSubfolder,
$fieldCanUploadFalse,
$fieldCanAttachExisting
);
$actions = new FieldList(
new FormAction('submit')
);
$validator = new RequiredFields();
parent::__construct($controller, $name, $fields, $actions, $validator);
$this->loadDataFrom($this->getRecord());
}
public function submit($data, Form $form) {
$record = $this->getRecord();
$form->saveInto($record);
$record->write();
return json_encode($record->toMap());
}
}
class UploadFieldTest_Controller extends Controller implements TestOnly {
protected $template = 'BlankPage';
private static $allowed_actions = array('Form', 'index', 'submit');
public function Form() {
return new UploadFieldTestForm($this, 'Form');
}
}

View File

@ -1,53 +1,55 @@
Folder:
folder1:
Name: UploadFieldTest
folder1-subfolder1:
Name: subfolder1
ParentID: =>Folder.folder1
folder1:
Name: UploadFieldTest
folder1-subfolder1:
Name: subfolder1
ParentID: =>Folder.folder1
File:
file1:
Title: File1
Filename: assets/UploadFieldTest/file1.txt
ParentID: =>Folder.folder1
file2:
Title: File2
Filename: assets/UploadFieldTest/file2.txt
ParentID: =>Folder.folder1
file3:
Title: File3
Filename: assets/UploadFieldTest/file3.txt
ParentID: =>Folder.folder1
file4:
Title: File4
Filename: assets/UploadFieldTest/file4.txt
ParentID: =>Folder.folder1
file5:
Title: File5
Filename: assets/UploadFieldTest/file5.txt
ParentID: =>Folder.folder1
file-noview:
Title: noview.txt
Name: noview.txt
Filename: assets/UploadFieldTest/noview.txt
ParentID: =>Folder.folder1
file-noedit:
Title: noedit.txt
Name: noedit.txt
Filename: assets/UploadFieldTest/noedit.txt
ParentID: =>Folder.folder1
file-nodelete:
Title: nodelete.txt
Name: nodelete.txt
Filename: assets/UploadFieldTest/nodelete.txt
ParentID: =>Folder.folder1
file-subfolder:
Title: file-subfolder.txt
Name: file-subfolder.txt
Filename: assets/UploadFieldTest/subfolder1/file-subfolder.txt
ParentID: =>Folder.folder1-subfolder1
file1:
Title: File1
Filename: assets/UploadFieldTest/file1.txt
ParentID: =>Folder.folder1
file2:
Title: File2
Filename: assets/UploadFieldTest/file2.txt
ParentID: =>Folder.folder1
file3:
Title: File3
Filename: assets/UploadFieldTest/file3.txt
ParentID: =>Folder.folder1
file4:
Title: File4
Filename: assets/UploadFieldTest/file4.txt
ParentID: =>Folder.folder1
file5:
Title: File5
Filename: assets/UploadFieldTest/file5.txt
ParentID: =>Folder.folder1
file-noview:
Title: noview.txt
Name: noview.txt
Filename: assets/UploadFieldTest/noview.txt
ParentID: =>Folder.folder1
file-noedit:
Title: noedit.txt
Name: noedit.txt
Filename: assets/UploadFieldTest/noedit.txt
ParentID: =>Folder.folder1
file-nodelete:
Title: nodelete.txt
Name: nodelete.txt
Filename: assets/UploadFieldTest/nodelete.txt
ParentID: =>Folder.folder1
file-subfolder:
Title: file-subfolder.txt
Name: file-subfolder.txt
Filename: assets/UploadFieldTest/subfolder1/file-subfolder.txt
ParentID: =>Folder.folder1-subfolder1
UploadFieldTest_Record:
record1:
Title: Record 1
HasOneFileID: =>File.file1
HasManyFiles: =>File.file2,=>File.file3
ManyManyFiles: =>File.file4,=>File.file5,=>File.file-noview,=>File.file-noedit,=>File.file-nodelete
record1:
Title: Record 1
HasOneFileID: =>File.file1
HasManyFiles: =>File.file2,=>File.file3
ManyManyFiles: =>File.file4,=>File.file5,=>File.file-noedit,=>File.file-nodelete
HasManyNoViewFiles: =>File.file4,=>File.file5,=>File.file-noedit,=>File.file-nodelete,=>File.file-noview
ReadonlyField: =>File.file4