From aeb8814ddc997a78170f1f6df25ff1f582e30b8c Mon Sep 17 00:00:00 2001 From: Andrew Short Date: Sun, 1 Dec 2013 22:24:40 +1100 Subject: [PATCH 01/16] Don't set all injector instances to the global instance. --- control/injector/Injector.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/control/injector/Injector.php b/control/injector/Injector.php index 82d9cae6f..b50c5ed8c 100644 --- a/control/injector/Injector.php +++ b/control/injector/Injector.php @@ -201,8 +201,6 @@ class Injector { if ($config) { $this->load($config); } - - self::$instance = $this; } /** From b7b041b435004e52ef880dcd32365298cca8405a Mon Sep 17 00:00:00 2001 From: Andrew Short Date: Sun, 1 Dec 2013 22:25:19 +1100 Subject: [PATCH 02/16] FIX: Only unregister the relevant class when adding an extension. This fixes an issue where the Config instance could not be injected, as it would be immediately cleared. --- core/Object.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Object.php b/core/Object.php index b4251d2db..13785a88e 100755 --- a/core/Object.php +++ b/core/Object.php @@ -550,7 +550,7 @@ abstract class Object { Config::inst()->update($class, 'extensions', array($extension)); Config::inst()->extraConfigSourcesChanged($class); - Injector::inst()->unregisterAllObjects(); + Injector::inst()->unregisterNamedObject($class); // load statics now for DataObject classes if(is_subclass_of($class, 'DataObject')) { From 050115f43141a850ac9d10f6a23be17695d31a76 Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Tue, 10 Dec 2013 10:35:03 +1300 Subject: [PATCH 03/16] FIX Dont pop up basic auth dialog when trying to flush and isDev=1, just redirect to Security/login like normal --- main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.php b/main.php index f60b5d9c9..888376897 100644 --- a/main.php +++ b/main.php @@ -134,7 +134,7 @@ $chain // Then if a flush was requested, redirect to it if ($token->parameterProvided() && !$token->tokenProvided()) { // First, check if we're in dev mode, or the database doesn't have any security data - $canFlush = Director::isDev() || !Security::database_is_ready(); + $canFlush = Director::isDev(true) || !Security::database_is_ready(); // Otherwise, we start up the session if needed, then check for admin if (!$canFlush) { From 293c672fa7740a8e7b99ac8f431b842cf78b0738 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Thu, 30 Jan 2014 15:55:14 +1300 Subject: [PATCH 04/16] BUG Default Member.Locale now chooses a better default value when i18n.locale is not a valid translation This will resolve issues in cases where the site locale may be assigned a value that does not have an explicit translation. E.g. if the locale is en_NZ (and it's appropriate for this to be the assigned locale), Afrikaans will no longer be the default selected locale when creating members. Now en_US is chosen as a better fallback default. This is a minor ease of use fix that means fewer CMS users can be accidentally created in Afrikaans within NZ based sites. Test cases included. --- i18n/i18n.php | 18 ++++++++++++++++++ security/Member.php | 2 +- tests/i18n/i18nTest.php | 26 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/i18n/i18n.php b/i18n/i18n.php index 3ede6ee56..e0b121eda 100644 --- a/i18n/i18n.php +++ b/i18n/i18n.php @@ -2208,6 +2208,24 @@ class i18n extends Object implements TemplateGlobalProvider { return (array)Config::inst()->get('i18n', 'all_locales'); } + /** + * Matches a given locale with the closest translation available in the system + * + * @param string $locale locale code + * @return string Locale of closest available translation, if available + */ + public static function get_closest_translation($locale) { + + // Check if exact match + $pool = self::get_existing_translations(); + if(isset($pool[$locale])) return $locale; + + // Fallback to best locale for common language + $lang = self::get_lang_from_locale($locale); + $candidate = self::get_locale_from_lang($lang); + if(isset($pool[$candidate])) return $candidate; + } + /** * Searches the root-directory for module-directories * (identified by having a _config.php on their first directory-level). diff --git a/security/Member.php b/security/Member.php index cb16a8632..a0812565c 100644 --- a/security/Member.php +++ b/security/Member.php @@ -158,7 +158,7 @@ class Member extends DataObject implements TemplateGlobalProvider { */ public function populateDefaults() { parent::populateDefaults(); - $this->Locale = i18n::get_locale(); + $this->Locale = i18n::get_closest_translation(i18n::get_locale()); } public function requireDefaultRecords() { diff --git a/tests/i18n/i18nTest.php b/tests/i18n/i18nTest.php index fd98cbf82..1d2cc131c 100644 --- a/tests/i18n/i18nTest.php +++ b/tests/i18n/i18nTest.php @@ -102,6 +102,32 @@ class i18nTest extends SapphireTest { $this->assertTrue(isset($translations['de_DE']), 'Checking for de_DE translation'); } + public function testGetClosestTranslation() { + + // Validate necessary assumptions for this test + $translations = i18n::get_existing_translations(); + $this->assertTrue(isset($translations['en_US'])); + $this->assertTrue(isset($translations['en_GB'])); + $this->assertTrue(isset($translations['es_ES'])); + $this->assertTrue(isset($translations['es_AR'])); + $this->assertFalse(isset($translations['en_ZZ'])); + $this->assertFalse(isset($translations['es_ZZ'])); + $this->assertFalse(isset($translations['zz_ZZ'])); + + // Test indeterminate locales + $this->assertEmpty(i18n::get_closest_translation('zz_ZZ')); + + // Test english fallback + $this->assertEquals('en_US', i18n::get_closest_translation('en_US')); + $this->assertEquals('en_GB', i18n::get_closest_translation('en_GB')); + $this->assertEquals('en_US', i18n::get_closest_translation('en_ZZ')); + + // Test spanish fallbacks + $this->assertEquals('es_AR', i18n::get_closest_translation('es_AR')); + $this->assertEquals('es_ES', i18n::get_closest_translation('es_ES')); + $this->assertEquals('es_ES', i18n::get_closest_translation('es_XX')); + } + public function testDataObjectFieldLabels() { $oldLocale = i18n::get_locale(); i18n::set_locale('de_DE'); From 2f817ba17756a3b27630e553e14d86ee4200ef0d Mon Sep 17 00:00:00 2001 From: Andrew Short Date: Mon, 3 Feb 2014 11:30:22 +1100 Subject: [PATCH 05/16] NEW: Allow specifying a factory to use for creating services. A service factory can be used for creating instances where a non-trivial construction process is required. This is done by adding a `factory` key to the service definition. --- control/injector/InjectionCreator.php | 15 ++-- control/injector/Injector.php | 76 +++++++++++-------- .../injector/SilverStripeInjectionCreator.php | 20 ++--- docs/en/reference/injector.md | 28 ++++++- .../Framework/Injector/Factory.php | 19 +++++ tests/injector/InjectorTest.php | 24 +++++- 6 files changed, 128 insertions(+), 54 deletions(-) create mode 100644 src/SilverStripe/Framework/Injector/Factory.php diff --git a/control/injector/InjectionCreator.php b/control/injector/InjectionCreator.php index c521a68d1..e6021d8a9 100644 --- a/control/injector/InjectionCreator.php +++ b/control/injector/InjectionCreator.php @@ -1,20 +1,16 @@ newInstance(); } -} \ No newline at end of file + +} diff --git a/control/injector/Injector.php b/control/injector/Injector.php index b50c5ed8c..4dca8204f 100644 --- a/control/injector/Injector.php +++ b/control/injector/Injector.php @@ -1,9 +1,13 @@ 'FactoryService' // A factory service to use to create instances. * 'construct' => array( // properties to set at construction * 'scalar', * '%$BeanId', @@ -94,25 +99,25 @@ require_once dirname(__FILE__) . '/SilverStripeServiceConfigurationLocator.php'; * * In addition to specifying the bindings directly in the configuration, * you can simply create a publicly accessible property on the target - * class which will automatically be injected if the autoScanProperties + * class which will automatically be injected if the autoScanProperties * option is set to true. This means a class defined as - * + * * * class MyController extends Controller { - * + * * private $permissionService; - * + * * public setPermissionService($p) { * $this->permissionService = $p; - * } + * } * } * - * + * * will have setPermissionService called if - * + * * * Injector::inst()->setAutoScanProperties(true) is called and - * * A service named 'PermissionService' has been configured - * + * * A service named 'PermissionService' has been configured + * * @author marcus@silverstripe.com.au * @package framework * @subpackage injector @@ -161,16 +166,18 @@ class Injector { * @var boolean */ private $autoScanProperties = false; - + /** - * The object used to create new class instances - * - * Use a custom class here to change the way classes are created to use - * a custom creation method. By default the InjectionCreator class is used, - * which simply creates a new class via 'new', however this could be overridden - * to use, for example, SilverStripe's Object::create() method. + * The default factory used to create new instances. * - * @var InjectionCreator + * The {@link InjectionCreator} is used by default, which simply directly + * creates objects. This can be changed to use a different default creation + * method if desired. + * + * Each individual component can also specify a custom factory to use by + * using the `factory` parameter. + * + * @var Factory */ protected $objectCreator; @@ -190,7 +197,7 @@ class Injector { ); $this->autoProperties = array(); - + $creatorClass = isset($config['creator']) ? $config['creator'] : 'InjectionCreator'; $locatorClass = isset($config['locator']) ? $config['locator'] : 'ServiceConfigurationLocator'; @@ -215,7 +222,16 @@ class Injector { } return self::$instance; } - + + /** + * Sets the default global injector instance. + * + * @param Injector $instance + */ + public static function set_inst(Injector $instance) { + self::$instance = $instance; + } + /** * Indicate whether we auto scan injected objects for properties to set. * @@ -226,17 +242,16 @@ class Injector { } /** - * Sets the object to use for creating new objects + * Sets the default factory to use for creating new objects. * - * @param InjectionCreator $obj + * @param Factory $obj */ - public function setObjectCreator($obj) { + public function setObjectCreator(Factory $obj) { $this->objectCreator = $obj; } /** - * Accessor (for testing purposes) - * @return InjectionCreator + * @return Factory */ public function getObjectCreator() { return $this->objectCreator; @@ -486,8 +501,9 @@ class Injector { $constructorParams = $spec['constructor']; } - $object = $this->objectCreator->create($class, $constructorParams); - + $factory = isset($spec['factory']) ? $this->get($spec['factory']) : $this->getObjectCreator(); + $object = $factory->create($class, $constructorParams); + // figure out if we have a specific id set or not. In some cases, we might be instantiating objects // that we don't manage directly; we don't want to store these in the service cache below if (!$id) { diff --git a/control/injector/SilverStripeInjectionCreator.php b/control/injector/SilverStripeInjectionCreator.php index 809d61142..f4e454960 100644 --- a/control/injector/SilverStripeInjectionCreator.php +++ b/control/injector/SilverStripeInjectionCreator.php @@ -1,22 +1,18 @@ newInstanceArgs($params); + + return $params ? $reflector->newInstanceArgs($params) : $reflector->newInstance(); } -} \ No newline at end of file + +} diff --git a/docs/en/reference/injector.md b/docs/en/reference/injector.md index 98ce9c4b2..20b8b792b 100644 --- a/docs/en/reference/injector.md +++ b/docs/en/reference/injector.md @@ -76,8 +76,7 @@ The subsequent call returns the SAME object as the first call. In this case, on creation of the MyController object, the injector will automatically instantiate the PermissionService object and set it as -the **permissions** property. - +the **permissions** property. ## Configuring objects managed by the dependency injector @@ -90,6 +89,31 @@ Configuration can be specified for two areas of dependency management * Defining dependency overrides for individual classes * Injector managed 'services' +### Factories + +Some services require non-trivial construction which means they must be created by a factory class. To do this, create +a factory class which implements the `[api:SilverStripe\Framework\Injector\Factory]` interface. You can then specify +the `factory` key in the service definition, and the factory service will be used. + +An example using the `MyFactory` service to create instances of the `MyService` service is shown below: + + :::yml + Injector: + MyService: + factory: MyFactory + MyFactory: + class: MyFactoryImplementation + + :::php + class MyFactoryImplementation implements SilverStripe\Framework\Injector\Factory { + public function create($service, array $params = array()) { + return new MyServiceImplementation(); + } + } + + // Will use MyFactoryImplementation::create() to create the service instance. + $instance = Injector::inst()->get('MyService'); + ### Dependency overrides To override the **static $dependency;** declaration for a class, you could diff --git a/src/SilverStripe/Framework/Injector/Factory.php b/src/SilverStripe/Framework/Injector/Factory.php new file mode 100644 index 000000000..ffc61ddf3 --- /dev/null +++ b/src/SilverStripe/Framework/Injector/Factory.php @@ -0,0 +1,19 @@ +assertEquals($item->property, 'othervalue'); } + /** + * Tests creating a service with a custom factory. + */ + public function testCustomFactory() { + $injector = new Injector(array( + 'service' => array('factory' => 'factory', 'constructor' => array(1, 2, 3)) + )); + + $factory = $this->getMock('SilverStripe\\Framework\\Injector\\Factory'); + $factory + ->expects($this->once()) + ->method('create') + ->with($this->equalTo('service'), $this->equalTo(array(1, 2, 3))) + ->will($this->returnCallback(function($args) { + return new TestObject(); + })); + + $injector->registerService($factory, 'factory'); + + $this->assertInstanceOf('TestObject', $injector->get('service')); + } + } class InjectorTestConfigLocator extends SilverStripeServiceConfigurationLocator implements TestOnly { @@ -667,7 +689,7 @@ class SSObjectCreator extends InjectionCreator { $this->injector = $injector; } - public function create($class, $params = array()) { + public function create($class, array $params = array()) { if (strpos($class, '(') === false) { return parent::create($class, $params); } else { From 4ac5a749b065887813625f31f2ca7bf1270a5014 Mon Sep 17 00:00:00 2001 From: Andrew Short Date: Sun, 1 Dec 2013 22:40:42 +1100 Subject: [PATCH 06/16] Register config as a service. --- core/Core.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/Core.php b/core/Core.php index 39a726b45..18f0b1891 100644 --- a/core/Core.php +++ b/core/Core.php @@ -84,8 +84,10 @@ require_once 'control/injector/Injector.php'; // Initialise the dependency injector as soon as possible, as it is // subsequently used by some of the following code -$default_options = array('locator' => 'SilverStripeServiceConfigurationLocator'); -Injector::inst($default_options); +$injector = new Injector(array('locator' => 'SilverStripeServiceConfigurationLocator')); +$injector->registerService(Config::inst()); + +Injector::set_inst($injector); /////////////////////////////////////////////////////////////////////////////// // MANIFEST From 0da5413e8ded4c74d574a51232aa27cb143e499f Mon Sep 17 00:00:00 2001 From: Joel Marcey Date: Mon, 3 Feb 2014 15:32:58 -0800 Subject: [PATCH 07/16] Support PHPUnit 3.8+ compatibility Summary: PHPUnit 3.8+ adds a method to its PHPUnit_Framework_TestListener called addRiskyTest(). Need to stub it out to avoid "must implement this interface method" fatals when using 3.8+ Test Plan: Reviewers: CC: Task ID: # Blame Rev: --- dev/SapphireTestReporter.php | 12 ++++++++++++ dev/SilverStripeListener.php | 12 ++++++++++++ dev/TeamCityListener.php | 12 ++++++++++++ dev/TestListener.php | 12 ++++++++++++ 4 files changed, 48 insertions(+) diff --git a/dev/SapphireTestReporter.php b/dev/SapphireTestReporter.php index 84da76c72..602520d0e 100644 --- a/dev/SapphireTestReporter.php +++ b/dev/SapphireTestReporter.php @@ -300,6 +300,18 @@ class SapphireTestReporter implements PHPUnit_Framework_TestListener { } } + /** + * Risky test. + * + * @param PHPUnit_Framework_Test $test + * @param Exception $e + * @param float $time + * @since Method available since Release 3.8.0 + */ + public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { + // Stub out to support PHPUnit 3.8 + } + /** * Trys to get the original exception thrown by the test on failure/error * to enable us to give a bit more detail about the failure/error diff --git a/dev/SilverStripeListener.php b/dev/SilverStripeListener.php index d5c0a4437..6c5c95fe9 100644 --- a/dev/SilverStripeListener.php +++ b/dev/SilverStripeListener.php @@ -46,4 +46,16 @@ class SilverStripeListener implements PHPUnit_Framework_TestListener { public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { } + + /** + * Risky test. + * + * @param PHPUnit_Framework_Test $test + * @param Exception $e + * @param float $time + * @since Method available since Release 3.8.0 + */ + public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { + // Stub out to support PHPUnit 3.8 + } } diff --git a/dev/TeamCityListener.php b/dev/TeamCityListener.php index fb94c49ec..0439742d6 100644 --- a/dev/TeamCityListener.php +++ b/dev/TeamCityListener.php @@ -61,4 +61,16 @@ class TeamCityListener implements PHPUnit_Framework_TestListener { $message = $this->escape($e->getMessage()); echo "##teamcity[testIgnored name='{$class}.{$test->getName()}' message='$message']\n"; } + + /** + * Risky test. + * + * @param PHPUnit_Framework_Test $test + * @param Exception $e + * @param float $time + * @since Method available since Release 3.8.0 + */ + public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { + // Stub out to support PHPUnit 3.8 + } } diff --git a/dev/TestListener.php b/dev/TestListener.php index 5494dfd89..4c0115004 100644 --- a/dev/TestListener.php +++ b/dev/TestListener.php @@ -38,6 +38,18 @@ class SS_TestListener implements PHPUnit_Framework_TestListener { $this->class->tearDownOnce(); } + /** + * Risky test. + * + * @param PHPUnit_Framework_Test $test + * @param Exception $e + * @param float $time + * @since Method available since Release 3.8.0 + */ + public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { + // Stub out to support PHPUnit 3.8 + } + /** * @param String Classname * @return boolean From d1c68e6020794fb63d4eb540dc5ad2de6cd70d92 Mon Sep 17 00:00:00 2001 From: Nik Rolls Date: Tue, 4 Feb 2014 15:36:24 +1300 Subject: [PATCH 08/16] Fix autocompletion on ::create and ::strong_create This ties in with IDEs that can autocomplete the LSB class when you @return static. --- core/Object.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/Object.php b/core/Object.php index 13785a88e..821a17d8a 100755 --- a/core/Object.php +++ b/core/Object.php @@ -127,7 +127,7 @@ abstract class Object { * * @param string $class the class name * @param mixed $arguments,... arguments to pass to the constructor - * @return Object + * @return static */ public static function create() { $args = func_get_args(); @@ -279,7 +279,7 @@ abstract class Object { * * @param string $class the class name * @param mixed $arguments,... arguments to pass to the constructor - * @return Object + * @return static */ public static function strong_create() { $args = func_get_args(); From 21b5a588c23b37d465be81aa04486e7ffde883c9 Mon Sep 17 00:00:00 2001 From: Matt Kirwan Date: Thu, 6 Feb 2014 17:09:28 +0000 Subject: [PATCH 09/16] Updated filename to ref 'Holder' not 'Page' --- docs/en/tutorials/2-extending-a-basic-site.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/tutorials/2-extending-a-basic-site.md b/docs/en/tutorials/2-extending-a-basic-site.md index ffaf84f1a..46ba47372 100644 --- a/docs/en/tutorials/2-extending-a-basic-site.md +++ b/docs/en/tutorials/2-extending-a-basic-site.md @@ -387,7 +387,7 @@ The controller for a page is only created when page is actually visited, while t An RSS feed is something that no news section should be without. SilverStripe makes it easy to create RSS feeds by providing an `[api:RSSFeed]` class to do all the hard work for us. Add the following in the *ArticleHolder_Controller* class: -**mysite/code/ArticlePage.php** +**mysite/code/ArticleHolder.php** :::php private static $allowed_actions = array( From 486af1d1a329f9efddfad2a50637e566c5578b0e Mon Sep 17 00:00:00 2001 From: Matt Kirwan Date: Thu, 6 Feb 2014 17:14:22 +0000 Subject: [PATCH 10/16] Updated filename to reference 'Holder' not 'Page' --- docs/en/tutorials/2-extending-a-basic-site.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/tutorials/2-extending-a-basic-site.md b/docs/en/tutorials/2-extending-a-basic-site.md index ffaf84f1a..c62e1419c 100644 --- a/docs/en/tutorials/2-extending-a-basic-site.md +++ b/docs/en/tutorials/2-extending-a-basic-site.md @@ -408,7 +408,7 @@ Depending on your browser, you should see something like the picture below. If y Now all we need is to let the user know that our RSS feed exists. Add this function to *ArticleHolder_Controller*: -**mysite/code/ArticlePage.php** +**mysite/code/ArticleHolder.php** :::php public function init() { From 4b2352afcf293dc0774791a31975ec1c6709bfcb Mon Sep 17 00:00:00 2001 From: Kirk Mayo Date: Fri, 7 Feb 2014 10:40:28 +1300 Subject: [PATCH 11/16] NEW: Correcting the JSRunnerTest template comments --- dev/JSTestRunner.php | 48 +++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/dev/JSTestRunner.php b/dev/JSTestRunner.php index 18e96e862..c97edc58f 100644 --- a/dev/JSTestRunner.php +++ b/dev/JSTestRunner.php @@ -6,27 +6,33 @@ * * To create your own tests, please use this template: * - * - * - * - * - * - * - * - * - * - *

My Test Name

- * - *

- *
    - *
    - * + * + * + * + * jQuery - Validation Test Suite + * + * + * + * + * + * + *

    + * + * jQuery Validation Plugin Test Suite

    + *

    + *
    + *

    + *
      + * * *
      * From 620ee834d03b359fa3e5e02a17eb779e149e4dd7 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 7 Feb 2014 11:56:48 +1300 Subject: [PATCH 12/16] Docs for whitespace fixes --- docs/en/installation/common-problems.md | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/en/installation/common-problems.md b/docs/en/installation/common-problems.md index f824e232d..2a5270594 100644 --- a/docs/en/installation/common-problems.md +++ b/docs/en/installation/common-problems.md @@ -74,3 +74,38 @@ needs to create/have write-access to: * The mysite folder (to create _config.php) * After the install, the assets directory is the only directory that needs write access. * Image thumbnails will not show in the CMS if permission is not given + +## I have whitespace before my HTML output, triggering quirks mode or preventing cookies from being set + +SilverStripe only uses class declarations in PHP files, and doesn't output any content +directly outside of these declarations. It's easy to accidentally add whitespace +or any other characters before the `` closing braket at the end of the document. + +Since we're dealing with hundreds of included files, identifying these mistakes manually can be tiresome. +The best way to detect whitespace is to look through your version control system for any uncommitted changes. +If that doesn't work out, here's a little script to run checks in all relevant PHP files. +Save it as `check.php` into your webroot, and run it as `php check.php` (or open it in your browser). +After using the script (and fixing errors afterwards), please remember to remove it again. + +```php + $file){ + if($file->getExtension() != 'php') continue; + if(preg_match('/thirdparty|vendor/',$file->getPathname())) continue; + $content = file_get_contents($file->getPathname()); + if(preg_match('/^[[:blank:]]+<\?' . 'php/', $content)) { + echo sprintf("%s: Space before opening bracket\n", $file->getPathname()); + $matched = true; + } + if(preg_match('/^\?' . '>\n?[[:blank:]]+/m', $content)) { + echo sprintf("%s: Space after closing bracket\n", $file->getPathname()); + $matched = true; + } +} +``` \ No newline at end of file From 632884252bff856d8efd420273e7dc80eda886d0 Mon Sep 17 00:00:00 2001 From: Kirk Mayo Date: Fri, 7 Feb 2014 15:10:44 +1300 Subject: [PATCH 13/16] NEW: Updating out of date URLs in the framework source code and docs --- docs/en/installation/windows-pi.md | 2 +- docs/en/misc/contributing/code.md | 6 +++--- docs/en/reference/cms-architecture.md | 2 +- search/FulltextSearchable.php | 2 +- security/MemberAuthenticator.php | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/en/installation/windows-pi.md b/docs/en/installation/windows-pi.md index 83a1ca916..f645e451c 100644 --- a/docs/en/installation/windows-pi.md +++ b/docs/en/installation/windows-pi.md @@ -5,7 +5,7 @@ ## Installing SilverStripe * Download and run Microsoft Web Platform Installer (WebPI): -[![](http://www.silverstripe.org/assets/downloads/webpi/wpiBadgeGreen.jpg)](http://www.microsoft.com/web/gallery/install.aspx?appsxml=www.microsoft.com%2fweb%2fwebpi%2f2.0%2fWebApplicationList.xml&appid=105) +[![](http://www.silverstripe.org/assets/downloads/webpi/wpiBadgeGreen.png)](http://www.microsoft.com/web/gallery/install.aspx?appsxml=www.microsoft.com%2fweb%2fwebpi%2f2.0%2fWebApplicationList.xml&appid=105) * In WebPI, select 'SilverStripe' from the 'Content Management System' link * Select install. It will install dependancies like MySQL and PHP if you don't have these installed already. diff --git a/docs/en/misc/contributing/code.md b/docs/en/misc/contributing/code.md index 14c4dc28a..364c17f3f 100644 --- a/docs/en/misc/contributing/code.md +++ b/docs/en/misc/contributing/code.md @@ -68,7 +68,7 @@ there are any problems they will follow up with you, so please ensure they have ### Quickfire Do's and Don't's If you aren't familiar with git and GitHub, try reading the ["GitHub bootcamp documentation"](http://help.github.com/). -We also found the [free online git book](http://progit.org/book/) and the [git crash course](http://gitref.org/) useful. +We also found the [free online git book](http://git-scm.com/book/) and the [git crash course](http://gitref.org/) useful. If you're familiar with it, here's the short version of what you need to know. Once you fork and download the code: * **Don't develop on the master branch.** Always create a development branch specific to "the issue" you're working on (mostly on our [bugtracker](/misc/contributing/issues)). Name it by issue number and description. For example, if you're working on Issue #100, a `DataObject::get_one()` bugfix, your development branch should be called 100-dataobject-get-one. If you decide to work on another issue mid-stream, create a new branch for that issue--don't work on both in one branch. @@ -104,7 +104,7 @@ step. * It's better to submit multiple patches with separate bits of functionality than a big patch containing lots of changes * Document your code inline through [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) syntax. See our -[API documentation](http://api.silverstripe.org/trunk) for good examples. +[API documentation](http://api.silverstripe.org/3.1/) for good examples. * Also check and update documentation on [doc.silverstripe.org](http://doc.silverstripe.org). Check for any references to functionality deprecated or extended through your patch. Documentation changes should be included in the patch. * We will attribute the change to you whereever possible (git does this automatically for pull requests) * If you get stuck, please post to the [forum](http://silverstripe.org/forum) or for deeper core problems, to the [core mailinglist](https://groups.google.com/forum/#!forum/silverstripe-dev) @@ -260,7 +260,7 @@ It's as if you had just started your branch. One immediate advantage you get is So when you're ready to send the new plugin upstream, you do one last rebase, test, and then merge (which is really no merge at all) and send out your pull request. Then in most cases, we have a simple fast-forward on our end (or at worst a very small rebase or merge) and over time that adds up to a simpler tree. -More info on the ["Rebasing" chapter on progit.org](http://progit.org/book/ch3-6.html) and the [git rebase man page](http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html). +More info on the ["Rebasing" chapter on git-scm.com](http://git-scm.com/book/ch3-6.html) and the [git rebase man page](http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html). ## License diff --git a/docs/en/reference/cms-architecture.md b/docs/en/reference/cms-architecture.md index 778b0ca0f..226d57da2 100644 --- a/docs/en/reference/cms-architecture.md +++ b/docs/en/reference/cms-architecture.md @@ -30,7 +30,7 @@ and the [Compass framework](http://compass-style.org/), which helps us maintain expressive and concise style declarations. The files are located in `framework/admin/scss` (and if you have the `cms` module installed, in `cms/scss`), and are compiled to a `css` folder on the same directory path. Changes to the SCSS files can be automatically converted by installing -the ["compass" module](http://www.silverstripe.org/compass-module/) for SilverStripe, +the ["compass" module](https://github.com/silverstripe-labs/silverstripe-compass) for SilverStripe, although [installing the compass framework](http://compass-style.org/install/) directly works as well. Each file describes its purpose at the top of the declarations. Note that you can write plain CSS without SCSS for your custom CMS interfaces as well, we just mandate SCSS for core usage. diff --git a/search/FulltextSearchable.php b/search/FulltextSearchable.php index b5cbd5812..3b450a131 100644 --- a/search/FulltextSearchable.php +++ b/search/FulltextSearchable.php @@ -9,7 +9,7 @@ * CAUTION: Will make all files in your /assets folder searchable by file name * unless "File" is excluded from FulltextSearchable::enable(). * - * @see http://doc.silverstripe.org/tutorial:4-site-search + * @see http://doc.silverstripe.org/framework/en/tutorials/4-site-search * * @package framework * @subpackage search diff --git a/security/MemberAuthenticator.php b/security/MemberAuthenticator.php index e78357df7..0aa35c996 100644 --- a/security/MemberAuthenticator.php +++ b/security/MemberAuthenticator.php @@ -11,7 +11,7 @@ class MemberAuthenticator extends Authenticator { /** * @var Array Contains encryption algorithm identifiers. * If set, will migrate to new precision-safe password hashing - * upon login. See http://open.silverstripe.org/ticket/3004. + * upon login. See http://open.silverstripe.org/ticket/3004 */ private static $migrate_legacy_hashes = array( 'md5' => 'md5_v2.4', From e91f10b0ab6679d124d4cbaf859635b9e0ece1b8 Mon Sep 17 00:00:00 2001 From: Mateusz Uzdowski Date: Fri, 7 Feb 2014 15:37:24 +1300 Subject: [PATCH 14/16] Add Behat step to click on a row in the first GridField table. --- .../Framework/Test/Behaviour/CmsUiContext.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php index 6cbc7e9ab..14a2e383f 100644 --- a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php +++ b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php @@ -124,6 +124,22 @@ class CmsUiContext extends BehatContext { return $table_element; } + /** + * Finds the first visible GridField table. + */ + protected function getFirstGridFieldTable() { + $page = $this->getSession()->getPage(); + $tableElements = $page->findAll('css', '.ss-gridfield-table'); + assertNotNull($tableElements, 'Table elements not found'); + + // Return first found table. + foreach($tableElements as $table) { + if($table->isVisible()) return $table; + } + + assertNotNull(null, 'First visible table element not found'); + } + /** * @Given /^I should see a "([^"]*)" button in CMS Content Toolbar$/ */ @@ -233,6 +249,20 @@ class CmsUiContext extends BehatContext { $element->click(); } + /** + * Clicks on a row in the first found visible GridField table. + * Example: I click on "New Zealand" in the table + * + * @Given /^I click on "([^"]*)" in the table$/ + */ + public function iClickOnInTheFirstTable($text) { + $table_element = $this->getFirstGridFieldTable(); + + $element = $table_element->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text)); + assertNotNull($element, sprintf('Element containing `%s` not found', $text)); + $element->click(); + } + /** * @Then /^I can see the preview panel$/ */ From 267786a7d19d34f661ccb723d511d6f2b8e9eee2 Mon Sep 17 00:00:00 2001 From: madmatt Date: Wed, 15 Jan 2014 16:20:19 +1300 Subject: [PATCH 15/16] Add behat test for security permission checkboxes. --- .../features/security-permissions.feature | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tests/behat/features/security-permissions.feature diff --git a/tests/behat/features/security-permissions.feature b/tests/behat/features/security-permissions.feature new file mode 100644 index 000000000..4bb8b57a2 --- /dev/null +++ b/tests/behat/features/security-permissions.feature @@ -0,0 +1,85 @@ +@javascript +Feature: Manage Security Permissions for Groups + As a site administrator + I want to control my user's security permissions in an intuitive way + So that I can easily control access to the CMS + + Background: + Given a "group" "test group" + And a "member" "ADMIN" belonging to "ADMIN Group" with "Email"="admin@test.com" + And the "group" "ADMIN group" has permissions "Full administrative rights" + And I am logged in with "ADMIN" permissions + And I go to "/admin/security" + And I click the "Groups" CMS tab + And I click on "test group" in the table + And I click the "Permissions" CMS tab + + Scenario: I can see sub-permissions being properly set and restored when using "Access to all CMS sections" + When I check "Access to all CMS sections" + Then the "Access to 'Security' section" checkbox should be checked + And the "Access to 'Security' section" field should be disabled + + When I uncheck "Access to all CMS sections" + Then the "Access to 'Security' section" checkbox should not be checked + And the "Access to 'Security' section" field should be enabled + + When I check "Access to 'Security' section" + And I check "Access to all CMS sections" + When I uncheck "Access to all CMS sections" + Then the "Access to 'Security' section" checkbox should be checked + + # Save so the driver can reset without having to deal with the popup alert. + Then I press the "Save" button + + Scenario: I can see sub-permissions being properly set and restored when using "Full administrative rights" + When I check "Access to 'Security' section" + And I check "Full administrative rights" + Then the "Access to all CMS sections" checkbox should be checked + And the "Access to all CMS sections" field should be disabled + And the "Access to 'Security' section" checkbox should be checked + And the "Access to 'Security' section" field should be disabled + + And I uncheck "Full administrative rights" + Then the "Access to all CMS sections" checkbox should not be checked + And the "Access to all CMS sections" field should be enabled + And the "Access to 'Security' section" checkbox should be checked + And the "Access to 'Security' section" field should be enabled + + # Save so the driver can reset without having to deal with the popup alert. + Then I press the "Save" button + + Scenario: I can see sub-permissions being handled properly between reloads when using "Full administrative rights" + When I check "Full administrative rights" + And I press the "Save" button + And I click the "Permissions" CMS tab + Then the "Full administrative rights" checkbox should be checked + And the "Access to 'Security' section" checkbox should be checked + And the "Access to 'Security' section" field should be disabled + + When I uncheck "Full administrative rights" + Then the "Access to 'Security' section" checkbox should not be checked + And the "Access to 'Security' section" field should be enabled + + When I press the "Save" button + And I click the "Permissions" CMS tab + Then the "Full administrative rights" checkbox should not be checked + And the "Access to 'Security' section" checkbox should not be checked + And the "Access to 'Security' section" field should be enabled + + Scenario: I can see sub-permissions being handled properly between reloads when using "Access to all CMS sections" + When I check "Access to all CMS sections" + And I press the "Save" button + And I click the "Permissions" CMS tab + Then the "Access to all CMS sections" checkbox should be checked + And the "Access to 'Security' section" checkbox should be checked + And the "Access to 'Security' section" field should be disabled + + When I uncheck "Access to all CMS sections" + Then the "Access to 'Security' section" checkbox should not be checked + And the "Access to 'Security' section" field should be enabled + + When I press the "Save" button + And I click the "Permissions" CMS tab + Then the "Access to all CMS sections" checkbox should not be checked + And the "Access to 'Security' section" checkbox should not be checked + And the "Access to 'Security' section" field should be enabled From db07a8e1dfbce8ac6a4c6f71e83dafc94c18e5df Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 7 Feb 2014 16:38:02 +1300 Subject: [PATCH 16/16] Fix $disable_themes test state Wasn't resetting the value after using it --- dev/FunctionalTest.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/dev/FunctionalTest.php b/dev/FunctionalTest.php index 5de580dcc..675e767a6 100644 --- a/dev/FunctionalTest.php +++ b/dev/FunctionalTest.php @@ -48,6 +48,11 @@ class FunctionalTest extends SapphireTest { * However, this will let you inspect the intermediary headers */ protected $autoFollowRedirection = true; + + /** + * @var String + */ + protected $originalTheme = null; /** * Returns the {@link Session} object for this test @@ -64,7 +69,10 @@ class FunctionalTest extends SapphireTest { $this->mainSession = new TestSession(); // Disable theme, if necessary - if(static::get_disable_themes()) Config::inst()->update('SSViewer', 'theme', null); + if(static::get_disable_themes()) { + $this->originalTheme = Config::inst()->get('SSViewer', 'theme'); + Config::inst()->update('SSViewer', 'theme', null); + } // Switch to draft site, if necessary if(static::get_use_draft_site()) { @@ -83,6 +91,10 @@ class FunctionalTest extends SapphireTest { parent::tearDown(); unset($this->mainSession); + + if(static::get_disable_themes()) { + Config::inst()->update('SSViewer', 'theme', $this->originalTheme); + } } /**