From 95d0ed486cc845e8760a491d117ecdea10d9c498 Mon Sep 17 00:00:00 2001 From: John Milmine Date: Thu, 29 Jan 2015 13:18:39 +1300 Subject: [PATCH 1/9] fixing tab force active Wasn't getting li, plus was find tabs in child tabsets --- admin/javascript/LeftAndMain.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js index 62e3aa9ce..a4ac01580 100644 --- a/admin/javascript/LeftAndMain.js +++ b/admin/javascript/LeftAndMain.js @@ -813,26 +813,36 @@ jQuery.noConflict(); sessionStates = sessionData ? JSON.parse(sessionData) : false; this.find('.cms-tabset, .ss-tabset').each(function() { - var index, tabset = $(this), tabsetId = tabset.attr('id'), tab, - forcedTab = tabset.find('.ss-tabs-force-active'); + var index, + tabset = $(this), + tabsetId = tabset.attr('id'), + tab, + forcedTab = tabset.children('ul').children('li.ss-tabs-force-active'); if(!tabset.data('tabs')) return; // don't act on uninit'ed controls // The tabs may have changed, notify the widget that it should update its internal state. 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) { - index = forcedTab.index(); + index = forcedTab.first().index(); } else if(overrideStates && overrideStates[tabsetId]) { tab = tabset.find(overrideStates[tabsetId].tabSelector); if(tab.length) index = tab.index(); } else if(sessionStates) { - $.each(sessionStates, function(i, sessionState) { - if(tabset.is('#' + sessionState.id)) index = sessionState.selected; - }); - } - if(index !== null) tabset.tabs('select', index); + $.each(sessionStates, function(i, state) { + if(tabsetId == state.id) { + tabset.tabs('select', state.selected); + } + }); + + index = null; + } + + if(index !== null) { + tabset.tabs('select', index); + } }); }, From b8c88753039d7ec25626476a5c6283936630dc95 Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Fri, 18 Dec 2015 13:51:45 +0000 Subject: [PATCH 2/9] DOCS Explicitly noting lack of PHP 7 support --- docs/en/00_Getting_Started/00_Server_Requirements.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/en/00_Getting_Started/00_Server_Requirements.md b/docs/en/00_Getting_Started/00_Server_Requirements.md index a45bff786..a2f04bf94 100644 --- a/docs/en/00_Getting_Started/00_Server_Requirements.md +++ b/docs/en/00_Getting_Started/00_Server_Requirements.md @@ -8,7 +8,7 @@ Our web-based [PHP installer](installation/) can check if you meet the requireme ## Web server software requirements - * PHP 5.3.2+ + * PHP 5.3.2+, <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). * 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. @@ -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 * 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 Hardware requirements vary widely depending on the traffic to your website, the complexity of its logic (i.e., PHP), and From 817b83687028894574ba5a8e8ee8f3af21f23188 Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Tue, 1 Mar 2016 12:56:34 +0000 Subject: [PATCH 3/9] FIX getIP from behind a load-balancer that adds many IPs to the header --- control/HTTPRequest.php | 26 ++++++++++++++++++++++++-- tests/control/HTTPRequestTest.php | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/control/HTTPRequest.php b/control/HTTPRequest.php index 21e5db00e..47d2ce9ff 100644 --- a/control/HTTPRequest.php +++ b/control/HTTPRequest.php @@ -669,14 +669,36 @@ class SS_HTTPRequest implements ArrayAccess { } if ($headerOverrideIP) { - return $headerOverrideIP; + return $this->getIPFromHeaderValue($headerOverrideIP); } elseif(isset($_SERVER['REMOTE_ADDR'])) { return $_SERVER['REMOTE_ADDR']; } else { return null; } } - + + /** + * 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 * as an array. diff --git a/tests/control/HTTPRequestTest.php b/tests/control/HTTPRequestTest.php index 734731c10..09ee883b8 100644 --- a/tests/control/HTTPRequestTest.php +++ b/tests/control/HTTPRequestTest.php @@ -254,4 +254,23 @@ class HTTPRequestTest extends SapphireTest { $this->assertEquals('home?test=1', $req->getURL(true)); $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)); + } + + } } From add2ecdf8bb977a0234cf773b578eae9872a0d28 Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Fri, 18 Mar 2016 15:56:39 +0000 Subject: [PATCH 4/9] FIX Parameter tokens now redirect to correct url if mod_rewrite is off --- core/startup/ParameterConfirmationToken.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/startup/ParameterConfirmationToken.php b/core/startup/ParameterConfirmationToken.php index d65c32c4d..03b032c75 100644 --- a/core/startup/ParameterConfirmationToken.php +++ b/core/startup/ParameterConfirmationToken.php @@ -191,8 +191,8 @@ class ParameterConfirmationToken { $_SERVER['HTTP_HOST'], // SilverStripe base self::$alternateBaseURL !== null ? self::$alternateBaseURL : BASE_URL, - // And URL - $url + // And URL including base script (eg: if it's index.php/page/url/) + (defined('BASE_SCRIPT_URL') ? '/' . BASE_SCRIPT_URL : '') . $url, )); // Join together with protocol into our current absolute URL, avoiding duplicated "/" characters From 11561aeb54b25e34957d5950ee05ac01d669f3c7 Mon Sep 17 00:00:00 2001 From: Nicola Fontana Date: Sun, 28 Feb 2016 21:40:34 +0100 Subject: [PATCH 5/9] Do not hang on nested parameters in search context Backport of 0b5a573 for 3.2 that does not add a new API, as required by #5056 to be semver compatible. --- admin/code/ModelAdmin.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/admin/code/ModelAdmin.php b/admin/code/ModelAdmin.php index 715132fa2..be6124ddc 100644 --- a/admin/code/ModelAdmin.php +++ b/admin/code/ModelAdmin.php @@ -226,7 +226,15 @@ abstract class ModelAdmin extends LeftAndMain { public function getList() { $context = $this->getSearchContext(); - $params = $this->request->requestVar('q'); + $params = $this->getRequest()->requestVar('q'); + + if(is_array($params)) { + $trimRecursive = function($v) use(&$trimRecursive) { + return is_array($v) ? array_map($trimRecursive, $v) : trim($v); + }; + $params = $trimRecursive($params); + } + $list = $context->getResults($params); $this->extend('updateList', $list); From c8f2e6702b651211bbacc423f831b61bc3d6d9a6 Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Mon, 21 Mar 2016 14:22:43 +0000 Subject: [PATCH 6/9] DOCS Fixing CSV Parser docs - fixes #5004 --- dev/CSVParser.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/CSVParser.php b/dev/CSVParser.php index 5cf20d645..9218f7819 100644 --- a/dev/CSVParser.php +++ b/dev/CSVParser.php @@ -10,10 +10,10 @@ * * * $parser = new CSVParser('myfile.csv'); - * $parser->mapColumns( + * $parser->mapColumns(array( * 'first name' => 'FirstName' * 'lastname' => 'Surname', - * 'last name' => 'Surname' + * 'last name' => 'Surname', * )); * foreach($parser as $row) { * // $row is a map of column name => column value From 6ec26562019454483db79132a5c076cfa87dfe34 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Fri, 1 Apr 2016 11:03:21 +1300 Subject: [PATCH 7/9] BUG fix ErrorControlChain causing errors to be displayed if display_errors in php.ini is false Fixes #5250 --- core/startup/ErrorControlChain.php | 16 ++++++- tests/core/startup/ErrorControlChainTest.php | 44 ++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/core/startup/ErrorControlChain.php b/core/startup/ErrorControlChain.php index 4ac510cbb..a8cf7277f 100644 --- a/core/startup/ErrorControlChain.php +++ b/core/startup/ErrorControlChain.php @@ -36,9 +36,21 @@ class ErrorControlChain { $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) { $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); + } } /** @@ -112,7 +124,7 @@ class ErrorControlChain { $this->handleFatalErrors = true; $this->originalDisplayErrors = ini_get('display_errors'); - ini_set('display_errors', !$this->suppression); + $this->setSuppression($this->suppression); $this->step(); } diff --git a/tests/core/startup/ErrorControlChainTest.php b/tests/core/startup/ErrorControlChainTest.php index 843fc6b69..d8e0df7f9 100644 --- a/tests/core/startup/ErrorControlChainTest.php +++ b/tests/core/startup/ErrorControlChainTest.php @@ -63,7 +63,11 @@ require_once '$classpath'; class ErrorControlChainTest extends SapphireTest { + protected $displayErrors = null; + function setUp() { + $this->displayErrors = (bool)ini_get('display_errors'); + // Check we can run PHP at all $null = is_writeable('/dev/null') ? '/dev/null' : 'NUL'; exec("php -v 2> $null", $out, $rv); @@ -76,8 +80,48 @@ class ErrorControlChainTest extends SapphireTest { 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() { + // 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 $chain = new ErrorControlChainTest_Chain(); From 36283b86d5305cc2c5d4823e54972cd301978389 Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Tue, 12 Apr 2016 15:02:54 +0100 Subject: [PATCH 8/9] FIX Stop "success" message showing in CMS fixes #5281 --- admin/javascript/LeftAndMain.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js index b9d9254d3..577cd9c33 100644 --- a/admin/javascript/LeftAndMain.js +++ b/admin/javascript/LeftAndMain.js @@ -141,7 +141,7 @@ jQuery.noConflict(); var msg = (xhr.getResponseHeader('X-Status')) ? xhr.getResponseHeader('X-Status') : xhr.statusText, reathenticate = xhr.getResponseHeader('X-Reauthenticate'), msgType = (xhr.status < 200 || xhr.status > 399) ? 'bad' : 'good', - ignoredMessages = ['OK']; + ignoredMessages = ['OK', 'success']; // Enable reauthenticate dialog if requested if(reathenticate) { From 1f820b0b1c6e38e23e72ca03b07900e22e228d54 Mon Sep 17 00:00:00 2001 From: Michael Strong Date: Mon, 18 Apr 2016 17:49:30 +1200 Subject: [PATCH 9/9] DOCS allowed_actions access control (#5333) --- .../02_Developer_Guides/02_Controllers/03_Access_Control.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/02_Developer_Guides/02_Controllers/03_Access_Control.md b/docs/en/02_Developer_Guides/02_Controllers/03_Access_Control.md index e8bb04e6a..f5ce5fc61 100644 --- a/docs/en/02_Developer_Guides/02_Controllers/03_Access_Control.md +++ b/docs/en/02_Developer_Guides/02_Controllers/03_Access_Control.md @@ -30,7 +30,7 @@ directly calling methods that they shouldn't. 'cmsrestrictedaction' => 'CMS_ACCESS_CMSMain', // 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' => '->canComplexAction("MyRestrictedAction", false, 42)', @@ -200,4 +200,4 @@ execution. This behavior can be used to implement permission checks. ## API Documentation -* [api:Controller] \ No newline at end of file +* [api:Controller]