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