From 4892c11aaaad10a6511d4b0451fed989c8af509c Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Tue, 22 Jan 2013 14:29:58 +0100 Subject: [PATCH 01/30] Clarified docs on isDev usage in dev/build --- docs/en/reference/database-structure.md | 2 +- docs/en/reference/urlvariabletools.md | 4 ++-- docs/en/topics/datamodel.md | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/en/reference/database-structure.md b/docs/en/reference/database-structure.md index e063ba2cd..485fef043 100644 --- a/docs/en/reference/database-structure.md +++ b/docs/en/reference/database-structure.md @@ -79,7 +79,7 @@ left-join for robustness; if there is no matching record in Page, we can return SilverStripe has a powerful tool for automatically building database schemas. We've designed it so that you should never have to build them manually. -To access it, visit (site-root)/dev/build?flush=1. This script will analyze the existing schema, compare it to what's required by your data classes, and alter the schema as required. +To access it, visit http:///dev/build?flush=1. This script will analyze the existing schema, compare it to what's required by your data classes, and alter the schema as required. Put the ?flush=1 on the end if you've added PHP files, so that the rest of the system will find these new classes. diff --git a/docs/en/reference/urlvariabletools.md b/docs/en/reference/urlvariabletools.md index 3c4b09276..e373b149c 100644 --- a/docs/en/reference/urlvariabletools.md +++ b/docs/en/reference/urlvariabletools.md @@ -26,8 +26,8 @@ Append the option and corresponding value to your URL in your browser's address | URL Variable | | Values | | Description | | ------------ | | ------ | | ----------- | - | isDev | | 1 | | Put the site into [development mode](/topics/debugging), enabling debugging messages to the browser on a live server. For security, you'll be asked to log in with an administrator log-in | - | isTest | | 1 | | Put the site into [test mode](/topics/debugging), enabling debugging messages to the admin email and generic errors to the browser on a live server | + | isDev | | 1 | | Put the site into [development mode](/topics/debugging), enabling debugging messages to the browser on a live server. For security, you'll be asked to log in with an administrator log-in. Will persist for the current browser session. | + | isTest | | 1 | | See above. | | debug | | 1 | | Show a collection of debugging information about the director / controller operation | | debug_request | | 1 | | Show all steps of the request from initial `[api:HTTPRequest]` to `[api:Controller]` to Template Rendering | diff --git a/docs/en/topics/datamodel.md b/docs/en/topics/datamodel.md index bf3aed566..f47daac20 100755 --- a/docs/en/topics/datamodel.md +++ b/docs/en/topics/datamodel.md @@ -23,7 +23,8 @@ The SilverStripe database-schema is generated automatically by visiting the URL. `http:///dev/build`
-Note: You need to be logged in as an administrator to perform this command. +Note: You need to be logged in as an administrator to perform this command, +unless your site is in "[dev mode](/topics/debugging)", or the command is run through CLI.
## Querying Data From 45eb0f99f72e26c9dfd639a7222c7435d13fe8d3 Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Wed, 23 Jan 2013 14:30:08 +1300 Subject: [PATCH 02/30] FIX PHPUnit latest not working with composer installed builds When using composer, we must rely on the composer autoloader to load in PHPUnit and not try do to so ourselves, as the old PHPUnit\Autoload.php file doesnt understand how to find things in vendor --- dev/phpunit/PhpUnitWrapper_3_5.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dev/phpunit/PhpUnitWrapper_3_5.php b/dev/phpunit/PhpUnitWrapper_3_5.php index 3085ad572..13974225c 100644 --- a/dev/phpunit/PhpUnitWrapper_3_5.php +++ b/dev/phpunit/PhpUnitWrapper_3_5.php @@ -20,12 +20,14 @@ class PhpUnitWrapper_3_5 extends PhpUnitWrapper { * Initialise the wrapper class. */ public function init() { - require_once 'PHP/CodeCoverage.php'; - require_once 'PHP/CodeCoverage/Report/HTML.php'; + if(!class_exists('PHPUnit_Framework_TestCase')) { + require_once 'PHP/CodeCoverage.php'; + require_once 'PHP/CodeCoverage/Report/HTML.php'; - require_once 'PHPUnit/Autoload.php'; + require_once 'PHPUnit/Autoload.php'; - require_once 'PHP/CodeCoverage/Filter.php'; + require_once 'PHP/CodeCoverage/Filter.php'; + } } /** From 9ac3cde00beac5ebe4f03b464b5a37ad4fb0c51c Mon Sep 17 00:00:00 2001 From: Nicolaas Date: Thu, 24 Jan 2013 11:49:08 +1300 Subject: [PATCH 03/30] Fix typo in templates/Controller.ss --- templates/Controller.ss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/Controller.ss b/templates/Controller.ss index 327846588..579194e12 100644 --- a/templates/Controller.ss +++ b/templates/Controller.ss @@ -23,7 +23,7 @@

