Merge remote-tracking branch 'origin/3.0' into 3.1

Conflicts:
	email/Mailer.php
This commit is contained in:
Ingo Schommer 2013-01-30 12:46:24 +01:00
commit 634c91c6ff
35 changed files with 358 additions and 85 deletions

View File

@ -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')) {

View File

@ -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;

View File

@ -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) {

View File

@ -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

View File

@ -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));

View File

@ -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;

View File

@ -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());
}
}

View File

@ -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');

View File

@ -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');

View File

@ -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
*/

View File

@ -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

View File

@ -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)

View File

@ -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;

View File

@ -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>

View File

@ -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.

View File

@ -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)

View File

@ -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 |

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -35,7 +35,7 @@ class RequiredFields extends Validator {
* Clears all the validation from this object.
*/
public function removeValidation(){
$this->required = null;
$this->required = array();
}
/**

View File

@ -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);
}

View File

@ -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'));
}
/**

View File

@ -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) {

View File

@ -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

View 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 {
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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

View File

@ -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']);
}

View File

@ -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-&gt;exceptionGeneratorThrower()</b>', $stacktrace[0]->li[0]->asXML());
$this->assertContains('<b>SS_LogTest-&gt;exceptionGenerator()</b>', $stacktrace[0]->li[1]->asXML());
$this->assertContains('<b>SS_LogTest-&gt;testEmailException()</b>', $stacktrace[0]->li[2]->asXML());
}
public function testSubclassedLogger() {
$this->assertTrue(SS_Log::get_logger() !== SS_LogTest_NewLogger::get_logger());
}

View File

@ -139,4 +139,38 @@ class HTMLTextTest extends SapphireTest {
$data = DBField::create_field('HTMLText', '"this is a test"');
$this->assertEquals($data->ATT(), '&quot;this is a test&quot;');
}
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());
}
}

View File

@ -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",