diff --git a/src/Core/Manifest/ConfigManifest.php b/src/Core/Manifest/ConfigManifest.php index ec2986c6b..b5f1c1aaa 100644 --- a/src/Core/Manifest/ConfigManifest.php +++ b/src/Core/Manifest/ConfigManifest.php @@ -126,7 +126,7 @@ class ConfigManifest } // If we don't have a variantKeySpec (because we're forcing regen, or it just wasn't in the cache), generate it - if (false === $this->variantKeySpec) { + if (false === $this->phpConfigSources || false === $this->variantKeySpec) { $this->regenerate($includeTests); } diff --git a/src/ORM/ListDecorator.php b/src/ORM/ListDecorator.php index 6a2adde56..8d1397fcd 100644 --- a/src/ORM/ListDecorator.php +++ b/src/ORM/ListDecorator.php @@ -20,8 +20,7 @@ abstract class ListDecorator extends ViewableData implements SS_List, Sortable, public function __construct(SS_List $list) { - $this->list = $list; - $this->failover = $this->list; + $this->setList($list); parent::__construct(); } @@ -36,6 +35,21 @@ abstract class ListDecorator extends ViewableData implements SS_List, Sortable, return $this->list; } + /** + * Set the list this decorator wraps around. + * + * Useful for keeping a decorator/paginated list configuration intact while modifying + * the underlying list. + * + * @return SS_List + */ + public function setList($list) + { + $this->list = $list; + $this->failover = $this->list; + return $this; + } + // PROXIED METHODS --------------------------------------------------------- public function offsetExists($key) diff --git a/src/Security/Security.php b/src/Security/Security.php index c5a71e7b5..46710400b 100644 --- a/src/Security/Security.php +++ b/src/Security/Security.php @@ -184,6 +184,15 @@ class Security extends Controller implements TemplateGlobalProvider */ private static $frame_options = 'SAMEORIGIN'; + /** + * Value of the X-Robots-Tag header (for the Security section) + * + * @config + * @var string + */ + private static $robots_tag = 'noindex, nofollow'; + + /** * Get location of word list file * @@ -368,7 +377,14 @@ class Security extends Controller implements TemplateGlobalProvider parent::init(); // Prevent clickjacking, see https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options - $this->getResponse()->addHeader('X-Frame-Options', $this->config()->frame_options); + if ($this->config()->frame_options) { + $this->getResponse()->addHeader('X-Frame-Options', $this->config()->frame_options); + } + + // Prevent search engines from indexing the login page + if ($this->config()->robots_tag) { + $this->getResponse()->addHeader('X-Robots-Tag', $this->config()->robots_tag); + } } public function index() diff --git a/tests/php/Core/Manifest/ConfigManifestTest.php b/tests/php/Core/Manifest/ConfigManifestTest.php index ea4e4f531..d8d5e6f20 100644 --- a/tests/php/Core/Manifest/ConfigManifestTest.php +++ b/tests/php/Core/Manifest/ConfigManifestTest.php @@ -164,6 +164,84 @@ class ConfigManifestTest extends SapphireTest $manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', false, false); } + /** + * Test cache regeneration if all or some of the cache files are missing + * + * 1. Test regeneration if all cache files are missing + * 2. Test regeneration if 'variant_key_spec' cache file is missing + * 3. Test regeneration if 'php_config_sources' cache file is missing + */ + public function testAutomaticCacheRegeneration() + { + $base = dirname(__FILE__) . '/fixtures/configmanifest'; + + // Test regeneration if all cache files are missing + $manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant')); + + $manifest->expects($this->once())// regenerate should be called once + ->method('regenerate') + ->with($this->equalTo(false)); // includeTests = false + + // Set up a cache where we expect load to never be called + $cache = $this->getCacheMock(); + $cache->expects($this->exactly(2)) + ->will($this->returnValue(false)) + ->method('load'); + + $manifest->expects($this->any()) + ->method('getCache') + ->will($this->returnValue($cache)); + + $manifest->__construct($base); + + // Test regeneration if 'variant_key_spec' cache file is missing + $manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant')); + + $manifest->expects($this->once())// regenerate should be called once + ->method('regenerate') + ->with($this->equalTo(false)); // includeTests = false + + + $cache = $this->getCacheMock(); + $cache->expects($this->exactly(2)) + ->method('load') + ->will($this->returnCallback(function ($parameter) { + if (strpos($parameter, 'variant_key_spec') !== false) { + return false; + } + return array(); + })); + + $manifest->expects($this->any()) + ->method('getCache') + ->will($this->returnValue($cache)); + + $manifest->__construct($base); + + // Test regeneration if 'php_config_sources' cache file is missing + $manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant')); + + $manifest->expects($this->once())// regenerate should be called once + ->method('regenerate') + ->with($this->equalTo(false)); // includeTests = false + + $cache = $this->getCacheMock(); + $cache->expects($this->exactly(2)) + ->method('load') + ->will($this->returnCallback(function ($parameter) { + if (strpos($parameter, 'php_config_sources') !== false) { + return false; + } + return array(); + })); + + $manifest->expects($this->any()) + ->method('getCache') + ->will($this->returnValue($cache)); + + $manifest->__construct($base); + } + /** * This test checks the processing of before and after reference paths (module-name/filename#fragment) * This method uses fixture/configmanifest/mysite/_config/addyamlconfigfile.yml as a fixture diff --git a/tests/php/Security/SecurityTest.php b/tests/php/Security/SecurityTest.php index 68e7fe8aa..35432489c 100644 --- a/tests/php/Security/SecurityTest.php +++ b/tests/php/Security/SecurityTest.php @@ -673,6 +673,22 @@ class SecurityTest extends FunctionalTest Security::$force_database_is_ready = $old; } + public function testSecurityControllerSendsRobotsTagHeader() + { + $response = $this->get(Config::inst()->get(Security::class, 'login_url')); + $robotsHeader = $response->getHeader('X-Robots-Tag'); + $this->assertNotNull($robotsHeader); + $this->assertContains('noindex', $robotsHeader); + } + + public function testDoNotSendEmptyRobotsHeaderIfNotDefined() + { + Config::inst()->remove(Security::class, 'robots_tag'); + $response = $this->get(Config::inst()->get(Security::class, 'login_url')); + $robotsHeader = $response->getHeader('X-Robots-Tag'); + $this->assertNull($robotsHeader); + } + /** * Execute a log-in form using Director::test(). * Helper method for the tests above