From 0e443bafa0c8c87c91f5344142418f32a3e5f853 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Mon, 8 Jul 2013 15:27:13 +1200 Subject: [PATCH 01/16] Deprecate Aggregate and DataObject::getComponentsQuery --- model/Aggregate.php | 7 +++++++ model/DataObject.php | 34 +++------------------------------- 2 files changed, 10 insertions(+), 31 deletions(-) diff --git a/model/Aggregate.php b/model/Aggregate.php index f0dce851b..372843299 100644 --- a/model/Aggregate.php +++ b/model/Aggregate.php @@ -29,6 +29,8 @@ * NOTE: The cache logic uses tags, and so a backend that supports tags is required. Currently only the File * backend (and the two-level backend with the File backend as the slow store) meets this requirement * + * @deprecated 3.1 Use DataList to aggregate data + * * @author hfried * @package framework * @subpackage core @@ -60,10 +62,15 @@ class Aggregate extends ViewableData { /** * Constructor * + * @deprecated 3.1 Use DataList to aggregate data + * * @param string $type The DataObject type we are building an aggregate for * @param string $filter (optional) An SQL filter to apply to the selected rows before calculating the aggregate */ public function __construct($type, $filter = '') { + Deprecation::notice('3.1', 'Call aggregate methods on a DataList directly instead. In templates' + . ' an example of the new syntax is <% cached List(Member).max(LastEdited) %> instead' + . ' (check partial-caching.md documentation for more details.)'); $this->type = $type; $this->filter = $filter; parent::__construct(); diff --git a/model/DataObject.php b/model/DataObject.php index beb53fb14..3d7a730fe 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -1468,39 +1468,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity } /** - * Get the query object for a $has_many Component. - * - * @param string $componentName - * @param string $filter - * @param string|array $sort - * @param string $join Deprecated, use leftJoin($table, $joinClause) instead - * @param string|array $limit - * @return SQLQuery + * @deprecated 3.1 Use getComponents to get a filtered DataList for an object's relation */ public function getComponentsQuery($componentName, $filter = "", $sort = "", $join = "", $limit = "") { - if(!$componentClass = $this->has_many($componentName)) { - user_error("DataObject::getComponentsQuery(): Unknown 1-to-many component '$componentName'" - . " on class '$this->class'", E_USER_ERROR); - } - - if($join) { - throw new \InvalidArgumentException( - 'The $join argument has been removed. Use leftJoin($table, $joinClause) instead.' - ); - } - - $joinField = $this->getRemoteJoinField($componentName, 'has_many'); - - $id = $this->getField("ID"); - - // get filter - $combinedFilter = "\"$joinField\" = '$id'"; - if(!empty($filter)) $combinedFilter .= " AND ({$filter})"; - - return DataList::create($componentClass) - ->where($combinedFilter) - ->canSortBy($sort) - ->limit($limit); + Deprecation::notice('3.1', "Use getComponents to get a filtered DataList for an object's relation"); + return $this->getComponents($componentName, $filter, $sort, $join, $limit); } /** From 7b7982969b8476ed1455a7fbe23c7e45f9946449 Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Wed, 10 Jul 2013 16:24:50 +1200 Subject: [PATCH 02/16] Add some docs about admin-side HTML sanitisation --- docs/en/topics/security.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/en/topics/security.md b/docs/en/topics/security.md index acc9ba89a..59e8deaf6 100644 --- a/docs/en/topics/security.md +++ b/docs/en/topics/security.md @@ -127,6 +127,38 @@ or [sanitize](http://htmlpurifier.org/) it correctly. See [http://shiflett.org/articles/foiling-cross-site-attacks](http://shiflett.org/articles/foiling-cross-site-attacks) for in-depth information about "Cross-Site-Scripting". +### What if I can't trust my editors? + +The default configuration of SilverStripe assumes some level of trust is given to your editors who have access +to the CMS. Though the HTML WYSIWYG editor is configured to provide some control over the HTML an editor provides, +this is not enforced server side, and so can be bypassed by a malicious editor. A editor that does so can use an +XSS attack against an admin to perform any administrative action. + +If you can't trust your editors, SilverStripe must be configured to filter the content so that any javascript is +stripped out + +To enable filtering, set the HtmlEditorField::$sanitise_server_side [configuration](/topics/configuration) property to +true, e.g. + + HtmlEditorField::config()->sanitise_server_side = true + +The built in sanitiser enforces the TinyMCE whitelist rules on the server side, and is sufficient to eliminate the +most common XSS vectors. + +However some subtle XSS attacks that exploit HTML parsing bugs need heavier filtering. For greater protection +you can install the [htmlpurifier](https://github.com/silverstripe-labs/silverstripe-htmlpurifier) module which +will replace the built in sanitiser with one that uses the [HTML Purifier](http://htmlpurifier.org/) library. + +In both cases, you must ensure that you have not configured TinyMCE to explicitly allow script elements or other +javascript-specific attributes. + +##### But I also need my editors to provide javascript + +It is not currently possible to allow editors to provide javascript content and yet still protect other users +from any malicious code within that javascript. + +We recommend configuring [shortcodes](/reference/shortcodes) that can be used by editors in place of using javascript directly. + ### Escaping model properties `[api:SSViewer]` (the SilverStripe template engine) automatically takes care of escaping HTML tags from specific From 378d829e8fce8eab9b0a9af9ffe74585e00e3dca Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Wed, 10 Jul 2013 12:47:13 +0100 Subject: [PATCH 03/16] Adding test to prove issue with HTTP Header parsing in RestfulService I have a header like: X-BB-Auth: xxxx and it is being given back to me as X-Bb-Auth - i want to prove the issue and the fix --- tests/api/RestfulServiceTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/api/RestfulServiceTest.php b/tests/api/RestfulServiceTest.php index a9248db3a..772ccd9ef 100644 --- a/tests/api/RestfulServiceTest.php +++ b/tests/api/RestfulServiceTest.php @@ -172,12 +172,14 @@ class RestfulServiceTest extends SapphireTest { public function testHttpHeaderParseing() { $headers = "content-type: text/html; charset=UTF-8\r\n". "Server: Funky/1.0\r\n". + "X-BB-ExampleMANycaPS: test\r\n". "Set-Cookie: foo=bar\r\n". "Set-Cookie: baz=quux\r\n". "Set-Cookie: bar=foo\r\n"; $expected = array( 'Content-Type' => 'text/html; charset=UTF-8', 'Server' => 'Funky/1.0', + 'X-BB-ExampleMANycaPS' => 'test', 'Set-Cookie' => array( 'foo=bar', 'baz=quux', From ddd6a15b4a6906b8ea23d04cb0f6415aac9650af Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Wed, 10 Jul 2013 13:00:40 +0100 Subject: [PATCH 04/16] FIX RestfulService header parsing now accepts non-title case headers --- api/RestfulService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/RestfulService.php b/api/RestfulService.php index 05913c3f5..5c95d451d 100644 --- a/api/RestfulService.php +++ b/api/RestfulService.php @@ -352,7 +352,7 @@ class RestfulService extends ViewableData { $match[1] = preg_replace_callback( '/(?<=^|[\x09\x20\x2D])./', create_function('$matches', 'return strtoupper($matches[0]);'), - strtolower(trim($match[1])) + trim($match[1]) ); if( isset($headers[$match[1]]) ) { if (!is_array($headers[$match[1]])) { From b58e2dbe3a1b94c6f69c6b708761f1dce71c0429 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 11 Jul 2013 01:17:02 +0200 Subject: [PATCH 05/16] Member.lock_out_delay_mins configurable, password security docs --- docs/en/topics/security.md | 34 ++++++++++++--- lang/en.yml | 1 + security/Member.php | 28 +++++++++--- tests/security/SecurityTest.php | 77 ++++++++++++++++++++------------- 4 files changed, 97 insertions(+), 43 deletions(-) diff --git a/docs/en/topics/security.md b/docs/en/topics/security.md index 59e8deaf6..3e64d5b6c 100644 --- a/docs/en/topics/security.md +++ b/docs/en/topics/security.md @@ -407,13 +407,37 @@ configuration and test fixtures). You should therefore block access to all yaml files (extension .yml) by default, and white list only yaml files you need to serve directly. -See [Apache](/installation/webserver) and [Nginx](/installation/nginx) installation documentation for details -specific to your web server +See [Apache](/installation/webserver) and [Nginx](/installation/nginx) installation documentation for details specific to your web server + +## Passwords + +SilverStripe stores passwords with a strong hashing algorithm (blowfish) by default +(see [api:PasswordEncryptor]). It adds randomness to these hashes via +salt values generated with the strongest entropy generators available on the platform +(see [api:RandomGenerator]). This prevents brute force attacks with +[Rainbow tables](http://en.wikipedia.org/wiki/Rainbow_table). + +Strong passwords are a crucial part of any system security. +So in addition to storing the password in a secure fashion, +you can also enforce specific password policies by configuring +a [api:PasswordValidator]: + + :::php + $validator = new PasswordValidator(); + $validator->minLength(7); + $validator->checkHistoricalPasswords(6); + $validator->characterStrength('lowercase','uppercase','digits','punctuation'); + Member::set_password_validator($validator); + +In addition, you can tighten password security with the following configuration settings: + + * `Member.password_expiry_days`: Set the number of days that a password should be valid for. + * `Member.lock_out_after_incorrect_logins`: Number of incorrect logins after which + the user is blocked from further attempts for the timespan defined in `$lock_out_delay_mins` + * `Member.lock_out_delay_mins`: Minutes of enforced lockout after incorrect password attempts. + Only applies if `lock_out_after_incorrect_logins` is greater than 0. ## Related * [http://silverstripe.org/security-releases/](http://silverstripe.org/security-releases/) - -## Links - * [Best-practices for securing MySQL (securityfocus.com)](http://www.securityfocus.com/infocus/1726) diff --git a/lang/en.yml b/lang/en.yml index 64df41040..154dc2ad4 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -367,6 +367,7 @@ en: EMPTYNEWPASSWORD: 'The new password can''t be empty, please try again' ENTEREMAIL: 'Please enter an email address to get a password reset link.' ERRORLOCKEDOUT: 'Your account has been temporarily disabled because of too many failed attempts at logging in. Please try again in 20 minutes.' + ERRORLOCKEDOUT2: 'Your account has been temporarily disabled because of too many failed attempts at logging in. Please try again in {count} minutes.' ERRORNEWPASSWORD: 'You have entered your new password differently, try again' ERRORPASSWORDNOTMATCH: 'Your current password does not match, please try again' ERRORWRONGCRED: 'That doesn''t seem to be the right e-mail address or password. Please try again.' diff --git a/security/Member.php b/security/Member.php index 3afbd5361..90afb5101 100644 --- a/security/Member.php +++ b/security/Member.php @@ -113,9 +113,18 @@ class Member extends DataObject implements TemplateGlobalProvider { /** * @config - * @var Int + * @var Int Number of incorrect logins after which + * the user is blocked from further attempts for the timespan + * defined in {@link $lock_out_delay_mins}. */ private static $lock_out_after_incorrect_logins = null; + + /** + * @config + * @var integer Minutes of enforced lockout after incorrect password attempts. + * Only applies if {@link $lock_out_after_incorrect_logins} greater than 0. + */ + private static $lock_out_delay_mins = 15; /** * @config @@ -238,11 +247,15 @@ class Member extends DataObject implements TemplateGlobalProvider { $result = new ValidationResult(); if($this->isLockedOut()) { - $result->error(_t ( - 'Member.ERRORLOCKEDOUT', - 'Your account has been temporarily disabled because of too many failed attempts at ' . - 'logging in. Please try again in 20 minutes.' - )); + $result->error( + _t( + 'Member.ERRORLOCKEDOUT2', + 'Your account has been temporarily disabled because of too many failed attempts at ' . + 'logging in. Please try again in {count} minutes.', + null, + array('count' => $this->config()->lock_out_delay_mins) + ) + ); } $this->extend('canLogIn', $result); @@ -1407,7 +1420,8 @@ class Member extends DataObject implements TemplateGlobalProvider { $this->write(); if($this->FailedLoginCount >= self::config()->lock_out_after_incorrect_logins) { - $this->LockedOutUntil = date('Y-m-d H:i:s', time() + 15*60); + $lockoutMins = self::config()->lock_out_delay_mins; + $this->LockedOutUntil = date('Y-m-d H:i:s', time() + $lockoutMins*60); $this->write(); } } diff --git a/tests/security/SecurityTest.php b/tests/security/SecurityTest.php index 294b082b4..0f76c7ac6 100644 --- a/tests/security/SecurityTest.php +++ b/tests/security/SecurityTest.php @@ -250,61 +250,76 @@ class SecurityTest extends FunctionalTest { i18n::set_locale('en_US'); Member::config()->lock_out_after_incorrect_logins = 5; + Member::config()->lock_out_delay_mins = 15; - /* LOG IN WITH A BAD PASSWORD 7 TIMES */ - - for($i=1;$i<=7;$i++) { + // Login with a wrong password for more than the defined threshold + for($i = 1; $i <= Member::config()->lock_out_after_incorrect_logins+1; $i++) { $this->doTestLoginForm('sam@silverstripe.com' , 'incorrectpassword'); $member = DataObject::get_by_id("Member", $this->idFromFixture('Member', 'test')); - /* THE FIRST 4 TIMES, THE MEMBER SHOULDN'T BE LOCKED OUT */ - if($i < 5) { - $this->assertNull($member->LockedOutUntil); + if($i < Member::config()->lock_out_after_incorrect_logins) { + $this->assertNull( + $member->LockedOutUntil, + 'User does not have a lockout time set if under threshold for failed attempts' + ); $this->assertContains($this->loginErrorMessage(), _t('Member.ERRORWRONGCRED')); + } else { + // Fuzzy matching for time to avoid side effects from slow running tests + $this->assertGreaterThan( + time() + 14*60, + strtotime($member->LockedOutUntil), + 'User has a lockout time set after too many failed attempts' + ); } - - /* AFTER THAT THE USER IS LOCKED OUT FOR 15 MINUTES */ - //(we check for at least 14 minutes because we don't want a slow running test to report a failure.) - else { - $this->assertGreaterThan(time() + 14*60, strtotime($member->LockedOutUntil)); - } - - if($i > 5) { - $this->assertContains(_t('Member.ERRORLOCKEDOUT'), $this->loginErrorMessage()); - // $this->assertTrue(false !== stripos($this->loginErrorMessage(), _t('Member.ERRORLOCKEDOUT'))); + $msg = _t( + 'Member.ERRORLOCKEDOUT2', + 'Your account has been temporarily disabled because of too many failed attempts at ' . + 'logging in. Please try again in {count} minutes.', + null, + array('count' => Member::config()->lock_out_delay_mins) + ); + if($i > Member::config()->lock_out_after_incorrect_logins) { + $this->assertContains($msg, $this->loginErrorMessage()); } } - /* THE USER CAN'T LOG IN NOW, EVEN IF THEY GET THE RIGHT PASSWORD */ - $this->doTestLoginForm('sam@silverstripe.com' , '1nitialPassword'); - $this->assertNull($this->session()->inst_get('loggedInAs')); + $this->assertNull( + $this->session()->inst_get('loggedInAs'), + 'The user can\'t log in after being locked out, even with the right password' + ); - /* BUT, IF TIME PASSES, THEY CAN LOG IN */ - // (We fake this by re-setting LockedOutUntil) $member = DataObject::get_by_id("Member", $this->idFromFixture('Member', 'test')); $member->LockedOutUntil = date('Y-m-d H:i:s', time() - 30); $member->write(); - $this->doTestLoginForm('sam@silverstripe.com' , '1nitialPassword'); - $this->assertEquals($this->session()->inst_get('loggedInAs'), $member->ID); + $this->assertEquals( + $this->session()->inst_get('loggedInAs'), + $member->ID, + 'After lockout expires, the user can login again' + ); // Log the user out $this->session()->inst_set('loggedInAs', null); - /* NOW THAT THE LOCK-OUT HAS EXPIRED, CHECK THAT WE ARE ALLOWED 4 FAILED ATTEMPTS BEFORE LOGGING IN */ - - $this->doTestLoginForm('sam@silverstripe.com' , 'incorrectpassword'); - $this->doTestLoginForm('sam@silverstripe.com' , 'incorrectpassword'); - $this->doTestLoginForm('sam@silverstripe.com' , 'incorrectpassword'); - $this->doTestLoginForm('sam@silverstripe.com' , 'incorrectpassword'); + // Login again with wrong password, but less attempts than threshold + for($i = 1; $i < Member::config()->lock_out_after_incorrect_logins; $i++) { + $this->doTestLoginForm('sam@silverstripe.com' , 'incorrectpassword'); + } $this->assertNull($this->session()->inst_get('loggedInAs')); - $this->assertTrue(false !== stripos($this->loginErrorMessage(), _t('Member.ERRORWRONGCRED'))); + $this->assertTrue( + false !== stripos($this->loginErrorMessage(), _t('Member.ERRORWRONGCRED')), + 'The user can retry with a wrong password after the lockout expires' + ); $this->doTestLoginForm('sam@silverstripe.com' , '1nitialPassword'); - $this->assertEquals($this->session()->inst_get('loggedInAs'), $member->ID); + $this->assertEquals( + $this->session()->inst_get('loggedInAs'), + $member->ID, + 'The user can login successfully after lockout expires, if staying below the threshold' + ); i18n::set_locale($local); } From c2c8498c642dc59b177c95317a016deb2d33fcf0 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 11 Jul 2013 15:13:27 +0200 Subject: [PATCH 06/16] BehatFixtureFactory 5.3.8 compat (wrong usage of is_a()) --- dev/BehatFixtureFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/BehatFixtureFactory.php b/dev/BehatFixtureFactory.php index b4ce190de..a4f7a829a 100644 --- a/dev/BehatFixtureFactory.php +++ b/dev/BehatFixtureFactory.php @@ -9,7 +9,7 @@ class BehatFixtureFactory extends \FixtureFactory { // Copy identifier to some visible property unless its already defined. // Exclude files, since they generate their own named based on the file path. - if(!is_a($name, 'File', true)) { + if(!$name != 'File' && !is_subclass_of($name, 'File')) { foreach(array('Name', 'Title') as $fieldName) { if(singleton($name)->hasField($fieldName) && !isset($data[$fieldName])) { $data[$fieldName] = $identifier; From 71f8c1306f23af39062a173bbaca28b14a8a83c3 Mon Sep 17 00:00:00 2001 From: Jeremy Thomerson Date: Thu, 11 Jul 2013 13:40:34 +0000 Subject: [PATCH 07/16] DOCFIX: small typo causing linking error --- docs/en/reference/grid-field.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/reference/grid-field.md b/docs/en/reference/grid-field.md index 06326568b..d21f443a4 100644 --- a/docs/en/reference/grid-field.md +++ b/docs/en/reference/grid-field.md @@ -154,7 +154,7 @@ The fields displayed in the edit form are from `DataObject::getCMSFields()` The `GridFieldDetailForm` component drives the record editing form which is usually configured through the configs `GridFieldConfig_RecordEditor` and `GridFieldConfig_RelationEditor` described above. It takes its fields from `DataObject->getCMSFields()`, -but can be customized to accept different fields via its `[api:GridFieldDetailForm->setFields()](api:setFields())` method. +but can be customized to accept different fields via its `[api:GridFieldDetailForm->setFields()]` method. The component also has the ability to load and save data stored on join tables when two records are related via a "many_many" relationship, as defined through From 7fbc7527648998c0b4c1a9207c79861ef5ff876d Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Fri, 12 Jul 2013 15:07:43 +1200 Subject: [PATCH 08/16] Typo --- model/fieldtypes/ForeignKey.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/fieldtypes/ForeignKey.php b/model/fieldtypes/ForeignKey.php index 3fe7b07f3..1c51529f3 100644 --- a/model/fieldtypes/ForeignKey.php +++ b/model/fieldtypes/ForeignKey.php @@ -7,7 +7,7 @@ * @uses DropdownField * * @param string $name - * @param DataOject $object The object that the foreign key is stored on (should have a relation with $name) + * @param DataObject $object The object that the foreign key is stored on (should have a relation with $name) * * @package framework * @subpackage model From 920edf88e7b29fe25345d060cea8ef19f1291194 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 12 Jul 2013 13:16:25 +0200 Subject: [PATCH 09/16] Test allowedExtensions in UploadField, return correct HTTP status --- forms/UploadField.php | 2 ++ tests/forms/uploadfield/UploadFieldTest.php | 33 ++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/forms/UploadField.php b/forms/UploadField.php index 2766b028f..cf02fe676 100644 --- a/forms/UploadField.php +++ b/forms/UploadField.php @@ -525,6 +525,7 @@ class UploadField extends FileField { } // Get the uploaded file into a new file object. + // The loadIntoFile() method also validates constraints like allowed extensions try { $this->upload->loadIntoFile($tmpfile, $fileObject, $this->folderName); } catch (Exception $e) { @@ -559,6 +560,7 @@ class UploadField extends FileField { } $response = new SS_HTTPResponse(Convert::raw2json(array($return))); $response->addHeader('Content-Type', 'text/plain'); + if($return['error']) $response->setStatusCode(403); return $response; } diff --git a/tests/forms/uploadfield/UploadFieldTest.php b/tests/forms/uploadfield/UploadFieldTest.php index 9543414ab..6da1d3844 100644 --- a/tests/forms/uploadfield/UploadFieldTest.php +++ b/tests/forms/uploadfield/UploadFieldTest.php @@ -123,6 +123,33 @@ class UploadFieldTest extends FunctionalTest { $this->assertEquals($record->ManyManyFiles()->Last()->Name, $tmpFileName); } + /** + * Partially covered by {@link UploadTest->testUploadAcceptsAllowedExtension()}, + * but this test additionally verifies that those constraints are actually enforced + * in this controller method. + */ + public function testAllowedExtensions() { + $this->loginWithPermission('ADMIN'); + + $invalidFile = 'invalid.php'; + $_FILES = array('AllowedExtensionsField' => $this->getUploadFile($invalidFile)); + $response = $this->post( + 'UploadFieldTest_Controller/Form/field/AllowedExtensionsField/upload', + array('AllowedExtensionsField' => $this->getUploadFile($invalidFile)) + ); + $this->assertTrue($response->isError()); + $this->assertContains('Extension is not allowed', $response->getBody()); + + $validFile = 'valid.jpg'; + $_FILES = array('AllowedExtensionsField' => $this->getUploadFile($validFile)); + $response = $this->post( + 'UploadFieldTest_Controller/Form/field/AllowedExtensionsField/upload', + array('AllowedExtensionsField' => $this->getUploadFile($validFile)) + ); + $this->assertFalse($response->isError()); + $this->assertNotContains('Extension is not allowed', $response->getBody()); + } + public function testAllowedMaxFileNumberWithHasOne() { $this->loginWithPermission('ADMIN'); @@ -831,6 +858,9 @@ class UploadFieldTest_Controller extends Controller implements TestOnly { $fieldCanAttachExisting->setConfig('canAttachExisting', false); $fieldCanAttachExisting->setRecord($record); + $fieldAllowedExtensions = new UploadField('AllowedExtensionsField'); + $fieldAllowedExtensions->getValidator()->setAllowedExtensions(array('jpg')); + $form = new Form( $this, 'Form', @@ -847,7 +877,8 @@ class UploadFieldTest_Controller extends Controller implements TestOnly { $fieldDisabled, $fieldSubfolder, $fieldCanUploadFalse, - $fieldCanAttachExisting + $fieldCanAttachExisting, + $fieldAllowedExtensions ), new FieldList( new FormAction('submit') From 2427d57fa54659f7ee17b79c4a31deec036ce9a3 Mon Sep 17 00:00:00 2001 From: ARNHOE Date: Sat, 13 Jul 2013 13:00:46 +0200 Subject: [PATCH 10/16] Updated loop/if/with to be more consistent --- admin/templates/CMSBreadcrumbs.ss | 8 +++--- .../templates/CMSProfileController_Content.ss | 12 ++++---- admin/templates/CMSTabSet.ss | 6 ++-- admin/templates/Includes/BackLink_Button.ss | 4 +-- admin/templates/Includes/CMSSectionIcon.ss | 6 ++-- admin/templates/Includes/Editor_toolbar.ss | 12 ++++---- .../Includes/LeftAndMain_EditForm.ss | 28 +++++++++---------- admin/templates/Includes/LeftAndMain_Menu.ss | 14 +++++----- .../LeftAndMain_SilverStripeNavigator.ss | 16 +++++------ .../templates/Includes/ModelAdmin_Content.ss | 4 +-- .../Includes/ModelAdmin_ImportSpec.ss | 4 +-- admin/templates/Includes/ModelAdmin_Tools.ss | 2 +- admin/templates/ModelSidebar.ss | 4 +-- templates/Image_iframe.ss | 8 +++--- templates/Includes/Form.ss | 14 +++++----- .../GridFieldAddExistingAutocompleter.ss | 2 +- .../Includes/GridFieldFilterHeader_Row.ss | 2 +- templates/Includes/GridFieldFooter.ss | 4 +-- templates/Includes/GridFieldItemEditView.ss | 4 +-- .../Includes/GridFieldSortableHeader_Row.ss | 2 +- templates/Includes/GridFieldToolbarHeader.ss | 4 +-- templates/Includes/GridField_FormAction.ss | 4 +-- templates/Includes/GridField_Item.ss | 4 +-- templates/Includes/GridField_print.ss | 8 +++--- .../Includes/HtmlEditorField_viewfile.ss | 4 +-- templates/Includes/UploadField_FileButtons.ss | 6 ++-- templates/RSSFeed.ss | 8 +++--- templates/RightLabelledFieldHolder.ss | 4 +-- templates/SearchForm.ss | 10 +++---- templates/UploadField.ss | 12 ++++---- templates/forms/CheckboxField_holder.ss | 4 +-- templates/forms/CheckboxField_holder_small.ss | 4 +-- templates/forms/CheckboxSetField.ss | 6 ++-- templates/forms/CompositeField.ss | 6 ++-- templates/forms/CompositeField_holder.ss | 8 +++--- templates/forms/DropdownField.ss | 4 +-- templates/forms/FieldGroup.ss | 6 ++-- .../forms/FieldGroup_DefaultFieldHolder.ss | 6 ++-- templates/forms/FieldGroup_holder.ss | 12 ++++---- templates/forms/FormAction.ss | 6 ++-- templates/forms/FormField.ss | 4 +-- templates/forms/FormField_holder.ss | 12 ++++---- templates/forms/FormField_holder_small.ss | 8 +++--- templates/forms/OptionsetField.ss | 4 +-- templates/forms/SelectionGroup.ss | 22 +++++++++------ templates/forms/TabSet.ss | 8 +++--- templates/forms/TreeDropdownField.ss | 6 ++-- 47 files changed, 176 insertions(+), 170 deletions(-) diff --git a/admin/templates/CMSBreadcrumbs.ss b/admin/templates/CMSBreadcrumbs.ss index 9ef793482..e0d9f13e5 100644 --- a/admin/templates/CMSBreadcrumbs.ss +++ b/admin/templates/CMSBreadcrumbs.ss @@ -1,15 +1,15 @@