mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge remote-tracking branch 'origin/3.0' into 3.1
Conflicts: email/Mailer.php
This commit is contained in:
commit
634c91c6ff
@ -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
|
||||
@ -105,7 +106,10 @@ 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_SEND_ALL_EMAILS_FROM')) {
|
||||
Config::inst()->update("Email","send_all_emails_from", SS_SEND_ALL_EMAILS_FROM);
|
||||
}
|
||||
|
||||
if(defined('SS_DEFAULT_ADMIN_USERNAME')) {
|
||||
|
@ -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;
|
||||
|
@ -674,6 +674,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) {
|
||||
|
@ -241,11 +241,17 @@ class SS_HTTPResponse {
|
||||
<meta http-equiv=\"refresh\" content=\"1; url=$url\" />
|
||||
<script type=\"text/javascript\">setTimeout('window.location.href = \"$url\"', 50);</script>";
|
||||
} 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
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -223,6 +223,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(
|
||||
@ -237,7 +238,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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,8 +255,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
|
||||
@ -273,8 +278,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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -289,6 +296,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");
|
||||
}
|
||||
@ -310,11 +319,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -373,6 +381,7 @@ class Debug {
|
||||
$renderer->writeFooter();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -497,7 +506,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();
|
||||
}
|
||||
}
|
||||
@ -524,7 +533,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -543,21 +552,18 @@ function errorHandler($errno, $errstr, $errfile, $errline) {
|
||||
case E_ERROR:
|
||||
case E_CORE_ERROR:
|
||||
case E_USER_ERROR:
|
||||
Debug::fatalHandler($errno, $errstr, $errfile, $errline, null);
|
||||
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, null);
|
||||
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, null);
|
||||
break;
|
||||
return Debug::noticeHandler($errno, $errstr, $errfile, $errline, debug_backtrace());
|
||||
}
|
||||
}
|
||||
|
@ -41,11 +41,8 @@ class SS_LogErrorEmailFormatter implements Zend_Log_Formatter_Interface {
|
||||
$data .= "<p style=\"color: white; background-color: $colour; margin: 0\">"
|
||||
. "[$errorType] $errstr<br />$errfile:$errline\n<br />\n<br />\n</p>\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');
|
||||
|
@ -205,7 +205,15 @@ 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,12 +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);
|
||||
Email::send_all_emails_to(null);
|
||||
|
||||
// Preserve memory settings
|
||||
$this->originalMemoryLimit = ini_get('memory_limit');
|
||||
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -1277,6 +1277,13 @@ HTML;
|
||||
Deny from all
|
||||
</Files>
|
||||
|
||||
# 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
|
||||
<Files *.yml>
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</Files>
|
||||
|
||||
ErrorDocument 404 /assets/error-404.html
|
||||
ErrorDocument 500 /assets/error-500.html
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
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
|
||||
|
||||
<Files *.yml>
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Files>
|
||||
|
@ -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://<mysite>/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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
<div class="warning" markdown="1">
|
||||
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.
|
||||
</div>
|
||||
|
||||
## 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)
|
||||
* [database-structure](database-structure)
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -23,7 +23,8 @@ The SilverStripe database-schema is generated automatically by visiting the URL.
|
||||
`http://<mysite>/dev/build`
|
||||
|
||||
<div class="notice" markdown='1'>
|
||||
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.
|
||||
</div>
|
||||
|
||||
## Querying Data
|
||||
|
@ -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
|
||||
|
||||
|
@ -118,16 +118,54 @@ 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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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;
|
||||
@ -388,37 +426,45 @@ class Email extends ViewableData {
|
||||
if(project()) $headers['X-SilverStripeSite'] = project();
|
||||
|
||||
$to = $this->to;
|
||||
$from = $this->from;
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -444,40 +490,49 @@ class Email extends ViewableData {
|
||||
|
||||
if(project()) $headers['X-SilverStripeSite'] = project();
|
||||
|
||||
|
||||
$to = $this->to;
|
||||
$from = $this->from;
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -511,7 +566,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
|
||||
|
@ -327,6 +327,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);
|
||||
}
|
||||
@ -336,12 +337,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 {
|
||||
|
@ -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
|
||||
|
@ -35,7 +35,7 @@ class RequiredFields extends Validator {
|
||||
* Clears all the validation from this object.
|
||||
*/
|
||||
public function removeValidation(){
|
||||
$this->required = null;
|
||||
$this->required = array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,6 +66,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);
|
||||
}
|
||||
|
||||
|
@ -312,7 +312,7 @@ class Date extends DBField {
|
||||
* @return boolean
|
||||
*/
|
||||
public function InPast() {
|
||||
return strtotime($this->value) < time();
|
||||
return strtotime($this->value) < SS_Datetime::now()->Format('U');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -320,7 +320,7 @@ class Date extends DBField {
|
||||
* @return boolean
|
||||
*/
|
||||
public function InFuture() {
|
||||
return strtotime($this->value) > time();
|
||||
return strtotime($this->value) > SS_Datetime::now()->Format('U');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -328,7 +328,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'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 <h1></h1>, <p></p> ,etc
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function exists() {
|
||||
return parent::exists() && $this->value != '<p></p>';
|
||||
// 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 <p></p> or <h1></h1> 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) {
|
||||
|
@ -383,9 +383,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) {
|
||||
@ -420,9 +422,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
|
||||
|
10
security/PermissionFailureException.php
Normal file
10
security/PermissionFailureException.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Throw this exception to register that a user doesn't have permission to do the given action
|
||||
* and potentially redirect them to the log-in page. The exception message may be presented to the
|
||||
* user, so it shouldn't be in nerd-speak.
|
||||
*/
|
||||
class PermissionFailureException extends Exception {
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
@ -344,7 +347,6 @@ class Security extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$customCSS = project() . '/css/tabs.css';
|
||||
if(Director::fileExists($customCSS)) {
|
||||
Requirements::css($customCSS);
|
||||
@ -360,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();
|
||||
@ -458,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' =>
|
||||
'<p>' .
|
||||
@ -517,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(
|
||||
@ -580,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'])) {
|
||||
@ -927,8 +939,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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,7 +23,7 @@
|
||||
<p>To get started with the SilverStripe framework:</p>
|
||||
<ol>
|
||||
<li>Create a <code>Controller</code> subclass (<a href="http://doc.silverstripe.org/framework/en/topics/controller">doc.silverstripe.org/framework/en/topics/controller</a>)</li>
|
||||
<li>Setup the routes.yml f to your <code>Controller</code> (<a href="http://doc.silverstripe.org/framework/en/reference/director#routing">doc.silverstripe.org/framework/en/reference/director#routing</a>).</li>
|
||||
<li>Setup the routes.yml to your <code>Controller</code> (<a href="http://doc.silverstripe.org/framework/en/reference/director#routing">doc.silverstripe.org/framework/en/reference/director#routing</a>).</li>
|
||||
<li>Create a template for your <code>Controller</code> (<a href="http://doc.silverstripe.org/framework/en/reference/templates">doc.silverstripe.org/framework/en/reference/templates</a>)</li>
|
||||
</ol>
|
||||
|
||||
|
@ -1,4 +1,9 @@
|
||||
<form $FormAttributes>
|
||||
<% if Message %>
|
||||
<p id="{$FormName}_error" class="message $MessageType">$Message</p>
|
||||
<% else %>
|
||||
<p id="{$FormName}_error" class="message $MessageType" style="display: none"></p>
|
||||
<% end_if %>
|
||||
<fieldset>
|
||||
<% loop Fields %>
|
||||
$FieldHolder
|
||||
|
@ -227,7 +227,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']);
|
||||
}
|
||||
|
@ -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('<b>SS_LogTest->exceptionGeneratorThrower()</b>', $stacktrace[0]->li[0]->asXML());
|
||||
$this->assertContains('<b>SS_LogTest->exceptionGenerator()</b>', $stacktrace[0]->li[1]->asXML());
|
||||
$this->assertContains('<b>SS_LogTest->testEmailException()</b>', $stacktrace[0]->li[2]->asXML());
|
||||
}
|
||||
|
||||
public function testSubclassedLogger() {
|
||||
$this->assertTrue(SS_Log::get_logger() !== SS_LogTest_NewLogger::get_logger());
|
||||
}
|
||||
|
@ -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("<p></p>");
|
||||
$this->assertFalse($h->exists());
|
||||
$h->setValue("<p> </p>");
|
||||
$this->assertFalse($h->exists());
|
||||
$h->setValue("<h2/>");
|
||||
$this->assertFalse($h->exists());
|
||||
$h->setValue("<h2></h2>");
|
||||
$this->assertFalse($h->exists());
|
||||
|
||||
$h->setValue("something");
|
||||
$this->assertTrue($h->exists());
|
||||
$h->setValue("<img src=\"dummy.png\">");
|
||||
$this->assertTrue($h->exists());
|
||||
$h->setValue("<img src=\"dummy.png\"><img src=\"dummy.png\">");
|
||||
$this->assertTrue($h->exists());
|
||||
$h->setValue("<p><img src=\"dummy.png\"></p>");
|
||||
$this->assertTrue($h->exists());
|
||||
|
||||
$h->setValue("<iframe src=\"http://www.google.com\"></iframe>");
|
||||
$this->assertTrue($h->exists());
|
||||
$h->setValue("<embed src=\"test.swf\">");
|
||||
$this->assertTrue($h->exists());
|
||||
$h->setValue("<object width=\"400\" height=\"400\" data=\"test.swf\"></object>");
|
||||
$this->assertTrue($h->exists());
|
||||
|
||||
|
||||
$h->setValue("<p>test</p>");
|
||||
$this->assertTrue($h->exists());
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user