From bdc17198e89cadbab7f13eb566f509d04a50485b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Martins?= Date: Fri, 14 Dec 2012 00:47:32 +0000 Subject: [PATCH 01/54] tinymce language portuguese is not working --- i18n/i18n.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/i18n/i18n.php b/i18n/i18n.php index 98f1c6904..79dfb8f43 100644 --- a/i18n/i18n.php +++ b/i18n/i18n.php @@ -940,14 +940,14 @@ class i18n extends Object implements TemplateGlobalProvider { 'nn_NO' => 'nn', 'pl_PL' => 'pl', 'pl_UA' => 'pl', - 'pt_AO' => 'pt_br', - 'pt_BR' => 'pt_br', - 'pt_CV' => 'pt_br', - 'pt_GW' => 'pt_br', - 'pt_MZ' => 'pt_br', - 'pt_PT' => 'pt_br', - 'pt_ST' => 'pt_br', - 'pt_TL' => 'pt_br', + 'pt_AO' => 'pt', + 'pt_BR' => 'pt', + 'pt_CV' => 'pt', + 'pt_GW' => 'pt', + 'pt_MZ' => 'pt', + 'pt_PT' => 'pt', + 'pt_ST' => 'pt', + 'pt_TL' => 'pt', 'ro_MD' => 'ro', 'ro_RO' => 'ro', 'ro_RS' => 'ro', From 55b611d99df27b90a0aa9e9c08356ebe1ff15e5e Mon Sep 17 00:00:00 2001 From: uniun Date: Fri, 14 Dec 2012 18:20:18 +0200 Subject: [PATCH 02/54] BUG Hardcoded project name in include_by_locale() include_by_locale() should not use hardcoded default project name 'mysite' (fixes #7969). --- i18n/i18n.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/i18n.php b/i18n/i18n.php index 98f1c6904..dd494b65e 100644 --- a/i18n/i18n.php +++ b/i18n/i18n.php @@ -1956,7 +1956,7 @@ class i18n extends Object implements TemplateGlobalProvider { // Sort modules by inclusion priority, then alphabetically // TODO Should be handled by priority flags within modules - $prios = array('sapphire' => 10, 'framework' => 10, 'admin' => 11, 'cms' => 12, 'mysite' => 90); + $prios = array('sapphire' => 10, 'framework' => 10, 'admin' => 11, 'cms' => 12, project() => 90); $modules = SS_ClassLoader::instance()->getManifest()->getModules(); ksort($modules); uksort( From 6aba24b3e95904961d8d0b1f550bebb534f70d99 Mon Sep 17 00:00:00 2001 From: uniun Date: Tue, 18 Dec 2012 11:57:11 +0200 Subject: [PATCH 03/54] BUG removeRequiredField() should use array_splice() instead of unset() Function unset() preserves numeric keys and method removeRequiredField() will give a PHP notice about nonexistent array key and loop won't iterate throughout all elements in array on second method call (and all subsequent). So it's better to use foreach loop and array_splice() function (it doesn't preserve numeric keys). --- forms/RequiredFields.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forms/RequiredFields.php b/forms/RequiredFields.php index 536140d9c..00df9656d 100644 --- a/forms/RequiredFields.php +++ b/forms/RequiredFields.php @@ -112,9 +112,9 @@ class RequiredFields extends Validator { } public function removeRequiredField($field) { - for($i=0; $irequired); $i++) { - if($field == $this->required[$i]) { - unset($this->required[$i]); + foreach ($this->required as $i => $required) { + if ($field == $required) { + array_splice($this->required, $i); } } } From 22efd3848e20b60cdc4173225ee84b2d86ab0be2 Mon Sep 17 00:00:00 2001 From: Stig Lindqvist Date: Wed, 19 Dec 2012 16:40:13 +1300 Subject: [PATCH 04/54] BUG Calling DataObject::relField() on a object with an empty relation list This causes a 'Fatal error: Call to a member function hasMethod() on a non-object'. This can happen when displaying a field in a gridfield on a belongs_to relationship. --- model/DataObject.php | 30 ++++++++++++++++++------------ tests/model/DataObjectTest.php | 3 +++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/model/DataObject.php b/model/DataObject.php index 27ae22fe0..ad4324e63 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -2619,27 +2619,33 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * The path to the related field is specified with dot separated syntax (eg: Parent.Child.Child.FieldName) * * @param $fieldPath string - * @return string + * @return string | null - will return null on a missing value */ public function relField($fieldName) { $component = $this; + // We're dealing with relations here so we traverse the dot syntax if(strpos($fieldName, '.') !== false) { - $parts = explode('.', $fieldName); - $fieldName = array_pop($parts); - - // Traverse dot syntax - foreach($parts as $relation) { - if($component instanceof SS_List) { - if(method_exists($component,$relation)) $component = $component->$relation(); - else $component = $component->relation($relation); - } else { + $relations = explode('.', $fieldName); + $fieldName = array_pop($relations); + foreach($relations as $relation) { + // Bail if any of the below sets a $component to a null object + if($component instanceof SS_List && !method_exists($component, $relation)) { + $component = $component->relation($relation); + // Just call the method and hope for the best + } else { $component = $component->$relation(); } } } - - if ($component->hasMethod($fieldName)) return $component->$fieldName(); + + // Bail if the component is null + if(!$component) { + return null; + } + if($component->hasMethod($fieldName)) { + return $component->$fieldName(); + } return $component->$fieldName; } diff --git a/tests/model/DataObjectTest.php b/tests/model/DataObjectTest.php index 6942dda02..ca4bb9a97 100644 --- a/tests/model/DataObjectTest.php +++ b/tests/model/DataObjectTest.php @@ -1091,6 +1091,9 @@ class DataObjectTest extends SapphireTest { $player = $this->objFromFixture('DataObjectTest_Player', 'player2'); // Test that we can traverse more than once, and that arbitrary methods are okay $this->assertEquals("Team 1", $player->relField('Teams.First.Title')); + + $newPlayer = new DataObjectTest_Player(); + $this->assertNull($newPlayer->relField('Teams.First.Title')); } public function testRelObject() { From 3a555d2bcbe21fee770d062a1cc76dd4844ffa51 Mon Sep 17 00:00:00 2001 From: Adam Judd Date: Tue, 18 Dec 2012 23:22:37 -0800 Subject: [PATCH 05/54] Set 'active' correctly for accordion widget The accordion widget was previously being passed true and false, where as it either expects true or the integer of the panel to show. This fix sets it as either false or 0. --- javascript/ToggleCompositeField.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ToggleCompositeField.js b/javascript/ToggleCompositeField.js index d853bfbee..ac8913618 100644 --- a/javascript/ToggleCompositeField.js +++ b/javascript/ToggleCompositeField.js @@ -4,7 +4,7 @@ onadd: function() { this.accordion({ collapsible: true, - active: !this.hasClass("ss-toggle-start-closed") + active: (this.hasClass("ss-toggle-start-closed")) ? false : 0 }); this._super(); From 3fca28887359eaf2034eec0df36666bb85758ee7 Mon Sep 17 00:00:00 2001 From: Nicolaas Date: Wed, 26 Dec 2012 22:29:36 +1300 Subject: [PATCH 06/54] Update forms/TextareaField.php small edit to description of the field (usage). Removed reference to cols and rows parameters. --- forms/TextareaField.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/forms/TextareaField.php b/forms/TextareaField.php index d52834af8..6644ba405 100644 --- a/forms/TextareaField.php +++ b/forms/TextareaField.php @@ -11,8 +11,6 @@ * new TextareaField( * $name = "description", * $title = "Description", - * $rows = 8, - * $cols = 3, * $value = "This is the default description" * ); * From a32451f72e5543f8524e6fc0b843baf973cc8a82 Mon Sep 17 00:00:00 2001 From: Nicolaas Date: Thu, 27 Dec 2012 19:22:54 +1300 Subject: [PATCH 07/54] adding extension hook in GridFieldDetailForm.php This hook is useful so that you can add additional fields / actions in a gridfield form that are not available in other settings (e.g. additional actions: previous / next / save and publish / unpublish / etc --- forms/gridfield/GridFieldDetailForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms/gridfield/GridFieldDetailForm.php b/forms/gridfield/GridFieldDetailForm.php index 74457bb82..ff0be993b 100644 --- a/forms/gridfield/GridFieldDetailForm.php +++ b/forms/gridfield/GridFieldDetailForm.php @@ -348,7 +348,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler { $cb = $this->component->getItemEditFormCallback(); if($cb) $cb($form, $this); - + $this->extend("updateItemEditForm", $form); return $form; } From abbee41b7852480ff8552474a147cc5912fe755a Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Mon, 7 Jan 2013 18:44:01 +1300 Subject: [PATCH 08/54] NEW: Add ReadonlyField::setIncludeHiddenField() The new config setter restores the 2.4 behaviour of including with a field. Although as a default, this option has security flaws; it is useful in a few circumstances and, if nothing else, is handy to make upgrading sites easier. --- forms/ReadonlyField.php | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/forms/ReadonlyField.php b/forms/ReadonlyField.php index e9a58f7c6..a2bd5888b 100644 --- a/forms/ReadonlyField.php +++ b/forms/ReadonlyField.php @@ -11,10 +11,43 @@ class ReadonlyField extends FormField { protected $readonly = true; + /** + * Include a hidden field in the HTML for the readonly field + * @var boolean + */ + protected $includeHiddenField = false; + + /** + * If true, a hidden field will be included in the HTML for the readonly field. + * + * This can be useful if you need to pass the data through on the form submission, as + * long as it's okay than an attacker could change the data before it's submitted. + * + * This is disabled by default as it can introduce security holes if the data is not + * allowed to be modified by the user. + * + * @param boolean $includeHiddenField + */ + public function setIncludeHiddenField($includeHiddenField) { + $this->includeHiddenField = $includeHiddenField; + } + public function performReadonlyTransformation() { return clone $this; } + public function Field($properties = array()) { + // Include a hidden field in the HTML + if($this->includeHiddenField && $this->readonly) { + $hidden = clone $this; + $hidden->setReadonly(false); + return parent::Field($properties) . $hidden->Field($properties); + + } else { + return parent::Field($properties); + } + } + public function Value() { if($this->value) return $this->dontEscape ? $this->value : Convert::raw2xml($this->value); else return '(' . _t('FormField.NONE', 'none') . ')'; @@ -25,7 +58,7 @@ class ReadonlyField extends FormField { parent::getAttributes(), array( 'type' => 'hidden', - 'value' => null, + 'value' => $this->readonly ? null : $this->value, ) ); } From 729bcc95db06d554ff60f5984cf2f9b840898a24 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Wed, 31 Aug 2011 18:19:20 +1200 Subject: [PATCH 09/54] BUGFIX: Don't clear form messages unless forTemplate() is actually called. BUGFIX: Clear session-stored form data as well as form error message. --- forms/Form.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/forms/Form.php b/forms/Form.php index 258b82ba3..8764d84d5 100644 --- a/forms/Form.php +++ b/forms/Form.php @@ -974,9 +974,7 @@ class Form extends RequestHandler { */ public function Message() { $this->getMessageFromSession(); - $message = $this->message; - $this->clearMessage(); - return $message; + return $this->message; } /** @@ -993,8 +991,6 @@ class Form extends RequestHandler { }else{ $this->message = Session::get("FormInfo.{$this->FormName()}.formError.message"); $this->messageType = Session::get("FormInfo.{$this->FormName()}.formError.type"); - - Session::clear("FormInfo.{$this->FormName()}"); } } @@ -1030,9 +1026,11 @@ class Form extends RequestHandler { $this->message = null; Session::clear("FormInfo.{$this->FormName()}.errors"); Session::clear("FormInfo.{$this->FormName()}.formError"); + Session::clear("FormInfo.{$this->FormName()}.data"); } public function resetValidation() { Session::clear("FormInfo.{$this->FormName()}.errors"); + Session::clear("FormInfo.{$this->FormName()}.data"); } /** @@ -1317,6 +1315,13 @@ class Form extends RequestHandler { (array)$this->getTemplate(), array('Form') )); + + // Now that we're rendered, clear message + Session::clear("FormInfo.{$this->FormName()}.errors"); + Session::clear("FormInfo.{$this->FormName()}.formError"); + Session::clear("FormInfo.{$this->FormName()}.data"); + + return $result; } /** From b114aa24889b23f1f55d2cb7c9df4962fdb1d58e Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Thu, 7 Jul 2011 17:28:58 +1200 Subject: [PATCH 10/54] BUGFIX: Added X-Forwarded-Protocol and User-Agent to Vary header. --- control/HTTP.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/control/HTTP.php b/control/HTTP.php index 03e776efa..a6a2074a2 100644 --- a/control/HTTP.php +++ b/control/HTTP.php @@ -287,6 +287,10 @@ class HTTP { if(self::$cache_age > 0) { $responseHeaders["Cache-Control"] = "max-age=" . self::$cache_age . ", must-revalidate"; $responseHeaders["Pragma"] = ""; + + // To do: User-Agent should only be added in situations where you *are* actually varying according to user-agent. + $responseHeaders['Vary'] = 'Cookie, X-Forwarded-Protocol, User-Agent'; + } else { $responseHeaders["Cache-Control"] = "no-cache, max-age=0, must-revalidate"; } From 2916f2043cc31317c0ec1ea54449e18c93b5e1cb Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Thu, 29 Sep 2011 16:21:32 +1300 Subject: [PATCH 11/54] NEW: Improve HTTP caching logic to automatically disable caching for requests that use the session. This improvement makes it easier to set a side-wide default cache time without needing to worry about CSRF-protected forms, etc. --- control/Controller.php | 3 +++ control/HTTP.php | 4 ++-- forms/Form.php | 12 ++++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/control/Controller.php b/control/Controller.php index b0e8727e7..b74549628 100644 --- a/control/Controller.php +++ b/control/Controller.php @@ -477,6 +477,9 @@ class Controller extends RequestHandler implements TemplateGlobalProvider { * @uses redirect() */ public function redirectBack() { + // Don't cache the redirect back ever + HTTP::set_cache_age(0); + $url = null; // In edge-cases, this will be called outside of a handleRequest() context; in that case, diff --git a/control/HTTP.php b/control/HTTP.php index a6a2074a2..17974f2ca 100644 --- a/control/HTTP.php +++ b/control/HTTP.php @@ -285,14 +285,14 @@ class HTTP { } if(self::$cache_age > 0) { - $responseHeaders["Cache-Control"] = "max-age=" . self::$cache_age . ", must-revalidate"; + $responseHeaders["Cache-Control"] = "max-age=" . self::$cache_age . ", must-revalidate, no-transform"; $responseHeaders["Pragma"] = ""; // To do: User-Agent should only be added in situations where you *are* actually varying according to user-agent. $responseHeaders['Vary'] = 'Cookie, X-Forwarded-Protocol, User-Agent'; } else { - $responseHeaders["Cache-Control"] = "no-cache, max-age=0, must-revalidate"; + $responseHeaders["Cache-Control"] = "no-cache, max-age=0, must-revalidate, no-transform"; } if(self::$modification_date && self::$cache_age > 0) { diff --git a/forms/Form.php b/forms/Form.php index 258b82ba3..e3fd536db 100644 --- a/forms/Form.php +++ b/forms/Form.php @@ -707,8 +707,16 @@ class Form extends RequestHandler { if(!$attrs || is_string($attrs)) $attrs = $this->getAttributes(); - // Forms shouldn't be cached, cos their error messages won't be shown - HTTP::set_cache_age(0); + // Figure out if we can cache this form + // - forms with validation shouldn't be cached, cos their error messages won't be shown + // - forms with security tokens shouldn't be cached because security tokens expire + $needsCacheDisabled = false; + if ($this->getSecurityToken()->isEnabled()) $needsCacheDisabled = true; + if ($this->FormMethod() != 'get') $needsCacheDisabled = true; + if (!($this->validator instanceof RequiredFields) || count($this->validator->getRequired())) $needsCacheDisabled = true; + + // If we need to disable cache, do it + if ($needsCacheDisabled) HTTP::set_cache_age(0); $attrs = $this->getAttributes(); From f41a7d8b652c411d8369bd6796786cd37235f7c0 Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Fri, 30 Sep 2011 17:14:19 +1300 Subject: [PATCH 12/54] FIX: Fix issue with not being able to log out on Chrome when caching enabled because of Chrome bug --- control/HTTP.php | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/control/HTTP.php b/control/HTTP.php index 17974f2ca..08fc73575 100644 --- a/control/HTTP.php +++ b/control/HTTP.php @@ -262,7 +262,7 @@ class HTTP { user_error("HTTP::add_cache_headers() must be passed an SS_HTTPResponse object", E_USER_WARNING); $body = null; } - + // Development sites have frequently changing templates; this can get stuffed up by the code // below. if(Director::isDev()) return; @@ -270,8 +270,8 @@ class HTTP { // The headers have been sent and we don't have an SS_HTTPResponse object to attach things to; no point in // us trying. if(headers_sent() && !$body) return; - - // Popuplate $responseHeaders with all the headers that we want to build + + // Popuplate $responseHeaders with all the headers that we want to build $responseHeaders = array(); if(function_exists('apache_request_headers')) { $requestHeaders = apache_request_headers(); @@ -289,19 +289,39 @@ class HTTP { $responseHeaders["Pragma"] = ""; // To do: User-Agent should only be added in situations where you *are* actually varying according to user-agent. - $responseHeaders['Vary'] = 'Cookie, X-Forwarded-Protocol, User-Agent'; + $responseHeaders['Vary'] = 'Cookie, X-Forwarded-Protocol, User-Agent, Accept'; } else { $responseHeaders["Cache-Control"] = "no-cache, max-age=0, must-revalidate, no-transform"; } if(self::$modification_date && self::$cache_age > 0) { - $responseHeaders["Last-Modified"] =self::gmt_date(self::$modification_date); + $responseHeaders["Last-Modified"] = self::gmt_date(self::$modification_date); + + /* Chrome ignores Varies when redirecting back (http://code.google.com/p/chromium/issues/detail?id=79758) + which means that if you log out, you get redirected back to a page which Chrome then checks against last-modified (which passes, getting a 304) + when it shouldn't be trying to use that page at all because it's the "logged in" version. + + By also using and etag that includes both the modification date and all the varies values which we also check against we can catch + this and not return a 304 + */ + $etagParts = array(self::$modification_date, serialize($_COOKIE)); + if (isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])) $etagParts[] = $_SERVER['HTTP_X_FORWARDED_PROTOCOL']; + if (isset($_SERVER['HTTP_USER_AGENT'])) $etagParts[] = $_SERVER['HTTP_USER_AGENT']; + if (isset($_SERVER['HTTP_ACCEPT'])) $etagParts[] = $_SERVER['HTTP_ACCEPT']; + + $etag = sha1(implode(':', $etagParts)); + $responseHeaders["ETag"] = $etag; // 304 response detection if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { $ifModifiedSince = strtotime(stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE'])); - if($ifModifiedSince >= self::$modification_date) { + + // As above, only 304 if the last request had all the same varies values + // (or the etag isn't passed as part of the request - but with chrome it always is) + $matchesEtag = !isset($_SERVER['HTTP_IF_NONE_MATCH']) || $_SERVER['HTTP_IF_NONE_MATCH'] == $etag; + + if($ifModifiedSince >= self::$modification_date && $matchesEtag) { if($body) { $body->setStatusCode(304); $body->setBody(''); From f6ab5a4020cfbb80d801099a5d23969065659aaf Mon Sep 17 00:00:00 2001 From: drzax Date: Wed, 9 Jan 2013 10:41:40 +1000 Subject: [PATCH 13/54] 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 f922321287f6e2cc453a17915bb8dc1637c18413 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 10 Jan 2013 22:21:08 +0100 Subject: [PATCH 14/54] 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 f4efaeefa73ce2163399b3b4a6622cbe026a0a25 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Fri, 11 Jan 2013 12:20:28 +1300 Subject: [PATCH 15/54] FIX: Fix DataObject::get_one() when the classname is passed with improper casing. --- model/DataObject.php | 6 ++---- tests/model/DataObjectTest.php | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/model/DataObject.php b/model/DataObject.php index 27ae22fe0..e3834648e 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -2850,12 +2850,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity && DataObject::$_cache_get_one[$callerClass][$cacheKey] instanceof DataObject && DataObject::$_cache_get_one[$callerClass][$cacheKey]->destroyed) { - DataObject::$_cache_get_one[$callerClass][$cacheKey - ] = false; + DataObject::$_cache_get_one[$callerClass][$cacheKey] = false; } if(!$cache || !isset(DataObject::$_cache_get_one[$callerClass][$cacheKey])) { - $dl = DataList::create($callerClass)->where($filter)->sort($orderby); - $dl->setDataModel(DataModel::inst()); + $dl = $callerClass::get()->where($filter)->sort($orderby); $item = $dl->First(); if($cache) { diff --git a/tests/model/DataObjectTest.php b/tests/model/DataObjectTest.php index 6942dda02..321588683 100644 --- a/tests/model/DataObjectTest.php +++ b/tests/model/DataObjectTest.php @@ -171,6 +171,10 @@ class DataObjectTest extends SapphireTest { $this->assertEquals('Bob', $comment->Name); $comment = DataObject::get_one('DataObjectTest_TeamComment', '', true, '"Name" DESC'); $this->assertEquals('Phil', $comment->Name); + + // Test get_one() with bad case on the classname + $subteam1 = DataObject::get_one('dataobjecttest_subteam', "\"Title\" = 'Subteam 1'", true); + $this->assertEquals($subteam1->Title, "Subteam 1"); } public function testGetSubclassFields() { From b918487375282a012a5fb0d96ebc9623f926222b Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 10 Jan 2013 23:43:20 +0100 Subject: [PATCH 16/54] Fixed preview overlay showing by default This was a regression made visible by the recent change to enforce dimensions on this overlay, which in turn visualizes the blocked/unblocked states of the preview (see fc6d6ffad) --- admin/javascript/LeftAndMain.Preview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/javascript/LeftAndMain.Preview.js b/admin/javascript/LeftAndMain.Preview.js index c0ef8e464..364798f06 100644 --- a/admin/javascript/LeftAndMain.Preview.js +++ b/admin/javascript/LeftAndMain.Preview.js @@ -166,7 +166,7 @@ // Preview might not be available in all admin interfaces - block/disable when necessary this.append('
'); - this.find('.cms-preview-overlay-light').hide(); + this.find('.cms-preview-overlay').hide(); this.disablePreview(); From 7306d02411c2d01f093d4d98ae200ce316142254 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 11 Jan 2013 00:33:31 +0100 Subject: [PATCH 17/54] 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. --- templates/UploadField.ss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/UploadField.ss b/templates/UploadField.ss index aa3450f80..3b8d105d6 100644 --- a/templates/UploadField.ss +++ b/templates/UploadField.ss @@ -55,7 +55,9 @@ + + <% else %> + multiple="multiple"<% end_if %> /> <% end_if %> <% if not $autoUpload %> From 2dfd42795e4c19f62d500b457e32b92e5cfcb76c Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Wed, 9 Jan 2013 11:34:02 +0100 Subject: [PATCH 18/54] 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 19/54] 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 20/54] 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 21/54] 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 22/54] 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 23/54] 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 24/54] 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 25/54] 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( From 9e82d8e857d4f1cf498a848374857f60e90388c7 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 11 Jan 2013 09:59:33 +0100 Subject: [PATCH 26/54] Fixed line lengths --- forms/TreeDropdownField.php | 5 +++-- tests/model/VersionedTest.php | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/forms/TreeDropdownField.php b/forms/TreeDropdownField.php index fcc4b5008..372a92ec1 100644 --- a/forms/TreeDropdownField.php +++ b/forms/TreeDropdownField.php @@ -85,8 +85,9 @@ class TreeDropdownField extends FormField { * @param bool $showSearch enable the ability to search the tree by * entering the text in the input field. */ - public function __construct($name, $title = null, $sourceObject = 'Group', $keyField = 'ID', $labelField = 'TreeTitle', - $showSearch = false) { + public function __construct($name, $title = null, $sourceObject = 'Group', $keyField = 'ID', + $labelField = 'TreeTitle', $showSearch = false + ) { $this->sourceObject = $sourceObject; $this->keyField = $keyField; diff --git a/tests/model/VersionedTest.php b/tests/model/VersionedTest.php index a671adc77..e7b11062d 100644 --- a/tests/model/VersionedTest.php +++ b/tests/model/VersionedTest.php @@ -75,7 +75,10 @@ class VersionedTest extends SapphireTest { */ public function testGetIncludingDeleted() { // Get all ids of pages - $allPageIDs = DataObject::get('VersionedTest_DataObject', "\"ParentID\" = 0", "\"VersionedTest_DataObject\".\"ID\" ASC")->column('ID'); + $allPageIDs = DataObject::get( + 'VersionedTest_DataObject', + "\"ParentID\" = 0", "\"VersionedTest_DataObject\".\"ID\" ASC" + )->column('ID'); // Modify a page, ensuring that the Version ID and Record ID will differ, // and then subsequently delete it From e020c7be57227dc34b7a6ac9c861d68c9cf4d347 Mon Sep 17 00:00:00 2001 From: uniun Date: Fri, 11 Jan 2013 15:50:09 +0100 Subject: [PATCH 27/54] BUG doSave() and doDelete() should use translated singular name --- forms/gridfield/GridFieldDetailForm.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forms/gridfield/GridFieldDetailForm.php b/forms/gridfield/GridFieldDetailForm.php index ff0be993b..2732bd669 100644 --- a/forms/gridfield/GridFieldDetailForm.php +++ b/forms/gridfield/GridFieldDetailForm.php @@ -419,7 +419,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler { 'GridFieldDetailForm.Saved', 'Saved {name} {link}', array( - 'name' => $this->record->singular_name(), + 'name' => $this->record->i18n_singular_name(), 'link' => $link ) ); @@ -457,7 +457,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler { $message = sprintf( _t('GridFieldDetailForm.Deleted', 'Deleted %s %s'), - $this->record->singular_name(), + $this->record->i18n_singular_name(), htmlspecialchars($title, ENT_QUOTES) ); From 4b182d3fad2318aa25e33ae2fa8d2e7c754be5df Mon Sep 17 00:00:00 2001 From: Fred Condo Date: Fri, 14 Dec 2012 14:26:13 -0800 Subject: [PATCH 28/54] Update documentation of nginx configuration - Avoid using "if" to check for file existence (use try_files instead) - Replicate the behavior of the .htaccess files - TODO: get static error pages to work --- docs/en/installation/nginx.md | 113 ++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 27 deletions(-) diff --git a/docs/en/installation/nginx.md b/docs/en/installation/nginx.md index 833989bd4..3a8d34e35 100644 --- a/docs/en/installation/nginx.md +++ b/docs/en/installation/nginx.md @@ -1,38 +1,97 @@ # Nginx -These instructions are also covered on the [Nginx Wiki](http://wiki.nginx.org/SilverStripe) +These instructions are also covered in less detail on the +[Nginx Wiki](http://wiki.nginx.org/SilverStripe). -The prerequisite is that you have already installed Nginx and you are able to run PHP files via the FastCGI-wrapper from -Nginx. +The prerequisite is that you have already installed Nginx and you are +able to run PHP files via the FastCGI-wrapper from Nginx. -Now you need to setup a virtual host in Nginx with the following configuration settings: +Now you need to set up a virtual host in Nginx with the following +configuration settings: server { - listen 80; - server_name yoursite.com; - - root /home/yoursite.com/httpdocs; - index index.html index.php; - - if (!-f $request_filename) { - rewrite ^/(.*?)(\?|$)(.*)$ /framework/main.php?url=$1&$3 last; - } - - error_page 404 /framework/main.php; - - location ~ \.php$ { - include fastcgi_params; - fastcgi_pass 127.0.0.1:9000; - fastcgi_index index.php; - fastcgi_param SCRIPT_FILENAME /home/yoursite.com/httpdocs$fastcgi_script_name; - fastcgi_buffer_size 32k; - fastcgi_buffers 4 32k; - fastcgi_busy_buffers_size 64k; - } + listen 80; + + # SSL configuration (optional, but recommended for security) + include ssl + + root /var/www/example.com; + index index.php index.html index.htm; + + server_name example.com; + + include silverstripe3; + include htaccess; + } + +Here is the include file `silverstripe3`: + + location / { + try_files $uri @silverstripe; + } + + location @silverstripe { + include fastcgi_params; + + # Defend against arbitrary PHP code execution + # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini + # More info: + # https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/ + fastcgi_split_path_info ^(.+\.php)(/.+)$; + + fastcgi_param SCRIPT_FILENAME $document_root/framework/main.php; + fastcgi_param SCRIPT_NAME /framework/main.php; + fastcgi_param QUERY_STRING url=$uri&$args; + + fastcgi_pass unix:/var/run/php5-fpm.sock; + fastcgi_index index.php; + fastcgi_buffer_size 32k; + fastcgi_buffers 4 32k; + fastcgi_busy_buffers_size 64k; } -The above configuration will setup a new virtual host `yoursite.com` with rewrite rules suited for SilverStripe. The -location block at the bottom will pass all php scripts to the FastCGI-wrapper. +Here is the include file `htaccess`: + + # Don't serve up any .htaccess files + location ~ /\.ht { + deny all; + } + + # Deny access to silverstripe-cache + location ~ ^/silverstripe-cache { + deny all; + } + + # Don't execute scripts in the assets + location ^~ /assets/ { + try_files $uri $uri/ =404; + } + + # cms & framework .htaccess rules + location ~ ^/(cms|framework|mysite)/.*\.(php|php[345]|phtml|inc)$ { + deny all; + } + location ~ ^/(cms|framework)/silverstripe_version$ { + deny all; + } + location ~ ^/framework/.*(main|static-main|rpc|tiny_mce_gzip)\.php$ { + allow all; + } + +Here is the optional include file `ssl`: + + listen 443 ssl; + ssl_certificate server.crt; + ssl_certificate_key server.key; + ssl_session_timeout 5m; + ssl_protocols SSLv3 TLSv1; + ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP; + +The above configuration sets up a virtual host `example.com` with +rewrite rules suited for SilverStripe. The location block named +`@silverstripe` passes all php scripts to the FastCGI-wrapper via a Unix +socket. This example is from a site running Ubuntu with the php5-fpm +package. Now you can proceed with the SilverStripe installation normally. From 7f605cfb54e1f860a182d3001e0612fee7fd7970 Mon Sep 17 00:00:00 2001 From: micschk Date: Wed, 17 Oct 2012 12:06:02 +0300 Subject: [PATCH 29/54] NEW Disable items in DropdownField and GroupedDropdownField --- forms/DropdownField.php | 37 ++++++++++++++++++ forms/GroupedDropdownField.php | 30 ++++++++++++++- tests/forms/DropdownFieldTest.php | 64 ++++++++++++++++++++++++++++++- 3 files changed, 128 insertions(+), 3 deletions(-) diff --git a/forms/DropdownField.php b/forms/DropdownField.php index e1d018936..5115f28fd 100644 --- a/forms/DropdownField.php +++ b/forms/DropdownField.php @@ -66,6 +66,14 @@ * ); * * + * Disabling individual items + * + * Individual items can be disabled by feeding their array keys to setDisabledItems. + * + * + * $DrDownField->setDisabledItems( array( 'US', 'GEM' ) ); + * + * * @see CheckboxSetField for multiple selections through checkboxes instead. * @see ListboxField for a single name[$code]\" type=\"checkbox\"" . " value=\"$code\"$checked class=\"checkbox\" />" . "" . "
  • \n"; } else { From 0c9b216894d8d328592601fa9f25536f2f08d507 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Fri, 18 Jan 2013 11:24:23 +1300 Subject: [PATCH 47/54] FIX: Escape the -f argument passed to mail() --- email/Mailer.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/email/Mailer.php b/email/Mailer.php index 734771916..6a0c23a81 100644 --- a/email/Mailer.php +++ b/email/Mailer.php @@ -1,4 +1,5 @@ Date: Sat, 19 Jan 2013 22:38:55 +1300 Subject: [PATCH 48/54] Update admin/code/ModelAdmin.php Changed "Clear Database before import" - which is incorrect (not the whole database gets wiped, only the data in the model at hand) with the simpler: "replace data". --- admin/code/ModelAdmin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/code/ModelAdmin.php b/admin/code/ModelAdmin.php index 353e2f9c4..aaeee5506 100644 --- a/admin/code/ModelAdmin.php +++ b/admin/code/ModelAdmin.php @@ -357,7 +357,7 @@ abstract class ModelAdmin extends LeftAndMain { $fields->push(new LiteralField("SpecFor{$modelName}", $specHTML)); $fields->push( - new CheckboxField('EmptyBeforeImport', _t('ModelAdmin.EMPTYBEFOREIMPORT', 'Clear Database before import'), + new CheckboxField('EmptyBeforeImport', _t('ModelAdmin.EMPTYBEFOREIMPORT', 'Replace data'), false) ); From 3d921e7459fd6cd802e07c6e1350fb02937f2e5f Mon Sep 17 00:00:00 2001 From: Will Rossiter Date: Sun, 20 Jan 2013 19:51:18 +1300 Subject: [PATCH 49/54] Update docs/en/reference/form-field-types.md --- docs/en/reference/form-field-types.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/en/reference/form-field-types.md b/docs/en/reference/form-field-types.md index a38c606d5..e37d33e5b 100644 --- a/docs/en/reference/form-field-types.md +++ b/docs/en/reference/form-field-types.md @@ -8,7 +8,7 @@ This is a highlevel overview of available `[api:FormField]` subclasses. An autom * `[api:DropdownField]`: A `