From f6ab5a4020cfbb80d801099a5d23969065659aaf Mon Sep 17 00:00:00 2001 From: drzax Date: Wed, 9 Jan 2013 10:41:40 +1000 Subject: [PATCH 01/11] Update docs/en/topics/i18n.md Make the documentation more clear and 3.x specific. Especially around template translations. --- docs/en/topics/i18n.md | 115 ++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 76 deletions(-) diff --git a/docs/en/topics/i18n.md b/docs/en/topics/i18n.md index 56a60d13f..b321c3822 100644 --- a/docs/en/topics/i18n.md +++ b/docs/en/topics/i18n.md @@ -79,7 +79,7 @@ not PHP's built-in [date()](http://nz.php.net/manual/en/function.date.php). By default, URLs for pages in SilverStripe (the `SiteTree->URLSegment` property) are automatically reduced to the allowed allowed subset of ASCII characters. -If characters outside this subsetare added, they are either removed or (if possible) "transliterated". +If characters outside this subset are added, they are either removed or (if possible) "transliterated". This describes the process of converting from one character set to another while keeping characters recognizeable. For example, vowels with french accents are replaced with their base characters, `pâté` becomes `pate`. @@ -103,7 +103,7 @@ Defaults can be applied globally for all field instances through [api:DateField: and [api:TimeField::set_default_config()]. If no 'locale' default is set on the field, [api:i18n::get_locale()] will be used. -Important: Form fields in the CMS are automatically configured according to the profile settings for the logged-in user (`Member->Locale`, `Member->DateFormat` and `Member->TimeFormat`). This means that in most cases, +**Important:** Form fields in the CMS are automatically configured according to the profile settings for the logged-in user (`Member->Locale`, `Member->DateFormat` and `Member->TimeFormat`). This means that in most cases, fields created through [api:DataObject::getCMSFields()] will get their i18n settings from a specific member The [api:DateField] API can be enhanced by JavaScript, and comes with @@ -130,12 +130,12 @@ language-dependent and use a translator function call instead. echo _t("Namespace.Entity","This is a string"); -All strings passed through the `_t()` function will be collected in a separate language table (see "Collecting entities" -below), which is the starting point for translations. +All strings passed through the `_t()` function will be collected in a separate language table (see [Collecting text](#collecting-text)), which is the starting point for translations. ### The _t() function -The `_t()` function is the main gateway to localized text, and takes four parameters, all but the first being optional. +The `_t()` function is the main gateway to localized text, and takes four parameters, all but the first being optional. +It can be used to translate strings in both PHP files and template files. The usage for each case is described below. * **$entity:** Unique identifier, composed by a namespace and an entity name, with a dot separating them. Both are arbitrary names, although by convention we use the name of the containing class or template. Use this identifier to reference the same translation elsewhere in your code. * **$string:** (optional) The original language string to be translated. Only needs to be declared once, and gets picked up the [text collector](#collecting-text). @@ -144,63 +144,45 @@ are very context dependent. This parameter allows the developer to convey this i to the translator. * **$array::** (optional) An array of injecting variables into the second parameter +#### Usage in PHP Files + :::php - //Example 4: Using context to hint information about a parameter + + // Simple string translation + _t('LeftAndMain.FILESIMAGES','Files & Images'); + + // Using the natural languate comment parameter to supply additional context information to translators + _t('LeftAndMain.HELLO','Site content','Menu title'); + + // Using injection to add variables into the translated strings. _t('CMSMain.RESTORED', "Restored {value} successfully", 'This is a message when restoring a broken part of the CMS', array('value' => $itemRestored) ); -### Usage +#### Usage in Template Files -There're two types of files in a module where you can use this _t() function: code files (under code folder) and -template files (under templates) +
+The preferred template syntax has changed somewhat since [version 2.x](http://doc.silverstripe.org/framework/en/2.4/topics/i18n#usage-2). +
-* In code files, in order to ask for a translated string, we have to write a normal php call to this function. +In `.ss` template files, instead of `_t(params)` the syntax `<%t params %>` is used. The syntax for passing parameters to the function is quite different to +the PHP version of the function. -Example: - - :::php - _t('LeftAndMain.HELLO','Site content',PR_HIGH,'Menu title'); - _t('LeftAndMain.FILESIMAGES','Files & Images',PR_HIGH); - _t('LeftAndMain.NEWSLETTERS','Newsletters'); - - -* In template files these calls are written slightly different to ease readibility, diminish overhead and allow a -cleaner template file. Calls can be placed anywhere, but they are preceded and followed by `<% and %>` as usual in the -SilverStripe templating language, and the first parameter is omitted (namespace in template files is always the file -itself). - -Therefore, the following would be a valid use in templates: + * Parameters are space separated, not comma separated + * The original language string and the natural language comment parameters are separated by ` on `. + * The final parameter (which is an array in PHP) is passed as a space separated list of key/value pairs. :::ss - - - -Using SS templating variables in the translatable string (e.g. $Author, $Date..) is not currently supported. - -### Injection Support - -Variable injection in `_t()` allows us to dynamically replace parts of a translated string, e.g. by a username or a page-title. The named parameters also allow flexible ordering of placeholders, -which might vary depending on the used language. - - :::php - // in PHP-file - _t( - 'CMSMain.RESTORED', - "Restored {title} successfully"), - array('title' => $title) - ); - - :::php - // in SS-template ($Name must be available in the current template-scope) - <%t MYPROJECT.INJECTIONS "Hello {name} {greeting}" name=$Name greeting="good to see you" %> - -Note that you can still use `sprintf()` wrapped around a `_t()` call -for your substitutions. In contrast to `sprintf()`, our API has a more translator friendly -placeholder syntax, as well as more graceful fallback if not all placeholders are found -(an outdated translation with less placeholders will throw a notice rather than a fatal error). + // Simple string translation + <%t Namespace.Entity "String to translate" %> + + // Using the natural languate comment parameter to supply additional context information to translators + <%t SearchResults.NoResult "There are no results matching your query." is "A message displayed to users when the search produces no results." %> + + // Using injection to add variables into the translated strings (note that $Name and $Greeting must be available in the current template scope). + <%t Header.Greeting "Hello {name} {greeting}" name=$Name greeting=$Greeting %> ## Collecting text @@ -215,11 +197,11 @@ If you want to run the text collector for just one module you can use the 'modul `http:///dev/tasks/i18nTextCollectorTask/?module=cms` -
-**Note**: You'll need to install PHPUnit to run the text collector (see [testing-guide](/topics/testing)). +
+You'll need to install PHPUnit to run the text collector (see [testing-guide](/topics/testing)).
-## Language definitions (3.x) +## Language definitions Each module can have one language table per locale, stored by convention in the `lang/` subfolder. The translation is powered by `[Zend_Translate](http://framework.zend.com/manual/en/zend.translate.html)`, @@ -247,30 +229,11 @@ Note that translations are cached across requests. The cache can be cleared through the `?flush=1` query parameter, or explicitly through `Zend_Translate::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL)`. -## Language definitions (2.x) +
+The format of language definitions has changed significantly in since version 2.x. +
-In SilverStripe 2.x, the tables are just PHP files with array notations, -stored based on their locale name (e.g. "en_US.php"). - -Example: framework/lang/en_US.php (extract) - - :::php - // ... - $lang['en_US']['ImageUploader']['ATTACH'] = array( - 'Attach %s', - 'Attach image/file' - ); - $lang['en_US']['UploadField']['NOTEADDFILES'] = 'You can add files once you have saved for the first time.'; - // ... - - -Translation table: framework/lang/de_DE.php (extract) - - :::php - $lang['de_DE']['ImageUploader']['ATTACH'] = '%s anhängen'; - $lang['de_DE']['UploadField']['NOTEADDFILES'] = 'Sie können Dateien hinzufügen sobald Sie das erste mal gespeichert haben'; - -In order to enable usage of PHP language definitions in 3.x, you need to register a legacy adapter +In order to enable usage of [version 2.x style language definitions](http://doc.silverstripe.org/framework/en/2.4/topics/i18n#language-tables-in-php) in 3.x, you need to register a legacy adapter in your `mysite/_config.php`: :::php From e8bfc241fdc5ff957646fe2ff2a2b05a243ad96e Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Wed, 9 Jan 2013 23:31:10 +0100 Subject: [PATCH 02/11] Setting SapphireTest::is_running_test() in PHPUnit bootstrap Otherwise conditional logic will only succeed when run through "sake dev/tests", not when run through phpunit directly (which is the recommended way now) --- dev/SapphireTest.php | 3 +++ tests/bootstrap.php | 2 ++ 2 files changed, 5 insertions(+) diff --git a/dev/SapphireTest.php b/dev/SapphireTest.php index 5e5d75a77..efeb8f842 100644 --- a/dev/SapphireTest.php +++ b/dev/SapphireTest.php @@ -131,6 +131,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase { return self::$is_running_test; } + public static function set_is_running_test($bool) { + self::$is_running_test = $bool; + } /** * Set the manifest to be used to look up test classes by helper functions diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 8e4da445d..bd02b154e 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -61,6 +61,8 @@ $controller = new FakeController(); // Get test manifest TestRunner::use_test_manifest(); +SapphireTest::set_is_running_test(true); + // Remove the error handler so that PHPUnit can add its own restore_error_handler(); From f922321287f6e2cc453a17915bb8dc1637c18413 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 10 Jan 2013 22:21:08 +0100 Subject: [PATCH 03/11] ManyManyList->getExtraFields() Needed for introspection for many_many relationships without knowing the name of the relationship, meaning we can't use DataObject->many_many_extraFields(). --- model/ManyManyList.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/model/ManyManyList.php b/model/ManyManyList.php index 1cfb5069c..27c6a2a62 100644 --- a/model/ManyManyList.php +++ b/model/ManyManyList.php @@ -189,4 +189,12 @@ class ManyManyList extends RelationList { return $result; } + + /** + * @return Array Map of field => fieldtype + */ + function getExtraFields() { + return $this->extraFields; + } + } From 2dfd42795e4c19f62d500b457e32b92e5cfcb76c Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Wed, 9 Jan 2013 11:34:02 +0100 Subject: [PATCH 04/11] NEW Restrict upload abilities in UploadField Conflicts: css/UploadField.css templates/UploadField.ss --- css/UploadField.css | 4 +- forms/UploadField.php | 14 ++++++- scss/UploadField.scss | 5 ++- templates/UploadField.ss | 4 ++ tests/forms/uploadfield/UploadFieldTest.php | 46 ++++++++++++++++++++- 5 files changed, 66 insertions(+), 7 deletions(-) diff --git a/css/UploadField.css b/css/UploadField.css index 41c2dfea0..c10480703 100644 --- a/css/UploadField.css +++ b/css/UploadField.css @@ -10,8 +10,8 @@ .ss-uploadfield .middleColumn { width: 526px; padding: 0; background: #fff; border: 1px solid #b3b3b3; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #efefef), color-stop(10%, #ffffff), color-stop(90%, #ffffff), color-stop(100%, #efefef)); background-image: -webkit-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -moz-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -o-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); } .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: #9a9a9a 0 0 3px 3px inset; -moz-box-shadow: #9a9a9a 0 0 3px 3px inset; box-shadow: #9a9a9a 0 0 3px 3px inset; border: 2px dashed gray; background: rgba(201, 205, 206, 0.8); display: none; } -.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info { margin: 0 0 0 100px; } +.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-preview.ss-uploadfield-dropzone { -webkit-box-shadow: #9a9a9a 0 0 3px 3px inset; -moz-box-shadow: #9a9a9a 0 0 3px 3px inset; box-shadow: #9a9a9a 0 0 3px 3px inset; border: 2px dashed gray; background: rgba(201, 205, 206, 0.8); display: none; margin-right: 15px; } +.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info { float: left; } .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; } diff --git a/forms/UploadField.php b/forms/UploadField.php index 033d2f956..4678b7959 100644 --- a/forms/UploadField.php +++ b/forms/UploadField.php @@ -81,6 +81,10 @@ class UploadField extends FileField { * @var int */ 'allowedMaxFileNumber' => null, + /** + * @var boolean Can the user upload new files, or just select from existing files. + */ + 'canUpload' => true, /** * @var int */ @@ -441,7 +445,9 @@ class UploadField extends FileField { * @return string json */ public function upload(SS_HTTPRequest $request) { - if($this->isDisabled() || $this->isReadonly()) return $this->httpError(403); + if($this->isDisabled() || $this->isReadonly() || !$this->canUpload()) { + return $this->httpError(403); + } // Protect against CSRF on destructive action $token = $this->getForm()->getSecurityToken(); @@ -629,6 +635,12 @@ class UploadField extends FileField { // 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); + } + } /** diff --git a/scss/UploadField.scss b/scss/UploadField.scss index 20590deb2..b600a0682 100644 --- a/scss/UploadField.scss +++ b/scss/UploadField.scss @@ -47,11 +47,12 @@ border: 2px dashed $color-medium-separator; background: $color-light-separator; display: none; + margin-right: 15px; } } .ss-uploadfield-item-info { - margin: 0 0 0 100px; - + float: left; + .ss-uploadfield-item-name { display: block; line-height: 13px; diff --git a/templates/UploadField.ss b/templates/UploadField.ss index 6a10370cd..1606d1bfd 100644 --- a/templates/UploadField.ss +++ b/templates/UploadField.ss @@ -34,6 +34,7 @@ <% end_if %> <% else %>
style="display: none;"<% end_if %>> + <% if canUpload %>
<% if $multiple %> <% _t('UploadField.DROPFILES', 'drop files') %> @@ -41,6 +42,7 @@ <% _t('UploadField.DROPFILE', 'drop a file') %> <% end_if %>
+ <% end_if %>
+ <% if canUpload %> + <% end_if %> <% if not $autoUpload %> diff --git a/tests/forms/uploadfield/UploadFieldTest.php b/tests/forms/uploadfield/UploadFieldTest.php index 2ceb16b17..8831e6206 100644 --- a/tests/forms/uploadfield/UploadFieldTest.php +++ b/tests/forms/uploadfield/UploadFieldTest.php @@ -476,6 +476,42 @@ class UploadFieldTest extends FunctionalTest { } + public function testCanUpload() { + $this->loginWithPermission('ADMIN'); + $response = $this->get('UploadFieldTest_Controller'); + $this->assertFalse($response->isError()); + + $parser = new CSSContentParser($response->getBody()); + $this->assertFalse( + (bool)$parser->getBySelector('#CanUploadFalseField .ss-uploadfield-fromcomputer-fileinput'), + 'Removes input file control' + ); + $this->assertFalse((bool)$parser->getBySelector('#CanUploadFalseField .ss-uploadfield-dropzone'), + 'Removes dropzone'); + $this->assertTrue( + (bool)$parser->getBySelector('#CanUploadFalseField .ss-uploadfield-fromfiles'), + 'Keeps "From files" button' + ); + } + + public function testCanUploadWithPermissionCode() { + $field = new UploadField('MyField'); + + $field->setConfig('canUpload', true); + $this->assertTrue($field->canUpload()); + + $field->setConfig('canUpload', false); + $this->assertFalse($field->canUpload()); + + $this->loginWithPermission('ADMIN'); + + $field->setConfig('canUpload', false); + $this->assertFalse($field->canUpload()); + + $field->setConfig('canUpload', 'ADMIN'); + $this->assertTrue($field->canUpload()); + } + public function testIsSaveable() { $form = $this->getMockForm(); @@ -775,6 +811,10 @@ class UploadFieldTest_Controller extends Controller implements TestOnly { $fieldSubfolder->setFolderName('UploadFieldTest/subfolder1'); $fieldSubfolder->setRecord($record); + $fieldCanUploadFalse = new UploadField('CanUploadFalseField'); + $fieldCanUploadFalse->setConfig('canUpload', false); + $fieldCanUploadFalse->setRecord($record); + $form = new Form( $this, 'Form', @@ -789,7 +829,8 @@ class UploadFieldTest_Controller extends Controller implements TestOnly { $fieldManyMany, $fieldReadonly, $fieldDisabled, - $fieldSubfolder + $fieldSubfolder, + $fieldCanUploadFalse ), new FieldList( new FormAction('submit') @@ -805,7 +846,8 @@ class UploadFieldTest_Controller extends Controller implements TestOnly { 'ManyManyFiles', 'ReadonlyField', 'DisabledField', - 'SubfolderField' + 'SubfolderField', + 'CanUploadFalseField' ) ); return $form; From 67c5db33202d75239b731c28ceef1054985d20a9 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Wed, 9 Jan 2013 13:28:12 +0100 Subject: [PATCH 05/11] NEW Global default config for UploadField --- _config/uploadfield.yml | 14 ++++++++++ docs/en/reference/uploadfield.md | 47 ++++++++++++++++++++++++++++---- forms/UploadField.php | 13 ++++++--- 3 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 _config/uploadfield.yml diff --git a/_config/uploadfield.yml b/_config/uploadfield.yml new file mode 100644 index 000000000..6227f3e50 --- /dev/null +++ b/_config/uploadfield.yml @@ -0,0 +1,14 @@ +name: uploadfield +--- +UploadField: + defaultConfig: + autoUpload: true + allowedMaxFileNumber: + canUpload: true + previewMaxWidth: 80 + previewMaxHeight: 60 + uploadTemplateName: 'ss-uploadfield-uploadtemplate' + downloadTemplateName: 'ss-uploadfield-downloadtemplate' + fileEditFields: + fileEditActions: + fileEditValidator: \ No newline at end of file diff --git a/docs/en/reference/uploadfield.md b/docs/en/reference/uploadfield.md index 37dbbfac9..1d8db946f 100644 --- a/docs/en/reference/uploadfield.md +++ b/docs/en/reference/uploadfield.md @@ -9,7 +9,9 @@ as well. That makes it flexible enough to sometimes even replace the Gridfield, like for instance in creating and managing a simple gallery. ## Usage -The UploadField can be used in two ways: + +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). ### Single fileupload @@ -76,7 +78,23 @@ UploadField will detect the relation based on its $name property value: WARNING: Currently the UploadField doesn't fully support has_many relations, so use a many_many relation instead! -## Set a custom folder +## Configuration + +### Overview + +The field can either be configured on an instance level through `setConfig(, )`, +or globally by overriding the YAML defaults. + +Example: mysite/_config/uploadfield.yml + + after: framework#uploadfield + --- + UploadField: + defaultConfig: + canUpload: false + + +### Set a custom folder This example will save all uploads in the `/assets/customfolder/` folder. If the folder doesn't exist, it will be created. @@ -98,7 +116,7 @@ the folder doesn't exist, it will be created. $uploadField->getValidator()->setAllowedExtensions(array('jpg', 'jpeg', 'png', 'gif')); -## Limit the maximum file size +### 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. @@ -110,8 +128,6 @@ NOTE: this only sets the configuration for your UploadField, this does NOT chang $size = $sizeMB * 1024 * 1024; // 2 MB in bytes $this->getValidator()->setAllowedMaxFileSize($size); -## Other configuration settings - ### Preview dimensions Set the dimensions of the image preview. By default the max width is set to 80 @@ -182,6 +198,27 @@ Then, in your GalleryPage, tell the UploadField to use this function: In a similar fashion you can use 'fileEditActions' 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 + 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. + 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 + uploaded files, see javascript/UploadField_downloadtemplate.js + - `fileEditFields`: (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 + (of a method on File to provide a actions) for the EditForm (Example: 'getCMSActions') + - `fileEditValidator`: (string) Validator (eg RequiredFields) or string $name + (of a method on File to provide a Validator) for the EditForm (Example: 'getCMSValidator') ## TODO: Using the UploadField in a frontend form diff --git a/forms/UploadField.php b/forms/UploadField.php index 4678b7959..82588ca33 100644 --- a/forms/UploadField.php +++ b/forms/UploadField.php @@ -13,6 +13,8 @@ * - Edit file * - allowedExtensions is by default File::$allowed_extensions
  • maxFileSize the value of min(upload_max_filesize, * post_max_size) from php.ini + * + * <>Usage * * @example * $UploadField = new UploadField('myFiles', 'Please upload some images (max. 5 files)'); @@ -66,9 +68,9 @@ class UploadField extends FileField { protected $items; /** - * Config for this field used in both, php and javascript (will be merged into the config of the javascript file - * upload plugin) - * @var array + * @var array Config for this field used in both, php and javascript + * (will be merged into the config of the javascript file upload plugin). + * See framework/_config/uploadfield.yml for configuration defaults and documentation. */ protected $ufConfig = array( /** @@ -82,7 +84,8 @@ class UploadField extends FileField { */ 'allowedMaxFileNumber' => null, /** - * @var boolean Can the user upload new files, or just select from existing files. + * @var boolean|string Can the user upload new files, or just select from existing files. + * String values are interpreted as permission codes. */ 'canUpload' => true, /** @@ -137,6 +140,8 @@ class UploadField extends FileField { $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')); + parent::__construct($name, $title); if($items) $this->setItems($items); From 4da1af9c3f3b557465a513bfc11582577894bb1d Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 11 Jan 2013 00:33:31 +0100 Subject: [PATCH 06/11] Show label-less input#file field in UploadField with canUpload=false The field carries the configuration, and some non-upload functionality like "attach files" still relies on the fileupload jQuery plugin being initialized. Conflicts: templates/UploadField.ss --- templates/UploadField.ss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/UploadField.ss b/templates/UploadField.ss index 1606d1bfd..3481472f9 100644 --- a/templates/UploadField.ss +++ b/templates/UploadField.ss @@ -56,6 +56,8 @@ <% _t('UploadField.FROMCOMPUTER', 'From your computer') %> multiple="multiple"<% end_if %> /> + <% else %> + multiple="multiple"<% end_if %> /> <% end_if %> <% if not $autoUpload %> From 2fdd9a3b13c66f6f9a8a4ba349ec225325fa0558 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Fri, 11 Jan 2013 16:51:52 +1300 Subject: [PATCH 07/11] FIX: Allow images attached to UploadFields to be unlinked without File::canEdit() or File::canDelete() permission. Although editing meta-data or deleting permanently would require File editing/deleting permissions, merely linking to a record does not. This change is important for allowing front-end use of UploadField; or, more importantly, use of UploadFile by people without CMS rights. --- forms/UploadField.php | 1 - templates/Includes/UploadField_FileButtons.ss | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/forms/UploadField.php b/forms/UploadField.php index 82588ca33..40523ca26 100644 --- a/forms/UploadField.php +++ b/forms/UploadField.php @@ -867,7 +867,6 @@ class UploadField_ItemHandler extends RequestHandler { // Check item permissions $item = $this->getItem(); 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(); diff --git a/templates/Includes/UploadField_FileButtons.ss b/templates/Includes/UploadField_FileButtons.ss index 1f0f6242c..6fbb0e185 100644 --- a/templates/Includes/UploadField_FileButtons.ss +++ b/templates/Includes/UploadField_FileButtons.ss @@ -4,13 +4,12 @@ - - <% if UploadFieldHasRelation %> +<% end_if %> +<% if UploadFieldHasRelation %> <% end_if %> -<% end_if %> <% if canDelete %> <% end_if %> From cc7318fde49a4153694af324b2c3f88d78f6913c Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Fri, 11 Jan 2013 17:33:06 +1300 Subject: [PATCH 08/11] NEW: Added canAttachExisting config option for UploadField. This is the companion setting to canUpload, letting you control whether existing files from the asset store can be referenced. It's particularly useful when using UploadField on the front-end. --- _config/uploadfield.yml | 1 + forms/UploadField.php | 10 ++++++++ templates/UploadField.ss | 3 +++ tests/forms/uploadfield/UploadFieldTest.php | 26 +++++++++++++++++++-- 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/_config/uploadfield.yml b/_config/uploadfield.yml index 6227f3e50..4639af322 100644 --- a/_config/uploadfield.yml +++ b/_config/uploadfield.yml @@ -5,6 +5,7 @@ UploadField: autoUpload: true allowedMaxFileNumber: canUpload: true + canAttachExisting: 'CMS_ACCESS_AssetAdmin' previewMaxWidth: 80 previewMaxHeight: 60 uploadTemplateName: 'ss-uploadfield-uploadtemplate' diff --git a/forms/UploadField.php b/forms/UploadField.php index 40523ca26..a72bc3883 100644 --- a/forms/UploadField.php +++ b/forms/UploadField.php @@ -89,6 +89,10 @@ class UploadField extends FileField { */ 'canUpload' => true, /** + * @var boolean|string Can the user attach files from the assets archive on the site? + * String values are interpreted as permission codes. + */ + 'canAttachExisting' => "CMS_ACCESS_AssetAdmin", * @var int */ 'previewMaxWidth' => 80, @@ -553,6 +557,7 @@ class UploadField extends FileField { public function attach($request) { if(!$request->isPOST()) return $this->httpError(403); if(!$this->managesRelation()) return $this->httpError(403); + if(!$this->canAttachExisting()) return $this->httpError(403); $return = array(); @@ -646,6 +651,11 @@ class UploadField extends FileField { return (is_bool($can)) ? $can : Permission::check($can); } + public function canAttachExisting() { + $can = $this->getConfig('canAttachExisting'); + return (is_bool($can)) ? $can : Permission::check($can); + } + } /** diff --git a/templates/UploadField.ss b/templates/UploadField.ss index 3481472f9..d3b8a817a 100644 --- a/templates/UploadField.ss +++ b/templates/UploadField.ss @@ -59,7 +59,10 @@ <% else %> multiple="multiple"<% end_if %> /> <% end_if %> + + <% if canAttachExisting %> + <% end_if %> <% if not $autoUpload %> <% end_if %> diff --git a/tests/forms/uploadfield/UploadFieldTest.php b/tests/forms/uploadfield/UploadFieldTest.php index 8831e6206..e1d7cacdb 100644 --- a/tests/forms/uploadfield/UploadFieldTest.php +++ b/tests/forms/uploadfield/UploadFieldTest.php @@ -512,6 +512,22 @@ class UploadFieldTest extends FunctionalTest { $this->assertTrue($field->canUpload()); } + public function testCanAttachExisting() { + $this->loginWithPermission('ADMIN'); + $response = $this->get('UploadFieldTest_Controller'); + $this->assertFalse($response->isError()); + + $parser = new CSSContentParser($response->getBody()); + $this->assertTrue( + (bool)$parser->getBySelector('#CanAttachExistingFalseField .ss-uploadfield-fromcomputer-fileinput'), + 'Keeps input file control' + ); + $this->assertFalse( + (bool)$parser->getBySelector('#CanAttachExistingFalseField .ss-uploadfield-fromfiles'), + 'Removes "From files" button' + ); + } + public function testIsSaveable() { $form = $this->getMockForm(); @@ -815,6 +831,10 @@ class UploadFieldTest_Controller extends Controller implements TestOnly { $fieldCanUploadFalse->setConfig('canUpload', false); $fieldCanUploadFalse->setRecord($record); + $fieldCanAttachExisting = new UploadField('CanAttachExistingFalseField'); + $fieldCanAttachExisting->setConfig('canAttachExisting', false); + $fieldCanAttachExisting->setRecord($record); + $form = new Form( $this, 'Form', @@ -830,7 +850,8 @@ class UploadFieldTest_Controller extends Controller implements TestOnly { $fieldReadonly, $fieldDisabled, $fieldSubfolder, - $fieldCanUploadFalse + $fieldCanUploadFalse, + $fieldCanAttachExisting ), new FieldList( new FormAction('submit') @@ -847,7 +868,8 @@ class UploadFieldTest_Controller extends Controller implements TestOnly { 'ReadonlyField', 'DisabledField', 'SubfolderField', - 'CanUploadFalseField' + 'CanUploadFalseField', + 'CanAttachExistingField' ) ); return $form; From 5b450f7feaf4d2ca90dee5fda504479e288789cd Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Fri, 11 Jan 2013 17:34:27 +1300 Subject: [PATCH 09/11] NEW: Added replaceExistingFile setting for UploadField. Sometimes has-one UploadFields can get confused about whether or not there is an existing file that needs deleting. This setting lets you make a more robust has-one UploadField, where any existing file will be replaced. It more closely mimics simple single-file upload fields. --- _config/uploadfield.yml | 1 + forms/UploadField.php | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/_config/uploadfield.yml b/_config/uploadfield.yml index 4639af322..ccd35fbf8 100644 --- a/_config/uploadfield.yml +++ b/_config/uploadfield.yml @@ -6,6 +6,7 @@ UploadField: allowedMaxFileNumber: canUpload: true canAttachExisting: 'CMS_ACCESS_AssetAdmin' + replaceExistingFile: false previewMaxWidth: 80 previewMaxHeight: 60 uploadTemplateName: 'ss-uploadfield-uploadtemplate' diff --git a/forms/UploadField.php b/forms/UploadField.php index a72bc3883..2766b028f 100644 --- a/forms/UploadField.php +++ b/forms/UploadField.php @@ -93,6 +93,13 @@ class UploadField extends FileField { * String values are interpreted as permission codes. */ 'canAttachExisting' => "CMS_ACCESS_AssetAdmin", + /** + * @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 */ 'previewMaxWidth' => 80, @@ -487,6 +494,10 @@ class UploadField extends FileField { $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(); } From f8758bad6a4b33bb5133613f5581838d123618e8 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Fri, 11 Jan 2013 17:36:25 +1300 Subject: [PATCH 10/11] FIX: Fixed margins so that margin is displayed between preview images and their title. Previously the margin was erroneously only shown for a drop zone. It's possible that this bug only affects single-image uploads. --- css/UploadField.css | 4 ++-- scss/UploadField.scss | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/css/UploadField.css b/css/UploadField.css index c10480703..367761248 100644 --- a/css/UploadField.css +++ b/css/UploadField.css @@ -10,8 +10,8 @@ .ss-uploadfield .middleColumn { width: 526px; padding: 0; background: #fff; border: 1px solid #b3b3b3; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #efefef), color-stop(10%, #ffffff), color-stop(90%, #ffffff), color-stop(100%, #efefef)); background-image: -webkit-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -moz-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -o-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); } .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: #9a9a9a 0 0 3px 3px inset; -moz-box-shadow: #9a9a9a 0 0 3px 3px inset; box-shadow: #9a9a9a 0 0 3px 3px inset; border: 2px dashed gray; background: rgba(201, 205, 206, 0.8); display: none; margin-right: 15px; } -.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info { float: left; } +.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-preview.ss-uploadfield-dropzone { -webkit-box-shadow: #9a9a9a 0 0 3px 3px inset; -moz-box-shadow: #9a9a9a 0 0 3px 3px inset; box-shadow: #9a9a9a 0 0 3px 3px inset; border: 2px dashed gray; background: rgba(201, 205, 206, 0.8); display: none; } +.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info { float: left; margin-left: 15px; } .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; } diff --git a/scss/UploadField.scss b/scss/UploadField.scss index b600a0682..b6c7feac9 100644 --- a/scss/UploadField.scss +++ b/scss/UploadField.scss @@ -47,11 +47,11 @@ border: 2px dashed $color-medium-separator; background: $color-light-separator; display: none; - margin-right: 15px; } } .ss-uploadfield-item-info { float: left; + margin-left: 15px; .ss-uploadfield-item-name { display: block; From 212c4f1e51721280c13c975c84685bd71fd8f47d Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 11 Jan 2013 09:30:21 +0100 Subject: [PATCH 11/11] Fixed UploadField regression from 4da1af9c3 --- tests/forms/uploadfield/UploadFieldTest.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/forms/uploadfield/UploadFieldTest.php b/tests/forms/uploadfield/UploadFieldTest.php index e1d7cacdb..9543414ab 100644 --- a/tests/forms/uploadfield/UploadFieldTest.php +++ b/tests/forms/uploadfield/UploadFieldTest.php @@ -482,10 +482,6 @@ class UploadFieldTest extends FunctionalTest { $this->assertFalse($response->isError()); $parser = new CSSContentParser($response->getBody()); - $this->assertFalse( - (bool)$parser->getBySelector('#CanUploadFalseField .ss-uploadfield-fromcomputer-fileinput'), - 'Removes input file control' - ); $this->assertFalse((bool)$parser->getBySelector('#CanUploadFalseField .ss-uploadfield-dropzone'), 'Removes dropzone'); $this->assertTrue(