mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Implement AssetField to edit DBFile fields
This commit is contained in:
parent
1ec5f0e0a1
commit
d1ea74e40d
@ -1,14 +0,0 @@
|
|||||||
name: uploadfield
|
|
||||||
---
|
|
||||||
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)
|
|
@ -53,7 +53,8 @@ doesn't necessarily have any visible styling.
|
|||||||
## Files
|
## Files
|
||||||
|
|
||||||
* `[api:FileField]`: Simple file upload dialog.
|
* `[api:FileField]`: Simple file upload dialog.
|
||||||
* `[api:UploadField]`: File uploads through HTML5 features, including upload progress, preview and relationship management.
|
* `[api:UploadField]`: Upload to a `[api:File]` record, including upload progress, preview and relationship management.
|
||||||
|
* `[api:AssetField]`: Upload to a `[api:DBFile]` database field. Very similar to UploadField
|
||||||
|
|
||||||
## Relations
|
## Relations
|
||||||
|
|
||||||
|
@ -7,6 +7,10 @@ But that's not all it does - it will also link the uploaded file(s) to an existi
|
|||||||
and let you edit the linked files as well. That makes it flexible enough to sometimes even
|
and let you edit the linked files as well. That makes it flexible enough to sometimes even
|
||||||
replace the GridField, like for instance in creating and managing a simple gallery.
|
replace the GridField, like for instance in creating and managing a simple gallery.
|
||||||
|
|
||||||
|
The field automatically creates a `File` record for each uploaded file.
|
||||||
|
In order to associate uploaded files directly to a `DataObject` via the
|
||||||
|
`[api:DBFile]` database field, please use [AssetField](AssetField).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The field can be used in three ways: To upload a single file into a `has_one` relationship,
|
The field can be used in three ways: To upload a single file into a `has_one` relationship,
|
||||||
|
@ -0,0 +1,225 @@
|
|||||||
|
# AssetField
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
This form field can be used to upload files into SilverStripe's asset store.
|
||||||
|
It associates a file directly to a `DataObject` through the `[api:DBFile]` database field.
|
||||||
|
Saving the file association directly in a `DataObject` (as opposed to a relation)
|
||||||
|
can simplify data management and publication.
|
||||||
|
|
||||||
|
In order to create `[api:File]` records to contain uploaded files,
|
||||||
|
please use the [AssetField](AssetField) instead.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The field expects to save into a `DataObject` record with a `DBFile`
|
||||||
|
property matching the name of the field itself.
|
||||||
|
|
||||||
|
|
||||||
|
```php
|
||||||
|
class Team extends DataObject {
|
||||||
|
|
||||||
|
private static $db = array(
|
||||||
|
'BannerImage' => 'DBFile'
|
||||||
|
);
|
||||||
|
|
||||||
|
function getCMSFields() {
|
||||||
|
$fields = parent::getCMSFields();
|
||||||
|
|
||||||
|
$fields->addFieldToTab(
|
||||||
|
'Root.Upload',
|
||||||
|
$assetField = new AssetField(
|
||||||
|
$name = 'BannerImage',
|
||||||
|
$title = 'Upload a banner'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Restrict validator to include only supported image formats
|
||||||
|
$assetField->setAllowedFileCategories('image/supported');
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
AssetField 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
|
||||||
|
|
||||||
|
:::yaml
|
||||||
|
after: framework#uploadfield
|
||||||
|
---
|
||||||
|
AssetField:
|
||||||
|
defaultConfig:
|
||||||
|
canUpload: false
|
||||||
|
|
||||||
|
|
||||||
|
### Set a custom folder
|
||||||
|
|
||||||
|
This example will save all uploads in the `customfolder` in the configured assets store root (normally under 'assets')
|
||||||
|
If the folder doesn't exist, it will be created.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$fields->addFieldToTab(
|
||||||
|
'Root.Upload',
|
||||||
|
$assetField = new AssetField(
|
||||||
|
$name = 'GalleryImage',
|
||||||
|
$title = 'Please upload an image'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$assetField->setFolderName('customfolder');
|
||||||
|
|
||||||
|
|
||||||
|
### Limit the allowed filetypes
|
||||||
|
|
||||||
|
`AllowedExtensions` defaults to the `File.allowed_extensions` configuration setting,
|
||||||
|
but can be overwritten for each AssetField:
|
||||||
|
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$assetField->setAllowedExtensions(array('jpg', 'jpeg', 'png', 'gif'));
|
||||||
|
|
||||||
|
|
||||||
|
Entire groups of file extensions can be specified in order to quickly limit types to known file categories.
|
||||||
|
This can be done by using file category names, which are defined via the `File.app_categories` config. This
|
||||||
|
list could be extended with any custom categories.
|
||||||
|
|
||||||
|
The built in categories are:
|
||||||
|
|
||||||
|
| File category | Example extensions |
|
||||||
|
|-----------------|--------------------|
|
||||||
|
| archive | zip, gz, rar |
|
||||||
|
| audio | mp3, wav, ogg |
|
||||||
|
| document | doc, txt, pdf |
|
||||||
|
| flash | fla, swf |
|
||||||
|
| image | jpg, tiff, ps |
|
||||||
|
| image/supported | jpg, gif, png |
|
||||||
|
| video | mkv, avi, mp4 |
|
||||||
|
|
||||||
|
Note that although all image types are included in the 'image' category, only images that are in the
|
||||||
|
'images/supported' list are compatible with the SilverStripe image manipulations API. Other types
|
||||||
|
can be uploaded, but cannot be resized.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$assetField->setAllowedFileCategories('image/supported');
|
||||||
|
|
||||||
|
|
||||||
|
This will limit files to the the compatible image formats: jpg, jpeg, gif, and png.
|
||||||
|
|
||||||
|
`AllowedExtensions` can also be set globally via the
|
||||||
|
[YAML configuration](/developer_guides/configuration/configuration/#configuration-yaml-syntax-and-rules),
|
||||||
|
for example you may add the following into your mysite/_config/config.yml:
|
||||||
|
|
||||||
|
|
||||||
|
:::yaml
|
||||||
|
File:
|
||||||
|
allowed_extensions:
|
||||||
|
- 7zip
|
||||||
|
- xzip
|
||||||
|
|
||||||
|
|
||||||
|
### Limit the maximum file size
|
||||||
|
|
||||||
|
`AllowedMaxFileSize` is by default set to the lower value of the 2 php.ini configurations:
|
||||||
|
`upload_max_filesize` and `post_max_size`. The value is set as bytes.
|
||||||
|
|
||||||
|
NOTE: this only sets the configuration for your AssetField, this does NOT change your
|
||||||
|
server upload settings, so if your server is set to only allow 1 MB and you set the
|
||||||
|
AssetField to 2 MB, uploads will not work.
|
||||||
|
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$sizeMB = 2; // 2 MB
|
||||||
|
$size = $sizeMB * 1024 * 1024; // 2 MB in bytes
|
||||||
|
$this->getValidator()->setAllowedMaxFileSize($size);
|
||||||
|
|
||||||
|
|
||||||
|
You can also specify a default global maximum file size setting in your config for different file types.
|
||||||
|
This is overridden when specifying the max allowed file size on the AssetField instance.
|
||||||
|
|
||||||
|
|
||||||
|
:::yaml
|
||||||
|
Upload_Validator:
|
||||||
|
default_max_file_size:
|
||||||
|
'[image]': '1m'
|
||||||
|
'[document]': '5m'
|
||||||
|
'jpeg': 2000
|
||||||
|
|
||||||
|
|
||||||
|
### Preview dimensions
|
||||||
|
|
||||||
|
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
|
||||||
|
$assetField->setPreviewMaxWidth(100);
|
||||||
|
$assetField->setPreviewMaxHeight(100);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Disable uploading of new files
|
||||||
|
|
||||||
|
Alternatively, you can force the user to only specify already existing files in the file library
|
||||||
|
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$assetField->setCanUpload(false);
|
||||||
|
|
||||||
|
|
||||||
|
### Automatic or manual upload
|
||||||
|
|
||||||
|
By default, the AssetField will try to automatically upload all selected files. 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
|
||||||
|
$assetField->setAutoUpload(false);
|
||||||
|
|
||||||
|
|
||||||
|
### Change Detection
|
||||||
|
|
||||||
|
The CMS interface will automatically notify the form containing
|
||||||
|
an AssetField instance of changes, such as a new upload,
|
||||||
|
or the removal of an existing upload (through a `dirty` event).
|
||||||
|
The UI can then choose an appropriate response (e.g. highlighting the "save" button).
|
||||||
|
If the AssetField doesn't save into a relation, there's technically no saveable change
|
||||||
|
(the upload has already happened), which is why this feature can be disabled on demand.
|
||||||
|
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$assetField->setConfig('changeDetection', false);
|
||||||
|
|
||||||
|
## Configuration Reference
|
||||||
|
|
||||||
|
* `setAllowedFileExtensions`: (array) List of file extensions allowed.
|
||||||
|
* `setAllowedFileCategories`: (array|string) List of types of files allowed. May be any number of
|
||||||
|
categories as defined in `File.app_categories` config.
|
||||||
|
* `setAutoUpload`: (boolean) Should the field automatically trigger an upload once a file is selected?
|
||||||
|
* `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.
|
||||||
|
* `setPreviewMaxWidth`: (int).
|
||||||
|
* `setPreviewMaxHeight`: (int).
|
||||||
|
* `setTemplateFileButtons`: (string) Template name to use for the file buttons.
|
||||||
|
* `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.
|
@ -153,4 +153,10 @@ You may also notice the 'Sync files' button (highlighted below). This button all
|
|||||||
|
|
||||||
## Upload
|
## Upload
|
||||||
|
|
||||||
Files can be managed through a `FileField` or an `UploadField`. The `[api:FileField]` class provides a simple HTML input with a type of "file", whereas an `[api:UploadField]` provides a much more feature-rich field (including AJAX-based uploads, previews, relationship management and file data management). See [`Reference - UploadField`](/developer_guides/forms/field_types/uploadfield) for more information about how to use the `UploadField` class.
|
Files can be managed through forms in three ways:
|
||||||
|
|
||||||
|
* `[api:FileField]`: provides a simple HTML input with a type of "file".
|
||||||
|
* [UploadField](/developer_guides/forms/field_types/uploadfield): more feature-rich field (
|
||||||
|
including AJAX-based uploads, previews, relationship management and file data management).
|
||||||
|
* [AssetField](/developer_guides/forms/field_types/assetfield): Similar to UploadField,
|
||||||
|
but operates on a `[api:DBFile]` database field instead of a `[api:File]` record.
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
the `DataObject::ID` in a `data-fileid` property, or via shortcodes. This is necessary because file
|
the `DataObject::ID` in a `data-fileid` property, or via shortcodes. This is necessary because file
|
||||||
urls are no longer able to identify assets.
|
urls are no longer able to identify assets.
|
||||||
* Extension point `HtmlEditorField::processImage` has been removed, and moved to `Image::regenerateImageHTML`
|
* Extension point `HtmlEditorField::processImage` has been removed, and moved to `Image::regenerateImageHTML`
|
||||||
|
* `Upload::load` now stores assets directly without saving into a `File` dataobject.
|
||||||
|
|
||||||
## New API
|
## New API
|
||||||
|
|
||||||
@ -31,6 +32,7 @@
|
|||||||
cache or combined files).
|
cache or combined files).
|
||||||
* `Requirements_Minifier` API can be used to declare any new mechanism for minifying combined required files.
|
* `Requirements_Minifier` API can be used to declare any new mechanism for minifying combined required files.
|
||||||
By default this api is provided by the `JSMinifier` class, but user code can substitute their own.
|
By default this api is provided by the `JSMinifier` class, but user code can substitute their own.
|
||||||
|
* `AssetField` formfield to provide an `UploadField` style uploader for the new `DBFile` database field.
|
||||||
|
|
||||||
## Deprecated classes/methods
|
## Deprecated classes/methods
|
||||||
|
|
||||||
|
@ -124,16 +124,92 @@ class Upload extends Controller {
|
|||||||
return Injector::inst()->createWithArgs('AssetNameGenerator', array($filename));
|
return Injector::inst()->createWithArgs('AssetNameGenerator', array($filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return AssetStore
|
||||||
|
*/
|
||||||
|
protected function getAssetStore() {
|
||||||
|
return Injector::inst()->get('AssetStore');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save an file passed from a form post into the AssetStore directly
|
||||||
|
*
|
||||||
|
* @param $tmpFile array Indexed array that PHP generated for every file it uploads.
|
||||||
|
* @param $folderPath string Folder path relative to /assets
|
||||||
|
* @return array|false Either the tuple array, or false if the file could not be saved
|
||||||
|
*/
|
||||||
|
public function load($tmpFile, $folderPath = false) {
|
||||||
|
// Validate filename
|
||||||
|
$filename = $this->getValidFilename($tmpFile, $folderPath);
|
||||||
|
if(!$filename) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save file into backend
|
||||||
|
$result = $this->storeTempFile($tmpFile, $filename, $this->getAssetStore());
|
||||||
|
|
||||||
|
//to allow extensions to e.g. create a version after an upload
|
||||||
|
$this->extend('onAfterLoad', $result);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save an file passed from a form post into this object.
|
* Save an file passed from a form post into this object.
|
||||||
* File names are filtered through {@link FileNameFilter}, see class documentation
|
* File names are filtered through {@link FileNameFilter}, see class documentation
|
||||||
* on how to influence this behaviour.
|
* on how to influence this behaviour.
|
||||||
*
|
*
|
||||||
* @param $tmpFile array Indexed array that PHP generated for every file it uploads.
|
* @param array $tmpFile
|
||||||
* @param $folderPath string Folder path relative to /assets
|
* @param AssetContainer $file
|
||||||
* @return Boolean|string Either success or error-message.
|
* @return bool True if the file was successfully saved into this record
|
||||||
*/
|
*/
|
||||||
public function load($tmpFile, $folderPath = false) {
|
public function loadIntoFile($tmpFile, $file = null, $folderPath = false) {
|
||||||
|
$this->file = $file;
|
||||||
|
|
||||||
|
// Validate filename
|
||||||
|
$filename = $this->getValidFilename($tmpFile, $folderPath);
|
||||||
|
if(!$filename) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$filename = $this->resolveExistingFile($filename);
|
||||||
|
|
||||||
|
// Save changes to underlying record (if it's a DataObject)
|
||||||
|
$this->storeTempFile($tmpFile, $filename, $this->file);
|
||||||
|
if($this->file instanceof DataObject) {
|
||||||
|
$this->file->write();
|
||||||
|
}
|
||||||
|
|
||||||
|
//to allow extensions to e.g. create a version after an upload
|
||||||
|
$this->file->extend('onAfterUpload');
|
||||||
|
$this->extend('onAfterLoadIntoFile', $this->file);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign this temporary file into the given destination
|
||||||
|
*
|
||||||
|
* @param array $tmpFile
|
||||||
|
* @param string $filename
|
||||||
|
* @param AssetContainer|AssetStore $container
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function storeTempFile($tmpFile, $filename, $container) {
|
||||||
|
// Save file into backend
|
||||||
|
$conflictResolution = $this->replaceFile
|
||||||
|
? AssetStore::CONFLICT_OVERWRITE
|
||||||
|
: AssetStore::CONFLICT_RENAME;
|
||||||
|
return $container->setFromLocalFile($tmpFile['tmp_name'], $filename, null, null, $conflictResolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a temporary file and upload path, validate the file and determine the
|
||||||
|
* value of the 'Filename' tuple that should be used to store this asset.
|
||||||
|
*
|
||||||
|
* @param array $tmpFile
|
||||||
|
* @param string $folderPath
|
||||||
|
* @return string|false Value of filename tuple, or false if invalid
|
||||||
|
*/
|
||||||
|
protected function getValidFilename($tmpFile, $folderPath = false) {
|
||||||
if(!is_array($tmpFile)) {
|
if(!is_array($tmpFile)) {
|
||||||
throw new InvalidArgumentException(
|
throw new InvalidArgumentException(
|
||||||
"Upload::load() Not passed an array. Most likely, the form hasn't got the right enctype"
|
"Upload::load() Not passed an array. Most likely, the form hasn't got the right enctype"
|
||||||
@ -157,23 +233,7 @@ class Upload extends Controller {
|
|||||||
if($folderPath) {
|
if($folderPath) {
|
||||||
$filename = File::join_paths($folderPath, $filename);
|
$filename = File::join_paths($folderPath, $filename);
|
||||||
}
|
}
|
||||||
|
return $filename;
|
||||||
// Validate filename
|
|
||||||
$filename = $this->resolveExistingFile($filename);
|
|
||||||
|
|
||||||
// Save file into backend
|
|
||||||
$conflictResolution = $this->replaceFile ? AssetStore::CONFLICT_OVERWRITE : AssetStore::CONFLICT_RENAME;
|
|
||||||
$this->file->setFromLocalFile($tmpFile['tmp_name'], $filename, null, null, $conflictResolution);
|
|
||||||
|
|
||||||
// Save changes to underlying record (if it's a DataObject)
|
|
||||||
if($this->file instanceof DataObject) {
|
|
||||||
$this->file->write();
|
|
||||||
}
|
|
||||||
|
|
||||||
//to allow extensions to e.g. create a version after an upload
|
|
||||||
$this->file->extend('onAfterUpload');
|
|
||||||
$this->extend('onAfterLoad', $this->file);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -226,18 +286,6 @@ class Upload extends Controller {
|
|||||||
throw new Exception("Could not rename {$filename} with {$tries} tries");
|
throw new Exception("Could not rename {$filename} with {$tries} tries");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load temporary PHP-upload into File-object.
|
|
||||||
*
|
|
||||||
* @param array $tmpFile
|
|
||||||
* @param AssetContainer $file
|
|
||||||
* @return Boolean
|
|
||||||
*/
|
|
||||||
public function loadIntoFile($tmpFile, $file, $folderPath = false) {
|
|
||||||
$this->file = $file;
|
|
||||||
return $this->load($tmpFile, $folderPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Boolean
|
* @return Boolean
|
||||||
*/
|
*/
|
||||||
|
@ -87,13 +87,12 @@ class DBFile extends CompositeDBField implements AssetContainer, ShortcodeHandle
|
|||||||
'Title' => 'Varchar',
|
'Title' => 'Varchar',
|
||||||
'MimeType' => 'Varchar',
|
'MimeType' => 'Varchar',
|
||||||
'String' => 'Text',
|
'String' => 'Text',
|
||||||
'Tag' => 'HTMLText'
|
'Tag' => 'HTMLText',
|
||||||
|
'Size' => 'Varchar'
|
||||||
);
|
);
|
||||||
|
|
||||||
public function scaffoldFormField($title = null, $params = null) {
|
public function scaffoldFormField($title = null, $params = null) {
|
||||||
return null;
|
return AssetField::create($this->getName(), $title);
|
||||||
// @todo
|
|
||||||
//return new AssetUploadField($this->getName(), $title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -241,13 +240,11 @@ class DBFile extends CompositeDBField implements AssetContainer, ShortcodeHandle
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get URL, but without resampling.
|
* Get URL, but without resampling.
|
||||||
|
* Note that this will return the url even if the file does not exist.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getSourceURL() {
|
public function getSourceURL() {
|
||||||
if(!$this->exists()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return $this
|
return $this
|
||||||
->getStore()
|
->getStore()
|
||||||
->getAsURL($this->Filename, $this->Hash, $this->Variant);
|
->getAsURL($this->Filename, $this->Hash, $this->Variant);
|
||||||
@ -294,7 +291,12 @@ class DBFile extends CompositeDBField implements AssetContainer, ShortcodeHandle
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function exists() {
|
public function exists() {
|
||||||
return !empty($this->Filename);
|
if(empty($this->Filename)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $this
|
||||||
|
->getStore()
|
||||||
|
->exists($this->Filename, $this->Hash, $this->Variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function get_shortcodes() {
|
public static function get_shortcodes() {
|
||||||
@ -454,4 +456,18 @@ class DBFile extends CompositeDBField implements AssetContainer, ShortcodeHandle
|
|||||||
|
|
||||||
return parent::setField($field, $value, $markChanged);
|
return parent::setField($field, $value, $markChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size of the file type in an appropriate format.
|
||||||
|
*
|
||||||
|
* @return string|false String value, or false if doesn't exist
|
||||||
|
*/
|
||||||
|
public function getSize() {
|
||||||
|
$size = $this->getAbsoluteSize();
|
||||||
|
if($size) {
|
||||||
|
return \File::format_size($size);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
766
forms/AssetField.php
Normal file
766
forms/AssetField.php
Normal file
@ -0,0 +1,766 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use SilverStripe\Filesystem\Storage\AssetContainer;
|
||||||
|
use SilverStripe\Filesystem\Storage\AssetStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Field for uploading into a DBFile instance.
|
||||||
|
*
|
||||||
|
* This formfield has fewer options than UploadField:
|
||||||
|
* - Assets can only be uploaded, not attached from library
|
||||||
|
* - Duplicate files will only be renamed, not allowed to overwrite existing references.
|
||||||
|
* - Only one file may be attached.
|
||||||
|
* - Files can't be edited once uploaded.
|
||||||
|
* - Attached files can only be removed, not deleted.
|
||||||
|
*
|
||||||
|
* @package forms
|
||||||
|
*/
|
||||||
|
class AssetField extends FileField {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private static $allowed_actions = array(
|
||||||
|
'upload'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private static $url_handlers = array(
|
||||||
|
'$Action!' => '$Action',
|
||||||
|
);
|
||||||
|
|
||||||
|
private static $casting = array(
|
||||||
|
'Value' => 'DBFile',
|
||||||
|
'UploadFieldThumbnailURL' => 'Varchar'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template to use for the file button widget
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $templateFileButtons = 'AssetField_FileButtons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parent data record. Will be infered from parent form or controller if blank. The destination
|
||||||
|
* DBFile should be a property of the name $name on this object.
|
||||||
|
*
|
||||||
|
* @var DataObject
|
||||||
|
*/
|
||||||
|
protected $record;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config for this field used in the front-end javascript
|
||||||
|
* (will be merged into the config of the javascript file upload plugin).
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $ufConfig = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Front end config defaults
|
||||||
|
*
|
||||||
|
* @config
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private static $defaultConfig = array(
|
||||||
|
/**
|
||||||
|
* Automatically upload the file once selected
|
||||||
|
*
|
||||||
|
* @var boolean
|
||||||
|
*/
|
||||||
|
'autoUpload' => true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can the user upload new files.
|
||||||
|
* String values are interpreted as permission codes.
|
||||||
|
*
|
||||||
|
* @var boolean|string
|
||||||
|
*/
|
||||||
|
'canUpload' => true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the target folder for new uploads in the field UI.
|
||||||
|
* Disable to keep the internal filesystem structure hidden from users.
|
||||||
|
*
|
||||||
|
* @var boolean|string
|
||||||
|
*/
|
||||||
|
'canPreviewFolder' => true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate a change event to the containing form if an upload
|
||||||
|
* or file edit/delete was performed.
|
||||||
|
*
|
||||||
|
* @var boolean
|
||||||
|
*/
|
||||||
|
'changeDetection' => true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum width of the preview thumbnail
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
'previewMaxWidth' => 80,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum height of the preview thumbnail
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
'previewMaxHeight' => 60,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* javascript template used to display uploading files
|
||||||
|
*
|
||||||
|
* @see javascript/UploadField_uploadtemplate.js
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
'uploadTemplateName' => 'ss-uploadfield-uploadtemplate',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* javascript template used to display already uploaded files
|
||||||
|
*
|
||||||
|
* @see javascript/UploadField_downloadtemplate.js
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
'downloadTemplateName' => 'ss-uploadfield-downloadtemplate'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Folder to display in "Select files" list.
|
||||||
|
* Defaults to listing all files regardless of folder.
|
||||||
|
* The folder path should be relative to the webroot.
|
||||||
|
* See {@link FileField->folderName} to set the upload target instead.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
* @example admin/folder/subfolder
|
||||||
|
*/
|
||||||
|
protected $displayFolderName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new UploadField instance
|
||||||
|
*
|
||||||
|
* @param string $name The internal field name, passed to forms.
|
||||||
|
* @param string $title The field label.
|
||||||
|
* @param Form $form Reference to the container form
|
||||||
|
*/
|
||||||
|
public function __construct($name, $title = null) {
|
||||||
|
$this->addExtraClass('ss-upload'); // class, used by js
|
||||||
|
$this->addExtraClass('ss-uploadfield'); // class, used by css for uploadfield only
|
||||||
|
|
||||||
|
$this->ufConfig = array_merge($this->ufConfig, self::config()->defaultConfig);
|
||||||
|
|
||||||
|
parent::__construct($name, $title);
|
||||||
|
|
||||||
|
// AssetField always uses rename replacement method
|
||||||
|
$this->getUpload()->setReplaceFile(false);
|
||||||
|
|
||||||
|
// filter out '' since this would be a regex problem on JS end
|
||||||
|
$this->getValidator()->setAllowedExtensions(
|
||||||
|
array_filter(Config::inst()->get('File', 'allowed_extensions'))
|
||||||
|
);
|
||||||
|
|
||||||
|
// get the lower max size
|
||||||
|
$maxUpload = File::ini2bytes(ini_get('upload_max_filesize'));
|
||||||
|
$maxPost = File::ini2bytes(ini_get('post_max_size'));
|
||||||
|
$this->getValidator()->setAllowedMaxFileSize(min($maxUpload, $maxPost));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set name of template used for Buttons on each file (replace, edit, remove, delete) (without path or extension)
|
||||||
|
*
|
||||||
|
* @param string
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setTemplateFileButtons($template) {
|
||||||
|
$this->templateFileButtons = $template;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTemplateFileButtons() {
|
||||||
|
return $this->templateFileButtons;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the target folder for new uploads in is visible the field UI.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function canPreviewFolder() {
|
||||||
|
if(!$this->isActive()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$can = $this->getConfig('canPreviewFolder');
|
||||||
|
if(is_bool($can)) {
|
||||||
|
return $can;
|
||||||
|
}
|
||||||
|
return Permission::check($can);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the target folder for new uploads in is visible the field UI.
|
||||||
|
* Disable to keep the internal filesystem structure hidden from users.
|
||||||
|
*
|
||||||
|
* @param boolean|string $canPreviewFolder Either a boolean flag, or a
|
||||||
|
* required permission code
|
||||||
|
* @return $this Self reference
|
||||||
|
*/
|
||||||
|
public function setCanPreviewFolder($canPreviewFolder) {
|
||||||
|
return $this->setConfig('canPreviewFolder', $canPreviewFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setDisplayFolderName($name) {
|
||||||
|
$this->displayFolderName = $name;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getDisplayFolderName() {
|
||||||
|
return $this->displayFolderName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force a record to be used as "Parent" for uploaded Files (eg a Page with a has_one to File)
|
||||||
|
* @param DataObject $record
|
||||||
|
*/
|
||||||
|
public function setRecord($record) {
|
||||||
|
$this->record = $record;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the record to use as "Parent" for uploaded Files (eg a Page with a has_one to File) If none is set, it will
|
||||||
|
* use Form->getRecord().
|
||||||
|
*
|
||||||
|
* @return DataObject
|
||||||
|
*/
|
||||||
|
public function getRecord() {
|
||||||
|
if (!$this->record
|
||||||
|
&& $this->form
|
||||||
|
&& ($record = $this->form->getRecord())
|
||||||
|
&& $record instanceof DataObject
|
||||||
|
) {
|
||||||
|
$this->record = $record;
|
||||||
|
}
|
||||||
|
return $this->record;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setValue($value, $record = null) {
|
||||||
|
// Extract value from underlying record
|
||||||
|
if(empty($value) && $this->getName() && $record instanceof DataObject) {
|
||||||
|
$name = $this->getName();
|
||||||
|
$value = $record->$name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert asset container to tuple value
|
||||||
|
if($value instanceof AssetContainer) {
|
||||||
|
if($value->exists()) {
|
||||||
|
$value = array(
|
||||||
|
'Filename' => $value->getFilename(),
|
||||||
|
'Hash' => $value->getHash(),
|
||||||
|
'Variant' => $value->getVariant()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If javascript is disabled, direct file upload (non-html5 style) can
|
||||||
|
// trigger a single or multiple file submission. Note that this may be
|
||||||
|
// included in addition to re-submitted File IDs as above, so these
|
||||||
|
// should be added to the list instead of operated on independently.
|
||||||
|
if($uploadedFile = $this->extractUploadedFileData($value)) {
|
||||||
|
$value = $this->saveTemporaryFile($uploadedFile, $error);
|
||||||
|
if(!$value) {
|
||||||
|
throw new ValidationException($error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set value using parent
|
||||||
|
return parent::setValue($value, $record);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
$name = $this->getName();
|
||||||
|
if(!$name) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
$value = $this->Value();
|
||||||
|
foreach(array('Filename', 'Hash', 'Variant') as $part) {
|
||||||
|
$partValue = isset($value[$part])
|
||||||
|
? $value[$part]
|
||||||
|
: null;
|
||||||
|
$record->setField("{$name}{$part}", $partValue);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a front-end config variable for the upload field
|
||||||
|
*
|
||||||
|
* @see https://github.com/blueimp/jQuery-File-Upload/wiki/Options for the list of front end options available
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $val
|
||||||
|
* @return $this self reference
|
||||||
|
*/
|
||||||
|
public function setConfig($key, $val) {
|
||||||
|
$this->ufConfig[$key] = $val;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a front-end config variable for the upload field
|
||||||
|
*
|
||||||
|
* @see https://github.com/blueimp/jQuery-File-Upload/wiki/Options for the list of front end options available
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getConfig($key) {
|
||||||
|
if(isset($this->ufConfig[$key])) {
|
||||||
|
return $this->ufConfig[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the field should automatically upload the file.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function getAutoUpload() {
|
||||||
|
return $this->getConfig('autoUpload');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the field should automatically upload the file
|
||||||
|
*
|
||||||
|
* @param boolean $autoUpload
|
||||||
|
* @return $this Self reference
|
||||||
|
*/
|
||||||
|
public function setAutoUpload($autoUpload) {
|
||||||
|
return $this->setConfig('autoUpload', $autoUpload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the user has permission to upload.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function canUpload() {
|
||||||
|
if(!$this->isActive()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$can = $this->getConfig('canUpload');
|
||||||
|
if(is_bool($can)) {
|
||||||
|
return $can;
|
||||||
|
}
|
||||||
|
return Permission::check($can);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify whether the user can upload files.
|
||||||
|
* String values will be treated as required permission codes
|
||||||
|
*
|
||||||
|
* @param bool|string $canUpload Either a boolean flag, or a required
|
||||||
|
* permission code
|
||||||
|
* @return $this Self reference
|
||||||
|
*/
|
||||||
|
public function setCanUpload($canUpload) {
|
||||||
|
return $this->setConfig('canUpload', $canUpload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the field is neither readonly nor disabled
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isActive() {
|
||||||
|
return !$this->isDisabled() && !$this->isReadonly();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets thumbnail width. Defaults to 80
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getPreviewMaxWidth() {
|
||||||
|
return $this->getConfig('previewMaxWidth');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set thumbnail width.
|
||||||
|
*
|
||||||
|
* @param int $previewMaxWidth
|
||||||
|
* @return $this Self reference
|
||||||
|
*/
|
||||||
|
public function setPreviewMaxWidth($previewMaxWidth) {
|
||||||
|
return $this->setConfig('previewMaxWidth', $previewMaxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets thumbnail height. Defaults to 60
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getPreviewMaxHeight() {
|
||||||
|
return $this->getConfig('previewMaxHeight');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set thumbnail height.
|
||||||
|
*
|
||||||
|
* @param int $previewMaxHeight
|
||||||
|
* @return $this 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');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set javascript template used to display uploading files
|
||||||
|
*
|
||||||
|
* @param string $uploadTemplateName
|
||||||
|
* @return $this 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');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set javascript template used to display already uploaded files
|
||||||
|
*
|
||||||
|
* @param string $downloadTemplateName
|
||||||
|
* @return $this Self reference
|
||||||
|
*/
|
||||||
|
public function setDownloadTemplateName($downloadTemplateName) {
|
||||||
|
return $this->setConfig('downloadTemplateName', $downloadTemplateName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Field($properties = array()) {
|
||||||
|
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');
|
||||||
|
Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/ssui.core.js');
|
||||||
|
Requirements::add_i18n_javascript(FRAMEWORK_DIR . '/javascript/lang');
|
||||||
|
|
||||||
|
Requirements::combine_files('uploadfield.js', array(
|
||||||
|
// @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',
|
||||||
|
THIRDPARTY_DIR . '/jquery-fileupload/jquery.fileupload.js',
|
||||||
|
THIRDPARTY_DIR . '/jquery-fileupload/jquery.fileupload-ui.js',
|
||||||
|
FRAMEWORK_DIR . '/javascript/UploadField_uploadtemplate.js',
|
||||||
|
FRAMEWORK_DIR . '/javascript/UploadField_downloadtemplate.js',
|
||||||
|
FRAMEWORK_DIR . '/javascript/UploadField.js',
|
||||||
|
));
|
||||||
|
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
|
||||||
|
$config = array(
|
||||||
|
'allowedMaxFileNumber' => 1, // Only one file allowed for AssetField
|
||||||
|
'url' => $this->Link('upload'),
|
||||||
|
'urlSelectDialog' => $this->Link('select'),
|
||||||
|
'urlAttach' => $this->Link('attach'),
|
||||||
|
'urlFileExists' => $this->link('fileexists'),
|
||||||
|
'acceptFileTypes' => '.+$',
|
||||||
|
// Fileupload treats maxNumberOfFiles as the max number of _additional_ items allowed
|
||||||
|
'maxNumberOfFiles' => $this->Value() ? 0 : 1,
|
||||||
|
'replaceFile' => false, // Should always be false for AssetField
|
||||||
|
);
|
||||||
|
|
||||||
|
// Validation: File extensions
|
||||||
|
if ($allowedExtensions = $this->getAllowedExtensions()) {
|
||||||
|
$config['acceptFileTypes'] = '(\.|\/)(' . implode('|', $allowedExtensions) . ')$';
|
||||||
|
$config['errorMessages']['acceptFileTypes'] = _t(
|
||||||
|
'File.INVALIDEXTENSIONSHORT',
|
||||||
|
'Extension is not allowed'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation: File size
|
||||||
|
if ($allowedMaxFileSize = $this->getValidator()->getAllowedMaxFileSize()) {
|
||||||
|
$config['maxFileSize'] = $allowedMaxFileSize;
|
||||||
|
$config['errorMessages']['maxFileSize'] = _t(
|
||||||
|
'File.TOOLARGESHORT',
|
||||||
|
'Filesize exceeds {size}',
|
||||||
|
array('size' => File::format_size($config['maxFileSize']))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mergedConfig = array_merge($config, $this->ufConfig);
|
||||||
|
return $this->customise(array(
|
||||||
|
'ConfigString' => Convert::raw2json($mergedConfig),
|
||||||
|
'UploadFieldFileButtons' => $this->renderWith($this->getTemplateFileButtons())
|
||||||
|
))->renderWith($this->getTemplates());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation method for this field, called when the entire form is validated
|
||||||
|
*
|
||||||
|
* @param Validator $validator
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function validate($validator) {
|
||||||
|
$name = $this->getName();
|
||||||
|
$value = $this->Value();
|
||||||
|
|
||||||
|
// If there is no file then quit
|
||||||
|
if(!$value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revalidate each file against nested validator
|
||||||
|
$this->getUpload()->clearErrors();
|
||||||
|
|
||||||
|
// Generate $_FILES style file attribute array for upload validator
|
||||||
|
$store = $this->getAssetStore();
|
||||||
|
$mime = $store->getMimeType($value['Filename'], $value['Hash'], $value['Variant']);
|
||||||
|
$metadata = $store->getMetadata($value['Filename'], $value['Hash'], $value['Variant']);
|
||||||
|
$tmpFile = array(
|
||||||
|
'name' => $value['Filename'],
|
||||||
|
'type' => $mime,
|
||||||
|
'size' => isset($metadata['size']) ? $metadata['size'] : 0,
|
||||||
|
'tmp_name' => null, // Should bypass is_uploaded_file check
|
||||||
|
'error' => UPLOAD_ERR_OK,
|
||||||
|
);
|
||||||
|
$this->getUpload()->validate($tmpFile);
|
||||||
|
|
||||||
|
// Check all errors
|
||||||
|
if($errors = $this->getUpload()->getErrors()) {
|
||||||
|
foreach($errors as $error) {
|
||||||
|
$validator->validationError($name, $error, "validation");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of post variables, extract all temporary file data into an array
|
||||||
|
*
|
||||||
|
* @param array $postVars Array of posted form data
|
||||||
|
* @return array data for uploaded file
|
||||||
|
*/
|
||||||
|
protected function extractUploadedFileData($postVars) {
|
||||||
|
// Note: Format of posted file parameters in php is a feature of using
|
||||||
|
// <input name='{$Name}[Upload]' /> for multiple file uploads
|
||||||
|
|
||||||
|
// Skip empty file
|
||||||
|
if(empty($postVars['tmp_name'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return single level array for posted file
|
||||||
|
if(empty($postVars['tmp_name']['Upload'])) {
|
||||||
|
return $postVars;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract posted feedback value
|
||||||
|
$tmpFile = array();
|
||||||
|
foreach(array('name', 'type', 'tmp_name', 'error', 'size') as $field) {
|
||||||
|
$tmpFile[$field] = $postVars[$field]['Upload'];
|
||||||
|
}
|
||||||
|
return $tmpFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the temporary file data into the asset store, and return the tuple details
|
||||||
|
* for the result.
|
||||||
|
*
|
||||||
|
* @param array $tmpFile Temporary file data
|
||||||
|
* @param string $error Error message
|
||||||
|
* @return array Result of saved file, or null if error
|
||||||
|
*/
|
||||||
|
protected function saveTemporaryFile($tmpFile, &$error = null) {
|
||||||
|
$error = null;
|
||||||
|
if (empty($tmpFile)) {
|
||||||
|
$error = _t('UploadField.FIELDNOTSET', 'File information not found');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($tmpFile['error']) {
|
||||||
|
$error = $tmpFile['error'];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the uploaded file into a new file object.
|
||||||
|
try {
|
||||||
|
$result = $this
|
||||||
|
->getUpload()
|
||||||
|
->load($tmpFile, $this->getFolderName());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// we shouldn't get an error here, but just in case
|
||||||
|
$error = $e->getMessage();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if upload field has an error
|
||||||
|
if ($this->getUpload()->isError()) {
|
||||||
|
$error = implode(' ' . PHP_EOL, $this->getUpload()->getErrors());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return tuple array of Filename, Hash and Variant
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely encodes the File object with all standard fields required
|
||||||
|
* by the front end
|
||||||
|
*
|
||||||
|
* @param string $filename
|
||||||
|
* @param string $hash
|
||||||
|
* @param string $variant
|
||||||
|
* @return array Encoded list of file attributes
|
||||||
|
*/
|
||||||
|
protected function encodeAssetAttributes($filename, $hash, $variant) {
|
||||||
|
// Force regeneration of file thumbnail for this tuple (without saving into db)
|
||||||
|
$object = DBFile::create();
|
||||||
|
$object->setValue(array('Filename' => $filename, 'Hash' => $hash, 'Variant' => $variant));
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'filename' => $filename,
|
||||||
|
'hash' => $hash,
|
||||||
|
'variant' => $variant,
|
||||||
|
'name' => $object->getBasename(),
|
||||||
|
'url' => $object->getURL(),
|
||||||
|
'thumbnail_url' => $object->ThumbnailURL(
|
||||||
|
$this->getPreviewMaxWidth(),
|
||||||
|
$this->getPreviewMaxHeight()
|
||||||
|
),
|
||||||
|
'size' => $object->getAbsoluteSize(),
|
||||||
|
'type' => File::get_file_type($object->getFilename()),
|
||||||
|
'buttons' => (string)$this->renderWith($this->getTemplateFileButtons()),
|
||||||
|
'fieldname' => $this->getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to handle upload of a single file
|
||||||
|
*
|
||||||
|
* @param SS_HTTPRequest $request
|
||||||
|
* @return SS_HTTPResponse
|
||||||
|
*/
|
||||||
|
public function upload(SS_HTTPRequest $request) {
|
||||||
|
if($this->isDisabled() || $this->isReadonly() || !$this->canUpload()) {
|
||||||
|
return $this->httpError(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protect against CSRF on destructive action
|
||||||
|
$token = $this
|
||||||
|
->getForm()
|
||||||
|
->getSecurityToken();
|
||||||
|
if(!$token->checkRequest($request)) {
|
||||||
|
return $this->httpError(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get form details
|
||||||
|
$name = $this->getName();
|
||||||
|
$postVars = $request->postVar($name);
|
||||||
|
|
||||||
|
// Extract uploaded files from Form data
|
||||||
|
$uploadedFile = $this->extractUploadedFileData($postVars);
|
||||||
|
if(!$uploadedFile) {
|
||||||
|
return $this->httpError(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the temporary files into a File objects
|
||||||
|
// and save data/error on a per file basis
|
||||||
|
$result = $this->saveTemporaryFile($uploadedFile, $error);
|
||||||
|
if(empty($result)) {
|
||||||
|
$return = array('error' => $error);
|
||||||
|
} else {
|
||||||
|
$return = $this->encodeAssetAttributes($result['Filename'], $result['Hash'], $result['Variant']);
|
||||||
|
}
|
||||||
|
$this
|
||||||
|
->getUpload()
|
||||||
|
->clearErrors();
|
||||||
|
|
||||||
|
// Format response with json
|
||||||
|
$response = new SS_HTTPResponse(Convert::raw2json(array($return)));
|
||||||
|
$response->addHeader('Content-Type', 'text/plain');
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function performReadonlyTransformation() {
|
||||||
|
$clone = clone $this;
|
||||||
|
$clone->addExtraClass('readonly');
|
||||||
|
$clone->setReadonly(true);
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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($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(empty($name) || empty($record)) {
|
||||||
|
return $default;
|
||||||
|
} else {
|
||||||
|
$class = $record->getRelationClass($name);
|
||||||
|
return empty($class) ? $default : $class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return AssetStore
|
||||||
|
*/
|
||||||
|
protected function getAssetStore() {
|
||||||
|
return Injector::inst()->get('AssetStore');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -460,7 +460,15 @@ class FormField extends RequestHandler {
|
|||||||
//
|
//
|
||||||
// CSS class needs to be different from the one rendered through {@link FieldHolder()}.
|
// CSS class needs to be different from the one rendered through {@link FieldHolder()}.
|
||||||
if($this->Message()) {
|
if($this->Message()) {
|
||||||
$classes[] .= 'holder-' . $this->MessageType();
|
$classes[] = 'holder-' . $this->MessageType();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->isDisabled()) {
|
||||||
|
$classes[] = 'disabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->isReadonly()) {
|
||||||
|
$classes[] = 'readonly';
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode(' ', $classes);
|
return implode(' ', $classes);
|
||||||
|
@ -80,11 +80,18 @@ class UploadField extends FileField {
|
|||||||
/**
|
/**
|
||||||
* Config for this field used in the front-end javascript
|
* Config for this field used in the front-end javascript
|
||||||
* (will be merged into the config of the javascript file upload plugin).
|
* (will be merged into the config of the javascript file upload plugin).
|
||||||
* See framework/_config/uploadfield.yml for configuration defaults and documentation.
|
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $ufConfig = array(
|
protected $ufConfig = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Front end config defaults
|
||||||
|
*
|
||||||
|
* @config
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private static $defaultConfig = array(
|
||||||
/**
|
/**
|
||||||
* Automatically upload the file once selected
|
* Automatically upload the file once selected
|
||||||
*
|
*
|
||||||
@ -212,7 +219,7 @@ class UploadField extends FileField {
|
|||||||
$this->addExtraClass('ss-upload'); // class, used by js
|
$this->addExtraClass('ss-upload'); // class, used by js
|
||||||
$this->addExtraClass('ss-uploadfield'); // class, used by css for uploadfield only
|
$this->addExtraClass('ss-uploadfield'); // class, used by css for uploadfield only
|
||||||
|
|
||||||
$this->ufConfig = array_merge($this->ufConfig, self::config()->defaultConfig);
|
$this->ufConfig = self::config()->defaultConfig;
|
||||||
|
|
||||||
parent::__construct($name, $title);
|
parent::__construct($name, $title);
|
||||||
|
|
||||||
@ -905,17 +912,6 @@ class UploadField extends FileField {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function extraClass() {
|
|
||||||
if($this->isDisabled()) {
|
|
||||||
$this->addExtraClass('disabled');
|
|
||||||
}
|
|
||||||
if($this->isReadonly()) {
|
|
||||||
$this->addExtraClass('readonly');
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent::extraClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function Field($properties = array()) {
|
public function Field($properties = array()) {
|
||||||
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
|
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
|
||||||
Requirements::javascript(THIRDPARTY_DIR . '/jquery-ui/jquery-ui.js');
|
Requirements::javascript(THIRDPARTY_DIR . '/jquery-ui/jquery-ui.js');
|
||||||
|
@ -7,9 +7,14 @@ window.tmpl.cache['ss-uploadfield-downloadtemplate'] = tmpl(
|
|||||||
'</span></div>' +
|
'</span></div>' +
|
||||||
'{% } %}' +
|
'{% } %}' +
|
||||||
'<div class="ss-uploadfield-item-info">' +
|
'<div class="ss-uploadfield-item-info">' +
|
||||||
'{% if (!file.error) { %}' +
|
'{% if (!file.error && file.id) { %}' +
|
||||||
'<input type="hidden" name="{%=file.fieldname%}[Files][]" value="{%=file.id%}" />' +
|
'<input type="hidden" name="{%=file.fieldname%}[Files][]" value="{%=file.id%}" />' +
|
||||||
'{% } %}' +
|
'{% } %}' +
|
||||||
|
'{% if (!file.error && file.filename) { %}' +
|
||||||
|
'<input type="hidden" value="{%=file.filename%}" name="{%=file.fieldname%}[Filename]" />' +
|
||||||
|
'<input type="hidden" value="{%=file.hash%}" name="{%=file.fieldname%}[Hash]" />' +
|
||||||
|
'<input type="hidden" value="{%=file.variant%}" name="{%=file.fieldname%}[Variant]" />' +
|
||||||
|
'{% } %}' +
|
||||||
'<label class="ss-uploadfield-item-name">' +
|
'<label class="ss-uploadfield-item-name">' +
|
||||||
'<span class="name" title="{%=file.name%}">{%=file.name%}</span> ' +
|
'<span class="name" title="{%=file.name%}">{%=file.name%}</span> ' +
|
||||||
'<span class="size">{%=o.formatFileSize(file.size)%}</span>' +
|
'<span class="size">{%=o.formatFileSize(file.size)%}</span>' +
|
||||||
|
@ -23,7 +23,7 @@ window.tmpl.cache['ss-uploadfield-uploadtemplate'] = tmpl(
|
|||||||
'<div class="ss-uploadfield-item-cancel cancel">' +
|
'<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>' +
|
'<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>' +
|
||||||
'<div class="ss-uploadfield-item-overwrite hide ">'+
|
'<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>' +
|
'<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>' +
|
'</div>' +
|
||||||
|
@ -128,8 +128,10 @@ abstract class CompositeDBField extends DBField {
|
|||||||
* If $record is assigned to a dataobject, this field becomes a loose wrapper over
|
* If $record is assigned to a dataobject, this field becomes a loose wrapper over
|
||||||
* the records on that object instead.
|
* the records on that object instead.
|
||||||
*
|
*
|
||||||
|
* {@see ViewableData::obj}
|
||||||
|
*
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
* @param DataObject $record
|
* @param mixed $record Parent object to this field, which could be a DataObject, record array, or other
|
||||||
* @param bool $markChanged
|
* @param bool $markChanged
|
||||||
*/
|
*/
|
||||||
public function setValue($value, $record = null, $markChanged = true) {
|
public function setValue($value, $record = null, $markChanged = true) {
|
||||||
@ -154,7 +156,7 @@ abstract class CompositeDBField extends DBField {
|
|||||||
|
|
||||||
// Load from $record
|
// Load from $record
|
||||||
$key = $this->getName() . $field;
|
$key = $this->getName() . $field;
|
||||||
if(isset($record[$key])) {
|
if(is_array($record) && isset($record[$key])) {
|
||||||
$this->setField($field, $record[$key]);
|
$this->setField($field, $record[$key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
48
templates/AssetField.ss
Normal file
48
templates/AssetField.ss
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<ul class="ss-uploadfield-files files">
|
||||||
|
<% if $Value %>
|
||||||
|
<li class="ss-uploadfield-item template-download" data-filename="$Value.Filename">
|
||||||
|
<div class="ss-uploadfield-item-preview preview"><span>
|
||||||
|
<img alt="$Name.ATT" src="$Value.ThumbnailURL($PreviewMaxWidth,$PreviewMaxHeight).ATT" />
|
||||||
|
</span></div>
|
||||||
|
<div class="ss-uploadfield-item-info">
|
||||||
|
<input type='hidden' value='$Value.Filename.ATT' name='{$Name}[Filename]' />
|
||||||
|
<input type='hidden' value='$Value.Hash.ATT' name='{$Name}[Hash]' />
|
||||||
|
<input type='hidden' value='$Value.Variant.ATT' name='{$Name}[Variant]' />
|
||||||
|
<label class="ss-uploadfield-item-name">
|
||||||
|
<span class="name">$Value.Basename.XML</span>
|
||||||
|
<span class="size">$Value.Size.XML</span>
|
||||||
|
<div class="clear"><!-- --></div>
|
||||||
|
</label>
|
||||||
|
<div class="ss-uploadfield-item-actions">
|
||||||
|
<% if $isActive %>
|
||||||
|
$UploadFieldFileButtons.RAW
|
||||||
|
<% end_if %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<% end_if %>
|
||||||
|
</ul>
|
||||||
|
<% if $canUpload %>
|
||||||
|
<div class="ss-uploadfield-item ss-uploadfield-addfile<% if $CustomisedItems %> borderTop<% end_if %>">
|
||||||
|
<div class="ss-uploadfield-item-preview ss-uploadfield-dropzone ui-corner-all">
|
||||||
|
<%t UploadField.DROPFILE 'drop a file' %>
|
||||||
|
</div>
|
||||||
|
<div class="ss-uploadfield-item-info">
|
||||||
|
<label class="ss-uploadfield-item-name">
|
||||||
|
<b><%t UploadField.ATTACHFILE 'Attach a file' %></b>
|
||||||
|
<% if $canPreviewFolder %>
|
||||||
|
<small>(<%t UploadField.UPLOADSINTO 'saves into /{path}' path=$FolderName %>)</small>
|
||||||
|
<% end_if %>
|
||||||
|
</label>
|
||||||
|
<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}[Upload]" class="$extraClass ss-uploadfield-fromcomputer-fileinput" data-config="{$ConfigString.ATT}" type="file" />
|
||||||
|
</label>
|
||||||
|
<% 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 %>
|
||||||
|
<div class="clear"><!-- --></div>
|
||||||
|
</div>
|
||||||
|
<div class="clear"><!-- --></div>
|
||||||
|
</div>
|
||||||
|
<% end_if %>
|
2
templates/Includes/AssetField_FileButtons.ss
Normal file
2
templates/Includes/AssetField_FileButtons.ss
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<button class="ss-uploadfield-item-remove ss-ui-button ui-corner-all" title="<%t AssetUploadField.REMOVEINFO 'Remove this file from this field' %>" data-icon="plug-disconnect-prohibition">
|
||||||
|
<%t UploadField.REMOVE 'Remove' %></button>
|
@ -41,7 +41,7 @@ class UploadTest extends SapphireTest {
|
|||||||
// test upload into default folder
|
// test upload into default folder
|
||||||
$u1 = new Upload();
|
$u1 = new Upload();
|
||||||
$u1->setValidator($v);
|
$u1->setValidator($v);
|
||||||
$u1->load($tmpFile);
|
$u1->loadIntoFile($tmpFile);
|
||||||
$file1 = $u1->getFile();
|
$file1 = $u1->getFile();
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'Uploads/UploadTest-testUpload.txt',
|
'Uploads/UploadTest-testUpload.txt',
|
||||||
@ -59,7 +59,7 @@ class UploadTest extends SapphireTest {
|
|||||||
// test upload into custom folder
|
// test upload into custom folder
|
||||||
$customFolder = 'UploadTest-testUpload';
|
$customFolder = 'UploadTest-testUpload';
|
||||||
$u2 = new Upload();
|
$u2 = new Upload();
|
||||||
$u2->load($tmpFile, $customFolder);
|
$u2->loadIntoFile($tmpFile, null, $customFolder);
|
||||||
$file2 = $u2->getFile();
|
$file2 = $u2->getFile();
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'UploadTest-testUpload/UploadTest-testUpload.txt',
|
'UploadTest-testUpload/UploadTest-testUpload.txt',
|
||||||
@ -99,17 +99,17 @@ class UploadTest extends SapphireTest {
|
|||||||
|
|
||||||
$v->setAllowedMaxFileSize(array('txt' => 10));
|
$v->setAllowedMaxFileSize(array('txt' => 10));
|
||||||
$u1->setValidator($v);
|
$u1->setValidator($v);
|
||||||
$result = $u1->load($tmpFile);
|
$result = $u1->loadIntoFile($tmpFile);
|
||||||
$this->assertFalse($result, 'Load failed because size was too big');
|
$this->assertFalse($result, 'Load failed because size was too big');
|
||||||
|
|
||||||
$v->setAllowedMaxFileSize(array('[document]' => 10));
|
$v->setAllowedMaxFileSize(array('[document]' => 10));
|
||||||
$u1->setValidator($v);
|
$u1->setValidator($v);
|
||||||
$result = $u1->load($tmpFile);
|
$result = $u1->loadIntoFile($tmpFile);
|
||||||
$this->assertFalse($result, 'Load failed because size was too big');
|
$this->assertFalse($result, 'Load failed because size was too big');
|
||||||
|
|
||||||
$v->setAllowedMaxFileSize(array('txt' => 200000));
|
$v->setAllowedMaxFileSize(array('txt' => 200000));
|
||||||
$u1->setValidator($v);
|
$u1->setValidator($v);
|
||||||
$result = $u1->load($tmpFile);
|
$result = $u1->loadIntoFile($tmpFile);
|
||||||
$this->assertTrue($result, 'Load failed with setting max file size');
|
$this->assertTrue($result, 'Load failed with setting max file size');
|
||||||
|
|
||||||
// check max file size set by app category
|
// check max file size set by app category
|
||||||
@ -128,17 +128,17 @@ class UploadTest extends SapphireTest {
|
|||||||
|
|
||||||
$v->setAllowedMaxFileSize(array('[image]' => '40k'));
|
$v->setAllowedMaxFileSize(array('[image]' => '40k'));
|
||||||
$u1->setValidator($v);
|
$u1->setValidator($v);
|
||||||
$result = $u1->load($tmpFile);
|
$result = $u1->loadIntoFile($tmpFile);
|
||||||
$this->assertTrue($result, 'Load failed with setting max file size');
|
$this->assertTrue($result, 'Load failed with setting max file size');
|
||||||
|
|
||||||
$v->setAllowedMaxFileSize(array('[image]' => '1k'));
|
$v->setAllowedMaxFileSize(array('[image]' => '1k'));
|
||||||
$u1->setValidator($v);
|
$u1->setValidator($v);
|
||||||
$result = $u1->load($tmpFile);
|
$result = $u1->loadIntoFile($tmpFile);
|
||||||
$this->assertFalse($result, 'Load failed because size was too big');
|
$this->assertFalse($result, 'Load failed because size was too big');
|
||||||
|
|
||||||
$v->setAllowedMaxFileSize(array('[image]' => 1000));
|
$v->setAllowedMaxFileSize(array('[image]' => 1000));
|
||||||
$u1->setValidator($v);
|
$u1->setValidator($v);
|
||||||
$result = $u1->load($tmpFile);
|
$result = $u1->loadIntoFile($tmpFile);
|
||||||
$this->assertFalse($result, 'Load failed because size was too big');
|
$this->assertFalse($result, 'Load failed because size was too big');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +213,7 @@ class UploadTest extends SapphireTest {
|
|||||||
// test upload into default folder
|
// test upload into default folder
|
||||||
$u1 = new Upload();
|
$u1 = new Upload();
|
||||||
$u1->setValidator($v);
|
$u1->setValidator($v);
|
||||||
$result = $u1->load($tmpFile);
|
$result = $u1->loadIntoFile($tmpFile);
|
||||||
|
|
||||||
$this->assertFalse($result, 'Load failed because size was too big');
|
$this->assertFalse($result, 'Load failed because size was too big');
|
||||||
}
|
}
|
||||||
@ -242,7 +242,7 @@ class UploadTest extends SapphireTest {
|
|||||||
// test upload into default folder
|
// test upload into default folder
|
||||||
$u = new Upload();
|
$u = new Upload();
|
||||||
$u->setValidator($v);
|
$u->setValidator($v);
|
||||||
$result = $u->load($tmpFile);
|
$result = $u->loadIntoFile($tmpFile);
|
||||||
|
|
||||||
$this->assertFalse($result, 'Load failed because extension was not accepted');
|
$this->assertFalse($result, 'Load failed because extension was not accepted');
|
||||||
}
|
}
|
||||||
@ -271,7 +271,7 @@ class UploadTest extends SapphireTest {
|
|||||||
// test upload into default folder
|
// test upload into default folder
|
||||||
$u = new Upload();
|
$u = new Upload();
|
||||||
$u->setValidator($v);
|
$u->setValidator($v);
|
||||||
$u->load($tmpFile);
|
$u->loadIntoFile($tmpFile);
|
||||||
$file = $u->getFile();
|
$file = $u->getFile();
|
||||||
$this->assertFileExists(
|
$this->assertFileExists(
|
||||||
AssetStoreTest_SpyStore::getLocalPath($file),
|
AssetStoreTest_SpyStore::getLocalPath($file),
|
||||||
@ -302,7 +302,7 @@ class UploadTest extends SapphireTest {
|
|||||||
|
|
||||||
// test upload into default folder
|
// test upload into default folder
|
||||||
$u = new Upload();
|
$u = new Upload();
|
||||||
$result = $u->load($tmpFile);
|
$result = $u->loadIntoFile($tmpFile);
|
||||||
|
|
||||||
$this->assertFalse($result, 'Load failed because extension was not accepted');
|
$this->assertFalse($result, 'Load failed because extension was not accepted');
|
||||||
$this->assertEquals(1, count($u->getErrors()), 'There is a single error of the file extension');
|
$this->assertEquals(1, count($u->getErrors()), 'There is a single error of the file extension');
|
||||||
@ -328,7 +328,7 @@ class UploadTest extends SapphireTest {
|
|||||||
|
|
||||||
// test upload into default folder
|
// test upload into default folder
|
||||||
$u = new Upload();
|
$u = new Upload();
|
||||||
$u->load($tmpFile);
|
$u->loadIntoFile($tmpFile);
|
||||||
$file = $u->getFile();
|
$file = $u->getFile();
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'UploadTest-testUpload.tar.gz',
|
'UploadTest-testUpload.tar.gz',
|
||||||
@ -341,7 +341,7 @@ class UploadTest extends SapphireTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
$u = new Upload();
|
$u = new Upload();
|
||||||
$u->load($tmpFile);
|
$u->loadIntoFile($tmpFile);
|
||||||
$file2 = $u->getFile();
|
$file2 = $u->getFile();
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'UploadTest-testUpload-v2.tar.gz',
|
'UploadTest-testUpload-v2.tar.gz',
|
||||||
@ -359,7 +359,7 @@ class UploadTest extends SapphireTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
$u = new Upload();
|
$u = new Upload();
|
||||||
$u->load($tmpFile);
|
$u->loadIntoFile($tmpFile);
|
||||||
$file3 = $u->getFile();
|
$file3 = $u->getFile();
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'UploadTest-testUpload-v3.tar.gz',
|
'UploadTest-testUpload-v3.tar.gz',
|
||||||
@ -401,7 +401,7 @@ class UploadTest extends SapphireTest {
|
|||||||
// test upload into default folder
|
// test upload into default folder
|
||||||
$u = new Upload();
|
$u = new Upload();
|
||||||
$u->setValidator($v);
|
$u->setValidator($v);
|
||||||
$u->load($tmpFile);
|
$u->loadIntoFile($tmpFile);
|
||||||
$file = $u->getFile();
|
$file = $u->getFile();
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
@ -416,7 +416,7 @@ class UploadTest extends SapphireTest {
|
|||||||
|
|
||||||
$u = new Upload();
|
$u = new Upload();
|
||||||
$u->setValidator($v);
|
$u->setValidator($v);
|
||||||
$u->load($tmpFile);
|
$u->loadIntoFile($tmpFile);
|
||||||
$file2 = $u->getFile();
|
$file2 = $u->getFile();
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'UploadTest-testUpload-v2',
|
'UploadTest-testUpload-v2',
|
||||||
@ -458,7 +458,7 @@ class UploadTest extends SapphireTest {
|
|||||||
// test upload into default folder
|
// test upload into default folder
|
||||||
$u = new Upload();
|
$u = new Upload();
|
||||||
$u->setValidator($v);
|
$u->setValidator($v);
|
||||||
$u->load($tmpFile);
|
$u->loadIntoFile($tmpFile);
|
||||||
$file = $u->getFile();
|
$file = $u->getFile();
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
@ -474,7 +474,7 @@ class UploadTest extends SapphireTest {
|
|||||||
$u = new Upload();
|
$u = new Upload();
|
||||||
$u->setValidator($v);
|
$u->setValidator($v);
|
||||||
$u->setReplaceFile(true);
|
$u->setReplaceFile(true);
|
||||||
$u->load($tmpFile);
|
$u->loadIntoFile($tmpFile);
|
||||||
$file2 = $u->getFile();
|
$file2 = $u->getFile();
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'UploadTest-testUpload',
|
'UploadTest-testUpload',
|
||||||
@ -516,7 +516,7 @@ class UploadTest extends SapphireTest {
|
|||||||
// test upload into default folder
|
// test upload into default folder
|
||||||
$u = new Upload();
|
$u = new Upload();
|
||||||
$u->setValidator($v);
|
$u->setValidator($v);
|
||||||
$u->load($tmpFile);
|
$u->loadIntoFile($tmpFile);
|
||||||
$file = $u->getFile();
|
$file = $u->getFile();
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
@ -595,7 +595,7 @@ class UploadTest extends SapphireTest {
|
|||||||
$u = new Upload();
|
$u = new Upload();
|
||||||
$u->setReplaceFile(true);
|
$u->setReplaceFile(true);
|
||||||
$u->setValidator($v);
|
$u->setValidator($v);
|
||||||
$u->load($tmpFile);
|
$u->loadIntoFile($tmpFile);
|
||||||
return $u->getFile();
|
return $u->getFile();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -637,7 +637,7 @@ class UploadTest extends SapphireTest {
|
|||||||
$u = new Upload();
|
$u = new Upload();
|
||||||
$u->setReplaceFile(false);
|
$u->setReplaceFile(false);
|
||||||
$u->setValidator($v);
|
$u->setValidator($v);
|
||||||
$u->load($tmpFile);
|
$u->loadIntoFile($tmpFile);
|
||||||
return $u->getFile();
|
return $u->getFile();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ class HtmlEditorFieldTest extends FunctionalTest {
|
|||||||
$files = File::get()->exclude('ClassName', 'Folder');
|
$files = File::get()->exclude('ClassName', 'Folder');
|
||||||
foreach($files as $file) {
|
foreach($files as $file) {
|
||||||
$fromPath = BASE_PATH . '/framework/tests/forms/images/' . $file->Name;
|
$fromPath = BASE_PATH . '/framework/tests/forms/images/' . $file->Name;
|
||||||
$destPath = BASE_PATH . $file->getURL(); // Only correct for test asset store
|
$destPath = AssetStoreTest_SpyStore::getLocalPath($file); // Only correct for test asset store
|
||||||
SS_Filesystem::makeFolder(dirname($destPath));
|
SS_Filesystem::makeFolder(dirname($destPath));
|
||||||
copy($fromPath, $destPath);
|
copy($fromPath, $destPath);
|
||||||
}
|
}
|
||||||
|
404
tests/forms/uploadfield/AssetFieldTest.php
Normal file
404
tests/forms/uploadfield/AssetFieldTest.php
Normal file
@ -0,0 +1,404 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package framework
|
||||||
|
* @subpackage tests
|
||||||
|
*/
|
||||||
|
class AssetFieldTest extends FunctionalTest {
|
||||||
|
|
||||||
|
protected static $fixture_file = 'AssetFieldTest.yml';
|
||||||
|
|
||||||
|
protected $extraDataObjects = array(
|
||||||
|
'AssetFieldTest_Object'
|
||||||
|
);
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
// Set backend root to /AssetFieldTest
|
||||||
|
AssetStoreTest_SpyStore::activate('AssetFieldTest');
|
||||||
|
$create = function($path) {
|
||||||
|
Filesystem::makeFolder(dirname($path));
|
||||||
|
$fh = fopen($path, "w+");
|
||||||
|
fwrite($fh, str_repeat('x', 1000000));
|
||||||
|
fclose($fh);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write all DBFile references
|
||||||
|
foreach(AssetFieldTest_Object::get() as $object) {
|
||||||
|
$path = AssetStoreTest_SpyStore::getLocalPath($object->File);
|
||||||
|
$create($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a test files for each of the fixture references
|
||||||
|
$files = File::get()->exclude('ClassName', 'Folder');
|
||||||
|
foreach($files as $file) {
|
||||||
|
$path = AssetStoreTest_SpyStore::getLocalPath($file);
|
||||||
|
$create($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown() {
|
||||||
|
AssetStoreTest_SpyStore::reset();
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that files can be uploaded against an object with no relation
|
||||||
|
*/
|
||||||
|
public function testUploadNoRelation() {
|
||||||
|
$this->loginWithPermission('ADMIN');
|
||||||
|
|
||||||
|
$tmpFileName = 'testUploadBasic.txt';
|
||||||
|
$response = $this->mockFileUpload('NoRelationField', $tmpFileName);
|
||||||
|
$responseJSON = json_decode($response->getBody(), true);
|
||||||
|
$this->assertFalse($response->isError());
|
||||||
|
$this->assertEquals('MyDocuments/testUploadBasic.txt', $responseJSON[0]['filename']);
|
||||||
|
$this->assertEquals('315ae4c3d44412baa0c81515b6fb35829a337a5a', $responseJSON[0]['hash']);
|
||||||
|
$this->assertEmpty($responseJSON[0]['variant']);
|
||||||
|
$this->assertFileExists(
|
||||||
|
BASE_PATH . '/assets/AssetFieldTest/MyDocuments/315ae4c3d4/testUploadBasic.txt'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that an object can be uploaded against a DBFile field
|
||||||
|
*/
|
||||||
|
public function testUploadDBFile() {
|
||||||
|
$this->loginWithPermission('ADMIN');
|
||||||
|
|
||||||
|
// Unset existing has_one relation before re-uploading
|
||||||
|
$record = $this->objFromFixture('AssetFieldTest_Object', 'object1');
|
||||||
|
$record->FileFilename = null;
|
||||||
|
$record->FileHash = null;
|
||||||
|
$record->write();
|
||||||
|
|
||||||
|
// Firstly, ensure the file can be uploaded
|
||||||
|
$tmpFileName = 'testUploadHasOneRelation.txt';
|
||||||
|
$response = $this->mockFileUpload('File', $tmpFileName);
|
||||||
|
$responseJSON = json_decode($response->getBody(), true);
|
||||||
|
$this->assertFalse($response->isError());
|
||||||
|
$this->assertFileExists(
|
||||||
|
BASE_PATH . '/assets/AssetFieldTest/MyFiles/315ae4c3d4/testUploadHasOneRelation.txt'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Secondly, ensure that simply uploading an object does not save the file against the relation
|
||||||
|
$record = AssetFieldTest_Object::get()->byID($record->ID);
|
||||||
|
$this->assertFalse($record->File->exists());
|
||||||
|
|
||||||
|
// Thirdly, test submitting the form with the encoded data
|
||||||
|
$response = $this->mockUploadFileSave(
|
||||||
|
'File',
|
||||||
|
$responseJSON[0]['filename'],
|
||||||
|
$responseJSON[0]['hash'],
|
||||||
|
$responseJSON[0]['variant']
|
||||||
|
);
|
||||||
|
$this->assertEmpty($response['errors']);
|
||||||
|
$record = AssetFieldTest_Object::get()->byID($record->ID);
|
||||||
|
$this->assertTrue($record->File->exists());
|
||||||
|
$this->assertEquals('315ae4c3d44412baa0c81515b6fb35829a337a5a', $record->File->Hash);
|
||||||
|
$this->assertEquals('MyFiles/testUploadHasOneRelation.txt', $record->File->Filename);
|
||||||
|
$this->assertEmpty($record->File->Variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partially covered by {@link UploadTest->testUploadAcceptsAllowedExtension()},
|
||||||
|
* but this test additionally verifies that those constraints are actually enforced
|
||||||
|
* in this controller method.
|
||||||
|
*/
|
||||||
|
public function testAllowedExtensions() {
|
||||||
|
$this->loginWithPermission('ADMIN');
|
||||||
|
|
||||||
|
// Test invalid file
|
||||||
|
// Relies on Upload_Validator failing to allow this extension
|
||||||
|
$response = $this->mockFileUpload('File', 'invalid.php');
|
||||||
|
$response = json_decode($response->getBody(), true);
|
||||||
|
$this->assertTrue(array_key_exists('error', $response[0]));
|
||||||
|
$this->assertContains('Extension is not allowed', $response[0]['error']);
|
||||||
|
|
||||||
|
// Test valid file
|
||||||
|
$response = $this->mockFileUpload('File', 'valid.txt');
|
||||||
|
$response = json_decode($response->getBody(), true);
|
||||||
|
$this->assertFalse(array_key_exists('error', $response[0]));
|
||||||
|
|
||||||
|
// Test that allowed files cannot be uploaded to restricted field
|
||||||
|
$response = $this->mockFileUpload('Image', 'valid.txt');
|
||||||
|
$response = json_decode($response->getBody(), true);
|
||||||
|
$this->assertTrue(array_key_exists('error', $response[0]));
|
||||||
|
$this->assertContains('Extension is not allowed', $response[0]['error']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that files can be removed from an existing field
|
||||||
|
*/
|
||||||
|
public function testRemoveFromHasOne() {
|
||||||
|
$record = $this->objFromFixture('AssetFieldTest_Object', 'object1');
|
||||||
|
|
||||||
|
// Check record exists
|
||||||
|
$this->assertTrue($record->File->exists());
|
||||||
|
$filePath = AssetStoreTest_SpyStore::getLocalPath($record->File);
|
||||||
|
$this->assertFileExists($filePath);
|
||||||
|
|
||||||
|
// Remove from record
|
||||||
|
$response = $this->mockUploadFileSave('File', null, null, null);
|
||||||
|
$this->assertEmpty($response['errors']);
|
||||||
|
|
||||||
|
// Check file is removed
|
||||||
|
$record = AssetFieldTest_Object::get()->byID($record->ID);
|
||||||
|
$this->assertFalse($record->File->exists());
|
||||||
|
|
||||||
|
// Check file object itself exists
|
||||||
|
// @todo - When assets are removed from a DBFile reference, these files should be archived
|
||||||
|
$this->assertFileExists($filePath, 'File is only detached, not deleted from filesystem');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test control output html
|
||||||
|
*/
|
||||||
|
public function testView() {
|
||||||
|
$this->loginWithPermission('ADMIN');
|
||||||
|
|
||||||
|
$record = $this->objFromFixture('AssetFieldTest_Object', 'object1');
|
||||||
|
|
||||||
|
// Requesting form is not an error
|
||||||
|
$response = $this->get('AssetFieldTest_Controller');
|
||||||
|
$this->assertFalse($response->isError());
|
||||||
|
|
||||||
|
// File exists in this response
|
||||||
|
$parser = new CSSContentParser($response->getBody());
|
||||||
|
$tuple = array();
|
||||||
|
$result = $parser->getBySelector(
|
||||||
|
"#AssetFieldTest_Form_Form_File_Holder .ss-uploadfield-files .ss-uploadfield-item input[type='hidden']"
|
||||||
|
);
|
||||||
|
foreach($result as $part) {
|
||||||
|
$name = (string)$part['name'];
|
||||||
|
$value = (string)$part['value'];
|
||||||
|
switch($name) {
|
||||||
|
case 'File[Filename]':
|
||||||
|
$tuple['Filename'] = $value;
|
||||||
|
break;
|
||||||
|
case 'File[Hash]':
|
||||||
|
$tuple['Hash'] = $value;
|
||||||
|
break;
|
||||||
|
case 'File[Variant]':
|
||||||
|
$tuple['Variant'] = $value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert this value is correct
|
||||||
|
$expected = array(
|
||||||
|
'Filename' => 'MyFiles/subfolder1/file-subfolder.txt',
|
||||||
|
'Hash' => '55b443b60176235ef09801153cca4e6da7494a0c',
|
||||||
|
'Variant' => '',
|
||||||
|
);
|
||||||
|
$this->assertEquals($expected, $record->File->getValue());
|
||||||
|
$this->assertEquals($expected, $tuple);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetRecord() {
|
||||||
|
$record = $this->objFromFixture('AssetFieldTest_Object', 'object1');
|
||||||
|
$form = $this->getMockForm();
|
||||||
|
|
||||||
|
$field = AssetField::create('MyField');
|
||||||
|
$field->setForm($form);
|
||||||
|
$this->assertNull($field->getRecord(), 'Returns no record by default');
|
||||||
|
|
||||||
|
$field = AssetField::create('MyField');
|
||||||
|
$field->setForm($form);
|
||||||
|
$form->loadDataFrom($record);
|
||||||
|
$this->assertEquals($record, $field->getRecord(), 'Returns record from form if available');
|
||||||
|
|
||||||
|
$field = AssetField::create('MyField');
|
||||||
|
$field->setForm($form);
|
||||||
|
$field->setRecord($record);
|
||||||
|
$this->assertEquals($record, $field->getRecord(), 'Returns record when set explicitly');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that getValue() / Value() methods work
|
||||||
|
*/
|
||||||
|
public function testValue() {
|
||||||
|
$record = $this->objFromFixture('AssetFieldTest_Object', 'object1');
|
||||||
|
|
||||||
|
// File field
|
||||||
|
$field = AssetField::create('File');
|
||||||
|
$this->assertEmpty($field->Value());
|
||||||
|
$field->setValue(null, $record);
|
||||||
|
$this->assertEquals(array(
|
||||||
|
'Filename' => 'MyFiles/subfolder1/file-subfolder.txt',
|
||||||
|
'Hash' => '55b443b60176235ef09801153cca4e6da7494a0c',
|
||||||
|
'Variant' => null,
|
||||||
|
), $field->Value());
|
||||||
|
|
||||||
|
// Empty field
|
||||||
|
$field = AssetField::create('Image');
|
||||||
|
$this->assertEmpty($field->Value());
|
||||||
|
$field->setValue(null, $record);
|
||||||
|
$this->assertEmpty($field->Value());
|
||||||
|
|
||||||
|
// Set via file (copies only tuple not the actual file reference)
|
||||||
|
$file = $this->objFromFixture('File', 'file1');
|
||||||
|
$field->setValue($file);
|
||||||
|
$this->assertEquals(array(
|
||||||
|
'Filename' => 'MyAssets/file1.txt',
|
||||||
|
'Hash' => '55b443b60176235ef09801153cca4e6da7494a0c',
|
||||||
|
'Variant' => null,
|
||||||
|
), $field->Value());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanUploadWithPermissionCode() {
|
||||||
|
$field = AssetField::create('MyField');
|
||||||
|
|
||||||
|
$field->setCanUpload(true);
|
||||||
|
$this->assertTrue($field->canUpload());
|
||||||
|
|
||||||
|
$field->setCanUpload(false);
|
||||||
|
$this->assertFalse($field->canUpload());
|
||||||
|
|
||||||
|
$field->setCanUpload('ADMIN');
|
||||||
|
$this->assertFalse($field->canUpload());
|
||||||
|
|
||||||
|
$this->loginWithPermission('ADMIN');
|
||||||
|
|
||||||
|
$field->setCanUpload(false);
|
||||||
|
$this->assertFalse($field->canUpload());
|
||||||
|
|
||||||
|
$field->setCanUpload('ADMIN');
|
||||||
|
$this->assertTrue($field->canUpload());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getMockForm() {
|
||||||
|
return new Form(new Controller(), 'Form', new FieldList(), new FieldList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Array Emulating an entry in the $_FILES superglobal
|
||||||
|
*/
|
||||||
|
protected function getUploadFile($tmpFileName = 'AssetFieldTest-testUpload.txt') {
|
||||||
|
$tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName;
|
||||||
|
$tmpFileContent = '';
|
||||||
|
for($i=0; $i<10000; $i++) $tmpFileContent .= '0';
|
||||||
|
file_put_contents($tmpFilePath, $tmpFileContent);
|
||||||
|
|
||||||
|
// emulates the $_FILES array
|
||||||
|
// Notice that unlike UploadFieldTest::getUploadFile the key is 'Upload' not 'Uploads'
|
||||||
|
// and the value is a literal not an array
|
||||||
|
return array(
|
||||||
|
'name' => array('Upload' => $tmpFileName),
|
||||||
|
'type' => array('Upload' => 'text/plaintext'),
|
||||||
|
'size' => array('Upload' => filesize($tmpFilePath)),
|
||||||
|
'tmp_name' => array('Upload' => $tmpFilePath),
|
||||||
|
'error' => array('Upload' => UPLOAD_ERR_OK),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates a form post to the test controller with the specified file tuple (Filename, Hash, Variant)
|
||||||
|
*
|
||||||
|
* @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 mockUploadFileSave($fileField, $filename, $hash, $variant = null) {
|
||||||
|
// collate file ids
|
||||||
|
$data = array(
|
||||||
|
'action_submit' => 1,
|
||||||
|
$fileField => array(
|
||||||
|
'Filename' => $filename,
|
||||||
|
'Hash' => $hash,
|
||||||
|
'Variant' => $variant
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$form = new AssetFieldTest_Form();
|
||||||
|
$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(
|
||||||
|
"AssetFieldTest_Controller/Form/field/{$fileField}/upload",
|
||||||
|
array($fileField => $upload)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssetFieldTest_Object extends DataObject implements TestOnly {
|
||||||
|
private static $db = array(
|
||||||
|
"Title" => "Text",
|
||||||
|
"File" => "DBFile",
|
||||||
|
"Image" => "DBFile('image/supported')"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssetFieldTest_Form extends Form implements TestOnly {
|
||||||
|
|
||||||
|
public function getRecord() {
|
||||||
|
if(empty($this->record)) {
|
||||||
|
$this->record = AssetFieldTest_Object::get()
|
||||||
|
->filter('Title', 'Object1')
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
return $this->record;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __construct($controller = null, $name = 'Form') {
|
||||||
|
if(empty($controller)) {
|
||||||
|
$controller = new AssetFieldTest_Controller();
|
||||||
|
}
|
||||||
|
|
||||||
|
$fields = new FieldList(
|
||||||
|
AssetField::create('File')
|
||||||
|
->setFolderName('MyFiles'),
|
||||||
|
AssetField::create('Image')
|
||||||
|
->setAllowedFileCategories('image/supported')
|
||||||
|
->setFolderName('MyImages'),
|
||||||
|
AssetField::create('NoRelationField')
|
||||||
|
->setFolderName('MyDocuments')
|
||||||
|
);
|
||||||
|
$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 AssetFieldTest_Controller extends Controller implements TestOnly {
|
||||||
|
|
||||||
|
protected $template = 'BlankPage';
|
||||||
|
|
||||||
|
private static $allowed_actions = array('Form');
|
||||||
|
|
||||||
|
public function Form() {
|
||||||
|
return new AssetFieldTest_Form($this, 'Form');
|
||||||
|
}
|
||||||
|
}
|
17
tests/forms/uploadfield/AssetFieldTest.yml
Normal file
17
tests/forms/uploadfield/AssetFieldTest.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
Folder:
|
||||||
|
folder1:
|
||||||
|
Name: MyAssets
|
||||||
|
|
||||||
|
File:
|
||||||
|
file1:
|
||||||
|
Title: File1
|
||||||
|
FileFilename: MyAssets/file1.txt
|
||||||
|
FileHash: 55b443b60176235ef09801153cca4e6da7494a0c
|
||||||
|
Name: file1.txt
|
||||||
|
Parent: =>Folder.folder1
|
||||||
|
|
||||||
|
AssetFieldTest_Object:
|
||||||
|
object1:
|
||||||
|
Title: 'Object1'
|
||||||
|
FileFilename: MyFiles/subfolder1/file-subfolder.txt
|
||||||
|
FileHash: 55b443b60176235ef09801153cca4e6da7494a0c
|
@ -26,7 +26,7 @@ class DataDifferencerTest extends SapphireTest {
|
|||||||
$files = File::get()->exclude('ClassName', 'Folder');
|
$files = File::get()->exclude('ClassName', 'Folder');
|
||||||
foreach($files as $file) {
|
foreach($files as $file) {
|
||||||
$fromPath = BASE_PATH . '/framework/tests/model/testimages/' . $file->Name;
|
$fromPath = BASE_PATH . '/framework/tests/model/testimages/' . $file->Name;
|
||||||
$destPath = BASE_PATH . $file->getURL(); // Only correct for test asset store
|
$destPath = AssetStoreTest_SpyStore::getLocalPath($file); // Only correct for test asset store
|
||||||
SS_Filesystem::makeFolder(dirname($destPath));
|
SS_Filesystem::makeFolder(dirname($destPath));
|
||||||
copy($fromPath, $destPath);
|
copy($fromPath, $destPath);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ class ImageTest extends SapphireTest {
|
|||||||
// Copy test images for each of the fixture references
|
// Copy test images for each of the fixture references
|
||||||
$files = File::get()->exclude('ClassName', 'Folder');
|
$files = File::get()->exclude('ClassName', 'Folder');
|
||||||
foreach($files as $image) {
|
foreach($files as $image) {
|
||||||
$filePath = BASE_PATH . $image->getURL(); // Only correct for test asset store
|
$filePath = AssetStoreTest_SpyStore::getLocalPath($image); // Only correct for test asset store
|
||||||
$sourcePath = BASE_PATH . '/framework/tests/model/testimages/' . $image->Name;
|
$sourcePath = BASE_PATH . '/framework/tests/model/testimages/' . $image->Name;
|
||||||
if(!file_exists($filePath)) {
|
if(!file_exists($filePath)) {
|
||||||
SS_Filesystem::makeFolder(dirname($filePath));
|
SS_Filesystem::makeFolder(dirname($filePath));
|
||||||
|
@ -1392,9 +1392,6 @@ class Requirements_Backend {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if($combinedURL && !$included) {
|
|
||||||
throw new Exception("Failed to merge combined file {$combinedFile} with existing requirements");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user