mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
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:
commit
5388ff41ec
@ -1,5 +1,5 @@
|
||||
---
|
||||
Name: coreconfig
|
||||
Name: emailconfig
|
||||
---
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
Swift_Transport: Swift_MailTransport
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
]);
|
||||
|
@ -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",
|
||||
|
@ -42,7 +42,7 @@ config:
|
||||
|
||||
|
||||
:::yaml
|
||||
Injector:
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
DataListFilter.CustomMatch:
|
||||
class: MyVendor/Search/CustomMatchFilter
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
||||
|
3
main.php
3
main.php
@ -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
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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>";
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
);
|
||||
|
@ -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'])) {
|
||||
|
@ -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}) . ';';
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
90
src/Core/Config/ConfigLoader.php
Normal file
90
src/Core/Config/ConfigLoader.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
171
src/Core/Config/CoreConfigFactory.php
Normal file
171
src/Core/Config/CoreConfigFactory.php
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
84
src/Core/Config/Middleware/ExtensionMiddleware.php
Normal file
84
src/Core/Config/Middleware/ExtensionMiddleware.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
src/Core/Config/Middleware/InheritanceMiddleware.php
Normal file
36
src/Core/Config/Middleware/InheritanceMiddleware.php
Normal 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;
|
||||
}
|
||||
}
|
48
src/Core/Config/Middleware/MiddlewareCommon.php
Normal file
48
src/Core/Config/Middleware/MiddlewareCommon.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
151
src/Core/Manifest/Module.php
Normal file
151
src/Core/Manifest/Module.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
76
src/Core/Manifest/ModuleLoader.php
Normal file
76
src/Core/Manifest/ModuleLoader.php
Normal 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);
|
||||
}
|
||||
}
|
230
src/Core/Manifest/ModuleManifest.php
Normal file
230
src/Core/Manifest/ModuleManifest.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
);
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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');
|
||||
|
@ -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."
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -106,6 +106,7 @@ class ReadonlyField extends FormField
|
||||
}
|
||||
|
||||
// Use default casting
|
||||
return $this->config()->casting['Value'];
|
||||
$casting = $this->config()->get('casting');
|
||||
return $casting['Value'];
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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')
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -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'])
|
||||
));
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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, '/');
|
||||
|
@ -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 -------------------------------------------------------------------------------------------
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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'));
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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());
|
||||
}
|
||||
*/
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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'];
|
||||
}
|
||||
|
@ -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'];
|
||||
}
|
||||
|
@ -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'];
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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')
|
||||
);
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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",
|
||||
);
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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');
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)',
|
||||
|
@ -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',
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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';
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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']]
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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';
|
||||
|
Loading…
x
Reference in New Issue
Block a user