To get started with the SilverStripe framework:

  1. Create a Controller subclass (doc.silverstripe.org/framework/en/topics/controller)
  2. -
  3. Setup the routes.yml f to your Controller (doc.silverstripe.org/framework/en/reference/director#routing).
  4. +
  5. Setup the routes.yml to your Controller (doc.silverstripe.org/framework/en/reference/director#routing).
  6. Create a template for your Controller (doc.silverstripe.org/framework/en/reference/templates)
From f5749795a1498affeee728e8f01abf0e874f1584 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Thu, 24 Jan 2013 08:37:47 +1300 Subject: [PATCH 04/30] BUG Exception handling and email notification mechanism now correctly considers the stacktrace as provided by the exceptionHandler function, instead of attempting to perform a debug_backtrace further down the reporting chain (which ends up generating an unnecessarily nested stacktrace). Debug was cleaned up so that errorHandler and exceptionHandler both act consistently. As a result, the LogErrorEmailFormatter class could be simplified. This was required to fix a bug in which exceptions would not have a visible stacktrace when handled by the email logger. --- dev/Debug.php | 6 +++--- dev/LogErrorEmailFormatter.php | 7 ++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/dev/Debug.php b/dev/Debug.php index c23719e00..9d42ddcc2 100644 --- a/dev/Debug.php +++ b/dev/Debug.php @@ -698,13 +698,13 @@ function errorHandler($errno, $errstr, $errfile, $errline) { case E_ERROR: case E_CORE_ERROR: case E_USER_ERROR: - Debug::fatalHandler($errno, $errstr, $errfile, $errline, null); + Debug::fatalHandler($errno, $errstr, $errfile, $errline, debug_backtrace()); break; case E_WARNING: case E_CORE_WARNING: case E_USER_WARNING: - Debug::warningHandler($errno, $errstr, $errfile, $errline, null); + Debug::warningHandler($errno, $errstr, $errfile, $errline, debug_backtrace()); break; case E_NOTICE: @@ -712,7 +712,7 @@ function errorHandler($errno, $errstr, $errfile, $errline) { case E_DEPRECATED: case E_USER_DEPRECATED: case E_STRICT: - Debug::noticeHandler($errno, $errstr, $errfile, $errline, null); + Debug::noticeHandler($errno, $errstr, $errfile, $errline, debug_backtrace()); break; } } diff --git a/dev/LogErrorEmailFormatter.php b/dev/LogErrorEmailFormatter.php index 712b9835d..370a6d5e5 100644 --- a/dev/LogErrorEmailFormatter.php +++ b/dev/LogErrorEmailFormatter.php @@ -41,11 +41,8 @@ class SS_LogErrorEmailFormatter implements Zend_Log_Formatter_Interface { $data .= "

" . "[$errorType] $errstr
$errfile:$errline\n
\n
\n

\n"; - // Get a backtrace, filtering out debug method calls - $data .= SS_Backtrace::backtrace(true, false, array( - 'SS_LogErrorEmailFormatter->format', - 'SS_LogEmailWriter->_write' - )); + // Render the provided backtrace + $data .= SS_Backtrace::get_rendered_backtrace($errcontext); // Compile extra data $blacklist = array('message', 'timestamp', 'priority', 'priorityName'); From 657c14fd820179be4ffc7cca5aadb42bfeaba42a Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Thu, 24 Jan 2013 13:28:58 +1300 Subject: [PATCH 05/30] Added test cases for exception error logging --- tests/dev/LogTest.php | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/dev/LogTest.php b/tests/dev/LogTest.php index 0cafd7c1d..b41a436ab 100644 --- a/tests/dev/LogTest.php +++ b/tests/dev/LogTest.php @@ -65,6 +65,49 @@ class SS_LogTest extends SapphireTest { ); } + protected function exceptionGeneratorThrower() { + throw new Exception("thrown from SS_LogTest::testExceptionGeneratorTop"); + } + + protected function exceptionGenerator() { + $this->exceptionGeneratorThrower(); + } + + public function testEmailException() { + $testEmailWriter = new SS_LogEmailWriter('test@test.com'); + SS_Log::add_writer($testEmailWriter, SS_Log::ERR); + + // Trigger exception handling mechanism + try { + $this->exceptionGenerator(); + } catch(Exception $exception) { + // Mimics exceptionHandler, but without the exit(1) + SS_Log::log( + array( + 'errno' => E_USER_ERROR, + 'errstr' => ("Uncaught " . get_class($exception) . ": " . $exception->getMessage()), + 'errfile' => $exception->getFile(), + 'errline' => $exception->getLine(), + 'errcontext' => $exception->getTrace() + ), + SS_Log::ERR + ); + } + + // Ensure email is sent + $this->assertEmailSent('test@test.com'); + + // Begin parsing of email body + $email = $this->findEmail('test@test.com'); + $parser = new CSSContentParser($email['htmlContent']); + + // Check that the first three lines of the stacktrace are correct + $stacktrace = $parser->getByXpath('//body/div[1]/ul[1]'); + $this->assertContains('SS_LogTest->exceptionGeneratorThrower()', $stacktrace[0]->li[0]->asXML()); + $this->assertContains('SS_LogTest->exceptionGenerator()', $stacktrace[0]->li[1]->asXML()); + $this->assertContains('SS_LogTest->testEmailException()', $stacktrace[0]->li[2]->asXML()); + } + public function testSubclassedLogger() { $this->assertTrue(SS_Log::get_logger() !== SS_LogTest_NewLogger::get_logger()); } From acfc0be471735a7f71954c77b9d4343fd0f91cad Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Tue, 29 Jan 2013 14:11:52 +1300 Subject: [PATCH 06/30] Document that yaml files shouldnt be served directly --- docs/en/installation/nginx.md | 9 +++++++-- docs/en/installation/webserver.md | 16 +++++++++++++++- docs/en/topics/security.md | 10 ++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/docs/en/installation/nginx.md b/docs/en/installation/nginx.md index 3a8d34e35..b9390385d 100644 --- a/docs/en/installation/nginx.md +++ b/docs/en/installation/nginx.md @@ -19,7 +19,7 @@ configuration settings: index index.php index.html index.htm; server_name example.com; - + include silverstripe3; include htaccess; } @@ -29,7 +29,7 @@ Here is the include file `silverstripe3`: location / { try_files $uri @silverstripe; } - + location @silverstripe { include fastcgi_params; @@ -68,6 +68,11 @@ Here is the include file `htaccess`: try_files $uri $uri/ =404; } + # Block access to yaml files + location ~ \.yml$ { + deny all; + } + # cms & framework .htaccess rules location ~ ^/(cms|framework|mysite)/.*\.(php|php[345]|phtml|inc)$ { deny all; diff --git a/docs/en/installation/webserver.md b/docs/en/installation/webserver.md index dffadc211..addf9319f 100644 --- a/docs/en/installation/webserver.md +++ b/docs/en/installation/webserver.md @@ -26,4 +26,18 @@ name' and the default login details. Follow the questions and select the *instal ## Issues? -If the above steps don't work for any reason have a read of the [Common Problems](common-problems) section. \ No newline at end of file +If the above steps don't work for any reason have a read of the [Common Problems](common-problems) section. + +## Security notes + +### Yaml + +For the reasons explained in [security](/topics/security) Yaml files are blocked by default by the .htaccess file +provided by the SilverStripe installer module. + +To allow serving yaml files from a specific directory, add code like this to an .htaccess file in that directory + + + Order allow,deny + Allow from all + diff --git a/docs/en/topics/security.md b/docs/en/topics/security.md index 5d20481af..fff3e792d 100644 --- a/docs/en/topics/security.md +++ b/docs/en/topics/security.md @@ -363,6 +363,16 @@ file in the assets directory. This requires PHP to be loaded as an Apache modul php_flag engine off Options -ExecCGI -Includes -Indexes +### Don't allow access to .yml files + +Yaml files are often used to store sensitive or semi-sensitive data for use by SilverStripe framework (for instance, +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 ## Related From bec5ae188625c148568429ad308a08b0590d485e Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Tue, 29 Jan 2013 14:20:12 +1300 Subject: [PATCH 07/30] Include code to block yaml files in installer generated .htaccess --- dev/install/install.php5 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dev/install/install.php5 b/dev/install/install.php5 index d3ec3783e..ae333d319 100644 --- a/dev/install/install.php5 +++ b/dev/install/install.php5 @@ -1268,6 +1268,13 @@ HTML; Deny from all +# This denies access to all yml files, since developers might include sensitive +# information in them. See the docs for work-arounds to serve some yaml files + + Order allow,deny + Deny from all + + ErrorDocument 404 /assets/error-404.html ErrorDocument 500 /assets/error-500.html From 048ba5b3069b9412a50adb5e2e8230cba2124524 Mon Sep 17 00:00:00 2001 From: drzax Date: Tue, 29 Jan 2013 14:35:51 +1000 Subject: [PATCH 08/30] MINOR Formatting fix --- docs/en/reference/sqlquery.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/reference/sqlquery.md b/docs/en/reference/sqlquery.md index 83f2684c6..e02ecf39c 100644 --- a/docs/en/reference/sqlquery.md +++ b/docs/en/reference/sqlquery.md @@ -37,8 +37,8 @@ We'll explain some ways to use *SELECT* with the full power of SQL, but still maintain a connection to the ORM where possible.
- Please read our ["security" topic](/topics/security) to find out - how to sanitize user input before using it in SQL queries. +Please read our ["security" topic](/topics/security) to find out +how to sanitize user input before using it in SQL queries.
## Usage @@ -140,4 +140,4 @@ An alternative approach would be a custom getter in the object definition. * [datamodel](/topics/datamodel) * `[api:DataObject]` -* [database-structure](database-structure) \ No newline at end of file +* [database-structure](database-structure) From 7f4541e9f04feee1fb7796ed3bc48971bcdda402 Mon Sep 17 00:00:00 2001 From: Nicolaas Date: Fri, 18 Jan 2013 19:07:35 +1300 Subject: [PATCH 09/30] Update docs/en/changelogs/3.0.0.md minor typo --- docs/en/changelogs/3.0.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/changelogs/3.0.0.md b/docs/en/changelogs/3.0.0.md index 6c49fe674..e8ed866f7 100644 --- a/docs/en/changelogs/3.0.0.md +++ b/docs/en/changelogs/3.0.0.md @@ -116,7 +116,7 @@ Any arrays you pass as values to `update()` will be automatically merged. To rep Note the different options for the third parameter of `get()`: - * `Config::INHERITED` will only get the configuration set for the specific class, not any of it's parents. + * `Config::UNINHERITED` will only get the configuration set for the specific class, not any of it's parents. * `Config::FIRST_SET` will inherit configuration from parents, but stop on the first class that actually provides a value. * `Config::EXCLUDE_EXTRA_SOURCES` will not use additional static sources (such as those defined on extensions) From 7026a488e617f7a1b7e31ee598a5c340d12397b3 Mon Sep 17 00:00:00 2001 From: Mateusz Uzdowski Date: Mon, 23 May 2011 18:12:31 +1200 Subject: [PATCH 10/30] BUGFIX: for date manipulation use the SS_Datetime::now, otherwise it does not respect the mock date. --- model/fieldtypes/Date.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/model/fieldtypes/Date.php b/model/fieldtypes/Date.php index f789dd82c..effeda1b6 100644 --- a/model/fieldtypes/Date.php +++ b/model/fieldtypes/Date.php @@ -302,7 +302,7 @@ class Date extends DBField { * @return boolean */ public function InPast() { - return strtotime($this->value) < time(); + return strtotime($this->value) < SS_Datetime::now()->Format('U'); } /** @@ -310,7 +310,7 @@ class Date extends DBField { * @return boolean */ public function InFuture() { - return strtotime($this->value) > time(); + return strtotime($this->value) > SS_Datetime::now()->Format('U'); } /** @@ -318,7 +318,7 @@ class Date extends DBField { * @return boolean */ public function IsToday() { - return (date('Y-m-d', strtotime($this->value)) == date('Y-m-d', time())); + return (date('Y-m-d', strtotime($this->value)) == SS_Datetime::now()->Format('Y-m-d')); } /** From 5e6f5f9f7e3e15df4056873df60b8be64189e01e Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Thu, 10 Jan 2013 12:33:20 +1300 Subject: [PATCH 11/30] NEW: Allow configuration of send_all_emails_to, ccs_all_emails_to, and bcc_all_emails_to via the config system. --- conf/ConfigureFromEnv.php | 2 +- dev/SapphireTest.php | 3 +- email/Email.php | 66 +++++++++++++++++++++++++++++---------- 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/conf/ConfigureFromEnv.php b/conf/ConfigureFromEnv.php index a994f2880..5fc8a6461 100644 --- a/conf/ConfigureFromEnv.php +++ b/conf/ConfigureFromEnv.php @@ -105,7 +105,7 @@ if(defined('SS_DATABASE_USERNAME') && defined('SS_DATABASE_PASSWORD')) { } if(defined('SS_SEND_ALL_EMAILS_TO')) { - Email::send_all_emails_to(SS_SEND_ALL_EMAILS_TO); + Config::inst()->update("Email","send_all_emails_to", SS_SEND_ALL_EMAILS_TO); } if(defined('SS_DEFAULT_ADMIN_USERNAME')) { diff --git a/dev/SapphireTest.php b/dev/SapphireTest.php index efeb8f842..ab555b952 100644 --- a/dev/SapphireTest.php +++ b/dev/SapphireTest.php @@ -206,7 +206,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase { $className = get_class($this); $fixtureFile = eval("return {$className}::\$fixture_file;"); $prefix = defined('SS_DATABASE_PREFIX') ? SS_DATABASE_PREFIX : 'ss_'; - + // Todo: this could be a special test model $this->model = DataModel::inst(); @@ -263,6 +263,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase { $this->originalMailer = Email::mailer(); $this->mailer = new TestMailer(); Email::set_mailer($this->mailer); + Config::inst()->remove('Email', 'send_all_emails_to'); Email::send_all_emails_to(null); // Preserve memory settings diff --git a/email/Email.php b/email/Email.php index 0a95099be..9c82f014a 100644 --- a/email/Email.php +++ b/email/Email.php @@ -125,16 +125,43 @@ class Email extends ViewableData { static $admin_email_address = ''; /** + * Send every email generated by the Email class to the given address. + * + * It will also add " [addressed to (email), cc to (email), bcc to (email)]" to the end of the subject line + * + * To set this, set Email.send_all_emails_to in your yml config file. + * It can also be set in _ss_environment.php with SS_SEND_ALL_EMAILS_TO. + * * @param string $send_all_emails_to Email-Address */ protected static $send_all_emails_to = null; /** + * BCC every email generated by the Email class to the given address. + * It won't affect the original delivery in the same way that send_all_emails_to does. It just adds a BCC header + * with the given email address. Note that you can only call this once - subsequent calls will overwrite the + * configuration variable. + * + * This can be used when you have a system that relies heavily on email and you want someone to be checking all + * correspondence. + * + * To set this, set Email.bcc_all_emails_to in your yml config file. + * * @param string $bcc_all_emails_to Email-Address */ protected static $bcc_all_emails_to = null; /** + * CC every email generated by the Email class to the given address. + * It won't affect the original delivery in the same way that send_all_emails_to does. It just adds a CC header + * with the given email address. Note that you can only call this once - subsequent calls will overwrite the + * configuration variable. + * + * This can be used when you have a system that relies heavily on email and you want someone to be checking all + * correspondence. + * + * To set this, set Email.cc_all_emails_to in your yml config file. + * * @param string $cc_all_emails_to Email-Address */ protected static $cc_all_emails_to = null; @@ -400,30 +427,32 @@ class Email extends ViewableData { $to = $this->to; $subject = $this->subject; - if(self::$send_all_emails_to) { + if($sendAllTo = $this->config()->send_all_emails_to) { $subject .= " [addressed to $to"; - $to = self::$send_all_emails_to; + $to = $sendAllTo; if($this->cc) $subject .= ", cc to $this->cc"; if($this->bcc) $subject .= ", bcc to $this->bcc"; $subject .= ']'; + unset($headers['Cc']); + unset($headers['Bcc']); } else { if($this->cc) $headers['Cc'] = $this->cc; if($this->bcc) $headers['Bcc'] = $this->bcc; } - if(self::$cc_all_emails_to) { + if($ccAllTo = $this->config()->cc_all_emails_to) { if(!empty($headers['Cc']) && trim($headers['Cc'])) { - $headers['Cc'] .= ', ' . self::$cc_all_emails_to; + $headers['Cc'] .= ', ' . $ccAllTo; } else { - $headers['Cc'] = self::$cc_all_emails_to; + $headers['Cc'] = $ccAllTo; } } - if(self::$bcc_all_emails_to) { + if($bccAllTo = $this->config()->bcc_all_emails_to) { if(!empty($headers['Bcc']) && trim($headers['Bcc'])) { - $headers['Bcc'] .= ', ' . self::$bcc_all_emails_to; + $headers['Bcc'] .= ', ' . $bccAllTo; } else { - $headers['Bcc'] = self::$bcc_all_emails_to; + $headers['Bcc'] = $bccAllTo; } } @@ -459,34 +488,37 @@ class Email extends ViewableData { if(project()) $headers['X-SilverStripeSite'] = project(); + $to = $this->to; $subject = $this->subject; - if(self::$send_all_emails_to) { + if($sendAllTo = $this->config()->send_all_emails_to) { $subject .= " [addressed to $to"; - $to = self::$send_all_emails_to; + $to = $sendAllTo; if($this->cc) $subject .= ", cc to $this->cc"; if($this->bcc) $subject .= ", bcc to $this->bcc"; $subject .= ']'; unset($headers['Cc']); unset($headers['Bcc']); + } else { if($this->cc) $headers['Cc'] = $this->cc; if($this->bcc) $headers['Bcc'] = $this->bcc; } - if(self::$cc_all_emails_to) { + + if($ccAllTo = $this->config()->cc_all_emails_to) { if(!empty($headers['Cc']) && trim($headers['Cc'])) { - $headers['Cc'] .= ', ' . self::$cc_all_emails_to; + $headers['Cc'] .= ', ' . $ccAllTo; } else { - $headers['Cc'] = self::$cc_all_emails_to; + $headers['Cc'] = $ccAllTo; } } - - if(self::$bcc_all_emails_to) { + + if($bccAllTo = $this->config()->bcc_all_emails_to) { if(!empty($headers['Bcc']) && trim($headers['Bcc'])) { - $headers['Bcc'] .= ', ' . self::$bcc_all_emails_to; + $headers['Bcc'] .= ', ' . $bccAllTo; } else { - $headers['Bcc'] = self::$bcc_all_emails_to; + $headers['Bcc'] = $bccAllTo; } } From c3a3ff4438f91d23ddc8ef1617e866c3a1f0692d Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Wed, 2 Mar 2011 18:23:07 +1300 Subject: [PATCH 12/30] NEW: Added Email::send_all_emails_from() setting. --- conf/ConfigureFromEnv.php | 4 ++++ email/Email.php | 31 +++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/conf/ConfigureFromEnv.php b/conf/ConfigureFromEnv.php index 5fc8a6461..ffa0beb99 100644 --- a/conf/ConfigureFromEnv.php +++ b/conf/ConfigureFromEnv.php @@ -41,6 +41,7 @@ * * Email: * - SS_SEND_ALL_EMAILS_TO: If you set this define, all emails will be redirected to this address. + * - SS_SEND_ALL_EMAILS_FROM: If you set this define, all emails will be send from this address. * * @package framework * @subpackage core @@ -107,6 +108,9 @@ if(defined('SS_DATABASE_USERNAME') && defined('SS_DATABASE_PASSWORD')) { if(defined('SS_SEND_ALL_EMAILS_TO')) { Config::inst()->update("Email","send_all_emails_to", SS_SEND_ALL_EMAILS_TO); } +if(defined('SS_SEND_ALL_EMAILS_FROM')) { + Config::inst()->update("Email","send_all_emails_from", SS_SEND_ALL_EMAILS_FROM); +} if(defined('SS_DEFAULT_ADMIN_USERNAME')) { if(!defined('SS_DEFAULT_ADMIN_PASSWORD')) { diff --git a/email/Email.php b/email/Email.php index 9c82f014a..e677e7969 100644 --- a/email/Email.php +++ b/email/Email.php @@ -135,6 +135,17 @@ class Email extends ViewableData { * @param string $send_all_emails_to Email-Address */ protected static $send_all_emails_to = null; + + /** + * Send every email generated by the Email class *from* the given address. + * It will also add " [, from to (email)]" to the end of the subject line + * + * To set this, set Email.send_all_emails_from in your yml config file. + * It can also be set in _ss_environment.php with SS_SEND_ALL_EMAILS_FROM. + * + * @param string $send_all_emails_from Email-Address + */ + protected static $send_all_emails_from = null; /** * BCC every email generated by the Email class to the given address. @@ -426,6 +437,7 @@ class Email extends ViewableData { if(project()) $headers['X-SilverStripeSite'] = project(); $to = $this->to; + $from = $this->from; $subject = $this->subject; if($sendAllTo = $this->config()->send_all_emails_to) { $subject .= " [addressed to $to"; @@ -456,9 +468,14 @@ class Email extends ViewableData { } } + if($sendAllfrom = $this->config()->send_all_emails_from) { + if($from) $subject .= " [from $from]"; + $from = $sendAllfrom; + } + Requirements::restore(); - return self::mailer()->sendPlain($to, $this->from, $subject, $this->body, $this->attachments, $headers); + return self::mailer()->sendPlain($to, $from, $subject, $this->body, $this->attachments, $headers); } /** @@ -490,6 +507,7 @@ class Email extends ViewableData { $to = $this->to; + $from = $this->from; $subject = $this->subject; if($sendAllTo = $this->config()->send_all_emails_to) { $subject .= " [addressed to $to"; @@ -521,10 +539,15 @@ class Email extends ViewableData { $headers['Bcc'] = $bccAllTo; } } - + + if($sendAllfrom = $this->config()->send_all_emails_from) { + if($from) $subject .= " [from $from]"; + $from = $sendAllfrom; + } + Requirements::restore(); - return self::mailer()->sendHTML($to, $this->from, $subject, $this->body, $this->attachments, $headers, + return self::mailer()->sendHTML($to, $from, $subject, $this->body, $this->attachments, $headers, $this->plaintext_body); } @@ -558,7 +581,7 @@ class Email extends ViewableData { public static function send_all_emails_to($emailAddress) { self::$send_all_emails_to = $emailAddress; } - + /** * CC every email generated by the Email class to the given address. * It won't affect the original delivery in the same way that send_all_emails_to does. It just adds a CC header From b7a1db7ce31f4712a6578d9db88f6f0601a0a11a Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Sat, 21 May 2011 14:42:26 +1200 Subject: [PATCH 13/30] FIX: Set up the test mailer before loading the fixture, in case fixture-creation causes emails to be generated. --- dev/SapphireTest.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/dev/SapphireTest.php b/dev/SapphireTest.php index ab555b952..20faa60e4 100644 --- a/dev/SapphireTest.php +++ b/dev/SapphireTest.php @@ -205,8 +205,16 @@ class SapphireTest extends PHPUnit_Framework_TestCase { $className = get_class($this); $fixtureFile = eval("return {$className}::\$fixture_file;"); + $prefix = defined('SS_DATABASE_PREFIX') ? SS_DATABASE_PREFIX : 'ss_'; + // Set up email + $this->originalMailer = Email::mailer(); + $this->mailer = new TestMailer(); + Email::set_mailer($this->mailer); + Config::inst()->remove('Email', 'send_all_emails_to'); + Email::send_all_emails_to(null); + // Todo: this could be a special test model $this->model = DataModel::inst(); @@ -259,13 +267,6 @@ class SapphireTest extends PHPUnit_Framework_TestCase { $this->logInWithPermission("ADMIN"); } - // Set up email - $this->originalMailer = Email::mailer(); - $this->mailer = new TestMailer(); - Email::set_mailer($this->mailer); - Config::inst()->remove('Email', 'send_all_emails_to'); - Email::send_all_emails_to(null); - // Preserve memory settings $this->originalMemoryLimit = ini_get('memory_limit'); From 33a1fc7b3adeedb92f138321243deb3bffd66cc4 Mon Sep 17 00:00:00 2001 From: Carlos Barberis Date: Wed, 20 Apr 2011 17:37:17 +1200 Subject: [PATCH 14/30] FIX: Fixed operation of inlined images in Mailer, when no inlined images actually attached. --- email/Mailer.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/email/Mailer.php b/email/Mailer.php index 6a0c23a81..1202b1530 100644 --- a/email/Mailer.php +++ b/email/Mailer.php @@ -291,7 +291,7 @@ function encodeMultipart($parts, $contentType, $headers = false) { */ function wrapImagesInline($htmlContent) { global $_INLINED_IMAGES; - $_INLINED_IMAGES = null; + $_INLINED_IMAGES = array(); $replacedContent = imageRewriter($htmlContent, 'wrapImagesInline_rewriter($URL)'); @@ -303,8 +303,8 @@ function wrapImagesInline($htmlContent) { // Make all the image parts global $_INLINED_IMAGES; - foreach($_INLINED_IMAGES as $url => $cid) { - $multiparts[] = encodeFileForEmail($url, false, "inline", "Content-ID: <$cid>\n"); + if($_INLINED_IMAGES) foreach($_INLINED_IMAGES as $url => $cid) { + $multiparts[] = encodeFileForEmail(BASE_PATH . '/' . $url, false, "inline", "Content-ID: <$cid>\n"); } // Merge together in a multipart @@ -312,10 +312,10 @@ function wrapImagesInline($htmlContent) { return processHeaders($headers, $body); } function wrapImagesInline_rewriter($url) { - $url = relativiseURL($url); + $url = Director::makeRelative($url); global $_INLINED_IMAGES; - if(!$_INLINED_IMAGES[$url]) { + if(!isset($_INLINED_IMAGES[$url])) { $identifier = "automatedmessage." . rand(1000,1000000000) . "@silverstripe.com"; $_INLINED_IMAGES[$url] = $identifier; } @@ -384,6 +384,7 @@ function encodeFileForEmail($file, $destFileName = false, $disposition = NULL, $ $file = array('filename' => $file); $fh = fopen($file['filename'], "rb"); if ($fh) { + $file['contents'] = ""; while(!feof($fh)) $file['contents'] .= fread($fh, 10000); fclose($fh); } @@ -393,12 +394,12 @@ function encodeFileForEmail($file, $destFileName = false, $disposition = NULL, $ if(!$destFileName) $base = basename($file['filename']); else $base = $destFileName; - $mimeType = $file['mimetype'] ? $file['mimetype'] : HTTP::get_mime_type($file['filename']); + $mimeType = !empty($file['mimetype']) ? $file['mimetype'] : HTTP::get_mime_type($file['filename']); if(!$mimeType) $mimeType = "application/unknown"; if (empty($disposition)) $disposition = isset($file['contentLocation']) ? 'inline' : 'attachment'; // Encode for emailing - if (substr($file['mimetype'], 0, 4) != 'text') { + if (substr($mimeType, 0, 4) != 'text') { $encoding = "base64"; $file['contents'] = chunk_split(base64_encode($file['contents'])); } else { From d8bfc0bb48c16aaa3235d36b43782f485b182d04 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Wed, 16 Mar 2011 16:13:14 +1300 Subject: [PATCH 15/30] API CHANGE: Added Security::set_login_url() so that you can define an alternative log-in page if you have made one yourself. --- dev/Debug.php | 2 +- security/Security.php | 22 +++++++++++++++++++++- tests/control/DirectorTest.php | 2 +- tests/security/SecurityTest.php | 11 +++++++---- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/dev/Debug.php b/dev/Debug.php index 9d42ddcc2..fef951a70 100644 --- a/dev/Debug.php +++ b/dev/Debug.php @@ -652,7 +652,7 @@ class Debug { $_SESSION['Security']['Message']['type'] = 'warning'; $_SESSION['BackURL'] = $_SERVER['REQUEST_URI']; header($_SERVER['SERVER_PROTOCOL'] . " 302 Found"); - header("Location: " . Director::baseURL() . "Security/login"); + header("Location: " . Director::baseURL() . Security::login_url()); die(); } } diff --git a/security/Security.php b/security/Security.php index f8da0e029..112fda9e2 100644 --- a/security/Security.php +++ b/security/Security.php @@ -242,7 +242,10 @@ class Security extends Controller { // Audit logging hook $controller->extend('permissionDenied', $member); - $controller->redirect("Security/login?BackURL=" . urlencode($_SERVER['REQUEST_URI'])); + $controller->redirect( + Config::inst()->get('Security', 'login_url') + . "?BackURL=" . urlencode($_SERVER['REQUEST_URI']) + ); } return; } @@ -927,8 +930,25 @@ class Security extends Controller { public static function set_ignore_disallowed_actions($flag) { self::$ignore_disallowed_actions = $flag; } + public static function ignore_disallowed_actions() { return self::$ignore_disallowed_actions; } + protected static $login_url = "Security/login"; + + /** + * Set a custom log-in URL if you have built your own log-in page. + */ + public static function set_login_url($loginUrl) { + self::$login_url = $loginUrl; + } + /** + * Get the URL of the log-in page. + * Defaults to Security/login but can be re-set with {@link set_login_url()} + */ + public static function login_url() { + return self::$login_url; + } + } diff --git a/tests/control/DirectorTest.php b/tests/control/DirectorTest.php index a157c59a6..48359bed3 100644 --- a/tests/control/DirectorTest.php +++ b/tests/control/DirectorTest.php @@ -252,7 +252,7 @@ class DirectorTest extends SapphireTest { } public function testForceSSLOnSubPagesPattern() { - $_SERVER['REQUEST_URI'] = Director::baseURL() . 'Security/login'; + $_SERVER['REQUEST_URI'] = Director::baseURL() . Config::inst()->get('Security', 'login_url'); $output = Director::forceSSL(array('/^Security/')); $this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']); } diff --git a/tests/security/SecurityTest.php b/tests/security/SecurityTest.php index 6337e1b71..3325d09f9 100644 --- a/tests/security/SecurityTest.php +++ b/tests/security/SecurityTest.php @@ -57,7 +57,10 @@ class SecurityTest extends FunctionalTest { $response = $this->get('SecurityTest_SecuredController'); $this->assertEquals(302, $response->getStatusCode()); - $this->assertContains('Security/login', $response->getHeader('Location')); + $this->assertContains( + Config::inst()->get('Security', 'login_url'), + $response->getHeader('Location') + ); $this->logInWithPermission('ADMIN'); $response = $this->get('SecurityTest_SecuredController'); @@ -74,7 +77,7 @@ class SecurityTest extends FunctionalTest { $this->session()->inst_set('loggedInAs', $member->ID); /* View the Security/login page */ - $response = $this->get('Security/login'); + $response = $this->get(Config::inst()->get('Security', 'login_url')); $items = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm input.action'); @@ -108,7 +111,7 @@ class SecurityTest extends FunctionalTest { $this->autoFollowRedirection = true; /* Attempt to get into the admin section */ - $response = $this->get('Security/login/'); + $response = $this->get(Config::inst()->get('Security', 'login_url')); $items = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm input.text'); @@ -396,7 +399,7 @@ class SecurityTest extends FunctionalTest { public function doTestLoginForm($email, $password, $backURL = 'test/link') { $this->get('Security/logout'); $this->session()->inst_set('BackURL', $backURL); - $this->get('Security/login'); + $this->get(Config::inst()->get('Security', 'login_url')); return $this->submitForm( "MemberLoginForm_LoginForm", From 1e1df8c43eb9ecb055c4abc57cc64a38096ff65c Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Mon, 12 Dec 2011 18:44:47 +1300 Subject: [PATCH 16/30] BUGFIX: Improved detection of empty HTMLText fields. --- model/fieldtypes/HTMLText.php | 15 ++++++++++++++- tests/model/HTMLTextTest.php | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/model/fieldtypes/HTMLText.php b/model/fieldtypes/HTMLText.php index bfb20e5a3..54e0ac9d6 100644 --- a/model/fieldtypes/HTMLText.php +++ b/model/fieldtypes/HTMLText.php @@ -136,8 +136,21 @@ class HTMLText extends Text { return ShortcodeParser::get_active()->parse($this->value); } + /** + * Returns true if the field has meaningful content. + * Excludes null content like

,

,etc + * + * @return boolean + */ public function exists() { - return parent::exists() && $this->value != '

'; + // If it's blank, it's blank + if(!parent::exists()) return false; + // If it's got a content tag + if(preg_match('/<(img|embed|object|iframe)[^>]*>/i', $this->value)) return true; + // If it's just one or two tags on its own (and not the above) it's empty. This might be

or

or whatever. + if(preg_match('/^[\\s]*(<[^>]+>[\\s]*){1,2}$/', $this->value)) return false; + // Otherwise its content is genuine content + return true; } public function scaffoldFormField($title = null, $params = null) { diff --git a/tests/model/HTMLTextTest.php b/tests/model/HTMLTextTest.php index f1fda34bc..a5ed4856c 100644 --- a/tests/model/HTMLTextTest.php +++ b/tests/model/HTMLTextTest.php @@ -139,4 +139,38 @@ class HTMLTextTest extends SapphireTest { $data = DBField::create_field('HTMLText', '"this is a test"'); $this->assertEquals($data->ATT(), '"this is a test"'); } + + function testExists() { + $h = new HTMLText; + $h->setValue(""); + $this->assertFalse($h->exists()); + $h->setValue("

"); + $this->assertFalse($h->exists()); + $h->setValue("

"); + $this->assertFalse($h->exists()); + $h->setValue("

"); + $this->assertFalse($h->exists()); + $h->setValue("

"); + $this->assertFalse($h->exists()); + + $h->setValue("something"); + $this->assertTrue($h->exists()); + $h->setValue(""); + $this->assertTrue($h->exists()); + $h->setValue(""); + $this->assertTrue($h->exists()); + $h->setValue("

"); + $this->assertTrue($h->exists()); + + $h->setValue(""); + $this->assertTrue($h->exists()); + $h->setValue(""); + $this->assertTrue($h->exists()); + $h->setValue(""); + $this->assertTrue($h->exists()); + + + $h->setValue("

test

"); + $this->assertTrue($h->exists()); + } } From 47e037e74ccbc70069f4339936f0984235bd1138 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Fri, 29 Apr 2011 15:12:21 +1200 Subject: [PATCH 17/30] FIX: Removed notice-level error after forms w/ required fields are made readonly. --- forms/RequiredFields.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms/RequiredFields.php b/forms/RequiredFields.php index a0e46278b..d58211413 100644 --- a/forms/RequiredFields.php +++ b/forms/RequiredFields.php @@ -35,7 +35,7 @@ class RequiredFields extends Validator { * Clears all the validation from this object. */ public function removeValidation(){ - $this->required = null; + $this->required = array(); } /** From 55f3ec1371ce80c3905c9dcdf2ece615c4ca34b8 Mon Sep 17 00:00:00 2001 From: Jean-Fabien Date: Thu, 26 May 2011 18:42:59 +1200 Subject: [PATCH 18/30] FIX: Added error message fields to default search form --- templates/SearchForm.ss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/templates/SearchForm.ss b/templates/SearchForm.ss index f3d195d0a..c6a525d1e 100644 --- a/templates/SearchForm.ss +++ b/templates/SearchForm.ss @@ -1,4 +1,9 @@
+ <% if Message %> +

$Message

+ <% else %> + + <% end_if %>
<% loop Fields %> $FieldHolder From f72c77e984d1f71e1ae6016533d0a09146da72a8 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Fri, 1 Apr 2011 10:36:04 +1300 Subject: [PATCH 19/30] MINOR: Fixed a glitch that causes warnings in cli-script execution. --- control/Director.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/control/Director.php b/control/Director.php index 86684bc67..cab2d598c 100644 --- a/control/Director.php +++ b/control/Director.php @@ -725,6 +725,9 @@ class Director implements TemplateGlobalProvider { $matched = false; if($patterns) { + // Calling from the command-line? + if(!isset($_SERVER['REQUEST_URI'])) return; + // protect portions of the site based on the pattern $relativeURL = self::makeRelative(Director::absoluteURL($_SERVER['REQUEST_URI'])); foreach($patterns as $pattern) { From f8206d15c81b161af13494e9e978794b947fb3c1 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Mon, 18 Apr 2011 16:44:11 +1200 Subject: [PATCH 20/30] BUGFIX: Prevent notice-level error in Session code when non-array is turned into an array. --- control/Session.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control/Session.php b/control/Session.php index e86616da7..d0326c5f4 100644 --- a/control/Session.php +++ b/control/Session.php @@ -415,7 +415,7 @@ class Session { protected function recursivelyApply($data, &$dest) { foreach($data as $k => $v) { if(is_array($v)) { - if(!isset($dest[$k])) $dest[$k] = array(); + if(!isset($dest[$k]) || !is_array($dest[$k])) $dest[$k] = array(); $this->recursivelyApply($v, $dest[$k]); } else { $dest[$k] = $v; From 82988d421bb28ef0e04f0fea8c6c6fd60c54c58a Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Fri, 11 Mar 2011 15:09:52 +1300 Subject: [PATCH 21/30] BUGFIX: Better error message when 401 response is corrupted. --- control/HTTPResponse.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/control/HTTPResponse.php b/control/HTTPResponse.php index 16a801838..1a0a38c06 100644 --- a/control/HTTPResponse.php +++ b/control/HTTPResponse.php @@ -220,11 +220,17 @@ class SS_HTTPResponse { "; } else { - if(!headers_sent()) { + $line = $file = null; + if(!headers_sent($file, $line)) { header($_SERVER['SERVER_PROTOCOL'] . " $this->statusCode " . $this->getStatusDescription()); foreach($this->headers as $header => $value) { header("$header: $value", true, $this->statusCode); } + } else { + // It's critical that these status codes are sent; we need to report a failure if not. + if($this->statusCode >= 300) { + user_error("Couldn't set response type to $this->statusCode because of output on line $line of $file", E_USER_WARNING); + } } // Only show error pages or generic "friendly" errors if the status code signifies From 6fcbad1a31519cdfeb0f2587100166097bba3833 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Tue, 15 Mar 2011 18:30:16 +1300 Subject: [PATCH 22/30] BUGFIX: Updated SilverStripe error handler so that log_errors still works. --- dev/Debug.php | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/dev/Debug.php b/dev/Debug.php index fef951a70..e29ee24ff 100644 --- a/dev/Debug.php +++ b/dev/Debug.php @@ -213,6 +213,7 @@ class Debug { public static function noticeHandler($errno, $errstr, $errfile, $errline, $errcontext) { if(error_reporting() == 0) return; + ini_set('display_errors', 0); // Send out the error details to the logger for writing SS_Log::log( @@ -227,7 +228,9 @@ class Debug { ); if(Director::isDev()) { - self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Notice"); + return self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Notice"); + } else { + return false; } } @@ -242,8 +245,10 @@ class Debug { */ public static function warningHandler($errno, $errstr, $errfile, $errline, $errcontext) { if(error_reporting() == 0) return; + ini_set('display_errors', 0); + if(self::$send_warnings_to) { - self::emailError(self::$send_warnings_to, $errno, $errstr, $errfile, $errline, $errcontext, "Warning"); + return self::emailError(self::$send_warnings_to, $errno, $errstr, $errfile, $errline, $errcontext, "Warning"); } // Send out the error details to the logger for writing @@ -263,8 +268,10 @@ class Debug { } if(Director::isDev()) { - self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Warning"); - } + return self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Warning"); + } else { + return false; + } } /** @@ -279,6 +286,8 @@ class Debug { * @param unknown_type $errcontext */ public static function fatalHandler($errno, $errstr, $errfile, $errline, $errcontext) { + ini_set('display_errors', 0); + if(self::$send_errors_to) { self::emailError(self::$send_errors_to, $errno, $errstr, $errfile, $errline, $errcontext, "Error"); } @@ -300,11 +309,10 @@ class Debug { } if(Director::isDev() || Director::is_cli()) { - self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Error"); + return self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Error"); } else { - self::friendlyError(); + return self::friendlyError(); } - exit(1); } /** @@ -363,6 +371,7 @@ class Debug { $renderer->writeFooter(); } } + return false; } /** @@ -679,7 +688,7 @@ function exceptionHandler($exception) { $file = $exception->getFile(); $line = $exception->getLine(); $context = $exception->getTrace(); - Debug::fatalHandler($errno, $message, $file, $line, $context); + return Debug::fatalHandler($errno, $message, $file, $line, $context); } /** @@ -698,21 +707,18 @@ function errorHandler($errno, $errstr, $errfile, $errline) { case E_ERROR: case E_CORE_ERROR: case E_USER_ERROR: - Debug::fatalHandler($errno, $errstr, $errfile, $errline, debug_backtrace()); - break; + return Debug::fatalHandler($errno, $errstr, $errfile, $errline, debug_backtrace()); case E_WARNING: case E_CORE_WARNING: case E_USER_WARNING: - Debug::warningHandler($errno, $errstr, $errfile, $errline, debug_backtrace()); - break; + return Debug::warningHandler($errno, $errstr, $errfile, $errline, debug_backtrace()); case E_NOTICE: case E_USER_NOTICE: case E_DEPRECATED: case E_USER_DEPRECATED: case E_STRICT: - Debug::noticeHandler($errno, $errstr, $errfile, $errline, debug_backtrace()); - break; + return Debug::noticeHandler($errno, $errstr, $errfile, $errline, debug_backtrace()); } } From b43bf68f9c5b1396ae50db6a1ea087efb7b3b19f Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Tue, 30 Aug 2011 23:40:29 +1200 Subject: [PATCH 23/30] MINOR: Minor fixes to FunctionalTest --- dev/TestSession.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/dev/TestSession.php b/dev/TestSession.php index 4e81e312a..afb71eb7f 100644 --- a/dev/TestSession.php +++ b/dev/TestSession.php @@ -96,8 +96,12 @@ class TestSession { $form->setField(new SimpleByName($k), $v); } - if($button) $submission = $form->submitButton(new SimpleByName($button)); - else $submission = $form->submit(); + if($button) { + $submission = $form->submitButton(new SimpleByName($button)); + if(!$submission) throw new Exception("Can't find button '$button' to submit as part of test."); + } else { + $submission = $form->submit(); + } $url = Director::makeRelative($form->getAction()->asString()); @@ -137,6 +141,15 @@ class TestSession { return $this->lastResponse; } + /** + * Return the fake HTTP_REFERER; set each time get() or post() is called. + * + * @return string + */ + public function lastUrl() { + return $this->lastUrl; + } + /** * Get the most recent response's content */ From b6fd27663aa38524ad127fb6756741689e324503 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Thu, 18 Aug 2011 23:32:40 +1200 Subject: [PATCH 24/30] MINOR: Don't throw redirection warning if redirection to the same place. --- control/Controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control/Controller.php b/control/Controller.php index b74549628..47066074f 100644 --- a/control/Controller.php +++ b/control/Controller.php @@ -454,7 +454,7 @@ class Controller extends RequestHandler implements TemplateGlobalProvider { public function redirect($url, $code=302) { if(!$this->response) $this->response = new SS_HTTPResponse(); - if($this->response->getHeader('Location')) { + if($this->response->getHeader('Location') && $this->response->getHeader('Location') != $url) { user_error("Already directed to " . $this->response->getHeader('Location') . "; now trying to direct to $url", E_USER_WARNING); return; From 9a2ba483dfe3207bb18c2571a7ee31fbbef27f38 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Thu, 1 Sep 2011 15:41:33 +1200 Subject: [PATCH 25/30] BUGFIX: Made CSRF-error wording friendlier. --- forms/Form.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/forms/Form.php b/forms/Form.php index 2417f3b84..1f30b1371 100644 --- a/forms/Form.php +++ b/forms/Form.php @@ -250,7 +250,9 @@ class Form extends RequestHandler { // Protection against CSRF attacks $token = $this->getSecurityToken(); if(!$token->checkRequest($request)) { - $this->httpError(400, "Sorry, your session has timed out."); + $this->httpError(400, _t("Form.CSRF_FAILED_MESSAGE", + "There seems to have been a technical problem. Please click the back button," + . " refresh your browser, and try again.")); } // Determine the action button clicked From c4dde9022df3b2d1bbdaa6468301ddeeaccda48f Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Tue, 16 Oct 2012 18:24:40 +1300 Subject: [PATCH 26/30] NEW: Allow hashes to be passed as ArrayList items; the will be turned into ArrayData objects. --- model/ArrayList.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/model/ArrayList.php b/model/ArrayList.php index 4335b405c..cd1a203d0 100644 --- a/model/ArrayList.php +++ b/model/ArrayList.php @@ -55,6 +55,9 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta * @return ArrayIterator */ public function getIterator() { + foreach($this->items as $i => $item) { + if(is_array($item)) $this->items[$i] = new ArrayData($item); + } return new ArrayIterator($this->items); } From f54697930efb4e6acf174a4b0fc570521a8b8788 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Wed, 16 Mar 2011 15:45:43 +1300 Subject: [PATCH 27/30] API CHANGE: Add a PermissionFailureException that can be thrown to trigger a log-in. --- control/RequestHandler.php | 2 ++ security/PermissionFailureException.php | 10 ++++++++++ 2 files changed, 12 insertions(+) create mode 100644 security/PermissionFailureException.php diff --git a/control/RequestHandler.php b/control/RequestHandler.php index c67793762..38221aa75 100644 --- a/control/RequestHandler.php +++ b/control/RequestHandler.php @@ -184,6 +184,8 @@ class RequestHandler extends ViewableData { $result = $this->$action($request); } catch(SS_HTTPResponse_Exception $responseException) { $result = $responseException->getResponse(); + } catch(PermissionFailureException $e) { + $result = Security::permissionFailure(null, $e->getMessage()); } } else { return $this->httpError(403, "Action '$action' isn't allowed on class " . get_class($this)); diff --git a/security/PermissionFailureException.php b/security/PermissionFailureException.php new file mode 100644 index 000000000..97b50e39c --- /dev/null +++ b/security/PermissionFailureException.php @@ -0,0 +1,10 @@ + Date: Thu, 18 Aug 2011 23:32:02 +1200 Subject: [PATCH 28/30] FIX: Ensure that Security views respect redirections triggered by Page_Controller::init() Sometimes Page_Controller::init() will trigger a redirection. For example, it may redirect to a canonical URL. In this case, the Security views, which co-opt Page_Controller, need to respect this. --- security/Security.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/security/Security.php b/security/Security.php index 112fda9e2..9c78bd397 100644 --- a/security/Security.php +++ b/security/Security.php @@ -347,7 +347,6 @@ class Security extends Controller { } } - $customCSS = project() . '/css/tabs.css'; if(Director::fileExists($customCSS)) { Requirements::css($customCSS); @@ -363,11 +362,12 @@ class Security extends Controller { $controller = Page_Controller::create($tmpPage); $controller->setDataModel($this->model); $controller->init(); - //Controller::$currentController = $controller; } else { $controller = $this; } + // if the controller calls Director::redirect(), this will break early + if(($response = $controller->getResponse()) && $response->isFinished()) return $response; $content = ''; $forms = $this->GetLoginForms(); @@ -461,6 +461,9 @@ class Security extends Controller { $controller = $this; } + // if the controller calls Director::redirect(), this will break early + if(($response = $controller->getResponse()) && $response->isFinished()) return $response; + $customisedController = $controller->customise(array( 'Content' => '

' . @@ -520,6 +523,9 @@ class Security extends Controller { $controller = $this; } + // if the controller calls Director::redirect(), this will break early + if(($response = $controller->getResponse()) && $response->isFinished()) return $response; + $email = Convert::raw2xml(rawurldecode($request->param('ID')) . '.' . $request->getExtension()); $customisedController = $controller->customise(array( @@ -583,6 +589,9 @@ class Security extends Controller { $controller = $this; } + // if the controller calls Director::redirect(), this will break early + if(($response = $controller->getResponse()) && $response->isFinished()) return $response; + // Extract the member from the URL. $member = null; if (isset($_REQUEST['m'])) { From c048a019f69d8e7b44f50576c7d1d35765b0aa58 Mon Sep 17 00:00:00 2001 From: jean Date: Thu, 9 Feb 2012 15:58:05 +1300 Subject: [PATCH 29/30] BUGFIX Avoid infinite redirection when logging out and when showing a custom login page after displaying the draft version of a page. --- security/Member.php | 5 ++++- security/Security.php | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/security/Member.php b/security/Member.php index a28d24ec6..082c221fb 100644 --- a/security/Member.php +++ b/security/Member.php @@ -421,9 +421,12 @@ class Member extends DataObject implements TemplateGlobalProvider { $this->extend('memberLoggedOut'); $this->RememberLoginToken = null; - Cookie::set('alc_enc', null); + Cookie::set('alc_enc', null); // // Clear the Remember Me cookie Cookie::forceExpiry('alc_enc'); + // Switch back to live in order to avoid infinite loops when redirecting to the login screen (if this login screen is versioned) + Session::clear('readingMode'); + $this->write(); // Audit logging hook diff --git a/security/Security.php b/security/Security.php index 9c78bd397..9bc78516f 100644 --- a/security/Security.php +++ b/security/Security.php @@ -951,7 +951,7 @@ class Security extends Controller { */ public static function set_login_url($loginUrl) { self::$login_url = $loginUrl; - } +} /** * Get the URL of the log-in page. * Defaults to Security/login but can be re-set with {@link set_login_url()} From c9f728fefb0d2cbbefd488f4abd9790183d5cc86 Mon Sep 17 00:00:00 2001 From: Simon Welsh Date: Wed, 30 Jan 2013 09:17:47 +1300 Subject: [PATCH 30/30] FIX Only check the remember token if a user exists --- security/Member.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/security/Member.php b/security/Member.php index 082c221fb..ac61473a5 100644 --- a/security/Member.php +++ b/security/Member.php @@ -384,9 +384,11 @@ class Member extends DataObject implements TemplateGlobalProvider { $member = DataObject::get_one("Member", "\"Member\".\"ID\" = '$SQL_uid'"); // check if autologin token matches - $hash = $member->encryptWithUserSettings($token); - if($member && (!$member->RememberLoginToken || $member->RememberLoginToken != $hash)) { - $member = null; + if($member) { + $hash = $member->encryptWithUserSettings($token); + if(!$member->RememberLoginToken || $member->RememberLoginToken !== $hash) { + $member = null; + } } if($member) {