Merge pull request #6641 from open-sausages/pulls/4.0/micmania-config

API Substitute silverstripe/config module for core config
This commit is contained in:
Sam Minnée 2017-02-28 11:26:42 +11:00 committed by GitHub
commit 5388ff41ec
126 changed files with 1840 additions and 3256 deletions

View File

@ -1,5 +1,5 @@
---
Name: coreconfig
Name: emailconfig
---
SilverStripe\Core\Injector\Injector:
Swift_Transport: Swift_MailTransport

View File

@ -1,16 +1,13 @@
---
Name: rootroutes
Before: '*'
---
SilverStripe\Control\Director:
rules:
'': SilverStripe\Control\Controller
---
Name: coreroutes
Before: '*'
After:
- '#rootroutes'
- '#modelascontrollerroutes'
---
SilverStripe\Control\Director:
rules:
@ -23,11 +20,9 @@ SilverStripe\Control\Director:
'SapphireREPL//$Action/$ID/$OtherID': SilverStripe\Dev\SapphireREPL
---
Name: adminroutes
Before: '*'
After:
- '#rootroutes'
- '#coreroutes'
- '#modelascontrollerroutes'
---
SilverStripe\Control\Director:
rules:

View File

@ -50,7 +50,7 @@ class AdminRootController extends Controller implements TemplateGlobalProvider
* The LeftAndMain child that will be used as the initial panel to display if none is selected (i.e. if you
* visit /admin)
*/
private static $default_panel = 'SilverStripe\\Admin\\SecurityAdmin';
private static $default_panel = SecurityAdmin::class;
/**
* @var array
@ -84,8 +84,9 @@ class AdminRootController extends Controller implements TemplateGlobalProvider
*/
protected static function add_rule_for_controller($controllerClass)
{
$urlSegment = Config::inst()->get($controllerClass, 'url_segment', Config::FIRST_SET);
$urlRule = Config::inst()->get($controllerClass, 'url_rule', Config::FIRST_SET);
$config = Config::forClass($controllerClass);
$urlSegment = $config->get('url_segment');
$urlRule = $config->get('url_rule');
if ($urlSegment) {
// Make director rule
@ -105,7 +106,8 @@ class AdminRootController extends Controller implements TemplateGlobalProvider
{
// If this is the final portion of the request (i.e. the URL is just /admin), direct to the default panel
if ($request->allParsed()) {
$segment = Config::inst()->get($this->config()->default_panel, 'url_segment');
$segment = Config::forClass($this->config()->default_panel)
->get('url_segment');
$this->redirect(Controller::join_links(self::admin_url(), $segment, '/'));
return $this->getResponse();
@ -114,6 +116,7 @@ class AdminRootController extends Controller implements TemplateGlobalProvider
$rules = self::rules();
foreach ($rules as $pattern => $controller) {
if (($arguments = $request->match($pattern, true)) !== false) {
/** @var LeftAndMain $controllerObj */
$controllerObj = Injector::inst()->create($controller);
$controllerObj->setSession($this->session);

View File

@ -88,8 +88,8 @@ class CMSMenu extends Object implements IteratorAggregate, i18nEntityProvider
protected static function menuitem_for_controller($controllerClass)
{
$urlBase = AdminRootController::admin_url();
$urlSegment = Config::inst()->get($controllerClass, 'url_segment', Config::FIRST_SET);
$menuPriority = Config::inst()->get($controllerClass, 'menu_priority', Config::FIRST_SET);
$urlSegment = Config::inst()->get($controllerClass, 'url_segment');
$menuPriority = Config::inst()->get($controllerClass, 'menu_priority');
// Don't add menu items defined the old way
if (!$urlSegment) {

View File

@ -21,7 +21,7 @@ class CampaignAdminList extends FormField
// Get endpoints from admin
$admin = CampaignAdmin::singleton();
$data['data']['recordType'] = $admin->config()->get('tree_class');
$data['data']['recordType'] = $admin->config()->uninherited('tree_class');
$oneSetAction = $admin->Link("set") . "/:id";
$setsAction = $admin->Link("sets");
$schemaEndpoint = $admin->Link("schema") . "/DetailEditForm";

View File

@ -86,7 +86,7 @@ class LeftAndMain extends Controller implements PermissionProvider
* @config
* @var string
*/
private static $url_segment;
private static $url_segment = null;
/**
* @config
@ -265,7 +265,7 @@ class LeftAndMain extends Controller implements PermissionProvider
/**
* Gets the combined configuration of all LeafAndMain subclasses required by the client app.
*
* @return array
* @return string
*
* WARNING: Experimental API
*/
@ -289,7 +289,7 @@ class LeftAndMain extends Controller implements PermissionProvider
// Set env
$combinedClientConfig['environment'] = Director::get_environment_type();
$combinedClientConfig['debugging'] = $this->config()->client_debugging;
$combinedClientConfig['debugging'] = LeftAndMain::config()->uninherited('client_debugging');
return Convert::raw2json($combinedClientConfig);
}
@ -471,7 +471,7 @@ class LeftAndMain extends Controller implements PermissionProvider
public static function getRequiredPermissions()
{
$class = get_called_class();
$code = Config::inst()->get($class, 'required_permission_codes', Config::FIRST_SET);
$code = Config::inst()->get($class, 'required_permission_codes');
if ($code === false) {
return false;
}
@ -509,7 +509,7 @@ class LeftAndMain extends Controller implements PermissionProvider
CMSMenu::add_link(
'Help',
_t('LeftAndMain.HELP', 'Help', 'Menu title'),
$this->config()->help_link,
LeftAndMain::config()->uninherited('help_link'),
-2,
array(
'target' => '_blank'
@ -591,7 +591,7 @@ class LeftAndMain extends Controller implements PermissionProvider
Requirements::add_i18n_javascript(ltrim(FRAMEWORK_DIR . '/client/lang', '/'), false, true);
Requirements::add_i18n_javascript(FRAMEWORK_ADMIN_DIR . '/client/lang', false, true);
if ($this->config()->session_keepalive_ping) {
if (LeftAndMain::config()->uninherited('session_keepalive_ping')) {
Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.Ping.js');
}
@ -644,7 +644,7 @@ class LeftAndMain extends Controller implements PermissionProvider
$this->extend('init', $dummy);
// Assign default cms theme and replace user-specified themes
SSViewer::set_themes($this->config()->admin_themes);
SSViewer::set_themes(LeftAndMain::config()->uninherited('admin_themes'));
//set the reading mode for the admin to stage
Versioned::set_stage(Versioned::DRAFT);
@ -676,7 +676,7 @@ class LeftAndMain extends Controller implements PermissionProvider
// Prevent clickjacking, see https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
$originalResponse = $this->getResponse();
$originalResponse->addHeader('X-Frame-Options', $this->config()->frame_options);
$originalResponse->addHeader('X-Frame-Options', LeftAndMain::config()->uninherited('frame_options'));
$originalResponse->addHeader('Vary', 'X-Requested-With');
return $response;
@ -714,7 +714,7 @@ class LeftAndMain extends Controller implements PermissionProvider
$this->setResponse($newResponse);
return ''; // Actual response will be re-requested by client
} else {
parent::redirect($url, $code);
return parent::redirect($url, $code);
}
}
@ -752,11 +752,8 @@ class LeftAndMain extends Controller implements PermissionProvider
public function Link($action = null)
{
// Handle missing url_segments
if ($this->config()->url_segment) {
$segment = $this->config()->get('url_segment', Config::FIRST_SET);
} else {
$segment = $this->class;
};
$segment = $this->config()->get('url_segment')
?: $this->class;
$link = Controller::join_links(
AdminRootController::admin_url(),
@ -795,7 +792,7 @@ class LeftAndMain extends Controller implements PermissionProvider
}
// Get default class title
$title = Config::inst()->get($class, 'menu_title', Config::FIRST_SET);
$title = static::config()->get('menu_title');
if (!$title) {
$title = preg_replace('/Admin$/', '', $class);
}
@ -816,7 +813,7 @@ class LeftAndMain extends Controller implements PermissionProvider
*/
public static function menu_icon_for_class($class)
{
$icon = Config::inst()->get($class, 'menu_icon', Config::FIRST_SET);
$icon = Config::inst()->get($class, 'menu_icon');
if (!empty($icon)) {
$class = strtolower(Convert::raw2htmlname(str_replace('\\', '-', $class)));
return ".icon.icon-16.icon-{$class} { background-image: url('{$icon}'); } ";
@ -834,7 +831,7 @@ class LeftAndMain extends Controller implements PermissionProvider
*/
public static function menu_icon_class_for_class($class)
{
return Config::inst()->get($class, 'menu_icon_class', Config::FIRST_SET);
return Config::inst()->get($class, 'menu_icon_class');
}
/**
@ -951,8 +948,6 @@ class LeftAndMain extends Controller implements PermissionProvider
// Provide styling for custom $menu-icon. Done here instead of in
// CMSMenu::populate_menu(), because the icon is part of
// the CMS right pane for the specified class as well...
$iconClass = false;
if ($menuItem->controller) {
$menuIcon = LeftAndMain::menu_icon_for_class($menuItem->controller);

View File

@ -2,6 +2,7 @@
namespace SilverStripe\Admin\Tests;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\FunctionalTest;
use SilverStripe\Security\Member;
@ -57,7 +58,7 @@ class CMSProfileControllerTest extends FunctionalTest
public function testExtendedPermissionsStopEditingOwnProfile()
{
$existingExtensions = Member::config()->get('extensions');
$existingExtensions = Member::config()->get('extensions', Config::EXCLUDE_EXTRA_SOURCES);
Member::config()->update('extensions', [
CMSProfileControllerTest\TestExtension::class
]);

View File

@ -26,7 +26,8 @@
"symfony/cache": "^3.3@dev",
"symfony/config": "^2.8",
"symfony/translation": "^2.8",
"vlucas/phpdotenv": "^2.4"
"vlucas/phpdotenv": "^2.4",
"silverstripe/config": "^1@dev"
},
"require-dev": {
"phpunit/PHPUnit": "~4.8",

View File

@ -42,7 +42,7 @@ config:
:::yaml
Injector:
SilverStripe\Core\Injector\Injector:
DataListFilter.CustomMatch:
class: MyVendor/Search/CustomMatchFilter

View File

@ -23,7 +23,7 @@ also track versioned history.
:::php
class MyStagedModel extends DataObject {
private staic $extensions = [
"Versioned"
Versioned::class
];
}
@ -34,8 +34,8 @@ can be specified by setting the constructor argument to "Versioned"
:::php
class VersionedModel extends DataObject {
private staic $extensions = [
"Versioned('Versioned')"
private static $extensions = [
"SilverStripe\\ORM\\Versioning\\Versioned('Versioned')"
];
}
@ -63,14 +63,14 @@ adding a suffix.
* `MyRecord` table: Contains staged data
* `MyRecord_Live` table: Contains live data
* `MyRecord_versions` table: Contains a version history (new record created on each save)
* `MyRecord_Versions` table: Contains a version history (new record created on each save)
Similarly, any subclass you create on top of a versioned base will trigger the creation of additional tables, which are
automatically joined as required:
* `MyRecordSubclass` table: Contains only staged data for subclass columns
* `MyRecordSubclass_Live` table: Contains only live data for subclass columns
* `MyRecordSubclass_versions` table: Contains only version history for subclass columns
* `MyRecordSubclass_Versions` table: Contains only version history for subclass columns
## Usage
@ -81,12 +81,12 @@ explicitly request a certain stage through various getters on the `Versioned` cl
:::php
// Fetching multiple records
$stageRecords = Versioned::get_by_stage('MyRecord', 'Stage');
$liveRecords = Versioned::get_by_stage('MyRecord', 'Live');
$stageRecords = Versioned::get_by_stage('MyRecord', Versioned::DRAFT);
$liveRecords = Versioned::get_by_stage('MyRecord', Versioned::LIVE);
// Fetching a single record
$stageRecord = Versioned::get_by_stage('MyRecord', 'Stage')->byID(99);
$liveRecord = Versioned::get_by_stage('MyRecord', 'Live')->byID(99);
$stageRecord = Versioned::get_by_stage('MyRecord', Versioned::DRAFT)->byID(99);
$liveRecord = Versioned::get_by_stage('MyRecord', Versioned::LIVE)->byID(99);
### Historical Versions
@ -131,7 +131,7 @@ done via one of several ways:
See "DataObject ownership" for reference on dependant objects.
:::php
$record = Versioned::get_by_stage('MyRecord', 'Stage')->byID(99);
$record = Versioned::get_by_stage('MyRecord', Versioned::DRAFT)->byID(99);
$record->MyField = 'changed';
// will update `MyRecord` table (assuming Versioned::current_stage() == 'Stage'),
// and write a row to `MyRecord_versions`.
@ -144,7 +144,7 @@ Similarly, an "unpublish" operation does the reverse, and removes a record from
:::php
$record = MyRecord::get()->byID(99); // stage doesn't matter here
// will remove the row from the `MyRecord_Live` table
$record->deleteFromStage('Live');
$record->deleteFromStage(Versioned::LIVE);
### Forcing the Current Stage
@ -154,7 +154,7 @@ is initialized. But it can also be set and reset temporarily to force a specific
:::php
$origMode = Versioned::get_reading_mode(); // save current mode
$obj = MyRecord::getComplexObjectRetrieval(); // returns 'Live' records
Versioned::set_reading_mode('Stage'); // temporarily overwrite mode
Versioned::set_reading_mode(Versioned::DRAFT); // temporarily overwrite mode
$obj = MyRecord::getComplexObjectRetrieval(); // returns 'Stage' records
Versioned::set_reading_mode($origMode); // reset current mode
@ -180,7 +180,7 @@ without requiring any custom code.
:::php
class MyPage extends Page {
private static $has_many = array(
'Banners' => 'Banner'
'Banners' => Banner::class
);
private static $owns = array(
'Banners'
@ -189,11 +189,11 @@ without requiring any custom code.
class Banner extends Page {
private static $extensions = array(
'Versioned'
Versioned::class
);
private static $has_one = array(
'Parent' => 'MyPage',
'Image' => 'Image',
'Parent' => MyPage::class,
'Image' => Image::class,
);
private static $owns = array(
'Image'
@ -219,7 +219,7 @@ E.g.
:::php
class MyParent extends DataObject {
private static $extensions = array(
'Versioned'
Versioned::class
);
private static $owns = array(
'ChildObjects'
@ -230,7 +230,7 @@ E.g.
}
class MyChild extends DataObject {
private static $extensions = array(
'Versioned'
Versioned::class
);
private static $owned_by = array(
'Parent'
@ -258,7 +258,7 @@ smaller modifications of the generated `DataList` objects.
Example: Get the first 10 live records, filtered by creation date:
:::php
$records = Versioned::get_by_stage('MyRecord', 'Live')->limit(10)->sort('Created', 'ASC');
$records = Versioned::get_by_stage('MyRecord', Versioned::LIVE)->limit(10)->sort('Created', 'ASC');
### Permissions
@ -283,7 +283,7 @@ E.g.
:::php
class MyObject extends DataObject {
private static $extensions = array(
'Versioned'
Versioned::class,
);
public function canViewVersioned($member = null) {
@ -325,7 +325,7 @@ E.g.
:::php
class MyObject extends DataObject {
private static $extensions = array(
'Versioned'
Versioned::class,
);
private static $non_live_permissions = array('ADMIN');
}
@ -348,7 +348,7 @@ to force a specific stage, we recommend the `Controller->init()` method for this
:::php
public function init() {
parent::init();
Versioned::set_reading_mode('Stage.Stage');
Versioned::set_stage(Versioned::DRAFT);
}

View File

@ -49,7 +49,7 @@ Make sure that after you have modified the `routes.yml` file, that you clear you
Name: mysiteroutes
After: framework/routes#coreroutes
---
Director:
SilverStripe\Control\Director:
rules:
'teams//$Action/$ID/$Name': 'TeamController'

View File

@ -5,7 +5,8 @@ summary: SilverStripe's YAML based Configuration API for setting runtime configu
SilverStripe comes with a comprehensive code based configuration system through the [api:Config] class. It primarily
relies on declarative [YAML](http://en.wikipedia.org/wiki/YAML) files, and falls back to procedural PHP code, as well
as PHP static variables.
as PHP static variables. This is provided by the [silverstripe/config](https://github.com/silverstripe/silverstripe-config)
library.
The Configuration API can be seen as separate from other forms of variables in the SilverStripe system due to three
properties API:
@ -49,19 +50,28 @@ be marked `private static` and follow the `lower_case_with_underscores` structur
This can be done by calling the static method [api:Config::inst()], like so:
:::php
$config = Config::inst()->get('MyClass');
$config = Config::inst()->get('MyClass', 'property');
Or through the `config()` object on the class.
$config = $this->config();
$config = $this->config()->get('property')';
Note that by default `Config::inst()` returns only an immutable version of config. Use `Config::modify()`
if it's necessary to alter class config. This is generally undesirable in most applications, as modification
of the config can immediately have performance implications, so this should be used sparingly, or
during testing to modify state.
There are three public methods available on the instance. `get($class, $variable)`, `remove($class, $variable)` and
`update($class, $variable, $value)`.
Note that while both objects have similar methods the APIs differ slightly. The below actions are equivalent:
<div class="notice" markdown="1">
There is no "set" method. It is not possible to completely set the value of a classes' property. `update` adds new
values that are treated as the highest priority in the merge, and remove adds a merge mask that filters out values.
</div>
* `Config::inst()->get('Class', 'property');` or `Class::config()->get('property')`
* `Config::inst()->uninherited('Class', 'property');` or `Class::config()->get('property', Config::UNINHERITED)`
* `Config::inst()->exists('Class', 'property');` or `Class::config()->exists('property')`
And mutable methods:
* `Config::modify()->merge('Class', 'property', 'newvalue');` or `Class::config()->merge('property', 'newvalue')`
* `Config::modify()->set('Class', 'property', 'newvalue');` or `Class::config()->set('property', 'newvalue')`
* `Config::modify()->remove('Class', 'property');` or `Class::config()->remove('property')`
To set those configuration options on our previously defined class we can define it in a `YAML` file.
@ -92,7 +102,7 @@ To use those variables in your application code:
echo implode(', ', Config::inst()->get('MyClass', 'option_two'));
// returns 'Foo, Bar, Baz'
Config::inst()->update('MyClass', 'option_one', true);
Config::inst()->set('MyClass', 'option_one', true);
echo Config::inst()->get('MyClass', 'option_one');
// returns true
@ -134,7 +144,7 @@ the result will be the higher priority false-ish value.
The locations that configuration values are taken from in highest -> lowest priority order are:
- Any values set via a call to Config#update
- Any values set via a call to Config#merge / Config#set
- The configuration values taken from the YAML files in `_config/` directories (internally sorted in before / after
order, where the item that is latest is highest priority)
- Any static set on an "additional static source" class (such as an extension) named the same as the name of the property
@ -155,13 +165,13 @@ rather than add.
'allowed_actions', Config::UNINHERITED
);
They are much simpler. They consist of a list of key / value pairs. When applied against the current composite value
Available masks include:
- If the composite value is a sequential array, any member of that array that matches any value in the mask is removed
- If the composite value is an associative array, any member of that array that matches both the key and value of any
pair in the mask is removed
- If the composite value is not an array, if that value matches any value in the mask it is removed
* Config::UNINHERITED - Exclude config inherited from parent classes
* Config::EXCLUDE_EXTRA_SOURCES - Exclude config applied by extensions
You can also pass in literal `true` to disable all extra sources, or merge config options with
bitwise `|` operator.
## Configuration YAML Syntax and Rules
@ -280,11 +290,13 @@ rules contained match.
You then list any of the following rules as sub-keys, with informational values as either a single value or a list.
- 'classexists', in which case the value(s) should be classes that must exist
- 'moduleexists', in which case the value(s) should be modules that must exist
- 'moduleexists', in which case the value(s) should be modules that must exist. This supports either folder
name or composer `vendor/name` format.
- 'environment', in which case the value(s) should be one of "live", "test" or "dev" to indicate the SilverStripe
mode the site must be in
- 'envvarset', in which case the value(s) should be environment variables that must be set
- 'constantdefined', in which case the value(s) should be constants that must be defined
- 'envorconstant' A variable which should be defined either via environment vars or constants
For instance, to add a property to "foo" when a module exists, and "bar" otherwise, you could do this:
@ -303,19 +315,23 @@ For instance, to add a property to "foo" when a module exists, and "bar" otherwi
property: 'bar'
---
Multiple conditions of the same type can be declared via array format
:::yaml
---
Only:
moduleexists:
- 'silverstripe/blog'
- 'silverstripe/lumberjack'
<div class="alert" markdown="1">
When you have more than one rule for a nested fragment, they're joined like
`FRAGMENT_INCLUDED = (ONLY && ONLY) && !(EXCEPT && EXCEPT)`.
That is, the fragment will be included if all Only rules match, except if all Except rules match.
</div>
<div class="alert" markdown="1">
Due to YAML limitations, having multiple conditions of the same kind (say, two `EnvVarSet` in one "Only" block)
will result in only the latter coming through.
</div>
## API Documentation
* [api:Config]

View File

@ -959,6 +959,26 @@ to update those with the appropriate function or config call. See
[CMS architecture](/developer_guides/customising_the_admin_interface/cms-architecture#the-admin-url) for language
specific functions.
#### Upgrading Config API usages
Performance optimisations have been made to Config which, under certain circumstances, require developer
care when modifying or caching config values. The top level config object is now immutable on application
bootstrap, and requires a developer to invoke `Config::modify()` to make mutable prior to modification.
This will immediately have a slight performance hit, so should be done sparingly, and avoided at all
if possible in performance intensive applications.
In addition, the `Config::inst()->update()` method is deprecated, and replaced with `Config::modify()->set()` and
`Config::modify()->merge()` to respectively replace and merge config.
When config is merged (either via modification or merged between yml blocks) falsey-values (including nulls)
now replace any prior values (even arrays).
One removed feature is the `Config::FIRST_SET` option. Either use uninherited config directly on the class
directly, or use the inherited config lookup. As falsey values now overwrite all parent class values, it is
now generally safer to use the default inherited config, where in the past you would need to use `FIRST_SET`.
## <a name="api-changes"></a>API Changes
### <a name="overview-general"></a>General and Core API
@ -997,6 +1017,11 @@ specific functions.
* Added a server requirement for the php-intl extension (shipped by default with most PHP distributions)
* Replaced Zend_Date and Zend_Locale with the php-intl extension.
* Consistently use CLDR date formats (rather than a mix of CLDR and date() formats)
* Moved config into a new module: [silverstripe/config](https://github.com/silverstripe/silverstripe-config/).
See upgrading notes below.
* Falsey config values (null, 0, false, etc) can now replace non-falsey values.
* Introduced new ModuleLoader manifest, which allows modules to be found via composer name.
E.g. `$cms = ModuleLoader::instance()->getManifest()->getModule('silverstripe/cms')`
#### <a name="overview-general-removed"></a>General and Core Removed API
@ -1033,6 +1058,7 @@ specific functions.
* Removed TextParser and BBCodeParser. These are available in an archived module,
[silverstripe-archive/bbcodeparser](https://github.com/silverstripe-archive/silverstripe-bbcodeparser)
* Removed `ViewableData::ThemeDir`. Use `ThemeResourceLoader::findThemedResource` in conjunction with `SSViewer::get_themes` instead.
* Removed `Config::FIRST_SET` and `Config::INHERITED`
#### <a name="overview-general-deprecated"></a>General and Core Deprecated API
@ -1040,6 +1066,8 @@ A very small number of methods were chosen for deprecation, and will be removed
* `ClassInfo::baseDataClass` - Use `DataObject::getSchema()->baseDataClass()` instead.
* `ClassInfo::table_for_object_field` - Use `DataObject::getSchema()->tableForField()` instead
* `Config::inst()->update()` is deprecated. Use `Config::modify()->set()` or `Config::modify()->merge()`
instead.
### <a name="overview-orm"></a>ORM API
@ -1437,6 +1465,11 @@ New `TimeField` methods replace `getConfig()` / `setConfig()`
* Removed `DatetimeField`, `DateField` and `TimeField` methods `getConfig` and `setConfig`. Individual
getters and setters for individual options are provided instead. See above for list of new methods.
* Removed `NumericField_Readonly`. Use `setReadonly(true)` instead.
* `SSViewer` deprecated methods removed:
* `set_source_file_comments()`
* `get_source_file_comments()`
* `getOption`
* `setOption`
### <a name="overview-i18n"></a>i18n API

View File

@ -163,7 +163,8 @@ $chain
// Next, check if we're in dev mode, or the database doesn't have any security data, or we are admin
if (Director::isDev() || !Security::database_is_ready() || Permission::check('ADMIN')) {
return $reloadToken->reloadWithToken();
$reloadToken->reloadWithToken();
return;
}
// Fail and redirect the user to the login page

View File

@ -127,7 +127,8 @@ class AssetControlExtension extends DataExtension
protected function processManipulation(AssetManipulationList $manipulations)
{
// When deleting from stage then check if we should archive assets
$archive = $this->owner->config()->keep_archived_assets;
$archive = $this->owner->config()->get('keep_archived_assets');
// Publish assets
$this->publishAll($manipulations->getPublicAssets());

View File

@ -11,6 +11,7 @@ use InvalidArgumentException;
*
* Each file finder instance can have several options set on it:
* - name_regex (string): A regular expression that file basenames must match.
* - dir_regexp (string): A regular expression that dir basenames must match
* - accept_callback (callback): A callback that is called to accept a file.
* If it returns false the item will be skipped. The callback is passed the
* basename, pathname and depth.
@ -51,6 +52,7 @@ class FileFinder
*/
protected static $default_options = array(
'name_regex' => null,
'dir_regex' => null,
'accept_callback' => null,
'accept_dir_callback' => null,
'accept_file_callback' => null,
@ -193,6 +195,12 @@ class FileFinder
*/
protected function acceptDir($basename, $pathname, $depth)
{
if ($regex = $this->getOption('dir_regex')) {
if (!preg_match($regex, $basename)) {
return false;
}
}
if ($this->getOption('ignore_vcs') && in_array($basename, self::$vcs_dirs)) {
return false;
}

View File

@ -7,6 +7,7 @@ use League\Flysystem\Config as FlysystemConfig;
use SilverStripe\Assets\File;
use SilverStripe\Assets\Filesystem;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\ORM\ArrayList;
use SilverStripe\View\ArrayData;
use SilverStripe\View\SSViewer;
@ -16,6 +17,7 @@ use SilverStripe\View\SSViewer;
*/
class AssetAdapter extends Local
{
use Configurable;
/**
* Server specific configuration necessary to block http traffic to a local folder
@ -50,7 +52,7 @@ class AssetAdapter extends Local
$root = realpath($root);
// Override permissions with config
$permissions = Config::inst()->get(get_class($this), 'file_permissions');
$permissions = $this->config()->get('file_permissions');
parent::__construct($root, $writeFlags, $linkHandling, $permissions);
// Configure server
@ -104,7 +106,7 @@ class AssetAdapter extends Local
list($type) = explode('/', strtolower($type));
// Determine configurations to write
$rules = Config::inst()->get(get_class($this), 'server_configuration', Config::FIRST_SET);
$rules = $this->config()->get('server_configuration');
if (empty($rules[$type])) {
return;
}

View File

@ -16,7 +16,7 @@ use SilverStripe\Assets\Storage\AssetStoreRouter;
use SilverStripe\Control\Director;
use SilverStripe\Control\Session;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Flushable;
use SilverStripe\Core\Injector\Injector;
@ -25,6 +25,7 @@ use SilverStripe\Core\Injector\Injector;
*/
class FlysystemAssetStore implements AssetStore, AssetStoreRouter, Flushable
{
use Configurable;
/**
* Session key to use for user grants
@ -347,7 +348,7 @@ class FlysystemAssetStore implements AssetStore, AssetStoreRouter, Flushable
{
if ($dirname
&& ltrim(dirname($dirname), '.')
&& ! Config::inst()->get(get_class($this), 'keep_empty_dirs')
&& ! $this->config()->get('keep_empty_dirs')
&& ! $filesystem->listContents($dirname)
) {
$filesystem->deleteDir($dirname);
@ -612,7 +613,7 @@ class FlysystemAssetStore implements AssetStore, AssetStoreRouter, Flushable
*/
protected function useLegacyFilenames()
{
return Config::inst()->get(get_class($this), 'legacy_filenames');
return $this->config()->get('legacy_filenames');
}
public function getMetadata($filename, $hash, $variant = null)
@ -860,7 +861,7 @@ class FlysystemAssetStore implements AssetStore, AssetStoreRouter, Flushable
// Add headers
$response->addHeader('Content-Type', $mime);
$headers = Config::inst()->get(get_class($this), 'file_response_headers');
$headers = $this->config()->get('file_response_headers');
foreach ($headers as $header => $value) {
$response->addHeader($header, $value);
}
@ -874,7 +875,7 @@ class FlysystemAssetStore implements AssetStore, AssetStoreRouter, Flushable
*/
protected function createDeniedResponse()
{
$code = (int)Config::inst()->get(get_class($this), 'denied_response_code');
$code = (int)$this->config()->get('denied_response_code');
return $this->createErrorResponse($code);
}
@ -885,7 +886,7 @@ class FlysystemAssetStore implements AssetStore, AssetStoreRouter, Flushable
*/
protected function createMissingResponse()
{
$code = (int)Config::inst()->get(get_class($this), 'missing_response_code');
$code = (int)$this->config()->get('missing_response_code');
return $this->createErrorResponse($code);
}

View File

@ -36,7 +36,12 @@ class ProtectedAssetAdapter extends AssetAdapter implements ProtectedAdapter
}
// Use environment defined path or default location is under assets
return getenv('SS_PROTECTED_ASSETS_PATH') ?: ASSETS_PATH . '/' . Config::inst()->get(get_class($this), 'secure_folder');
if ($path = getenv('SS_PROTECTED_ASSETS_PATH')) {
return $path;
}
// Default location
return ASSETS_PATH . '/' . Config::inst()->get(__CLASS__, 'secure_folder');
}
/**

View File

@ -21,7 +21,6 @@ use SilverStripe\Forms\FieldList;
*/
class Image extends File implements ShortcodeHandler
{
/**
* @config
* @var string
@ -54,7 +53,7 @@ class Image extends File implements ShortcodeHandler
$image = "<img src=\"{$previewLink}\" class=\"editor__thumbnail\" />";
$link = $this->Link();
$statusTitle = $this->getStatusTitle();
$statusFlag = "<span class=\"editor__status-flag\">{$statusTitle}</span>";

View File

@ -479,8 +479,8 @@ trait ImageManipulation
*/
public function CMSThumbnail()
{
$width = (int)Config::inst()->get(get_class($this), 'cms_thumbnail_width');
$height = (int)Config::inst()->get(get_class($this), 'cms_thumbnail_height');
$width = (int)Config::inst()->get(__CLASS__, 'cms_thumbnail_width');
$height = (int)Config::inst()->get(__CLASS__, 'cms_thumbnail_height');
return $this->ThumbnailIcon($width, $height);
}
@ -491,8 +491,8 @@ trait ImageManipulation
*/
public function StripThumbnail()
{
$width = (int)Config::inst()->get(get_class($this), 'strip_thumbnail_width');
$height = (int)Config::inst()->get(get_class($this), 'strip_thumbnail_height');
$width = (int)Config::inst()->get(__CLASS__, 'strip_thumbnail_width');
$height = (int)Config::inst()->get(__CLASS__, 'strip_thumbnail_height');
return $this->ThumbnailIcon($width, $height);
}
@ -503,7 +503,7 @@ trait ImageManipulation
*/
public function PreviewThumbnail()
{
$width = (int)Config::inst()->get(get_class($this), 'asset_preview_width');
$width = (int)Config::inst()->get(__CLASS__, 'asset_preview_width');
return $this->ScaleMaxWidth($width) ?: $this->IconTag();
}

View File

@ -61,7 +61,7 @@ class ImagickBackend extends Imagick implements Image_Backend
protected function setDefaultQuality()
{
$this->setQuality(Config::inst()->get('SilverStripe\\Assets\\ImagickBackend', 'default_quality'));
$this->setQuality(Config::inst()->get(__CLASS__, 'default_quality'));
}
public function writeToStore(AssetStore $assetStore, $filename, $hash = null, $variant = null, $config = array())

View File

@ -90,7 +90,7 @@ class Upload_Validator
// Check if there is any defined instance max file sizes
if (empty($this->allowedMaxFileSize)) {
// Set default max file sizes if there isn't
$fileSize = Config::inst()->get('SilverStripe\\Assets\\Upload_Validator', 'default_max_file_size');
$fileSize = Config::inst()->get(__CLASS__, 'default_max_file_size');
if ($fileSize) {
$this->setAllowedMaxFileSize($fileSize);
} else {

View File

@ -173,7 +173,7 @@ class CookieJar implements Cookie_Backend
return setcookie($name, $value, $expiry, $path, $domain, $secure, $httpOnly);
}
if (Cookie::config()->get('report_errors')) {
if (Cookie::config()->uninherited('report_errors')) {
throw new LogicException(
"Cookie '$name' can't be set. The site started outputting content at line $line in $file"
);

View File

@ -81,14 +81,14 @@ class Director implements TemplateGlobalProvider
*
* @var array
*/
private static $dev_servers = array();
protected static $dev_servers = array();
/**
* @config
*
* @var array
*/
private static $test_servers = array();
protected static $test_servers = array();
/**
* Setting this explicitly specifies the protocol ("http" or "https") used, overriding the normal
@ -101,13 +101,6 @@ class Director implements TemplateGlobalProvider
*/
private static $alternate_protocol;
/**
* @config
*
* @var string
*/
private static $alternate_host;
/**
* @config
*
@ -120,7 +113,7 @@ class Director implements TemplateGlobalProvider
*
* @var string
*/
private static $environment_type;
protected static $environment_type;
/**
* Process the given URL, creating the appropriate controller and executing it.
@ -226,7 +219,7 @@ class Director implements TemplateGlobalProvider
$response->output();
}
}
// Handle a controller
// Handle a controller
} elseif ($result) {
if ($result instanceof HTTPResponse) {
$response = $result;
@ -429,7 +422,7 @@ class Director implements TemplateGlobalProvider
*/
protected static function handleRequest(HTTPRequest $request, Session $session, DataModel $model)
{
$rules = Director::config()->get('rules');
$rules = Director::config()->uninherited('rules');
if (isset($_REQUEST['debug'])) {
Debug::show($rules);
@ -578,9 +571,11 @@ class Director implements TemplateGlobalProvider
* - SERVER_NAME
* - gethostname()
*
* @param bool $respectConfig Set to false to ignore config override
* (Necessary for checking host pre-config)
* @return string
*/
public static function host()
public static function host($respectConfig = true)
{
$headerOverride = false;
if (TRUSTED_PROXY) {
@ -598,13 +593,15 @@ class Director implements TemplateGlobalProvider
}
}
if ($host = static::config()->get('alternate_host')) {
return $host;
}
if ($respectConfig) {
if ($host = Director::config()->uninherited('alternate_host')) {
return $host;
}
if ($baseURL = static::config()->get('alternate_base_url')) {
if (preg_match('/^(http[^:]*:\/\/[^\/]+)(\/|$)/', $baseURL, $matches)) {
return parse_url($baseURL, PHP_URL_HOST);
if ($baseURL = Director::config()->uninherited('alternate_base_url')) {
if (preg_match('/^(http[^:]*:\/\/[^\/]+)(\/|$)/', $baseURL, $matches)) {
return parse_url($baseURL, PHP_URL_HOST);
}
}
}
@ -711,20 +708,6 @@ class Director implements TemplateGlobalProvider
}
}
/**
* Sets the root URL for the website. If the site isn't accessible from the URL you provide,
* weird things will happen.
*
* @deprecated 4.0 Use the "Director.alternate_base_url" config setting instead.
*
* @param string $baseURL
*/
public static function setBaseURL($baseURL)
{
Deprecation::notice('4.0', 'Use the "Director.alternate_base_url" config setting instead');
Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', $baseURL);
}
/**
* Returns the root filesystem folder for the site. It will be automatically calculated unless
* it is overridden with {@link setBaseFolder()}.
@ -733,24 +716,10 @@ class Director implements TemplateGlobalProvider
*/
public static function baseFolder()
{
$alternate = static::config()->get('alternate_base_folder');
$alternate = Director::config()->uninherited('alternate_base_folder');
return ($alternate) ? $alternate : BASE_PATH;
}
/**
* Sets the root folder for the website. If the site isn't accessible from the folder you provide,
* weird things will happen.
*
* @deprecated 4.0 Use the "Director.alternate_base_folder" config setting instead.
*
* @param string $baseFolder
*/
public static function setBaseFolder($baseFolder)
{
Deprecation::notice('4.0', 'Use the "Director.alternate_base_folder" config setting instead');
Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_folder', $baseFolder);
}
/**
* Turns an absolute URL or folder into one that's relative to the root of the site. This is useful
* when turning a URL into a filesystem reference, or vice versa.
@ -1172,40 +1141,50 @@ class Director implements TemplateGlobalProvider
* Once the environment type is set, it can be checked with {@link Director::isDev()},
* {@link Director::isTest()}, and {@link Director::isLive()}.
*
* @deprecated 4.0 Use the "Director.environment_type" config setting instead
*
* @param $et string
* @param string $environment
*/
public static function set_environment_type($et)
public static function set_environment_type($environment)
{
if ($et != 'dev' && $et != 'test' && $et != 'live') {
user_error(
"Director::set_environment_type passed '$et'. It should be passed dev, test, or live",
E_USER_WARNING
if (!in_array($environment, ['dev', 'test', 'live'])) {
throw new \InvalidArgumentException(
"Director::set_environment_type passed '$environment'. It should be passed dev, test, or live"
);
} else {
Deprecation::notice('4.0', 'Use the "Director.environment_type" config setting instead');
Config::inst()->update('SilverStripe\\Control\\Director', 'environment_type', $et);
}
self::$environment_type = $environment;
}
/**
* Can also be checked with {@link Director::isDev()}, {@link Director::isTest()}, and
* {@link Director::isLive()}.
*
* @return bool|string
* @return bool
*/
public static function get_environment_type()
{
if (Director::isLive()) {
return 'live';
} elseif (Director::isTest()) {
return 'test';
} elseif (Director::isDev()) {
return 'dev';
} else {
return false;
// Check saved session
if ($env = self::session_environment()) {
return $env;
}
// Check set
if (self::$environment_type) {
return self::$environment_type;
}
// Check getenv
if ($env = getenv('SS_ENVIRONMENT_TYPE')) {
return $env;
}
// Check if we are running on one of the test servers
// Note: Bypass config for checking environment type
if (in_array(static::host(false), self::$dev_servers)) {
return 'dev';
}
if (in_array(static::host(false), self::$test_servers)) {
return 'test';
}
return 'live';
}
/**
@ -1216,7 +1195,7 @@ class Director implements TemplateGlobalProvider
*/
public static function isLive()
{
return !(Director::isDev() || Director::isTest());
return self::get_environment_type() === 'live';
}
/**
@ -1227,19 +1206,7 @@ class Director implements TemplateGlobalProvider
*/
public static function isDev()
{
// Check session
if ($env = self::session_environment()) {
return $env === 'dev';
}
// Check config
if (Config::inst()->get('SilverStripe\\Control\\Director', 'environment_type') === 'dev') {
return true;
}
// Check if we are running on one of the test servers
$devServers = (array)Config::inst()->get('SilverStripe\\Control\\Director', 'dev_servers');
return in_array(static::host(), $devServers);
return self::get_environment_type() === 'dev';
}
/**
@ -1250,24 +1217,7 @@ class Director implements TemplateGlobalProvider
*/
public static function isTest()
{
// In case of isDev and isTest both being set, dev has higher priority
if (self::isDev()) {
return false;
}
// Check saved session
if ($env = self::session_environment()) {
return $env === 'test';
}
// Check config
if (Config::inst()->get('SilverStripe\\Control\\Director', 'environment_type') === 'test') {
return true;
}
// Check if we are running on one of the test servers
$testServers = (array)Config::inst()->get('SilverStripe\\Control\\Director', 'test_servers');
return in_array(static::host(), $testServers);
return self::get_environment_type() === 'test';
}
/**
@ -1275,7 +1225,7 @@ class Director implements TemplateGlobalProvider
*
* @return null|string
*/
protected static function session_environment()
public static function session_environment()
{
// Set session from querystring
if (isset($_GET['isDev'])) {

View File

@ -111,15 +111,15 @@ class Email extends ViewableData
public static function obfuscate($email, $method = 'visible')
{
switch ($method) {
case 'direction' :
case 'direction':
Requirements::customCSS('span.codedirection { unicode-bidi: bidi-override; direction: rtl; }', 'codedirectionCSS');
return '<span class="codedirection">' . strrev($email) . '</span>';
case 'visible' :
case 'visible':
$obfuscated = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
return strtr($email, $obfuscated);
case 'hex' :
case 'hex':
$encoded = '';
for ($x = 0; $x < strlen($email); $x++) {
$encoded .= '&#x' . bin2hex($email{$x}) . ';';

View File

@ -5,7 +5,6 @@ namespace SilverStripe\Control;
use SilverStripe\Assets\File;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Config\Config;
use InvalidArgumentException;
use finfo;
@ -308,7 +307,7 @@ class HTTP
// to get the file mime-type
$ext = File::get_file_extension($filename);
// Get the mime-types
$mimeTypes = HTTP::config()->get('MimeTypes');
$mimeTypes = HTTP::config()->uninherited('MimeTypes');
// The mime type doesn't exist
if (!isset($mimeTypes[$ext])) {
@ -397,11 +396,11 @@ class HTTP
// Populate $responseHeaders with all the headers that we want to build
$responseHeaders = array();
$cacheControlHeaders = Config::inst()->get(__CLASS__, 'cache_control');
$cacheControlHeaders = HTTP::config()->uninherited('cache_control');
// currently using a config setting to cancel this, seems to be so that the CMS caches ajax requests
if (function_exists('apache_request_headers') && Config::inst()->get(__CLASS__, 'cache_ajax_requests')) {
if (function_exists('apache_request_headers') && static::config()->uninherited('cache_ajax_requests')) {
$requestHeaders = array_change_key_case(apache_request_headers(), CASE_LOWER);
if (isset($requestHeaders['x-requested-with'])
&& $requestHeaders['x-requested-with']=='XMLHttpRequest'
@ -421,7 +420,7 @@ class HTTP
// To do: User-Agent should only be added in situations where you *are* actually
// varying according to user-agent.
$vary = Config::inst()->get(__CLASS__, 'vary');
$vary = HTTP::config()->uninherited('vary');
if ($vary && strlen($vary)) {
$responseHeaders['Vary'] = $vary;
}

View File

@ -221,7 +221,7 @@ class RSSFeed extends ViewableData
*/
public function outputToBrowser()
{
$prevState = SSViewer::config()->get('source_file_comments');
$prevState = SSViewer::config()->uninherited('source_file_comments');
SSViewer::config()->update('source_file_comments', false);
$response = Controller::curr()->getResponse();

View File

@ -2,10 +2,10 @@
namespace SilverStripe\Control;
use InvalidArgumentException;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Object;
use SilverStripe\Dev\Debug;
use SilverStripe\Dev\Deprecation;
use SilverStripe\ORM\DataModel;
use SilverStripe\Security\Security;
use SilverStripe\Security\PermissionFailureException;
@ -264,7 +264,7 @@ class RequestHandler extends ViewableData
$handlerClass = ($this->class) ? $this->class : get_class($this);
// We stop after RequestHandler; in other words, at ViewableData
while ($handlerClass && $handlerClass != 'SilverStripe\\View\\ViewableData') {
while ($handlerClass && $handlerClass != ViewableData::class) {
$urlHandlers = Config::inst()->get($handlerClass, 'url_handlers', Config::UNINHERITED);
if ($urlHandlers) {
@ -338,22 +338,14 @@ class RequestHandler extends ViewableData
public function allowedActions($limitToClass = null)
{
if ($limitToClass) {
$actions = Config::inst()->get(
$limitToClass,
'allowed_actions',
Config::UNINHERITED | Config::EXCLUDE_EXTRA_SOURCES
);
$actions = Config::forClass($limitToClass)->get('allowed_actions', true);
} else {
$actions = Config::inst()->get(get_class($this), 'allowed_actions');
$actions = $this->config()->get('allowed_actions');
}
if (is_array($actions)) {
if (array_key_exists('*', $actions)) {
Deprecation::notice(
'3.0',
'Wildcards (*) are no longer valid in $allowed_actions due their ambiguous '
. ' and potentially insecure behaviour. Please define all methods explicitly instead.'
);
throw new InvalidArgumentException("Invalid allowed_action '*'");
}
// convert all keys and values to lowercase to
@ -413,10 +405,7 @@ class RequestHandler extends ViewableData
}
}
$actionsWithoutExtra = $this->config()->get(
'allowed_actions',
Config::UNINHERITED | Config::EXCLUDE_EXTRA_SOURCES
);
$actionsWithoutExtra = $this->config()->get('allowed_actions', true);
if (!is_array($actions) || !$actionsWithoutExtra) {
if ($action != 'doInit' && $action != 'run' && method_exists($this, $action)) {
return true;

View File

@ -2,126 +2,15 @@
namespace SilverStripe\Core\Config;
use SilverStripe\Core\Object;
use SilverStripe\Core\Manifest\ConfigStaticManifest;
use SilverStripe\Core\Manifest\ConfigManifest;
use UnexpectedValueException;
use stdClass;
use InvalidArgumentException;
use SilverStripe\Config\Collections\ConfigCollectionInterface;
use SilverStripe\Config\Collections\MutableConfigCollectionInterface;
/**
* The configuration system works like this:
*
* Each class has a set of named properties
*
* Each named property can contain either
*
* - An array
* - A non-array value
*
* If the value is an array, each value in the array may also be one of those
* three types.
*
* A property can have a value specified in multiple locations, each of which
* have a hard coded or explicit priority. We combine all these values together
* into a "composite" value using rules that depend on the priority order of
* the locations to give the final value, using these rules:
*
* - If the value is an array, each array is added to the _beginning_ of the
* composite array in ascending priority order. If a higher priority item has
* a non-integer key which is the same as a lower priority item, the value of
* those items is merged using these same rules, and the result of the merge
* is located in the same location the higher priority item would be if there
* was no key clash. Other than in this key-clash situation, within the
* particular array, order is preserved.
*
* - If the value is not an array, the highest priority value is used without
* any attempt to merge.
*
* It is an error to have mixed types of the same named property in different
* locations (but an error will not necessarily be raised due to optimizations
* in the lookup code).
*
* The exception to this is "false-ish" values - empty arrays, empty strings,
* etc. When merging a non-false-ish value with a false-ish value, the result
* will be the non-false-ish value regardless of priority. When merging two
* false-ish values the result will be the higher priority false-ish value.
*
* The locations that configuration values are taken from in highest -> lowest
* priority order.
*
* - Any values set via a call to Config#update.
*
* - The configuration values taken from the YAML files in _config directories
* (internally sorted in before / after order, where the item that is latest
* is highest priority).
*
* - Any static set on an "additional static source" class (such as an
* extension) named the same as the name of the property.
*
* - Any static set on the class named the same as the name of the property.
*
* - The composite configuration value of the parent class of this class.
*
* At some of these levels you can also set masks. These remove values from the
* composite value at their priority point rather than add. They are much
* simpler. They consist of a list of key / value pairs. When applied against
* the current composite value:
*
* - If the composite value is a sequential array, any member of that array
* that matches any value in the mask is removed.
*
* - If the composite value is an associative array, any member of that array
* that matches both the key and value of any pair in the mask is removed.
*
* - If the composite value is not an array, if that value matches any value
* in the mask it is removed.
*/
class Config
abstract class Config
{
/**
* A marker instance for the "anything" singleton value. Don't access
* directly, even in-class, always use self::anything()
*
* @var Object
*/
private static $_anything = null;
/**
* @var bool
*/
protected $collectConfigPHPSettings;
/**
* @var bool
*/
protected $configPHPIsSafe;
/**
* Get a marker class instance that is used to do a "remove anything with
* this key" by adding $key => Config::anything() to the suppress array
*
* @return Object
*/
public static function anything()
{
if (self::$_anything === null) {
self::$_anything = new stdClass();
}
return self::$_anything;
}
// -- Source options bitmask --
/**
* source options bitmask value - merge all parent configuration in as
* lowest priority.
*
* @const
*/
const INHERITED = 0;
/**
* source options bitmask value - only get configuration set for this
* specific class, not any of it's parents.
@ -130,104 +19,44 @@ class Config
*/
const UNINHERITED = 1;
/**
* source options bitmask value - inherit, but stop on the first class
* that actually provides a value (event an empty value).
*
* @const
*/
const FIRST_SET = 2;
/**
* @const source options bitmask value - do not use additional statics
* sources (such as extension)
*/
const EXCLUDE_EXTRA_SOURCES = 4;
// -- get_value_type response enum --
/**
* Return flag for get_value_type indicating value is a scalar (or really
* just not-an-array, at least ATM)
*
* @const
*/
const ISNT_ARRAY = 1;
/**
* Return flag for get_value_type indicating value is an array.
* @const
*/
const IS_ARRAY = 2;
/**
* Get whether the value is an array or not. Used to be more complicated,
* but still nice sugar to have an enum to compare and not just a true /
* false value.
*
* @param mixed $val The value
*
* @return int One of ISNT_ARRAY or IS_ARRAY
*/
protected static function get_value_type($val)
{
if (is_array($val)) {
return self::IS_ARRAY;
}
return self::ISNT_ARRAY;
}
/**
* What to do if there's a type mismatch.
*
* @throws UnexpectedValueException
*/
protected static function type_mismatch()
{
throw new UnexpectedValueException('Type mismatch in configuration. All values for a particular property must'
. ' contain the same type (or no value at all).');
}
/**
* @todo If we can, replace next static & static methods with DI once that's in
*/
protected static $instance;
/**
* Get the current active Config instance.
*
* Configs should not normally be manually created.
*
* In general use you will use this method to obtain the current Config
* instance.
* instance. It assumes the config instance has already been set.
*
* @return Config
* @return ConfigCollectionInterface
*/
public static function inst()
{
if (!self::$instance) {
self::$instance = new Config();
}
return self::$instance;
return ConfigLoader::instance()->getManifest();
}
/**
* Set the current active {@link Config} instance.
* Make this config available to be modified
*
* {@link Config} objects should not normally be manually created.
*
* A use case for replacing the active configuration set would be for
* creating an isolated environment for unit tests.
*
* @param Config $instance New instance of Config to assign
* @return Config Reference to new active Config instance
* @return MutableConfigCollectionInterface
*/
public static function set_instance($instance)
public static function modify()
{
self::$instance = $instance;
return $instance;
$instance = static::inst();
if ($instance instanceof MutableConfigCollectionInterface) {
return $instance;
}
// By default nested configs should become mutable
$instance = static::nest();
if ($instance instanceof MutableConfigCollectionInterface) {
return $instance;
}
throw new InvalidArgumentException("Nested config could not be made mutable");
}
/**
@ -238,527 +67,48 @@ class Config
* remove on the new value returned by {@link Config::inst()}, and then discard
* those changes later by calling unnest.
*
* @return Config Reference to new active Config instance
* @return ConfigCollectionInterface Active config
*/
public static function nest()
{
$current = self::$instance;
$new = clone $current;
$new->nestedFrom = $current;
return self::set_instance($new);
// Clone current config and nest
$new = self::inst()->nest();
ConfigLoader::instance()->pushManifest($new);
return $new;
}
/**
* Change the active Config back to the Config instance the current active
* Config object was copied from.
*
* @return Config Reference to new active Config instance
* @return ConfigCollectionInterface
*/
public static function unnest()
{
if (self::inst()->nestedFrom) {
self::set_instance(self::inst()->nestedFrom);
} else {
// Unnest unless we would be left at 0 manifests
$loader = ConfigLoader::instance();
if ($loader->countManifests() < 2) {
user_error(
"Unable to unnest root Config, please make sure you don't have mis-matched nest/unnest",
E_USER_WARNING
);
} else {
$loader->popManifest();
}
return self::inst();
return static::inst();
}
/**
* @var array
*/
protected $cache;
/**
* Each copy of the Config object need's it's own cache, so changes don't
* leak through to other instances.
*/
public function __construct()
{
$this->cache = new Config_MemCache();
}
public function __clone()
{
$this->cache = clone $this->cache;
}
/**
* @var Config - The config instance this one was copied from when
* Config::nest() was called.
*/
protected $nestedFrom = null;
/**
* @var array - Array of arrays. Each member is an nested array keyed as
* $class => $name => $value, where value is a config value to treat as
* the highest priority item.
*/
protected $overrides = array();
/**
* @var array $suppresses Array of arrays. Each member is an nested array
* keyed as $class => $name => $value, where value is a config value suppress
* from any lower priority item.
*/
protected $suppresses = array();
/**
* @var array
*/
protected $staticManifests = array();
/**
* @param ConfigStaticManifest $manifest
*/
public function pushConfigStaticManifest(ConfigStaticManifest $manifest)
{
array_unshift($this->staticManifests, $manifest);
$this->cache->clean();
}
/** @var [array] - The list of settings pulled from config files to search through */
protected $manifests = array();
/**
* Add another manifest to the list of config manifests to search through.
*
* WARNING: Config manifests to not merge entries, and do not solve before/after rules inter-manifest -
* instead, the last manifest to be added always wins
* @param ConfigManifest $manifest
*/
public function pushConfigYamlManifest(ConfigManifest $manifest)
{
array_unshift($this->manifests, $manifest);
// Now that we've got another yaml config manifest we need to clean the cache
$this->cache->clean();
// We also need to clean the cache if the manifest's calculated config values change
$manifest->registerChangeCallback(array($this->cache, 'clean'));
// @todo: Do anything with these. They're for caching after config.php has executed
$this->collectConfigPHPSettings = true;
$this->configPHPIsSafe = false;
$manifest->activateConfig();
$this->collectConfigPHPSettings = false;
}
/** @var [Config_ForClass] - The list of Config_ForClass instances, keyed off class */
static protected $for_class_instances = array();
/**
* Get an accessor that returns results by class by default.
*
* Shouldn't be overridden, since there might be many Config_ForClass instances already held in the wild. Each
* Config_ForClass instance asks the current_instance of Config for the actual result, so override that instead
*
* @param $class
* @param string $class
* @return Config_ForClass
*/
public function forClass($class)
public static function forClass($class)
{
if (isset(self::$for_class_instances[$class])) {
return self::$for_class_instances[$class];
} else {
return self::$for_class_instances[$class] = new Config_ForClass($class);
}
}
/**
* Merge a lower priority associative array into an existing higher priority associative array, as per the class
* docblock rules
*
* It is assumed you've already checked that you've got two associative arrays, not scalars or sequential arrays
*
* @param $dest array - The existing high priority associative array
* @param $src array - The low priority associative array to merge in
*/
public static function merge_array_low_into_high(&$dest, $src)
{
foreach ($src as $k => $v) {
if (!$v) {
continue;
} elseif (is_int($k)) {
$dest[] = $v;
} elseif (isset($dest[$k])) {
$newType = self::get_value_type($v);
$currentType = self::get_value_type($dest[$k]);
// Throw error if types don't match
if ($currentType !== $newType) {
self::type_mismatch();
}
if ($currentType == self::IS_ARRAY) {
self::merge_array_low_into_high($dest[$k], $v);
} else {
continue;
}
} else {
$dest[$k] = $v;
}
}
}
/**
* Merge a higher priority assocative array into an existing lower priority associative array, as per the class
* docblock rules.
*
* Much more expensive that the other way around, as there's no way to insert an associative k/v pair into an
* array at the top of the array
*
* @static
* @param $dest array - The existing low priority associative array
* @param $src array - The high priority array to merge in
*/
public static function merge_array_high_into_low(&$dest, $src)
{
$res = $src;
self::merge_array_low_into_high($res, $dest);
$dest = $res;
}
public static function merge_high_into_low(&$result, $value)
{
$newType = self::get_value_type($value);
if (!$result) {
$result = $value;
} else {
$currentType = self::get_value_type($result);
if ($currentType !== $newType) {
self::type_mismatch();
}
if ($currentType == self::ISNT_ARRAY) {
$result = $value;
} else {
self::merge_array_high_into_low($result, $value);
}
}
}
public static function merge_low_into_high(&$result, $value, $suppress)
{
$newType = self::get_value_type($value);
if ($suppress) {
if ($newType == self::IS_ARRAY) {
$value = self::filter_array_by_suppress_array($value, $suppress);
if (!$value) {
return;
}
} else {
if (self::check_value_contained_in_suppress_array($value, $suppress)) {
return;
}
}
}
if (!$result) {
$result = $value;
} else {
$currentType = self::get_value_type($result);
if ($currentType !== $newType) {
self::type_mismatch();
}
if ($currentType == self::ISNT_ARRAY) {
return; // PASS
} else {
self::merge_array_low_into_high($result, $value);
}
}
}
public static function check_value_contained_in_suppress_array($v, $suppresses)
{
foreach ($suppresses as $suppress) {
list($sk, $sv) = $suppress;
if ($sv === self::anything() || $v == $sv) {
return true;
}
}
return false;
}
protected static function check_key_or_value_contained_in_suppress_array($k, $v, $suppresses)
{
foreach ($suppresses as $suppress) {
list($sk, $sv) = $suppress;
if (($sk === self::anything() || $k == $sk) && ($sv === self::anything() || $v == $sv)) {
return true;
}
}
return false;
}
protected static function filter_array_by_suppress_array($array, $suppress)
{
$res = array();
foreach ($array as $k => $v) {
$suppressed = self::check_key_or_value_contained_in_suppress_array($k, $v, $suppress);
if (!$suppressed) {
if (is_numeric($k)) {
$res[] = $v;
} else {
$res[$k] = $v;
}
}
}
return $res;
}
protected $extraConfigSources = array();
public function extraConfigSourcesChanged($class)
{
unset($this->extraConfigSources[$class]);
$this->cache->clean("__{$class}");
}
protected function getUncached($class, $name, $sourceOptions, &$result, $suppress, &$tags)
{
$tags[] = "__{$class}";
$tags[] = "__{$class}__{$name}";
// If result is already not something to merge into, just return it
if ($result !== null && !is_array($result)) {
return $result;
}
// First, look through the override values
foreach ($this->overrides as $k => $overrides) {
if (isset($overrides[$class][$name])) {
$value = $overrides[$class][$name];
self::merge_low_into_high($result, $value, $suppress);
if ($result !== null && !is_array($result)) {
return $result;
}
}
if (isset($this->suppresses[$k][$class][$name])) {
$suppress = $suppress
? array_merge($suppress, $this->suppresses[$k][$class][$name])
: $this->suppresses[$k][$class][$name];
}
}
$nothing = null;
// Then the manifest values
foreach ($this->manifests as $manifest) {
$value = $manifest->get($class, $name, $nothing);
if ($value !== $nothing) {
self::merge_low_into_high($result, $value, $suppress);
if ($result !== null && !is_array($result)) {
return $result;
}
}
}
$sources = array($class);
// Include extensions only if not flagged not to, and some have been set
if (($sourceOptions & self::EXCLUDE_EXTRA_SOURCES) != self::EXCLUDE_EXTRA_SOURCES) {
// If we don't have a fresh list of extra sources, get it from the class itself
if (!array_key_exists($class, $this->extraConfigSources)) {
$this->extraConfigSources[$class] = Object::get_extra_config_sources($class);
}
// Update $sources with any extra sources
$extraSources = $this->extraConfigSources[$class];
if ($extraSources) {
$sources = array_merge($sources, $extraSources);
}
}
$value = $nothing = null;
foreach ($sources as $staticSource) {
if (is_array($staticSource)) {
$value = isset($staticSource[$name]) ? $staticSource[$name] : $nothing;
} else {
foreach ($this->staticManifests as $i => $statics) {
$value = $statics->get($staticSource, $name, $nothing);
if ($value !== $nothing) {
break;
}
}
}
if ($value !== $nothing) {
self::merge_low_into_high($result, $value, $suppress);
if ($result !== null && !is_array($result)) {
return $result;
}
}
}
// Finally, merge in the values from the parent class
if (($sourceOptions & self::UNINHERITED) != self::UNINHERITED &&
(($sourceOptions & self::FIRST_SET) != self::FIRST_SET || $result === null)
) {
$parent = get_parent_class($class);
if ($parent) {
$this->getUncached($parent, $name, $sourceOptions, $result, $suppress, $tags);
}
}
return $result;
}
/**
* Get the config value associated for a given class and property
*
* This merges all current sources and overrides together to give final value
* todo: Currently this is done every time. This function is an inner loop function, so we really need to be
* caching heavily here.
*
* @param $class string - The name of the class to get the value for
* @param $name string - The property to get the value for
* @param int $sourceOptions Bitmask which can be set to some combintain of Config::UNINHERITED,
* Config::FIRST_SET, and Config::EXCLUDE_EXTENSIONS.
*
* Config::UNINHERITED does not include parent classes when merging configuration fragments
* Config::FIRST_SET stops inheriting once the first class that sets a value (even an empty value) is encoutered
* Config::EXCLUDE_EXTRA_SOURCES does not include any additional static sources (such as extensions)
*
* Config::INHERITED is a utility constant that can be used to mean "none of the above", equvilient to 0
* Setting both Config::UNINHERITED and Config::FIRST_SET behaves the same as just Config::UNINHERITED
*
* should the parent classes value be merged in as the lowest priority source?
* @param $result mixed Reference to a variable to put the result in. Also returned, so this can be left
* as null safely. If you do pass a value, it will be treated as the highest priority
* value in the result chain
* @param $suppress array Internal use when called by child classes. Array of mask pairs to filter value by
* @return mixed The value of the config item, or null if no value set. Could be an associative array,
* sequential array or scalar depending on value (see class docblock)
*/
public function get($class, $name, $sourceOptions = 0, &$result = null, $suppress = null)
{
// Have we got a cached value? Use it if so
$key = $class.$name.$sourceOptions;
list($cacheHit, $result) = $this->cache->checkAndGet($key);
if (!$cacheHit) {
$tags = array();
$result = null;
$this->getUncached($class, $name, $sourceOptions, $result, $suppress, $tags);
$this->cache->set($key, $result, $tags);
}
return $result;
}
/**
* Update a configuration value
*
* Configuration is modify only. The value passed is merged into the existing configuration. If you want to
* replace the current array value, you'll need to call remove first.
*
* @param string $class The class to update a configuration value for
* @param string $name The configuration property name to update
* @param mixed $val The value to update with
*
* Arrays are recursively merged into current configuration as "latest" - for associative arrays the passed value
* replaces any item with the same key, for sequential arrays the items are placed at the end of the array, for
* non-array values, this value replaces any existing value
*
* You will get an error if you try and override array values with non-array values or vice-versa
*/
public function update($class, $name, $val)
{
if (is_null($val)) {
$this->remove($class, $name);
} else {
if (!isset($this->overrides[0][$class])) {
$this->overrides[0][$class] = array();
}
if (!array_key_exists($name, $this->overrides[0][$class])) {
$this->overrides[0][$class][$name] = $val;
} else {
self::merge_high_into_low($this->overrides[0][$class][$name], $val);
}
}
$this->cache->clean("__{$class}__{$name}");
}
/**
* Remove a configuration value
*
* You can specify a key, a key and a value, or neither. Either argument can be Config::anything(), which is
* what is defaulted to if you don't specify something
*
* This removes any current configuration value that matches the key and/or value specified
*
* Works like this:
* - Check the current override array, and remove any values that match the arguments provided
* - Keeps track of the arguments passed to this method, and in get filters everything _except_ the current
* override array to exclude any match
*
* This way we can re-set anything removed by a call to this function by calling set. Because the current override
* array is only filtered immediately on calling this remove method, that value will then be exposed. However,
* every other source is filtered on request, so no amount of changes to parent's configuration etc can override a
* remove call.
*
* @param string $class The class to remove a configuration value from
* @param string $name The configuration name
* @param mixed $key An optional key to filter against.
* If referenced config value is an array, only members of that array that match this key will be removed
* Must also match value if provided to be removed
* @param mixed $value And optional value to filter against.
* If referenced config value is an array, only members of that array that match this value will be removed
* If referenced config value is not an array, value will be removed only if it matches this argument
* Must also match key if provided and referenced config value is an array to be removed
*
* Matching is always by "==", not by "==="
*/
public function remove($class, $name, $key = null, $value = null)
{
if (func_num_args() < 3) {
$key = self::anything();
}
if (func_num_args() < 4) {
$value = self::anything();
}
$suppress = array($key, $value);
if (isset($this->overrides[0][$class][$name])) {
$value = $this->overrides[0][$class][$name];
if (is_array($value)) {
$this->overrides[0][$class][$name] = self::filter_array_by_suppress_array($value, array($suppress));
} else {
if (self::check_value_contained_in_suppress_array($value, array($suppress))) {
unset($this->overrides[0][$class][$name]);
}
}
}
if (!isset($this->suppresses[0][$class])) {
$this->suppresses[0][$class] = array();
}
if (!isset($this->suppresses[0][$class][$name])) {
$this->suppresses[0][$class][$name] = array();
}
$this->suppresses[0][$class][$name][] = $suppress;
$this->cache->clean("__{$class}__{$name}");
return new Config_ForClass($class);
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace SilverStripe\Core\Config;
use SilverStripe\Config\Collections\ConfigCollectionInterface;
/**
* Registers config sources via ConfigCollectionInterface
*/
class ConfigLoader
{
/**
* @var self
*/
private static $instance;
/**
* @var ConfigCollectionInterface[] map of config collections
*/
protected $manifests = array();
/**
* @return self
*/
public static function instance()
{
return self::$instance ? self::$instance : self::$instance = new self();
}
/**
* Returns the currently active class manifest instance that is used for
* loading classes.
*
* @return ConfigCollectionInterface
*/
public function getManifest()
{
return $this->manifests[count($this->manifests) - 1];
}
/**
* Returns true if this class loader has a manifest.
*
* @return bool
*/
public function hasManifest()
{
return (bool)$this->manifests;
}
/**
* Pushes a class manifest instance onto the top of the stack.
*
* @param ConfigCollectionInterface $manifest
*/
public function pushManifest(ConfigCollectionInterface $manifest)
{
$this->manifests[] = $manifest;
}
/**
* @return ConfigCollectionInterface
*/
public function popManifest()
{
return array_pop($this->manifests);
}
/**
* Check number of manifests
*
* @return int
*/
public function countManifests()
{
return count($this->manifests);
}
/**
* Nest the current manifest
*
* @return ConfigCollectionInterface
*/
public function nest()
{
$manifest = $this->getManifest()->nest();
$this->pushManifest($manifest);
return $manifest;
}
}

View File

@ -2,20 +2,21 @@
namespace SilverStripe\Core\Config;
use SilverStripe\Dev\Deprecation;
class Config_ForClass
{
/**
* @var string $class
*/
protected $class;
/**
* @param string $class
* @param string|object $class
*/
public function __construct($class)
{
$this->class = $class;
$this->class = is_object($class) ? get_class($class) : $class;
}
/**
@ -33,19 +34,45 @@ class Config_ForClass
*/
public function __set($name, $val)
{
$this->update($name, $val);
$this->set($name, $val);
}
/**
* Explicit pass-through to Config::update()
*
* @param string $name
* @param mixed $val
* @param mixed $value
* @return $this
*/
public function update($name, $val)
public function update($name, $value)
{
Config::inst()->update($this->class, $name, $val);
Deprecation::notice('5.0', 'Use merge() instead');
return $this->merge($name, $value);
}
/**
* Merge a given config
*
* @param string $name
* @param mixed $value
* @return $this
*/
public function merge($name, $value)
{
Config::modify()->merge($this->class, $name, $value);
return $this;
}
/**
* Replace config value
*
* @param string $name
* @param mixed $value
* @return $this
*/
public function set($name, $value)
{
Config::modify()->set($this->class, $name, $value);
return $this;
}
@ -61,12 +88,12 @@ class Config_ForClass
/**
* @param string $name
* @param int $sourceOptions
* @param mixed $options
* @return mixed
*/
public function get($name, $sourceOptions = 0)
public function get($name, $options = 0)
{
return Config::inst()->get($this->class, $name, $sourceOptions);
return Config::inst()->get($this->class, $name, $options);
}
/**
@ -77,7 +104,7 @@ class Config_ForClass
*/
public function remove($name)
{
Config::inst()->remove($this->class, $name);
Config::modify()->remove($this->class, $name);
return $this;
}
@ -88,6 +115,17 @@ class Config_ForClass
*/
public function forClass($class)
{
return Config::inst()->forClass($class);
return Config::forClass($class);
}
/**
* Get uninherited config
*
* @param string $name Name of config
* @return mixed
*/
public function uninherited($name)
{
return $this->get($name, Config::UNINHERITED);
}
}

View File

@ -1,80 +0,0 @@
<?php
namespace SilverStripe\Core\Config;
class Config_MemCache
{
protected $cache;
protected $i = 0;
protected $c = 0;
protected $tags = array();
public function __construct()
{
$this->cache = array();
}
public function set($key, $val, $tags = array())
{
foreach ($tags as $t) {
if (!isset($this->tags[$t])) {
$this->tags[$t] = array();
}
$this->tags[$t][$key] = true;
}
$this->cache[$key] = array($val, $tags);
}
private $hit = 0;
private $miss = 0;
public function stats()
{
return $this->miss ? ($this->hit / $this->miss) : 0;
}
public function get($key)
{
list($hit, $result) = $this->checkAndGet($key);
return $hit ? $result : false;
}
/**
* Checks for a cache hit and returns the value as a multi-value return
*
* @param string $key
* @return array First element boolean, isHit. Second element the actual result.
*/
public function checkAndGet($key)
{
if (array_key_exists($key, $this->cache)) {
++$this->hit;
return array(true, $this->cache[$key][0]);
} else {
++$this->miss;
return array(false, null);
}
}
public function clean($tag = null)
{
if ($tag) {
if (isset($this->tags[$tag])) {
foreach ($this->tags[$tag] as $k => $dud) {
// Remove the key from everywhere else it is tagged
$ts = $this->cache[$k][1];
foreach ($ts as $t) {
unset($this->tags[$t][$k]);
}
unset($this->cache[$k]);
}
unset($this->tags[$tag]);
}
} else {
$this->cache = array();
$this->tags = array();
}
}
}

View File

@ -2,6 +2,8 @@
namespace SilverStripe\Core\Config;
use SilverStripe\Dev\Deprecation;
/**
* Provides extensions to this object to integrate it with standard config API methods.
*
@ -17,29 +19,19 @@ trait Configurable
*/
public static function config()
{
return Config::inst()->forClass(get_called_class());
return Config::forClass(get_called_class());
}
/**
* Gets the first set value for the given config option
* Get inherited config value
*
* @param string $name
* @return mixed
*/
public function stat($name)
{
return Config::inst()->get(get_class($this), $name, Config::FIRST_SET);
}
/**
* Update the config value for a given property
*
* @param string $name
* @param mixed $value
*/
public function set_stat($name, $value)
{
Config::inst()->update(get_class($this), $name, $value);
Deprecation::notice('5.0', 'Use ->get');
return $this->config()->get($name);
}
/**
@ -50,6 +42,20 @@ trait Configurable
*/
public function uninherited($name)
{
return Config::inst()->get(get_class($this), $name, Config::UNINHERITED);
return $this->config()->uninherited($name);
}
/**
* Update the config value for a given property
*
* @param string $name
* @param mixed $value
* @return $this
*/
public function set_stat($name, $value)
{
Deprecation::notice('5.0', 'Use ->config()->set()');
$this->config()->set($name, $value);
return $this;
}
}

View File

@ -0,0 +1,171 @@
<?php
namespace SilverStripe\Core\Config;
use SilverStripe\Config\Collections\CachedConfigCollection;
use SilverStripe\Config\Collections\MemoryConfigCollection;
use SilverStripe\Config\Transformer\PrivateStaticTransformer;
use SilverStripe\Config\Transformer\YamlTransformer;
use SilverStripe\Control\Director;
use SilverStripe\Core\Config\Middleware\ExtensionMiddleware;
use SilverStripe\Core\Config\Middleware\InheritanceMiddleware;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Manifest\ModuleLoader;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Finder\Finder;
/**
* Factory for silverstripe configs
*/
class CoreConfigFactory
{
/**
* @var static
*/
protected static $inst = null;
/**
* @return static
*/
public static function inst()
{
if (!self::$inst) {
self::$inst = new static();
}
return self::$inst;
}
/**
* Create root application config.
* This will be an immutable cached config,
* which conditionally generates a nested "core" config.
*
* @param bool $flush
* @return CachedConfigCollection
*/
public function createRoot($flush)
{
$instance = new CachedConfigCollection();
// Set root cache
$instance->setPool(new FilesystemAdapter('configcache', 0, getTempFolder()));
$instance->setFlush($flush);
// Set collection creator
$instance->setCollectionCreator(function () {
return $this->createCore();
});
return $instance;
}
/**
* Rebuild new uncached config, which is mutable
*
* @return MemoryConfigCollection
*/
public function createCore()
{
$config = new MemoryConfigCollection();
// Set default middleware
$config->setMiddlewares([
new InheritanceMiddleware(Config::UNINHERITED),
new ExtensionMiddleware(Config::EXCLUDE_EXTRA_SOURCES),
]);
// Transform
$config->transform([
$this->buildStaticTransformer(),
$this->buildYamlTransformer()
]);
return $config;
}
/**
* @return YamlTransformer
*/
protected function buildYamlTransformer()
{
// Get all module dirs
$modules = ModuleLoader::instance()->getManifest()->getModules();
$dirs = [];
foreach ($modules as $module) {
// Load from _config dirs
$path = $module->getPath() . '/_config';
if (is_dir($path)) {
$dirs[] = $path;
}
}
return $this->buildYamlTransformerForPath($dirs);
}
/**
* @return PrivateStaticTransformer
*/
public function buildStaticTransformer()
{
return new PrivateStaticTransformer(function () {
$classes = ClassLoader::instance()->getManifest()->getClasses();
return array_keys($classes);
});
}
/**
* @param array|string $dirs Base dir to load from
* @return YamlTransformer
*/
public function buildYamlTransformerForPath($dirs)
{
// Construct
$transformer = YamlTransformer::create(
BASE_PATH,
Finder::create()
->in($dirs)
->files()
->name('/\.(yml|yaml)$/')
);
// Add default rules
$envvarset = function ($var, $value = null) {
if (getenv($var) === false) {
return false;
}
if ($value) {
return getenv($var) === $value;
}
return true;
};
$constantdefined = function ($const, $value = null) {
if (!defined($const)) {
return false;
}
if ($value) {
return constant($const) === $value;
}
return true;
};
return $transformer
->addRule('classexists', function ($class) {
return class_exists($class);
})
->addRule('envvarset', $envvarset)
->addRule('constantdefined', $constantdefined)
->addRule(
'envorconstant',
// Composite rule
function ($name, $value = null) use ($envvarset, $constantdefined) {
return $envvarset($name, $value) || $constantdefined($name, $value);
}
)
->addRule('environment', function ($env) {
$current = Director::get_environment_type();
return strtolower($current) === strtolower($env);
})
->addRule('moduleexists', function ($module) {
return ModuleLoader::instance()->getManifest()->moduleExists($module);
});
}
}

View File

@ -1,115 +0,0 @@
<?php
namespace SilverStripe\Core\Config;
use Exception;
use IteratorAggregate;
/**
* A Directed Acyclic Graph - used for doing topological sorts on dependencies, such as the before/after conditions
* in config yaml fragments
*/
class DAG implements IteratorAggregate
{
/**
* The nodes/vertices in the graph. Should be a numeric sequence of items (no string keys, no gaps).
* @var array|null
*/
protected $data;
/**
* The edges in the graph, in $to_idx => [$from_idx1, $from_idx2, ...] format
* @var array
*/
protected $dag;
public function __construct($data = null)
{
$data = $data ? array_values($data) : array();
$this->data = $data;
$this->dag = array_fill_keys(array_keys($data), array());
}
/**
* Add another node/vertex
* @param mixed $item The item to add to the graph
*/
public function additem($item)
{
$this->data[] = $item;
$this->dag[] = array();
}
/**
* Add an edge from one vertex to another.
*
* When passing actual nodes (as opposed to indexes), uses array_search with strict = true to find
*
* @param int $from The index in $data of the node/vertex, or the node/vertex
* itself, that the edge goes from
* @param int $to The index in $data of the node/vertex, or the node/vertex
* itself, that the edge goes to
* @throws Exception
*/
public function addedge($from, $to)
{
$i = is_numeric($from) ? $from : array_search($from, $this->data, true);
$j = is_numeric($to) ? $to : array_search($to, $this->data, true);
if ($i === false) {
throw new Exception("Couldnt find 'from' item in data when adding edge to DAG");
}
if ($j === false) {
throw new Exception("Couldnt find 'to' item in data when adding edge to DAG");
}
if (!isset($this->dag[$j])) {
$this->dag[$j] = array();
}
$this->dag[$j][] = $i;
}
/**
* Sort graph so that each node (a) comes before any nodes (b) where an edge exists from a to b
* @return array - The nodes
* @throws Exception - If the graph is cyclic (and so can't be sorted)
*/
public function sort()
{
$data = $this->data;
$dag = $this->dag;
$sorted = array();
while (true) {
$withedges = array_filter($dag, 'count');
$starts = array_diff_key($dag, $withedges);
if (!count($starts)) {
break;
}
foreach ($starts as $i => $foo) {
$sorted[] = $data[$i];
}
foreach ($withedges as $j => $deps) {
$withedges[$j] = array_diff($withedges[$j], array_keys($starts));
}
$dag = $withedges;
}
if ($dag) {
$remainder = new DAG($data);
$remainder->dag = $dag;
throw new DAG_CyclicException("DAG has cyclic requirements", $remainder);
}
return $sorted;
}
public function getIterator()
{
return new DAG_Iterator($this->data, $this->dag);
}
}

View File

@ -1,25 +0,0 @@
<?php
namespace SilverStripe\Core\Config;
use Exception;
/**
* Exception thrown when the {@link SilverStripe\Core\Config\DAG} class is unable to resolve sorting the DAG due
* to cyclic dependencies.
*/
class DAG_CyclicException extends Exception
{
public $dag;
/**
* @param string $message The Exception message
* @param DAG $dag The remainder of the Directed Acyclic Graph (DAG) after the last successful sort
*/
public function __construct($message, $dag)
{
$this->dag = $dag;
parent::__construct($message);
}
}

View File

@ -1,57 +0,0 @@
<?php
namespace SilverStripe\Core\Config;
use Iterator;
class DAG_Iterator implements Iterator
{
protected $data;
protected $dag;
protected $dagkeys;
protected $i;
public function __construct($data, $dag)
{
$this->data = $data;
$this->dag = $dag;
$this->rewind();
}
public function key()
{
return $this->i;
}
public function current()
{
$res = array();
$res['from'] = $this->data[$this->i];
$res['to'] = array();
foreach ($this->dag[$this->i] as $to) {
$res['to'][] = $this->data[$to];
}
return $res;
}
public function next()
{
$this->i = array_shift($this->dagkeys);
}
public function rewind()
{
$this->dagkeys = array_keys($this->dag);
$this->next();
}
public function valid()
{
return $this->i !== null;
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace SilverStripe\Core\Config\Middleware;
use Generator;
use InvalidArgumentException;
use SilverStripe\Config\MergeStrategy\Priority;
use SilverStripe\Config\Middleware\Middleware;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Object;
class ExtensionMiddleware implements Middleware
{
use MiddlewareCommon;
/**
* Get config for a class
*
* @param string $class Name of class
* @param int|true $excludeMiddleware Middleware disable flags
* @param callable $next Callback to next middleware
* @return array Complete class config
*/
public function getClassConfig($class, $excludeMiddleware, $next)
{
// Get base config
$config = $next($class, $excludeMiddleware);
if (!$this->enabled($excludeMiddleware)) {
return $config;
}
foreach ($this->getExtraConfig($class, $config) as $extra) {
$config = Priority::mergeArray($extra, $config);
}
return $config;
}
/**
* Applied config to a class from its extensions
*
* @param string $class
* @param array $classConfig
* @return Generator
*/
protected function getExtraConfig($class, $classConfig)
{
if (empty($classConfig['extensions'])) {
return;
}
$extensions = $classConfig['extensions'];
foreach ($extensions as $extension) {
list($extensionClass, $extensionArgs) = Object::parse_class_spec($extension);
if (!class_exists($extensionClass)) {
throw new InvalidArgumentException("$class references nonexistent $extensionClass in 'extensions'");
}
// Init extension
call_user_func(array($extensionClass, 'add_to_class'), $class, $extensionClass, $extensionArgs);
// Check class hierarchy from root up
foreach (ClassInfo::ancestry($extensionClass) as $extensionClassParent) {
// Merge config from extension
$extensionConfig = Config::inst()->get($extensionClassParent, null, true);
if ($extensionConfig) {
yield $extensionConfig;
}
if (ClassInfo::has_method_from($extensionClassParent, 'get_extra_config', $extensionClassParent)) {
$extensionConfig = call_user_func(
[ $extensionClassParent, 'get_extra_config' ],
$class,
$extensionClass,
$extensionArgs
);
if ($extensionConfig) {
yield $extensionConfig;
}
}
}
}
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace SilverStripe\Core\Config\Middleware;
use SilverStripe\Config\MergeStrategy\Priority;
use SilverStripe\Config\Middleware\Middleware;
use SilverStripe\Core\ClassInfo;
class InheritanceMiddleware implements Middleware
{
use MiddlewareCommon;
/**
* Get config for a class
*
* @param string $class Name of class
* @param int|true $excludeMiddleware Middleware disable flags
* @param callable $next Callback to next middleware
* @return array Complete class config
*/
public function getClassConfig($class, $excludeMiddleware, $next)
{
// Check if enabled
if (!$this->enabled($excludeMiddleware)) {
return $next($class, $excludeMiddleware);
}
// Merge hierarchy
$config = [];
foreach (ClassInfo::ancestry($class) as $nextClass) {
$nextConfig = $next($nextClass, $excludeMiddleware);
$config = Priority::mergeArray($nextConfig, $config);
}
return $config;
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace SilverStripe\Core\Config\Middleware;
/**
* Abstract flag-aware middleware
*/
trait MiddlewareCommon
{
/**
* Disable flag
*
* @var int
*/
protected $disableFlag = 0;
public function __construct($disableFlag = 0)
{
$this->disableFlag = $disableFlag;
}
/**
* Check if this middlware is enabled
*
* @param int|true $excludeMiddleware
* @return bool
*/
protected function enabled($excludeMiddleware)
{
if ($excludeMiddleware === true) {
return false;
}
if (!$this->disableFlag) {
return true;
}
return ($excludeMiddleware & $this->disableFlag) !== $this->disableFlag;
}
public function serialize()
{
return json_encode([$this->disableFlag]);
}
public function unserialize($serialized)
{
list($this->disableFlag) = json_decode($serialized, true);
}
}

View File

@ -1,13 +1,15 @@
<?php
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\CoreConfigFactory;
use SilverStripe\Core\Config\ConfigLoader;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Injector\SilverStripeServiceConfigurationLocator;
use SilverStripe\Core\Manifest\ClassManifest;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Manifest\ConfigStaticManifest;
use SilverStripe\Core\Manifest\ConfigManifest;
use SilverStripe\Control\Director;
use SilverStripe\Dev\Deprecation;
use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\Core\Manifest\ModuleManifest;
use SilverStripe\i18n\i18n;
/**
@ -56,7 +58,7 @@ gc_enable();
// Initialise the dependency injector as soon as possible, as it is
// subsequently used by some of the following code
$injector = new Injector(array('locator' => 'SilverStripe\\Core\\Injector\\SilverStripeServiceConfigurationLocator'));
$injector = new Injector(array('locator' => SilverStripeServiceConfigurationLocator::class));
Injector::set_inst($injector);
///////////////////////////////////////////////////////////////////////////////
@ -76,13 +78,16 @@ $loader = ClassLoader::instance();
$loader->registerAutoloader();
$loader->pushManifest($manifest);
// Now that the class manifest is up, load the static configuration
$configManifest = new ConfigStaticManifest();
Config::inst()->pushConfigStaticManifest($configManifest);
// Init module manifest
$moduleManifest = new ModuleManifest(BASE_PATH, false, $flush);
ModuleLoader::instance()->pushManifest($moduleManifest);
// And then the yaml configuration
$configManifest = new ConfigManifest(BASE_PATH, false, $flush);
Config::inst()->pushConfigYamlManifest($configManifest);
// Build config manifest
$configManifest = CoreConfigFactory::inst()->createRoot($flush);
ConfigLoader::instance()->pushManifest($configManifest);
// After loading config, boot _config.php files
ModuleLoader::instance()->getManifest()->activateConfig();
// Load template manifest
SilverStripe\View\ThemeResourceLoader::instance()->addSet('$default', new SilverStripe\View\ThemeManifest(
@ -103,7 +108,6 @@ if (Director::isLive()) {
/**
* Load error handlers
*/
$errorHandler = Injector::inst()->get('ErrorHandler');
$errorHandler->start();

View File

@ -122,11 +122,7 @@ trait Extensible
if (in_array($class, self::$unextendable_classes)) {
continue;
}
$extensions = Config::inst()->get(
$class,
'extensions',
Config::UNINHERITED | Config::EXCLUDE_EXTRA_SOURCES
);
$extensions = Config::inst()->get($class, 'extensions', true);
if ($extensions) {
foreach ($extensions as $extension) {
@ -217,8 +213,10 @@ trait Extensible
}
}
Config::inst()->update($class, 'extensions', array($extension));
Config::inst()->extraConfigSourcesChanged($class);
Config::modify()
->merge($class, 'extensions', array(
$extension
));
Injector::inst()->unregisterNamedObject($class);
@ -234,6 +232,8 @@ trait Extensible
/**
* Remove an extension from a class.
* Note: This will not remove extensions from parent classes, and must be called
* directly on the class assigned the extension.
*
* Keep in mind that this won't revert any datamodel additions
* of the extension at runtime, unless its used before the
@ -252,22 +252,23 @@ trait Extensible
{
$class = get_called_class();
Config::inst()->remove($class, 'extensions', Config::anything(), $extension);
// remove any instances of the extension with parameters
$config = Config::inst()->get($class, 'extensions');
if ($config) {
foreach ($config as $k => $v) {
// extensions with parameters will be stored in config as
// ExtensionName("Param").
if (preg_match(sprintf("/^(%s)\(/", preg_quote($extension, '/')), $v)) {
Config::inst()->remove($class, 'extensions', Config::anything(), $v);
}
// Build filtered extension list
$found = false;
$config = Config::inst()->get($class, 'extensions', true) ?: [];
foreach ($config as $key => $candidate) {
// extensions with parameters will be stored in config as ExtensionName("Param").
if (strcasecmp($candidate, $extension) === 0 ||
stripos($candidate, $extension.'(') === 0
) {
$found = true;
unset($config[$key]);
}
}
Config::inst()->extraConfigSourcesChanged($class);
// Don't dirty cache if no changes
if (!$found) {
return;
}
Config::modify()->set($class, 'extensions', $config);
// unset singletons to avoid side-effects
Injector::inst()->unregisterAllObjects();
@ -292,7 +293,7 @@ trait Extensible
*/
public static function get_extensions($class, $includeArgumentString = false)
{
$extensions = Config::inst()->get($class, 'extensions');
$extensions = Config::forClass($class)->get('extensions', Config::EXCLUDE_EXTRA_SOURCES);
if (empty($extensions)) {
return array();
}
@ -329,7 +330,7 @@ trait Extensible
$sources = null;
// Get a list of extensions
$extensions = Config::inst()->get($class, 'extensions', Config::UNINHERITED | Config::EXCLUDE_EXTRA_SOURCES);
$extensions = Config::inst()->get($class, 'extensions', true);
if (!$extensions) {
return null;

View File

@ -21,7 +21,7 @@ abstract class Extension
* This is used by extensions designed to be applied to controllers.
* It works the same way as {@link Controller::$allowed_actions}.
*/
private static $allowed_actions = null;
private static $allowed_actions = [];
/**
* The object this extension is applied to.

View File

@ -386,8 +386,8 @@ class ClassManifest
*/
protected function setDefaults()
{
$this->classes['sstemplateparser'] = FRAMEWORK_PATH.'/View/SSTemplateParser.php';
$this->classes['sstemplateparseexception'] = FRAMEWORK_PATH.'/View/SSTemplateParseException.php';
$this->classes['sstemplateparser'] = FRAMEWORK_PATH.'/src/View/SSTemplateParser.php';
$this->classes['sstemplateparseexception'] = FRAMEWORK_PATH.'/src/View/SSTemplateParseException.php';
}
/**

View File

@ -1,763 +0,0 @@
<?php
namespace SilverStripe\Core\Manifest;
use SilverStripe\Control\Director;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\DAG;
use SilverStripe\Core\Config\DAG_CyclicException;
use SilverStripe\Core\Config\Config;
use Symfony\Component\Cache\Simple\FilesystemCache;
use Symfony\Component\Yaml\Parser;
use Traversable;
/**
* A utility class which builds a manifest of configuration items
*/
class ConfigManifest
{
/** @var string - The base path used when building the manifest */
protected $base;
/** @var string - A string to prepend to all cache keys to ensure all keys are unique to just this $base */
protected $key;
/** @var bool - Whether `test` directories should be searched when searching for configuration */
protected $includeTests;
/**
* @var FilesystemCache
*/
protected $cache;
/**
* All the values needed to be collected to determine the correct combination of fragements for
* the current environment.
* @var array
*/
protected $variantKeySpec = false;
/**
* All the _config.php files. Need to be included every request & can't be cached. Not variant specific.
* @var array
*/
protected $phpConfigSources = array();
/**
* All the _config/*.yml fragments pre-parsed and sorted in ascending include order. Not variant specific.
* @var array
*/
protected $yamlConfigFragments = array();
/**
* The calculated config from _config/*.yml, sorted, filtered and merged. Variant specific.
* @var array
*/
public $yamlConfig = array();
/**
* The variant key state as when yamlConfig was loaded
* @var string
*/
protected $yamlConfigVariantKey = null;
/**
* @var [callback] A list of callbacks to be called whenever the content of yamlConfig changes
*/
protected $configChangeCallbacks = array();
/**
* A side-effect of collecting the _config fragments is the calculation of all module directories, since
* the definition of a module is "a directory that contains either a _config.php file or a _config directory
* @var array
*/
public $modules = array();
/**
* Adds a path as a module
*
* @param string $path
*/
public function addModule($path)
{
$module = basename($path);
if (isset($this->modules[$module]) && $this->modules[$module] != $path) {
user_error("Module ".$module." in two places - ".$path." and ".$this->modules[$module]);
}
$this->modules[$module] = $path;
}
/**
* Returns true if the passed module exists
*
* @param string $module
* @return bool
*/
public function moduleExists($module)
{
return array_key_exists($module, $this->modules);
}
/**
* Constructs and initialises a new configuration object, either loading
* from the cache or re-scanning for classes.
*
* @param string $base The project base path.
* @param bool $includeTests
* @param bool $forceRegen Force the manifest to be regenerated.
*/
public function __construct($base, $includeTests = false, $forceRegen = false)
{
$this->base = $base;
$this->key = sha1($base).'_';
$this->includeTests = $includeTests;
// Get a cache singleton
$this->cache = $this->getCache();
// Unless we're forcing regen, try loading from cache
if (!$forceRegen) {
// The PHP config sources are always needed
$this->phpConfigSources = $this->cache->get($this->key.'php_config_sources');
// Get the variant key spec
$this->variantKeySpec = $this->cache->get($this->key.'variant_key_spec');
}
// 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->phpConfigSources || false === $this->variantKeySpec) {
$this->regenerate($includeTests);
}
// At this point $this->variantKeySpec will always contain something valid, so we can build the variant
$this->buildYamlConfigVariant();
}
/**
* Provides a hook for mock unit tests despite no DI
* @return \Psr\SimpleCache\CacheInterface
*/
protected function getCache()
{
// TODO Replace with CoreConfigCreator, see https://github.com/silverstripe/silverstripe-framework/pull/6641/files#diff-f8c9b17e06432278197a7d5c3a1043cb
return new FilesystemCache('SS_Configuration', 0, getTempFolder());
}
/**
* Register a callback to be called whenever the calculated merged config changes
*
* In some situations the merged config can change - for instance, code in _config.php can cause which Only
* and Except fragments match. Registering a callback with this function allows code to be called when
* this happens.
*
* @param callback $callback
*/
public function registerChangeCallback($callback)
{
$this->configChangeCallbacks[] = $callback;
}
/**
* Includes all of the php _config.php files found by this manifest. Called by SS_Config when adding this manifest
* @return void
*/
public function activateConfig()
{
foreach ($this->phpConfigSources as $config) {
require_once $config;
}
if ($this->variantKey() != $this->yamlConfigVariantKey) {
$this->buildYamlConfigVariant();
}
}
/**
* Gets the (merged) config value for the given class and config property name
*
* @param string $class - The class to get the config property value for
* @param string $name - The config property to get the value for
* @param mixed $default - What to return if no value was contained in any YAML file for the passed $class and $name
* @return mixed The merged set of all values contained in all the YAML configuration files for the passed
* $class and $name, or $default if there are none
*/
public function get($class, $name, $default = null)
{
if (isset($this->yamlConfig[$class][$name])) {
return $this->yamlConfig[$class][$name];
}
return $default;
}
/**
* Returns the string that uniquely identifies this variant. The variant is the combination of classes, modules,
* environment, environment variables and constants that selects which yaml fragments actually make it into the
* configuration because of "only"
* and "except" rules.
*
* @return string
*/
public function variantKey()
{
$key = $this->variantKeySpec; // Copy to fill in actual values
if (isset($key['environment'])) {
$key['environment'] = Director::isDev() ? 'dev' : (Director::isTest() ? 'test' : 'live');
}
if (isset($key['envvars'])) {
foreach ($key['envvars'] as $variable => $foo) {
$key['envvars'][$variable] = getenv($variable) ?: null;
}
}
if (isset($key['constants'])) {
foreach ($key['constants'] as $variable => $foo) {
$key['constants'][$variable] = defined($variable) ? constant($variable) : null;
}
}
return sha1(serialize($key));
}
/**
* Completely regenerates the manifest file. Scans through finding all php _config.php and yaml _config/*.ya?ml
* files,parses the yaml files into fragments, sorts them and figures out what values need to be checked to pick
* the correct variant.
*
* Does _not_ build the actual variant
*
* @param bool $includeTests
* @param bool $cache Cache the result.
*/
public function regenerate($includeTests = false, $cache = true)
{
$this->phpConfigSources = array();
$this->yamlConfigFragments = array();
$this->variantKeySpec = array();
$finder = new ManifestFileFinder();
$finder->setOptions(array(
'min_depth' => 0,
'name_regex' => '/(^|[\/\\\\])_config.php$/',
'ignore_tests' => !$includeTests,
'file_callback' => array($this, 'addSourceConfigFile'),
// Cannot be max_depth: 1 due to "/framework/admin/_config.php"
'max_depth' => 2
));
$finder->find($this->base);
$finder = new ManifestFileFinder();
$finder->setOptions(array(
'name_regex' => '/\.ya?ml$/',
'ignore_tests' => !$includeTests,
'file_callback' => array($this, 'addYAMLConfigFile'),
'max_depth' => 2
));
$finder->find($this->base);
$this->prefilterYamlFragments();
$this->sortYamlFragments();
$this->buildVariantKeySpec();
if ($cache) {
$this->cache->set($this->key.'php_config_sources', $this->phpConfigSources);
$this->cache->set($this->key.'yaml_config_fragments', $this->yamlConfigFragments);
$this->cache->set($this->key.'variant_key_spec', $this->variantKeySpec);
}
}
/**
* Handle finding a php file. We just keep a record of all php files found, we don't include them
* at this stage
*
* Public so that ManifestFileFinder can call it. Not for general use.
*
* @param string $basename
* @param string $pathname
* @param int $depth
*/
public function addSourceConfigFile($basename, $pathname, $depth)
{
$this->phpConfigSources[] = $pathname;
// Add this module too
$this->addModule(dirname($pathname));
}
/**
* Handle finding a yml file. Parse the file by spliting it into header/fragment pairs,
* and normalising some of the header values (especially: give anonymous name if none assigned,
* splt/complete before and after matchers)
*
* Public so that ManifestFileFinder can call it. Not for general use.
*
* @param string $basename
* @param string $pathname
* @param int $depth
*/
public function addYAMLConfigFile($basename, $pathname, $depth)
{
if (!preg_match('{/([^/]+)/_config/}', $pathname, $match)) {
return;
}
// Keep track of all the modules we've seen
$this->addModule(dirname(dirname($pathname)));
$parser = new Parser();
// The base header
$base = array(
'module' => $match[1],
'file' => basename(basename($basename, '.yml'), '.yaml')
);
// Make sure the linefeeds are all converted to \n, PCRE '$' will not match anything else.
$fileContents = str_replace(array("\r\n", "\r"), "\n", file_get_contents($pathname));
// YAML parsers really should handle this properly themselves, but neither spyc nor symfony-yaml do. So we
// follow in their vein and just do what we need, not what the spec says
$parts = preg_split('/^---$/m', $fileContents, -1, PREG_SPLIT_NO_EMPTY);
// If only one document, it's a headerless fragment. So just add it with an anonymous name
if (count($parts) == 1) {
$this->yamlConfigFragments[] = $base + array(
'name' => 'anonymous-1',
'fragment' => $parser->parse($parts[0])
);
} // Otherwise it's a set of header/document pairs
else {
// If we got an odd number of parts the config file doesn't have a header for every document
if (count($parts) % 2 != 0) {
user_error("Configuration file '$pathname' does not have an equal number of headers and config blocks");
}
// Step through each pair
for ($i = 0; $i < count($parts); $i+=2) {
// Make all the first-level keys of the header lower case
$header = array_change_key_case($parser->parse($parts[$i]), CASE_LOWER);
// Assign a name if non assigned already
if (!isset($header['name'])) {
$header['name'] = 'anonymous-'.(1+$i/2);
}
// Parse & normalise the before and after if present
foreach (array('before', 'after') as $order) {
if (isset($header[$order])) {
// First, splice into parts (multiple before or after parts are allowed, comma separated)
if (is_array($header[$order])) {
$orderparts = $header[$order];
} else {
$orderparts = preg_split('/\s*,\s*/', $header[$order], -1, PREG_SPLIT_NO_EMPTY);
}
// For each, parse out into module/file#name, and set any missing to "*"
$header[$order] = array();
foreach ($orderparts as $part) {
preg_match(
'! (?P<module>\*|[^\/#]+)? (\/ (?P<file>\*|\w+))? (\# (?P<fragment>\*|\w+))? !x',
$part,
$match
);
$header[$order][] = array(
'module' => isset($match['module']) && $match['module'] ? $match['module'] : '*',
'file' => isset($match['file']) && $match['file'] ? $match['file'] : '*',
'name' => isset($match['fragment']) && $match['fragment'] ? $match['fragment'] : '*'
);
}
}
}
// And add to the fragments list
$this->yamlConfigFragments[] = $base + $header + array(
'fragment' => $parser->parse($parts[$i+1])
);
}
}
}
/**
* Sorts the YAML fragments so that the "before" and "after" rules are met.
* Throws an error if there's a loop
*
* We can't use regular sorts here - we need a topological sort. Easiest
* way is with a DAG, so build up a DAG based on the before/after rules, then
* sort that.
*
* @return void
*/
protected function sortYamlFragments()
{
$frags = $this->yamlConfigFragments;
// Build a directed graph
$dag = new DAG($frags);
foreach ($frags as $i => $frag) {
foreach ($frags as $j => $otherfrag) {
if ($i == $j) {
continue;
}
$order = $this->relativeOrder($frag, $otherfrag);
if ($order == 'before') {
$dag->addedge($i, $j);
} elseif ($order == 'after') {
$dag->addedge($j, $i);
}
}
}
try {
$this->yamlConfigFragments = $dag->sort();
} catch (DAG_CyclicException $e) {
if (!Director::isLive() && isset($_REQUEST['debug'])) {
$res = '<h1>Remaining config fragment graph</h1>';
$res .= '<dl>';
foreach ($e->dag as $node) {
$res .= "<dt>{$node['from']['module']}/{$node['from']['file']}#{$node['from']['name']}"
. " marked to come after</dt><dd><ul>";
foreach ($node['to'] as $to) {
$res .= "<li>{$to['module']}/{$to['file']}#{$to['name']}</li>";
}
$res .= "</ul></dd>";
}
$res .= '</dl>';
echo $res;
}
user_error(
'Based on their before & after rules two fragments both need to be before/after each other',
E_USER_ERROR
);
}
}
/**
* Return a string "after", "before" or "undefined" depending on whether the YAML fragment array element passed
* as $a should be positioned after, before, or either compared to the YAML fragment array element passed as $b
*
* @param array $a A YAML config fragment as loaded by addYAMLConfigFile
* @param array $b A YAML config fragment as loaded by addYAMLConfigFile
* @return string "after", "before" or "undefined"
*/
protected function relativeOrder($a, $b)
{
$matches = array();
// Do the same thing for after and before
foreach (array('before', 'after') as $rulename) {
$matches[$rulename] = array();
// Figure out for each rule, which part matches
if (isset($a[$rulename])) {
foreach ($a[$rulename] as $rule) {
$match = array();
foreach (array('module', 'file', 'name') as $part) {
// If part is *, we match _unless_ the opposite rule has a non-* matcher than also matches $b
if ($rule[$part] == '*') {
$match[$part] = 'wild';
} else {
$match[$part] = ($rule[$part] == $b[$part]);
}
}
$matches[$rulename][] = $match;
}
}
}
// Figure out the specificness of each match. 1 an actual match, 0 for a wildcard match, remove if no match
$matchlevel = array('before' => -1, 'after' => -1);
foreach (array('before', 'after') as $rulename) {
foreach ($matches[$rulename] as $i => $rule) {
$level = 0;
foreach ($rule as $part => $partmatches) {
if ($partmatches === false) {
continue 2;
}
if ($partmatches === true) {
$level += 1;
}
}
if ($matchlevel[$rulename] === false || $level > $matchlevel[$rulename]) {
$matchlevel[$rulename] = $level;
}
}
}
if ($matchlevel['before'] === -1 && $matchlevel['after'] === -1) {
return 'undefined';
} elseif ($matchlevel['before'] === $matchlevel['after']) {
user_error('Config fragment requires itself to be both before _and_ after another fragment', E_USER_ERROR);
} else {
return ($matchlevel['before'] > $matchlevel['after']) ? 'before' : 'after';
}
}
/**
* This function filters the loaded yaml fragments, removing any that can't ever have their "only" and "except"
* rules match.
*
* Some tests in "only" and "except" rules need to be checked per request, but some are manifest based -
* these are invariant over requests and only need checking on manifest rebuild. So we can prefilter these before
* saving yamlConfigFragments to speed up the process of checking the per-request variant/
*/
public function prefilterYamlFragments()
{
$matchingFragments = array();
foreach ($this->yamlConfigFragments as $i => $fragment) {
$matches = true;
if (isset($fragment['only'])) {
$matches = $matches && ($this->matchesPrefilterVariantRules($fragment['only']) !== false);
}
if (isset($fragment['except'])) {
$matches = $matches && ($this->matchesPrefilterVariantRules($fragment['except']) !== true);
}
if ($matches) {
$matchingFragments[] = $fragment;
}
}
$this->yamlConfigFragments = $matchingFragments;
}
/**
* Returns false if the prefilterable parts of the rule aren't met, and true if they are
*
* @param $rules array - A hash of rules as allowed in the only or except portion of a config fragment header
* @return bool - True if the rules are met, false if not. (Note that depending on whether we were passed an
* only or an except rule,
* which values means accept or reject a fragment
*/
public function matchesPrefilterVariantRules($rules)
{
$matches = "undefined"; // Needs to be truthy, but not true
foreach ($rules as $k => $v) {
switch (strtolower($k)) {
case 'classexists':
$matches = $matches && ClassInfo::exists($v);
break;
case 'moduleexists':
$matches = $matches && $this->moduleExists($v);
break;
default:
// NOP
}
if ($matches === false) {
return $matches;
}
}
return $matches;
}
/**
* Builds the variant key spec - the list of values that need to be build to give a key that uniquely identifies
* this variant.
*/
public function buildVariantKeySpec()
{
$this->variantKeySpec = array();
foreach ($this->yamlConfigFragments as $fragment) {
if (isset($fragment['only'])) {
$this->addVariantKeySpecRules($fragment['only']);
}
if (isset($fragment['except'])) {
$this->addVariantKeySpecRules($fragment['except']);
}
}
}
/**
* Adds any variables referenced in the passed rules to the $this->variantKeySpec array
*
* @param array $rules
*/
public function addVariantKeySpecRules($rules)
{
foreach ($rules as $k => $v) {
switch (strtolower($k)) {
case 'classexists':
case 'moduleexists':
// Classes and modules are a special case - we can pre-filter on config regenerate because we
// already know if the class or module exists
break;
case 'environment':
$this->variantKeySpec['environment'] = true;
break;
case 'envvarset':
if (!isset($this->variantKeySpec['envvars'])) {
$this->variantKeySpec['envvars'] = array();
}
$this->variantKeySpec['envvars'][$k] = $k;
break;
case 'constantdefined':
if (!isset($this->variantKeySpec['constants'])) {
$this->variantKeySpec['constants'] = array();
}
$this->variantKeySpec['constants'][$k] = $k;
break;
default:
if (!isset($this->variantKeySpec['envvars'])) {
$this->variantKeySpec['envvars'] = array();
}
if (!isset($this->variantKeySpec['constants'])) {
$this->variantKeySpec['constants'] = array();
}
$this->variantKeySpec['envvars'][$k] = $this->variantKeySpec['constants'][$k] = $k;
}
}
}
/**
* Calculates which yaml config fragments are applicable in this variant, and merge those all together into
* the $this->yamlConfig propperty
*
* Checks cache and takes care of loading yamlConfigFragments if they aren't already present, but expects
* $variantKeySpec to already be set
*
* @param bool $cache
*/
public function buildYamlConfigVariant($cache = true)
{
// Only try loading from cache if we don't have the fragments already loaded, as there's no way to know if a
// given variant is stale compared to the complete set of fragments
if (!$this->yamlConfigFragments) {
// First try and just load the exact variant
if ($this->yamlConfig = $this->cache->get($this->key.'yaml_config_'.$this->variantKey())) {
$this->yamlConfigVariantKey = $this->variantKey();
return;
} // Otherwise try and load the fragments so we can build the variant
else {
$this->yamlConfigFragments = $this->cache->get($this->key.'yaml_config_fragments');
}
}
// If we still don't have any fragments we have to build them
if (!$this->yamlConfigFragments) {
$this->regenerate($this->includeTests, $cache);
}
$this->yamlConfig = array();
$this->yamlConfigVariantKey = $this->variantKey();
foreach ($this->yamlConfigFragments as $i => $fragment) {
$matches = true;
if (isset($fragment['only'])) {
$matches = $matches && ($this->matchesVariantRules($fragment['only']) !== false);
}
if (isset($fragment['except'])) {
$matches = $matches && ($this->matchesVariantRules($fragment['except']) !== true);
}
if ($matches) {
$this->mergeInYamlFragment($this->yamlConfig, $fragment['fragment']);
}
}
if ($cache) {
$this->cache->set($this->key.'yaml_config_'.$this->variantKey(), $this->yamlConfig);
}
// Since yamlConfig has changed, call any callbacks that are interested
foreach ($this->configChangeCallbacks as $callback) {
call_user_func($callback);
}
}
/**
* Returns false if the non-prefilterable parts of the rule aren't met, and true if they are
*
* @param array $rules
* @return bool|string
*/
public function matchesVariantRules($rules)
{
$matches = "undefined"; // Needs to be truthy, but not true
foreach ($rules as $k => $v) {
switch (strtolower($k)) {
case 'classexists':
case 'moduleexists':
break;
case 'environment':
switch (strtolower($v)) {
case 'live':
$matches = $matches && Director::isLive();
break;
case 'test':
$matches = $matches && Director::isTest();
break;
case 'dev':
$matches = $matches && Director::isDev();
break;
default:
user_error('Unknown environment '.$v.' in config fragment', E_USER_ERROR);
}
break;
case 'envvarset':
$matches = $matches && getenv($v);
break;
case 'constantdefined':
$matches = $matches && defined($v);
break;
default:
$matches = $matches && (
getenv($k) == $v ||
(defined($k) && constant($k) == $v)
);
break;
}
if ($matches === false) {
return $matches;
}
}
return $matches;
}
/**
* Recursively merge a yaml fragment's configuration array into the primary merged configuration array.
* @param $into
* @param $fragment
* @return void
*/
public function mergeInYamlFragment(&$into, $fragment)
{
if (is_array($fragment) || ($fragment instanceof Traversable)) {
foreach ($fragment as $k => $v) {
Config::merge_high_into_low($into[$k], $v);
}
}
}
}

View File

@ -1,35 +0,0 @@
<?php
namespace SilverStripe\Core\Manifest;
use ReflectionClass;
/**
* Allows access to config values set on classes using private statics.
*/
class ConfigStaticManifest
{
/**
* @param string $class
* @param string $name
* @return mixed
*/
public function get($class, $name)
{
if (class_exists($class)) {
// The config system is case-sensitive so we need to check the exact value
$reflection = new ReflectionClass($class);
if (strcmp($reflection->name, $class) === 0) {
if ($reflection->hasProperty($name)) {
$property = $reflection->getProperty($name);
if ($property->isStatic() && $property->isPrivate()) {
$property->setAccessible(true);
return $property->getValue();
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,151 @@
<?php
namespace SilverStripe\Core\Manifest;
use Exception;
use Serializable;
class Module implements Serializable
{
/**
* Directory
*
* @var string
*/
protected $path = null;
/**
* Base folder of application
*
* @var string
*/
protected $basePath = null;
/**
* Cache of composer data
*
* @var array
*/
protected $composerData = null;
public function __construct($path, $base)
{
$this->path = rtrim($path, '/\\');
$this->basePath = rtrim($base, '/\\');
$this->loadComposer();
}
/**
* Gets name of this module. Used as unique key and identifier for this module.
*
* If installed by composer, this will be the full composer name (vendor/name).
* If not insalled by composer this will default to the basedir()
*
* @return string
*/
public function getName()
{
return $this->getComposerName() ?: $this->getShortName();
}
/**
* Get full composer name. Will be null if no composer.json is available
*
* @return string|null
*/
public function getComposerName()
{
if (isset($this->composerData['name'])) {
return $this->composerData['name'];
}
return null;
}
/**
* Gets "short" name of this module. This is the base directory this module
* is installed in.
*
* If installed in root, this will be generated from the composer name instead
*
* @return string
*/
public function getShortName()
{
// If installed in the root directory we need to infer from composer
if ($this->path === $this->basePath && $this->composerData) {
// Sometimes we customise installer name
if (isset($this->composerData['extra']['installer-name'])) {
return $this->composerData['extra']['installer-name'];
}
// Strip from full composer name
$composerName = $this->getComposerName();
if ($composerName) {
list(, $name) = explode('/', $composerName);
return $name;
}
}
// Base name of directory
return basename($this->path);
}
/**
* Get base path for this module
*
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* Get path relative to base dir.
* If module path is base this will be empty string
*
* @return string
*/
public function getRelativePath()
{
return ltrim(substr($this->path, strlen($this->basePath)), '/\\');
}
public function serialize()
{
return json_encode([$this->path, $this->basePath, $this->composerData]);
}
public function unserialize($serialized)
{
list($this->path, $this->basePath, $this->composerData) = json_decode($serialized, true);
}
/**
* Activate _config.php for this module, if one exists
*/
public function activate()
{
$config = "{$this->path}/_config.php";
if (file_exists($config)) {
require_once $config;
}
}
/**
* @throws Exception
*/
protected function loadComposer()
{
// Load composer data
$path = "{$this->path}/composer.json";
if (file_exists($path)) {
$content = file_get_contents($path);
$result = json_decode($content, true);
if (json_last_error()) {
throw new Exception(json_last_error_msg());
}
$this->composerData = $result;
}
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace SilverStripe\Core\Manifest;
/**
* Module manifest holder
*/
class ModuleLoader
{
/**
* @var self
*/
private static $instance;
/**
* @var ModuleManifest[] Module manifests
*/
protected $manifests = array();
/**
* @return self
*/
public static function instance()
{
return self::$instance ? self::$instance : self::$instance = new self();
}
/**
* Returns the currently active class manifest instance that is used for
* loading classes.
*
* @return ModuleManifest
*/
public function getManifest()
{
return $this->manifests[count($this->manifests) - 1];
}
/**
* Returns true if this class loader has a manifest.
*
* @return bool
*/
public function hasManifest()
{
return (bool)$this->manifests;
}
/**
* Pushes a module manifest instance onto the top of the stack.
*
* @param ModuleManifest $manifest
*/
public function pushManifest(ModuleManifest $manifest)
{
$this->manifests[] = $manifest;
}
/**
* @return ModuleManifest
*/
public function popManifest()
{
return array_pop($this->manifests);
}
/**
* Check number of manifests
*
* @return int
*/
public function countManifests()
{
return count($this->manifests);
}
}

View File

@ -0,0 +1,230 @@
<?php
namespace SilverStripe\Core\Manifest;
use LogicException;
/**
* A utility class which builds a manifest of configuration items
*/
class ModuleManifest
{
/**
* The base path used when building the manifest
*
* @var string
*/
protected $base;
/**
* A string to prepend to all cache keys to ensure all keys are unique to just this $base
*
* @var string
*/
protected $cacheKey;
/**
* Whether `test` directories should be searched when searching for configuration
*
* @var bool
*/
protected $includeTests;
/**
* @var ManifestCache
*/
protected $cache;
/**
* List of all modules.
*
* @var Module[]
*/
protected $modules = array();
/**
* Adds a path as a module
*
* @param string $path
*/
public function addModule($path)
{
$module = new Module($path, $this->base);
$name = $module->getName();
// Save if not already added
if (empty($this->modules[$name])) {
$this->modules[$name] = $module;
return;
}
// Validate duplicate module
$path = $module->getPath();
$otherPath = $this->modules[$name]->getPath();
if ($otherPath !== $path) {
throw new LogicException(
"Module {$name} is in two places - {$path} and {$otherPath}"
);
}
}
/**
* Returns true if the passed module exists
*
* @param string $name Either full composer name or short name
* @return bool
*/
public function moduleExists($name)
{
$module = $this->getModule($name);
return !empty($module);
}
/**
* Constructs and initialises a new configuration object, either loading
* from the cache or re-scanning for classes.
*
* @param string $base The project base path.
* @param bool $includeTests
* @param bool $forceRegen Force the manifest to be regenerated.
*/
public function __construct($base, $includeTests = false, $forceRegen = false)
{
$this->base = $base;
$this->cacheKey = sha1($base).'_modules';
$this->includeTests = $includeTests;
$this->cache = $this->getCache($includeTests);
// Unless we're forcing regen, try loading from cache
if (!$forceRegen) {
$this->modules = $this->cache->load($this->cacheKey) ?: [];
}
if (empty($this->modules)) {
$this->regenerate($includeTests);
}
}
/**
* Provides a hook for mock unit tests despite no DI
*
* @param bool $includeTests
* @return ManifestCache
*/
protected function getCache($includeTests = false)
{
// Cache
$cacheClass = getenv('SS_MANIFESTCACHE') ?: ManifestCache_File::class;
return new $cacheClass('classmanifest'.($includeTests ? '_tests' : ''));
}
/**
* Includes all of the php _config.php files found by this manifest.
*/
public function activateConfig()
{
foreach ($this->getModules() as $module) {
$module->activate();
}
}
/**
* Completely regenerates the manifest file. Scans through finding all php _config.php and yaml _config/*.ya?ml
* files,parses the yaml files into fragments, sorts them and figures out what values need to be checked to pick
* the correct variant.
*
* Does _not_ build the actual variant
*
* @param bool $includeTests
* @param bool $cache Cache the result.
*/
public function regenerate($includeTests = false, $cache = true)
{
$this->modules = [];
$finder = new ManifestFileFinder();
$finder->setOptions(array(
'min_depth' => 0,
'name_regex' => '/(^|[\/\\\\])_config.php$/',
'ignore_tests' => !$includeTests,
'file_callback' => array($this, 'addSourceConfigFile'),
// Cannot be max_depth: 1 due to "/framework/admin/_config.php"
'max_depth' => 2
));
$finder->find($this->base);
$finder = new ManifestFileFinder();
$finder->setOptions(array(
'name_regex' => '/\.ya?ml$/',
'ignore_tests' => !$includeTests,
'file_callback' => array($this, 'addYAMLConfigFile'),
'max_depth' => 2
));
$finder->find($this->base);
if ($cache) {
$this->cache->save($this->modules, $this->cacheKey);
}
}
/**
* Record finding of _config.php file
*
* @param string $basename
* @param string $pathname
* @param int $depth
*/
public function addSourceConfigFile($basename, $pathname, $depth)
{
$this->addModule(dirname($pathname));
}
/**
* Handle lookup of _config/*.yml file
*
* @param string $basename
* @param string $pathname
* @param int $depth
*/
public function addYAMLConfigFile($basename, $pathname, $depth)
{
if (preg_match('{/([^/]+)/_config/}', $pathname, $match)) {
$this->addModule(dirname(dirname($pathname)));
}
}
/**
* Get module by name
*
* @param string $name
* @return Module
*/
public function getModule($name)
{
// Optimised find
if (isset($this->modules[$name])) {
return $this->modules[$name];
}
// Fall back to lookup by shortname
if (!strstr($name, '/')) {
foreach ($this->modules as $module) {
if (strcasecmp($module->getShortName(), $name) === 0) {
return $module;
}
}
}
return null;
}
/**
* Get modules found
*
* @return Module[]
*/
public function getModules()
{
return $this->modules;
}
}

View File

@ -3,26 +3,28 @@
namespace SilverStripe\Dev;
use SilverStripe\CMS\Controllers\RootURLController;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\Cookie;
use SilverStripe\Control\Email\Email;
use SilverStripe\Control\Email\Mailer;
use SilverStripe\Control\Session;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Control\Tests\FakeController;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\ConfigLoader;
use SilverStripe\Core\Config\CoreConfigFactory;
use SilverStripe\Core\Config\DefaultConfig;
use SilverStripe\Core\Config\Middleware\ExtensionMiddleware;
use SilverStripe\Core\Flushable;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Manifest\ClassManifest;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Manifest\ConfigStaticManifest;
use SilverStripe\Core\Resettable;
use SilverStripe\i18n\i18n;
use SilverStripe\ORM\SS_List;
use SilverStripe\ORM\Versioning\Versioned;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Hierarchy\Hierarchy;
use SilverStripe\ORM\DataModel;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\FieldType\DBField;
@ -224,13 +226,15 @@ class SapphireTest extends PHPUnit_Framework_TestCase
*/
protected $originalReadingMode = null;
protected $originalEnv = null;
public function setUp()
{
//nest config and injector for each test so they are effectively sandboxed per test
Config::nest();
Injector::nest();
$this->originalEnv = Director::get_environment_type();
$this->originalReadingMode = Versioned::get_reading_mode();
// We cannot run the tests on this abstract class.
@ -244,7 +248,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase
self::$is_running_test = true;
// i18n needs to be set to the defaults or tests fail
i18n::set_locale(i18n::config()->get('default_locale'));
i18n::set_locale(i18n::config()->uninherited('default_locale'));
// Set default timezone consistently to avoid NZ-specific dependencies
date_default_timezone_set('UTC');
@ -333,6 +337,8 @@ class SapphireTest extends PHPUnit_Framework_TestCase
*/
public function setUpOnce()
{
static::start();
//nest config and injector for each suite so they are effectively sandboxed
Config::nest();
Injector::nest();
@ -590,6 +596,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase
$response->removeHeader('Location');
}
Director::set_environment_type($this->originalEnv);
Versioned::set_reading_mode($this->originalReadingMode);
//unnest injector / config now that tests are over
@ -985,11 +992,23 @@ class SapphireTest extends PHPUnit_Framework_TestCase
return var_export($extracted, true);
}
/**
* Start test environment
*/
public static function start()
{
if (!static::is_running_test()) {
new FakeController();
static::use_test_manifest();
static::set_is_running_test(true);
}
}
/**
* Pushes a class and template manifest instance that include tests onto the
* top of the loader stacks.
*/
public static function use_test_manifest()
protected static function use_test_manifest()
{
$flush = !empty($_GET['flush']);
$classManifest = new ClassManifest(
@ -1008,11 +1027,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase
$flush
));
Config::inst()->pushConfigStaticManifest(new ConfigStaticManifest(
BASE_PATH,
true,
$flush
));
// Once new class loader is registered, push a new uncached config
$config = CoreConfigFactory::inst()->createCore();
ConfigLoader::instance()->pushManifest($config);
// Invalidate classname spec since the test manifest will now pull out new subclasses for each internal class
// (e.g. Member will now have various subclasses of DataObjects that implement TestOnly)
@ -1322,6 +1339,30 @@ class SapphireTest extends PHPUnit_Framework_TestCase
}
protected function setUpRoutes()
{
// Get overridden routes
$rules = $this->getExtraRoutes();
// Add all other routes
foreach (Director::config()->uninherited('rules') as $route => $rule) {
if (!isset($rules[$route])) {
$rules[$route] = $rule;
}
}
// Add default catch-all rule
$rules['$Controller//$Action/$ID/$OtherID'] = '*';
// Add controller-name auto-routing
Director::config()->set('rules', $rules);
}
/**
* Get extra routes to merge into Director.rules
*
* @return array
*/
protected function getExtraRoutes()
{
$rules = [];
foreach ($this->getExtraControllers() as $class) {
@ -1330,11 +1371,6 @@ class SapphireTest extends PHPUnit_Framework_TestCase
$route = rtrim($link, '/') . '//$Action/$ID/$OtherID';
$rules[$route] = $class;
}
// Add default catch-all rule
$rules['$Controller//$Action/$ID/$OtherID'] = '*';
// Add controller-name auto-routing
Director::config()->update('rules', $rules);
return $rules;
}
}

View File

@ -52,7 +52,7 @@ class CountryDropdownField extends DropdownField
}
// Get sorted countries
$source = i18n::getData()->i18nCountries();
$source = i18n::getData()->getCountries();
return parent::setSource($source);
}
@ -62,7 +62,7 @@ class CountryDropdownField extends DropdownField
// Default value to best availabel locale
$value = $this->Value();
if ($this->config()->default_to_locale
if (CountryDropdownField::config()->default_to_locale
&& (!$value || !isset($source[$value]))
&& $this->locale()
) {
@ -74,7 +74,7 @@ class CountryDropdownField extends DropdownField
// Default to default country otherwise
if (!$value || !isset($source[$value])) {
$this->setValue($this->config()->default_country);
$this->setValue(CountryDropdownField::config()->default_country);
}
return parent::Field($properties);

View File

@ -27,7 +27,7 @@ class CurrencyField extends TextField
if (!$value) {
$value = 0.00;
}
$this->value = DBCurrency::config()->get('currency_symbol')
$this->value = DBCurrency::config()->uninherited('currency_symbol')
. number_format((double)preg_replace('/[^0-9.\-]/', '', $value), 2);
return $this;
}
@ -59,7 +59,7 @@ class CurrencyField extends TextField
public function validate($validator)
{
$currencySymbol = preg_quote(DBCurrency::config()->get('currency_symbol'));
$currencySymbol = preg_quote(DBCurrency::config()->uninherited('currency_symbol'));
$regex = '/^\s*(\-?'.$currencySymbol.'?|'.$currencySymbol.'\-?)?(\d{1,3}(\,\d{3})*|(\d+))(\.\d{2})?\s*$/';
if (!empty($this->value) && !preg_match($regex, $this->value)) {
$validator->validationError(

View File

@ -244,7 +244,7 @@ class DateField extends TextField
protected function getISO8601Formatter()
{
$formatter = IntlDateFormatter::create(
i18n::config()->get('default_locale'),
i18n::config()->uninherited('default_locale'),
IntlDateFormatter::MEDIUM,
IntlDateFormatter::NONE
);

View File

@ -3,6 +3,7 @@
namespace SilverStripe\Forms;
use InvalidArgumentException;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\i18n\i18n;
@ -109,7 +110,7 @@ class DateField_View_JQuery
$locale = $this->getField()->getClientLocale();
// Check standard mappings
$map = $this->config()->locale_map;
$map = Config::inst()->get(__CLASS__, 'locale_map');
if (array_key_exists($locale, $map)) {
return $map[$locale];
}

View File

@ -137,7 +137,7 @@ class DatetimeField extends FormField
protected function getISO8601Formatter()
{
$formatter = IntlDateFormatter::create(
i18n::config()->get('default_locale'),
i18n::config()->uninherited('default_locale'),
IntlDateFormatter::MEDIUM,
IntlDateFormatter::MEDIUM,
date_default_timezone_get() // Default to server timezone

View File

@ -375,7 +375,7 @@ class FormField extends RequestHandler
*/
protected function setupDefaultClasses()
{
$defaultClasses = self::config()->get('default_classes');
$defaultClasses = $this->config()->get('default_classes');
if ($defaultClasses) {
foreach ($defaultClasses as $class) {
$this->addExtraClass($class);

View File

@ -80,7 +80,7 @@ class FormScaffolder extends Object
}
// Add logical fields directly specified in db config
foreach ($this->obj->config()->db as $fieldName => $fieldType) {
foreach ($this->obj->config()->get('db') as $fieldName => $fieldType) {
// Skip restricted fields
if ($this->restrictFields && !in_array($fieldName, $this->restrictFields)) {
continue;

View File

@ -18,7 +18,6 @@ use SilverStripe\Forms\FormField;
use SilverStripe\Forms\Form;
use LogicException;
use InvalidArgumentException;
use SilverStripe\View\Requirements;
/**
* Displays a {@link SS_List} in a grid format.
@ -128,7 +127,7 @@ class GridField extends FormField
$this->setConfig($config);
$state = $this->config->getComponentByType('SilverStripe\\Forms\\GridField\\GridState_Component');
$state = $this->config->getComponentByType(GridState_Component::class);
if (!$state) {
$this->config->addComponent(new GridState_Component());

View File

@ -326,7 +326,7 @@ class GridFieldAddExistingAutocompleter implements GridField_HTMLProvider, GridF
*/
public function scaffoldSearchFields($dataClass)
{
$obj = singleton($dataClass);
$obj = DataObject::singleton($dataClass);
$fields = null;
if ($fieldSpecs = $obj->searchableFields()) {
$customSearchableFields = $obj->stat('searchable_fields');

View File

@ -47,9 +47,9 @@ class GridFieldPageCount implements GridField_HTMLProvider
protected function getPaginator($gridField)
{
/** @var GridFieldPaginator $paginator */
$paginator = $gridField->getConfig()->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldPaginator');
$paginator = $gridField->getConfig()->getComponentByType(GridFieldPaginator::class);
if (!$paginator && $this->config()->get('require_paginator')) {
if (!$paginator && GridFieldPageCount::config()->uninherited('require_paginator')) {
throw new LogicException(
get_class($this) . " relies on a GridFieldPaginator to be added " .
"to the same GridField, but none are present."

View File

@ -42,7 +42,8 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu
*/
public function __construct($itemsPerPage = null)
{
$this->itemsPerPage = $itemsPerPage ?: GridFieldPaginator::config()->get('default_items_per_page');
$this->itemsPerPage = $itemsPerPage
?: GridFieldPaginator::config()->uninherited('default_items_per_page');
}
/**

View File

@ -105,7 +105,7 @@ class HTMLEditorField extends TextareaField
$this->setEditorConfig($config);
}
$this->setRows($this->config()->default_rows);
$this->setRows(HTMLEditorField::config()->default_rows);
}
public function getAttributes()
@ -130,7 +130,7 @@ class HTMLEditorField extends TextareaField
// Sanitise if requested
$htmlValue = Injector::inst()->create('HTMLValue', $this->Value());
if ($this->config()->sanitise_server_side) {
if (HTMLEditorField::config()->sanitise_server_side) {
$santiser = HTMLEditorSanitiser::create(HTMLEditorConfig::get_active());
$santiser->sanitise($htmlValue);
}

View File

@ -118,7 +118,7 @@ abstract class HTMLEditorField_File extends ViewableData
'left' => _t('HTMLEditorField.CSSCLASSLEFT', 'On the left, with text wrapping around.'),
'right' => _t('HTMLEditorField.CSSCLASSRIGHT', 'On the right, with text wrapping around.')
),
HtmlEditorField::config()->get('media_alignment')
HtmlEditorField::config()->uninherited('media_alignment')
),
FieldGroup::create(
_t('HTMLEditorField.IMAGEDIMENSIONS', 'Dimensions'),
@ -313,8 +313,8 @@ abstract class HTMLEditorField_File extends ViewableData
{
// Get preview from file
if ($this->file) {
$width = $this->config()->media_preview_width;
$height = $this->config()->media_preview_height;
$width = HTMLEditorField_File::config()->media_preview_width;
$height = HTMLEditorField_File::config()->media_preview_height;
return $this->file->ThumbnailURL($width, $height);
}
return null;
@ -356,7 +356,7 @@ abstract class HTMLEditorField_File extends ViewableData
return $height;
}
}
return $this->config()->insert_height;
return HTMLEditorField_File::config()->insert_height;
}
/**
@ -372,7 +372,7 @@ abstract class HTMLEditorField_File extends ViewableData
return $width;
}
}
return $this->config()->insert_width;
return HTMLEditorField_File::config()->insert_width;
}
/**
@ -383,7 +383,7 @@ abstract class HTMLEditorField_File extends ViewableData
public function getInsertWidth()
{
$width = $this->getWidth();
$maxWidth = $this->config()->insert_width;
$maxWidth = HTMLEditorField_File::config()->insert_width;
return ($width <= $maxWidth) ? $width : $maxWidth;
}
@ -396,7 +396,7 @@ abstract class HTMLEditorField_File extends ViewableData
{
$width = $this->getWidth();
$height = $this->getHeight();
$maxWidth = $this->config()->insert_width;
$maxWidth = HTMLEditorField_File::config()->insert_width;
return ($width <= $maxWidth) ? $height : round($height * ($maxWidth / $width));
}
}

View File

@ -181,7 +181,7 @@ class HTMLEditorField_Image extends HTMLEditorField_File
public function getInsertWidth()
{
$width = $this->getWidth();
$maxWidth = $this->config()->insert_width;
$maxWidth = HTMLEditorField_Image::config()->insert_width;
return $width <= $maxWidth
? $width
: $maxWidth;
@ -196,7 +196,7 @@ class HTMLEditorField_Image extends HTMLEditorField_File
{
$width = $this->getWidth();
$height = $this->getHeight();
$maxWidth = $this->config()->insert_width;
$maxWidth = HTMLEditorField_Image::config()->insert_width;
return ($width <= $maxWidth) ? $height : round($height * ($maxWidth / $width));
}

View File

@ -3,6 +3,7 @@
namespace SilverStripe\Forms\HTMLEditor;
use SilverStripe\Assets\File;
use SilverStripe\Assets\Folder;
use SilverStripe\Assets\Upload;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\Controller;
@ -252,7 +253,7 @@ class HTMLEditorField_Toolbar extends RequestHandler
));
}
$scheme = strtolower(parse_url($fileUrl, PHP_URL_SCHEME));
$allowed_schemes = self::config()->fileurl_scheme_whitelist;
$allowed_schemes = self::config()->get('fileurl_scheme_whitelist');
if (!$scheme || ($allowed_schemes && !in_array($scheme, $allowed_schemes))) {
throw $this->getErrorFor(_t(
"HTMLEditorField_Toolbar.ERROR_SCHEME",
@ -260,7 +261,7 @@ class HTMLEditorField_Toolbar extends RequestHandler
));
}
$domain = strtolower(parse_url($fileUrl, PHP_URL_HOST));
$allowed_domains = self::config()->fileurl_domain_whitelist;
$allowed_domains = self::config()->get('fileurl_domain_whitelist');
if (!$domain || ($allowed_domains && !in_array($domain, $allowed_domains))) {
throw $this->getErrorFor(_t(
"HTMLEditorField_Toolbar.ERROR_HOSTNAME",

View File

@ -555,7 +555,8 @@ class TinyMCEConfig extends HTMLEditorConfig
// https://www.tinymce.com/docs/api/class/tinymce.editormanager/#baseURL
$tinyMCEBaseURL = Controller::join_links(
Director::absoluteBaseURL(),
$this->config()->get('base_dir') ?: ADMIN_THIRDPARTY_DIR . '/tinymce'
TinyMCEConfig::config()->get('base_dir')
?: ADMIN_THIRDPARTY_DIR . '/tinymce'
);
$settings['baseURL'] = $tinyMCEBaseURL;

View File

@ -95,7 +95,7 @@ class MemberDatetimeOptionsetField extends OptionsetField
*/
protected function previewFormat($format)
{
$date = DBDatetime::create_field('Datetime', $this->config()->preview_date);
$date = DBDatetime::create_field('Datetime', MemberDatetimeOptionsetField::config()->preview_date);
return $date->Format($format);
}

View File

@ -55,7 +55,10 @@ class NumericField extends TextField
{
if ($this->getHTML5()) {
// Locale-independent html5 number formatter
$formatter = NumberFormatter::create(i18n::config()->get('default_locale'), NumberFormatter::DECIMAL);
$formatter = NumberFormatter::create(
i18n::config()->uninherited('default_locale'),
NumberFormatter::DECIMAL
);
$formatter->setAttribute(NumberFormatter::GROUPING_USED, false);
$formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, '.');
} else {

View File

@ -106,6 +106,7 @@ class ReadonlyField extends FormField
}
// Use default casting
return $this->config()->casting['Value'];
$casting = $this->config()->get('casting');
return $casting['Value'];
}
}

View File

@ -158,7 +158,7 @@ class TimeField extends TextField
protected function getISO8601Formatter()
{
$formatter = IntlDateFormatter::create(
i18n::config()->get('default_locale'),
i18n::config()->uninherited('default_locale'),
IntlDateFormatter::NONE,
IntlDateFormatter::MEDIUM,
date_default_timezone_get() // Default to server timezone

View File

@ -135,9 +135,8 @@ class UploadField_SelectHandler extends RequestHandler
));
// Set configurable pagination for file list field
$pageSize = Config::inst()->get(get_class($this), 'page_size');
$pageSize = $this->config()->get('page_size');
$config->addComponent(new GridFieldPaginator($pageSize));
// If relation is to be autoset, we need to make sure we only list compatible objects.
$baseClass = $this->parent->getRelationAutosetClass();

View File

@ -2,6 +2,7 @@
namespace SilverStripe\ORM;
use SilverStripe\Assets\AssetControlExtension;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Object;
@ -252,9 +253,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @config
* @var array
*/
private static $extensions = array(
'AssetControl' => 'SilverStripe\\Assets\\AssetControlExtension'
);
private static $extensions = [
'AssetControl' => AssetControlExtension::class,
];
/**
* Override table name for this class. If ignored will default to FQN of class.
@ -651,7 +652,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public function isEmpty()
{
$fixed = $this->config()->fixed_fields;
$fixed = DataObject::config()->uninherited('fixed_fields');
foreach ($this->toMap() as $field => $value) {
// only look at custom fields
if (isset($fixed[$field])) {
@ -1175,7 +1176,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
);
}
if ($this->config()->get('validation_enabled')) {
// Note: Validation can only be disabled at the global level, not per-model
if (DataObject::config()->uninherited('validation_enabled')) {
$result = $this->validate();
if (!$result->isValid()) {
return new ValidationException($result);
@ -1659,10 +1661,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
// Go through all relationship configuration fields.
$config = $this->config();
$candidates = array_merge(
($relations = Config::inst()->get($this->class, 'has_one')) ? $relations : array(),
($relations = Config::inst()->get($this->class, 'has_many')) ? $relations : array(),
($relations = Config::inst()->get($this->class, 'belongs_to')) ? $relations : array()
($relations = $config->get('has_one')) ? $relations : array(),
($relations = $config->get('has_many')) ? $relations : array(),
($relations = $config->get('belongs_to')) ? $relations : array()
);
if (isset($candidates[$relationName])) {
@ -1689,8 +1692,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
public function getRelationType($component)
{
$types = array('has_one', 'has_many', 'many_many', 'belongs_many_many', 'belongs_to');
$config = $this->config();
foreach ($types as $type) {
$relations = Config::inst()->get($this->class, $type);
$relations = $config->get($type);
if ($relations && isset($relations[$component])) {
return $type;
}
@ -1891,7 +1895,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public function hasOne()
{
return (array)Config::inst()->get($this->class, 'has_one', Config::INHERITED);
return (array)$this->config()->get('has_one');
}
/**
@ -1904,7 +1908,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public function belongsTo($classOnly = true)
{
$belongsTo = (array)Config::inst()->get($this->class, 'belongs_to', Config::INHERITED);
$belongsTo = (array)$this->config()->get('belongs_to');
if ($belongsTo && $classOnly) {
return preg_replace('/(.+)?\..+/', '$1', $belongsTo);
} else {
@ -1922,7 +1926,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public function hasMany($classOnly = true)
{
$hasMany = (array)Config::inst()->get($this->class, 'has_many', Config::INHERITED);
$hasMany = (array)$this->config()->get('has_many');
if ($hasMany && $classOnly) {
return preg_replace('/(.+)?\..+/', '$1', $hasMany);
} else {
@ -1940,7 +1944,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public function manyManyExtraFields()
{
return Config::inst()->get($this->class, 'many_many_extraFields', Config::INHERITED);
return $this->config()->get('many_many_extraFields');
}
/**
@ -1953,8 +1957,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public function manyMany()
{
$manyManys = (array)Config::inst()->get($this->class, 'many_many', Config::INHERITED);
$belongsManyManys = (array)Config::inst()->get($this->class, 'belongs_many_many', Config::INHERITED);
$config = $this->config();
$manyManys = (array)$config->get('many_many');
$belongsManyManys = (array)$config->get('belongs_many_many');
$items = array_merge($manyManys, $belongsManyManys);
return $items;
}
@ -1970,7 +1975,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
public function database_extensions($class)
{
$extensions = Config::inst()->get($class, 'database_extensions', Config::UNINHERITED);
if ($extensions) {
return $extensions;
} else {
@ -2533,65 +2537,25 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public function can($perm, $member = null, $context = array())
{
if (!isset($member)) {
$member = Member::currentUser();
if (!$member || !($member instanceof Member) || is_numeric($member)) {
$member = Member::currentUserID();
}
if (Permission::checkMember($member, "ADMIN")) {
if ($member && Permission::checkMember($member, "ADMIN")) {
return true;
}
if ($this->getSchema()->manyManyComponent(static::class, 'Can' . $perm)) {
if ($this->ParentID && $this->SecurityType == 'Inherit') {
if (!($p = $this->Parent)) {
return false;
}
return $this->Parent->can($perm, $member);
} else {
$permissionCache = $this->uninherited('permissionCache');
$memberID = $member ? $member->ID : 'none';
if (!isset($permissionCache[$memberID][$perm])) {
if ($member->ID) {
$groups = $member->Groups();
}
$groupList = implode(', ', $groups->column("ID"));
// TODO Fix relation table hardcoding
$query = new SQLSelect(
"\"Page_Can$perm\".PageID",
array("\"Page_Can$perm\""),
"GroupID IN ($groupList)"
);
$permissionCache[$memberID][$perm] = $query->execute()->column();
if ($perm == "View") {
// TODO Fix relation table hardcoding
$query = new SQLSelect("\"SiteTree\".\"ID\"", array(
"\"SiteTree\"",
"LEFT JOIN \"Page_CanView\" ON \"Page_CanView\".\"PageID\" = \"SiteTree\".\"ID\""
), "\"Page_CanView\".\"PageID\" IS NULL");
$unsecuredPages = $query->execute()->column();
if ($permissionCache[$memberID][$perm]) {
$permissionCache[$memberID][$perm]
= array_merge($permissionCache[$memberID][$perm], $unsecuredPages);
} else {
$permissionCache[$memberID][$perm] = $unsecuredPages;
}
}
Config::inst()->update($this->class, 'permissionCache', $permissionCache);
}
if ($permissionCache[$memberID][$perm]) {
return in_array($this->ID, $permissionCache[$memberID][$perm]);
}
}
} else {
return parent::can($perm, $member);
if (is_string($perm) && method_exists($this, 'can' . ucfirst($perm))) {
$method = 'can' . ucfirst($perm);
return $this->$method($member);
}
$results = $this->extendedCan('can', $member);
if (isset($results)) {
return $results;
}
return ($member && Permission::checkMember($member, $perm));
}
/**
@ -3240,7 +3204,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/
public function requireDefaultRecords()
{
$defaultRecords = $this->config()->get('default_records', Config::UNINHERITED);
$defaultRecords = $this->config()->uninherited('default_records');
if (!empty($defaultRecords)) {
$hasData = DataObject::get_one($this->class);
@ -3381,9 +3345,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if ($ancestorClass === ViewableData::class) {
break;
}
$types = array(
'db' => (array)Config::inst()->get($ancestorClass, 'db', Config::UNINHERITED)
);
$types = [
'db' => (array)Config::inst()->get($ancestorClass, 'db', Config::UNINHERITED)
];
if ($includerelations) {
$types['has_one'] = (array)Config::inst()->get($ancestorClass, 'has_one', Config::UNINHERITED);
$types['has_many'] = (array)Config::inst()->get($ancestorClass, 'has_many', Config::UNINHERITED);
@ -3537,7 +3501,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @var array
* @config
*/
private static $db = null;
private static $db = [];
/**
* Use a casting object for a field. This is a map from
@ -3593,7 +3557,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @var array
* @config
*/
private static $defaults = null;
private static $defaults = [];
/**
* Multidimensional array which inserts default data into the database
@ -3621,7 +3585,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @var array
* @config
*/
private static $has_one = null;
private static $has_one = [];
/**
* A meta-relationship that allows you to define the reverse side of a {@link DataObject::$has_one}.
@ -3635,7 +3599,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @var array
* @config
*/
private static $belongs_to;
private static $belongs_to = [];
/**
* This defines a one-to-many relationship. It is a map of component name to the remote data class.
@ -3648,7 +3612,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @var array
* @config
*/
private static $has_many = null;
private static $has_many = [];
/**
* many-many relationship definitions.
@ -3656,7 +3620,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @var array
* @config
*/
private static $many_many = null;
private static $many_many = [];
/**
* Extra fields to include on the connecting many-many table.
@ -3674,7 +3638,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @var array
* @config
*/
private static $many_many_extraFields = null;
private static $many_many_extraFields = [];
/**
* The inverse side of a many-many relationship.
@ -3682,7 +3646,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @var array
* @config
*/
private static $belongs_many_many = null;
private static $belongs_many_many = [];
/**
* The default sort expression. This will be inserted in the ORDER BY
@ -3733,14 +3697,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* default display in the search form.
* @config
*/
private static $field_labels = null;
private static $field_labels = [];
/**
* Provides a default list of fields to be used by a 'summary'
* view of this object.
* @config
*/
private static $summary_fields = null;
private static $summary_fields = [];
public function provideI18nEntities()
{

View File

@ -17,7 +17,6 @@ use LogicException;
*/
class DataObjectSchema
{
use Injectable;
use Configurable;
@ -289,7 +288,7 @@ class DataObjectSchema
// Generate default table name
if (!$table) {
$separator = $this->config()->get('table_namespace_separator');
$separator = DataObjectSchema::config()->uninherited('table_namespace_separator');
$table = str_replace('\\', $separator, trim($class, '\\'));
}
@ -410,7 +409,7 @@ class DataObjectSchema
$dbFields = array();
// Ensure fixed fields appear at the start
$fixedFields = DataObject::config()->get('fixed_fields');
$fixedFields = DataObject::config()->uninherited('fixed_fields');
if (get_parent_class($class) === DataObject::class) {
// Merge fixed with ClassName spec and custom db fields
$dbFields = $fixedFields;
@ -495,7 +494,7 @@ class DataObjectSchema
}
// Short circuit for fixed fields
$fixed = DataObject::config()->get('fixed_fields');
$fixed = DataObject::config()->uninherited('fixed_fields');
if (isset($fixed[$fieldName])) {
return $this->baseDataClass($candidateClass);
}
@ -660,7 +659,7 @@ class DataObjectSchema
*/
public function hasOneComponent($class, $component)
{
$hasOnes = Config::inst()->get($class, 'has_one');
$hasOnes = Config::forClass($class)->get('has_one');
if (!isset($hasOnes[$component])) {
return null;
}
@ -682,7 +681,7 @@ class DataObjectSchema
*/
public function belongsToComponent($class, $component, $classOnly = true)
{
$belongsTo = (array)Config::inst()->get($class, 'belongs_to');
$belongsTo = (array)Config::forClass($class)->get('belongs_to');
if (!isset($belongsTo[$component])) {
return null;
}

View File

@ -595,7 +595,7 @@ class Versioned extends DataExtension implements TemplateGlobalProvider, Resetta
// Build a list of suffixes whose tables need versioning
$allSuffixes = array();
$versionableExtensions = $owner->config()->versionableExtensions;
$versionableExtensions = $owner->config()->get('versionableExtensions');
if (count($versionableExtensions)) {
foreach ($versionableExtensions as $versionableExtension => $suffixes) {
if ($owner->hasExtension($versionableExtension)) {
@ -622,7 +622,7 @@ class Versioned extends DataExtension implements TemplateGlobalProvider, Resetta
$fields = $schema->databaseFields($class, false);
unset($fields['ID']);
if ($fields) {
$options = Config::inst()->get($class, 'create_table_options', Config::FIRST_SET);
$options = Config::inst()->get($class, 'create_table_options');
$indexes = $owner->databaseIndexes();
$extensionClass = $allSuffixes[$suffix];
if ($suffix && ($extension = $owner->getExtensionInstance($extensionClass))) {
@ -1104,7 +1104,7 @@ class Versioned extends DataExtension implements TemplateGlobalProvider, Resetta
return $list;
}
$relationships = $owner->config()->{$source};
$relationships = $owner->config()->get($source);
foreach ($relationships as $relationship) {
// Warn if invalid config
if (!$owner->hasMethod($relationship)) {
@ -1375,7 +1375,7 @@ class Versioned extends DataExtension implements TemplateGlobalProvider, Resetta
}
// Fall back to default permission check
$permissions = Config::inst()->get($owner->class, 'non_live_permissions', Config::FIRST_SET);
$permissions = Config::inst()->get($owner->class, 'non_live_permissions');
$check = Permission::checkMember($member, $permissions);
return (bool)$check;
}
@ -1441,7 +1441,7 @@ class Versioned extends DataExtension implements TemplateGlobalProvider, Resetta
public function extendWithSuffix($table)
{
$owner = $this->owner;
$versionableExtensions = $owner->config()->versionableExtensions;
$versionableExtensions = $owner->config()->get('versionableExtensions');
if (count($versionableExtensions)) {
foreach ($versionableExtensions as $versionableExtension => $suffixes) {
@ -1546,8 +1546,8 @@ class Versioned extends DataExtension implements TemplateGlobalProvider, Resetta
// after publishing, objects which used to be owned need to be
// dis-connected from this object (set ForeignKeyID = 0)
$owns = $owner->config()->owns;
$hasMany = $owner->config()->has_many;
$owns = $owner->config()->get('owns');
$hasMany = $owner->config()->get('has_many');
if (empty($owns) || empty($hasMany)) {
return;
}

View File

@ -160,7 +160,7 @@ class BasicAuth
*/
public static function protect_site_if_necessary()
{
$config = Config::inst()->forClass('SilverStripe\\Security\\BasicAuth');
$config = Config::forClass('SilverStripe\\Security\\BasicAuth');
if ($config->entire_site_protected) {
self::requireLogin($config->entire_site_protected_message, $config->entire_site_protected_code, false);
}

View File

@ -474,8 +474,8 @@ class Member extends DataObject implements TemplateGlobalProvider
}
if ($remember) {
$rememberLoginHash = RememberLoginHash::generate($this);
$tokenExpiryDays = RememberLoginHash::config()->get('token_expiry_days');
$deviceExpiryDays = RememberLoginHash::config()->get('device_expiry_days');
$tokenExpiryDays = RememberLoginHash::config()->uninherited('token_expiry_days');
$deviceExpiryDays = RememberLoginHash::config()->uninherited('device_expiry_days');
Cookie::set(
'alc_enc',
$this->ID . ':' . $rememberLoginHash->getToken(),
@ -610,7 +610,7 @@ class Member extends DataObject implements TemplateGlobalProvider
if ($rememberLoginHash) {
$rememberLoginHash->renew();
$tokenExpiryDays = RememberLoginHash::config()->get('token_expiry_days');
$tokenExpiryDays = RememberLoginHash::config()->uninherited('token_expiry_days');
Cookie::set(
'alc_enc',
$member->ID . ':' . $rememberLoginHash->getToken(),
@ -930,7 +930,7 @@ class Member extends DataObject implements TemplateGlobalProvider
*/
public static function create_new_password()
{
$words = Security::config()->get('word_list');
$words = Security::config()->uninherited('word_list');
if ($words && file_exists($words)) {
$words = file($words);

View File

@ -123,7 +123,7 @@ class MemberLoginForm extends LoginForm
'title',
sprintf(
_t('Member.REMEMBERME', "Remember me next time? (for %d days on this device)"),
RememberLoginHash::config()->get('token_expiry_days')
RememberLoginHash::config()->uninherited('token_expiry_days')
)
)
);

View File

@ -367,7 +367,7 @@ class Security extends Controller implements TemplateGlobalProvider
$controller->extend('permissionDenied', $member);
return $controller->redirect(Controller::join_links(
static::config()->get('login_url'),
Security::config()->uninherited('login_url'),
"?BackURL=" . urlencode($_SERVER['REQUEST_URI'])
));
}

View File

@ -4000,7 +4000,7 @@ class SSTemplateParser extends Parser implements TemplateParser
// the passed cache key, the block index, and the sha hash of the template.
$res['php'] .= '$keyExpression = function() use ($scope, $cache) {' . PHP_EOL;
$res['php'] .= '$val = \'\';' . PHP_EOL;
if ($globalKey = SSViewer::config()->get('global_key')) {
if ($globalKey = SSViewer::config()->uninherited('global_key')) {
// Embed the code necessary to evaluate the globalKey directly into the template,
// so that SSTemplateParser only needs to be called during template regeneration.
// Warning: If the global key is changed, it's necessary to flush the template cache.

View File

@ -62,29 +62,6 @@ class SSViewer implements Flushable
*/
private static $cacheblock_cache_flushed = false;
/**
* Set whether HTML comments indicating the source .SS file used to render this page should be
* included in the output. This is enabled by default
*
* @deprecated 4.0 Use the "SSViewer.source_file_comments" config setting instead
* @param boolean $val
*/
public static function set_source_file_comments($val)
{
Deprecation::notice('4.0', 'Use the "SSViewer.source_file_comments" config setting instead');
SSViewer::config()->update('source_file_comments', $val);
}
/**
* @deprecated 4.0 Use the "SSViewer.source_file_comments" config setting instead
* @return boolean
*/
public static function get_source_file_comments()
{
Deprecation::notice('4.0', 'Use the "SSViewer.source_file_comments" config setting instead');
return SSViewer::config()->get('source_file_comments');
}
/**
* @var array $templates List of templates to select from
*/
@ -181,31 +158,29 @@ class SSViewer implements Flushable
*/
public static function set_themes($themes = [])
{
SSViewer::config()
->remove('themes')
->update('themes', $themes);
SSViewer::config()->set('themes', $themes);
}
public static function add_themes($themes = [])
{
SSViewer::config()->update('themes', $themes);
SSViewer::config()->merge('themes', $themes);
}
public static function get_themes()
{
$default = [self::DEFAULT_THEME];
if (!SSViewer::config()->get('theme_enabled')) {
if (!SSViewer::config()->uninherited('theme_enabled')) {
return $default;
}
// Explicit list is assigned
if ($list = SSViewer::config()->get('themes')) {
if ($list = SSViewer::config()->uninherited('themes')) {
return $list;
}
// Support legacy behaviour
if ($theme = SSViewer::config()->get('theme')) {
if ($theme = SSViewer::config()->uninherited('theme')) {
return [$theme, self::DEFAULT_THEME];
}
@ -347,47 +322,6 @@ class SSViewer implements Flushable
return (bool)ThemeResourceLoader::instance()->findTemplate($templates, self::get_themes());
}
/**
* Set a global rendering option.
*
* The following options are available:
* - rewriteHashlinks: If true (the default), <a href="#..."> will be rewritten to contain the
* current URL. This lets it play nicely with our <base> tag.
* - If rewriteHashlinks = 'php' then, a piece of PHP script will be inserted before the hash
* links: "<?php echo $_SERVER['REQUEST_URI']; ?>". This is useful if you're generating a
* page that will be saved to a .php file and may be accessed from different URLs.
*
* @deprecated 4.0 Use the "SSViewer.rewrite_hash_links" config setting instead
* @param string $optionName
* @param mixed $optionVal
*/
public static function setOption($optionName, $optionVal)
{
if ($optionName == 'rewriteHashlinks') {
Deprecation::notice('4.0', 'Use the "SSViewer.rewrite_hash_links" config setting instead');
SSViewer::config()->update('rewrite_hash_links', $optionVal);
} else {
Deprecation::notice('4.0', 'Use the "SSViewer.' . $optionName . '" config setting instead');
SSViewer::config()->update($optionName, $optionVal);
}
}
/**
* @deprecated 4.0 Use the "SSViewer.rewrite_hash_links" config setting instead
* @param string
* @return mixed
*/
public static function getOption($optionName)
{
if ($optionName == 'rewriteHashlinks') {
Deprecation::notice('4.0', 'Use the "SSViewer.rewrite_hash_links" config setting instead');
return SSViewer::config()->get('rewrite_hash_links');
} else {
Deprecation::notice('4.0', 'Use the "SSViewer.' . $optionName . '" config setting instead');
return SSViewer::config()->get($optionName);
}
}
/**
* @config
* @var boolean
@ -612,7 +546,7 @@ class SSViewer implements Flushable
// If we have our crazy base tag, then fix # links referencing the current page.
$rewrite = SSViewer::config()->get('rewrite_hash_links');
$rewrite = SSViewer::config()->uninherited('rewrite_hash_links');
if ($this->rewriteHashlinks && $rewrite) {
if (strpos($output, '<base') !== false) {
if ($rewrite === 'php') {
@ -669,7 +603,7 @@ class SSViewer implements Flushable
return $this->getParser()->compileString(
$content,
$template,
Director::isDev() && SSViewer::config()->get('source_file_comments')
Director::isDev() && SSViewer::config()->uninherited('source_file_comments')
);
}

View File

@ -4,7 +4,6 @@ namespace SilverStripe\View;
use InvalidArgumentException;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Object;
@ -82,7 +81,7 @@ class SSViewer_DataPresenter extends SSViewer_Scope
if (!is_array($details)) {
$details = array(
'method' => $details,
'casting' => ViewableData::config()->get('default_cast', Config::FIRST_SET)
'casting' => ViewableData::config()->uninherited('default_cast')
);
}
@ -185,7 +184,7 @@ class SSViewer_DataPresenter extends SSViewer_Scope
// If not provided, use default
if (!$casting) {
$casting = ViewableData::config()->get('default_cast', Config::FIRST_SET);
$casting = ViewableData::config()->uninherited('default_cast');
}
$obj = Injector::inst()->get($casting, false, array($property));

View File

@ -2,6 +2,7 @@
namespace SilverStripe\View;
use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\Dev\Debug;
/**
@ -119,23 +120,20 @@ class ThemeResourceLoader
$subpath = '';
}
// To do: implement more flexible module path lookup
$package = $parts[0];
switch ($package) {
case 'silverstripe/framework':
$modulePath = FRAMEWORK_DIR;
break;
case 'silverstripe/cms':
$modulePath = CMS_DIR;
break;
// Find matching module for this package
$module = ModuleLoader::instance()->getManifest()->getModule($package);
if ($module) {
$modulePath = $module->getRelativePath();
} else {
// fall back to dirname
list(, $modulePath) = explode('/', $parts[0], 2);
default:
list($vendor, $modulePath) = explode('/', $parts[0], 2);
// If the module is in the themes/<module>/ prefer that
if (is_dir(THEMES_PATH . '/' .$modulePath)) {
$modulePath = THEMES_DIR . '/' . $$modulePath;
}
// If the module is in the themes/<module>/ prefer that
if (is_dir(THEMES_PATH . '/' .$modulePath)) {
$modulePath = THEMES_DIR . '/' . $$modulePath;
}
}
return ltrim($modulePath . $subpath, '/');

View File

@ -6,7 +6,6 @@ use SilverStripe\Core\Object;
use SilverStripe\ORM\ArrayLib;
use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injector;
@ -277,7 +276,7 @@ class ViewableData extends Object implements IteratorAggregate
*/
public function castingHelper($field)
{
$specs = $this->config()->casting;
$specs = $this->config()->get('casting');
if (isset($specs[$field])) {
return $specs[$field];
}
@ -294,7 +293,11 @@ class ViewableData extends Object implements IteratorAggregate
}
// Fall back to default_cast
return $this->config()->get('default_cast');
$default = $this->config()->get('default_cast');
if (empty($default)) {
throw new \Exception("No default_cast");
}
return $default;
}
/**
@ -318,11 +321,13 @@ class ViewableData extends Object implements IteratorAggregate
*/
public function escapeTypeForField($field)
{
$class = $this->castingClass($field) ?: $this->config()->default_cast;
$class = $this->castingClass($field) ?: $this->config()->get('default_cast');
// TODO: It would be quicker not to instantiate the object, but to merely
// get its class from the Injector
return Injector::inst()->get($class, true)->config()->escape_type;
/** @var DBField $type */
$type = Injector::inst()->get($class, true);
return $type->config()->get('escape_type');
}
// TEMPLATE ACCESS LAYER -------------------------------------------------------------------------------------------

View File

@ -48,17 +48,12 @@
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use SilverStripe\Control\Director;
use SilverStripe\Control\Email\Email;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\Install\DatabaseAdapterRegistry;
use SilverStripe\Security\BasicAuth;
use SilverStripe\Security\Security;
if ($envType = getenv('SS_ENVIRONMENT_TYPE')) {
Director::config()->environment_type = $envType;
}
global $database;
// No database provided

View File

@ -44,8 +44,8 @@ class Sources implements Resettable
array_splice($moduleNames, $idx, 1);
}
// Get the order from the config syste (lowest to highest)
$order = static::config()->get('module_priority');
// Get the order from the config system (lowest to highest)
$order = Sources::config()->uninherited('module_priority');
// Find all modules that don't have their order specified by the config system
$unspecified = array_diff($moduleNames, $order);

View File

@ -135,7 +135,7 @@ class YamlWriter implements Writer
{
// Strip non-plural keys away
if (is_array($value)) {
$forms = i18n::config()->get('plurals');
$forms = i18n::config()->uninherited('plurals');
$forms = array_combine($forms, $forms);
return array_intersect_key($value, $forms);
}

View File

@ -99,10 +99,10 @@ class i18nTextCollector
{
$this->defaultLocale = $locale
? $locale
: i18n::getData()->langFromLocale(i18n::config()->get('default_locale'));
: i18n::getData()->langFromLocale(i18n::config()->uninherited('default_locale'));
$this->basePath = Director::baseFolder();
$this->baseSavePath = Director::baseFolder();
$this->setWarnOnEmptyDefault(i18n::config()->get('missing_default_warning'));
$this->setWarnOnEmptyDefault(i18n::config()->uninherited('missing_default_warning'));
}
/**

View File

@ -163,7 +163,7 @@ class i18n implements TemplateGlobalProvider
}
// Encourage the provision of default values so that text collector can discover new strings
if (!$default && static::config()->get('missing_default_warning')) {
if (!$default && i18n::config()->uninherited('missing_default_warning')) {
user_error("Missing default for localisation key $entity", E_USER_WARNING);
}
@ -232,7 +232,7 @@ class i18n implements TemplateGlobalProvider
public static function parse_plurals($string)
{
if (strstr($string, '|') && strstr($string, '{count}')) {
$keys = i18n::config()->get('default_plurals');
$keys = i18n::config()->uninherited('default_plurals');
$values = explode('|', $string);
if (count($keys) == count($values)) {
return array_combine($keys, $values);
@ -251,7 +251,7 @@ class i18n implements TemplateGlobalProvider
public static function encode_plurals($plurals)
{
// Validate against global plural list
$forms = static::config()->get('plurals');
$forms = i18n::config()->uninherited('plurals');
$forms = array_combine($forms, $forms);
$intersect = array_intersect_key($plurals, $forms);
if ($intersect) {
@ -343,7 +343,7 @@ class i18n implements TemplateGlobalProvider
*/
public static function get_locale()
{
return self::$current_locale ?: static::config()->get('default_locale');
return self::$current_locale ?: i18n::config()->uninherited('default_locale');
}
/**

View File

@ -19,12 +19,5 @@ $_SERVER['REQUEST_URI'] = BASE_URL;
// Fake a session
$_SESSION = null;
// Prepare manifest autoloader
$controller = new FakeController();
SapphireTest::use_test_manifest();
SapphireTest::set_is_running_test(true);
// Remove the error handler so that PHPUnit can add its own
restore_error_handler();

View File

@ -124,15 +124,15 @@ class GDTest extends SapphireTest
case 0:
$this->assertColourEquals(0, $samples[2]['alpha'], $tolerance);
$this->assertColourEquals(0, $samples[12]['alpha'], $tolerance);
break;
break;
case 1:
$this->assertColourEquals(0, $samples[2]['alpha'], $tolerance);
$this->assertColourEquals(127, $samples[12]['alpha'], $tolerance);
break;
break;
default:
$this->assertColourEquals(63, $samples[2]['alpha'], $tolerance);
$this->assertColourEquals(127, $samples[12]['alpha'], $tolerance);
break;
break;
}
}

View File

@ -13,14 +13,14 @@ use SilverStripe\Dev\SapphireTest;
class AssetStoreTest extends SapphireTest
{
/**
* @skipUpgrade
*/
public function setUp()
{
parent::setUp();
// Set backend and base url
/**
* @skipUpgrade
*/
TestAssetStore::activate('AssetStoreTest');
}
@ -332,7 +332,7 @@ class AssetStoreTest extends SapphireTest
*/
public function testLegacyFilenames()
{
Config::inst()->update(get_class(new FlysystemAssetStore()), 'legacy_filenames', true);
Config::modify()->set(FlysystemAssetStore::class, 'legacy_filenames', true);
$backend = $this->getBackend();
@ -442,12 +442,12 @@ class AssetStoreTest extends SapphireTest
$store = $this->getBackend();
// Disable legacy filenames
Config::inst()->update(get_class(new FlysystemAssetStore()), 'legacy_filenames', false);
Config::modify()->set(FlysystemAssetStore::class, 'legacy_filenames', false);
$this->assertEquals(AssetStore::CONFLICT_OVERWRITE, $store->getDefaultConflictResolution(null));
$this->assertEquals(AssetStore::CONFLICT_OVERWRITE, $store->getDefaultConflictResolution('somevariant'));
// Enable legacy filenames
Config::inst()->update(get_class(new FlysystemAssetStore()), 'legacy_filenames', true);
Config::modify()->set(FlysystemAssetStore::class, 'legacy_filenames', true);
$this->assertEquals(AssetStore::CONFLICT_RENAME, $store->getDefaultConflictResolution(null));
$this->assertEquals(AssetStore::CONFLICT_OVERWRITE, $store->getDefaultConflictResolution('somevariant'));
}

View File

@ -14,7 +14,7 @@ use SilverStripe\Assets\Flysystem\GeneratedAssetHandler;
use SilverStripe\Assets\Storage\DBFile;
use SilverStripe\Assets\File;
use SilverStripe\Assets\Folder;
use SilverStripe\Core\Config\Config;
use SilverStripe\Control\Director;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\View\Requirements;
@ -23,7 +23,6 @@ use SilverStripe\View\Requirements;
*/
class TestAssetStore extends FlysystemAssetStore
{
/**
* Enable disclosure of secure assets
*
@ -81,10 +80,10 @@ class TestAssetStore extends FlysystemAssetStore
Requirements::backend()->setAssetHandler($generated);
// Disable legacy and set defaults
Config::inst()->remove(get_class(new FlysystemAssetStore()), 'legacy_filenames');
Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', '/');
DBFile::config()->force_resample = false;
File::config()->force_resample = false;
FlysystemAssetStore::config()->set('legacy_filenames', false);
Director::config()->set('alternate_base_url', '/');
DBFile::config()->set('force_resample', false);
File::config()->set('force_resample', false);
self::reset();
self::$basedir = $basedir;
@ -136,18 +135,14 @@ class TestAssetStore extends FlysystemAssetStore
$asset = $asset->File;
}
// Extract filesystem used to store this object
/**
* @var TestAssetStore $assetStore
*/
/** @var TestAssetStore $assetStore */
$assetStore = Injector::inst()->get('AssetStore');
$fileID = $assetStore->getFileID($asset->Filename, $asset->Hash, $asset->Variant);
$filesystem = $assetStore->getProtectedFilesystem();
if (!$filesystem->has($fileID)) {
$filesystem = $assetStore->getPublicFilesystem();
}
/**
* @var Local $adapter
*/
/** @var Local $adapter */
$adapter = $filesystem->getAdapter();
return $adapter->applyPathPrefix($fileID);
}

View File

@ -2,7 +2,9 @@
namespace SilverStripe\Control\Tests;
use InvalidArgumentException;
use PHPUnit_Framework_Error;
use SilverStripe\Control\RequestHandler;
use SilverStripe\Control\Tests\ControllerTest\AccessBaseController;
use SilverStripe\Control\Tests\ControllerTest\AccessSecuredController;
use SilverStripe\Control\Tests\ControllerTest\AccessWildcardSecuredController;
@ -20,6 +22,7 @@ use SilverStripe\Control\HTTPRequest;
use SilverStripe\Dev\Deprecation;
use SilverStripe\Dev\FunctionalTest;
use SilverStripe\ORM\DataModel;
use SilverStripe\Security\Member;
use SilverStripe\View\SSViewer;
class ControllerTest extends FunctionalTest
@ -103,7 +106,7 @@ class ControllerTest extends FunctionalTest
public function testAllowedActions()
{
$adminUser = $this->objFromFixture('SilverStripe\\Security\\Member', 'admin');
$adminUser = $this->objFromFixture(Member::class, 'admin');
$response = $this->get("UnsecuredController/");
$this->assertEquals(
@ -121,7 +124,7 @@ class ControllerTest extends FunctionalTest
'when called with an action in the URL'
);
Config::inst()->update('SilverStripe\\Control\\RequestHandler', 'require_allowed_actions', false);
Config::modify()->merge(RequestHandler::class, 'require_allowed_actions', false);
$response = $this->get("UnsecuredController/index");
$this->assertEquals(
200,
@ -129,7 +132,7 @@ class ControllerTest extends FunctionalTest
'Access granted on index action without $allowed_actions on defining controller, ' .
'when called with an action in the URL, and explicitly allowed through config'
);
Config::inst()->update('SilverStripe\\Control\\RequestHandler', 'require_allowed_actions', true);
Config::modify()->merge(RequestHandler::class, 'require_allowed_actions', true);
$response = $this->get("UnsecuredController/method1");
$this->assertEquals(
@ -139,7 +142,7 @@ class ControllerTest extends FunctionalTest
'when called without an action in the URL'
);
Config::inst()->update('SilverStripe\\Control\\RequestHandler', 'require_allowed_actions', false);
Config::modify()->merge(RequestHandler::class, 'require_allowed_actions', false);
$response = $this->get("UnsecuredController/method1");
$this->assertEquals(
200,
@ -147,7 +150,7 @@ class ControllerTest extends FunctionalTest
'Access granted on action without $allowed_actions on defining controller, ' .
'when called without an action in the URL, and explicitly allowed through config'
);
Config::inst()->update('SilverStripe\\Control\\RequestHandler', 'require_allowed_actions', true);
Config::modify()->merge(RequestHandler::class, 'require_allowed_actions', true);
$response = $this->get("AccessBaseController/");
$this->assertEquals(
@ -313,13 +316,9 @@ class ControllerTest extends FunctionalTest
$this->session()->inst_set('loggedInAs', null);
}
/**
* @expectedException PHPUnit_Framework_Error
* @expectedExceptionMessage Wildcards (*) are no longer valid
*/
public function testWildcardAllowedActions()
{
Deprecation::set_enabled(true);
$this->setExpectedException(InvalidArgumentException::class, "Invalid allowed_action '*'");
$this->get('AccessWildcardSecuredController');
}
@ -399,7 +398,7 @@ class ControllerTest extends FunctionalTest
}
/**
* @covers SilverStripe\Control\Controller::hasAction
* @covers \SilverStripe\Control\Controller::hasAction
*/
public function testHasAction()
{
@ -463,7 +462,7 @@ class ControllerTest extends FunctionalTest
/* Controller::BaseURL no longer exists, but was just a direct call to Director::BaseURL, so not sure what this
* code was supposed to test
public function testBaseURL() {
Config::inst()->update('Director', 'alternate_base_url', '/baseurl/');
Config::modify()->merge('Director', 'alternate_base_url', '/baseurl/');
$this->assertEquals(Controller::BaseURL(), Director::BaseURL());
}
*/

View File

@ -31,7 +31,6 @@ class DirectorTest extends SapphireTest
{
parent::setUp();
// Hold the original request URI once so it doesn't get overwritten
if (!self::$originalRequestURI) {
self::$originalRequestURI = $_SERVER['REQUEST_URI'];
@ -42,18 +41,6 @@ class DirectorTest extends SapphireTest
$this->originalSession = $_SESSION;
$_SESSION = array();
Config::inst()->update(
'SilverStripe\\Control\\Director',
'rules',
array(
'DirectorTestRule/$Action/$ID/$OtherID' => TestController::class,
'en-nz/$Action/$ID/$OtherID' => array(
'Controller' => TestController::class,
'Locale' => 'en_NZ'
)
)
);
$headers = array(
'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'
);
@ -64,13 +51,29 @@ class DirectorTest extends SapphireTest
}
}
Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', '/');
Config::modify()->set(Director::class, 'alternate_base_url', '/');
}
protected function getExtraRoutes()
{
$rules = parent::getExtraRoutes();
$rules['DirectorTestRule/$Action/$ID/$OtherID'] = TestController::class;
$rules['en-nz/$Action/$ID/$OtherID'] = [
'Controller' => TestController::class,
'Locale' => 'en_NZ',
];
return $rules;
}
protected function setUpRoutes()
{
// Don't merge with any existing rules
Director::config()->set('rules', $this->getExtraRoutes());
}
public function tearDown()
{
// TODO Remove director rule, currently API doesnt allow this
$_GET = $this->originalGet;
$_SESSION = $this->originalSession;
@ -492,6 +495,7 @@ class DirectorTest extends SapphireTest
public function testUnmatchedRequestReturns404()
{
// Remove non-tested rules
$this->assertEquals(404, Director::test('no-route')->getStatusCode());
}

View File

@ -21,12 +21,7 @@ class FakeController extends Controller
$this->pushCurrent();
$request = new HTTPRequest(
(isset($_SERVER['X-HTTP-Method-Override']))
? $_SERVER['X-HTTP-Method-Override']
: $_SERVER['REQUEST_METHOD'],
'/'
);
$request = new HTTPRequest('GET', '/');
$this->setRequest($request);
$this->setResponse(new HTTPResponse());

View File

@ -27,13 +27,13 @@ class HTTPTest extends FunctionalTest
$this->assertNotEmpty($response->getHeader('Cache-Control'));
// Ensure max-age is zero for development.
Director::config()->update('environment_type', 'dev');
Director::set_environment_type('dev');
$response = new HTTPResponse($body, 200);
HTTP::add_cache_headers($response);
$this->assertContains('max-age=0', $response->getHeader('Cache-Control'));
// Ensure max-age setting is respected in production.
Director::config()->update('environment_type', 'live');
Director::set_environment_type('live');
$response = new HTTPResponse($body, 200);
HTTP::add_cache_headers($response);
$this->assertContains('max-age=30', explode(', ', $response->getHeader('Cache-Control')));
@ -59,7 +59,7 @@ class HTTPTest extends FunctionalTest
{
$body = "<html><head></head><body><h1>Mysite</h1></body></html>";
$response = new HTTPResponse($body, 200);
Director::config()->update('environment_type', 'live');
Director::set_environment_type('live');
HTTP::set_cache_age(30);
HTTP::add_cache_headers($response);

View File

@ -45,50 +45,36 @@ class RequestHandlingTest extends FunctionalTest
FormActionController::class
];
public function setUp()
public function getExtraRoutes()
{
parent::setUp();
$routes = parent::getExtraRoutes();
return array_merge(
$routes,
[
// If we don't request any variables, then the whole URL will get shifted off.
// This is fine, but it means that the controller will have to parse the Action from the URL itself.
'testGoodBase1' => TestController::class,
Director::config()->update(
'rules',
array(
// If we don't request any variables, then the whole URL will get shifted off.
// This is fine, but it means that the controller will have to parse the Action from the URL itself.
'testGoodBase1' => TestController::class,
// The double-slash indicates how much of the URL should be shifted off the stack.
// This is important for dealing with nested request handlers appropriately.
'testGoodBase2//$Action/$ID/$OtherID' => TestController::class,
// The double-slash indicates how much of the URL should be shifted off the stack.
// This is important for dealing with nested request handlers appropriately.
'testGoodBase2//$Action/$ID/$OtherID' => TestController::class,
// By default, the entire URL will be shifted off. This creates a bit of
// backward-incompatability, but makes the URL rules much more explicit.
'testBadBase/$Action/$ID/$OtherID' => TestController::class,
// By default, the entire URL will be shifted off. This creates a bit of
// backward-incompatability, but makes the URL rules much more explicit.
'testBadBase/$Action/$ID/$OtherID' => TestController::class,
// Rules with an extension always default to the index() action
'testBaseWithExtension/virtualfile.xml' => TestController::class,
// Rules with an extension always default to the index() action
'testBaseWithExtension/virtualfile.xml' => TestController::class,
// Without the extension, the methodname should be matched
'testBaseWithExtension//$Action/$ID/$OtherID' => TestController::class,
// Without the extension, the methodname should be matched
'testBaseWithExtension//$Action/$ID/$OtherID' => TestController::class,
// Test nested base
'testParentBase/testChildBase//$Action/$ID/$OtherID' => TestController::class,
)
// Test nested base
'testParentBase/testChildBase//$Action/$ID/$OtherID' => TestController::class,
]
);
}
// public function testRequestHandlerChainingLatestParams() {
// $c = new RequestHandlingTest_Controller();
// $c->init();
// $response = $c->handleRequest(new HTTPRequest('GET', 'testGoodBase1/TestForm/fields/MyField'));
// $this->assertEquals(
// $c->getRequest()->latestParams(),
// array(
// 'Action' => 'fields',
// 'ID' => 'MyField'
// )
// );
// }
public function testConstructedWithNullRequest()
{
$r = new RequestHandler();

View File

@ -2,32 +2,16 @@
namespace SilverStripe\Core\Tests\Config;
use SilverStripe\Config\MergeStrategy\Priority;
use SilverStripe\Core\Object;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\Deprecation;
use SilverStripe\Dev\SapphireTest;
class ConfigTest extends SapphireTest
{
protected $depSettings = null;
public function setUp()
{
parent::setUp();
$this->depSettings = Deprecation::dump_settings();
Deprecation::set_enabled(false);
}
public function tearDown()
{
Deprecation::restore_settings($this->depSettings);
parent::tearDown();
}
public function testNest()
{
// Check basic config
$this->assertEquals(3, Config::inst()->get(ConfigTest\TestNest::class, 'foo'));
$this->assertEquals(5, Config::inst()->get(ConfigTest\TestNest::class, 'bar'));
@ -38,7 +22,7 @@ class ConfigTest extends SapphireTest
$this->assertEquals(5, Config::inst()->get(ConfigTest\TestNest::class, 'bar'));
// Test nested data can be updated
Config::inst()->update(ConfigTest\TestNest::class, 'foo', 4);
Config::modify()->merge(ConfigTest\TestNest::class, 'foo', 4);
$this->assertEquals(4, Config::inst()->get(ConfigTest\TestNest::class, 'foo'));
$this->assertEquals(5, Config::inst()->get(ConfigTest\TestNest::class, 'bar'));
@ -51,45 +35,85 @@ class ConfigTest extends SapphireTest
public function testUpdateStatic()
{
$this->assertEquals(
Config::inst()->get(ConfigTest\First::class, 'first', Config::FIRST_SET),
array('test_1')
['test_1'],
Config::inst()->get(ConfigTest\First::class, 'first')
);
$this->assertEquals(
Config::inst()->get(ConfigTest\Second::class, 'first', Config::FIRST_SET),
array('test_2')
[
'test_1',
'test_2'
],
Config::inst()->get(ConfigTest\Second::class, 'first')
);
$this->assertEquals(
Config::inst()->get(ConfigTest\Third::class, 'first', Config::FIRST_SET),
array('test_3')
[ 'test_2' ],
Config::inst()->get(ConfigTest\Second::class, 'first', true)
);
$this->assertEquals(
[
'test_1',
'test_2',
'test_3'
],
Config::inst()->get(ConfigTest\Third::class, 'first')
);
$this->assertEquals(
[ 'test_3' ],
Config::inst()->get(ConfigTest\Third::class, 'first', true)
);
Config::inst()->update(ConfigTest\First::class, 'first', array('test_1_2'));
Config::inst()->update(ConfigTest\Third::class, 'first', array('test_3_2'));
Config::inst()->update(ConfigTest\Fourth::class, 'first', array('test_4'));
Config::modify()->merge(ConfigTest\First::class, 'first', array('test_1_2'));
Config::modify()->merge(ConfigTest\Third::class, 'first', array('test_3_2'));
Config::modify()->merge(ConfigTest\Fourth::class, 'first', array('test_4'));
$this->assertEquals(
Config::inst()->get(ConfigTest\First::class, 'first', Config::FIRST_SET),
array('test_1_2', 'test_1')
);
Config::inst()->update(ConfigTest\Fourth::class, 'second', array('test_4'));
Config::inst()->update(ConfigTest\Third::class, 'second', array('test_3_2'));
$this->assertEquals(
Config::inst()->get(ConfigTest\Fourth::class, 'second', Config::FIRST_SET),
array('test_4')
['test_1', 'test_1_2'],
Config::inst()->get(ConfigTest\First::class, 'first')
);
$this->assertEquals(
Config::inst()->get(ConfigTest\Third::class, 'second', Config::FIRST_SET),
array('test_3_2', 'test_3')
['test_1', 'test_1_2'],
Config::inst()->get(ConfigTest\First::class, 'first', true)
);
Config::inst()->remove(ConfigTest\Third::class, 'second');
$this->assertEquals(array(), Config::inst()->get(ConfigTest\Third::class, 'second'));
Config::inst()->update(ConfigTest\Third::class, 'second', array('test_3_2'));
Config::modify()->merge(ConfigTest\Fourth::class, 'second', array('test_4'));
Config::modify()->merge(ConfigTest\Third::class, 'second', array('test_3_2'));
$this->assertEquals(
Config::inst()->get(ConfigTest\Third::class, 'second', Config::FIRST_SET),
array('test_3_2')
['test_1', 'test_3', 'test_3_2', 'test_4'],
Config::inst()->get(ConfigTest\Fourth::class, 'second')
);
$this->assertEquals(
['test_4'],
Config::inst()->get(ConfigTest\Fourth::class, 'second', true)
);
$this->assertEquals(
['test_1', 'test_3', 'test_3_2'],
Config::inst()->get(ConfigTest\Third::class, 'second')
);
$this->assertEquals(
['test_3', 'test_3_2'],
Config::inst()->get(ConfigTest\Third::class, 'second', true)
);
Config::modify()->remove(ConfigTest\Third::class, 'second');
$this->assertEquals(
['test_1'],
Config::inst()->get(ConfigTest\Third::class, 'second')
);
$this->assertTrue(
Config::inst()->exists(ConfigTest\Third::class, 'second')
);
$this->assertEquals(
null,
Config::inst()->get(ConfigTest\Third::class, 'second', true)
);
$this->assertFalse(
Config::inst()->exists(ConfigTest\Third::class, 'second', true)
);
Config::modify()->merge(ConfigTest\Third::class, 'second', ['test_3_2']);
$this->assertEquals(
['test_1', 'test_3_2'],
Config::inst()->get(ConfigTest\Third::class, 'second')
);
}
@ -97,30 +121,30 @@ class ConfigTest extends SapphireTest
{
// Booleans
$this->assertTrue(Config::inst()->get(ConfigTest\First::class, 'bool'));
Config::inst()->update(ConfigTest\First::class, 'bool', false);
Config::modify()->merge(ConfigTest\First::class, 'bool', false);
$this->assertFalse(Config::inst()->get(ConfigTest\First::class, 'bool'));
Config::inst()->update(ConfigTest\First::class, 'bool', true);
Config::modify()->merge(ConfigTest\First::class, 'bool', true);
$this->assertTrue(Config::inst()->get(ConfigTest\First::class, 'bool'));
// Integers
$this->assertEquals(42, Config::inst()->get(ConfigTest\First::class, 'int'));
Config::inst()->update(ConfigTest\First::class, 'int', 0);
Config::modify()->merge(ConfigTest\First::class, 'int', 0);
$this->assertEquals(0, Config::inst()->get(ConfigTest\First::class, 'int'));
Config::inst()->update(ConfigTest\First::class, 'int', 42);
Config::modify()->merge(ConfigTest\First::class, 'int', 42);
$this->assertEquals(42, Config::inst()->get(ConfigTest\First::class, 'int'));
// Strings
$this->assertEquals('value', Config::inst()->get(ConfigTest\First::class, 'string'));
Config::inst()->update(ConfigTest\First::class, 'string', '');
Config::modify()->merge(ConfigTest\First::class, 'string', '');
$this->assertEquals('', Config::inst()->get(ConfigTest\First::class, 'string'));
Config::inst()->update(ConfigTest\First::class, 'string', 'value');
Config::modify()->merge(ConfigTest\First::class, 'string', 'value');
$this->assertEquals('value', Config::inst()->get(ConfigTest\First::class, 'string'));
// Nulls
$this->assertEquals('value', Config::inst()->get(ConfigTest\First::class, 'nullable'));
Config::inst()->update(ConfigTest\First::class, 'nullable', null);
Config::modify()->merge(ConfigTest\First::class, 'nullable', null);
$this->assertNull(Config::inst()->get(ConfigTest\First::class, 'nullable'));
Config::inst()->update(ConfigTest\First::class, 'nullable', 'value');
Config::modify()->merge(ConfigTest\First::class, 'nullable', 'value');
$this->assertEquals('value', Config::inst()->get(ConfigTest\First::class, 'nullable'));
}
@ -138,8 +162,8 @@ class ConfigTest extends SapphireTest
$this->assertEquals(Config::inst()->get(ConfigTest\First::class, 'third', Config::UNINHERITED), 'test_1');
$this->assertEquals(Config::inst()->get(ConfigTest\Fourth::class, 'third', Config::UNINHERITED), null);
Config::inst()->update(ConfigTest\First::class, 'first', array('test_1b'));
Config::inst()->update(ConfigTest\Second::class, 'first', array('test_2b'));
Config::modify()->merge(ConfigTest\First::class, 'first', array('test_1b'));
Config::modify()->merge(ConfigTest\Second::class, 'first', array('test_2b'));
// Check that it can be applied to parent and subclasses, and queried directly
$this->assertContains(
@ -161,54 +185,78 @@ class ConfigTest extends SapphireTest
// Subclasses that don't have the static explicitly defined should allow definition, also
// This also checks that set can be called after the first uninherited get()
// call (which can be buggy due to caching)
Config::inst()->update(ConfigTest\Fourth::class, 'first', array('test_4b'));
Config::modify()->merge(ConfigTest\Fourth::class, 'first', array('test_4b'));
$this->assertContains('test_4b', Config::inst()->get(ConfigTest\Fourth::class, 'first', Config::UNINHERITED));
}
public function testCombinedStatic()
{
$this->assertEquals(
Config::inst()->get(ConfigTest\Combined3::class, 'first'),
array('test_3', 'test_2', 'test_1')
['test_1', 'test_2', 'test_3'],
ConfigTest\Combined3::config()->get('first')
);
// test that null values are ignored, but values on either side are still merged
// Test that unset values are ignored
$this->assertEquals(
Config::inst()->get(ConfigTest\Combined3::class, 'second'),
array('test_3', 'test_1')
['test_1', 'test_3'],
ConfigTest\Combined3::config()->get('second')
);
}
public function testMerges()
{
$result = array('A' => 1, 'B' => 2, 'C' => 3);
Config::merge_array_low_into_high($result, array('C' => 4, 'D' => 5));
$this->assertEquals($result, array('A' => 1, 'B' => 2, 'C' => 3, 'D' => 5));
$result = array('A' => 1, 'B' => 2, 'C' => 3);
Config::merge_array_high_into_low($result, array('C' => 4, 'D' => 5));
$this->assertEquals($result, array('A' => 1, 'B' => 2, 'C' => 4, 'D' => 5));
$result = array('A' => 1, 'B' => 2, 'C' => array(1, 2, 3));
Config::merge_array_low_into_high($result, array('C' => array(4, 5, 6), 'D' => 5));
$this->assertEquals($result, array('A' => 1, 'B' => 2, 'C' => array(1, 2, 3, 4, 5, 6), 'D' => 5));
$result = array('A' => 1, 'B' => 2, 'C' => array(1, 2, 3));
Config::merge_array_high_into_low($result, array('C' => array(4, 5, 6), 'D' => 5));
$this->assertEquals($result, array('A' => 1, 'B' => 2, 'C' => array(4, 5, 6, 1, 2, 3), 'D' => 5));
$result = array('A' => 1, 'B' => 2, 'C' => array('Foo' => 1, 'Bar' => 2), 'D' => 3);
Config::merge_array_low_into_high($result, array('C' => array('Bar' => 3, 'Baz' => 4)));
$result = Priority::mergeArray(
['A' => 1, 'B' => 2, 'C' => 3],
['C' => 4, 'D' => 5]
);
$this->assertEquals(
$result,
array('A' => 1, 'B' => 2, 'C' => array('Foo' => 1, 'Bar' => 2, 'Baz' => 4), 'D' => 3)
['A' => 1, 'B' => 2, 'C' => 3, 'D' => 5],
$result
);
$result = array('A' => 1, 'B' => 2, 'C' => array('Foo' => 1, 'Bar' => 2), 'D' => 3);
Config::merge_array_high_into_low($result, array('C' => array('Bar' => 3, 'Baz' => 4)));
$result = Priority::mergeArray(
['C' => 4, 'D' => 5],
['A' => 1, 'B' => 2, 'C' => 3]
);
$this->assertEquals(
$result,
array('A' => 1, 'B' => 2, 'C' => array('Foo' => 1, 'Bar' => 3, 'Baz' => 4), 'D' => 3)
['A' => 1, 'B' => 2, 'C' => 4, 'D' => 5],
$result
);
$result = Priority::mergeArray(
[ 'C' => [4, 5, 6], 'D' => 5 ],
[ 'A' => 1, 'B' => 2, 'C' => [1, 2, 3] ]
);
$this->assertEquals(
['A' => 1, 'B' => 2, 'C' => [1, 2, 3, 4, 5, 6], 'D' => 5],
$result
);
$result = Priority::mergeArray(
['A' => 1, 'B' => 2, 'C' => [1, 2, 3]],
['C' => [4, 5, 6], 'D' => 5]
);
$this->assertEquals(
['A' => 1, 'B' => 2, 'C' => [4, 5, 6, 1, 2, 3], 'D' => 5],
$result
);
$result = Priority::mergeArray(
['A' => 1, 'B' => 2, 'C' => ['Foo' => 1, 'Bar' => 2], 'D' => 3],
['C' => ['Bar' => 3, 'Baz' => 4]]
);
$this->assertEquals(
['A' => 1, 'B' => 2, 'C' => ['Foo' => 1, 'Bar' => 2, 'Baz' => 4], 'D' => 3],
$result
);
$result = Priority::mergeArray(
['C' => ['Bar' => 3, 'Baz' => 4]],
['A' => 1, 'B' => 2, 'C' => ['Foo' => 1, 'Bar' => 2], 'D' => 3]
);
$this->assertEquals(
['A' => 1, 'B' => 2, 'C' => ['Foo' => 1, 'Bar' => 3, 'Baz' => 4], 'D' => 3],
$result
);
}
@ -240,41 +288,4 @@ class ConfigTest extends SapphireTest
$this->assertTrue(empty($config->bar));
$this->assertNull($config->bar);
}
public function testFragmentOrder()
{
$this->markTestIncomplete();
}
public function testCacheCleaning()
{
$cache = new ConfigTest\ConfigTestMemCache();
for ($i = 0; $i < 1000;
$i++) {
$cache->set($i, $i);
}
$this->assertEquals(1000, count($cache->cache));
$cache->clean();
$this->assertEquals(0, count($cache->cache), 'Clean clears all items');
$this->assertFalse($cache->get(1), 'Clean clears all items');
$cache->set(1, 1, array('Foo'));
$this->assertEquals(1, count($cache->cache));
$this->assertEquals(1, count($cache->tags));
$cache->clean('Foo');
$this->assertEquals(0, count($cache->tags), 'Clean items with matching tag');
$this->assertFalse($cache->get(1), 'Clean items with matching tag');
$cache->set(1, 1, array('Foo', 'Bar'));
$this->assertEquals(2, count($cache->tags));
$this->assertEquals(1, count($cache->cache));
$cache->clean('Bar');
$this->assertEquals(1, count($cache->tags));
$this->assertEquals(0, count($cache->cache), 'Clean items with any single matching tag');
$this->assertFalse($cache->get(1), 'Clean items with any single matching tag');
}
}

View File

@ -2,17 +2,20 @@
namespace SilverStripe\Core\Tests\Config\ConfigTest;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Dev\TestOnly;
class Combined1 extends Config implements TestOnly
class Combined1 implements TestOnly
{
use Configurable;
/**
* @config
*/
private static $first = array('test_1');
private static $first = ['test_1'];
/**
* @config
*/
private static $second = array('test_1');
private static $second = ['test_1'];
}

View File

@ -4,6 +4,5 @@ namespace SilverStripe\Core\Tests\Config\ConfigTest;
class Combined2 extends Combined1
{
private static $first = array('test_2');
private static $second = null;
private static $first = ['test_2'];
}

View File

@ -4,6 +4,6 @@ namespace SilverStripe\Core\Tests\Config\ConfigTest;
class Combined3 extends Combined2
{
private static $first = array('test_3');
private static $second = array('test_3');
private static $first = ['test_3'];
private static $second = ['test_3'];
}

View File

@ -1,12 +0,0 @@
<?php
namespace SilverStripe\Core\Tests\Config\ConfigTest;
use SilverStripe\Core\Config\Config_MemCache;
use SilverStripe\Dev\TestOnly;
class ConfigTestMemCache extends Config_MemCache implements TestOnly
{
public $cache;
public $tags;
}

View File

@ -13,7 +13,7 @@ class InjectorTestConfigLocator extends SilverStripeServiceConfigurationLocator
switch ($name) {
case TestObject::class:
return $this->configs[$name] = array(
return $this->configs[$name] = array(
'class' => ConstructableObject::class,
'constructor' => array(
'%$'.OtherTestObject::class
@ -21,7 +21,7 @@ class InjectorTestConfigLocator extends SilverStripeServiceConfigurationLocator
);
case 'ConfigConstructor':
return $this->configs[$name] = array(
return $this->configs[$name] = array(
'class' => ConstructableObject::class,
'constructor' => array('value')
);

View File

@ -50,8 +50,8 @@ class ClassManifestTest extends SapphireTest
'classc' => "{$this->base}/module/classes/ClassC.php",
'classd' => "{$this->base}/module/classes/ClassD.php",
'classe' => "{$this->base}/module/classes/ClassE.php",
'sstemplateparser' => FRAMEWORK_PATH."/View/SSTemplateParser.php",
'sstemplateparseexception' => FRAMEWORK_PATH."/View/SSTemplateParseException.php"
'sstemplateparser' => FRAMEWORK_PATH."/src/View/SSTemplateParser.php",
'sstemplateparseexception' => FRAMEWORK_PATH."/src/View/SSTemplateParseException.php"
);
$this->assertEquals($expect, $this->manifest->getClasses());
}

View File

@ -2,15 +2,30 @@
namespace SilverStripe\Core\Tests\Manifest;
use Dotenv\Loader;
use SilverStripe\Config\Collections\MemoryConfigCollection;
use SilverStripe\Control\Director;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Manifest\ConfigManifest;
use SilverStripe\Core\Config\CoreConfigFactory;
use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\Core\Manifest\ModuleManifest;
use SilverStripe\Dev\SapphireTest;
use ReflectionProperty;
use Symfony\Component\Cache\Simple\ArrayCache;
class ConfigManifestTest extends SapphireTest
{
public function setUp()
{
parent::setUp();
$moduleManifest = new ModuleManifest(dirname(__FILE__) . '/fixtures/configmanifest');
ModuleLoader::instance()->pushManifest($moduleManifest);
}
public function tearDown()
{
ModuleLoader::instance()->popManifest();
parent::tearDown();
}
/**
* This is a helper method for getting a new manifest
*
@ -19,378 +34,33 @@ class ConfigManifestTest extends SapphireTest
*/
protected function getConfigFixtureValue($name)
{
$manifest = new ConfigManifest(dirname(__FILE__).'/fixtures/configmanifest', true, true);
return $manifest->get(__CLASS__, $name);
return $this->getTestConfig()->get(__CLASS__, $name);
}
/**
* Build a new config based on YMl manifest
*
* @return MemoryConfigCollection
*/
public function getTestConfig()
{
$config = new MemoryConfigCollection();
$transformer = CoreConfigFactory::inst()->buildYamlTransformerForPath(dirname(__FILE__) . '/fixtures/configmanifest');
$config->transform([$transformer]);
return $config;
}
/**
* This is a helper method for displaying a relevant message about a parsing failure
*
* @param string $path
* @return string
*/
protected function getParsedAsMessage($path)
{
return sprintf('Reference path "%s" failed to parse correctly', $path);
}
/**
* A helper method to return a mock of the cache in order to test expectations and reduce dependency
*
* @return \PHPUnit_Framework_MockObject_MockObject
*/
protected function getCacheMock()
{
return $this->getMock(
ArrayCache::class,
array('set', 'get'),
array(),
'',
false
);
}
/**
* A helper method to return a mock of the manifest in order to test expectations and reduce dependency
*
* @param $methods
* @return \PHPUnit_Framework_MockObject_MockObject
*/
protected function getManifestMock($methods)
{
return $this->getMock(
ConfigManifest::class,
$methods,
array(), // no constructor arguments
'', // default
false // don't call the constructor
);
}
/**
* Test the caching functionality when we are forcing regeneration
*
* 1. Test that regenerate is called in the default case and that cache->load isn't
* 2. Test that save is called correctly after the regeneration
*/
public function testCachingForceRegeneration()
{
// Test that regenerate is called correctly.
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
$manifest->expects($this->once()) // regenerate should be called once
->method('regenerate')
->with($this->equalTo(true)); // includeTests = true
// Set up a cache where we expect load to never be called
$cache = $this->getCacheMock();
$cache->expects($this->never())
->method('get');
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', true, true);
// Test that save is called correctly
$manifest = $this->getManifestMock(array('getCache'));
$cache = $this->getCacheMock();
$cache->expects($this->atLeastOnce())
->method('set');
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', true, true);
}
/**
* Test the caching functionality when we are not forcing regeneration
*
* 1. Test that load is called
* 2. Test the regenerate is called when the cache is unprimed
* 3. Test that when there is a value in the cache regenerate isn't called
*/
public function testCachingNotForceRegeneration()
{
// Test that load is called
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
// Load should be called twice
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('get');
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', true, false);
// Now test that regenerate is called because the cache is unprimed
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('get')
->will($this->onConsecutiveCalls(false, false));
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->expects($this->once())
->method('regenerate')
->with($this->equalTo(false)); //includeTests = false
$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', false, false);
// Now test that when there is a value in the cache that regenerate isn't called
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('get')
->will($this->onConsecutiveCalls(array(), array()));
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->expects($this->never())
->method('regenerate');
$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('get');
$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('get')
->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('get')
->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
*/
public function testAddYAMLConfigFileReferencePathParsing()
{
// Use a mock to avoid testing unrelated functionality
$manifest = $this->getManifestMock(array('addModule'));
// This tests that the addModule method is called with the correct value
$manifest->expects($this->once())
->method('addModule')
->with($this->equalTo(dirname(__FILE__).'/fixtures/configmanifest/mysite'));
// Call the method to be tested
$manifest->addYAMLConfigFile(
'addyamlconfigfile.yml',
dirname(__FILE__).'/fixtures/configmanifest/mysite/_config/addyamlconfigfile.yml',
false
);
// There is no getter for yamlConfigFragments
$property = new ReflectionProperty('SilverStripe\\Core\\Manifest\\ConfigManifest', 'yamlConfigFragments');
$property->setAccessible(true);
// Get the result back from the parsing
$result = $property->getValue($manifest);
$this->assertEquals(
array(
array(
'module' => 'mysite',
'file' => 'testfile',
'name' => 'fragment',
),
),
@$result[0]['after'],
$this->getParsedAsMessage('mysite/testfile#fragment')
);
$this->assertEquals(
array(
array(
'module' => 'test-module',
'file' => 'testfile',
'name' => 'fragment',
),
),
@$result[1]['after'],
$this->getParsedAsMessage('test-module/testfile#fragment')
);
$this->assertEquals(
array(
array(
'module' => '*',
'file' => '*',
'name' => '*',
),
),
@$result[2]['after'],
$this->getParsedAsMessage('*')
);
$this->assertEquals(
array(
array(
'module' => '*',
'file' => 'testfile',
'name' => '*'
),
),
@$result[3]['after'],
$this->getParsedAsMessage('*/testfile')
);
$this->assertEquals(
array(
array(
'module' => '*',
'file' => '*',
'name' => 'fragment'
),
),
@$result[4]['after'],
$this->getParsedAsMessage('*/*#fragment')
);
$this->assertEquals(
array(
array(
'module' => '*',
'file' => '*',
'name' => 'fragment'
),
),
@$result[5]['after'],
$this->getParsedAsMessage('#fragment')
);
$this->assertEquals(
array(
array(
'module' => 'test-module',
'file' => '*',
'name' => 'fragment'
),
),
@$result[6]['after'],
$this->getParsedAsMessage('test-module#fragment')
);
$this->assertEquals(
array(
array(
'module' => 'test-module',
'file' => '*',
'name' => '*'
),
),
@$result[7]['after'],
$this->getParsedAsMessage('test-module')
);
$this->assertEquals(
array(
array(
'module' => 'test-module',
'file' => '*',
'name' => '*'
),
),
@$result[8]['after'],
$this->getParsedAsMessage('test-module/*')
);
$this->assertEquals(
array(
array(
'module' => 'test-module',
'file' => '*',
'name' => '*'
),
),
@$result[9]['after'],
$this->getParsedAsMessage('test-module/*/#*')
);
}
public function testClassRules()
{
$config = $this->getConfigFixtureValue('Class');
@ -427,8 +97,7 @@ class ConfigManifestTest extends SapphireTest
public function testEnvVarSetRules()
{
$loader = new \Dotenv\Loader(null);
$loader = new Loader(null);
$loader->setEnvironmentVariable('ENVVARSET_FOO', 1);
$config = $this->getConfigFixtureValue('EnvVarSet');
@ -465,10 +134,10 @@ class ConfigManifestTest extends SapphireTest
public function testEnvOrConstantMatchesValueRules()
{
$loader = new \Dotenv\Loader(null);
$loader = new Loader(null);
$loader->setEnvironmentVariable('ENVORCONSTANTMATCHESVALUE_FOO', 'Foo');
define('ENVORCONSTANTMATCHESVALUE_BAR', 'Bar');
$loader->setEnvironmentVariable('CONSTANTMATCHESVALUE_FOO', 'Foo');
define('CONSTANTMATCHESVALUE_BAR', 'Bar');
$config = $this->getConfigFixtureValue('EnvOrConstantMatchesValue');
$this->assertEquals(
@ -505,43 +174,22 @@ class ConfigManifestTest extends SapphireTest
public function testEnvironmentRules()
{
foreach (array('dev', 'test', 'live') as $env) {
Config::nest();
Config::inst()->update('SilverStripe\\Control\\Director', 'environment_type', $env);
Director::set_environment_type($env);
$config = $this->getConfigFixtureValue('Environment');
foreach (array('dev', 'test', 'live') as $check) {
$this->assertEquals(
$env == $check ? $check : 'not'.$check,
@$config[ucfirst($check).'Environment'],
'Only & except rules correctly detect environment'
'Only & except rules correctly detect environment in env ' . $env
);
}
Config::unnest();
}
}
public function testDynamicEnvironmentRules()
{
// First, make sure environment_type is live
Director::config()->update('environment_type', 'live');
$this->assertEquals('live', Director::config()->get('environment_type'));
// Then, load in a new manifest, which includes a _config.php that sets environment_type to dev
$manifest = new ConfigManifest(dirname(__FILE__).'/fixtures/configmanifest_dynamicenv', true, true);
Config::inst()->pushConfigYamlManifest($manifest);
// Make sure that stuck
$this->assertEquals('dev', Director::config()->get('environment_type'));
// And that the dynamic rule was calculated correctly
$this->assertEquals('dev', Config::inst()->get(__CLASS__, 'DynamicEnvironment'));
}
public function testMultipleRules()
{
$loader = new \Dotenv\Loader(null);
$loader = new Loader(null);
$loader->setEnvironmentVariable('MULTIPLERULES_ENVVARIABLESET', 1);
define('MULTIPLERULES_DEFINEDCONSTANT', 'defined');
@ -577,121 +225,4 @@ class ConfigManifestTest extends SapphireTest
'Fragment is included if both blocks succeed.'
);
}
public function testRelativeOrder()
{
$accessor = new ConfigManifestTest\ConfigManifestAccess(BASE_PATH, true, false);
// A fragment with a fully wildcard before rule
$beforeWildcarded = array(
'module' => 'foo', 'file' => 'alpha', 'name' => '1',
'before' => array(array('module' => '*', 'file' => '*', 'name' => '*'))
);
// A fragment with a fully wildcard before rule and a fully explicit after rule
$beforeWildcardedAfterExplicit = array_merge(
$beforeWildcarded,
array(
'after' => array(array('module' => 'bar', 'file' => 'beta', 'name' => '2'))
)
);
// A fragment with a fully wildcard before rule and two fully explicit after rules
$beforeWildcardedAfterTwoExplicitRules = array_merge(
$beforeWildcarded,
array(
'after' => array(
array('module' => 'bar', 'file' => 'beta', 'name' => '2'),
array('module' => 'baz', 'file' => 'gamma', 'name' => '3')
)
)
);
// A fragment with a fully wildcard before rule and a partially explicit after rule
$beforeWildcardedAfterPartialWildcarded = array_merge(
$beforeWildcarded,
array(
'after' => array(array('module' => 'bar', 'file' => 'beta', 'name' => '*'))
)
);
// Wildcard should match any module
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcarded,
array('module' => 'qux', 'file' => 'delta', 'name' => '4')
),
'before'
);
// Wildcard should match any module even if there is an opposing rule, if opposing rule doesn't match
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterExplicit,
array('module' => 'qux', 'file' => 'delta', 'name' => '4')
),
'before'
);
// Wildcard should match any module even if there is an opposing rule, if opposing rule doesn't match, no
// matter how many opposing rules
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterExplicit,
array('module' => 'qux', 'file' => 'delta', 'name' => '4')
),
'before'
);
// Wildcard should match any module even if there is an opposing rule, if opposing rule doesn't match
// (even if some portions do)
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterExplicit,
array('module' => 'bar', 'file' => 'beta', 'name' => 'nomatchy')
),
'before'
);
// When opposing rule matches, wildcard should be ignored
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterExplicit,
array('module' => 'bar', 'file' => 'beta', 'name' => '2')
),
'after'
);
// When any one of mutiple opposing rule exists, wildcard should be ignored
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterTwoExplicitRules,
array('module' => 'bar', 'file' => 'beta', 'name' => '2')
),
'after'
);
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterTwoExplicitRules,
array('module' => 'baz', 'file' => 'gamma', 'name' => '3')
),
'after'
);
// When two opposed wildcard rules, and more specific one doesn't match, other should win
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterPartialWildcarded,
array('module' => 'qux', 'file' => 'delta', 'name' => '4')
),
'before'
);
// When two opposed wildcard rules, and more specific one does match, more specific one should win
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterPartialWildcarded,
array('module' => 'bar', 'file' => 'beta', 'name' => 'wildcardmatchy')
),
'after'
);
}
}

View File

@ -1,13 +0,0 @@
<?php
namespace SilverStripe\Core\Tests\Manifest\ConfigManifestTest;
use SilverStripe\Core\Manifest\ConfigManifest;
class ConfigManifestAccess extends ConfigManifest
{
public function relativeOrder($a, $b)
{
return parent::relativeOrder($a, $b);
}
}

View File

@ -1,38 +0,0 @@
<?php
namespace SilverStripe\Core\Tests\Manifest;
use SilverStripe\Core\Manifest\ConfigStaticManifest;
use SilverStripe\Dev\SapphireTest;
class ConfigStaticManifestTest extends SapphireTest
{
private static $testString = 'string';
private static $testArray = array('foo' => 'bar');
protected static $ignored = true;
public function testGet()
{
$manifest = new ConfigStaticManifest();
// Test madeup value
$this->assertNull($manifest->get(__CLASS__, 'madeup', null));
// Test string value
$this->assertEquals('string', $manifest->get(__CLASS__, 'testString'));
// Test array value
$this->assertEquals(array('foo' => 'bar'), $manifest->get(__CLASS__, 'testArray'));
// Test to ensure we're only picking up private statics
$this->assertNull($manifest->get(__CLASS__, 'ignored', null));
// Test madeup class
if (!class_exists('aonsffgrgx')) {
$this->assertNull($manifest->get('aonsffgrgx', 'madeup', null));
}
}
}

View File

@ -263,8 +263,8 @@ class NamespacedClassManifestTest extends SapphireTest
'silverstripe\test\classf' => "{$this->base}/module/classes/ClassF.php",
'silverstripe\test\classg' => "{$this->base}/module/classes/ClassG.php",
'silverstripe\test\classh' => "{$this->base}/module/classes/ClassH.php",
'sstemplateparser' => FRAMEWORK_PATH."/View/SSTemplateParser.php",
'sstemplateparseexception' => FRAMEWORK_PATH."/View/SSTemplateParseException.php",
'sstemplateparser' => FRAMEWORK_PATH."/src/View/SSTemplateParser.php",
'sstemplateparseexception' => FRAMEWORK_PATH."/src/View/SSTemplateParseException.php",
'silverstripe\framework\tests\classi' => "{$this->base}/module/classes/ClassI.php",
);

View File

@ -1,69 +1,79 @@
---
Only:
ENVORCONSTANTMATCHESVALUE_FOO: Foo
envorconstant:
CONSTANTMATCHESVALUE_FOO: Foo
---
SilverStripe\Core\Tests\Manifest\ConfigManifestTest:
EnvOrConstantMatchesValue:
FooIsFoo: Yes
---
Only:
ENVORCONSTANTMATCHESVALUE_FOO: Qux
envorconstant:
CONSTANTMATCHESVALUE_FOO: 'Qux'
---
SilverStripe\Core\Tests\Manifest\ConfigManifestTest:
EnvOrConstantMatchesValue:
FooIsQux: Yes
---
Only:
ENVORCONSTANTMATCHESVALUE_BAR: Bar
envorconstant:
CONSTANTMATCHESVALUE_BAR: Bar
---
SilverStripe\Core\Tests\Manifest\ConfigManifestTest:
EnvOrConstantMatchesValue:
BarIsBar: Yes
---
Only:
ENVORCONSTANTMATCHESVALUE_BAR: Qux
envorconstant:
CONSTANTMATCHESVALUE_BAR: Qux
---
SilverStripe\Core\Tests\Manifest\ConfigManifestTest:
EnvOrConstantMatchesValue:
BarIsQux: Yes
---
Only:
ENVORCONSTANTMATCHESVALUE_Baz: Baz
envorconstant:
CONSTANTMATCHESVALUE_Baz: Baz
---
SilverStripe\Core\Tests\Manifest\ConfigManifestTest:
EnvOrConstantMatchesValue:
BazIsBaz: Yes
---
Except:
ENVORCONSTANTMATCHESVALUE_FOO: Foo
envorconstant:
CONSTANTMATCHESVALUE_FOO: Foo
---
SilverStripe\Core\Tests\Manifest\ConfigManifestTest:
EnvOrConstantMatchesValue:
FooIsFoo: No
---
Except:
ENVORCONSTANTMATCHESVALUE_FOO: Qux
envorconstant:
CONSTANTMATCHESVALUE_FOO: Qux
---
SilverStripe\Core\Tests\Manifest\ConfigManifestTest:
EnvOrConstantMatchesValue:
FooIsQux: No
---
Except:
ENVORCONSTANTMATCHESVALUE_BAR: Bar
envorconstant:
CONSTANTMATCHESVALUE_BAR: Bar
---
SilverStripe\Core\Tests\Manifest\ConfigManifestTest:
EnvOrConstantMatchesValue:
BarIsBar: No
---
Except:
ENVORCONSTANTMATCHESVALUE_BAR: Qux
envorconstant:
CONSTANTMATCHESVALUE_BAR: Qux
---
SilverStripe\Core\Tests\Manifest\ConfigManifestTest:
EnvOrConstantMatchesValue:
BarIsQux: No
---
Except:
ENVORCONSTANTMATCHESVALUE_Baz: Baz
envorconstant:
CONSTANTMATCHESVALUE_Baz: Baz
---
SilverStripe\Core\Tests\Manifest\ConfigManifestTest:
EnvOrConstantMatchesValue:

View File

@ -1,6 +1,6 @@
<?php
use SilverStripe\Core\Config\Config;
use SilverStripe\Control\Director;
// Dynamically change environment
Config::inst()->update('SilverStripe\\Control\\Director', 'environment_type', 'dev');
Director::set_environment_type('dev');

View File

@ -49,22 +49,25 @@ class MemoryLimitTest extends SapphireTest
return;
}
set_time_limit(6000);
// Can't change time limit
if (!set_time_limit(6000)) {
return;
}
// It can go up
increase_time_limit_to(7000);
$this->assertTrue(increase_time_limit_to(7000));
$this->assertEquals(7000, ini_get('max_execution_time'));
// But not down
increase_time_limit_to(5000);
$this->assertTrue(increase_time_limit_to(5000));
$this->assertEquals(7000, ini_get('max_execution_time'));
// 0/nothing means infinity
increase_time_limit_to();
$this->assertTrue(increase_time_limit_to());
$this->assertEquals(0, ini_get('max_execution_time'));
// Can't go down from there
increase_time_limit_to(10000);
$this->assertTrue(increase_time_limit_to(10000));
$this->assertEquals(0, ini_get('max_execution_time'));
}
@ -82,6 +85,7 @@ class MemoryLimitTest extends SapphireTest
set_increase_memory_limit_max(-1);
set_increase_time_limit_max(-1);
}
public function tearDown()
{
ini_set('memory_limit', $this->origMemLimit);

View File

@ -209,13 +209,13 @@ class AssetFieldTest extends FunctionalTest
switch ($name) {
case 'File[Filename]':
$tuple['Filename'] = $value;
break;
break;
case 'File[Hash]':
$tuple['Hash'] = $value;
break;
break;
case 'File[Variant]':
$tuple['Variant'] = $value;
break;
break;
}
}

View File

@ -2,6 +2,7 @@
namespace SilverStripe\Forms\Tests\GridField;
use SilverStripe\Dev\Debug;
use SilverStripe\Forms\Tests\GridField\GridFieldAddExistingAutocompleterTest\TestController;
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Cheerleader;
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Permissions;
@ -29,22 +30,20 @@ class GridFieldAddExistingAutocompleterTest extends FunctionalTest
TestController::class
];
function testScaffoldSearchFields()
public function testScaffoldSearchFields()
{
$autoCompleter = new GridFieldAddExistingAutocompleter($targetFragment = 'before', array('Test'));
$this->assertEquals(
$autoCompleter->scaffoldSearchFields(Team::class),
array(
'Name:PartialMatch',
'City:StartsWith',
'Cheerleaders.Name:StartsWith'
)
),
$autoCompleter->scaffoldSearchFields(Team::class)
);
$this->assertEquals(
$autoCompleter->scaffoldSearchFields(Cheerleader::class),
array(
'Name:StartsWith'
)
[ 'Name:StartsWith' ],
$autoCompleter->scaffoldSearchFields(Cheerleader::class)
);
}

View File

@ -28,23 +28,19 @@ class HTMLEditorFieldToolbarTest extends SapphireTest
{
parent::setUp();
HTMLEditorField_Toolbar::config()->update('fileurl_scheme_whitelist', array('http'));
HTMLEditorField_Toolbar::config()->update('fileurl_domain_whitelist', array('example.com'));
HTMLEditorField_Toolbar::config()->set('fileurl_scheme_whitelist', array('http'));
HTMLEditorField_Toolbar::config()->set('fileurl_domain_whitelist', array('example.com'));
// Filesystem mock
TestAssetStore::activate(__CLASS__);
// Load up files
/**
* @var File $file1
*/
/** @var File $file1 */
$file1 = $this->objFromFixture(File::class, 'example_file');
$file1->setFromString(str_repeat('x', 1000), $file1->Name);
$file1->write();
/**
* @var Image $image1
*/
/** @var Image $image1 */
$image1 = $this->objFromFixture(Image::class, 'example_image');
$image1->setFromLocalFile(
__DIR__ . '/HTMLEditorFieldTest/images/example.jpg',
@ -55,48 +51,42 @@ class HTMLEditorFieldToolbarTest extends SapphireTest
public function testValidLocalReference()
{
/**
* @var File $exampleFile
*/
/** @var File $exampleFile */
$exampleFile = $this->objFromFixture(File::class, 'example_file');
$expectedUrl = $exampleFile->AbsoluteLink();
HTMLEditorField_Toolbar::config()->update(
HTMLEditorField_Toolbar::config()->set(
'fileurl_domain_whitelist',
array(
'example.com',
strtolower(parse_url($expectedUrl, PHP_URL_HOST))
)
[
'example.com',
strtolower(parse_url($expectedUrl, PHP_URL_HOST))
]
);
list($file, $url) = $this->getToolbar()->viewfile_getRemoteFileByURL($exampleFile->AbsoluteLink());
list(, $url) = $this->getToolbar()->viewfile_getRemoteFileByURL($exampleFile->AbsoluteLink());
$this->assertEquals($expectedUrl, $url);
}
public function testValidScheme()
{
list($file, $url) = $this->getToolbar()->viewfile_getRemoteFileByURL('http://example.com/test.pdf');
list(, $url) = $this->getToolbar()->viewfile_getRemoteFileByURL('http://example.com/test.pdf');
$this->assertEquals($url, 'http://example.com/test.pdf');
}
/**
* @expectedException SilverStripe\Control\HTTPResponse_Exception
*/
public function testInvalidScheme()
{
list($file, $url) = $this->getToolbar()->viewfile_getRemoteFileByURL('nosuchscheme://example.com/test.pdf');
$this->setExpectedException(HTTPResponse_Exception::class);
$this->getToolbar()->viewfile_getRemoteFileByURL('nosuchscheme://example.com/test.pdf');
}
public function testValidDomain()
{
list($file, $url) = $this->getToolbar()->viewfile_getRemoteFileByURL('http://example.com/test.pdf');
list(, $url) = $this->getToolbar()->viewfile_getRemoteFileByURL('http://example.com/test.pdf');
$this->assertEquals($url, 'http://example.com/test.pdf');
}
/**
* @expectedException SilverStripe\Control\HTTPResponse_Exception
*/
public function testInvalidDomain()
{
list($file, $url) = $this->getToolbar()->viewfile_getRemoteFileByURL('http://evil.com/test.pdf');
$this->setExpectedException(HTTPResponse_Exception::class);
$this->getToolbar()->viewfile_getRemoteFileByURL('http://evil.com/test.pdf');
}
}

View File

@ -14,16 +14,16 @@ class DBTest extends SapphireTest
$prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_';
Director::config()->update('environment_type', 'dev');
Director::set_environment_type('dev');
$this->assertTrue(DB::valid_alternative_database_name($prefix.'tmpdb1234567'));
$this->assertFalse(DB::valid_alternative_database_name($prefix.'tmpdb12345678'));
$this->assertFalse(DB::valid_alternative_database_name('tmpdb1234567'));
$this->assertFalse(DB::valid_alternative_database_name('random'));
$this->assertFalse(DB::valid_alternative_database_name(''));
Director::config()->update('environment_type', 'live');
Director::set_environment_type('live');
$this->assertFalse(DB::valid_alternative_database_name($prefix.'tmpdb1234567'));
Director::config()->update('environment_type', 'dev');
Director::set_environment_type('dev');
}
}

View File

@ -2,10 +2,13 @@
namespace SilverStripe\ORM\Tests;
use SilverStripe\Assets\Tests\FileMigrationHelperTest\Extension;
use SilverStripe\Core\Config\Middleware\ExtensionMiddleware;
use SilverStripe\Dev\Debug;
use SilverStripe\ORM\DataObject;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\Tests\DataExtensionTest\TestMember;
use SilverStripe\ORM\DB;
use SilverStripe\Security\Member;
class DataExtensionTest extends SapphireTest
@ -127,13 +130,11 @@ class DataExtensionTest extends SapphireTest
// Pull the record out of the DB and examine the extended fields
$player = DataObject::get_one(
DataExtensionTest\Player::class,
array(
'"DataExtensionTest_Player"."Name"' => 'Joe'
)
[ '"DataExtensionTest_Player"."Name"' => 'Joe' ]
);
$this->assertEquals($player->DateBirth, '1990-05-10');
$this->assertEquals($player->Address, '123 somewhere street');
$this->assertEquals($player->Status, 'Goalie');
$this->assertEquals('1990-05-10', $player->DateBirth);
$this->assertEquals('123 somewhere street', $player->Address);
$this->assertEquals('Goalie', $player->Status);
}
/**
@ -141,7 +142,10 @@ class DataExtensionTest extends SapphireTest
*/
public function testApiAccessCanBeExtended()
{
$this->assertTrue(Config::inst()->get(DataExtensionTest\TestMember::class, 'api_access', Config::FIRST_SET));
$this->assertTrue(Config::inst()->get(
DataExtensionTest\TestMember::class,
'api_access'
));
}
public function testPermissionExtension()

View File

@ -4,12 +4,9 @@ namespace SilverStripe\ORM\Tests\DataExtensionTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataExtension;
use SilverStripe\ORM\Tests\DataExtensionTest\RelatedObject;
class ContactRole extends DataExtension implements TestOnly
{
private static $table_name = 'DataExtensionTest_ContactRole';
private static $db = array(
'Website' => 'Varchar',
'Phone' => 'Varchar(255)',

View File

@ -4,7 +4,6 @@ namespace SilverStripe\ORM\Tests\DataExtensionTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataExtension;
use SilverStripe\ORM\Tests\DataExtensionTest\Player;
class PlayerExtension extends DataExtension implements TestOnly
{
@ -15,7 +14,7 @@ class PlayerExtension extends DataExtension implements TestOnly
// Only add these extensions if the $class is set to DataExtensionTest_Player, to
// test that the argument works.
if ($class == Player::class) {
if (strcasecmp($class, Player::class) === 0) {
$config['db'] = array(
'Address' => 'Text',
'DateBirth' => 'Date',

View File

@ -147,9 +147,7 @@ class DataObjectSchemaGenerationTest extends SapphireTest
// Test with alternate index format, although these indexes are the same
$config = TestIndexObject::config();
$config
->remove('indexes')
->update('indexes', $config->get('indexes_alt'));
$config->set('indexes', $config->get('indexes_alt'));
// Verify that it still doesn't need to be recreated
$schema->schemaUpdate(

View File

@ -1377,21 +1377,21 @@ class DataObjectTest extends SapphireTest
public function testValidateModelDefinitionsFailsWithArray()
{
Config::inst()->update(DataObjectTest\Team::class, 'has_one', array('NotValid' => array('NoArraysAllowed')));
Config::modify()->merge(DataObjectTest\Team::class, 'has_one', array('NotValid' => array('NoArraysAllowed')));
$this->setExpectedException(InvalidArgumentException::class);
DataObject::getSchema()->hasOneComponent(DataObjectTest\Team::class, 'NotValid');
}
public function testValidateModelDefinitionsFailsWithIntKey()
{
Config::inst()->update(DataObjectTest\Team::class, 'has_many', array(12 => DataObjectTest\Player::class));
Config::modify()->set(DataObjectTest\Team::class, 'has_many', array(0 => DataObjectTest\Player::class));
$this->setExpectedException(InvalidArgumentException::class);
DataObject::getSchema()->hasManyComponent(DataObjectTest\Team::class, 12);
DataObject::getSchema()->hasManyComponent(DataObjectTest\Team::class, 0);
}
public function testValidateModelDefinitionsFailsWithIntValue()
{
Config::inst()->update(DataObjectTest\Team::class, 'many_many', array('Players' => 12));
Config::modify()->merge(DataObjectTest\Team::class, 'many_many', array('Players' => 12));
$this->setExpectedException(InvalidArgumentException::class);
DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Players');
}
@ -1460,7 +1460,7 @@ class DataObjectTest extends SapphireTest
// Check everything works when no relation is present
$teamWithoutSponsor = $this->objFromFixture(DataObjectTest\Team::class, 'team3');
$this->assertInstanceOf('SilverStripe\\ORM\\ManyManyList', $teamWithoutSponsor->Sponsors());
$this->assertInstanceOf(ManyManyList::class, $teamWithoutSponsor->Sponsors());
$this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
// Test that belongs_many_many can be infered from with getNonReciprocalComponent

View File

@ -4,7 +4,23 @@ namespace SilverStripe\ORM\Tests\DataObjectTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\HasManyList;
use SilverStripe\ORM\ManyManyList;
/**
* @property string Title
* @property string DatabaseField
* @method Player Captain()
* @method Player Founder()
* @method Player HasOneRelationship()
* @method HasManyList SubTeams()
* @method HasManyList Comments()
* @method HasManyList Fans()
* @method HasManyList PlayerFans()
* @method ManyManyList Players()
* @method ManyManyList Sponsors()
* @method ManyManyList EquipmentSuppliers()
*/
class Team extends DataObject implements TestOnly
{
private static $table_name = 'DataObjectTest_Team';

View File

@ -26,7 +26,6 @@ class MemberTest extends FunctionalTest
protected static $fixture_file = 'MemberTest.yml';
protected $orig = array();
protected $local = null;
protected $illegalExtensions = array(
Member::class => array(
@ -44,13 +43,7 @@ class MemberTest extends FunctionalTest
//Setting the locale has to happen in the constructor (using the setUp and tearDown methods doesn't work)
//This is because the test relies on the yaml file being interpreted according to a particular date format
//and this setup occurs before the setUp method is run
$this->local = i18n::config()->get('default_locale');
i18n::config()->update('default_locale', 'en_US');
}
public function __destruct()
{
i18n::config()->update('default_locale', $this->local);
i18n::config()->set('default_locale', 'en_US');
}
/**

View File

@ -12,29 +12,15 @@ use SilverStripe\Security\Tests\PasswordEncryptorTest\TestEncryptor;
class PasswordEncryptorTest extends SapphireTest
{
/**
*
* @var Config
*/
private $config = null;
public function setUp()
{
parent::setUp();
$this->config = clone(Config::inst());
}
public function tearDown()
{
parent::tearDown();
Config::set_instance($this->config);
PasswordEncryptor_Blowfish::set_cost(10);
}
public function testCreateForCode()
{
Config::inst()->update(
Config::modify()->merge(
PasswordEncryptor::class,
'encryptors',
['test' => [TestEncryptor::class => null]]
@ -53,7 +39,7 @@ class PasswordEncryptorTest extends SapphireTest
public function testRegister()
{
Config::inst()->update(
Config::modify()->merge(
PasswordEncryptor::class,
'encryptors',
array('test' => array(TestEncryptor::class => null))
@ -64,31 +50,21 @@ class PasswordEncryptorTest extends SapphireTest
$this->assertContains(TestEncryptor::class, key($encryptor));
}
public function testUnregister()
{
Config::inst()->update(
PasswordEncryptor::class,
'encryptors',
array('test' => array(TestEncryptor::class => null))
);
Config::inst()->remove(PasswordEncryptor::class, 'encryptors', 'test');
$this->assertNotContains('test', array_keys(PasswordEncryptor::get_encryptors()));
}
public function testEncryptorPHPHashWithArguments()
{
Config::inst()->update(
Config::modify()->merge(
PasswordEncryptor::class,
'encryptors',
['test_md5' => [PasswordEncryptor_PHPHash::class=>'md5']]
);
/** @var PasswordEncryptor_PHPHash $e */
$e = PasswordEncryptor::create_for_algorithm('test_md5');
$this->assertEquals('md5', $e->getAlgorithm());
}
public function testEncryptorPHPHash()
{
Config::inst()->update(
Config::modify()->merge(
PasswordEncryptor::class,
'encryptors',
['test_sha1' => [PasswordEncryptor_PHPHash::class => 'sha1']]
@ -104,11 +80,12 @@ class PasswordEncryptorTest extends SapphireTest
public function testEncryptorBlowfish()
{
Config::inst()->update(
Config::modify()->merge(
PasswordEncryptor::class,
'encryptors',
['test_blowfish' => [PasswordEncryptor_Blowfish::class => '']]
);
/** @var PasswordEncryptor_Blowfish $e */
$e = PasswordEncryptor::create_for_algorithm('test_blowfish');
$password = 'mypassword';
@ -156,7 +133,7 @@ class PasswordEncryptorTest extends SapphireTest
public function testEncryptorPHPHashCheck()
{
Config::inst()->update(
Config::modify()->merge(
PasswordEncryptor::class,
'encryptors',
['test_sha1' => [PasswordEncryptor_PHPHash::class => 'sha1']]
@ -174,7 +151,7 @@ class PasswordEncryptorTest extends SapphireTest
*/
public function testEncryptorLegacyPHPHashCheck()
{
Config::inst()->update(
Config::modify()->merge(
PasswordEncryptor::class,
'encryptors',
['test_sha1legacy' => [PasswordEncryptor_LegacyPHPHash::class => 'sha1']]

View File

@ -1087,11 +1087,11 @@ EOS
$type = strtolower($type);
switch (strtolower($type)) {
case 'css':
return $backend->getCSS();
return $backend->getCSS();
case 'js':
case 'javascript':
case 'script':
return $backend->getJavascript();
return $backend->getJavascript();
}
return array();
}

View File

@ -68,11 +68,10 @@ class SSViewerTest extends SapphireTest
*/
public function testCurrentTheme()
{
//TODO: SiteConfig moved to CMS
SSViewer::config()->update('theme', 'mytheme');
$this->assertEquals(
'mytheme',
SSViewer::config()->get('theme'),
SSViewer::config()->uninherited('theme'),
'Current theme is the default - user has not defined one'
);
}
@ -1730,7 +1729,7 @@ EOC;
public function testRenderWithSourceFileComments()
{
Director::config()->update('environment_type', 'dev');
Director::set_environment_type('dev');
SSViewer::config()->update('source_file_comments', true);
$i = __DIR__ . '/SSViewerTest/templates/Includes';
$f = __DIR__ . '/SSViewerTest/templates/SSViewerTestComments';