mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API UploadField functions on new records
Fixed regression from1e5d40474d
(UploadField::canPreviewFolder). Merged in pull request #2009 - (6018bdd631
). Merged pull request #1259 (34bfc862ee
).
This commit is contained in:
parent
2b9a44ef67
commit
7f057ce343
@ -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)
|
||||
|
@ -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; }
|
||||
|
@ -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; }
|
||||
|
@ -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`
|
||||
|
@ -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');
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>' +
|
||||
'{% } %}'
|
||||
);
|
||||
);
|
||||
|
@ -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>' +
|
||||
'{% } %}'
|
||||
);
|
||||
);
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 %>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user