Merge remote-tracking branch 'origin/3.1'

Conflicts:
	docs/en/misc/contributing/code.md
This commit is contained in:
Ingo Schommer 2014-02-07 16:43:22 +13:00
commit 4af9143d3b
27 changed files with 447 additions and 94 deletions

View File

@ -1,20 +1,16 @@
<?php <?php
use SilverStripe\Framework\Injector\Factory;
/** /**
* A class for creating new objects by the injector. * A class for creating new objects by the injector.
* *
* @package framework * @package framework
* @subpackage injector * @subpackage injector
*/ */
class InjectionCreator { class InjectionCreator implements Factory {
/** public function create($class, array $params = array()) {
* @param string $object
* A string representation of the class to create
* @param array $params
* An array of parameters to be passed to the constructor
*/
public function create($class, $params = array()) {
$reflector = new ReflectionClass($class); $reflector = new ReflectionClass($class);
if (count($params)) { if (count($params)) {
@ -23,4 +19,5 @@ class InjectionCreator {
return $reflector->newInstance(); return $reflector->newInstance();
} }
} }

View File

@ -1,9 +1,13 @@
<?php <?php
require_once dirname(__FILE__) . '/InjectionCreator.php'; require_once FRAMEWORK_PATH . '/src/SilverStripe/Framework/Injector/Factory.php';
require_once dirname(__FILE__) . '/SilverStripeInjectionCreator.php';
require_once dirname(__FILE__) . '/ServiceConfigurationLocator.php'; require_once __DIR__ . '/InjectionCreator.php';
require_once dirname(__FILE__) . '/SilverStripeServiceConfigurationLocator.php'; require_once __DIR__ . '/SilverStripeInjectionCreator.php';
require_once __DIR__ . '/ServiceConfigurationLocator.php';
require_once __DIR__ . '/SilverStripeServiceConfigurationLocator.php';
use SilverStripe\Framework\Injector\Factory;
/** /**
* A simple injection manager that manages creating objects and injecting * A simple injection manager that manages creating objects and injecting
@ -71,6 +75,7 @@ require_once dirname(__FILE__) . '/SilverStripeServiceConfigurationLocator.php';
* // type * // type
* // By default, singleton is assumed * // By default, singleton is assumed
* *
* 'factory' => 'FactoryService' // A factory service to use to create instances.
* 'construct' => array( // properties to set at construction * 'construct' => array( // properties to set at construction
* 'scalar', * 'scalar',
* '%$BeanId', * '%$BeanId',
@ -163,14 +168,16 @@ class Injector {
private $autoScanProperties = false; private $autoScanProperties = false;
/** /**
* The object used to create new class instances * The default factory used to create new instances.
* *
* Use a custom class here to change the way classes are created to use * The {@link InjectionCreator} is used by default, which simply directly
* a custom creation method. By default the InjectionCreator class is used, * creates objects. This can be changed to use a different default creation
* which simply creates a new class via 'new', however this could be overridden * method if desired.
* to use, for example, SilverStripe's Object::create() method.
* *
* @var InjectionCreator * Each individual component can also specify a custom factory to use by
* using the `factory` parameter.
*
* @var Factory
*/ */
protected $objectCreator; protected $objectCreator;
@ -201,8 +208,6 @@ class Injector {
if ($config) { if ($config) {
$this->load($config); $this->load($config);
} }
self::$instance = $this;
} }
/** /**
@ -218,6 +223,15 @@ class Injector {
return self::$instance; 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. * Indicate whether we auto scan injected objects for properties to set.
* *
@ -228,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; $this->objectCreator = $obj;
} }
/** /**
* Accessor (for testing purposes) * @return Factory
* @return InjectionCreator
*/ */
public function getObjectCreator() { public function getObjectCreator() {
return $this->objectCreator; return $this->objectCreator;
@ -488,7 +501,8 @@ class Injector {
$constructorParams = $spec['constructor']; $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 // 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 // that we don't manage directly; we don't want to store these in the service cache below

View File

@ -1,22 +1,18 @@
<?php <?php
use SilverStripe\Framework\Injector\Factory;
/** /**
* @package framework * @package framework
* @subpackage injector * @subpackage injector
*/ */
class SilverStripeInjectionCreator implements Factory {
class SilverStripeInjectionCreator { public function create($class, array $params = array()) {
/**
*
* @param string $object
* A string representation of the class to create
* @param array $params
* An array of parameters to be passed to the constructor
*/
public function create($class, $params = array()) {
$class = Object::getCustomClass($class); $class = Object::getCustomClass($class);
$reflector = new ReflectionClass($class); $reflector = new ReflectionClass($class);
return $reflector->newInstanceArgs($params); return $params ? $reflector->newInstanceArgs($params) : $reflector->newInstance();
} }
} }

View File

@ -84,8 +84,10 @@ require_once 'control/injector/Injector.php';
// Initialise the dependency injector as soon as possible, as it is // Initialise the dependency injector as soon as possible, as it is
// subsequently used by some of the following code // subsequently used by some of the following code
$default_options = array('locator' => 'SilverStripeServiceConfigurationLocator'); $injector = new Injector(array('locator' => 'SilverStripeServiceConfigurationLocator'));
Injector::inst($default_options); $injector->registerService(Config::inst());
Injector::set_inst($injector);
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// MANIFEST // MANIFEST

View File

@ -127,7 +127,7 @@ abstract class Object {
* *
* @param string $class the class name * @param string $class the class name
* @param mixed $arguments,... arguments to pass to the constructor * @param mixed $arguments,... arguments to pass to the constructor
* @return Object * @return static
*/ */
public static function create() { public static function create() {
$args = func_get_args(); $args = func_get_args();
@ -279,7 +279,7 @@ abstract class Object {
* *
* @param string $class the class name * @param string $class the class name
* @param mixed $arguments,... arguments to pass to the constructor * @param mixed $arguments,... arguments to pass to the constructor
* @return Object * @return static
*/ */
public static function strong_create() { public static function strong_create() {
$args = func_get_args(); $args = func_get_args();
@ -550,7 +550,7 @@ abstract class Object {
Config::inst()->update($class, 'extensions', array($extension)); Config::inst()->update($class, 'extensions', array($extension));
Config::inst()->extraConfigSourcesChanged($class); Config::inst()->extraConfigSourcesChanged($class);
Injector::inst()->unregisterAllObjects(); Injector::inst()->unregisterNamedObject($class);
// load statics now for DataObject classes // load statics now for DataObject classes
if(is_subclass_of($class, 'DataObject')) { if(is_subclass_of($class, 'DataObject')) {

View File

@ -49,6 +49,11 @@ class FunctionalTest extends SapphireTest {
*/ */
protected $autoFollowRedirection = true; protected $autoFollowRedirection = true;
/**
* @var String
*/
protected $originalTheme = null;
/** /**
* Returns the {@link Session} object for this test * Returns the {@link Session} object for this test
*/ */
@ -64,7 +69,10 @@ class FunctionalTest extends SapphireTest {
$this->mainSession = new TestSession(); $this->mainSession = new TestSession();
// Disable theme, if necessary // 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 // Switch to draft site, if necessary
if(static::get_use_draft_site()) { if(static::get_use_draft_site()) {
@ -83,6 +91,10 @@ class FunctionalTest extends SapphireTest {
parent::tearDown(); parent::tearDown();
unset($this->mainSession); unset($this->mainSession);
if(static::get_disable_themes()) {
Config::inst()->update('SSViewer', 'theme', $this->originalTheme);
}
} }
/** /**

View File

@ -6,27 +6,33 @@
* *
* To create your own tests, please use this template: * To create your own tests, please use this template:
* <code> * <code>
* <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> * <!DOCTYPE html>
* <html> * <html id="html">
* <head> * <head>
* <script src="http://code.jquery.com/jquery-latest.js"></script> * <title>jQuery - Validation Test Suite</title>
* <link rel="stylesheet" href="http://dev.jquery.com/view/trunk/qunit/testsuite.css" type="text/css" media="screen" /> * <link rel="Stylesheet" media="screen"
* <script> * href="thirdparty/jquery-validate/test/qunit/qunit.css" />
* $(document).ready(function(){ * <script type="text/javascript"
* test("test my feature", function() { * src="thirdparty/jquery-validate/lib/jquery.js"></script>
* ok('mytest'); * <script type="text/javascript"
* }); * src="thirdparty/jquery-validate/test/qunit/qunit.js"></script>
* }); * <script>
* </script> * $(document).ready(function(){
* </head> * test("test my feature", function() {
* <body> * ok('mytest');
* <script type="text/javascript" src="http://jqueryjs.googlecode.com/svn/trunk/qunit/testrunner.js"></script> * });
* <h1>My Test Name</h1> * });
* <h2 id="banner"></h2> * </script>
* <h2 id="userAgent"></h2> * </head>
* <ol id="tests"></ol> * <body id="body">
* <div id="main"></div> * <h1 id="qunit-header">
* </body> * <a href="http://bassistance.de/jquery-plugins/jquery-plugin-validation/">
* jQuery Validation Plugin</a> Test Suite</h1>
* <h2 id="qunit-banner"></h2>
* <div id="qunit-testrunner-toolbar"></div>
* <h2 id="qunit-userAgent"></h2>
* <ol id="qunit-tests"></ol>
* </body>
* </html> * </html>
* </code> * </code>
* *

View File

@ -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 * 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 * to enable us to give a bit more detail about the failure/error

View File

@ -46,4 +46,16 @@ class SilverStripeListener implements PHPUnit_Framework_TestListener {
public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { 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
}
} }

View File

@ -61,4 +61,16 @@ class TeamCityListener implements PHPUnit_Framework_TestListener {
$message = $this->escape($e->getMessage()); $message = $this->escape($e->getMessage());
echo "##teamcity[testIgnored name='{$class}.{$test->getName()}' message='$message']\n"; 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
}
} }

View File

@ -38,6 +38,18 @@ class SS_TestListener implements PHPUnit_Framework_TestListener {
$this->class->tearDownOnce(); $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 * @param String Classname
* @return boolean * @return boolean

View File

@ -74,3 +74,38 @@ needs to create/have write-access to:
* The mysite folder (to create _config.php) * The mysite folder (to create _config.php)
* After the install, the assets directory is the only directory that needs write access. * 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 * 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 `<?php` opening bracket at the start of the document,
or after 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
<?php
// Check for whitespace around PHP brackets which show in output,
// and hence can break HTML rendering and HTTP operations.
$path = dirname(__FILE__);
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST);
$matched = false;
foreach($files as $name => $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;
}
}
```

View File

@ -5,7 +5,7 @@
## Installing SilverStripe ## Installing SilverStripe
* Download and run Microsoft Web Platform Installer (WebPI): * 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&amp;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&amp;appid=105)
* In WebPI, select 'SilverStripe' from the 'Content Management System' link * 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. * Select install. It will install dependancies like MySQL and PHP if you don't have these installed already.

View File

@ -21,7 +21,7 @@ We also found the [free online git book](http://progit.org/book/) and the [git c
If you're familiar with it, here's the short version of what you need to know. Once you fork and download the code: 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. * **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.
x
* **Do not merge the upstream master** with your development branch; *rebase* your branch on top of the upstream branch you branched off. * **Do not merge the upstream master** with your development branch; *rebase* your branch on top of the upstream branch you branched off.
* **A single development branch should represent changes related to a single issue.** If you decide to work on another issue, create another branch. * **A single development branch should represent changes related to a single issue.** If you decide to work on another issue, create another branch.
@ -89,6 +89,25 @@ there are any problems they will follow up with you, so please ensure they have
![Workflow diagram](http://www.silverstripe.org/assets/doc-silverstripe-org/collaboration-on-github.png) ![Workflow diagram](http://www.silverstripe.org/assets/doc-silverstripe-org/collaboration-on-github.png)
### 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://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.
* **Do not merge the upstream master** with your development branch; *rebase* your branch on top of the upstream master.
* **A single development branch should represent changes related to a single issue.** If you decide to work on another issue, create another branch.
* **Squash your commits, so that each commit addresses a single issue.** After you rebase your work on top of the upstream master, you can squash multiple commits into one. Say, for instance, you've got three commits in related to Issue #100. Squash all three into one with the message "Issue #100 Description of the issue here." We won't accept pull requests for multiple commits related to a single issue; it's up to you to squash and clean your commit tree. (Remember, if you squash commits you've already pushed to GitHub, you won't be able to push that same branch again. Create a new local branch, squash, and push the new squashed branch.)
* **Choose the correct branch**: Assume the current release is 3.0.3, and 3.1.0 is in beta state.
Most pull requests should go against the `3.1.x-dev` *pre-release branch*, only critical bugfixes
against the `3.0.x-dev` *release branch*. If you're changing an API or introducing a major feature,
the pull request should go against `master` (read more about our [release process](/misc/release-process)). Branches are periodically merged "upwards" (3.0 into 3.1, 3.1 into master).
### Editing files directly on GitHub.com ### Editing files directly on GitHub.com
If you see a typo or another small fix that needs to be made, and you don't have an installation set up for contributions, you can edit files directly in the github.com web interface. Every file view has an "edit this file" link. If you see a typo or another small fix that needs to be made, and you don't have an installation set up for contributions, you can edit files directly in the github.com web interface. Every file view has an "edit this file" link.
@ -109,7 +128,7 @@ step.
* It's better to submit multiple patches with separate bits of functionality than a big patch containing lots of * It's better to submit multiple patches with separate bits of functionality than a big patch containing lots of
changes changes
* Document your code inline through [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) syntax. See our * 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. * 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) * 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) * 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)
@ -272,7 +291,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. 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 ## License

View File

@ -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` 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 (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 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. 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 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. plain CSS without SCSS for your custom CMS interfaces as well, we just mandate SCSS for core usage.

View File

@ -78,7 +78,6 @@ In this case, on creation of the MyController object, the injector will
automatically instantiate the PermissionService object and set it as automatically instantiate the PermissionService object and set it as
the **permissions** property. the **permissions** property.
## Configuring objects managed by the dependency injector ## Configuring objects managed by the dependency injector
The above declarative style of dependency management would cover a large The above declarative style of dependency management would cover a large
@ -90,6 +89,31 @@ Configuration can be specified for two areas of dependency management
* Defining dependency overrides for individual classes * Defining dependency overrides for individual classes
* Injector managed 'services' * 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 ### Dependency overrides
To override the **static $dependency;** declaration for a class, you could To override the **static $dependency;** declaration for a class, you could

View File

@ -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: 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 :::php
private static $allowed_actions = array( private static $allowed_actions = array(
@ -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*: 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 :::php
public function init() { public function init() {

View File

@ -2208,6 +2208,24 @@ class i18n extends Object implements TemplateGlobalProvider {
return (array)Config::inst()->get('i18n', 'all_locales'); 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 * Searches the root-directory for module-directories
* (identified by having a _config.php on their first directory-level). * (identified by having a _config.php on their first directory-level).

View File

@ -134,7 +134,7 @@ $chain
// Then if a flush was requested, redirect to it // Then if a flush was requested, redirect to it
if ($token->parameterProvided() && !$token->tokenProvided()) { if ($token->parameterProvided() && !$token->tokenProvided()) {
// First, check if we're in dev mode, or the database doesn't have any security data // 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 // Otherwise, we start up the session if needed, then check for admin
if (!$canFlush) { if (!$canFlush) {

View File

@ -9,7 +9,7 @@
* CAUTION: Will make all files in your /assets folder searchable by file name * CAUTION: Will make all files in your /assets folder searchable by file name
* unless "File" is excluded from FulltextSearchable::enable(). * 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 * @package framework
* @subpackage search * @subpackage search

View File

@ -174,7 +174,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
*/ */
public function populateDefaults() { public function populateDefaults() {
parent::populateDefaults(); parent::populateDefaults();
$this->Locale = i18n::get_locale(); $this->Locale = i18n::get_closest_translation(i18n::get_locale());
} }
public function requireDefaultRecords() { public function requireDefaultRecords() {

View File

@ -11,7 +11,7 @@ class MemberAuthenticator extends Authenticator {
/** /**
* @var Array Contains encryption algorithm identifiers. * @var Array Contains encryption algorithm identifiers.
* If set, will migrate to new precision-safe password hashing * 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( private static $migrate_legacy_hashes = array(
'md5' => 'md5_v2.4', 'md5' => 'md5_v2.4',

View File

@ -0,0 +1,19 @@
<?php
namespace SilverStripe\Framework\Injector;
/**
* A factory which is used for creating service instances.
*/
interface Factory {
/**
* Creates a new service instance.
*
* @param string $service The class name of the service.
* @param array $params The constructor parameters.
* @return object The created service instances.
*/
public function create($service, array $params = array());
}

View File

@ -124,6 +124,22 @@ class CmsUiContext extends BehatContext {
return $table_element; 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$/ * @Given /^I should see a "([^"]*)" button in CMS Content Toolbar$/
*/ */
@ -233,6 +249,20 @@ class CmsUiContext extends BehatContext {
$element->click(); $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$/ * @Then /^I can see the preview panel$/
*/ */

View File

@ -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

View File

@ -102,6 +102,32 @@ class i18nTest extends SapphireTest {
$this->assertTrue(isset($translations['de_DE']), 'Checking for de_DE translation'); $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() { public function testDataObjectFieldLabels() {
$oldLocale = i18n::get_locale(); $oldLocale = i18n::get_locale();
i18n::set_locale('de_DE'); i18n::set_locale('de_DE');

View File

@ -541,6 +541,28 @@ class InjectorTest extends SapphireTest {
$this->assertEquals($item->property, 'othervalue'); $this->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 { class InjectorTestConfigLocator extends SilverStripeServiceConfigurationLocator implements TestOnly {
@ -667,7 +689,7 @@ class SSObjectCreator extends InjectionCreator {
$this->injector = $injector; $this->injector = $injector;
} }
public function create($class, $params = array()) { public function create($class, array $params = array()) {
if (strpos($class, '(') === false) { if (strpos($class, '(') === false) {
return parent::create($class, $params); return parent::create($class, $params);
} else { } else {