diff --git a/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Nginx.md b/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Nginx.md
index cea572907..71b5747db 100644
--- a/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Nginx.md
+++ b/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Nginx.md
@@ -16,57 +16,80 @@ If you don't fully understand the configuration presented here, consult the
Especially be aware of [accidental php-execution](https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/ "Don't trust the tutorials") when extending the configuration.
-But enough of the disclaimer, on to the actual configuration — typically in `nginx.conf`. This assumes
-you are running your site configuration with a separate `public/` webroot folder.
+But enough of the disclaimer, on to the actual configuration — typically in `nginx.conf`:
- server {
- listen 80;
- root /var/www/the-website/public;
+```nginx
+server {
+ include mime.types;
+ default_type application/octet-stream;
+ client_max_body_size 0; # Manage this in php.ini
+ listen 80;
+ root /path/to/ss/folder;
+ server_name example.com www.example.com;
- server_name site.com www.site.com;
+ # Defend against SS-2015-013 -- http://www.silverstripe.org/software/download/security-releases/ss-2015-013
+ if ($http_x_forwarded_host) {
+ return 400;
+ }
- # Defend against SS-2015-013 -- http://www.silverstripe.org/software/download/security-releases/ss-2015-013
- if ($http_x_forwarded_host) {
- return 400;
- }
+ location / {
+ try_files $uri /index.php?$query_string;
+ }
- location / {
- try_files $uri /index.php?$query_string;
- }
+ error_page 404 /assets/error-404.html;
+ error_page 500 /assets/error-500.html;
- error_page 404 /assets/error-404.html;
- error_page 500 /assets/error-500.html;
+ location ^~ /assets/ {
+ sendfile on;
+ try_files $uri =404;
+ }
- location ^~ /assets/ {
- location ~ /\. {
- deny all;
- }
- sendfile on;
- try_files $uri /index.php?$query_string;
- }
-
- location ~ /\.. {
- deny all;
- }
+ location /index.php {
+ fastcgi_buffer_size 32k;
+ fastcgi_busy_buffers_size 64k;
+ fastcgi_buffers 4 32k;
+ fastcgi_keep_conn on;
+ fastcgi_pass 127.0.0.1:9000;
+ fastcgi_index index.php;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ include fastcgi_params;
+ }
- location ~ web\.config$ {
- deny all;
- }
+ # Denials
+ location ~ /\.. {
+ deny all;
+ }
+ location ~ \.ss$ {
+ satisfy any;
+ allow 127.0.0.1;
+ deny all;
+ }
+ location ~ web\.config$ {
+ deny all;
+ }
+ location ~ \.ya?ml$ {
+ deny all;
+ }
+ location ~* README.*$ {
+ deny all;
+ }
+ location ^~ /vendor/ {
+ deny all;
+ }
+ location ~* /silverstripe-cache/ {
+ deny all;
+ }
+ location ~* composer\.(json|lock)$ {
+ deny all;
+ }
+ location ~* /(cms|framework)/silverstripe_version$ {
+ deny all;
+ }
+}
+```
- location ~ \.php$ {
- fastcgi_keep_conn on;
- fastcgi_pass 127.0.0.1:9000;
- fastcgi_index index.php;
- fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
- include fastcgi_params;
- fastcgi_buffer_size 32k;
- fastcgi_busy_buffers_size 64k;
- fastcgi_buffers 4 32k;
- }
- }
-
-The above configuration sets up a virtual host `site.com` with
-rewrite rules suited for SilverStripe. The location block for php files
-passes all php scripts to the FastCGI-wrapper via a TCP socket.
+The above configuration sets up a virtual host `example.com` with
+rewrite rules suited for SilverStripe. The location block for index.php
+passes the php script to the FastCGI-wrapper via a TCP socket.
Now you can proceed with the SilverStripe installation normally.
diff --git a/docs/en/02_Developer_Guides/13_i18n/index.md b/docs/en/02_Developer_Guides/13_i18n/index.md
index a4db8a2c3..9cd295db6 100644
--- a/docs/en/02_Developer_Guides/13_i18n/index.md
+++ b/docs/en/02_Developer_Guides/13_i18n/index.md
@@ -263,7 +263,7 @@ _t('CMSMain.RESTORED',
);
// Plurals are invoked via a `|` pipe-delimeter with a {count} argument
-_t('MyObject.PLURALS', 'An object|{count} objects', [ 'count' => '$count ]);
+_t('MyObject.PLURALS', 'An object|{count} objects', [ 'count' => $count ]);
```
#### Usage in Template Files
diff --git a/docs/en/04_Changelogs/3.5.7.md b/docs/en/04_Changelogs/3.5.7.md
new file mode 100644
index 000000000..0056bdb71
--- /dev/null
+++ b/docs/en/04_Changelogs/3.5.7.md
@@ -0,0 +1,18 @@
+# 3.5.7
+
+
+
+## Change Log
+
+### Bugfixes
+
+ * 2018-01-26 [416915b08](https://github.com/silverstripe/silverstripe-framework/commit/416915b08248285083518850ad8d015ca8ed25c2) tableName is blank in CompositeDBField->addToQuery (Dominik Beerbohm)
+ * 2018-01-25 [cf69d0486](https://github.com/silverstripe/silverstripe-framework/commit/cf69d048665befa90eb43146f86cde984b876b3a) Fix ping including requirements (Damian Mooyman)
+ * 2018-01-24 [c2cd6b383](https://github.com/silverstripe/silverstripe-framework/commit/c2cd6b3832c6bc4775b2742df593b445c2aca391) Fix Member_GroupSet::removeAll() (fixes #3948) (Loz Calver)
+ * 2018-01-24 [f2b4c192e](https://github.com/silverstripe/silverstripe-framework/commit/f2b4c192ec4d70779f7c667a976e741a7f3a26c5) Fix UploadField cuts off “Save” button (closes #2862) (Loz Calver)
+ * 2018-01-23 [7384e3fc2](https://github.com/silverstripe/silverstripe-framework/commit/7384e3fc25987742ea08af74b704857a936e8ec0) Gridfields with dropdowns having lots of overflow (Scott Hutchinson)
+ * 2017-12-21 [44930f211](https://github.com/silverstripe/silverstripe-framework/commit/44930f211be3f658fc92f2d5318255de03078701) Allow HTML 5 input tags in FunctionalTest form submissions (Daniel Hensby)
+ * 2017-12-14 [81150c592](https://github.com/silverstripe/silverstripe-framework/commit/81150c59225dbf1e95bb0b4dbcfbe18346f2bdff) Use PHP 5.3 array syntax (Daniel Hensby)
+ * 2016-10-21 [8e5bb6fbd](https://github.com/silverstripe/silverstripe-framework/commit/8e5bb6fbdce0b2ca2d08a45534df2264db5e6b12) Fix : relObject() should return null if one of the node is null (Jason)
+ * 2016-03-15 [22b3a71ec](https://github.com/silverstripe/silverstripe-framework/commit/22b3a71ec0c8cd8c38030fa0bf5449abefafe8a3) ing val reference to url in https hotlink (Denise Rivera)
+ * 2015-04-22 [1f63637b9](https://github.com/silverstripe/silverstripe-framework/commit/1f63637b9369d4644a92523ada5d1a5dc0576c12) for #4095, TinyMCE not able to modify props of embed media (bug 1) and invalid HTML inserted (bug 2) (Patrick Nelson)
diff --git a/docs/en/04_Changelogs/3.6.5.md b/docs/en/04_Changelogs/3.6.5.md
new file mode 100644
index 000000000..2683d9e1f
--- /dev/null
+++ b/docs/en/04_Changelogs/3.6.5.md
@@ -0,0 +1,16 @@
+# 3.6.5
+
+
+
+## Change Log
+
+### Bugfixes
+
+ * 2018-01-26 [416915b08](https://github.com/silverstripe/silverstripe-framework/commit/416915b08248285083518850ad8d015ca8ed25c2) tableName is blank in CompositeDBField->addToQuery (Dominik Beerbohm)
+ * 2018-01-25 [cf69d0486](https://github.com/silverstripe/silverstripe-framework/commit/cf69d048665befa90eb43146f86cde984b876b3a) Fix ping including requirements (Damian Mooyman)
+ * 2018-01-24 [c2cd6b383](https://github.com/silverstripe/silverstripe-framework/commit/c2cd6b3832c6bc4775b2742df593b445c2aca391) Fix Member_GroupSet::removeAll() (fixes #3948) (Loz Calver)
+ * 2018-01-24 [f2b4c192e](https://github.com/silverstripe/silverstripe-framework/commit/f2b4c192ec4d70779f7c667a976e741a7f3a26c5) Fix UploadField cuts off “Save” button (closes #2862) (Loz Calver)
+ * 2018-01-23 [7384e3fc2](https://github.com/silverstripe/silverstripe-framework/commit/7384e3fc25987742ea08af74b704857a936e8ec0) Gridfields with dropdowns having lots of overflow (Scott Hutchinson)
+ * 2016-10-21 [8e5bb6fbd](https://github.com/silverstripe/silverstripe-framework/commit/8e5bb6fbdce0b2ca2d08a45534df2264db5e6b12) Fix : relObject() should return null if one of the node is null (Jason)
+ * 2016-03-15 [22b3a71ec](https://github.com/silverstripe/silverstripe-framework/commit/22b3a71ec0c8cd8c38030fa0bf5449abefafe8a3) ing val reference to url in https hotlink (Denise Rivera)
+ * 2015-04-22 [1f63637b9](https://github.com/silverstripe/silverstripe-framework/commit/1f63637b9369d4644a92523ada5d1a5dc0576c12) for #4095, TinyMCE not able to modify props of embed media (bug 1) and invalid HTML inserted (bug 2) (Patrick Nelson)
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 5cfd2f92b..9f66d310f 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -11,7 +11,7 @@ Requires PHPUnit ^5.7
tests/php
- cms/tests
+ vendor/silverstripe/cms/tests
diff --git a/src/Dev/CliDebugView.php b/src/Dev/CliDebugView.php
index 86c87422f..b2d25fa27 100644
--- a/src/Dev/CliDebugView.php
+++ b/src/Dev/CliDebugView.php
@@ -160,7 +160,7 @@ class CliDebugView extends DebugView
public function debugVariableText($val)
{
// Check debug
- if (ClassInfo::hasMethod($val, 'debug')) {
+ if (is_object($val) && ClassInfo::hasMethod($val, 'debug')) {
return $val->debug();
}
diff --git a/src/Forms/GridField/GridFieldExportButton.php b/src/Forms/GridField/GridFieldExportButton.php
index c9d65e036..e676cc020 100644
--- a/src/Forms/GridField/GridFieldExportButton.php
+++ b/src/Forms/GridField/GridFieldExportButton.php
@@ -155,7 +155,7 @@ class GridFieldExportButton implements GridField_HTMLProvider, GridField_ActionP
* Generate export fields for CSV.
*
* @param GridField $gridField
- * @return array
+ * @return string
*/
public function generateExportFileData($gridField)
{
@@ -232,7 +232,7 @@ class GridFieldExportButton implements GridField_HTMLProvider, GridField_ActionP
// Convert the $fileData array into csv by capturing fputcsv's output
$csv = fopen('php://temp', 'r+');
foreach ($fileData as $line) {
- fputcsv($csv, $line, $this->csvSeparator, $this->csvEnclosure);
+ fputcsv($csv, $line, $this->getCsvSeparator(), $this->getCsvEnclosure());
}
rewind($csv);
return stream_get_contents($csv);
diff --git a/src/ORM/Connect/MySQLDatabase.php b/src/ORM/Connect/MySQLDatabase.php
index 051ab965f..fa3672db5 100644
--- a/src/ORM/Connect/MySQLDatabase.php
+++ b/src/ORM/Connect/MySQLDatabase.php
@@ -57,6 +57,11 @@ class MySQLDatabase extends Database
*/
private static $collation = 'utf8_general_ci';
+ /**
+ * @var bool
+ */
+ protected $transactionNesting = 0;
+
public function connect($parameters)
{
// Ensure that driver is available (required by PDO)
@@ -300,16 +305,21 @@ class MySQLDatabase extends Database
public function transactionStart($transactionMode = false, $sessionCharacteristics = false)
{
- // This sets the isolation level for the NEXT transaction, not the current one.
- if ($transactionMode) {
- $this->query('SET TRANSACTION ' . $transactionMode);
- }
+ if ($this->transactionNesting > 0) {
+ $this->transactionSavepoint('NESTEDTRANSACTION' . $this->transactionNesting);
+ } else {
+ // This sets the isolation level for the NEXT transaction, not the current one.
+ if ($transactionMode) {
+ $this->query('SET TRANSACTION ' . $transactionMode);
+ }
- $this->query('START TRANSACTION');
+ $this->query('START TRANSACTION');
- if ($sessionCharacteristics) {
- $this->query('SET SESSION TRANSACTION ' . $sessionCharacteristics);
+ if ($sessionCharacteristics) {
+ $this->query('SET SESSION TRANSACTION ' . $sessionCharacteristics);
+ }
}
+ ++$this->transactionNesting;
}
public function transactionSavepoint($savepoint)
@@ -322,13 +332,22 @@ class MySQLDatabase extends Database
if ($savepoint) {
$this->query('ROLLBACK TO ' . $savepoint);
} else {
- $this->query('ROLLBACK');
+ --$this->transactionNesting;
+ if ($this->transactionNesting > 0) {
+ $this->transactionRollback('NESTEDTRANSACTION' . $this->transactionNesting);
+ } else {
+ $this->query('ROLLBACK');
+ }
}
}
public function transactionEnd($chain = false)
{
- $this->query('COMMIT AND ' . ($chain ? '' : 'NO ') . 'CHAIN');
+ --$this->transactionNesting;
+ if ($this->transactionNesting <= 0) {
+ $this->transactionNesting = 0;
+ $this->query('COMMIT AND ' . ($chain ? '' : 'NO ') . 'CHAIN');
+ }
}
public function comparisonClause(
diff --git a/src/Security/Member_GroupSet.php b/src/Security/Member_GroupSet.php
index 582cd2816..ed5abef80 100644
--- a/src/Security/Member_GroupSet.php
+++ b/src/Security/Member_GroupSet.php
@@ -5,6 +5,7 @@ namespace SilverStripe\Security;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\ManyManyList;
+use SilverStripe\ORM\Queries\SQLDelete;
use SilverStripe\ORM\Queries\SQLSelect;
/**
@@ -85,6 +86,36 @@ class Member_GroupSet extends ManyManyList
}
}
+ public function removeAll()
+ {
+ // Remove the join to the join table to avoid MySQL row locking issues.
+ $query = $this->dataQuery();
+ $foreignFilter = $query->getQueryParam('Foreign.Filter');
+ $query->removeFilterOn($foreignFilter);
+
+ // Select ID column
+ $selectQuery = $query->query();
+ $dataClassIDColumn = DataObject::getSchema()->sqlColumnForField($this->dataClass(), 'ID');
+ $selectQuery->setSelect($dataClassIDColumn);
+
+ $from = $selectQuery->getFrom();
+ unset($from[$this->joinTable]);
+ $selectQuery->setFrom($from);
+ $selectQuery->setOrderBy(); // ORDER BY in subselects breaks MS SQL Server and is not necessary here
+ $selectQuery->setDistinct(false);
+
+ // Use a sub-query as SQLite does not support setting delete targets in
+ // joined queries.
+ $delete = new SQLDelete();
+ $delete->setFrom("\"{$this->joinTable}\"");
+ $delete->addWhere(parent::foreignIDFilter());
+ $subSelect = $selectQuery->sql($parameters);
+ $delete->addWhere(array(
+ "\"{$this->joinTable}\".\"{$this->localKey}\" IN ($subSelect)" => $parameters
+ ));
+ $delete->execute();
+ }
+
/**
* Determine if the following groups IDs can be added
*
diff --git a/src/Security/Security.php b/src/Security/Security.php
index 3deb8c0a8..675ff0ba0 100644
--- a/src/Security/Security.php
+++ b/src/Security/Security.php
@@ -25,6 +25,7 @@ use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\ORM\ValidationResult;
use SilverStripe\View\ArrayData;
+use SilverStripe\View\Requirements;
use SilverStripe\View\SSViewer;
use SilverStripe\View\TemplateGlobalProvider;
@@ -491,6 +492,7 @@ class Security extends Controller implements TemplateGlobalProvider
*/
public function ping()
{
+ Requirements::clear();
return 1;
}
diff --git a/tests/php/Dev/CLIDebugViewTest.php b/tests/php/Dev/CLIDebugViewTest.php
index 8729def03..7d8d06661 100644
--- a/tests/php/Dev/CLIDebugViewTest.php
+++ b/tests/php/Dev/CLIDebugViewTest.php
@@ -61,5 +61,16 @@ EOS
,
$view->debugVariable(new ObjectWithDebug(), $this->caller)
);
+
+ $this->assertEquals(
+ <<debugVariable(ObjectWithDebug::class, $this->caller)
+ );
}
}
diff --git a/tests/php/ORM/TransactionTest.php b/tests/php/ORM/TransactionTest.php
index 9c4bc5541..6a0d2b122 100644
--- a/tests/php/ORM/TransactionTest.php
+++ b/tests/php/ORM/TransactionTest.php
@@ -5,57 +5,88 @@ namespace SilverStripe\ORM\Tests;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\DataObject;
use SilverStripe\Dev\SapphireTest;
+use SilverStripe\ORM\Tests\TransactionTest\TestObject;
class TransactionTest extends SapphireTest
{
+ protected $usesDatabase = true;
- protected static $extra_dataobjects = array(
- TransactionTest\TestObject::class
- );
+ protected static $extra_dataobjects = [
+ TransactionTest\TestObject::class,
+ ];
+
+ public static function setUpBeforeClass()
+ {
+ parent::setUpBeforeClass();
+ if (!DB::get_conn()->supportsTransactions()) {
+ static::markTestSkipped('Current database does not support transactions');
+ }
+ }
+
+ public function testNestedTransaction()
+ {
+ $this->assertCount(0, TestObject::get());
+ try {
+ DB::get_conn()->withTransaction(function () {
+ $obj = TransactionTest\TestObject::create();
+ $obj->Title = 'Test';
+ $obj->write();
+
+ $this->assertCount(1, TestObject::get());
+
+ DB::get_conn()->withTransaction(function () {
+ $obj = TransactionTest\TestObject::create();
+ $obj->Title = 'Test2';
+ $obj->write();
+ $this->assertCount(2, TestObject::get());
+ });
+
+ throw new \Exception('roll back transaction');
+ });
+ } catch (\Exception $e) {
+ $this->assertEquals('roll back transaction', $e->getMessage());
+ }
+ $this->assertCount(0, TestObject::get());
+ }
public function testCreateWithTransaction()
{
+ DB::get_conn()->transactionStart();
+ $obj = new TransactionTest\TestObject();
+ $obj->Title = 'First page';
+ $obj->write();
- if (DB::get_conn()->supportsTransactions()==true) {
- DB::get_conn()->transactionStart();
- $obj=new TransactionTest\TestObject();
- $obj->Title='First page';
- $obj->write();
+ $obj = new TransactionTest\TestObject();
+ $obj->Title = 'Second page';
+ $obj->write();
- $obj=new TransactionTest\TestObject();
- $obj->Title='Second page';
- $obj->write();
+ //Create a savepoint here:
+ DB::get_conn()->transactionSavepoint('rollback');
- //Create a savepoint here:
- DB::get_conn()->transactionSavepoint('rollback');
+ $obj = new TransactionTest\TestObject();
+ $obj->Title = 'Third page';
+ $obj->write();
- $obj=new TransactionTest\TestObject();
- $obj->Title='Third page';
- $obj->write();
+ $obj = new TransactionTest\TestObject();
+ $obj->Title = 'Fourth page';
+ $obj->write();
- $obj=new TransactionTest\TestObject();
- $obj->Title='Fourth page';
- $obj->write();
+ //Revert to a savepoint:
+ DB::get_conn()->transactionRollback('rollback');
- //Revert to a savepoint:
- DB::get_conn()->transactionRollback('rollback');
+ DB::get_conn()->transactionEnd();
- DB::get_conn()->transactionEnd();
+ $first = DataObject::get(TransactionTest\TestObject::class, "\"Title\"='First page'");
+ $second = DataObject::get(TransactionTest\TestObject::class, "\"Title\"='Second page'");
+ $third = DataObject::get(TransactionTest\TestObject::class, "\"Title\"='Third page'");
+ $fourth = DataObject::get(TransactionTest\TestObject::class, "\"Title\"='Fourth page'");
- $first=DataObject::get(TransactionTest\TestObject::class, "\"Title\"='First page'");
- $second=DataObject::get(TransactionTest\TestObject::class, "\"Title\"='Second page'");
- $third=DataObject::get(TransactionTest\TestObject::class, "\"Title\"='Third page'");
- $fourth=DataObject::get(TransactionTest\TestObject::class, "\"Title\"='Fourth page'");
+ //These pages should be in the system
+ $this->assertTrue(is_object($first) && $first->exists());
+ $this->assertTrue(is_object($second) && $second->exists());
- //These pages should be in the system
- $this->assertTrue(is_object($first) && $first->exists());
- $this->assertTrue(is_object($second) && $second->exists());
-
- //These pages should NOT exist, we reverted to a savepoint:
- $this->assertFalse(is_object($third) && $third->exists());
- $this->assertFalse(is_object($fourth) && $fourth->exists());
- } else {
- $this->markTestSkipped('Current database does not support transactions');
- }
+ //These pages should NOT exist, we reverted to a savepoint:
+ $this->assertFalse(is_object($third) && $third->exists());
+ $this->assertFalse(is_object($fourth) && $fourth->exists());
}
}
diff --git a/tests/php/Security/MemberTest.php b/tests/php/Security/MemberTest.php
index a78508d15..c4188e17c 100644
--- a/tests/php/Security/MemberTest.php
+++ b/tests/php/Security/MemberTest.php
@@ -411,6 +411,35 @@ class MemberTest extends FunctionalTest
);
}
+ /**
+ * Assertions to check that Member_GroupSet is functionally equivalent to ManyManyList
+ */
+ public function testRemoveGroups()
+ {
+ $staffmember = $this->objFromFixture(Member::class, 'staffmember');
+
+ $staffgroup = $this->objFromFixture(Group::class, 'staffgroup');
+ $managementgroup = $this->objFromFixture(Group::class, 'managementgroup');
+
+ $this->assertTrue(
+ $staffmember->inGroups(array($staffgroup, $managementgroup)),
+ 'inGroups() succeeds if a membership is detected on one of many passed groups'
+ );
+
+ $staffmember->Groups()->remove($managementgroup);
+ $this->assertFalse(
+ $staffmember->inGroup($managementgroup),
+ 'member was not removed from group using ->Groups()->remove()'
+ );
+
+ $staffmember->Groups()->removeAll();
+ $this->assertCount(
+ 0,
+ $staffmember->Groups(),
+ 'member was not removed from all groups using ->Groups()->removeAll()'
+ );
+ }
+
public function testAddToGroupByCode()
{
/** @var Member $grouplessMember */