Merge 3.1 into 3.2

Conflicts:
	admin/javascript/LeftAndMain.js
	control/HTTPRequest.php
	docs/en/00_Getting_Started/00_Server_Requirements.md
This commit is contained in:
Daniel Hensby 2016-04-26 00:09:33 +01:00
commit a0812f987a
No known key found for this signature in database
GPG Key ID: E38EC566FE29EB66
9 changed files with 124 additions and 19 deletions

View File

@ -176,7 +176,7 @@ jQuery.noConflict();
var msg = (xhr.getResponseHeader('X-Status')) ? xhr.getResponseHeader('X-Status') : xhr.statusText, var msg = (xhr.getResponseHeader('X-Status')) ? xhr.getResponseHeader('X-Status') : xhr.statusText,
reathenticate = xhr.getResponseHeader('X-Reauthenticate'), reathenticate = xhr.getResponseHeader('X-Reauthenticate'),
msgType = (xhr.status < 200 || xhr.status > 399) ? 'bad' : 'good', msgType = (xhr.status < 200 || xhr.status > 399) ? 'bad' : 'good',
ignoredMessages = ['OK']; ignoredMessages = ['OK', 'success'];
// Enable reauthenticate dialog if requested // Enable reauthenticate dialog if requested
if(reathenticate) { if(reathenticate) {
@ -911,8 +911,11 @@ jQuery.noConflict();
sessionStates = sessionData ? JSON.parse(sessionData) : false; sessionStates = sessionData ? JSON.parse(sessionData) : false;
this.find('.cms-tabset, .ss-tabset').each(function() { this.find('.cms-tabset, .ss-tabset').each(function() {
var index, tabset = $(this), tabsetId = tabset.attr('id'), tab, var index,
forcedTab = tabset.find('.ss-tabs-force-active'); tabset = $(this),
tabsetId = tabset.attr('id'),
tab,
forcedTab = tabset.children('ul').children('li.ss-tabs-force-active');
if(!tabset.data('tabs')){ if(!tabset.data('tabs')){
return; // don't act on uninit'ed controls return; // don't act on uninit'ed controls
@ -921,18 +924,18 @@ jQuery.noConflict();
// The tabs may have changed, notify the widget that it should update its internal state. // The tabs may have changed, notify the widget that it should update its internal state.
tabset.tabs('refresh'); tabset.tabs('refresh');
// Make sure the intended tab is selected. // Make sure the intended tab is selected. Only force the tab on the correct tabset though
if(forcedTab.length) { if(forcedTab.length) {
index = forcedTab.index(); index = forcedTab.first().index();
} else if(overrideStates && overrideStates[tabsetId]) { } else if(overrideStates && overrideStates[tabsetId]) {
tab = tabset.find(overrideStates[tabsetId].tabSelector); tab = tabset.find(overrideStates[tabsetId].tabSelector);
if(tab.length){ if(tab.length){
index = tab.index(); index = tab.index();
} }
} else if(sessionStates) { } else if(sessionStates) {
$.each(sessionStates, function(i, sessionState) { $.each(sessionStates, function(i, state) {
if(tabset.is('#' + sessionState.id)){ if(tabsetId == state.id){
index = sessionState.selected; index = state.selected;
} }
}); });
} }

View File

@ -671,7 +671,7 @@ class SS_HTTPRequest implements ArrayAccess {
} }
if ($headerOverrideIP) { if ($headerOverrideIP) {
return $headerOverrideIP; return $this->getIPFromHeaderValue($headerOverrideIP);
} elseif(isset($_SERVER['REMOTE_ADDR'])) { } elseif(isset($_SERVER['REMOTE_ADDR'])) {
return $_SERVER['REMOTE_ADDR']; return $_SERVER['REMOTE_ADDR'];
} else { } else {
@ -679,6 +679,28 @@ class SS_HTTPRequest implements ArrayAccess {
} }
} }
/**
* Extract an IP address from a header value that has been obtained. Accepts single IP or comma separated string of
* IPs
*
* @param string $headerValue The value from a trusted header
* @return string The IP address
*/
protected function getIPFromHeaderValue($headerValue) {
if (strpos($headerValue, ',') !== false) {
//sometimes the IP from a load balancer could be "x.x.x.x, y.y.y.y, z.z.z.z" so we need to find the most
// likely candidate
$ips = explode(',', $headerValue);
foreach ($ips as $ip) {
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
return $ip;
}
}
}
return $headerValue;
}
/** /**
* Returns all mimetypes from the HTTP "Accept" header * Returns all mimetypes from the HTTP "Accept" header
* as an array. * as an array.

View File

@ -67,9 +67,21 @@ class ErrorControlChain {
$this->error = (bool)$error; $this->error = (bool)$error;
} }
/**
* Sets whether errors are suppressed or not
* Notes:
* - Errors cannot be suppressed if not handling errors.
* - Errors cannot be un-suppressed if original mode dis-allowed visible errors
*
* @param bool $suppression
*/
public function setSuppression($suppression) { public function setSuppression($suppression) {
$this->suppression = (bool)$suppression; $this->suppression = (bool)$suppression;
if ($this->handleFatalErrors) ini_set('display_errors', !$suppression); // Don't modify errors unless handling fatal errors, and if errors were
// originally allowed to be displayed.
if ($this->handleFatalErrors && $this->originalDisplayErrors) {
ini_set('display_errors', !$suppression);
}
} }
/** /**
@ -167,7 +179,7 @@ class ErrorControlChain {
$this->handleFatalErrors = true; $this->handleFatalErrors = true;
$this->originalDisplayErrors = ini_get('display_errors'); $this->originalDisplayErrors = ini_get('display_errors');
ini_set('display_errors', !$this->suppression); $this->setSuppression($this->suppression);
$this->step(); $this->step();
} }

View File

@ -194,8 +194,8 @@ class ParameterConfirmationToken {
$_SERVER['HTTP_HOST'], $_SERVER['HTTP_HOST'],
// SilverStripe base // SilverStripe base
self::$alternateBaseURL !== null ? self::$alternateBaseURL : BASE_URL, self::$alternateBaseURL !== null ? self::$alternateBaseURL : BASE_URL,
// And URL // And URL including base script (eg: if it's index.php/page/url/)
$url (defined('BASE_SCRIPT_URL') ? '/' . BASE_SCRIPT_URL : '') . $url,
)); ));
// Join together with protocol into our current absolute URL, avoiding duplicated "/" characters // Join together with protocol into our current absolute URL, avoiding duplicated "/" characters

View File

@ -10,10 +10,10 @@
* *
* <code> * <code>
* $parser = new CSVParser('myfile.csv'); * $parser = new CSVParser('myfile.csv');
* $parser->mapColumns( * $parser->mapColumns(array(
* 'first name' => 'FirstName' * 'first name' => 'FirstName'
* 'lastname' => 'Surname', * 'lastname' => 'Surname',
* 'last name' => 'Surname' * 'last name' => 'Surname',
* )); * ));
* foreach($parser as $row) { * foreach($parser as $row) {
* // $row is a map of column name => column value * // $row is a map of column name => column value

View File

@ -8,7 +8,7 @@ Our web-based [PHP installer](installation/) can check if you meet the requireme
## Web server software requirements ## Web server software requirements
* PHP 5.3.3+ * PHP 5.3.3+, <7
* We recommend using a PHP accelerator or opcode cache, such as [xcache](http://xcache.lighttpd.net/) or [WinCache](http://www.iis.net/download/wincacheforphp). * We recommend using a PHP accelerator or opcode cache, such as [xcache](http://xcache.lighttpd.net/) or [WinCache](http://www.iis.net/download/wincacheforphp).
* Allocate at least 48MB of memory to each PHP process. (SilverStripe can be resource hungry for some intensive operations.) * Allocate at least 48MB of memory to each PHP process. (SilverStripe can be resource hungry for some intensive operations.)
* Required modules: dom, gd2, fileinfo, hash, iconv, mbstring, mysqli (or other database driver), session, simplexml, tokenizer, xml. * Required modules: dom, gd2, fileinfo, hash, iconv, mbstring, mysqli (or other database driver), session, simplexml, tokenizer, xml.
@ -23,7 +23,7 @@ Our web-based [PHP installer](installation/) can check if you meet the requireme
* MySQL 5.0+ * MySQL 5.0+
* PostgreSQL 8.3+ (requires ["postgresql" module](http://silverstripe.org/postgresql-module)) * PostgreSQL 8.3+ (requires ["postgresql" module](http://silverstripe.org/postgresql-module))
* SQL Server 2008+ (requires ["mssql" module](http://silverstripe.org/microsoft-sql-server-database/)) * SQL Server 2008+ (requires ["mssql" module](http://silverstripe.org/microsoft-sql-server-database/))
* Support for `[Oracle](http://www.silverstripe.org/oracle-database-module/)` and [SQLite](http://silverstripe.org/sqlite-database/) is not commercially supported, but is under development by our open source community. * Support for [Oracle](http://www.silverstripe.org/oracle-database-module/) and [SQLite](http://silverstripe.org/sqlite-database/) is not commercially supported, but is under development by our open source community.
* One of the following web server products: * One of the following web server products:
* Apache 2.0+ with mod_rewrite and "AllowOverride All" set * Apache 2.0+ with mod_rewrite and "AllowOverride All" set
* IIS 7+ * IIS 7+
@ -34,6 +34,11 @@ Our web-based [PHP installer](installation/) can check if you meet the requireme
* Microsoft Windows XP SP3, Vista, Windows 7, Server 2008, Server 2008 R2 * Microsoft Windows XP SP3, Vista, Windows 7, Server 2008, Server 2008 R2
* Mac OS X 10.4+ * Mac OS X 10.4+
### Why doesn't SilverStripe 3 work with PHP 7?
Unfortunately, SilverStripe has classes named the same as PHP reserved words, such as "Int" and "Float". This means that
we are unable to make SilverStripe 3 support PHP 7 without breaking backward compatibility. SilverStripe 4 will work
with PHP 7 and will be released in 2016. Until then, we recommend that you use PHP 5.6.
## Web server hardware requirements ## Web server hardware requirements
Hardware requirements vary widely depending on the traffic to your website, the complexity of its logic (i.e., PHP), and Hardware requirements vary widely depending on the traffic to your website, the complexity of its logic (i.e., PHP), and

View File

@ -30,7 +30,7 @@ directly calling methods that they shouldn't.
'cmsrestrictedaction' => 'CMS_ACCESS_CMSMain', 'cmsrestrictedaction' => 'CMS_ACCESS_CMSMain',
// complexaction can only be accessed if $this->canComplexAction() returns true. // complexaction can only be accessed if $this->canComplexAction() returns true.
'complexaction' '->canComplexAction' 'complexaction' => '->canComplexAction',
// complexactioncheck can only be accessed if $this->canComplexAction("MyRestrictedAction", false, 42) is true. // complexactioncheck can only be accessed if $this->canComplexAction("MyRestrictedAction", false, 42) is true.
'complexactioncheck' => '->canComplexAction("MyRestrictedAction", false, 42)', 'complexactioncheck' => '->canComplexAction("MyRestrictedAction", false, 42)',
@ -200,4 +200,4 @@ execution. This behavior can be used to implement permission checks.
## API Documentation ## API Documentation
* [api:Controller] * [api:Controller]

View File

@ -254,4 +254,23 @@ class HTTPRequestTest extends SapphireTest {
$this->assertEquals('home?test=1', $req->getURL(true)); $this->assertEquals('home?test=1', $req->getURL(true));
$this->assertEquals('home', $req->getURL()); $this->assertEquals('home', $req->getURL());
} }
public function testGetIPFromHeaderValue() {
$req = new SS_HTTPRequest('GET', '/');
$reflectionMethod = new ReflectionMethod($req, 'getIPFromHeaderValue');
$reflectionMethod->setAccessible(true);
$headers = array(
'80.79.208.21, 149.126.76.1, 10.51.0.68' => '80.79.208.21',
'52.19.19.103, 10.51.0.49' => '52.19.19.103',
'10.51.0.49, 52.19.19.103' => '52.19.19.103',
'10.51.0.49' => '10.51.0.49',
'127.0.0.1, 10.51.0.49' => '127.0.0.1',
);
foreach ($headers as $header => $ip) {
$this->assertEquals($ip, $reflectionMethod->invoke($req, $header));
}
}
} }

View File

@ -63,7 +63,11 @@ require_once '$classpath';
class ErrorControlChainTest extends SapphireTest { class ErrorControlChainTest extends SapphireTest {
protected $displayErrors = null;
function setUp() { function setUp() {
$this->displayErrors = (bool)ini_get('display_errors');
// Check we can run PHP at all // Check we can run PHP at all
$null = is_writeable('/dev/null') ? '/dev/null' : 'NUL'; $null = is_writeable('/dev/null') ? '/dev/null' : 'NUL';
exec("php -v 2> $null", $out, $rv); exec("php -v 2> $null", $out, $rv);
@ -76,8 +80,48 @@ class ErrorControlChainTest extends SapphireTest {
parent::setUp(); parent::setUp();
} }
public function tearDown() {
if($this->displayErrors !== null) {
ini_set('display_errors', $this->displayErrors);
$this->displayErrors = null;
}
parent::tearDown(); // TODO: Change the autogenerated stub
}
function testErrorSuppression() { function testErrorSuppression() {
// Errors disabled by default
ini_set('display_errors', false);
$chain = new ErrorControlChain();
$whenNotSuppressed = null;
$whenSuppressed = null;
$chain->then(function($chain) use(&$whenNotSuppressed, &$whenSuppressed) {
$chain->setSuppression(true);
$whenSuppressed = ini_get('display_errors');
$chain->setSuppression(false);
$whenNotSuppressed = ini_get('display_errors');
})->execute();
// Disabled errors never un-disable
$this->assertFalse((bool)$whenNotSuppressed);
$this->assertFalse((bool)$whenSuppressed);
// Errors enabled by default
ini_set('display_errors', true);
$chain = new ErrorControlChain();
$whenNotSuppressed = null;
$whenSuppressed = null;
$chain->then(function($chain) use(&$whenNotSuppressed, &$whenSuppressed) {
$chain->setSuppression(true);
$whenSuppressed = ini_get('display_errors');
$chain->setSuppression(false);
$whenNotSuppressed = ini_get('display_errors');
})->execute();
// Errors can be suppressed an un-suppressed when initially enabled
$this->assertTrue((bool)$whenNotSuppressed);
$this->assertFalse((bool)$whenSuppressed);
// Fatal error // Fatal error
$chain = new ErrorControlChainTest_Chain(); $chain = new ErrorControlChainTest_Chain();