diff --git a/src/Control/Middleware/CanonicalURLMiddleware.php b/src/Control/Middleware/CanonicalURLMiddleware.php index feec6e109..66e2a2afe 100644 --- a/src/Control/Middleware/CanonicalURLMiddleware.php +++ b/src/Control/Middleware/CanonicalURLMiddleware.php @@ -412,7 +412,10 @@ class CanonicalURLMiddleware implements HTTPMiddleware $paths = (array) $this->getEnforceTrailingSlashConfigIgnorePaths(); if (!empty($paths)) { foreach ($paths as $path) { - if (str_starts_with(trim($path, '/'), trim($requestPath, '/'))) { + if (str_starts_with( + $this->trailingSlashForComparison($requestPath), + $this->trailingSlashForComparison($path) + )) { return false; } } @@ -439,6 +442,15 @@ class CanonicalURLMiddleware implements HTTPMiddleware return true; } + /** + * Ensure a string has a trailing slash to that we can use str_starts_with and compare + * paths like admin/ with administration/ and get a correct result. + */ + private function trailingSlashForComparison(string $path): string + { + return trim($path, '/') . '/'; + } + /** * @return int */ diff --git a/src/Core/Cache/ApcuCacheFactory.php b/src/Core/Cache/ApcuCacheFactory.php index b41de1603..188224063 100644 --- a/src/Core/Cache/ApcuCacheFactory.php +++ b/src/Core/Cache/ApcuCacheFactory.php @@ -3,12 +3,11 @@ namespace SilverStripe\Core\Cache; use SilverStripe\Core\Injector\Injector; -use Symfony\Component\Cache\Simple\ApcuCache; -use Memcached; +use Symfony\Component\Cache\Adapter\ApcuAdapter; +use Symfony\Component\Cache\Psr16Cache; class ApcuCacheFactory implements CacheFactory { - /** * @var string */ @@ -31,10 +30,11 @@ class ApcuCacheFactory implements CacheFactory ? $params['namespace'] . '_' . md5(BASE_PATH) : md5(BASE_PATH); $defaultLifetime = isset($params['defaultLifetime']) ? $params['defaultLifetime'] : 0; - return Injector::inst()->createWithArgs(ApcuCache::class, [ + $psr6Cache = Injector::inst()->createWithArgs(ApcuAdapter::class, [ $namespace, $defaultLifetime, $this->version ]); + return Injector::inst()->createWithArgs(Psr16Cache::class, [$psr6Cache]); } } diff --git a/src/Core/Cache/MemcachedCacheFactory.php b/src/Core/Cache/MemcachedCacheFactory.php index 307b5e549..1752baf5b 100644 --- a/src/Core/Cache/MemcachedCacheFactory.php +++ b/src/Core/Cache/MemcachedCacheFactory.php @@ -3,7 +3,8 @@ namespace SilverStripe\Core\Cache; use SilverStripe\Core\Injector\Injector; -use Symfony\Component\Cache\Simple\MemcachedCache; +use Symfony\Component\Cache\Adapter\MemcachedAdapter; +use Symfony\Component\Cache\Psr16Cache; use Memcached; class MemcachedCacheFactory implements CacheFactory @@ -31,10 +32,11 @@ class MemcachedCacheFactory implements CacheFactory ? $params['namespace'] . '_' . md5(BASE_PATH) : md5(BASE_PATH); $defaultLifetime = isset($params['defaultLifetime']) ? $params['defaultLifetime'] : 0; - return Injector::inst()->createWithArgs(MemcachedCache::class, [ + $psr6Cache = Injector::inst()->createWithArgs(MemcachedAdapter::class, [ $this->memcachedClient, $namespace, $defaultLifetime ]); + return Injector::inst()->createWithArgs(Psr16Cache::class, [$psr6Cache]); } } diff --git a/src/Core/CoreKernel.php b/src/Core/CoreKernel.php index 3af15bb99..db9b69f69 100644 --- a/src/Core/CoreKernel.php +++ b/src/Core/CoreKernel.php @@ -6,6 +6,7 @@ use SilverStripe\Control\HTTPResponse_Exception; use SilverStripe\Dev\Install\DatabaseAdapterRegistry; use SilverStripe\ORM\DB; use Exception; +use LogicException; /** * Simple Kernel container @@ -112,6 +113,29 @@ class CoreKernel extends BaseKernel "password" => Environment::getEnv('SS_DATABASE_PASSWORD') ?: null, ]; + // Only add SSL keys in the array if there is an actual value associated with them + $sslConf = [ + 'ssl_key' => 'SS_DATABASE_SSL_KEY', + 'ssl_cert' => 'SS_DATABASE_SSL_CERT', + 'ssl_ca' => 'SS_DATABASE_SSL_CA', + 'ssl_cipher' => 'SS_DATABASE_SSL_CIPHER', + ]; + foreach ($sslConf as $key => $envVar) { + $envValue = Environment::getEnv($envVar); + if ($envValue) { + $databaseConfig[$key] = $envValue; + } + } + + // Having only the key or cert without the other is bad configuration. + if ((isset($databaseConfig['ssl_key']) && !isset($databaseConfig['ssl_cert'])) + || (!isset($databaseConfig['ssl_key']) && isset($databaseConfig['ssl_cert'])) + ) { + user_error('Database SSL cert and key must both be defined to use SSL in the database.', E_USER_WARNING); + unset($databaseConfig['ssl_key']); + unset($databaseConfig['ssl_cert']); + } + // Set the port if called for $dbPort = Environment::getEnv('SS_DATABASE_PORT'); if ($dbPort) { diff --git a/src/Dev/Install/MySQLDatabaseConfigurationHelper.php b/src/Dev/Install/MySQLDatabaseConfigurationHelper.php index 1c31fa6cf..21fff8627 100644 --- a/src/Dev/Install/MySQLDatabaseConfigurationHelper.php +++ b/src/Dev/Install/MySQLDatabaseConfigurationHelper.php @@ -32,15 +32,15 @@ class MySQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper case 'MySQLDatabase': $conn = mysqli_init(); - // Set SSL parameters if they exist. All parameters are required. - if (array_key_exists('ssl_key', $databaseConfig) && - array_key_exists('ssl_cert', $databaseConfig) && - array_key_exists('ssl_ca', $databaseConfig) + // Set SSL parameters if they exist. + // Must have both the SSL cert and key, or the common authority, or preferably all three. + if ((array_key_exists('ssl_key', $databaseConfig) && array_key_exists('ssl_cert', $databaseConfig)) + || array_key_exists('ssl_ca', $databaseConfig) ) { $conn->ssl_set( - $databaseConfig['ssl_key'], - $databaseConfig['ssl_cert'], - $databaseConfig['ssl_ca'], + $databaseConfig['ssl_key'] ?? null, + $databaseConfig['ssl_cert'] ?? null, + $databaseConfig['ssl_ca'] ?? null, dirname($databaseConfig['ssl_ca']), array_key_exists('ssl_cipher', $databaseConfig) ? $databaseConfig['ssl_cipher'] diff --git a/src/Forms/Form.php b/src/Forms/Form.php index 5735330b5..96b9d1ee4 100644 --- a/src/Forms/Form.php +++ b/src/Forms/Form.php @@ -1617,7 +1617,9 @@ class Form extends ViewableData implements HasRequestHandler public function defaultAction() { if ($this->hasDefaultAction && $this->actions) { - return $this->actions->first(); + return $this->actions->flattenFields()->filterByCallback(function ($field) { + return $field instanceof FormAction; + })->first(); } return null; } diff --git a/src/Forms/GridField/GridFieldFilterHeader.php b/src/Forms/GridField/GridFieldFilterHeader.php index 1069b4bdc..b6b71eda5 100755 --- a/src/Forms/GridField/GridFieldFilterHeader.php +++ b/src/Forms/GridField/GridFieldFilterHeader.php @@ -6,6 +6,7 @@ use LogicException; use SilverStripe\Admin\LeftAndMain; use SilverStripe\Control\Controller; use SilverStripe\Control\HTTPResponse; +use SilverStripe\Core\ClassInfo; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\Form; use SilverStripe\Forms\Schema\FormSchema; @@ -203,20 +204,36 @@ class GridFieldFilterHeader extends AbstractGridFieldComponent implements GridFi return false; } $modelClass = $gridField->getModelClass(); - // note: searchableFields() will return summary_fields if there are no searchable_fields on the model - $searchableFields = array_keys($modelClass::singleton()->searchableFields()); - $summaryFields = array_keys($modelClass::singleton()->summaryFields()); - sort($searchableFields); - sort($summaryFields); - // searchable_fields has been explictily defined i.e. searchableFields() is not falling back to summary_fields - if ($searchableFields !== $summaryFields) { - return true; - } - // we have fallen back to summary_fields, check they are filterable - foreach ($searchableFields as $searchableField) { - if ($list->canFilterBy($searchableField)) { + $singleton = singleton($modelClass); + if (ClassInfo::hasMethod($singleton, 'summaryFields') + && ClassInfo::hasMethod($singleton, 'searchableFields') + ) { + // note: searchableFields() will return summary_fields if there are no searchable_fields on the model + $searchableFields = array_keys($singleton->searchableFields()); + $summaryFields = array_keys($singleton->summaryFields()); + sort($searchableFields); + sort($summaryFields); + // searchable_fields has been explictily defined i.e. searchableFields() is not falling back to summary_fields + if ($searchableFields !== $summaryFields) { return true; } + // we have fallen back to summary_fields, check they are filterable + foreach ($searchableFields as $searchableField) { + if ($list->canFilterBy($searchableField)) { + return true; + } + } + } else { + // Allows non-DataObject classes to be used with this component + $columns = $gridField->getColumns(); + foreach ($columns as $columnField) { + $metadata = $gridField->getColumnMetadata($columnField); + $title = $metadata['title']; + + if ($title && $list->canFilterBy($columnField)) { + return true; + } + } } return false; } diff --git a/src/ORM/Connect/MySQLiConnector.php b/src/ORM/Connect/MySQLiConnector.php index b4ab7b697..20a45d97c 100644 --- a/src/ORM/Connect/MySQLiConnector.php +++ b/src/ORM/Connect/MySQLiConnector.php @@ -96,14 +96,15 @@ class MySQLiConnector extends DBConnector ); } - // Set SSL parameters if they exist. All parameters are required. - if (array_key_exists('ssl_key', $parameters ?? []) && - array_key_exists('ssl_cert', $parameters ?? []) && - array_key_exists('ssl_ca', $parameters ?? [])) { + // Set SSL parameters if they exist. + // Must have both the SSL cert and key, or the common authority, or preferably all three. + if ((array_key_exists('ssl_key', $parameters ?? []) && array_key_exists('ssl_cert', $parameters ?? [])) + || array_key_exists('ssl_ca', $parameters ?? []) + ) { $this->dbConn->ssl_set( - $parameters['ssl_key'], - $parameters['ssl_cert'], - $parameters['ssl_ca'], + $parameters['ssl_key'] ?? null, + $parameters['ssl_cert'] ?? null, + $parameters['ssl_ca'] ?? null, dirname($parameters['ssl_ca'] ?? ''), array_key_exists('ssl_cipher', $parameters ?? []) ? $parameters['ssl_cipher'] diff --git a/src/View/SSViewer_BasicIteratorSupport.php b/src/View/SSViewer_BasicIteratorSupport.php index 04dcaa8e8..074c8d71d 100644 --- a/src/View/SSViewer_BasicIteratorSupport.php +++ b/src/View/SSViewer_BasicIteratorSupport.php @@ -26,8 +26,6 @@ class SSViewer_BasicIteratorSupport implements TemplateIteratorProvider return [ 'IsFirst', 'IsLast', - 'First', - 'Last', 'FirstLast', 'Middle', 'MiddleString', diff --git a/tests/php/Control/Middleware/CanonicalURLMiddlewareTest.php b/tests/php/Control/Middleware/CanonicalURLMiddlewareTest.php index 0798ca594..5689fb767 100644 --- a/tests/php/Control/Middleware/CanonicalURLMiddlewareTest.php +++ b/tests/php/Control/Middleware/CanonicalURLMiddlewareTest.php @@ -85,86 +85,114 @@ class CanonicalURLMiddlewareTest extends SapphireTest $this->assertFalse($middleware->getForceBasicAuthToSSL(), 'Explicitly set is returned'); } - public function testRedirectTrailingSlash() + public function provideRedirectTrailingSlash() { - $testScenarios = [ + $testScenarios = []; + foreach ([true, false] as $forceRedirect) { + foreach ([true, false] as $addTrailingSlash) { + foreach ([true, false] as $requestHasSlash) { + $testScenarios[] = [ + $forceRedirect, + $addTrailingSlash, + $requestHasSlash, + ]; + } + } + } + return $testScenarios; + } + + /** + * @dataProvider provideRedirectTrailingSlash + */ + public function testRedirectTrailingSlash(bool $forceRedirect, bool $addTrailingSlash, bool $requestHasSlash) + { + Controller::config()->set('add_trailing_slash', $addTrailingSlash); + + $noRedirect = !$forceRedirect || ($addTrailingSlash && $requestHasSlash) || (!$addTrailingSlash && !$requestHasSlash); + $middleware = $this->getMockedMiddleware(false); + $middleware->setEnforceTrailingSlashConfig($forceRedirect); + + $requestSlash = $requestHasSlash ? '/' : ''; + $requestURL = "/about-us{$requestSlash}"; + + $this->performRedirectTest($requestURL, $middleware, !$noRedirect, $addTrailingSlash); + } + + private function performRedirectTest(string $requestURL, CanonicalURLMiddleware $middleware, bool $shouldRedirect, bool $addTrailingSlash) + { + Environment::setEnv('REQUEST_URI', $requestURL); + $request = new HTTPRequest('GET', $requestURL); + $request->setScheme('https'); + $request->addHeader('host', 'www.example.com'); + $mockResponse = (new HTTPResponse) + ->setStatusCode(200); + + $result = $middleware->process($request, function () use ($mockResponse) { + return $mockResponse; + }); + + if (!$shouldRedirect) { + $this->assertNull($result->getHeader('Location'), 'No location header should be added'); + $this->assertEquals(200, $result->getStatusCode(), 'No redirection should be made'); + } else { + $this->assertEquals(301, $result->getStatusCode(), 'Responses should be redirected to include/omit trailing slash'); + if ($addTrailingSlash) { + $this->assertStringEndsWith('/', $result->getHeader('Location'), 'Trailing slash should be added'); + } else { + $this->assertStringEndsNotWith('/', $result->getHeader('Location'), 'Trailing slash should be removed'); + } + } + } + + public function provideRedirectTrailingSlashIgnorePaths() + { + return [ [ - 'forceRedirect' => true, - 'addTrailingSlash' => true, - 'requestHasSlash' => true, - ], - [ - 'forceRedirect' => true, - 'addTrailingSlash' => true, - 'requestHasSlash' => false, - ], - [ - 'forceRedirect' => true, - 'addTrailingSlash' => false, - 'requestHasSlash' => true, - ], - [ - 'forceRedirect' => true, 'addTrailingSlash' => false, 'requestHasSlash' => false, ], [ - 'forceRedirect' => false, - 'addTrailingSlash' => true, - 'requestHasSlash' => true, - ], - [ - 'forceRedirect' => false, - 'addTrailingSlash' => true, - 'requestHasSlash' => false, - ], - [ - 'forceRedirect' => false, 'addTrailingSlash' => false, 'requestHasSlash' => true, ], [ - 'forceRedirect' => false, - 'addTrailingSlash' => false, + 'addTrailingSlash' => true, + 'requestHasSlash' => true, + ], + [ + 'addTrailingSlash' => true, 'requestHasSlash' => false, ], ]; - foreach ($testScenarios as $scenario) { - $forceRedirect = $scenario['forceRedirect']; - $addTrailingSlash = $scenario['addTrailingSlash']; - $requestHasSlash = $scenario['requestHasSlash']; + } - $middleware = $this->getMockedMiddleware(false); + /** + * @dataProvider provideRedirectTrailingSlashIgnorePaths + */ + public function testRedirectTrailingSlashIgnorePaths(bool $addTrailingSlash, bool $requestHasSlash) + { + Controller::config()->set('add_trailing_slash', $addTrailingSlash); - $middleware->setEnforceTrailingSlashConfig($forceRedirect); - Controller::config()->set('add_trailing_slash', $addTrailingSlash); + $middleware = $this->getMockedMiddleware(false); + $middleware->setEnforceTrailingSlashConfig(true); - $requestSlash = $requestHasSlash ? '/' : ''; - $requestURL = "/about-us{$requestSlash}"; + $requestSlash = $requestHasSlash ? '/' : ''; + $noRedirectPaths = [ + "/admin{$requestSlash}", + "/admin/graphql{$requestSlash}", + "/dev/tasks/my-task{$requestSlash}", + ]; + $allowRedirectPaths = [ + "/administration{$requestSlash}", + "/administration/more-path{$requestSlash}", + ]; - Environment::setEnv('REQUEST_URI', $requestURL); - $request = new HTTPRequest('GET', $requestURL); - $request->setScheme('https'); - $request->addHeader('host', 'www.example.com'); - $mockResponse = (new HTTPResponse) - ->setStatusCode(200); - - $result = $middleware->process($request, function () use ($mockResponse) { - return $mockResponse; - }); - - $noRedirect = !$forceRedirect || ($addTrailingSlash && $requestHasSlash) || (!$addTrailingSlash && !$requestHasSlash); - if ($noRedirect) { - $this->assertNull($result->getHeader('Location'), 'No location header should be added'); - $this->assertEquals(200, $result->getStatusCode(), 'No redirection should be made'); - } else { - $this->assertEquals(301, $result->getStatusCode(), 'Responses should be redirected to include/omit trailing slash'); - if ($addTrailingSlash) { - $this->assertStringEndsWith('/', $result->getHeader('Location'), 'Trailing slash should be added'); - } else { - $this->assertStringEndsNotWith('/', $result->getHeader('Location'), 'Trailing slash should be removed'); - } - } + foreach ($noRedirectPaths as $path) { + $this->performRedirectTest($path, $middleware, false, $addTrailingSlash); + } + foreach ($allowRedirectPaths as $path) { + $this->performRedirectTest($path, $middleware, $addTrailingSlash !== $requestHasSlash, $addTrailingSlash); } } diff --git a/tests/php/Core/Cache/CacheTest.php b/tests/php/Core/Cache/CacheTest.php index 1853acf88..228b1121a 100644 --- a/tests/php/Core/Cache/CacheTest.php +++ b/tests/php/Core/Cache/CacheTest.php @@ -2,14 +2,17 @@ namespace SilverStripe\Core\Tests\Cache; +use Behat\Gherkin\Cache\MemoryCache; use Psr\SimpleCache\CacheInterface; use SilverStripe\Core\Cache\ApcuCacheFactory; use SilverStripe\Core\Cache\MemcachedCacheFactory; use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Tests\Cache\CacheTest\MockCache; use SilverStripe\Dev\SapphireTest; -use Symfony\Component\Cache\Simple\ApcuCache; -use Symfony\Component\Cache\Simple\MemcachedCache; +use Symfony\Component\Cache\Psr16Cache; +use Symfony\Component\Cache\Adapter\ApcuAdapter; +use Symfony\Component\Cache\Adapter\MemcachedAdapter; +use Memcached; class CacheTest extends SapphireTest { @@ -22,7 +25,10 @@ class CacheTest extends SapphireTest ApcuCacheFactory::class => [ 'constructor' => [ 'version' => 'ss40test' ] ], - MemcachedCacheFactory::class => MemcachedCacheFactory::class, + 'MemcachedClient' => Memcached::class, + MemcachedCacheFactory::class => [ + 'constructor' => [ 'memcachedClient' => '%$MemcachedClient' ] + ], CacheInterface::class . '.TestApcuCache' => [ 'factory' => ApcuCacheFactory::class, 'constructor' => [ @@ -37,42 +43,42 @@ class CacheTest extends SapphireTest 'defaultLifetime' => 5600, ], ], - ApcuCache::class => MockCache::class, - MemcachedCache::class => MockCache::class, + Psr16Cache::class => MockCache::class, + ApcuAdapter::class => MockCache::class, + MemcachedAdapter::class => MockCache::class, ]); } public function testApcuCacheFactory() { - $cache = Injector::inst()->get(CacheInterface::class . '.TestApcuCache'); - $this->assertInstanceOf( - MockCache::class, - $cache - ); + $psr16Cache = Injector::inst()->get(CacheInterface::class . '.TestApcuCache'); + $this->assertInstanceOf(MockCache::class, $psr16Cache); + $this->assertEquals(MockCache::class, get_class($psr16Cache->getArgs()[0])); $this->assertEquals( [ 'TestApcuCache_' . md5(BASE_PATH), 2600, 'ss40test' ], - $cache->getArgs() + $psr16Cache->getArgs()[0]->getArgs() ); } public function testMemCacheFactory() { - $cache = Injector::inst()->get(CacheInterface::class . '.TestMemcache'); - $this->assertInstanceOf( - MockCache::class, - $cache - ); + if (!class_exists(Memcached::class)) { + $this->markTestSkipped('Memcached is not installed'); + } + $psr16Cache = Injector::inst()->get(CacheInterface::class . '.TestMemcache'); + $this->assertInstanceOf(MockCache::class, $psr16Cache); + $this->assertEquals(MockCache::class, get_class($psr16Cache->getArgs()[0])); $this->assertEquals( [ - null, + new MemCached(), 'TestMemCache_' . md5(BASE_PATH), 5600 ], - $cache->getArgs() + $psr16Cache->getArgs()[0]->getArgs() ); } } diff --git a/tests/php/Forms/FormTest.php b/tests/php/Forms/FormTest.php index e78413a29..eab888d68 100644 --- a/tests/php/Forms/FormTest.php +++ b/tests/php/Forms/FormTest.php @@ -7,6 +7,7 @@ use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\Session; use SilverStripe\Dev\CSSContentParser; use SilverStripe\Dev\FunctionalTest; +use SilverStripe\Forms\CompositeField; use SilverStripe\Forms\DateField; use SilverStripe\Forms\DatetimeField; use SilverStripe\Forms\FieldList; @@ -23,7 +24,6 @@ use SilverStripe\Forms\Tests\FormTest\ControllerWithStrictPostCheck; use SilverStripe\Forms\Tests\FormTest\Player; use SilverStripe\Forms\Tests\FormTest\Team; use SilverStripe\Forms\Tests\FormTest\TestController; -use SilverStripe\Forms\Tests\ValidatorTest\TestValidator; use SilverStripe\Forms\TextareaField; use SilverStripe\Forms\TextField; use SilverStripe\Forms\TimeField; @@ -345,6 +345,23 @@ class FormTest extends FunctionalTest ); } + public function testDefaultAction() + { + $form = Form::create(Controller::curr(), 'Form', new FieldList(), new FieldList( + new FormAction('doForm', 'Form Action') + )); + $this->assertNotNull($form->defaultAction()); + $this->assertEquals('action_doForm', $form->defaultAction()->getName()); + + $form = Form::create(Controller::curr(), 'AnotherForm', new FieldList(), new FieldList( + new CompositeField( + new FormAction('doAnotherForm', 'Another Form Action') + ) + )); + $this->assertNotNull($form->defaultAction()); + $this->assertEquals('action_doAnotherForm', $form->defaultAction()->getName()); + } + public function testLoadDataFromIgnoreFalseish() { $form = new Form( diff --git a/tests/php/Forms/GridField/GridFieldFilterHeaderTest.php b/tests/php/Forms/GridField/GridFieldFilterHeaderTest.php index ffc5c185d..145a87441 100644 --- a/tests/php/Forms/GridField/GridFieldFilterHeaderTest.php +++ b/tests/php/Forms/GridField/GridFieldFilterHeaderTest.php @@ -8,13 +8,16 @@ use SilverStripe\Dev\SapphireTest; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\Form; use SilverStripe\Forms\GridField\GridField; +use SilverStripe\Forms\GridField\GridFieldConfig; use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor; use SilverStripe\Forms\GridField\GridFieldFilterHeader; use SilverStripe\Forms\Tests\GridField\GridFieldFilterHeaderTest\Cheerleader; use SilverStripe\Forms\Tests\GridField\GridFieldFilterHeaderTest\CheerleaderHat; use SilverStripe\Forms\Tests\GridField\GridFieldFilterHeaderTest\Mom; +use SilverStripe\Forms\Tests\GridField\GridFieldFilterHeaderTest\NonDataObject; use SilverStripe\Forms\Tests\GridField\GridFieldFilterHeaderTest\Team; use SilverStripe\Forms\Tests\GridField\GridFieldFilterHeaderTest\TeamGroup; +use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataObject; @@ -173,7 +176,7 @@ class GridFieldFilterHeaderTest extends SapphireTest { $gridField = $this->gridField; $filterHeader = $gridField->getConfig()->getComponentByType(GridFieldFilterHeader::class); - + // test that you can filter by something if searchable_fields is not defined // silverstripe will scaffold db columns that are in the gridfield to be // searchable by default @@ -194,4 +197,33 @@ class GridFieldFilterHeaderTest extends SapphireTest Config::modify()->set(Team::class, 'summary_fields', ['MySummaryField']); $this->assertFalse($filterHeader->canFilterAnyColumns($gridField)); } + + public function testCanFilterAnyColumnsNonDataObject() + { + $list = new ArrayList([ + new NonDataObject([]), + ]); + $config = GridFieldConfig::create()->addComponent(new GridFieldFilterHeader()); + $gridField = new GridField('testfield', 'testfield', $list, $config); + $form = new Form(null, 'Form', new FieldList([$gridField]), new FieldList()); + /** @var GridFieldFilterHeader $component */ + $component = $gridField->getConfig()->getComponentByType(GridFieldFilterHeader::class); + + $this->assertFalse($component->canFilterAnyColumns($gridField)); + } + + public function testRenderHeadersNonDataObject() + { + $list = new ArrayList([ + new NonDataObject([]), + ]); + $config = GridFieldConfig::create()->addComponent(new GridFieldFilterHeader()); + $gridField = new GridField('testfield', 'testfield', $list, $config); + $form = new Form(null, 'Form', new FieldList([$gridField]), new FieldList()); + /** @var GridFieldFilterHeader $component */ + $component = $gridField->getConfig()->getComponentByType(GridFieldFilterHeader::class); + $htmlFragment = $component->getHTMLFragments($gridField); + + $this->assertNull($htmlFragment); + } } diff --git a/tests/php/Forms/GridField/GridFieldFilterHeaderTest/NonDataObject.php b/tests/php/Forms/GridField/GridFieldFilterHeaderTest/NonDataObject.php new file mode 100644 index 000000000..c446dddfc --- /dev/null +++ b/tests/php/Forms/GridField/GridFieldFilterHeaderTest/NonDataObject.php @@ -0,0 +1,14 @@ + 'Title']; + } +}