mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Refactor bootstrap, request handling
See https://github.com/silverstripe/silverstripe-framework/pull/7037 and https://github.com/silverstripe/silverstripe-framework/issues/6681 Squashed commit of the following: commit8f65e56532
Author: Ingo Schommer <me@chillu.com> Date: Thu Jun 22 22:25:50 2017 +1200 Fixed upgrade guide spelling commit76f95944fa
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 16:38:34 2017 +1200 BUG Fix non-test class manifest including sapphiretest / functionaltest commit9379834cb4
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 15:50:47 2017 +1200 BUG Fix nesting bug in Kernel commit188ce35d82
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 15:14:51 2017 +1200 BUG fix db bootstrapping issues commit7ed4660e7a
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 14:49:07 2017 +1200 BUG Fix issue in DetailedErrorFormatter commit738f50c497
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 11:49:19 2017 +1200 Upgrading notes on mysite/_config.php commit6279d28e5e
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 11:43:28 2017 +1200 Update developer documentation commit5c90d53a84
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 10:48:44 2017 +1200 Update installer to not use global databaseConfig commitf9b2ba4755
Author: Damian Mooyman <damian@silverstripe.com> Date: Wed Jun 21 21:04:39 2017 +1200 Fix behat issues commit5b59a912b6
Author: Damian Mooyman <damian@silverstripe.com> Date: Wed Jun 21 17:07:11 2017 +1200 Move HTTPApplication to SilverStripe\Control namespace commite2c4a18f63
Author: Damian Mooyman <damian@silverstripe.com> Date: Wed Jun 21 16:29:03 2017 +1200 More documentation Fix up remaining tests Refactor temp DB into TempDatabase class so it’s available outside of unit tests. commit5d235e64f3
Author: Damian Mooyman <damian@silverstripe.com> Date: Wed Jun 21 12:13:15 2017 +1200 API HTTPRequestBuilder::createFromEnvironment() now cleans up live globals BUG Fix issue with SSViewer Fix Security / View tests commitd88d4ed4e4
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 20 16:39:43 2017 +1200 API Refactor AppKernel into CoreKernel commitf7946aec33
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 20 16:00:40 2017 +1200 Docs and minor cleanup commit12bd31f936
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 20 15:34:34 2017 +1200 API Remove OutputMiddleware API Move environment / global / ini management into Environment class API Move getTempFolder into TempFolder class API Implement HTTPRequestBuilder / CLIRequestBuilder BUG Restore SS_ALLOWED_HOSTS check in original location API CoreKernel now requires $basePath to be passed in API Refactor installer.php to use application to bootstrap API move memstring conversion globals to Convert BUG Fix error in CoreKernel nesting not un-nesting itself properly. commitbba9791146
Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 19 18:07:53 2017 +1200 API Create HTTPMiddleware and standardise middleware for request handling commit2a10c2397b
Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 19 17:42:42 2017 +1200 Fixed ORM tests commitd75a8d1d93
Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 19 17:15:07 2017 +1200 FIx i18n tests commit06364af3c3
Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 19 16:59:34 2017 +1200 Fix controller namespace Move states to sub namespace commit2a278e2953
Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 19 12:49:45 2017 +1200 Fix forms namespace commitb65c21241b
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 15 18:56:48 2017 +1200 Update API usages commitd1d4375c95
Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 15 18:41:44 2017 +1200 API Refactor $flush into HTPPApplication API Enforce health check in Controller::pushCurrent() API Better global backup / restore Updated Director::test() to use new API commitb220534f06
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 22:05:57 2017 +1200 Move app nesting to a test state helper commit603704165c
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 21:46:04 2017 +1200 Restore kernel stack to fix multi-level nesting commit2f6336a15b
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 17:23:21 2017 +1200 API Implement kernel nesting commitfc7188da7d
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 15:43:13 2017 +1200 Fix core tests commita0ae723514
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 15:23:52 2017 +1200 Fix manifest tests commitca03395251
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 15:00:00 2017 +1200 API Move extension management into test state commitc66d433977
Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 14:10:59 2017 +1200 API Refactor SapphireTest state management into SapphireTestState API Remove Injector::unregisterAllObjects() API Remove FakeController commitf26ae75c6e
Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 12 18:04:34 2017 +1200 Implement basic CLI application object commit001d559662
Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 12 17:39:38 2017 +1200 Remove references to SapphireTest::is_running_test() Upgrade various code commitde079c041d
Author: Damian Mooyman <damian@silverstripe.com> Date: Wed Jun 7 18:07:33 2017 +1200 API Implement APP object API Refactor of Session
This commit is contained in:
parent
4ad6bdbe7e
commit
3873e4ba00
20
_config/tests.yml
Normal file
20
_config/tests.yml
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
Name: sapphiretest
|
||||
---
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
SilverStripe\Dev\State\SapphireTestState:
|
||||
properties:
|
||||
States:
|
||||
globals: %$SilverStripe\Dev\State\GlobalsTestState
|
||||
extensions: %$SilverStripe\Dev\State\ExtensionTestState
|
||||
flushable: %$SilverStripe\Dev\State\FlushableTestState
|
||||
requirements: %$SilverStripe\View\Dev\RequirementsTestState
|
||||
---
|
||||
Name: kerneltest
|
||||
Before: '*'
|
||||
---
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
SilverStripe\Dev\State\SapphireTestState:
|
||||
properties:
|
||||
States:
|
||||
kernel: %$SilverStripe\Dev\State\KernelTestState
|
157
cli-script.php
157
cli-script.php
@ -1,152 +1,23 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\Control\Director;
|
||||
// CLI specific bootstrapping
|
||||
use SilverStripe\Control\CLIRequestBuilder;
|
||||
use SilverStripe\Control\HTTPApplication;
|
||||
use SilverStripe\Core\CoreKernel;
|
||||
|
||||
require __DIR__ . '/src/includes/autoload.php';
|
||||
|
||||
/**
|
||||
* File similar to main.php designed for command-line scripts
|
||||
*
|
||||
* This file lets you execute SilverStripe requests from the command-line. The URL is passed as the first argument to
|
||||
* the scripts.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Ensure that people can't access this from a web-server
|
||||
*/
|
||||
if(PHP_SAPI != "cli" && PHP_SAPI != "cgi" && PHP_SAPI != "cgi-fcgi") {
|
||||
// Ensure that people can't access this from a web-server
|
||||
if (!in_array(PHP_SAPI, ["cli", "cgi", "cgi-fcgi"])) {
|
||||
echo "cli-script.php can't be run from a web request, you have to run it on the command-line.";
|
||||
die();
|
||||
}
|
||||
|
||||
// We update the $_SERVER variable to contain data consistent with the rest of the application.
|
||||
$_SERVER = array_merge(array(
|
||||
'SERVER_PROTOCOL' => 'HTTP/1.1',
|
||||
'HTTP_ACCEPT' => 'text/plain;q=0.5',
|
||||
'HTTP_ACCEPT_LANGUAGE' => '*;q=0.5',
|
||||
'HTTP_ACCEPT_ENCODING' => '',
|
||||
'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1;q=0.5',
|
||||
'SERVER_SIGNATURE' => 'Command-line PHP/' . phpversion(),
|
||||
'SERVER_SOFTWARE' => 'PHP/' . phpversion(),
|
||||
'SERVER_ADDR' => '127.0.0.1',
|
||||
'REMOTE_ADDR' => '127.0.0.1',
|
||||
'REQUEST_METHOD' => 'GET',
|
||||
'HTTP_USER_AGENT' => 'CLI',
|
||||
), $_SERVER);
|
||||
|
||||
/**
|
||||
* Identify the cli-script.php file and change to its container directory, so that require_once() works
|
||||
*/
|
||||
$_SERVER['SCRIPT_FILENAME'] = __FILE__;
|
||||
chdir(dirname($_SERVER['SCRIPT_FILENAME']));
|
||||
|
||||
/**
|
||||
* Process arguments and load them into the $_GET and $_REQUEST arrays
|
||||
* For example,
|
||||
* sake my/url somearg otherarg key=val --otherkey=val third=val&fourth=val
|
||||
*
|
||||
* Will result in the following get data:
|
||||
* args => array('somearg', 'otherarg'),
|
||||
* key => val
|
||||
* otherkey => val
|
||||
* third => val
|
||||
* fourth => val
|
||||
*/
|
||||
if(isset($_SERVER['argv'][2])) {
|
||||
$args = array_slice($_SERVER['argv'],2);
|
||||
if(!isset($_GET)) $_GET = array();
|
||||
if(!isset($_REQUEST)) $_REQUEST = array();
|
||||
foreach($args as $arg) {
|
||||
if(strpos($arg,'=') == false) {
|
||||
$_GET['args'][] = $arg;
|
||||
} else {
|
||||
$newItems = array();
|
||||
parse_str( (substr($arg,0,2) == '--') ? substr($arg,2) : $arg, $newItems );
|
||||
$_GET = array_merge($_GET, $newItems);
|
||||
}
|
||||
}
|
||||
$_REQUEST = array_merge($_REQUEST, $_GET);
|
||||
}
|
||||
|
||||
// Set 'url' GET parameter
|
||||
if(isset($_SERVER['argv'][1])) {
|
||||
$_REQUEST['url'] = $_SERVER['argv'][1];
|
||||
$_GET['url'] = $_SERVER['argv'][1];
|
||||
}
|
||||
|
||||
// require composers autoloader
|
||||
if (file_exists($autoloadPath = dirname(__DIR__) . '/vendor/autoload.php')) {
|
||||
require_once $autoloadPath;
|
||||
}
|
||||
else {
|
||||
echo "Failed to include composer's autoloader, unable to continue\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Include SilverStripe's core code
|
||||
*/
|
||||
require_once("Core/Core.php");
|
||||
|
||||
global $databaseConfig;
|
||||
|
||||
// We don't have a session in cli-script, but this prevents errors
|
||||
$_SESSION = null;
|
||||
|
||||
// Connect to database
|
||||
if(!isset($databaseConfig) || !isset($databaseConfig['database']) || !$databaseConfig['database']) {
|
||||
echo "\nPlease configure your database connection details. You can do this by creating a file
|
||||
called .env in " . BASE_PATH;
|
||||
echo <<<ENVCONTENT
|
||||
|
||||
Put the following content into this file:
|
||||
--------------------------------------------------
|
||||
|
||||
# Change this from 'dev' to 'live' for a production environment.
|
||||
SS_ENVIRONMENT_TYPE="dev"
|
||||
|
||||
/* This defines a default database user */
|
||||
SS_DATABASE_SERVER="localhost"
|
||||
SS_DATABASE_USERNAME="<user>"
|
||||
SS_DATABASE_PASSWORD="<password>"
|
||||
SS_DATABASE_NAME="<database>"
|
||||
--------------------------------------------------
|
||||
|
||||
Once you have done that, run 'composer install' or './framework/sake dev/build' to create
|
||||
an empty database.
|
||||
|
||||
For more information, please read this page in our docs:
|
||||
http://docs.silverstripe.org/en/getting_started/environment_management/
|
||||
|
||||
|
||||
ENVCONTENT;
|
||||
exit(1);
|
||||
}
|
||||
DB::connect($databaseConfig);
|
||||
|
||||
// register_argc_argv: Tells PHP whether to declare the argv & argc variables (that would contain the GET information)
|
||||
// http://php.net/manual/en/ini.core.php#ini.register-argc-argv
|
||||
if (!(int)ini_get('register_argc_argv')) {
|
||||
echo 'register_argc_argv is required to obtain the GET information but is disabled.'
|
||||
.'Please enable it in your php.ini ('
|
||||
. php_ini_loaded_file()
|
||||
.')';
|
||||
die();
|
||||
}
|
||||
|
||||
// Get the request URL from the querystring arguments
|
||||
$url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : null;
|
||||
if(!$url) {
|
||||
echo 'Please specify an argument to cli-script.php/sake. For more information, visit'
|
||||
. ' http://docs.silverstripe.org/en/developer_guides/cli' . "\n";
|
||||
die();
|
||||
}
|
||||
|
||||
$_SERVER['REQUEST_URI'] = BASE_URL . '/' . $url;
|
||||
|
||||
// Direct away - this is the "main" function, that hands control to the apporopriate controller
|
||||
DataModel::set_inst(new DataModel());
|
||||
Director::direct($url, DataModel::inst());
|
||||
|
||||
// Build request and detect flush
|
||||
$request = CLIRequestBuilder::createFromEnvironment();
|
||||
|
||||
// Default application
|
||||
$kernel = new CoreKernel(BASE_PATH);
|
||||
$app = new HTTPApplication($kernel);
|
||||
$response = $app->handle($request);
|
||||
$response->output();
|
||||
|
@ -82,12 +82,12 @@
|
||||
"SilverStripe\\Framework\\Tests\\Behaviour\\": "tests/behat/src/"
|
||||
},
|
||||
"files": [
|
||||
"src/Core/Constants.php",
|
||||
"src/Dev/PhpUnitShim.php"
|
||||
"src/includes/constants.php"
|
||||
]
|
||||
},
|
||||
"include-path": [
|
||||
"src/",
|
||||
"src/includes/",
|
||||
"thirdparty/"
|
||||
],
|
||||
"scripts": {
|
||||
|
@ -24,12 +24,13 @@ Our web-based [PHP installer](installation/) can check if you meet the requireme
|
||||
* `/dev/urandom`
|
||||
* [`mcrypt_create_iv()`](http://php.net/manual/en/function.mcrypt-create-iv.php)
|
||||
* CAPICOM Utilities (`CAPICOM.Utilities.1`, Windows only)
|
||||
* Required modules: dom, gd2, fileinfo, hash, iconv, mbstring, mysqli (or other database driver), session, simplexml, tokenizer, xml.
|
||||
* Required modules: ctype, dom, fileinfo, hash, intl, mbstring, session, simplexml, tokenizer, xml.
|
||||
* At least one from each group of extensions:
|
||||
* Image library extension (gd2, imagick)
|
||||
* DB connector library (pdo, mysqli, pgsql)
|
||||
* Recommended configuration
|
||||
|
||||
safe_mode = Off
|
||||
magic_quotes_gpc = Off
|
||||
memory_limit = 48M
|
||||
* Dev (local development for running test framework): memory_limit 512MB
|
||||
* Production: memory_limit = 64M
|
||||
|
||||
* See [phpinfo()](http://php.net/manual/en/function.phpinfo.php) for more information about your environment
|
||||
* One of the following databases:
|
||||
|
@ -27,9 +27,9 @@ First we're telling Homebrew about some new repositories to get the PHP installa
|
||||
brew tap homebrew/dupes
|
||||
brew tap homebrew/php
|
||||
|
||||
We're installing PHP 5.5 here, with the required `mcrypt` module:
|
||||
We're installing PHP 5.6 here, with the required `mcrypt` module:
|
||||
|
||||
brew install php55 php55-mcrypt
|
||||
brew install php56 php56-mcrypt php56-intl php56-apcu
|
||||
|
||||
There's a [Homebrew Troubleshooting](https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Troubleshooting.md) guide if Homebrew doesn't work out as expected (run `brew update` and `brew doctor`).
|
||||
|
||||
|
@ -22,21 +22,26 @@ existing modules or the directories lists in "Core Structure".
|
||||
| --------- | ----------- |
|
||||
| `<mysite>/` | This directory contains all of your code that defines your website. |
|
||||
| `<mysite>/_config` | YAML configuration specific to your application |
|
||||
| `<mysite>/code` | PHP code for model and controller (subdirectories are optional) |
|
||||
| `<mysite>/templates` | HTML [templates](/developer_guides/templates) with *.ss-extension |
|
||||
| `<mysite>/src` | PHP code for model and controller (subdirectories are optional) |
|
||||
| `<mysite>/tests` | PHP Unit tests |
|
||||
| `<mysite>/templates` | HTML [templates](/developer_guides/templates) with *.ss-extension for the `$default` theme |
|
||||
| `<mysite>/css ` | CSS files |
|
||||
| `<mysite>/images ` | Images used in the HTML templates |
|
||||
| `<mysite>/javascript` | Javascript and other script files |
|
||||
| `<mysite>/client` | More complex projects can alternatively contain frontend assets in a common `client` folder |
|
||||
| `<mysite>/themes/<yourtheme>` | Custom nested themes (note: theme structure is described below) |
|
||||
|
||||
Check our [JavaScript Coding Conventions](javascript_coding_conventions) for more details
|
||||
on folder and file naming in SilverStripe core modules.
|
||||
|
||||
## Themes Structure
|
||||
|
||||
| `themes/simple/` | Standard "simple" theme |
|
||||
| Directory | Description |
|
||||
| ------------------ | --------------------------- |
|
||||
| `themes/yourtheme/` | The themes folder can contain more than one theme - here's your own |
|
||||
| `themes/simple/` | Standard "simple" theme |
|
||||
| `themes/<yourtheme>/` | Custom theme base directory |
|
||||
| `themes/<yourtheme>/templates` | Theme templates |
|
||||
| `themes/<yourtheme>/css` | Theme CSS files |
|
||||
|
||||
|
||||
See [themes](/developer_guides/templates/themes)
|
||||
@ -77,7 +82,7 @@ Example Forum Documentation:
|
||||
| `forum/docs/en/` | English documentation |
|
||||
| `forum/docs/en/index.md` | Documentation homepage. Should provide an introduction and links to remaining docs |
|
||||
| `forum/docs/en/Getting_Started.md` | Documentation page. Naming convention is Uppercase and underscores. |
|
||||
| `forum/docs/en//_images/` | Folder to store any images or media |
|
||||
| `forum/docs/en/_images/` | Folder to store any images or media |
|
||||
| `forum/docs/en/Some_Topic/` | You can organise documentation into nested folders. Naming convention is Uppercase and underscores. |
|
||||
|`forum/docs/en/04_Some_Topic/00_Getting_Started.md`|Structure is created by use of numbered prefixes. This applies to nested folders and documentations pages, index.md should not have a prefix.|
|
||||
|
||||
|
@ -19,16 +19,11 @@ resources are required temporarily. In general, we recommend running resource in
|
||||
[command line](../cli), where configuration defaults for these settings are higher or even unlimited.
|
||||
|
||||
<div class="info" markdown="1">
|
||||
SilverStripe can request more resources through `increase_memory_limit_to()` and `increase_time_limit_to()` functions.
|
||||
SilverStripe can request more resources through `Environment::increaseMemoryLimitTo()` and
|
||||
`Environment::increaseTimeLimitTo()` functions.
|
||||
</div>
|
||||
|
||||
:::php
|
||||
function myBigFunction() {
|
||||
increase_time_limit_to(400);
|
||||
|
||||
// or..
|
||||
|
||||
set_increase_time_limit_max();
|
||||
|
||||
// ..
|
||||
public function myBigFunction() {
|
||||
Environment::increaseTimeLimitTo(400);
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
title: App Object and Kernel
|
||||
summary: Provides bootstrapping and entrypoint to the SilverStripe application
|
||||
|
||||
# Kernel
|
||||
|
||||
The [api:Kernel] object provides a container for the various manifests, services, and components
|
||||
which a SilverStripe application must have available in order for requests to be executed.
|
||||
|
||||
This can be accessed in user code via Injector
|
||||
|
||||
:::php
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
echo "Current environment: " . $kernel->getEnvironment();
|
||||
|
||||
## Kernel services
|
||||
|
||||
Services accessible from this kernel include:
|
||||
|
||||
* getContainer() -> Current [api:Injector] service
|
||||
* getThemeResourceLoader() -> [api:ThemeResourceLoader] Service for loading of discovered templates.
|
||||
Also used to contain nested theme sets such as the `$default` set for all root module /templates folders.
|
||||
* getEnvironment() -> String value for the current environment. One of 'dev', 'live' or 'test'
|
||||
|
||||
Several meta-services are also available from Kernel (which are themselves containers for
|
||||
other core services) but are not commonly accessed directly:
|
||||
|
||||
* getClassLoader() -> [api:ClassLoader] service which handles the class manifest
|
||||
* getModuleLoader() -> [api:ModuleLoadel] service which handles module registration
|
||||
* getConfigLoader() -> [api:ConfigLoader] Service which assists with nesting of [api:Config] instances
|
||||
* getInjectorLoader() -> [api:InjectorLoader] Service which assists with nesting of [api:Injector] instances
|
||||
|
||||
## Kernel nesting
|
||||
|
||||
As with Config and Injector the Kernel can be nested to safely modify global application state,
|
||||
and subsequently restore state. Unlike those classes, however, there is no `::unnest()`. Instead
|
||||
you should call `->activate()` on the kernel instance you would like to unnest to.
|
||||
|
||||
:::php
|
||||
$oldKernel = Injector::inst()->get(Kernel::class);
|
||||
try {
|
||||
// Injector::inst() / Config::inst() are automatically updated to the new kernel
|
||||
$newKernel = $oldKernel->nest();
|
||||
Config::modify()->set(Director::class, 'alternate_base_url', '/myurl');
|
||||
}
|
||||
finally {
|
||||
// Any changes to config (or other application state) have now been reverted
|
||||
$oldKernel->activate();
|
||||
}
|
||||
|
||||
|
||||
# Application
|
||||
|
||||
An application represents a basic excution controller for the top level application entry point.
|
||||
The role of the application is to:
|
||||
|
||||
- Control bootstrapping of a provided kernel instance
|
||||
- Handle errors raised from an application
|
||||
- Direct requests to the request handler, and return a valid response
|
||||
|
||||
## HTTPApplication
|
||||
|
||||
The HTTPApplication provides a specialised application implementation for handling HTTP Requests.
|
||||
This class provides basic support for HTTP Middleware, such as [api:ErrorControlChainMiddleware].
|
||||
|
||||
`main.php` contains the default application implementation.
|
||||
|
||||
:::php
|
||||
<?php
|
||||
|
||||
use SilverStripe\Control\HTTPApplication;
|
||||
use SilverStripe\Control\HTTPRequestBuilder;
|
||||
use SilverStripe\Core\CoreKernel;
|
||||
use SilverStripe\Core\Startup\ErrorControlChainMiddleware;
|
||||
|
||||
require __DIR__ . '/src/includes/autoload.php';
|
||||
|
||||
// Build request and detect flush
|
||||
$request = HTTPRequestBuilder::createFromEnvironment();
|
||||
|
||||
// Default application
|
||||
$kernel = new CoreKernel(BASE_PATH);
|
||||
$app = new HTTPApplication($kernel);
|
||||
$app->addMiddleware(new ErrorControlChainMiddleware($app));
|
||||
$response = $app->handle($request);
|
||||
$response->output();
|
||||
|
||||
|
||||
Users can customise their own application by coping the above to a file in `mysite/main.php`, and
|
||||
updating their `.htaccess` to point to the new file.
|
||||
|
||||
:::
|
||||
<IfModule mod_rewrite.c>
|
||||
# ...
|
||||
RewriteCond %{REQUEST_URI} ^(.*)$
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule .* mysite/main.php?url=%1 [QSA]
|
||||
# ...
|
||||
</IfModule>
|
||||
|
||||
|
||||
Note: This config must also be duplicated in the below template which provide asset routing:
|
||||
|
||||
`silverstripe-assets/templates/SilverStripe/Assets/Flysystem/PublicAssetAdapter_HTAccess.ss`:
|
||||
|
||||
:::ss
|
||||
<IfModule mod_rewrite.c>
|
||||
# ...
|
||||
# Non existant files passed to requesthandler
|
||||
RewriteCond %{REQUEST_URI} ^(.*)$
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule .* ../mysite/main.php?url=%1 [QSA]
|
||||
</IfModule>
|
||||
|
||||
## Custom application actions
|
||||
|
||||
If it's necessary to boot a SilverStripe kernel and application, but not do any
|
||||
request processing, you can use the Application::execute() method to invoke a custom
|
||||
application entry point.
|
||||
|
||||
This may be necessary if using SilverStripe code within the context of a non-SilverStripe
|
||||
application.
|
||||
|
||||
For example, the below will setup a request, session, and current controller,
|
||||
but will leave the application in a "ready" state without performing any
|
||||
routing.
|
||||
|
||||
:::php
|
||||
$request = CLIRequestBuilder::createFromEnvironment();
|
||||
$kernel = new TestKernel(BASE_PATH);
|
||||
$app = new HTTPApplication($kernel);
|
||||
$app->execute($request, function (HTTPRequest $request) {
|
||||
// Start session and execute
|
||||
$request->getSession()->init();
|
||||
|
||||
// Set dummy controller
|
||||
$controller = Controller::create();
|
||||
$controller->setRequest($request);
|
||||
$controller->pushCurrent();
|
||||
$controller->doInit();
|
||||
}, true);
|
||||
|
||||
|
||||
|
@ -78,21 +78,26 @@ can leave sensitive files exposed to public access (the `RewriteRule` conditions
|
||||
|
||||
## Bootstrap
|
||||
|
||||
All requests go through `framework/main.php`, which sets up the execution environment:
|
||||
The `constants.php` file is included automatically in any project which requires silverstripe/framework.
|
||||
This is included automatically when the composer `vendor/autoload.php` is included, and performs its
|
||||
tasks silently in the background.
|
||||
|
||||
* Tries to locate an `.env`
|
||||
[configuration file](/getting_started/environment_management) in the webroot.
|
||||
* Sets constants based on the filesystem structure (e.g. `BASE_URL`, `BASE_PATH` and `TEMP_FOLDER`)
|
||||
* Normalizes the `url` parameter in preparation for handing it off to `Director`
|
||||
* Connects to a database, based on information stored in the global `$databaseConfig` variable.
|
||||
The configuration is either defined in your `_config.php`, or through `.env`
|
||||
* Sets up [error handlers](../debugging/error_handling)
|
||||
* Optionally continues a [session](../cookies_and_sessions/sessions) if the request already contains a session identifier
|
||||
* Loads manifests for PHP classes, templates, as well as any [YAML configuration](../configuration).
|
||||
* Optionally regenerates these manifests (if a ["flush" query parameter](flushable) is set)
|
||||
* Executes all procedural configuration defined through `_config.php` in all discovered modules
|
||||
* Loads the Composer PHP class autoloader
|
||||
* Hands control over to [api:Director]
|
||||
|
||||
All requests go through `framework/main.php`, which sets up the core [api:Kernel] and [api:HTTPApplication]
|
||||
objects. See [/developer_guides/execution_pipeline/app_object_and_kernel] for details on this.
|
||||
The main process follows:
|
||||
|
||||
|
||||
* Include `autoload.php`
|
||||
* Construct [api:HTTPRequest] object from environment.
|
||||
* Construct a `Kernel` instance
|
||||
* Construct a `HTTPApplication` instance
|
||||
* Add any necessary middleware to this application
|
||||
* Pass the request to the application, and request a response
|
||||
|
||||
|
||||
While you usually don't need to modify the bootstrap on this level, some deeper customizations like
|
||||
adding your own manifests or a performance-optimized routing might require it.
|
||||
|
@ -111,6 +111,16 @@ the `SilverStripe\Forms` namespace.
|
||||
The below sections deal with upgrades to specific parts of various API. Projects which rely on certain
|
||||
API should be upgraded as appropriate using any of the relevant processes documented below.
|
||||
|
||||
#### Upgrade `mysite/_config.php`
|
||||
|
||||
The globals `$database` and `$databaseConfig` are deprecated. You should upgrade your
|
||||
site _config.php files to use the `.env` configuration (below).
|
||||
|
||||
If you need to configure database details in PHP, use the new `DB::setConfig()` api instead.
|
||||
|
||||
You should remove any references to `ConfigureFromEnv.php` in your project, as this file
|
||||
is no longer necessary.
|
||||
|
||||
#### Upgrade of `_ss_environment.php` to `.env` configuration
|
||||
|
||||
The php configuration `_ss_environment.php` file has been replaced in favour of a non-executable
|
||||
@ -160,6 +170,9 @@ logic early in the bootstrap, this is best placed in the `_config.php` files.
|
||||
|
||||
Note also that `$_FILE_TO_URL_MAPPING` has been removed and replaced with `SS_BASE_URL` env var.
|
||||
|
||||
The global values `$database` and `$databaseConfig` have been deprecated, as has `ConfigureFromEnv.php`
|
||||
which is no longer necessary.
|
||||
|
||||
See [Environment Management docs](/getting-started/environment_management/) for full details.
|
||||
|
||||
#### Replace usages of Object class
|
||||
@ -245,6 +258,30 @@ Extensions
|
||||
$has = DataObject::has_extension(File::class, Versioned::class); // alternate
|
||||
$extensions = DataObject::get_extensions(File::class);
|
||||
|
||||
#### Upgrade references to Session object
|
||||
|
||||
Session object is no longer statically accessible via `Session::inst()`. Instead, Session
|
||||
is a member of the current request.
|
||||
|
||||
Before:
|
||||
|
||||
:::php
|
||||
public function httpSubmission($data, $form, $request) {
|
||||
Session::set('loggedIn', null);
|
||||
}
|
||||
|
||||
|
||||
After:
|
||||
|
||||
:::php
|
||||
public function httpSubmission($data, $form, $request) {
|
||||
$request->getSession()->set('loggedIn', null);
|
||||
}
|
||||
|
||||
|
||||
In some places it may still be necessary to access the session object where no request is available.
|
||||
In rare cases it is still possible to access the request of the current controller via
|
||||
`Controller::curr()->getRequest()` to gain access to the current session.
|
||||
|
||||
#### Compatibility with the new front-end building tools
|
||||
|
||||
@ -1248,6 +1285,7 @@ After (`mysite/_config/config.yml`):
|
||||
#### <a name="overview-general-api"></a>General and Core API Additions / Changes
|
||||
|
||||
* Minimum PHP version raised to 5.6 (with support for PHP 7.x)
|
||||
* Dropped support for PHP safe mode (removed php 5.4).
|
||||
* Once PHP versions become [unsupported by the PHP Project](http://php.net/supported-versions.php)),
|
||||
we drop support for those versions in the [next minor release](/contributing/release-process
|
||||
This means PHP 5.6 and PHP 7.0 support will become unsupported in Dec 2018.
|
||||
@ -1270,6 +1308,9 @@ After (`mysite/_config/config.yml`):
|
||||
* `Configurable` Provides Config API helper methods
|
||||
* `Injectable` Provides Injector API helper methods
|
||||
* `Extensible` Allows extensions to be applied
|
||||
* `Convert` class has extra methods for formatting file sizes in php_ini compatible format
|
||||
* `Convert::memstring2bytes()` will parse a php_ini memory size.
|
||||
* `Convert::bytes2memstring()` will format the memory size with the appropriate scale.
|
||||
* `SiteTree.alternatePreviewLink` is deprecated. Use `updatePreviewLink` instead.
|
||||
* `Injector` dependencies no longer automatically inherit from parent classes.
|
||||
* `$action` parameter to `Controller::Link()` method is standardised.
|
||||
@ -1335,10 +1376,34 @@ After (`mysite/_config/config.yml`):
|
||||
* `findAnAdministrator` use `DefaultAdminService::findOrCreateDefaultAdmin()` instead
|
||||
* `Member` methods deprecated:
|
||||
* `checkPassword`. Use Authenticator::checkPassword() instead
|
||||
* `RequestFilter` changed. $session and $dataModel variables removed from preRequest / postRequest
|
||||
|
||||
#### <a name="overview-general-removed"></a>General and Core Removed API
|
||||
|
||||
* Removed `ConfigureFromEnv.php` as it's no longer necessary.
|
||||
* `Session` object has had significant refactoring. This object no longer is accessed via
|
||||
`Session::inst()`, but instead should be queried from the current request via `$request->getSession()`.
|
||||
All static methods have been removed, and the `inst_` prefix removed from all instance members.
|
||||
Please see the upgrading section on Session object for more details.
|
||||
* `Director.rules` config no longer support `redirect:<url>` directly via config.
|
||||
* `Director::get_environment_type()` and `Director::set_environment_type()` are removed.
|
||||
Get the `Kernel` instance via injector and query `getEnvironment()` instead.
|
||||
E.g. `$type = Injector::inst()->get(Kernel::class)->getEnvironment();`.
|
||||
* Many global methods have been refactored into `Environment` or `Convert` class.
|
||||
* `increase_xdebug_nesting_level_to` removed (functionality has been inlined into `AppKernel`)
|
||||
* `set_increase_time_limit_max` moved to `Environment::setTimeLimitMax()`
|
||||
* `get_increase_time_limit_max` moved to `Environment::getTimeLimitMax()`
|
||||
* `set_increase_memory_limit_max` moved to `Environment::setMemoryLimitMax()`
|
||||
* `get_increase_memory_limit_max` moved to `Environment::getMemoryLimitMax()`
|
||||
* `increase_time_limit_to` moved to `Environment::increaseTimeLimitTo()`
|
||||
* `increase_memory_limit_to` moved to `Environment::increaseMemoryLimitTo()`
|
||||
* `translate_memstring` moved to `Convert::memstring2bytes`.
|
||||
* `getTempFolder` moved to `TempFolder::getTempFolder()`
|
||||
* `getTempParentFolder` removed.
|
||||
* `getTempFolderUsername` removed.
|
||||
* `CMSMain::buildbrokenlinks()` action is removed.
|
||||
* `Injector::unregisterAllObjects()` has been removed. Use `unregisterObjects` to unregister
|
||||
groups of objects limited by type instead.
|
||||
* `SS_Log` class has been removed. Use `Injector::inst()->get(LoggerInterface::class)` instead.
|
||||
* Removed `CMSBatchAction_Delete`
|
||||
* Removed `CMSBatchAction_DeleteFromLive`
|
||||
@ -1496,6 +1561,17 @@ A very small number of methods were chosen for deprecation, and will be removed
|
||||
* `DBMoney` values are now treated as empty only if `Amount` field is null. If an `Amount` value
|
||||
is provided without a `Currency` specified, it will be formatted as per the current locale.
|
||||
* Removed `DatabaseAdmin#clearAllData()`. Use `DB::get_conn()->clearAllData()` instead
|
||||
* `SapphireTest` temp DB methods have been removed and put into a new `TempDatabase` class.
|
||||
This allows applications to create temp databases when not running tests.
|
||||
This class now takes a connection name as a constructor class, and no longer
|
||||
has static methods.
|
||||
The following methods have been moved to this new class and renamed as non-static:
|
||||
* `using_temp_db` -> `isUsed()`
|
||||
* `kill_temp_db` -> `kill()`
|
||||
* `empty_temp_db` _> `clearAllData()`
|
||||
* `create_temp_db` -> `build()`
|
||||
* `delete_all_temp_dbs` -> `deleteAll()`
|
||||
* `resetDBSchema` -> `resetSchema()`
|
||||
|
||||
The below methods have been added or had their functionality updated to `DBDate`, `DBTime` and `DBDatetime`
|
||||
* `getTimestamp()` added to get the respective date / time as unix timestamp (seconds since 1970-01-01)
|
||||
@ -1523,6 +1599,8 @@ The below methods have been added or had their functionality updated to `DBDate`
|
||||
|
||||
#### <a name="overview-orm-removed"></a>ORM Removed API
|
||||
|
||||
* Deprecated globals `$database` and `$databaseConfig`. Please use `DB::setConfig()` instead.
|
||||
* `DataModel` removed
|
||||
* `DataObject::can*` methods no longer accept a member ID. These must now be passed a Member object or left null
|
||||
* `DataObject::db` removed and replaced with `DataObjectSchema::fieldSpec` and `DataObjectSchema::fieldSpecs`
|
||||
* `DataObject::manyManyComponent` moved to `DataObjectSchema`
|
||||
|
234
main.php
234
main.php
@ -1,226 +1,18 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Security\Permission;
|
||||
use SilverStripe\Core\Startup\ParameterConfirmationToken;
|
||||
use SilverStripe\Core\Startup\ErrorControlChain;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\HTTPApplication;
|
||||
use SilverStripe\Control\HTTPRequestBuilder;
|
||||
use SilverStripe\Core\CoreKernel;
|
||||
use SilverStripe\Core\Startup\ErrorControlChainMiddleware;
|
||||
|
||||
require __DIR__ . '/src/includes/autoload.php';
|
||||
|
||||
// Build request and detect flush
|
||||
$request = HTTPRequestBuilder::createFromEnvironment();
|
||||
|
||||
|
||||
/************************************************************************************
|
||||
************************************************************************************
|
||||
** **
|
||||
** If you can read this text in your browser then you don't have PHP installed. **
|
||||
** Please install PHP 5.5.0 or higher . **
|
||||
** **
|
||||
************************************************************************************
|
||||
************************************************************************************/
|
||||
|
||||
if (version_compare(phpversion(), '5.5.0', '<')) {
|
||||
header($_SERVER['SERVER_PROTOCOL'] . " 500 Server Error");
|
||||
echo str_replace('$PHPVersion', phpversion(), file_get_contents("Dev/Install/php5-required.html"));
|
||||
die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main file that handles every page request.
|
||||
*
|
||||
* The main.php does a number of set-up activities for the request.
|
||||
*
|
||||
* - Includes the .env file in your webroot
|
||||
* - Gets an up-to-date manifest from {@link ManifestBuilder}
|
||||
* - Sets up error handlers with {@link Debug::loadErrorHandlers()}
|
||||
* - Calls {@link DB::connect()}, passing it the global variable $databaseConfig that should
|
||||
* be defined in an _config.php
|
||||
* - Sets up the default director rules using {@link Director::$rules}
|
||||
*
|
||||
* After that, it calls {@link Director::direct()}, which is responsible for doing most of the
|
||||
* real work.
|
||||
*
|
||||
* CONFIGURING THE WEBSERVER
|
||||
*
|
||||
* To use SilverStripe, every request that doesn't point directly to a file should be rewritten to
|
||||
* framework/main.php?url=(url). For example, http://www.example.com/about-us/rss would be rewritten
|
||||
* to http://www.example.com/framework/main.php?url=about-us/rss
|
||||
*
|
||||
* It's important that requests that point directly to a file aren't rewritten; otherwise, visitors
|
||||
* won't be able to download any CSS, JS, image files, or other downloads.
|
||||
*
|
||||
* On Apache, RewriteEngine can be used to do this.
|
||||
*
|
||||
* @see Director::direct()
|
||||
*/
|
||||
|
||||
// require composers autoloader, unless it is already installed
|
||||
if(!class_exists('Composer\\Autoload\\ClassLoader', false)) {
|
||||
if (file_exists($autoloadPath = dirname(__DIR__) . '/vendor/autoload.php')) {
|
||||
require_once $autoloadPath;
|
||||
}
|
||||
else {
|
||||
if (!headers_sent()) {
|
||||
header($_SERVER['SERVER_PROTOCOL'] . " 500 Server Error");
|
||||
header('Content-Type: text/plain');
|
||||
}
|
||||
echo "Failed to include composer's autoloader, unable to continue\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// IIS will sometimes generate this.
|
||||
if(!empty($_SERVER['HTTP_X_ORIGINAL_URL'])) {
|
||||
$_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_ORIGINAL_URL'];
|
||||
}
|
||||
|
||||
// Enable the entity loader to be able to load XML in Zend_Locale_Data
|
||||
libxml_disable_entity_loader(false);
|
||||
|
||||
/**
|
||||
* Figure out the request URL
|
||||
*/
|
||||
global $url;
|
||||
|
||||
// Helper to safely parse and load a querystring fragment
|
||||
$parseQuery = function($query) {
|
||||
parse_str($query, $_GET);
|
||||
if ($_GET) $_REQUEST = array_merge((array)$_REQUEST, (array)$_GET);
|
||||
};
|
||||
|
||||
// Apache rewrite rules and IIS use this
|
||||
if (isset($_GET['url']) && php_sapi_name() !== 'cli-server') {
|
||||
|
||||
// Prevent injection of url= querystring argument by prioritising any leading url argument
|
||||
if(isset($_SERVER['QUERY_STRING']) &&
|
||||
preg_match('/^(?<url>url=[^&?]*)(?<query>.*[&?]url=.*)$/', $_SERVER['QUERY_STRING'], $results)
|
||||
) {
|
||||
$queryString = $results['query'].'&'.$results['url'];
|
||||
$parseQuery($queryString);
|
||||
}
|
||||
|
||||
$url = $_GET['url'];
|
||||
|
||||
// IIS includes get variables in url
|
||||
$i = strpos($url, '?');
|
||||
if($i !== false) {
|
||||
$url = substr($url, 0, $i);
|
||||
}
|
||||
|
||||
// Lighttpd and PHP 5.4's built-in webserver use this
|
||||
} else {
|
||||
// Get raw URL -- still needs to be decoded below (after parsing out query string).
|
||||
$url = $_SERVER['REQUEST_URI'];
|
||||
|
||||
// Querystring args need to be explicitly parsed
|
||||
if(strpos($url,'?') !== false) {
|
||||
list($url, $query) = explode('?',$url,2);
|
||||
$parseQuery($query);
|
||||
}
|
||||
|
||||
// Decode URL now that it has been separated from query string.
|
||||
$url = urldecode($url);
|
||||
|
||||
// Pass back to the webserver for files that exist
|
||||
if(php_sapi_name() === 'cli-server' && file_exists(BASE_PATH . $url) && is_file(BASE_PATH . $url)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove base folders from the URL if webroot is hosted in a subfolder
|
||||
if (substr(strtolower($url), 0, strlen(BASE_URL)) == strtolower(BASE_URL)) $url = substr($url, strlen(BASE_URL));
|
||||
|
||||
/**
|
||||
* Include SilverStripe's core code
|
||||
*/
|
||||
require_once('Core/Startup/ErrorControlChain.php');
|
||||
require_once('Core/Startup/ParameterConfirmationToken.php');
|
||||
|
||||
// Prepare tokens and execute chain
|
||||
$reloadToken = ParameterConfirmationToken::prepare_tokens(array('isTest', 'isDev', 'flush'));
|
||||
$chain = new ErrorControlChain();
|
||||
$chain
|
||||
->then(function($chain) use ($reloadToken) {
|
||||
// If no redirection is necessary then we can disable error supression
|
||||
if (!$reloadToken) $chain->setSuppression(false);
|
||||
|
||||
// Load in core
|
||||
require_once('Core/Core.php');
|
||||
|
||||
// Connect to database
|
||||
global $databaseConfig;
|
||||
if ($databaseConfig) DB::connect($databaseConfig);
|
||||
|
||||
// Check if a token is requesting a redirect
|
||||
if (!$reloadToken) return;
|
||||
|
||||
// Otherwise, we start up the session if needed
|
||||
if(!isset($_SESSION) && Session::request_contains_session_id()) {
|
||||
Session::start();
|
||||
}
|
||||
|
||||
// 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')) {
|
||||
$reloadToken->reloadWithToken();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fail and redirect the user to the login page
|
||||
$loginPage = Director::absoluteURL(Security::config()->login_url);
|
||||
$loginPage .= "?BackURL=" . urlencode($_SERVER['REQUEST_URI']);
|
||||
header('location: '.$loginPage, true, 302);
|
||||
die;
|
||||
})
|
||||
// Finally if a token was requested but there was an error while figuring out if it's allowed, do it anyway
|
||||
->thenIfErrored(function() use ($reloadToken){
|
||||
if ($reloadToken) {
|
||||
$reloadToken->reloadWithToken();
|
||||
}
|
||||
})
|
||||
->execute();
|
||||
|
||||
global $databaseConfig;
|
||||
|
||||
// Redirect to the installer if no database is selected
|
||||
if(!isset($databaseConfig) || !isset($databaseConfig['database']) || !$databaseConfig['database']) {
|
||||
|
||||
// Is there an _ss_environment.php file?
|
||||
if(file_exists(BASE_PATH . '/_ss_environment.php') || file_exists(dirname(BASE_PATH) . '/_ss_environment.php')) {
|
||||
header($_SERVER['SERVER_PROTOCOL'] . " 500 Server Error");
|
||||
$dv = new SilverStripe\Dev\DebugView();
|
||||
echo $dv->renderHeader();
|
||||
echo $dv->renderInfo(
|
||||
"Configuraton Error",
|
||||
Director::absoluteBaseURL()
|
||||
);
|
||||
echo $dv->renderParagraph(
|
||||
'You need to replace your _ss_environment.php file with a .env file, or with environment variables.<br><br>'
|
||||
. 'See the <a href="https://docs.silverstripe.org/en/4/getting_started/environment_management/">'
|
||||
. 'Environment Management</a> docs for more information.'
|
||||
);
|
||||
echo $dv->renderFooter();
|
||||
|
||||
die();
|
||||
}
|
||||
|
||||
if(!file_exists(BASE_PATH . '/install.php')) {
|
||||
header($_SERVER['SERVER_PROTOCOL'] . " 500 Server Error");
|
||||
die('SilverStripe Framework requires a $databaseConfig defined.');
|
||||
}
|
||||
$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'];
|
||||
$s = (isset($_SERVER['SSL']) || (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) ? 's' : '';
|
||||
$installURL = "http$s://" . $host . BASE_URL . '/install.php';
|
||||
|
||||
// The above dirname() will equate to "\" on Windows when installing directly from http://localhost (not using
|
||||
// a sub-directory), this really messes things up in some browsers. Let's get rid of the backslashes
|
||||
$installURL = str_replace('\\', '', $installURL);
|
||||
|
||||
header("Location: $installURL");
|
||||
die();
|
||||
}
|
||||
|
||||
// Direct away - this is the "main" function, that hands control to the appropriate controller
|
||||
DataModel::set_inst(new DataModel());
|
||||
Director::direct($url, DataModel::inst());
|
||||
// Default application
|
||||
$kernel = new CoreKernel(BASE_PATH);
|
||||
$app = new HTTPApplication($kernel);
|
||||
$app->addMiddleware(new ErrorControlChainMiddleware($app));
|
||||
$response = $app->handle($request);
|
||||
$response->output();
|
||||
|
@ -1,9 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage core
|
||||
*
|
||||
* Alternative main.php file for servers that need the php5 extension
|
||||
*/
|
||||
include("main.php");
|
||||
?>
|
69
src/Control/CLIRequestBuilder.php
Normal file
69
src/Control/CLIRequestBuilder.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Control;
|
||||
|
||||
/**
|
||||
* CLI specific request building logic
|
||||
*/
|
||||
class CLIRequestBuilder extends HTTPRequestBuilder
|
||||
{
|
||||
public static function cleanEnvironment(array $variables)
|
||||
{
|
||||
// Create all blank vars
|
||||
foreach (['_REQUEST', '_GET', '_POST', '_SESSION', '_SERVER', '_COOKIE', '_ENV', '_FILES'] as $key) {
|
||||
if (!isset($variables[$key])) {
|
||||
$variables[$key] = [];
|
||||
};
|
||||
}
|
||||
|
||||
// We update the $_SERVER variable to contain data consistent with the rest of the application.
|
||||
$variables['_SERVER'] = array_merge(array(
|
||||
'SERVER_PROTOCOL' => 'HTTP/1.1',
|
||||
'HTTP_ACCEPT' => 'text/plain;q=0.5',
|
||||
'HTTP_ACCEPT_LANGUAGE' => '*;q=0.5',
|
||||
'HTTP_ACCEPT_ENCODING' => '',
|
||||
'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1;q=0.5',
|
||||
'SERVER_SIGNATURE' => 'Command-line PHP/' . phpversion(),
|
||||
'SERVER_SOFTWARE' => 'PHP/' . phpversion(),
|
||||
'SERVER_ADDR' => '127.0.0.1',
|
||||
'REMOTE_ADDR' => '127.0.0.1',
|
||||
'REQUEST_METHOD' => 'GET',
|
||||
'HTTP_USER_AGENT' => 'CLI',
|
||||
), $variables['_SERVER']);
|
||||
|
||||
/**
|
||||
* Process arguments and load them into the $_GET and $_REQUEST arrays
|
||||
* For example,
|
||||
* sake my/url somearg otherarg key=val --otherkey=val third=val&fourth=val
|
||||
*
|
||||
* Will result in the following get data:
|
||||
* args => array('somearg', 'otherarg'),
|
||||
* key => val
|
||||
* otherkey => val
|
||||
* third => val
|
||||
* fourth => val
|
||||
*/
|
||||
if (isset($variables['_SERVER']['argv'][2])) {
|
||||
$args = array_slice($variables['_SERVER']['argv'], 2);
|
||||
foreach ($args as $arg) {
|
||||
if (strpos($arg, '=') == false) {
|
||||
$variables['_GET']['args'][] = $arg;
|
||||
} else {
|
||||
$newItems = array();
|
||||
parse_str((substr($arg, 0, 2) == '--') ? substr($arg, 2) : $arg, $newItems);
|
||||
$variables['_GET'] = array_merge($variables['_GET'], $newItems);
|
||||
}
|
||||
}
|
||||
$_REQUEST = array_merge($_REQUEST, $variables['_GET']);
|
||||
}
|
||||
|
||||
// Set 'url' GET parameter
|
||||
if (isset($variables['_SERVER']['argv'][1])) {
|
||||
$variables['_GET']['url'] = $variables['_SERVER']['argv'][1];
|
||||
$variables['_SERVER']['REQUEST_URI'] = $variables['_SERVER']['argv'][1];
|
||||
}
|
||||
|
||||
// Parse rest of variables as standard
|
||||
return parent::cleanEnvironment($variables);
|
||||
}
|
||||
}
|
@ -3,9 +3,7 @@
|
||||
namespace SilverStripe\Control;
|
||||
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\ORM\FieldType\DBHTMLText;
|
||||
use SilverStripe\Security\BasicAuth;
|
||||
use SilverStripe\Security\Member;
|
||||
@ -47,13 +45,6 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
|
||||
*/
|
||||
protected $action;
|
||||
|
||||
/**
|
||||
* The {@link Session} object for this controller.
|
||||
*
|
||||
* @var Session
|
||||
*/
|
||||
protected $session;
|
||||
|
||||
/**
|
||||
* Stack of current controllers. Controller::$controller_stack[0] is the current controller.
|
||||
*
|
||||
@ -61,6 +52,14 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
|
||||
*/
|
||||
protected static $controller_stack = array();
|
||||
|
||||
/**
|
||||
* Assign templates for this controller.
|
||||
* Map of action => template name
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $templates = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
@ -152,16 +151,14 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
|
||||
* @todo setDataModel and setRequest are redundantly called in parent::handleRequest() - sort this out
|
||||
*
|
||||
* @param HTTPRequest $request
|
||||
* @param DataModel $model
|
||||
*/
|
||||
protected function beforeHandleRequest(HTTPRequest $request, DataModel $model)
|
||||
protected function beforeHandleRequest(HTTPRequest $request)
|
||||
{
|
||||
//Set up the internal dependencies (request, response)
|
||||
$this->setRequest($request);
|
||||
//Push the current controller to protect against weird session issues
|
||||
$this->pushCurrent();
|
||||
//Set up the internal dependencies (request, response, datamodel)
|
||||
$this->setRequest($request);
|
||||
$this->setResponse(new HTTPResponse());
|
||||
$this->setDataModel($model);
|
||||
//kick off the init functionality
|
||||
$this->doInit();
|
||||
}
|
||||
@ -192,24 +189,22 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
|
||||
* and end the method with $this->afterHandleRequest()
|
||||
*
|
||||
* @param HTTPRequest $request
|
||||
* @param DataModel $model
|
||||
*
|
||||
* @return HTTPResponse
|
||||
*/
|
||||
public function handleRequest(HTTPRequest $request, DataModel $model)
|
||||
public function handleRequest(HTTPRequest $request)
|
||||
{
|
||||
if (!$request) {
|
||||
user_error("Controller::handleRequest() not passed a request!", E_USER_ERROR);
|
||||
}
|
||||
|
||||
//set up the controller for the incoming request
|
||||
$this->beforeHandleRequest($request, $model);
|
||||
$this->beforeHandleRequest($request);
|
||||
|
||||
//if the before handler manipulated the response in a way that we shouldn't proceed, then skip our request
|
||||
// handling
|
||||
if (!$this->getResponse()->isFinished()) {
|
||||
//retrieve the response for the request
|
||||
$response = parent::handleRequest($request, $model);
|
||||
$response = parent::handleRequest($request);
|
||||
|
||||
//prepare the response (we can receive an assortment of response types (strings/objects/HTTPResponses)
|
||||
$this->prepareResponse($response);
|
||||
@ -520,7 +515,7 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
|
||||
$template = $this->getViewer($this->getAction());
|
||||
|
||||
// if the object is already customised (e.g. through Controller->run()), use it
|
||||
$obj = ($this->customisedObj) ? $this->customisedObj : $this;
|
||||
$obj = $this->getCustomisedObj() ?: $this;
|
||||
|
||||
if ($params) {
|
||||
$obj = $this->customise($params);
|
||||
@ -593,18 +588,15 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
|
||||
* Pushes this controller onto the stack of current controllers. This means that any redirection,
|
||||
* session setting, or other things that rely on Controller::curr() will now write to this
|
||||
* controller object.
|
||||
*
|
||||
* Note: Ensure this controller is assigned a request with a valid session before pushing
|
||||
* it to the stack.
|
||||
*/
|
||||
public function pushCurrent()
|
||||
{
|
||||
// Ensure this controller has a valid session
|
||||
$this->getRequest()->getSession();
|
||||
array_unshift(self::$controller_stack, $this);
|
||||
// Create a new session object
|
||||
if (!$this->session) {
|
||||
if (isset(self::$controller_stack[1])) {
|
||||
$this->session = self::$controller_stack[1]->getSession();
|
||||
} else {
|
||||
$this->session = Injector::inst()->create('SilverStripe\\Control\\Session', array());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -653,26 +645,6 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
|
||||
return $this->getResponse() && $this->getResponse()->getHeader('Location');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Session object representing this Controller's session.
|
||||
*
|
||||
* @return Session
|
||||
*/
|
||||
public function getSession()
|
||||
{
|
||||
return $this->session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Session object.
|
||||
*
|
||||
* @param Session $session
|
||||
*/
|
||||
public function setSession(Session $session)
|
||||
{
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins two or more link segments together, putting a slash between them if necessary. Use this
|
||||
* for building the results of {@link Link()} methods. If either of the links have query strings,
|
||||
|
@ -2,15 +2,12 @@
|
||||
|
||||
namespace SilverStripe\Control;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\CMS\Model\SiteTree;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\Core\Environment;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Kernel;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\ORM\ArrayLib;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
use SilverStripe\View\Requirements;
|
||||
use SilverStripe\View\Requirements_Backend;
|
||||
@ -120,112 +117,40 @@ class Director implements TemplateGlobalProvider
|
||||
*
|
||||
* @uses handleRequest() rule-lookup logic is handled by this.
|
||||
* @uses TestController::handleRequest() This handles the page logic for a Director::direct() call.
|
||||
* @param string $url
|
||||
* @param DataModel $model
|
||||
* @param HTTPRequest $request
|
||||
* @return HTTPResponse
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
public static function direct($url, DataModel $model)
|
||||
public static function direct(HTTPRequest $request)
|
||||
{
|
||||
// check allowed hosts
|
||||
if (getenv('SS_ALLOWED_HOSTS') && !Director::is_cli()) {
|
||||
$all_allowed_hosts = explode(',', getenv('SS_ALLOWED_HOSTS'));
|
||||
if (!in_array(static::host(), $all_allowed_hosts)) {
|
||||
throw new HTTPResponse_Exception('Invalid Host', 400);
|
||||
if (getenv('SS_ALLOWED_HOSTS') && !static::is_cli()) {
|
||||
$allowedHosts = explode(',', getenv('SS_ALLOWED_HOSTS'));
|
||||
if (!in_array(static::host(), $allowedHosts)) {
|
||||
return new HTTPResponse('Invalid Host', 400);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Validate $_FILES array before merging it with $_POST
|
||||
foreach ($_FILES as $k => $v) {
|
||||
if (is_array($v['tmp_name'])) {
|
||||
$v = ArrayLib::array_values_recursive($v['tmp_name']);
|
||||
foreach ($v as $tmpFile) {
|
||||
if ($tmpFile && !is_uploaded_file($tmpFile)) {
|
||||
user_error("File upload '$k' doesn't appear to be a valid upload", E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($v['tmp_name'] && !is_uploaded_file($v['tmp_name'])) {
|
||||
user_error("File upload '$k' doesn't appear to be a valid upload", E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$req = new HTTPRequest(
|
||||
(isset($_SERVER['X-HTTP-Method-Override']))
|
||||
? $_SERVER['X-HTTP-Method-Override']
|
||||
: $_SERVER['REQUEST_METHOD'],
|
||||
$url,
|
||||
$_GET,
|
||||
ArrayLib::array_merge_recursive((array) $_POST, (array) $_FILES),
|
||||
@file_get_contents('php://input')
|
||||
);
|
||||
|
||||
$headers = self::extract_request_headers($_SERVER);
|
||||
foreach ($headers as $header => $value) {
|
||||
$req->addHeader($header, $value);
|
||||
}
|
||||
|
||||
// Initiate an empty session - doesn't initialize an actual PHP session until saved (see below)
|
||||
$session = Session::create(isset($_SESSION) ? $_SESSION : array());
|
||||
|
||||
// Only resume a session if its not started already, and a session identifier exists
|
||||
if (!isset($_SESSION) && Session::request_contains_session_id()) {
|
||||
$session->inst_start();
|
||||
}
|
||||
|
||||
$output = RequestProcessor::singleton()->preRequest($req, $session, $model);
|
||||
|
||||
// Pre-request
|
||||
$output = RequestProcessor::singleton()->preRequest($request);
|
||||
if ($output === false) {
|
||||
// @TODO Need to NOT proceed with the request in an elegant manner
|
||||
throw new HTTPResponse_Exception(_t('SilverStripe\\Control\\Director.INVALID_REQUEST', 'Invalid request'), 400);
|
||||
return new HTTPResponse(_t(__CLASS__.'.INVALID_REQUEST', 'Invalid request'), 400);
|
||||
}
|
||||
|
||||
$result = Director::handleRequest($req, $session, $model);
|
||||
// Generate output
|
||||
$result = static::handleRequest($request);
|
||||
|
||||
// Save session data. Note that inst_save() will start/resume the session if required.
|
||||
$session->inst_save();
|
||||
// Save session data. Note that save() will start/resume the session if required.
|
||||
$request->getSession()->save();
|
||||
|
||||
// Return code for a redirection request
|
||||
if (is_string($result) && substr($result, 0, 9) == 'redirect:') {
|
||||
$url = substr($result, 9);
|
||||
|
||||
if (Director::is_cli()) {
|
||||
// on cli, follow SilverStripe redirects automatically
|
||||
Director::direct(
|
||||
str_replace(Director::absoluteBaseURL(), '', $url),
|
||||
DataModel::inst()
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
$response = new HTTPResponse();
|
||||
$response->redirect($url);
|
||||
$res = RequestProcessor::singleton()->postRequest($req, $response, $model);
|
||||
|
||||
if ($res !== false) {
|
||||
$response->output();
|
||||
}
|
||||
}
|
||||
// Handle a controller
|
||||
} elseif ($result) {
|
||||
if ($result instanceof HTTPResponse) {
|
||||
$response = $result;
|
||||
} else {
|
||||
$response = new HTTPResponse();
|
||||
$response->setBody($result);
|
||||
// Post-request handling
|
||||
$postRequest = RequestProcessor::singleton()->postRequest($request, $result);
|
||||
if ($postRequest === false) {
|
||||
return new HTTPResponse(_t(__CLASS__ . '.REQUEST_ABORTED', 'Request aborted'), 500);
|
||||
}
|
||||
|
||||
$res = RequestProcessor::singleton()->postRequest($req, $response, $model);
|
||||
if ($res !== false) {
|
||||
$response->output();
|
||||
} else {
|
||||
// @TODO Proper response here.
|
||||
throw new HTTPResponse_Exception("Invalid response");
|
||||
}
|
||||
|
||||
|
||||
//$controllerObj->getSession()->inst_save();
|
||||
}
|
||||
// Return
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -253,7 +178,7 @@ class Director implements TemplateGlobalProvider
|
||||
*/
|
||||
public static function test(
|
||||
$url,
|
||||
$postVars = null,
|
||||
$postVars = [],
|
||||
$session = array(),
|
||||
$httpMethod = null,
|
||||
$body = null,
|
||||
@ -261,132 +186,157 @@ class Director implements TemplateGlobalProvider
|
||||
$cookies = array(),
|
||||
&$request = null
|
||||
) {
|
||||
return static::mockRequest(
|
||||
function (HTTPRequest $request) {
|
||||
return static::direct($request);
|
||||
},
|
||||
$url,
|
||||
$postVars,
|
||||
$session,
|
||||
$httpMethod,
|
||||
$body,
|
||||
$headers,
|
||||
$cookies,
|
||||
$request
|
||||
);
|
||||
}
|
||||
|
||||
Config::nest();
|
||||
Injector::nest();
|
||||
/**
|
||||
* Mock a request, passing this to the given callback, before resetting.
|
||||
*
|
||||
* @param callable $callback Action to pass the HTTPRequst object
|
||||
* @param string $url The URL to build
|
||||
* @param array $postVars The $_POST & $_FILES variables.
|
||||
* @param array|Session $session The {@link Session} object representing the current session.
|
||||
* By passing the same object to multiple calls of Director::test(), you can simulate a persisted
|
||||
* session.
|
||||
* @param string $httpMethod The HTTP method, such as GET or POST. It will default to POST if
|
||||
* postVars is set, GET otherwise. Overwritten by $postVars['_method'] if present.
|
||||
* @param string $body The HTTP body.
|
||||
* @param array $headers HTTP headers with key-value pairs.
|
||||
* @param array|Cookie_Backend $cookies to populate $_COOKIE.
|
||||
* @param HTTPRequest $request The {@see SS_HTTP_Request} object generated as a part of this request.
|
||||
* @return mixed Result of callback
|
||||
*/
|
||||
public static function mockRequest(
|
||||
$callback,
|
||||
$url,
|
||||
$postVars = [],
|
||||
$session = [],
|
||||
$httpMethod = null,
|
||||
$body = null,
|
||||
$headers = [],
|
||||
$cookies = [],
|
||||
&$request = null
|
||||
) {
|
||||
// Build list of cleanup promises
|
||||
$finally = [];
|
||||
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
$kernel->nest();
|
||||
$finally[] = function () use ($kernel) {
|
||||
$kernel->activate();
|
||||
};
|
||||
|
||||
// backup existing vars, and create new vars
|
||||
$existingVars = Environment::getVariables();
|
||||
$finally[] = function () use ($existingVars) {
|
||||
Environment::setVariables($existingVars);
|
||||
};
|
||||
$newVars = $existingVars;
|
||||
|
||||
// These are needed so that calling Director::test() does not muck with whoever is calling it.
|
||||
// Really, it's some inappropriate coupling and should be resolved by making less use of statics.
|
||||
$oldReadingMode = null;
|
||||
if (class_exists(Versioned::class)) {
|
||||
$oldReadingMode = Versioned::get_reading_mode();
|
||||
}
|
||||
$getVars = array();
|
||||
|
||||
if (!$httpMethod) {
|
||||
$httpMethod = ($postVars || is_array($postVars)) ? "POST" : "GET";
|
||||
$finally[] = function () use ($oldReadingMode) {
|
||||
Versioned::set_reading_mode($oldReadingMode);
|
||||
};
|
||||
}
|
||||
|
||||
if (!$session) {
|
||||
$session = Session::create([]);
|
||||
// Default httpMethod
|
||||
$newVars['_SERVER']['REQUEST_METHOD'] = $httpMethod ?: ($postVars ? "POST" : "GET");
|
||||
$newVars['_POST'] = (array)$postVars;
|
||||
|
||||
// Setup session
|
||||
if ($session instanceof Session) {
|
||||
// Note: If passing $session as object, ensure that changes are written back
|
||||
// This is important for classes such as FunctionalTest which emulate cross-request persistence
|
||||
$newVars['_SESSION'] = $session->getAll();
|
||||
$finally[] = function () use ($session) {
|
||||
if (isset($_SESSION)) {
|
||||
foreach ($_SESSION as $key => $value) {
|
||||
$session->set($key, $value);
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
$newVars['_SESSION'] = $session ?: [];
|
||||
}
|
||||
|
||||
// Setup cookies
|
||||
$cookieJar = $cookies instanceof Cookie_Backend
|
||||
? $cookies
|
||||
: Injector::inst()->createWithArgs(Cookie_Backend::class, array($cookies ?: []));
|
||||
|
||||
// Back up the current values of the superglobals
|
||||
$existingRequestVars = isset($_REQUEST) ? $_REQUEST : array();
|
||||
$existingGetVars = isset($_GET) ? $_GET : array();
|
||||
$existingPostVars = isset($_POST) ? $_POST : array();
|
||||
$existingSessionVars = isset($_SESSION) ? $_SESSION : array();
|
||||
$existingCookies = isset($_COOKIE) ? $_COOKIE : array();
|
||||
$existingServer = isset($_SERVER) ? $_SERVER : array();
|
||||
|
||||
$existingRequirementsBackend = Requirements::backend();
|
||||
|
||||
$newVars['_COOKIE'] = $cookieJar->getAll(false);
|
||||
Cookie::config()->update('report_errors', false);
|
||||
Requirements::set_backend(Requirements_Backend::create());
|
||||
Injector::inst()->registerService($cookieJar, Cookie_Backend::class);
|
||||
|
||||
if (strpos($url, '#') !== false) {
|
||||
$url = substr($url, 0, strpos($url, '#'));
|
||||
}
|
||||
// Backup requirements
|
||||
$existingRequirementsBackend = Requirements::backend();
|
||||
Requirements::set_backend(Requirements_Backend::create());
|
||||
$finally[] = function () use ($existingRequirementsBackend) {
|
||||
Requirements::set_backend($existingRequirementsBackend);
|
||||
};
|
||||
|
||||
// Strip any hash
|
||||
$url = strtok($url, '#');
|
||||
|
||||
// Handle absolute URLs
|
||||
if (parse_url($url, PHP_URL_HOST)) {
|
||||
$bits = parse_url($url);
|
||||
|
||||
// If a port is mentioned in the absolute URL, be sure to add that into the HTTP host
|
||||
if (isset($bits['port'])) {
|
||||
$_SERVER['HTTP_HOST'] = $bits['host'].':'.$bits['port'];
|
||||
} else {
|
||||
$_SERVER['HTTP_HOST'] = $bits['host'];
|
||||
}
|
||||
$newVars['_SERVER']['HTTP_HOST'] = isset($bits['port'])
|
||||
? $bits['host'].':'.$bits['port']
|
||||
: $bits['host'];
|
||||
}
|
||||
|
||||
// Ensure URL is properly made relative.
|
||||
// Example: url passed is "/ss31/my-page" (prefixed with BASE_URL), this should be changed to "my-page"
|
||||
$url = self::makeRelative($url);
|
||||
|
||||
$urlWithQuerystring = $url;
|
||||
if (strpos($url, '?') !== false) {
|
||||
list($url, $getVarsEncoded) = explode('?', $url, 2);
|
||||
parse_str($getVarsEncoded, $getVars);
|
||||
parse_str($getVarsEncoded, $newVars['_GET']);
|
||||
} else {
|
||||
$newVars['_GET'] = [];
|
||||
}
|
||||
$newVars['_SERVER']['REQUEST_URI'] = Director::baseURL() . $url;
|
||||
$newVars['_REQUEST'] = array_merge($newVars['_GET'], $newVars['_POST']);
|
||||
|
||||
// Replace the super globals with appropriate test values
|
||||
$_REQUEST = ArrayLib::array_merge_recursive((array) $getVars, (array) $postVars);
|
||||
$_GET = (array) $getVars;
|
||||
$_POST = (array) $postVars;
|
||||
$_SESSION = $session ? $session->inst_getAll() : array();
|
||||
$_COOKIE = $cookieJar->getAll(false);
|
||||
Injector::inst()->registerService($cookieJar, Cookie_Backend::class);
|
||||
$_SERVER['REQUEST_URI'] = Director::baseURL() . $urlWithQuerystring;
|
||||
// Normalise vars
|
||||
$newVars = HTTPRequestBuilder::cleanEnvironment($newVars);
|
||||
|
||||
$request = new HTTPRequest($httpMethod, $url, $getVars, $postVars, $body);
|
||||
// Create new request
|
||||
$request = HTTPRequestBuilder::createFromVariables($newVars, $body);
|
||||
if ($headers) {
|
||||
foreach ($headers as $k => $v) {
|
||||
$request->addHeader($k, $v);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply new vars to environment
|
||||
Environment::setVariables($newVars);
|
||||
|
||||
try {
|
||||
// Pre-request filtering
|
||||
$model = DataModel::inst();
|
||||
$requestProcessor = Injector::inst()->get(RequestProcessor::class);
|
||||
$output = $requestProcessor->preRequest($request, $session, $model);
|
||||
if ($output === false) {
|
||||
throw new HTTPResponse_Exception(_t('SilverStripe\\Control\\Director.INVALID_REQUEST', 'Invalid request'), 400);
|
||||
}
|
||||
|
||||
// Process request
|
||||
$result = Director::handleRequest($request, $session, $model);
|
||||
|
||||
// Ensure that the result is an HTTPResponse object
|
||||
if (is_string($result)) {
|
||||
if (substr($result, 0, 9) == 'redirect:') {
|
||||
$response = new HTTPResponse();
|
||||
$response->redirect(substr($result, 9));
|
||||
$result = $response;
|
||||
} else {
|
||||
$result = new HTTPResponse($result);
|
||||
}
|
||||
}
|
||||
|
||||
$output = $requestProcessor->postRequest($request, $result, $model);
|
||||
if ($output === false) {
|
||||
throw new HTTPResponse_Exception("Invalid response");
|
||||
}
|
||||
|
||||
// Return valid response
|
||||
return $result;
|
||||
// Normal request handling
|
||||
return call_user_func($callback, $request);
|
||||
} finally {
|
||||
// Restore the super globals
|
||||
$_REQUEST = $existingRequestVars;
|
||||
$_GET = $existingGetVars;
|
||||
$_POST = $existingPostVars;
|
||||
$_SESSION = $existingSessionVars;
|
||||
$_COOKIE = $existingCookies;
|
||||
$_SERVER = $existingServer;
|
||||
|
||||
Requirements::set_backend($existingRequirementsBackend);
|
||||
|
||||
// These are needed so that calling Director::test() does not muck with whoever is calling it.
|
||||
// Really, it's some inappropriate coupling and should be resolved by making less use of statics
|
||||
if (class_exists(Versioned::class)) {
|
||||
Versioned::set_reading_mode($oldReadingMode);
|
||||
// Restore state in reverse order to assignment
|
||||
foreach (array_reverse($finally) as $callback) {
|
||||
call_user_func($callback);
|
||||
}
|
||||
|
||||
Injector::unnest(); // Restore old CookieJar, etc
|
||||
Config::unnest();
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,15 +345,14 @@ class Director implements TemplateGlobalProvider
|
||||
*
|
||||
* @skipUpgrade
|
||||
* @param HTTPRequest $request
|
||||
* @param Session $session
|
||||
* @param DataModel $model
|
||||
* @return HTTPResponse|string
|
||||
* @return HTTPResponse
|
||||
*/
|
||||
protected static function handleRequest(HTTPRequest $request, Session $session, DataModel $model)
|
||||
protected static function handleRequest(HTTPRequest $request)
|
||||
{
|
||||
$rules = Director::config()->uninherited('rules');
|
||||
|
||||
foreach ($rules as $pattern => $controllerOptions) {
|
||||
// Normalise route rule
|
||||
if (is_string($controllerOptions)) {
|
||||
if (substr($controllerOptions, 0, 2) == '->') {
|
||||
$controllerOptions = array('Redirect' => substr($controllerOptions, 2));
|
||||
@ -412,7 +361,9 @@ class Director implements TemplateGlobalProvider
|
||||
}
|
||||
}
|
||||
|
||||
if (($arguments = $request->match($pattern, true)) !== false) {
|
||||
// Match pattern
|
||||
$arguments = $request->match($pattern, true);
|
||||
if ($arguments !== false) {
|
||||
$request->setRouteParams($controllerOptions);
|
||||
// controllerOptions provide some default arguments
|
||||
$arguments = array_merge($controllerOptions, $arguments);
|
||||
@ -424,24 +375,20 @@ class Director implements TemplateGlobalProvider
|
||||
|
||||
// Handle redirection
|
||||
if (isset($arguments['Redirect'])) {
|
||||
return "redirect:" . Director::absoluteURL($arguments['Redirect'], true);
|
||||
} else {
|
||||
// Redirection
|
||||
$response = new HTTPResponse();
|
||||
$response->redirect(static::absoluteURL($arguments['Redirect']));
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Find the controller name
|
||||
$controller = $arguments['Controller'];
|
||||
$controllerObj = Injector::inst()->create($controller);
|
||||
$controllerObj->setSession($session);
|
||||
|
||||
try {
|
||||
$result = $controllerObj->handleRequest($request, $model);
|
||||
return $controllerObj->handleRequest($request);
|
||||
} catch (HTTPResponse_Exception $responseException) {
|
||||
$result = $responseException->getResponse();
|
||||
}
|
||||
if (!is_object($result) || $result instanceof HTTPResponse) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
user_error("Bad result from url " . $request->getURL() . " handled by " .
|
||||
get_class($controllerObj)." controller: ".get_class($result), E_USER_WARNING);
|
||||
return $responseException->getResponse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -852,36 +799,6 @@ class Director implements TemplateGlobalProvider
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a $_SERVER data array and extracts HTTP request headers.
|
||||
*
|
||||
* @param array $server
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function extract_request_headers(array $server)
|
||||
{
|
||||
$headers = array();
|
||||
|
||||
foreach ($server as $key => $value) {
|
||||
if (substr($key, 0, 5) == 'HTTP_') {
|
||||
$key = substr($key, 5);
|
||||
$key = strtolower(str_replace('_', ' ', $key));
|
||||
$key = str_replace(' ', '-', ucwords($key));
|
||||
$headers[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($server['CONTENT_TYPE'])) {
|
||||
$headers['Content-Type'] = $server['CONTENT_TYPE'];
|
||||
}
|
||||
if (isset($server['CONTENT_LENGTH'])) {
|
||||
$headers['Content-Length'] = $server['CONTENT_LENGTH'];
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a filesystem reference relative to the site root, return the full file-system path.
|
||||
*
|
||||
@ -942,17 +859,15 @@ class Director implements TemplateGlobalProvider
|
||||
* Skip any further processing and immediately respond with a redirect to the passed URL.
|
||||
*
|
||||
* @param string $destURL
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
protected static function force_redirect($destURL)
|
||||
{
|
||||
// Redirect to installer
|
||||
$response = new HTTPResponse();
|
||||
$response->redirect($destURL, 301);
|
||||
|
||||
HTTP::add_cache_headers($response);
|
||||
|
||||
// TODO: Use an exception - ATM we can be called from _config.php, before Director#handleRequest's try block
|
||||
$response->output();
|
||||
die;
|
||||
throw new HTTPResponse_Exception($response);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -983,19 +898,23 @@ class Director implements TemplateGlobalProvider
|
||||
*
|
||||
* @param array $patterns Array of regex patterns to match URLs that should be HTTPS.
|
||||
* @param string $secureDomain Secure domain to redirect to. Defaults to the current domain.
|
||||
*
|
||||
* @return bool|string String of URL when unit tests running, boolean FALSE if patterns don't match request URI.
|
||||
* @return bool true if already on SSL, false if doesn't match patterns (or cannot redirect)
|
||||
* @throws HTTPResponse_Exception Throws exception with redirect, if successful
|
||||
*/
|
||||
public static function forceSSL($patterns = null, $secureDomain = null)
|
||||
{
|
||||
// Calling from the command-line?
|
||||
// Already on SSL
|
||||
if (static::is_https()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Can't redirect without a url
|
||||
if (!isset($_SERVER['REQUEST_URI'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$matched = false;
|
||||
|
||||
if ($patterns) {
|
||||
$matched = false;
|
||||
$relativeURL = self::makeRelative(Director::absoluteURL($_SERVER['REQUEST_URI']));
|
||||
|
||||
// protect portions of the site based on the pattern
|
||||
@ -1005,33 +924,22 @@ class Director implements TemplateGlobalProvider
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// protect the entire site
|
||||
$matched = true;
|
||||
}
|
||||
|
||||
if ($matched && !self::is_https()) {
|
||||
// if an domain is specified, redirect to that instead of the current domain
|
||||
if ($secureDomain) {
|
||||
$url = 'https://' . $secureDomain . $_SERVER['REQUEST_URI'];
|
||||
} else {
|
||||
$url = $_SERVER['REQUEST_URI'];
|
||||
}
|
||||
|
||||
$destURL = str_replace('http:', 'https:', Director::absoluteURL($url));
|
||||
|
||||
// This coupling to SapphireTest is necessary to test the destination URL and to not interfere with tests
|
||||
if (class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test()) {
|
||||
return $destURL;
|
||||
} else {
|
||||
self::force_redirect($destURL);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (!$matched) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if an domain is specified, redirect to that instead of the current domain
|
||||
if (!$secureDomain) {
|
||||
$secureDomain = static::host();
|
||||
}
|
||||
$url = 'https://' . $secureDomain . $_SERVER['REQUEST_URI'];
|
||||
|
||||
// Force redirect
|
||||
self::force_redirect($url);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a redirect to a domain starting with "www."
|
||||
*/
|
||||
@ -1073,47 +981,7 @@ class Director implements TemplateGlobalProvider
|
||||
*/
|
||||
public static function is_cli()
|
||||
{
|
||||
return (php_sapi_name() == "cli");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the environment type of the current site.
|
||||
*
|
||||
* Typically, a SilverStripe site have a number of environments:
|
||||
* - Development environments, such a copy on your local machine.
|
||||
* - Test sites, such as the one you show the client before going live.
|
||||
* - The live site itself.
|
||||
*
|
||||
* The behaviour of these environments often varies slightly. For example, development sites may
|
||||
* have errors dumped to the screen, and order confirmation emails might be sent to the developer
|
||||
* instead of the client.
|
||||
*
|
||||
* To help with this, SilverStripe supports the notion of an environment type. The environment
|
||||
* type can be dev, test, or live.
|
||||
*
|
||||
* Dev mode can also be forced by putting ?isDev=1 in your URL, which will ask you to log in and
|
||||
* then push the site into dev mode for the remainder of the session. Putting ?isDev=0 onto the URL
|
||||
* can turn it back.
|
||||
*
|
||||
* Test mode can also be forced by putting ?isTest=1 in your URL, which will ask you to log in and
|
||||
* then push the site into test mode for the remainder of the session. Putting ?isTest=0 onto the URL
|
||||
* can turn it back.
|
||||
*
|
||||
* Generally speaking, these methods will be called from your _config.php file.
|
||||
*
|
||||
* Once the environment type is set, it can be checked with {@link Director::isDev()},
|
||||
* {@link Director::isTest()}, and {@link Director::isLive()}.
|
||||
*
|
||||
* @param string $environment
|
||||
*/
|
||||
public static function set_environment_type($environment)
|
||||
{
|
||||
if (!in_array($environment, ['dev', 'test', 'live'])) {
|
||||
throw new InvalidArgumentException(
|
||||
"Director::set_environment_type passed '$environment'. It should be passed dev, test, or live"
|
||||
);
|
||||
}
|
||||
self::$environment_type = $environment;
|
||||
return php_sapi_name() === "cli";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1124,22 +992,9 @@ class Director implements TemplateGlobalProvider
|
||||
*/
|
||||
public static function get_environment_type()
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
return 'live';
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
return $kernel->getEnvironment();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1175,37 +1030,6 @@ class Director implements TemplateGlobalProvider
|
||||
return self::get_environment_type() === 'test';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check or update any temporary environment specified in the session.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public static function session_environment()
|
||||
{
|
||||
// Set session from querystring
|
||||
if (isset($_GET['isDev'])) {
|
||||
if (isset($_SESSION)) {
|
||||
unset($_SESSION['isTest']); // In case we are changing from test mode
|
||||
$_SESSION['isDev'] = $_GET['isDev'];
|
||||
}
|
||||
return 'dev';
|
||||
} elseif (isset($_GET['isTest'])) {
|
||||
if (isset($_SESSION)) {
|
||||
unset($_SESSION['isDev']); // In case we are changing from dev mode
|
||||
$_SESSION['isTest'] = $_GET['isTest'];
|
||||
}
|
||||
return 'test';
|
||||
}
|
||||
// Check session
|
||||
if (isset($_SESSION['isDev']) && $_SESSION['isDev']) {
|
||||
return 'dev';
|
||||
} elseif (isset($_SESSION['isTest']) && $_SESSION['isTest']) {
|
||||
return 'test';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of strings of the method names of methods on the call that should be exposed
|
||||
* as global variables in the templates.
|
||||
|
@ -96,6 +96,72 @@ class Email extends ViewableData
|
||||
return \Swift_Validate::email($address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get send_all_emails_to
|
||||
*
|
||||
* @return array Keys are addresses, values are names
|
||||
*/
|
||||
public static function getSendAllEmailsTo()
|
||||
{
|
||||
return static::mergeConfiguredEmails('send_all_emails_to', 'SS_SEND_ALL_EMAILS_TO');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cc_all_emails_to
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getCCAllEmailsTo()
|
||||
{
|
||||
return static::mergeConfiguredEmails('cc_all_emails_to', 'SS_CC_ALL_EMAILS_TO');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bcc_all_emails_to
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getBCCAllEmailsTo()
|
||||
{
|
||||
return static::mergeConfiguredEmails('bcc_all_emails_to', 'SS_BCC_ALL_EMAILS_TO');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get send_all_emails_from
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getSendAllEmailsFrom()
|
||||
{
|
||||
return static::mergeConfiguredEmails('send_all_emails_from', 'SS_SEND_ALL_EMAILS_FROM');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise email list from config merged with env vars
|
||||
*
|
||||
* @param string $config Config key
|
||||
* @param string $env Env variable key
|
||||
* @return array Array of email addresses
|
||||
*/
|
||||
protected static function mergeConfiguredEmails($config, $env)
|
||||
{
|
||||
// Normalise config list
|
||||
$normalised = [];
|
||||
$source = (array)static::config()->get($config);
|
||||
foreach ($source as $address => $name) {
|
||||
if ($address && !is_numeric($address)) {
|
||||
$normalised[$address] = $name;
|
||||
} elseif ($name) {
|
||||
$normalised[$name] = null;
|
||||
}
|
||||
}
|
||||
$extra = getenv($env);
|
||||
if ($extra) {
|
||||
$normalised[$extra] = null;
|
||||
}
|
||||
return $normalised;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode an email-address to protect it from spambots.
|
||||
* At the moment only simple string substitutions,
|
||||
@ -197,7 +263,7 @@ class Email extends ViewableData
|
||||
public function setSwiftMessage($swiftMessage)
|
||||
{
|
||||
$swiftMessage->setDate(DBDatetime::now()->getTimestamp());
|
||||
if (!$swiftMessage->getFrom() && ($defaultFrom = $this->config()->admin_email)) {
|
||||
if (!$swiftMessage->getFrom() && ($defaultFrom = $this->config()->get('admin_email'))) {
|
||||
$swiftMessage->setFrom($defaultFrom);
|
||||
}
|
||||
$this->swiftMessage = $swiftMessage;
|
||||
@ -238,7 +304,7 @@ class Email extends ViewableData
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @return string
|
||||
*/
|
||||
public function getSender()
|
||||
{
|
||||
|
@ -11,36 +11,29 @@ class SwiftPlugin implements \Swift_Events_SendListener
|
||||
*/
|
||||
public function beforeSendPerformed(\Swift_Events_SendEvent $evt)
|
||||
{
|
||||
|
||||
/** @var \Swift_Message $message */
|
||||
$message = $evt->getMessage();
|
||||
$sendAllTo = Email::config()->send_all_emails_to;
|
||||
$ccAllTo = Email::config()->cc_all_emails_to;
|
||||
$bccAllTo = Email::config()->bcc_all_emails_to;
|
||||
$sendAllFrom = Email::config()->send_all_emails_from;
|
||||
|
||||
$sendAllTo = Email::getSendAllEmailsTo();
|
||||
if (!empty($sendAllTo)) {
|
||||
$this->setTo($message, $sendAllTo);
|
||||
}
|
||||
|
||||
$ccAllTo = Email::getCCAllEmailsTo();
|
||||
if (!empty($ccAllTo)) {
|
||||
if (!is_array($ccAllTo)) {
|
||||
$ccAllTo = array($ccAllTo => null);
|
||||
}
|
||||
foreach ($ccAllTo as $address => $name) {
|
||||
$message->addCc($address, $name);
|
||||
}
|
||||
}
|
||||
|
||||
$bccAllTo = Email::getBCCAllEmailsTo();
|
||||
if (!empty($bccAllTo)) {
|
||||
if (!is_array($bccAllTo)) {
|
||||
$bccAllTo = array($bccAllTo => null);
|
||||
}
|
||||
foreach ($bccAllTo as $address => $name) {
|
||||
$message->addBcc($address, $name);
|
||||
}
|
||||
}
|
||||
|
||||
$sendAllFrom = Email::getSendAllEmailsFrom();
|
||||
if (!empty($sendAllFrom)) {
|
||||
$this->setFrom($message, $sendAllFrom);
|
||||
}
|
||||
@ -48,7 +41,7 @@ class SwiftPlugin implements \Swift_Events_SendListener
|
||||
|
||||
/**
|
||||
* @param \Swift_Mime_Message $message
|
||||
* @param string $to
|
||||
* @param array|string $to
|
||||
*/
|
||||
protected function setTo($message, $to)
|
||||
{
|
||||
@ -70,7 +63,7 @@ class SwiftPlugin implements \Swift_Events_SendListener
|
||||
|
||||
/**
|
||||
* @param \Swift_Mime_Message $message
|
||||
* @param string $from
|
||||
* @param array|string $from
|
||||
*/
|
||||
protected function setFrom($message, $from)
|
||||
{
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace SilverStripe\Control;
|
||||
|
||||
use SilverStripe\Core\Flushable;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
|
||||
/**
|
||||
@ -11,37 +10,17 @@ use SilverStripe\Core\ClassInfo;
|
||||
*/
|
||||
class FlushRequestFilter implements RequestFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @param HTTPRequest $request
|
||||
* @param Session $session
|
||||
* @param DataModel $model
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function preRequest(HTTPRequest $request, Session $session, DataModel $model)
|
||||
public function preRequest(HTTPRequest $request)
|
||||
{
|
||||
if (array_key_exists('flush', $request->getVars())) {
|
||||
foreach (ClassInfo::implementorsOf(Flushable::class) as $class) {
|
||||
$class::flush();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @param HTTPRequest $request
|
||||
* @param HTTPResponse $response
|
||||
* @param DataModel $model
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function postRequest(HTTPRequest $request, HTTPResponse $response, DataModel $model)
|
||||
public function postRequest(HTTPRequest $request, HTTPResponse $response)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ class HTTP
|
||||
*/
|
||||
public static function absoluteURLs($html)
|
||||
{
|
||||
$html = str_replace('$CurrentPageURL', $_SERVER['REQUEST_URI'], $html);
|
||||
$html = str_replace('$CurrentPageURL', Controller::curr()->getRequest()->getURL(), $html);
|
||||
return HTTP::urlRewriter($html, function ($url) {
|
||||
//no need to rewrite, if uri has a protocol (determined here by existence of reserved URI character ":")
|
||||
if (preg_match('/^\w+:/', $url)) {
|
||||
@ -163,14 +163,17 @@ class HTTP
|
||||
*
|
||||
* @param string $varname
|
||||
* @param string $varvalue
|
||||
* @param string $currentURL Relative or absolute URL.
|
||||
* @param string|null $currentURL Relative or absolute URL, or HTTPRequest to get url from
|
||||
* @param string $separator Separator for http_build_query().
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function setGetVar($varname, $varvalue, $currentURL = null, $separator = '&')
|
||||
{
|
||||
$uri = $currentURL ? $currentURL : Director::makeRelative($_SERVER['REQUEST_URI']);
|
||||
if (!isset($currentURL)) {
|
||||
$request = Controller::curr()->getRequest();
|
||||
$currentURL = $request->getURL(true);
|
||||
}
|
||||
$uri = $currentURL;
|
||||
|
||||
$isRelative = false;
|
||||
// We need absolute URLs for parse_url()
|
||||
|
127
src/Control/HTTPApplication.php
Normal file
127
src/Control/HTTPApplication.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Control;
|
||||
|
||||
use SilverStripe\Core\Application;
|
||||
use SilverStripe\Control\HTTPMiddleware;
|
||||
use SilverStripe\Core\Kernel;
|
||||
|
||||
/**
|
||||
* Invokes the HTTP application within an ErrorControlChain
|
||||
*/
|
||||
class HTTPApplication implements Application
|
||||
{
|
||||
/**
|
||||
* @var HTTPMiddleware[]
|
||||
*/
|
||||
protected $middlewares = [];
|
||||
|
||||
/**
|
||||
* @var Kernel
|
||||
*/
|
||||
protected $kernel;
|
||||
|
||||
public function __construct(Kernel $kernel)
|
||||
{
|
||||
$this->kernel = $kernel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HTTPMiddleware[]
|
||||
*/
|
||||
public function getMiddlewares()
|
||||
{
|
||||
return $this->middlewares;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HTTPMiddleware[] $middlewares
|
||||
* @return $this
|
||||
*/
|
||||
public function setMiddlewares($middlewares)
|
||||
{
|
||||
$this->middlewares = $middlewares;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HTTPMiddleware $middleware
|
||||
* @return $this
|
||||
*/
|
||||
public function addMiddleware(HTTPMiddleware $middleware)
|
||||
{
|
||||
$this->middlewares[] = $middleware;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call middleware
|
||||
*
|
||||
* @param HTTPRequest $request
|
||||
* @param callable $last Last config to call
|
||||
* @return HTTPResponse
|
||||
*/
|
||||
protected function callMiddleware(HTTPRequest $request, $last)
|
||||
{
|
||||
// Reverse middlewares
|
||||
$next = $last;
|
||||
/** @var HTTPMiddleware $middleware */
|
||||
foreach (array_reverse($this->getMiddlewares()) as $middleware) {
|
||||
$next = function ($request) use ($middleware, $next) {
|
||||
return $middleware->process($request, $next);
|
||||
};
|
||||
}
|
||||
return call_user_func($next, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the kernel for this application
|
||||
*
|
||||
* @return Kernel
|
||||
*/
|
||||
public function getKernel()
|
||||
{
|
||||
return $this->kernel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the given HTTP request
|
||||
*
|
||||
* @param HTTPRequest $request
|
||||
* @return HTTPResponse
|
||||
*/
|
||||
public function handle(HTTPRequest $request)
|
||||
{
|
||||
$flush = $request->getVar('flush') || strpos($request->getURL(), 'dev/build') === 0;
|
||||
|
||||
// Ensure boot is invoked
|
||||
return $this->execute($request, function (HTTPRequest $request) {
|
||||
// Start session and execute
|
||||
$request->getSession()->init();
|
||||
return Director::direct($request);
|
||||
}, $flush);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely boot the application and execute the given main action
|
||||
*
|
||||
* @param HTTPRequest $request
|
||||
* @param callable $callback
|
||||
* @param bool $flush
|
||||
* @return HTTPResponse
|
||||
*/
|
||||
public function execute(HTTPRequest $request, callable $callback, $flush = false)
|
||||
{
|
||||
try {
|
||||
return $this->callMiddleware($request, function ($request) use ($callback, $flush) {
|
||||
// Pre-request boot
|
||||
$this->getKernel()->boot($flush);
|
||||
return call_user_func($callback, $request);
|
||||
});
|
||||
} catch (HTTPResponse_Exception $ex) {
|
||||
return $ex->getResponse();
|
||||
} finally {
|
||||
$this->getKernel()->shutdown();
|
||||
}
|
||||
}
|
||||
}
|
19
src/Control/HTTPMiddleware.php
Normal file
19
src/Control/HTTPMiddleware.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Control;
|
||||
|
||||
/**
|
||||
* HTTP Request middleware
|
||||
* Based on https://github.com/php-fig/fig-standards/blob/master/proposed/http-middleware/middleware.md#21-psrhttpservermiddlewaremiddlewareinterface
|
||||
*/
|
||||
interface HTTPMiddleware
|
||||
{
|
||||
/**
|
||||
* Generate response for the given request
|
||||
*
|
||||
* @param HTTPRequest $request
|
||||
* @param callable $delegate
|
||||
* @return HTTPResponse
|
||||
*/
|
||||
public function process(HTTPRequest $request, callable $delegate);
|
||||
}
|
@ -2,9 +2,10 @@
|
||||
|
||||
namespace SilverStripe\Control;
|
||||
|
||||
use ArrayAccess;
|
||||
use BadMethodCallException;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\ORM\ArrayLib;
|
||||
use ArrayAccess;
|
||||
|
||||
/**
|
||||
* Represents a HTTP-request, including a URL that is tokenised for parsing, and a request method
|
||||
@ -22,7 +23,6 @@ use ArrayAccess;
|
||||
*/
|
||||
class HTTPRequest implements ArrayAccess
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@ -125,6 +125,11 @@ class HTTPRequest implements ArrayAccess
|
||||
*/
|
||||
protected $unshiftedButParsedParts = 0;
|
||||
|
||||
/**
|
||||
* @var Session
|
||||
*/
|
||||
protected $session;
|
||||
|
||||
/**
|
||||
* Construct a HTTPRequest from a URL relative to the site root.
|
||||
*
|
||||
@ -138,7 +143,6 @@ class HTTPRequest implements ArrayAccess
|
||||
{
|
||||
$this->httpMethod = strtoupper(self::detect_method($httpMethod, $postVars));
|
||||
$this->setUrl($url);
|
||||
|
||||
$this->getVars = (array) $getVars;
|
||||
$this->postVars = (array) $postVars;
|
||||
$this->body = $body;
|
||||
@ -435,21 +439,15 @@ class HTTPRequest implements ArrayAccess
|
||||
return $this->requestVar($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @param string $offset
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
$this->getVars[$offset] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @param mixed $offset
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->getVars[$offset]);
|
||||
unset($this->postVars[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -866,4 +864,25 @@ class HTTPRequest implements ArrayAccess
|
||||
return $origMethod;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Session
|
||||
*/
|
||||
public function getSession()
|
||||
{
|
||||
if (empty($this->session)) {
|
||||
throw new BadMethodCallException("No session available for this HTTPRequest");
|
||||
}
|
||||
return $this->session;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Session $session
|
||||
* @return $this
|
||||
*/
|
||||
public function setSession(Session $session)
|
||||
{
|
||||
$this->session = $session;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
148
src/Control/HTTPRequestBuilder.php
Normal file
148
src/Control/HTTPRequestBuilder.php
Normal file
@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Control;
|
||||
|
||||
use SilverStripe\Core\Environment;
|
||||
|
||||
class HTTPRequestBuilder
|
||||
{
|
||||
/**
|
||||
* Create HTTPRequest instance from the current environment variables.
|
||||
* May throw errors if request is invalid.
|
||||
*
|
||||
* @throws HTTPResponse_Exception
|
||||
* @return HTTPRequest
|
||||
*/
|
||||
public static function createFromEnvironment()
|
||||
{
|
||||
// Clean and update live global variables
|
||||
$variables = static::cleanEnvironment(Environment::getVariables());
|
||||
Environment::setVariables($variables); // Currently necessary for SSViewer, etc to work
|
||||
|
||||
// Health-check prior to creating environment
|
||||
return static::createFromVariables($variables, @file_get_contents('php://input'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build HTTPRequest from given variables
|
||||
*
|
||||
* @param array $variables
|
||||
* @param string $input Request body
|
||||
* @return HTTPRequest
|
||||
*/
|
||||
public static function createFromVariables(array $variables, $input)
|
||||
{
|
||||
// Strip `url` out of querystring
|
||||
$url = $variables['_GET']['url'];
|
||||
unset($variables['_GET']['url']);
|
||||
|
||||
// Build request
|
||||
$request = new HTTPRequest(
|
||||
$variables['_SERVER']['REQUEST_METHOD'],
|
||||
$url,
|
||||
$variables['_GET'],
|
||||
$variables['_POST'],
|
||||
$input
|
||||
);
|
||||
|
||||
// Add headers
|
||||
$headers = static::extractRequestHeaders($variables['_SERVER']);
|
||||
foreach ($headers as $header => $value) {
|
||||
$request->addHeader($header, $value);
|
||||
}
|
||||
|
||||
// Initiate an empty session - doesn't initialize an actual PHP session (see HTTPApplication)
|
||||
$session = new Session(isset($variables['_SESSION']) ? $variables['_SESSION'] : null);
|
||||
$request->setSession($session);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a $_SERVER data array and extracts HTTP request headers.
|
||||
*
|
||||
* @param array $server
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function extractRequestHeaders(array $server)
|
||||
{
|
||||
$headers = array();
|
||||
foreach ($server as $key => $value) {
|
||||
if (substr($key, 0, 5) == 'HTTP_') {
|
||||
$key = substr($key, 5);
|
||||
$key = strtolower(str_replace('_', ' ', $key));
|
||||
$key = str_replace(' ', '-', ucwords($key));
|
||||
$headers[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($server['CONTENT_TYPE'])) {
|
||||
$headers['Content-Type'] = $server['CONTENT_TYPE'];
|
||||
}
|
||||
if (isset($server['CONTENT_LENGTH'])) {
|
||||
$headers['Content-Length'] = $server['CONTENT_LENGTH'];
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up HTTP global vars for $_GET / $_REQUEST prior to bootstrapping
|
||||
* Will also populate the $_GET['url'] var safely
|
||||
*
|
||||
* @param array $variables
|
||||
* @return array Cleaned variables
|
||||
*/
|
||||
public static function cleanEnvironment(array $variables)
|
||||
{
|
||||
// IIS will sometimes generate this.
|
||||
if (!empty($variables['_SERVER']['HTTP_X_ORIGINAL_URL'])) {
|
||||
$variables['_SERVER']['REQUEST_URI'] = $variables['_SERVER']['HTTP_X_ORIGINAL_URL'];
|
||||
}
|
||||
|
||||
// Override REQUEST_METHOD
|
||||
if (isset($variables['_SERVER']['X-HTTP-Method-Override'])) {
|
||||
$variables['_SERVER']['REQUEST_METHOD'] = $variables['_SERVER']['X-HTTP-Method-Override'];
|
||||
}
|
||||
|
||||
// Prevent injection of url= querystring argument by prioritising any leading url argument
|
||||
if (isset($variables['_SERVER']['QUERY_STRING']) &&
|
||||
preg_match('/^(?<url>url=[^&?]*)(?<query>.*[&?]url=.*)$/', $variables['_SERVER']['QUERY_STRING'], $results)
|
||||
) {
|
||||
$queryString = $results['query'].'&'.$results['url'];
|
||||
parse_str($queryString, $variables['_GET']);
|
||||
}
|
||||
|
||||
// Decode url from REQUEST_URI if not passed via $_GET['url']
|
||||
if (!isset($variables['_GET']['url'])) {
|
||||
$url = $variables['_SERVER']['REQUEST_URI'];
|
||||
|
||||
// Querystring args need to be explicitly parsed
|
||||
if (strpos($url, '?') !== false) {
|
||||
list($url, $queryString) = explode('?', $url, 2);
|
||||
parse_str($queryString);
|
||||
}
|
||||
|
||||
// Ensure $_GET['url'] is set
|
||||
$variables['_GET']['url'] = urldecode($url);
|
||||
}
|
||||
|
||||
// Remove base folders from the URL if webroot is hosted in a subfolder
|
||||
if (substr(strtolower($variables['_GET']['url']), 0, strlen(BASE_URL)) === strtolower(BASE_URL)) {
|
||||
$variables['_GET']['url'] = substr($variables['_GET']['url'], strlen(BASE_URL));
|
||||
}
|
||||
|
||||
// Merge $_FILES into $_POST
|
||||
$variables['_POST'] = array_merge((array)$variables['_POST'], (array)$variables['_FILES']);
|
||||
|
||||
// Merge $_POST, $_GET, and $_COOKIE into $_REQUEST
|
||||
$variables['_REQUEST'] = array_merge(
|
||||
(array)$variables['_GET'],
|
||||
(array)$variables['_POST'],
|
||||
(array)$variables['_COOKIE']
|
||||
);
|
||||
|
||||
return $variables;
|
||||
}
|
||||
}
|
@ -2,8 +2,6 @@
|
||||
|
||||
namespace SilverStripe\Control;
|
||||
|
||||
use SilverStripe\ORM\DataModel;
|
||||
|
||||
/**
|
||||
* A request filter is an object that's executed before and after a
|
||||
* request occurs. By returning 'false' from the preRequest method,
|
||||
@ -14,24 +12,20 @@ use SilverStripe\ORM\DataModel;
|
||||
*/
|
||||
interface RequestFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* Filter executed before a request processes
|
||||
*
|
||||
* @param HTTPRequest $request Request container object
|
||||
* @param Session $session Request session
|
||||
* @param DataModel $model Current DataModel
|
||||
* @return boolean Whether to continue processing other filters. Null or true will continue processing (optional)
|
||||
*/
|
||||
public function preRequest(HTTPRequest $request, Session $session, DataModel $model);
|
||||
public function preRequest(HTTPRequest $request);
|
||||
|
||||
/**
|
||||
* Filter executed AFTER a request
|
||||
*
|
||||
* @param HTTPRequest $request Request container object
|
||||
* @param HTTPResponse $response Response output object
|
||||
* @param DataModel $model Current DataModel
|
||||
* @return boolean Whether to continue processing other filters. Null or true will continue processing (optional)
|
||||
* @param HTTPResponse $response
|
||||
* @return bool Whether to continue processing other filters. Null or true will continue processing (optional)
|
||||
*/
|
||||
public function postRequest(HTTPRequest $request, HTTPResponse $response, DataModel $model);
|
||||
public function postRequest(HTTPRequest $request, HTTPResponse $response);
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ use InvalidArgumentException;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Security\PermissionFailureException;
|
||||
use SilverStripe\Security\Permission;
|
||||
@ -123,22 +122,9 @@ class RequestHandler extends ViewableData
|
||||
|
||||
$this->setRequest(new NullHTTPRequest());
|
||||
|
||||
// This will prevent bugs if setDataModel() isn't called.
|
||||
$this->model = DataModel::inst();
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DataModel for this request.
|
||||
*
|
||||
* @param DataModel $model
|
||||
*/
|
||||
public function setDataModel($model)
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles URL requests.
|
||||
*
|
||||
@ -156,10 +142,9 @@ class RequestHandler extends ViewableData
|
||||
* customise the controller.
|
||||
*
|
||||
* @param HTTPRequest $request The object that is reponsible for distributing URL parsing
|
||||
* @param DataModel $model
|
||||
* @return HTTPResponse|RequestHandler|string|array
|
||||
*/
|
||||
public function handleRequest(HTTPRequest $request, DataModel $model)
|
||||
public function handleRequest(HTTPRequest $request)
|
||||
{
|
||||
// $handlerClass is used to step up the class hierarchy to implement url_handlers inheritance
|
||||
if ($this->brokenOnConstruct) {
|
||||
@ -170,7 +155,6 @@ class RequestHandler extends ViewableData
|
||||
}
|
||||
|
||||
$this->setRequest($request);
|
||||
$this->setDataModel($model);
|
||||
|
||||
$match = $this->findAction($request);
|
||||
|
||||
@ -237,7 +221,7 @@ class RequestHandler extends ViewableData
|
||||
if ($result instanceof HasRequestHandler) {
|
||||
$result = $result->getRequestHandler();
|
||||
}
|
||||
$returnValue = $result->handleRequest($request, $model);
|
||||
$returnValue = $result->handleRequest($request);
|
||||
|
||||
// Array results can be used to handle
|
||||
if (is_array($returnValue)) {
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace SilverStripe\Control;
|
||||
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
|
||||
/**
|
||||
* Represents a request processer that delegates pre and post request handling to nested request filters
|
||||
@ -34,10 +33,10 @@ class RequestProcessor implements RequestFilter
|
||||
$this->filters = $filters;
|
||||
}
|
||||
|
||||
public function preRequest(HTTPRequest $request, Session $session, DataModel $model)
|
||||
public function preRequest(HTTPRequest $request)
|
||||
{
|
||||
foreach ($this->filters as $filter) {
|
||||
$res = $filter->preRequest($request, $session, $model);
|
||||
$res = $filter->preRequest($request);
|
||||
if ($res === false) {
|
||||
return false;
|
||||
}
|
||||
@ -45,10 +44,10 @@ class RequestProcessor implements RequestFilter
|
||||
return null;
|
||||
}
|
||||
|
||||
public function postRequest(HTTPRequest $request, HTTPResponse $response, DataModel $model)
|
||||
public function postRequest(HTTPRequest $request, HTTPResponse $response)
|
||||
{
|
||||
foreach ($this->filters as $filter) {
|
||||
$res = $filter->postRequest($request, $response, $model);
|
||||
$res = $filter->postRequest($request, $response);
|
||||
if ($res === false) {
|
||||
return false;
|
||||
}
|
||||
|
@ -2,10 +2,8 @@
|
||||
|
||||
namespace SilverStripe\Control;
|
||||
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use BadMethodCallException;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
|
||||
/**
|
||||
* Handles all manipulation of the session.
|
||||
@ -89,7 +87,7 @@ use SilverStripe\Dev\Deprecation;
|
||||
*/
|
||||
class Session
|
||||
{
|
||||
use Injectable;
|
||||
use Configurable;
|
||||
|
||||
/**
|
||||
* Set session timeout in seconds.
|
||||
@ -130,12 +128,23 @@ class Session
|
||||
private static $cookie_secure = false;
|
||||
|
||||
/**
|
||||
* Session data
|
||||
* Session data.
|
||||
* Will be null if session has not been started
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
protected $data = array();
|
||||
protected $data = null;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $changedData = array();
|
||||
|
||||
/**
|
||||
* Get user agent for this request
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function userAgent()
|
||||
{
|
||||
if (isset($_SERVER['HTTP_USER_AGENT'])) {
|
||||
@ -148,123 +157,75 @@ class Session
|
||||
/**
|
||||
* Start PHP session, then create a new Session object with the given start data.
|
||||
*
|
||||
* @param $data array|Session Can be an array of data (such as $_SESSION) or another Session object to clone.
|
||||
* @param array|null|Session $data Can be an array of data (such as $_SESSION) or another Session object to clone.
|
||||
* If null, this session is treated as unstarted.
|
||||
*/
|
||||
public function __construct($data)
|
||||
{
|
||||
if ($data instanceof Session) {
|
||||
$data = $data->inst_getAll();
|
||||
$data = $data->getAll();
|
||||
}
|
||||
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init this session instance before usage
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
if (!$this->isStarted()) {
|
||||
$this->start();
|
||||
}
|
||||
|
||||
if (isset($this->data['HTTP_USER_AGENT'])) {
|
||||
if ($this->data['HTTP_USER_AGENT'] != $this->userAgent()) {
|
||||
// Funny business detected!
|
||||
$this->inst_clearAll();
|
||||
$this->inst_destroy();
|
||||
$this->inst_start();
|
||||
if (isset($this->data['HTTP_USER_AGENT'])) {
|
||||
if ($this->data['HTTP_USER_AGENT'] !== $this->userAgent()) {
|
||||
$this->clearAll();
|
||||
$this->destroy();
|
||||
$this->start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a value to a specific key in the session array
|
||||
* Destroy existing session and restart
|
||||
*/
|
||||
public function restart()
|
||||
{
|
||||
$this->destroy();
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this session has started
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $val
|
||||
* @return bool
|
||||
*/
|
||||
public static function add_to_array($name, $val)
|
||||
public function isStarted()
|
||||
{
|
||||
return self::current_session()->inst_addToArray($name, $val);
|
||||
return isset($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a key/value pair in the session
|
||||
* Begin session
|
||||
*
|
||||
* @param string $name Key
|
||||
* @param string|array $val Value
|
||||
* @param string $sid
|
||||
*/
|
||||
public static function set($name, $val)
|
||||
public function start($sid = null)
|
||||
{
|
||||
return self::current_session()->inst_set($name, $val);
|
||||
if ($this->isStarted()) {
|
||||
throw new BadMethodCallException("Session has already started");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a specific value by session key
|
||||
*
|
||||
* @param string $name Key to lookup
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get($name)
|
||||
{
|
||||
return self::current_session()->inst_get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the values in session
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_all()
|
||||
{
|
||||
return self::current_session()->inst_getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear a given session key, value pair.
|
||||
*
|
||||
* @param string $name Key to lookup
|
||||
*/
|
||||
public static function clear($name)
|
||||
{
|
||||
return self::current_session()->inst_clear($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the values
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clear_all()
|
||||
{
|
||||
self::current_session()->inst_clearAll();
|
||||
self::$default_session = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all the values in our session to $_SESSION
|
||||
*/
|
||||
public static function save()
|
||||
{
|
||||
return self::current_session()->inst_save();
|
||||
}
|
||||
|
||||
protected static $default_session = null;
|
||||
|
||||
protected static function current_session()
|
||||
{
|
||||
if (Controller::has_curr()) {
|
||||
return Controller::curr()->getSession();
|
||||
} else {
|
||||
if (!self::$default_session) {
|
||||
self::$default_session = Injector::inst()->create('SilverStripe\\Control\\Session', isset($_SESSION) ? $_SESSION : array());
|
||||
}
|
||||
|
||||
return self::$default_session;
|
||||
}
|
||||
}
|
||||
|
||||
public function inst_start($sid = null)
|
||||
{
|
||||
$path = Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_path');
|
||||
$path = $this->config()->get('cookie_path');
|
||||
if (!$path) {
|
||||
$path = Director::baseURL();
|
||||
}
|
||||
$domain = Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_domain');
|
||||
$secure = Director::is_https() && Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_secure');
|
||||
$session_path = Config::inst()->get('SilverStripe\\Control\\Session', 'session_store_path');
|
||||
$timeout = Config::inst()->get('SilverStripe\\Control\\Session', 'timeout');
|
||||
$domain = $this->config()->get('cookie_domain');
|
||||
$secure = Director::is_https() && $this->config()->get('cookie_secure');
|
||||
$session_path = $this->config()->get('session_store_path');
|
||||
$timeout = $this->config()->get('timeout');
|
||||
|
||||
// Director::baseURL can return absolute domain names - this extracts the relevant parts
|
||||
// for the session otherwise we can get broken session cookies
|
||||
@ -300,6 +261,8 @@ class Session
|
||||
session_start();
|
||||
|
||||
$this->data = isset($_SESSION) ? $_SESSION : array();
|
||||
} else {
|
||||
$this->data = [];
|
||||
}
|
||||
|
||||
// Modify the timeout behaviour so it's the *inactive* time before the session expires.
|
||||
@ -310,28 +273,41 @@ class Session
|
||||
}
|
||||
}
|
||||
|
||||
public function inst_destroy($removeCookie = true)
|
||||
/**
|
||||
* Destroy this session
|
||||
*
|
||||
* @param bool $removeCookie
|
||||
*/
|
||||
public function destroy($removeCookie = true)
|
||||
{
|
||||
if (session_id()) {
|
||||
if ($removeCookie) {
|
||||
$path = Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_path') ?: Director::baseURL();
|
||||
$domain = Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_domain');
|
||||
$secure = Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_secure');
|
||||
|
||||
$path = $this->config()->get('cookie_path') ?: Director::baseURL();
|
||||
$domain = $this->config()->get('cookie_domain');
|
||||
$secure = $this->config()->get('cookie_secure');
|
||||
Cookie::force_expiry(session_name(), $path, $domain, $secure, true);
|
||||
}
|
||||
|
||||
session_destroy();
|
||||
|
||||
}
|
||||
// Clean up the superglobal - session_destroy does not do it.
|
||||
// http://nz1.php.net/manual/en/function.session-destroy.php
|
||||
unset($_SESSION);
|
||||
$this->data = array();
|
||||
}
|
||||
$this->data = null;
|
||||
}
|
||||
|
||||
public function inst_set($name, $val)
|
||||
/**
|
||||
* Set session value
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $val
|
||||
* @return $this
|
||||
*/
|
||||
public function set($name, $val)
|
||||
{
|
||||
if (!$this->isStarted()) {
|
||||
throw new BadMethodCallException("Session cannot be modified until it's started");
|
||||
}
|
||||
|
||||
// Quicker execution path for "."-free names
|
||||
if (strpos($name, '.') === false) {
|
||||
$this->data[$name] = $val;
|
||||
@ -360,10 +336,21 @@ class Session
|
||||
$diffVar = $val;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function inst_addToArray($name, $val)
|
||||
/**
|
||||
* Merge value with array
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $val
|
||||
*/
|
||||
public function addToArray($name, $val)
|
||||
{
|
||||
if (!$this->isStarted()) {
|
||||
throw new BadMethodCallException("Session cannot be modified until it's started");
|
||||
}
|
||||
|
||||
$names = explode('.', $name);
|
||||
|
||||
// We still want to do this even if we have strict path checking for legacy code
|
||||
@ -379,8 +366,18 @@ class Session
|
||||
$diffVar[sizeof($var)-1] = $val;
|
||||
}
|
||||
|
||||
public function inst_get($name)
|
||||
/**
|
||||
* Get session value
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
if (!$this->isStarted()) {
|
||||
throw new BadMethodCallException("Session cannot be accessed until it's started");
|
||||
}
|
||||
|
||||
// Quicker execution path for "."-free names
|
||||
if (strpos($name, '.') === false) {
|
||||
if (isset($this->data[$name])) {
|
||||
@ -407,8 +404,18 @@ class Session
|
||||
}
|
||||
}
|
||||
|
||||
public function inst_clear($name)
|
||||
/**
|
||||
* Clear session value
|
||||
*
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
public function clear($name)
|
||||
{
|
||||
if (!$this->isStarted()) {
|
||||
throw new BadMethodCallException("Session cannot be modified until it's started");
|
||||
}
|
||||
|
||||
$names = explode('.', $name);
|
||||
|
||||
// We still want to do this even if we have strict path checking for legacy code
|
||||
@ -418,7 +425,7 @@ class Session
|
||||
foreach ($names as $n) {
|
||||
// don't clear a record that doesn't exist
|
||||
if (!isset($var[$n])) {
|
||||
return;
|
||||
return $this;
|
||||
}
|
||||
$var = &$var[$n];
|
||||
}
|
||||
@ -432,38 +439,54 @@ class Session
|
||||
$var = null;
|
||||
$diffVar = null;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function inst_clearAll()
|
||||
/**
|
||||
* Clear all values
|
||||
*/
|
||||
public function clearAll()
|
||||
{
|
||||
if (!$this->isStarted()) {
|
||||
throw new BadMethodCallException("Session cannot be modified until it's started");
|
||||
}
|
||||
|
||||
if ($this->data && is_array($this->data)) {
|
||||
foreach (array_keys($this->data) as $key) {
|
||||
$this->inst_clear($key);
|
||||
$this->clear($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function inst_getAll()
|
||||
/**
|
||||
* Get all values
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function inst_finalize()
|
||||
/**
|
||||
* Set user agent key
|
||||
*/
|
||||
public function finalize()
|
||||
{
|
||||
$this->inst_set('HTTP_USER_AGENT', $this->userAgent());
|
||||
$this->set('HTTP_USER_AGENT', $this->userAgent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Save data to session
|
||||
* Only save the changes, so that anyone manipulating $_SESSION directly doesn't get burned.
|
||||
*/
|
||||
public function inst_save()
|
||||
public function save()
|
||||
{
|
||||
if ($this->changedData) {
|
||||
$this->inst_finalize();
|
||||
$this->finalize();
|
||||
|
||||
if (!isset($_SESSION)) {
|
||||
$this->inst_start();
|
||||
if (!$this->isStarted()) {
|
||||
$this->start();
|
||||
}
|
||||
|
||||
$this->recursivelyApply($this->changedData, $_SESSION);
|
||||
@ -493,55 +516,11 @@ class Session
|
||||
|
||||
/**
|
||||
* Return the changed data, for debugging purposes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function inst_changedData()
|
||||
public function changedData()
|
||||
{
|
||||
return $this->changedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the appropriate form message in session, with type. This will be shown once,
|
||||
* for the form specified.
|
||||
*
|
||||
* @param string $formname the form name you wish to use ( usually $form->FormName() )
|
||||
* @param string $message the message you wish to add to it
|
||||
* @param string $type the type of message
|
||||
*/
|
||||
public static function setFormMessage($formname, $message, $type)
|
||||
{
|
||||
Session::set("FormInfo.$formname.formError.message", $message);
|
||||
Session::set("FormInfo.$formname.formError.type", $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is there a session ID in the request?
|
||||
* @return bool
|
||||
*/
|
||||
public static function request_contains_session_id()
|
||||
{
|
||||
$secure = Director::is_https() && Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_secure');
|
||||
$name = $secure ? 'SECSESSID' : session_name();
|
||||
return isset($_COOKIE[$name]) || isset($_REQUEST[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize session.
|
||||
*
|
||||
* @param string $sid Start the session with a specific ID
|
||||
*/
|
||||
public static function start($sid = null)
|
||||
{
|
||||
self::current_session()->inst_start($sid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the active session.
|
||||
*
|
||||
* @param bool $removeCookie If set to TRUE, removes the user's cookie, FALSE does not remove
|
||||
*/
|
||||
public static function destroy($removeCookie = true)
|
||||
{
|
||||
self::current_session()->inst_destroy($removeCookie);
|
||||
}
|
||||
}
|
||||
|
29
src/Core/Application.php
Normal file
29
src/Core/Application.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core;
|
||||
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
|
||||
/**
|
||||
* Identifies a class as a root silverstripe application
|
||||
*/
|
||||
interface Application
|
||||
{
|
||||
/**
|
||||
* Get the kernel for this application
|
||||
*
|
||||
* @return Kernel
|
||||
*/
|
||||
public function getKernel();
|
||||
|
||||
/**
|
||||
* Safely boot the application and execute the given main action
|
||||
*
|
||||
* @param HTTPRequest $request
|
||||
* @param callable $callback
|
||||
* @param bool $flush
|
||||
* @return HTTPResponse
|
||||
*/
|
||||
public function execute(HTTPRequest $request, callable $callback, $flush = false);
|
||||
}
|
@ -87,7 +87,7 @@ abstract class Config
|
||||
{
|
||||
// Unnest unless we would be left at 0 manifests
|
||||
$loader = ConfigLoader::inst();
|
||||
if ($loader->countManifests() < 2) {
|
||||
if ($loader->countManifests() <= 1) {
|
||||
user_error(
|
||||
"Unable to unnest root Config, please make sure you don't have mis-matched nest/unnest",
|
||||
E_USER_WARNING
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace SilverStripe\Core\Config;
|
||||
|
||||
use BadMethodCallException;
|
||||
use SilverStripe\Config\Collections\ConfigCollectionInterface;
|
||||
|
||||
/**
|
||||
@ -25,7 +26,7 @@ class ConfigLoader
|
||||
*/
|
||||
public static function inst()
|
||||
{
|
||||
return self::$instance ? self::$instance : self::$instance = new self();
|
||||
return self::$instance ? self::$instance : self::$instance = new static();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -36,6 +37,14 @@ class ConfigLoader
|
||||
*/
|
||||
public function getManifest()
|
||||
{
|
||||
if ($this !== self::$instance) {
|
||||
throw new BadMethodCallException(
|
||||
"Non-current config manifest cannot be accessed. Please call ->activate() first"
|
||||
);
|
||||
}
|
||||
if (empty($this->manifests)) {
|
||||
throw new BadMethodCallException("No config manifests available");
|
||||
}
|
||||
return $this->manifests[count($this->manifests) - 1];
|
||||
}
|
||||
|
||||
@ -78,14 +87,31 @@ class ConfigLoader
|
||||
}
|
||||
|
||||
/**
|
||||
* Nest the current manifest
|
||||
* Nest the config loader and activates it
|
||||
*
|
||||
* @return ConfigCollectionInterface
|
||||
* @return static
|
||||
*/
|
||||
public function nest()
|
||||
{
|
||||
$manifest = $this->getManifest()->nest();
|
||||
$this->pushManifest($manifest);
|
||||
return $manifest;
|
||||
// Nest config
|
||||
$manifest = clone $this->getManifest();
|
||||
|
||||
// Create new blank loader with new stack (top level nesting)
|
||||
$newLoader = new static;
|
||||
$newLoader->pushManifest($manifest);
|
||||
|
||||
// Activate new loader
|
||||
return $newLoader->activate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this instance as the current instance
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function activate()
|
||||
{
|
||||
static::$instance = $this;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -2,22 +2,18 @@
|
||||
|
||||
namespace SilverStripe\Core\Config;
|
||||
|
||||
use Monolog\Handler\ErrorLogHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
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\Cache\CacheFactory;
|
||||
use SilverStripe\Core\Config\Middleware\ExtensionMiddleware;
|
||||
use SilverStripe\Core\Config\Middleware\InheritanceMiddleware;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Kernel;
|
||||
use SilverStripe\Core\Manifest\ClassLoader;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
/**
|
||||
@ -26,19 +22,18 @@ use Symfony\Component\Finder\Finder;
|
||||
class CoreConfigFactory
|
||||
{
|
||||
/**
|
||||
* @var static
|
||||
* @var CacheFactory
|
||||
*/
|
||||
protected static $inst = null;
|
||||
protected $cacheFactory = null;
|
||||
|
||||
/**
|
||||
* @return static
|
||||
* Create factory
|
||||
*
|
||||
* @param CacheFactory $cacheFactory
|
||||
*/
|
||||
public static function inst()
|
||||
public function __construct(CacheFactory $cacheFactory = null)
|
||||
{
|
||||
if (!self::$inst) {
|
||||
self::$inst = new static();
|
||||
}
|
||||
return self::$inst;
|
||||
$this->cacheFactory = $cacheFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,20 +41,19 @@ class CoreConfigFactory
|
||||
* This will be an immutable cached config,
|
||||
* which conditionally generates a nested "core" config.
|
||||
*
|
||||
* @param bool $flush
|
||||
* @param CacheFactory $cacheFactory
|
||||
* @return CachedConfigCollection
|
||||
*/
|
||||
public function createRoot($flush, CacheFactory $cacheFactory)
|
||||
public function createRoot()
|
||||
{
|
||||
$instance = new CachedConfigCollection();
|
||||
|
||||
// Create config cache
|
||||
$cache = $cacheFactory->create(CacheInterface::class.'.configcache', [
|
||||
if ($this->cacheFactory) {
|
||||
$cache = $this->cacheFactory->create(CacheInterface::class . '.configcache', [
|
||||
'namespace' => 'configcache'
|
||||
]);
|
||||
$instance->setCache($cache);
|
||||
$instance->setFlush($flush);
|
||||
}
|
||||
|
||||
// Set collection creator
|
||||
$instance->setCollectionCreator(function () {
|
||||
@ -171,8 +165,11 @@ class CoreConfigFactory
|
||||
}
|
||||
)
|
||||
->addRule('environment', function ($env) {
|
||||
$current = Director::get_environment_type();
|
||||
return strtolower($current) === strtolower($env);
|
||||
// Note: The below relies on direct assignment of kernel to injector instance,
|
||||
// and will fail if failing back to config service locator
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
return strtolower($kernel->getEnvironment()) === strtolower($env);
|
||||
})
|
||||
->addRule('moduleexists', function ($module) {
|
||||
return ModuleLoader::inst()->getManifest()->moduleExists($module);
|
||||
|
@ -550,4 +550,47 @@ class Convert
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Turn a memory string, such as 512M into an actual number of bytes.
|
||||
*
|
||||
* @param string $memString A memory limit string, such as "64M"
|
||||
* @return float
|
||||
*/
|
||||
public static function memstring2bytes($memString)
|
||||
{
|
||||
switch (strtolower(substr($memString, -1))) {
|
||||
case "b":
|
||||
return round(substr($memString, 0, -1));
|
||||
case "k":
|
||||
return round(substr($memString, 0, -1) * 1024);
|
||||
case "m":
|
||||
return round(substr($memString, 0, -1) * 1024 * 1024);
|
||||
case "g":
|
||||
return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
|
||||
default:
|
||||
return round($memString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $bytes
|
||||
* @param int $decimal decimal precision
|
||||
* @return string
|
||||
*/
|
||||
public static function bytes2memstring($bytes, $decimal = 0)
|
||||
{
|
||||
$scales = ['B','K','M','G'];
|
||||
// Get scale
|
||||
$scale = (int)floor(log($bytes, 1024));
|
||||
if (!isset($scales[$scale])) {
|
||||
$scale = 2;
|
||||
}
|
||||
|
||||
// Size
|
||||
$num = round($bytes / pow(1024, $scale), $decimal);
|
||||
return $num . $scales[$scale];
|
||||
}
|
||||
}
|
||||
|
@ -1,324 +0,0 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\Core\Cache\ManifestCacheFactory;
|
||||
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\Control\Director;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Core\Manifest\ModuleManifest;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\Logging\ErrorHandler;
|
||||
|
||||
/**
|
||||
* This file is the Framework bootstrap. It will get your environment ready to call Director::direct().
|
||||
*
|
||||
* It takes care of:
|
||||
* - Checking of PHP memory limit
|
||||
* - Including all the files needed to get the manifest built
|
||||
* - Building and including the manifest
|
||||
*
|
||||
* @todo This file currently contains a lot of bits and pieces, and its various responsibilities should probably be
|
||||
* moved into different subsystems.
|
||||
* @todo A lot of this stuff is very order-dependent. This could be decoupled.
|
||||
*/
|
||||
|
||||
/**
|
||||
* All errors are reported, including E_STRICT by default *unless* the site is in
|
||||
* live mode, where reporting is limited to fatal errors and warnings (see later in this file)
|
||||
*/
|
||||
error_reporting(E_ALL | E_STRICT);
|
||||
|
||||
global $_increase_time_limit_max;
|
||||
$_increase_time_limit_max = -1;
|
||||
|
||||
/**
|
||||
* Ensure we have enough memory
|
||||
*/
|
||||
increase_memory_limit_to('64M');
|
||||
|
||||
/**
|
||||
* Ensure we don't run into xdebug's fairly conservative infinite recursion protection limit
|
||||
*/
|
||||
increase_xdebug_nesting_level_to(200);
|
||||
|
||||
/**
|
||||
* Set default encoding
|
||||
*/
|
||||
mb_http_output('UTF-8');
|
||||
mb_internal_encoding('UTF-8');
|
||||
mb_regex_encoding('UTF-8');
|
||||
|
||||
/**
|
||||
* Enable better garbage collection
|
||||
*/
|
||||
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' => SilverStripeServiceConfigurationLocator::class));
|
||||
Injector::set_inst($injector);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// MANIFEST
|
||||
|
||||
// Regenerate the manifest if ?flush is set, or if the database is being built.
|
||||
// The coupling is a hack, but it removes an annoying bug where new classes
|
||||
// referenced in _config.php files can be referenced during the build process.
|
||||
$requestURL = isset($_REQUEST['url']) ? trim($_REQUEST['url'], '/') : false;
|
||||
$flush = (isset($_GET['flush']) || $requestURL === trim(BASE_URL . '/dev/build', '/'));
|
||||
|
||||
// Manifest cache factory
|
||||
$manifestCacheFactory = new ManifestCacheFactory([
|
||||
'namespace' => 'manifestcache',
|
||||
'directory' => getTempFolder(),
|
||||
]);
|
||||
|
||||
// Build class manifest
|
||||
$manifest = new ClassManifest(BASE_PATH, false, $flush, $manifestCacheFactory);
|
||||
|
||||
// Register SilverStripe's class map autoload
|
||||
$loader = ClassLoader::inst();
|
||||
$loader->registerAutoloader();
|
||||
$loader->pushManifest($manifest);
|
||||
|
||||
// Init module manifest
|
||||
$moduleManifest = new ModuleManifest(BASE_PATH, false, $flush, $manifestCacheFactory);
|
||||
ModuleLoader::inst()->pushManifest($moduleManifest);
|
||||
|
||||
// Build config manifest
|
||||
$configManifest = CoreConfigFactory::inst()->createRoot($flush, $manifestCacheFactory);
|
||||
ConfigLoader::inst()->pushManifest($configManifest);
|
||||
|
||||
// After loading config, boot _config.php files
|
||||
ModuleLoader::inst()->getManifest()->activateConfig();
|
||||
|
||||
// Load template manifest
|
||||
SilverStripe\View\ThemeResourceLoader::inst()->addSet('$default', new SilverStripe\View\ThemeManifest(
|
||||
BASE_PATH,
|
||||
project(),
|
||||
false,
|
||||
$flush,
|
||||
$manifestCacheFactory
|
||||
));
|
||||
|
||||
// If in live mode, ensure deprecation, strict and notices are not reported
|
||||
if (Director::isLive()) {
|
||||
error_reporting(E_ALL & ~(E_DEPRECATED | E_STRICT | E_NOTICE));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// POST-MANIFEST COMMANDS
|
||||
|
||||
/**
|
||||
* Load error handlers
|
||||
*/
|
||||
$errorHandler = Injector::inst()->get(ErrorHandler::class);
|
||||
$errorHandler->start();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// HELPER FUNCTIONS
|
||||
|
||||
/**
|
||||
* Creates a class instance by the "singleton" design pattern.
|
||||
* It will always return the same instance for this class,
|
||||
* which can be used for performance reasons and as a simple
|
||||
* way to access instance methods which don't rely on instance
|
||||
* data (e.g. the custom SilverStripe static handling).
|
||||
*
|
||||
* @param string $className
|
||||
* @return mixed
|
||||
*/
|
||||
function singleton($className)
|
||||
{
|
||||
if ($className === Config::class) {
|
||||
throw new InvalidArgumentException("Don't pass Config to singleton()");
|
||||
}
|
||||
if (!isset($className)) {
|
||||
throw new InvalidArgumentException("singleton() Called without a class");
|
||||
}
|
||||
if (!is_string($className)) {
|
||||
throw new InvalidArgumentException(
|
||||
"singleton() passed bad class_name: " . var_export($className, true)
|
||||
);
|
||||
}
|
||||
return Injector::inst()->get($className);
|
||||
}
|
||||
|
||||
function project()
|
||||
{
|
||||
global $project;
|
||||
return $project;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main translator function. Returns the string defined by $entity according to the
|
||||
* currently set locale.
|
||||
*
|
||||
* Also supports pluralisation of strings. Pass in a `count` argument, as well as a
|
||||
* default value with `|` pipe-delimited options for each plural form.
|
||||
*
|
||||
* @param string $entity Entity that identifies the string. It must be in the form
|
||||
* "Namespace.Entity" where Namespace will be usually the class name where this
|
||||
* string is used and Entity identifies the string inside the namespace.
|
||||
* @param mixed $arg,... Additional arguments are parsed as such:
|
||||
* - Next string argument is a default. Pass in a `|` pipe-delimeted value with `{count}`
|
||||
* to do pluralisation.
|
||||
* - Any other string argument after default is context for i18nTextCollector
|
||||
* - Any array argument in any order is an injection parameter list. Pass in a `count`
|
||||
* injection parameter to pluralise.
|
||||
* @return string
|
||||
*/
|
||||
function _t($entity, $arg = null)
|
||||
{
|
||||
// Pass args directly to handle deprecation
|
||||
return call_user_func_array([i18n::class, '_t'], func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the memory limit to the given level if it's currently too low.
|
||||
* Only increases up to the maximum defined in {@link set_increase_memory_limit_max()},
|
||||
* and defaults to the 'memory_limit' setting in the PHP configuration.
|
||||
*
|
||||
* @param string|int $memoryLimit A memory limit string, such as "64M". If omitted, unlimited memory will be set.
|
||||
* @return Boolean TRUE indicates a successful change, FALSE a denied change.
|
||||
*/
|
||||
function increase_memory_limit_to($memoryLimit = -1)
|
||||
{
|
||||
$curLimit = ini_get('memory_limit');
|
||||
|
||||
// Can't go higher than infinite
|
||||
if ($curLimit == -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check hard maximums
|
||||
$max = get_increase_memory_limit_max();
|
||||
|
||||
if ($max && $max != -1 && translate_memstring($memoryLimit) > translate_memstring($max)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Increase the memory limit if it's too low
|
||||
if ($memoryLimit == -1 || translate_memstring($memoryLimit) > translate_memstring($curLimit)) {
|
||||
ini_set('memory_limit', $memoryLimit);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$_increase_memory_limit_max = ini_get('memory_limit');
|
||||
|
||||
/**
|
||||
* Set the maximum allowed value for {@link increase_memory_limit_to()}.
|
||||
* The same result can also be achieved through 'suhosin.memory_limit'
|
||||
* if PHP is running with the Suhosin system.
|
||||
*
|
||||
* @param string $memoryLimit Memory limit string
|
||||
*/
|
||||
function set_increase_memory_limit_max($memoryLimit)
|
||||
{
|
||||
global $_increase_memory_limit_max;
|
||||
$_increase_memory_limit_max = $memoryLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Memory limit string
|
||||
*/
|
||||
function get_increase_memory_limit_max()
|
||||
{
|
||||
global $_increase_memory_limit_max;
|
||||
return $_increase_memory_limit_max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the XDebug parameter max_nesting_level, which limits how deep recursion can go.
|
||||
* Only does anything if (a) xdebug is installed and (b) the new limit is higher than the existing limit
|
||||
*
|
||||
* @param int $limit - The new limit to increase to
|
||||
*/
|
||||
function increase_xdebug_nesting_level_to($limit)
|
||||
{
|
||||
if (function_exists('xdebug_enable')) {
|
||||
$current = ini_get('xdebug.max_nesting_level');
|
||||
if ((int)$current < $limit) {
|
||||
ini_set('xdebug.max_nesting_level', $limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a memory string, such as 512M into an actual number of bytes.
|
||||
*
|
||||
* @param string $memString A memory limit string, such as "64M"
|
||||
* @return float
|
||||
*/
|
||||
function translate_memstring($memString)
|
||||
{
|
||||
switch (strtolower(substr($memString, -1))) {
|
||||
case "k":
|
||||
return round(substr($memString, 0, -1)*1024);
|
||||
case "m":
|
||||
return round(substr($memString, 0, -1)*1024*1024);
|
||||
case "g":
|
||||
return round(substr($memString, 0, -1)*1024*1024*1024);
|
||||
default:
|
||||
return round($memString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the time limit of this script. By default, the time will be unlimited.
|
||||
* Only works if 'safe_mode' is off in the PHP configuration.
|
||||
* Only values up to {@link get_increase_time_limit_max()} are allowed.
|
||||
*
|
||||
* @param int $timeLimit The time limit in seconds. If omitted, no time limit will be set.
|
||||
* @return Boolean TRUE indicates a successful change, FALSE a denied change.
|
||||
*/
|
||||
function increase_time_limit_to($timeLimit = null)
|
||||
{
|
||||
$max = get_increase_time_limit_max();
|
||||
if ($max != -1 && $max != null && $timeLimit > $max) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ini_get('safe_mode')) {
|
||||
if (!$timeLimit) {
|
||||
set_time_limit(0);
|
||||
return true;
|
||||
} else {
|
||||
$currTimeLimit = ini_get('max_execution_time');
|
||||
// Only increase if its smaller
|
||||
if ($currTimeLimit && $currTimeLimit < $timeLimit) {
|
||||
set_time_limit($timeLimit);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum allowed value for {@link increase_timeLimit_to()};
|
||||
*
|
||||
* @param int $timeLimit Limit in seconds
|
||||
*/
|
||||
function set_increase_time_limit_max($timeLimit)
|
||||
{
|
||||
global $_increase_time_limit_max;
|
||||
$_increase_time_limit_max = $timeLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Int Limit in seconds
|
||||
*/
|
||||
function get_increase_time_limit_max()
|
||||
{
|
||||
global $_increase_time_limit_max;
|
||||
return $_increase_time_limit_max;
|
||||
}
|
580
src/Core/CoreKernel.php
Normal file
580
src/Core/CoreKernel.php
Normal file
@ -0,0 +1,580 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use SilverStripe\Config\Collections\CachedConfigCollection;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\HTTPResponse_Exception;
|
||||
use SilverStripe\Core\Cache\ManifestCacheFactory;
|
||||
use SilverStripe\Core\Config\ConfigLoader;
|
||||
use SilverStripe\Core\Config\CoreConfigFactory;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Injector\InjectorLoader;
|
||||
use SilverStripe\Core\Injector\SilverStripeServiceConfigurationLocator;
|
||||
use SilverStripe\Core\Manifest\ClassLoader;
|
||||
use SilverStripe\Core\Manifest\ClassManifest;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Core\Manifest\ModuleManifest;
|
||||
use SilverStripe\Dev\DebugView;
|
||||
use SilverStripe\Dev\Install\DatabaseAdapterRegistry;
|
||||
use SilverStripe\Logging\ErrorHandler;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\View\ThemeManifest;
|
||||
use SilverStripe\View\ThemeResourceLoader;
|
||||
|
||||
/**
|
||||
* Simple Kernel container
|
||||
*/
|
||||
class CoreKernel implements Kernel
|
||||
{
|
||||
/**
|
||||
* @var Kernel
|
||||
*/
|
||||
protected $nestedFrom = null;
|
||||
|
||||
/**
|
||||
* @var Injector
|
||||
*/
|
||||
protected $container = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $enviroment = null;
|
||||
|
||||
/**
|
||||
* @var ClassLoader
|
||||
*/
|
||||
protected $classLoader = null;
|
||||
|
||||
/**
|
||||
* @var ModuleLoader
|
||||
*/
|
||||
protected $moduleLoader = null;
|
||||
|
||||
/**
|
||||
* @var ConfigLoader
|
||||
*/
|
||||
protected $configLoader = null;
|
||||
|
||||
/**
|
||||
* @var InjectorLoader
|
||||
*/
|
||||
protected $injectorLoader = null;
|
||||
|
||||
/**
|
||||
* @var ThemeResourceLoader
|
||||
*/
|
||||
protected $themeResourceLoader = null;
|
||||
|
||||
protected $basePath = null;
|
||||
|
||||
/**
|
||||
* Create a new kernel for this application
|
||||
*
|
||||
* @param string $basePath Path to base dir for this application
|
||||
*/
|
||||
public function __construct($basePath)
|
||||
{
|
||||
$this->basePath = $basePath;
|
||||
|
||||
// Initialise the dependency injector as soon as possible, as it is
|
||||
// subsequently used by some of the following code
|
||||
$injectorLoader = InjectorLoader::inst();
|
||||
$injector = new Injector(array('locator' => SilverStripeServiceConfigurationLocator::class));
|
||||
$injectorLoader->pushManifest($injector);
|
||||
$this->setInjectorLoader($injectorLoader);
|
||||
|
||||
// Manifest cache factory
|
||||
$manifestCacheFactory = $this->buildManifestCacheFactory();
|
||||
|
||||
// Class loader
|
||||
$classLoader = ClassLoader::inst();
|
||||
$classLoader->pushManifest(new ClassManifest($basePath, $manifestCacheFactory));
|
||||
$this->setClassLoader($classLoader);
|
||||
|
||||
// Module loader
|
||||
$moduleLoader = ModuleLoader::inst();
|
||||
$moduleManifest = new ModuleManifest($basePath, $manifestCacheFactory);
|
||||
$moduleLoader->pushManifest($moduleManifest);
|
||||
$this->setModuleLoader($moduleLoader);
|
||||
|
||||
// Config loader
|
||||
// @todo refactor CoreConfigFactory
|
||||
$configFactory = new CoreConfigFactory($manifestCacheFactory);
|
||||
$configManifest = $configFactory->createRoot();
|
||||
$configLoader = ConfigLoader::inst();
|
||||
$configLoader->pushManifest($configManifest);
|
||||
$this->setConfigLoader($configLoader);
|
||||
|
||||
// Load template manifest
|
||||
$themeResourceLoader = ThemeResourceLoader::inst();
|
||||
$themeResourceLoader->addSet('$default', new ThemeManifest(
|
||||
$basePath,
|
||||
project(),
|
||||
$manifestCacheFactory
|
||||
));
|
||||
$this->setThemeResourceLoader($themeResourceLoader);
|
||||
}
|
||||
|
||||
public function getEnvironment()
|
||||
{
|
||||
// Check set
|
||||
if ($this->enviroment) {
|
||||
return $this->enviroment;
|
||||
}
|
||||
|
||||
// Check saved session
|
||||
$env = $this->sessionEnvironment();
|
||||
if ($env) {
|
||||
return $env;
|
||||
}
|
||||
|
||||
// Check getenv
|
||||
if ($env = getenv('SS_ENVIRONMENT_TYPE')) {
|
||||
return $env;
|
||||
}
|
||||
|
||||
return self::LIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check or update any temporary environment specified in the session.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
protected function sessionEnvironment()
|
||||
{
|
||||
// Check isDev in querystring
|
||||
if (isset($_GET['isDev'])) {
|
||||
if (isset($_SESSION)) {
|
||||
unset($_SESSION['isTest']); // In case we are changing from test mode
|
||||
$_SESSION['isDev'] = $_GET['isDev'];
|
||||
}
|
||||
return self::DEV;
|
||||
}
|
||||
|
||||
// Check isTest in querystring
|
||||
if (isset($_GET['isTest'])) {
|
||||
if (isset($_SESSION)) {
|
||||
unset($_SESSION['isDev']); // In case we are changing from dev mode
|
||||
$_SESSION['isTest'] = $_GET['isTest'];
|
||||
}
|
||||
return self::TEST;
|
||||
}
|
||||
|
||||
// Check session
|
||||
if (!empty($_SESSION['isDev'])) {
|
||||
return self::DEV;
|
||||
}
|
||||
if (!empty($_SESSION['isTest'])) {
|
||||
return self::TEST;
|
||||
}
|
||||
|
||||
// no session environment
|
||||
return null;
|
||||
}
|
||||
|
||||
public function boot($flush = false)
|
||||
{
|
||||
$this->bootPHP();
|
||||
$this->bootManifests($flush);
|
||||
$this->bootErrorHandling();
|
||||
$this->bootDatabase();
|
||||
$this->bootConfigs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Include all _config.php files
|
||||
*/
|
||||
protected function bootConfigs()
|
||||
{
|
||||
// After loading all other app manifests, include _config.php files
|
||||
$this->getModuleLoader()->getManifest()->activateConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure database
|
||||
*
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
protected function bootDatabase()
|
||||
{
|
||||
// Check if a DB is named
|
||||
$name = $this->getDatabaseName();
|
||||
|
||||
// Gracefully fail if no DB is configured
|
||||
if (empty($name)) {
|
||||
$this->detectLegacyEnvironment();
|
||||
$this->redirectToInstaller();
|
||||
}
|
||||
|
||||
// Set default database config
|
||||
$databaseConfig = $this->getDatabaseConfig();
|
||||
$databaseConfig['database'] = $this->getDatabaseName();
|
||||
DB::setConfig($databaseConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there's a legacy _ss_environment.php file
|
||||
*
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
protected function detectLegacyEnvironment()
|
||||
{
|
||||
// Is there an _ss_environment.php file?
|
||||
if (!file_exists($this->basePath . '/_ss_environment.php') &&
|
||||
!file_exists(dirname($this->basePath) . '/_ss_environment.php')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build error response
|
||||
$dv = new DebugView();
|
||||
$body =
|
||||
$dv->renderHeader() .
|
||||
$dv->renderInfo(
|
||||
"Configuraton Error",
|
||||
Director::absoluteBaseURL()
|
||||
) .
|
||||
$dv->renderParagraph(
|
||||
'You need to replace your _ss_environment.php file with a .env file, or with environment variables.<br><br>'
|
||||
. 'See the <a href="https://docs.silverstripe.org/en/4/getting_started/environment_management/">'
|
||||
. 'Environment Management</a> docs for more information.'
|
||||
) .
|
||||
$dv->renderFooter();
|
||||
|
||||
// Raise error
|
||||
$response = new HTTPResponse($body, 500);
|
||||
throw new HTTPResponse_Exception($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* If missing configuration, redirect to install.php
|
||||
*/
|
||||
protected function redirectToInstaller()
|
||||
{
|
||||
// Error if installer not available
|
||||
if (!file_exists($this->basePath . '/install.php')) {
|
||||
throw new HTTPResponse_Exception(
|
||||
'SilverStripe Framework requires a $databaseConfig defined.',
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
// Redirect to installer
|
||||
$response = new HTTPResponse();
|
||||
$response->redirect(Director::absoluteURL('install.php'));
|
||||
throw new HTTPResponse_Exception($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load database config from environment
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDatabaseConfig()
|
||||
{
|
||||
// Check global config
|
||||
global $databaseConfig;
|
||||
if (!empty($databaseConfig)) {
|
||||
return $databaseConfig;
|
||||
}
|
||||
|
||||
/** @skipUpgrade */
|
||||
$databaseConfig = [
|
||||
"type" => getenv('SS_DATABASE_CLASS') ?: 'MySQLDatabase',
|
||||
"server" => getenv('SS_DATABASE_SERVER') ?: 'localhost',
|
||||
"username" => getenv('SS_DATABASE_USERNAME') ?: null,
|
||||
"password" => getenv('SS_DATABASE_PASSWORD') ?: null,
|
||||
];
|
||||
|
||||
// Set the port if called for
|
||||
$dbPort = getenv('SS_DATABASE_PORT');
|
||||
if ($dbPort) {
|
||||
$databaseConfig['port'] = $dbPort;
|
||||
}
|
||||
|
||||
// Set the timezone if called for
|
||||
$dbTZ = getenv('SS_DATABASE_TIMEZONE');
|
||||
if ($dbTZ) {
|
||||
$databaseConfig['timezone'] = $dbTZ;
|
||||
}
|
||||
|
||||
// For schema enabled drivers:
|
||||
$dbSchema = getenv('SS_DATABASE_SCHEMA');
|
||||
if ($dbSchema) {
|
||||
$databaseConfig["schema"] = $dbSchema;
|
||||
}
|
||||
|
||||
// For SQlite3 memory databases (mainly for testing purposes)
|
||||
$dbMemory = getenv('SS_DATABASE_MEMORY');
|
||||
if ($dbMemory) {
|
||||
$databaseConfig["memory"] = $dbMemory;
|
||||
}
|
||||
|
||||
// Allow database adapters to handle their own configuration
|
||||
DatabaseAdapterRegistry::autoconfigure();
|
||||
return $databaseConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name of database
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDatabaseName()
|
||||
{
|
||||
$prefix = getenv('SS_DATABASE_PREFIX') ?: 'SS_';
|
||||
|
||||
// Check globals
|
||||
global $database;
|
||||
if (!empty($database)) {
|
||||
return $prefix.$database;
|
||||
}
|
||||
global $databaseConfig;
|
||||
if (!empty($databaseConfig['database'])) {
|
||||
return $databaseConfig['database']; // Note: Already includes prefix
|
||||
}
|
||||
|
||||
// Check environment
|
||||
$database = getenv('SS_DATABASE_NAME');
|
||||
if ($database) {
|
||||
return $prefix.$database;
|
||||
}
|
||||
|
||||
// Auto-detect name
|
||||
$chooseName = getenv('SS_DATABASE_CHOOSE_NAME');
|
||||
if ($chooseName) {
|
||||
// Find directory to build name from
|
||||
$loopCount = (int)$chooseName;
|
||||
$databaseDir = $this->basePath;
|
||||
for ($i = 0; $i < $loopCount-1; $i++) {
|
||||
$databaseDir = dirname($databaseDir);
|
||||
}
|
||||
|
||||
// Build name
|
||||
$database = str_replace('.', '', basename($databaseDir));
|
||||
return $prefix.$database;
|
||||
}
|
||||
|
||||
// no DB name (may be optional for some connectors)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise PHP with default variables
|
||||
*/
|
||||
protected function bootPHP()
|
||||
{
|
||||
if ($this->getEnvironment() === self::LIVE) {
|
||||
// limited to fatal errors and warnings in live mode
|
||||
error_reporting(E_ALL & ~(E_DEPRECATED | E_STRICT | E_NOTICE));
|
||||
} else {
|
||||
// Report all errors in dev / test mode
|
||||
error_reporting(E_ALL | E_STRICT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure we have enough memory
|
||||
*/
|
||||
Environment::increaseMemoryLimitTo('64M');
|
||||
|
||||
// Ensure we don't run into xdebug's fairly conservative infinite recursion protection limit
|
||||
if (function_exists('xdebug_enable')) {
|
||||
$current = ini_get('xdebug.max_nesting_level');
|
||||
if ((int)$current < 200) {
|
||||
ini_set('xdebug.max_nesting_level', 200);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default encoding
|
||||
*/
|
||||
mb_http_output('UTF-8');
|
||||
mb_internal_encoding('UTF-8');
|
||||
mb_regex_encoding('UTF-8');
|
||||
|
||||
/**
|
||||
* Enable better garbage collection
|
||||
*/
|
||||
gc_enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ManifestCacheFactory
|
||||
*/
|
||||
protected function buildManifestCacheFactory()
|
||||
{
|
||||
return new ManifestCacheFactory([
|
||||
'namespace' => 'manifestcache',
|
||||
'directory' => TempFolder::getTempFolder($this->basePath),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function getIncludeTests()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot all manifests
|
||||
*
|
||||
* @param bool $flush
|
||||
*/
|
||||
protected function bootManifests($flush)
|
||||
{
|
||||
// Setup autoloader
|
||||
$this->getClassLoader()->init($this->getIncludeTests(), $flush);
|
||||
|
||||
// Find modules
|
||||
$this->getModuleLoader()->init($this->getIncludeTests(), $flush);
|
||||
|
||||
// Flush config
|
||||
if ($flush) {
|
||||
$config = $this->getConfigLoader()->getManifest();
|
||||
if ($config instanceof CachedConfigCollection) {
|
||||
$config->setFlush(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Find default templates
|
||||
$defaultSet = $this->getThemeResourceLoader()->getSet('$default');
|
||||
if ($defaultSet instanceof ThemeManifest) {
|
||||
$defaultSet->init($this->getIncludeTests(), $flush);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on error handling
|
||||
*/
|
||||
protected function bootErrorHandling()
|
||||
{
|
||||
// Register error handler
|
||||
$errorHandler = Injector::inst()->get(ErrorHandler::class);
|
||||
$errorHandler->start();
|
||||
|
||||
// Register error log file
|
||||
$errorLog = getenv('SS_ERROR_LOG');
|
||||
if ($errorLog) {
|
||||
$logger = Injector::inst()->get(LoggerInterface::class);
|
||||
if ($logger instanceof Logger) {
|
||||
$logger->pushHandler(new StreamHandler($this->basePath . '/' . $errorLog, Logger::WARNING));
|
||||
} else {
|
||||
user_error("SS_ERROR_LOG setting only works with Monolog, you are using another logger", E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
public function nest()
|
||||
{
|
||||
// Clone this kernel, nesting config / injector manifest containers
|
||||
$kernel = clone $this;
|
||||
$kernel->setConfigLoader($this->configLoader->nest());
|
||||
$kernel->setInjectorLoader($this->injectorLoader->nest());
|
||||
return $kernel;
|
||||
}
|
||||
|
||||
public function activate()
|
||||
{
|
||||
$this->configLoader->activate();
|
||||
$this->injectorLoader->activate();
|
||||
|
||||
// Self register
|
||||
$this->getInjectorLoader()
|
||||
->getManifest()
|
||||
->registerService($this, Kernel::class);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNestedFrom()
|
||||
{
|
||||
return $this->nestedFrom;
|
||||
}
|
||||
|
||||
public function getContainer()
|
||||
{
|
||||
return $this->getInjectorLoader()->getManifest();
|
||||
}
|
||||
|
||||
public function setInjectorLoader(InjectorLoader $injectorLoader)
|
||||
{
|
||||
$this->injectorLoader = $injectorLoader;
|
||||
$injectorLoader
|
||||
->getManifest()
|
||||
->registerService($this, Kernel::class);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getInjectorLoader()
|
||||
{
|
||||
return $this->injectorLoader;
|
||||
}
|
||||
|
||||
public function getClassLoader()
|
||||
{
|
||||
return $this->classLoader;
|
||||
}
|
||||
|
||||
public function setClassLoader(ClassLoader $classLoader)
|
||||
{
|
||||
$this->classLoader = $classLoader;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getModuleLoader()
|
||||
{
|
||||
return $this->moduleLoader;
|
||||
}
|
||||
|
||||
public function setModuleLoader(ModuleLoader $moduleLoader)
|
||||
{
|
||||
$this->moduleLoader = $moduleLoader;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setEnvironment($environment)
|
||||
{
|
||||
if (!in_array($environment, [self::DEV, self::TEST, self::LIVE, null])) {
|
||||
throw new InvalidArgumentException(
|
||||
"Director::set_environment_type passed '$environment'. It should be passed dev, test, or live"
|
||||
);
|
||||
}
|
||||
$this->enviroment = $environment;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getConfigLoader()
|
||||
{
|
||||
return $this->configLoader;
|
||||
}
|
||||
|
||||
public function setConfigLoader($configLoader)
|
||||
{
|
||||
$this->configLoader = $configLoader;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getThemeResourceLoader()
|
||||
{
|
||||
return $this->themeResourceLoader;
|
||||
}
|
||||
|
||||
public function setThemeResourceLoader($themeResourceLoader)
|
||||
{
|
||||
$this->themeResourceLoader = $themeResourceLoader;
|
||||
return $this;
|
||||
}
|
||||
}
|
154
src/Core/Environment.php
Normal file
154
src/Core/Environment.php
Normal file
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core;
|
||||
|
||||
/**
|
||||
* Consolidates access and modification of PHP global variables and settings.
|
||||
* This class should be used sparingly, and only if information cannot be obtained
|
||||
* from a current {@link HTTPRequest} object.
|
||||
*/
|
||||
class Environment
|
||||
{
|
||||
/**
|
||||
* Set maximum limit allowed for increaseMemoryLimit
|
||||
*
|
||||
* @var float|null
|
||||
*/
|
||||
protected static $memoryLimitMax = null;
|
||||
|
||||
/**
|
||||
* Set maximum limited allowed for increaseTimeLimit
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected static $timeLimitMax = null;
|
||||
|
||||
/**
|
||||
* Extract env vars prior to modification
|
||||
*
|
||||
* @return array List of all super globals
|
||||
*/
|
||||
public static function getVariables()
|
||||
{
|
||||
// Suppress return by-ref
|
||||
return array_merge($GLOBALS, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a backed up or modified list of vars to $globals
|
||||
*
|
||||
* @param array $vars
|
||||
*/
|
||||
public static function setVariables(array $vars)
|
||||
{
|
||||
foreach ($vars as $key => $value) {
|
||||
$GLOBALS[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the memory limit to the given level if it's currently too low.
|
||||
* Only increases up to the maximum defined in {@link setMemoryLimitMax()},
|
||||
* and defaults to the 'memory_limit' setting in the PHP configuration.
|
||||
*
|
||||
* @param string|float|int $memoryLimit A memory limit string, such as "64M". If omitted, unlimited memory will be set.
|
||||
* @return bool true indicates a successful change, false a denied change.
|
||||
*/
|
||||
public static function increaseMemoryLimitTo($memoryLimit = -1)
|
||||
{
|
||||
$memoryLimit = Convert::memstring2bytes($memoryLimit);
|
||||
$curLimit = Convert::memstring2bytes(ini_get('memory_limit'));
|
||||
|
||||
// Can't go higher than infinite
|
||||
if ($curLimit < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check hard maximums
|
||||
$max = static::getMemoryLimitMax();
|
||||
if ($max > 0 && ($memoryLimit < 0 || $memoryLimit > $max)) {
|
||||
$memoryLimit = $max;
|
||||
}
|
||||
|
||||
// Increase the memory limit if it's too low
|
||||
if ($memoryLimit < 0) {
|
||||
ini_set('memory_limit', '-1');
|
||||
} elseif ($memoryLimit > $curLimit) {
|
||||
ini_set('memory_limit', Convert::bytes2memstring($memoryLimit));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum allowed value for {@link increaseMemoryLimitTo()}.
|
||||
* The same result can also be achieved through 'suhosin.memory_limit'
|
||||
* if PHP is running with the Suhosin system.
|
||||
*
|
||||
* @param string|float $memoryLimit Memory limit string or float value
|
||||
*/
|
||||
static function setMemoryLimitMax($memoryLimit)
|
||||
{
|
||||
if (isset($memoryLimit) && !is_numeric($memoryLimit)) {
|
||||
$memoryLimit = Convert::memstring2bytes($memoryLimit);
|
||||
}
|
||||
static::$memoryLimitMax = $memoryLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int Memory limit in bytes
|
||||
*/
|
||||
public static function getMemoryLimitMax()
|
||||
{
|
||||
if (static::$memoryLimitMax === null) {
|
||||
return Convert::memstring2bytes(ini_get('memory_limit'));
|
||||
}
|
||||
return static::$memoryLimitMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the time limit of this script. By default, the time will be unlimited.
|
||||
* Only works if 'safe_mode' is off in the PHP configuration.
|
||||
* Only values up to {@link getTimeLimitMax()} are allowed.
|
||||
*
|
||||
* @param int $timeLimit The time limit in seconds. If omitted, no time limit will be set.
|
||||
* @return Boolean TRUE indicates a successful change, FALSE a denied change.
|
||||
*/
|
||||
public static function increaseTimeLimitTo($timeLimit = null)
|
||||
{
|
||||
// Check vs max limit
|
||||
$max = static::getTimeLimitMax();
|
||||
if ($max > 0 && $timeLimit > $max) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$timeLimit) {
|
||||
set_time_limit(0);
|
||||
} else {
|
||||
$currTimeLimit = ini_get('max_execution_time');
|
||||
// Only increase if its smaller
|
||||
if ($currTimeLimit > 0 && $currTimeLimit < $timeLimit) {
|
||||
set_time_limit($timeLimit);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum allowed value for {@link increaseTimeLimitTo()};
|
||||
*
|
||||
* @param int $timeLimit Limit in seconds
|
||||
*/
|
||||
public static function setTimeLimitMax($timeLimit)
|
||||
{
|
||||
static::$timeLimitMax = $timeLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Int Limit in seconds
|
||||
*/
|
||||
public static function getTimeLimitMax()
|
||||
{
|
||||
return static::$timeLimitMax;
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ use InvalidArgumentException;
|
||||
use SilverStripe\Control\RequestHandler;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\View\ViewableData;
|
||||
|
||||
/**
|
||||
@ -271,8 +272,8 @@ trait Extensible
|
||||
}
|
||||
Config::modify()->set($class, 'extensions', $config);
|
||||
|
||||
// unset singletons to avoid side-effects
|
||||
Injector::inst()->unregisterAllObjects();
|
||||
// Unset singletons
|
||||
Injector::inst()->unregisterObjects($class);
|
||||
|
||||
// unset some caches
|
||||
$subclasses = ClassInfo::subclassesFor($class);
|
||||
|
@ -2,14 +2,15 @@
|
||||
|
||||
namespace SilverStripe\Core\Injector;
|
||||
|
||||
use ArrayObject;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use ReflectionMethod;
|
||||
use ReflectionObject;
|
||||
use ReflectionProperty;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use ReflectionProperty;
|
||||
use ArrayObject;
|
||||
use ReflectionObject;
|
||||
use ReflectionMethod;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
|
||||
/**
|
||||
@ -204,19 +205,16 @@ class Injector implements ContainerInterface
|
||||
$this->serviceCache = array(
|
||||
'Injector' => $this,
|
||||
);
|
||||
$this->specs = array(
|
||||
'Injector' => array('class' => 'SilverStripe\\Core\\Injector\\Injector')
|
||||
);
|
||||
|
||||
$this->specs = [
|
||||
'Injector' => ['class' => static::class]
|
||||
];
|
||||
$this->autoProperties = array();
|
||||
|
||||
|
||||
$creatorClass = isset($config['creator'])
|
||||
? $config['creator']
|
||||
: 'SilverStripe\\Core\\Injector\\InjectionCreator';
|
||||
: InjectionCreator::class;
|
||||
$locatorClass = isset($config['locator'])
|
||||
? $config['locator']
|
||||
: 'SilverStripe\\Core\\Injector\\SilverStripeServiceConfigurationLocator';
|
||||
: SilverStripeServiceConfigurationLocator::class;
|
||||
|
||||
$this->objectCreator = new $creatorClass;
|
||||
$this->configLocator = new $locatorClass;
|
||||
@ -234,28 +232,11 @@ class Injector implements ContainerInterface
|
||||
protected $nestedFrom = null;
|
||||
|
||||
/**
|
||||
* If a user wants to use the injector as a static reference
|
||||
*
|
||||
* @param array $config
|
||||
* @return Injector
|
||||
*/
|
||||
public static function inst($config = null)
|
||||
public static function inst()
|
||||
{
|
||||
if (!self::$instance) {
|
||||
self::$instance = new Injector($config);
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default global injector instance.
|
||||
*
|
||||
* @param Injector $instance
|
||||
* @return Injector Reference to new active Injector instance
|
||||
*/
|
||||
public static function set_inst(Injector $instance)
|
||||
{
|
||||
return self::$instance = $instance;
|
||||
return InjectorLoader::inst()->getManifest();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -270,11 +251,10 @@ class Injector implements ContainerInterface
|
||||
*/
|
||||
public static function nest()
|
||||
{
|
||||
$current = self::$instance;
|
||||
|
||||
$new = clone $current;
|
||||
$new->nestedFrom = $current;
|
||||
return self::set_inst($new);
|
||||
// Clone current injector and nest
|
||||
$new = clone self::inst();
|
||||
InjectorLoader::inst()->pushManifest($new);
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -285,15 +265,17 @@ class Injector implements ContainerInterface
|
||||
*/
|
||||
public static function unnest()
|
||||
{
|
||||
if (self::inst()->nestedFrom) {
|
||||
self::set_inst(self::inst()->nestedFrom);
|
||||
} else {
|
||||
// Unnest unless we would be left at 0 manifests
|
||||
$loader = InjectorLoader::inst();
|
||||
if ($loader->countManifests() <= 1) {
|
||||
user_error(
|
||||
"Unable to unnest root Injector, please make sure you don't have mis-matched nest/unnest",
|
||||
E_USER_WARNING
|
||||
);
|
||||
} else {
|
||||
$loader->popManifest();
|
||||
}
|
||||
return self::inst();
|
||||
return static::inst();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -407,7 +389,7 @@ class Injector implements ContainerInterface
|
||||
|
||||
// make sure the class is set...
|
||||
if (empty($class)) {
|
||||
throw new \InvalidArgumentException('Missing spec class');
|
||||
throw new InvalidArgumentException('Missing spec class');
|
||||
}
|
||||
$spec['class'] = $class;
|
||||
|
||||
@ -654,21 +636,21 @@ class Injector implements ContainerInterface
|
||||
|
||||
// Format validation
|
||||
if (!is_array($method) || !isset($method[0]) || isset($method[2])) {
|
||||
throw new \InvalidArgumentException(
|
||||
throw new InvalidArgumentException(
|
||||
"'calls' entries in service definition should be 1 or 2 element arrays."
|
||||
);
|
||||
}
|
||||
if (!is_string($method[0])) {
|
||||
throw new \InvalidArgumentException("1st element of a 'calls' entry should be a string");
|
||||
throw new InvalidArgumentException("1st element of a 'calls' entry should be a string");
|
||||
}
|
||||
if (isset($method[1]) && !is_array($method[1])) {
|
||||
throw new \InvalidArgumentException("2nd element of a 'calls' entry should an arguments array");
|
||||
throw new InvalidArgumentException("2nd element of a 'calls' entry should an arguments array");
|
||||
}
|
||||
|
||||
// Check that the method exists and is callable
|
||||
$objectMethod = array($object, $method[0]);
|
||||
if (!is_callable($objectMethod)) {
|
||||
throw new \InvalidArgumentException("'$method[0]' in 'calls' entry is not a public method");
|
||||
throw new InvalidArgumentException("'$method[0]' in 'calls' entry is not a public method");
|
||||
}
|
||||
|
||||
// Call it
|
||||
@ -850,17 +832,18 @@ class Injector implements ContainerInterface
|
||||
* @param object $service The object to register
|
||||
* @param string $replace The name of the object to replace (if different to the
|
||||
* class name of the object to register)
|
||||
* @return $this
|
||||
*/
|
||||
public function registerService($service, $replace = null)
|
||||
{
|
||||
$registerAt = get_class($service);
|
||||
if ($replace != null) {
|
||||
if ($replace !== null) {
|
||||
$registerAt = $replace;
|
||||
}
|
||||
|
||||
$this->specs[$registerAt] = array('class' => get_class($service));
|
||||
$this->serviceCache[$registerAt] = $service;
|
||||
$this->inject($service);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -868,18 +851,40 @@ class Injector implements ContainerInterface
|
||||
* by the inject
|
||||
*
|
||||
* @param string $name The name to unregister
|
||||
* @return $this
|
||||
*/
|
||||
public function unregisterNamedObject($name)
|
||||
{
|
||||
unset($this->serviceCache[$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear out all objects that are managed by the injetor.
|
||||
* Clear out objects of one or more types that are managed by the injetor.
|
||||
*
|
||||
* @param array|string $types Base class of object (not service name) to remove
|
||||
* @return $this
|
||||
*/
|
||||
public function unregisterAllObjects()
|
||||
public function unregisterObjects($types)
|
||||
{
|
||||
$this->serviceCache = array('Injector' => $this);
|
||||
if (!is_array($types)) {
|
||||
$types = [ $types ];
|
||||
}
|
||||
|
||||
// Filter all objects
|
||||
foreach ($this->serviceCache as $key => $object) {
|
||||
foreach ($types as $filterClass) {
|
||||
// Prevent destructive flushing
|
||||
if (strcasecmp($filterClass, 'object') === 0) {
|
||||
throw new InvalidArgumentException("Global unregistration is not allowed");
|
||||
}
|
||||
if ($object instanceof $filterClass) {
|
||||
unset($this->serviceCache[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
117
src/Core/Injector/InjectorLoader.php
Normal file
117
src/Core/Injector/InjectorLoader.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Injector;
|
||||
|
||||
use BadMethodCallException;
|
||||
|
||||
/**
|
||||
* Registers chained injectors
|
||||
*/
|
||||
class InjectorLoader
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
* @var self
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* @var Injector[] map of injector instances
|
||||
*/
|
||||
protected $manifests = array();
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public static function inst()
|
||||
{
|
||||
return self::$instance ? self::$instance : self::$instance = new static();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently active class manifest instance that is used for
|
||||
* loading classes.
|
||||
*
|
||||
* @return Injector
|
||||
*/
|
||||
public function getManifest()
|
||||
{
|
||||
if ($this !== self::$instance) {
|
||||
throw new BadMethodCallException(
|
||||
"Non-current injector manifest cannot be accessed. Please call ->activate() first"
|
||||
);
|
||||
}
|
||||
if (empty($this->manifests)) {
|
||||
throw new BadMethodCallException("No injector manifests available");
|
||||
}
|
||||
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 Injector $manifest
|
||||
*/
|
||||
public function pushManifest(Injector $manifest)
|
||||
{
|
||||
$this->manifests[] = $manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Injector
|
||||
*/
|
||||
public function popManifest()
|
||||
{
|
||||
return array_pop($this->manifests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check number of manifests
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function countManifests()
|
||||
{
|
||||
return count($this->manifests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nest the config loader
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function nest()
|
||||
{
|
||||
// Nest injector (note: Don't call getManifest()->nest() since that self-pushes a new manifest)
|
||||
$manifest = clone $this->getManifest();
|
||||
|
||||
// Create new blank loader with new stack (top level nesting)
|
||||
$newLoader = new static;
|
||||
$newLoader->pushManifest($manifest);
|
||||
|
||||
// Activate new loader
|
||||
$newLoader->activate();
|
||||
return $newLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this instance as the current instance
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function activate()
|
||||
{
|
||||
static::$instance = $this;
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -51,7 +51,7 @@ class SilverStripeServiceConfigurationLocator implements ServiceConfigurationLoc
|
||||
return $this->configs[$name];
|
||||
}
|
||||
|
||||
$config = Config::inst()->get('SilverStripe\\Core\\Injector\\Injector', $name);
|
||||
$config = Config::inst()->get(Injector::class, $name);
|
||||
$this->configs[$name] = $config;
|
||||
return $config;
|
||||
}
|
||||
|
135
src/Core/Kernel.php
Normal file
135
src/Core/Kernel.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core;
|
||||
|
||||
use SilverStripe\Core\Config\ConfigLoader;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Injector\InjectorLoader;
|
||||
use SilverStripe\Core\Manifest\ClassLoader;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\View\ThemeResourceLoader;
|
||||
|
||||
/**
|
||||
* Represents the core state of a SilverStripe application
|
||||
* Based loosely on symfony/http-kernel's KernelInterface component
|
||||
*/
|
||||
interface Kernel
|
||||
{
|
||||
/**
|
||||
* Test environment
|
||||
*/
|
||||
const TEST = 'test';
|
||||
|
||||
/**
|
||||
* Dev environment
|
||||
*/
|
||||
const DEV = 'dev';
|
||||
|
||||
/**
|
||||
* Live (default) environment
|
||||
*/
|
||||
const LIVE = 'live';
|
||||
|
||||
/*
|
||||
* Boots the current kernel
|
||||
*
|
||||
* @param bool $flush
|
||||
*/
|
||||
public function boot($flush = false);
|
||||
|
||||
/**
|
||||
* Shutdowns the kernel.
|
||||
*/
|
||||
public function shutdown();
|
||||
|
||||
/**
|
||||
* Nests this kernel, all components, and returns the nested value.
|
||||
*
|
||||
* @return Kernel
|
||||
*/
|
||||
public function nest();
|
||||
|
||||
/**
|
||||
* Ensures this kernel is the active kernel after or during nesting
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function activate();
|
||||
|
||||
/**
|
||||
* @return Injector
|
||||
*/
|
||||
public function getContainer();
|
||||
|
||||
/**
|
||||
* @return ClassLoader
|
||||
*/
|
||||
public function getClassLoader();
|
||||
|
||||
/**
|
||||
* @param ClassLoader $classLoader
|
||||
* @return $this
|
||||
*/
|
||||
public function setClassLoader(ClassLoader $classLoader);
|
||||
|
||||
/**
|
||||
* Get loader for injector instance
|
||||
*
|
||||
* @return InjectorLoader
|
||||
*/
|
||||
public function getInjectorLoader();
|
||||
|
||||
/**
|
||||
* @param InjectorLoader $injectorLoader
|
||||
* @return $this
|
||||
*/
|
||||
public function setInjectorLoader(InjectorLoader $injectorLoader);
|
||||
|
||||
/**
|
||||
* @return ModuleLoader
|
||||
*/
|
||||
public function getModuleLoader();
|
||||
|
||||
/**
|
||||
* @param ModuleLoader $moduleLoader
|
||||
* @return $this
|
||||
*/
|
||||
public function setModuleLoader(ModuleLoader $moduleLoader);
|
||||
|
||||
/**
|
||||
* @return ConfigLoader
|
||||
*/
|
||||
public function getConfigLoader();
|
||||
|
||||
/**
|
||||
* @param ConfigLoader $configLoader
|
||||
* @return $this
|
||||
*/
|
||||
public function setConfigLoader($configLoader);
|
||||
|
||||
/**
|
||||
* @return ThemeResourceLoader
|
||||
*/
|
||||
public function getThemeResourceLoader();
|
||||
|
||||
/**
|
||||
* @param ThemeResourceLoader $themeResourceLoader
|
||||
* @return $this
|
||||
*/
|
||||
public function setThemeResourceLoader($themeResourceLoader);
|
||||
|
||||
/**
|
||||
* One of dev, live, or test
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEnvironment();
|
||||
|
||||
/**
|
||||
* Sets new environment
|
||||
*
|
||||
* @param string $environment
|
||||
* @return $this
|
||||
*/
|
||||
public function setEnvironment($environment);
|
||||
}
|
@ -19,7 +19,9 @@ class ClassLoader
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* @var array Map of 'instance' (ClassManifest) and other options.
|
||||
* Map of 'instance' (ClassManifest) and other options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $manifests = array();
|
||||
|
||||
@ -28,7 +30,7 @@ class ClassLoader
|
||||
*/
|
||||
public static function inst()
|
||||
{
|
||||
return self::$instance ? self::$instance : self::$instance = new self();
|
||||
return self::$instance ? self::$instance : self::$instance = new static();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -113,6 +115,23 @@ class ClassLoader
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the class loader
|
||||
*
|
||||
* @param bool $includeTests
|
||||
* @param bool $forceRegen
|
||||
*/
|
||||
public function init($includeTests = false, $forceRegen = false)
|
||||
{
|
||||
foreach ($this->manifests as $manifest) {
|
||||
/** @var ClassManifest $instance */
|
||||
$instance = $manifest['instance'];
|
||||
$instance->init($includeTests, $forceRegen);
|
||||
}
|
||||
|
||||
$this->registerAutoloader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a class or interface name exists in the manifest.
|
||||
*
|
||||
|
@ -29,12 +29,11 @@ class ClassManifest
|
||||
protected $base;
|
||||
|
||||
/**
|
||||
* Set if including test classes
|
||||
* Used to build cache during boot
|
||||
*
|
||||
* @see TestOnly
|
||||
* @var bool
|
||||
* @var CacheFactory
|
||||
*/
|
||||
protected $tests;
|
||||
protected $cacheFactory;
|
||||
|
||||
/**
|
||||
* Cache to use, if caching.
|
||||
@ -122,27 +121,30 @@ class ClassManifest
|
||||
* from the cache or re-scanning for classes.
|
||||
*
|
||||
* @param string $base The manifest base path.
|
||||
* @param bool $includeTests Include the contents of "tests" directories.
|
||||
* @param bool $forceRegen Force the manifest to be regenerated.
|
||||
* @param CacheFactory $cacheFactory Optional cache to use. Set to null to not cache.
|
||||
*/
|
||||
public function __construct(
|
||||
$base,
|
||||
$includeTests = false,
|
||||
$forceRegen = false,
|
||||
CacheFactory $cacheFactory = null
|
||||
) {
|
||||
public function __construct($base, CacheFactory $cacheFactory = null)
|
||||
{
|
||||
$this->base = $base;
|
||||
$this->tests = $includeTests;
|
||||
$this->cacheFactory = $cacheFactory;
|
||||
$this->cacheKey = 'manifest';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the class manifest
|
||||
*
|
||||
* @param bool $includeTests
|
||||
* @param bool $forceRegen
|
||||
*/
|
||||
public function init($includeTests = false, $forceRegen = false)
|
||||
{
|
||||
// build cache from factory
|
||||
if ($cacheFactory) {
|
||||
$this->cache = $cacheFactory->create(
|
||||
if ($this->cacheFactory) {
|
||||
$this->cache = $this->cacheFactory->create(
|
||||
CacheInterface::class.'.classmanifest',
|
||||
[ 'namespace' => 'classmanifest' . ($includeTests ? '_tests' : '') ]
|
||||
);
|
||||
}
|
||||
$this->cacheKey = 'manifest';
|
||||
|
||||
if (!$forceRegen && $this->cache && ($data = $this->cache->get($this->cacheKey))) {
|
||||
$this->classes = $data['classes'];
|
||||
@ -151,7 +153,7 @@ class ClassManifest
|
||||
$this->implementors = $data['implementors'];
|
||||
$this->traits = $data['traits'];
|
||||
} else {
|
||||
$this->regenerate();
|
||||
$this->regenerate($includeTests);
|
||||
}
|
||||
}
|
||||
|
||||
@ -346,8 +348,10 @@ class ClassManifest
|
||||
|
||||
/**
|
||||
* Completely regenerates the manifest file.
|
||||
*
|
||||
* @param bool $includeTests
|
||||
*/
|
||||
public function regenerate()
|
||||
public function regenerate($includeTests)
|
||||
{
|
||||
$resets = array(
|
||||
'classes', 'roots', 'children', 'descendants', 'interfaces',
|
||||
@ -363,8 +367,10 @@ class ClassManifest
|
||||
$finder->setOptions(array(
|
||||
'name_regex' => '/^[^_].*\\.php$/',
|
||||
'ignore_files' => array('index.php', 'main.php', 'cli-script.php'),
|
||||
'ignore_tests' => !$this->tests,
|
||||
'file_callback' => array($this, 'handleFile'),
|
||||
'ignore_tests' => !$includeTests,
|
||||
'file_callback' => function ($basename, $pathname) use ($includeTests) {
|
||||
$this->handleFile($basename, $pathname, $includeTests);
|
||||
},
|
||||
));
|
||||
$finder->find($this->base);
|
||||
|
||||
@ -384,7 +390,7 @@ class ClassManifest
|
||||
}
|
||||
}
|
||||
|
||||
public function handleFile($basename, $pathname)
|
||||
public function handleFile($basename, $pathname, $includeTests)
|
||||
{
|
||||
$classes = null;
|
||||
$interfaces = null;
|
||||
@ -397,6 +403,7 @@ class ClassManifest
|
||||
$key = preg_replace('/[^a-zA-Z0-9_]/', '_', $basename) . '_' . md5_file($pathname);
|
||||
|
||||
// Attempt to load from cache
|
||||
$changed = false;
|
||||
if ($this->cache
|
||||
&& ($data = $this->cache->get($key))
|
||||
&& $this->validateItemCache($data)
|
||||
@ -405,6 +412,7 @@ class ClassManifest
|
||||
$interfaces = $data['interfaces'];
|
||||
$traits = $data['traits'];
|
||||
} else {
|
||||
$changed = true;
|
||||
// Build from php file parser
|
||||
$fileContents = ClassContentRemover::remove_class_content($pathname);
|
||||
try {
|
||||
@ -418,23 +426,16 @@ class ClassManifest
|
||||
$classes = $this->getVisitor()->getClasses();
|
||||
$interfaces = $this->getVisitor()->getInterfaces();
|
||||
$traits = $this->getVisitor()->getTraits();
|
||||
|
||||
// Save back to cache if configured
|
||||
if ($this->cache) {
|
||||
$cache = array(
|
||||
'classes' => $classes,
|
||||
'interfaces' => $interfaces,
|
||||
'traits' => $traits,
|
||||
);
|
||||
$this->cache->set($key, $cache);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge this data into the global list
|
||||
foreach ($classes as $className => $classInfo) {
|
||||
$extends = isset($classInfo['extends']) ? $classInfo['extends'] : null;
|
||||
$implements = isset($classInfo['interfaces']) ? $classInfo['interfaces'] : null;
|
||||
|
||||
$extends = !empty($classInfo['extends'])
|
||||
? array_map('strtolower', $classInfo['extends'])
|
||||
: [];
|
||||
$implements = !empty($classInfo['interfaces'])
|
||||
? array_map('strtolower', $classInfo['interfaces'])
|
||||
: [];
|
||||
$lowercaseName = strtolower($className);
|
||||
if (array_key_exists($lowercaseName, $this->classes)) {
|
||||
throw new Exception(sprintf(
|
||||
@ -445,12 +446,20 @@ class ClassManifest
|
||||
));
|
||||
}
|
||||
|
||||
// Skip if implements TestOnly, but doesn't include tests
|
||||
if (!$includeTests
|
||||
&& $implements
|
||||
&& in_array(strtolower(TestOnly::class), $implements)
|
||||
) {
|
||||
$changed = true;
|
||||
unset($classes[$className]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->classes[$lowercaseName] = $pathname;
|
||||
|
||||
if ($extends) {
|
||||
foreach ($extends as $ancestor) {
|
||||
$ancestor = strtolower($ancestor);
|
||||
|
||||
if (!isset($this->children[$ancestor])) {
|
||||
$this->children[$ancestor] = array($className);
|
||||
} else {
|
||||
@ -463,8 +472,6 @@ class ClassManifest
|
||||
|
||||
if ($implements) {
|
||||
foreach ($implements as $interface) {
|
||||
$interface = strtolower($interface);
|
||||
|
||||
if (!isset($this->implementors[$interface])) {
|
||||
$this->implementors[$interface] = array($className);
|
||||
} else {
|
||||
@ -480,6 +487,16 @@ class ClassManifest
|
||||
foreach ($traits as $traitName => $traitInfo) {
|
||||
$this->traits[strtolower($traitName)] = $pathname;
|
||||
}
|
||||
|
||||
// Save back to cache if configured
|
||||
if ($changed && $this->cache) {
|
||||
$cache = array(
|
||||
'classes' => $classes,
|
||||
'interfaces' => $interfaces,
|
||||
'traits' => $traits,
|
||||
);
|
||||
$this->cache->set($key, $cache);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,7 +30,7 @@ class ClassManifestVisitor extends NodeVisitorAbstract
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\Stmt\Class_) {
|
||||
$extends = '';
|
||||
$extends = [];
|
||||
$interfaces = [];
|
||||
|
||||
if ($node->extends) {
|
||||
|
@ -22,7 +22,7 @@ class ModuleLoader
|
||||
*/
|
||||
public static function inst()
|
||||
{
|
||||
return self::$instance ? self::$instance : self::$instance = new self();
|
||||
return self::$instance ? self::$instance : self::$instance = new static();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,4 +85,17 @@ class ModuleLoader
|
||||
{
|
||||
return count($this->manifests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the module loader
|
||||
*
|
||||
* @param bool $includeTests
|
||||
* @param bool $forceRegen
|
||||
*/
|
||||
public function init($includeTests = false, $forceRegen = false)
|
||||
{
|
||||
foreach ($this->manifests as $manifest) {
|
||||
$manifest->init($includeTests, $forceRegen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,11 +26,11 @@ class ModuleManifest
|
||||
protected $cacheKey;
|
||||
|
||||
/**
|
||||
* Whether `test` directories should be searched when searching for configuration
|
||||
* Factory to use to build cache
|
||||
*
|
||||
* @var bool
|
||||
* @var CacheFactory
|
||||
*/
|
||||
protected $includeTests;
|
||||
protected $cacheFactory;
|
||||
|
||||
/**
|
||||
* @var CacheInterface
|
||||
@ -87,19 +87,24 @@ class ModuleManifest
|
||||
* 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.
|
||||
* @param CacheFactory $cacheFactory Cache factory to use
|
||||
*/
|
||||
public function __construct($base, $includeTests = false, $forceRegen = false, CacheFactory $cacheFactory = null)
|
||||
public function __construct($base, CacheFactory $cacheFactory = null)
|
||||
{
|
||||
$this->base = $base;
|
||||
$this->cacheKey = sha1($base) . '_modules';
|
||||
$this->includeTests = $includeTests;
|
||||
$this->cacheFactory = $cacheFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $includeTests
|
||||
* @param bool $forceRegen Force the manifest to be regenerated.
|
||||
*/
|
||||
public function init($includeTests = false, $forceRegen = false)
|
||||
{
|
||||
// build cache from factory
|
||||
if ($cacheFactory) {
|
||||
$this->cache = $cacheFactory->create(
|
||||
if ($this->cacheFactory) {
|
||||
$this->cache = $this->cacheFactory->create(
|
||||
CacheInterface::class.'.modulemanifest',
|
||||
[ 'namespace' => 'modulemanifest' . ($includeTests ? '_tests' : '') ]
|
||||
);
|
||||
|
119
src/Core/Startup/ErrorControlChainMiddleware.php
Normal file
119
src/Core/Startup/ErrorControlChainMiddleware.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Startup;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\HTTPResponse_Exception;
|
||||
use SilverStripe\Core\Application;
|
||||
use SilverStripe\Control\HTTPMiddleware;
|
||||
use SilverStripe\Security\Permission;
|
||||
use SilverStripe\Security\Security;
|
||||
|
||||
/**
|
||||
* Decorates application bootstrapping with errorcontrolchain
|
||||
*/
|
||||
class ErrorControlChainMiddleware implements HTTPMiddleware
|
||||
{
|
||||
/**
|
||||
* @var Application
|
||||
*/
|
||||
protected $application = null;
|
||||
|
||||
/**
|
||||
* Build error control chain for an application
|
||||
*
|
||||
* @param Application $application
|
||||
*/
|
||||
public function __construct(Application $application)
|
||||
{
|
||||
$this->application = $application;
|
||||
}
|
||||
|
||||
public function process(HTTPRequest $request, callable $next)
|
||||
{
|
||||
$result = null;
|
||||
|
||||
// Prepare tokens and execute chain
|
||||
$reloadToken = ParameterConfirmationToken::prepare_tokens(
|
||||
['isTest', 'isDev', 'flush'],
|
||||
$request
|
||||
);
|
||||
$chain = new ErrorControlChain();
|
||||
$chain
|
||||
->then(function () use ($request, $chain, $reloadToken, $next, &$result) {
|
||||
// If no redirection is necessary then we can disable error supression
|
||||
if (!$reloadToken) {
|
||||
$chain->setSuppression(false);
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if a token is requesting a redirect
|
||||
if ($reloadToken) {
|
||||
$result = $this->safeReloadWithToken($request, $reloadToken);
|
||||
} else {
|
||||
// If no reload necessary, process application
|
||||
$result = call_user_func($next, $request);
|
||||
}
|
||||
} catch (HTTPResponse_Exception $exception) {
|
||||
$result = $exception->getResponse();
|
||||
}
|
||||
})
|
||||
// Finally if a token was requested but there was an error while figuring out if it's allowed, do it anyway
|
||||
->thenIfErrored(function () use ($reloadToken, &$result) {
|
||||
if ($reloadToken) {
|
||||
$result = $reloadToken->reloadWithToken();
|
||||
}
|
||||
})
|
||||
->execute();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload application with the given token, but only if either the user is authenticated,
|
||||
* or authentication is impossible.
|
||||
*
|
||||
* @param HTTPRequest $request
|
||||
* @param ParameterConfirmationToken $reloadToken
|
||||
* @return HTTPResponse
|
||||
*/
|
||||
protected function safeReloadWithToken(HTTPRequest $request, $reloadToken)
|
||||
{
|
||||
// Safe reload requires manual boot
|
||||
$this->getApplication()->getKernel()->boot(false);
|
||||
|
||||
// Ensure session is started
|
||||
$request->getSession()->init();
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Fail and redirect the user to the login page
|
||||
$loginPage = Director::absoluteURL(Security::config()->get('login_url'));
|
||||
$loginPage .= "?BackURL=" . urlencode($request->getURL());
|
||||
$result = new HTTPResponse();
|
||||
$result->redirect($loginPage);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Application
|
||||
*/
|
||||
public function getApplication()
|
||||
{
|
||||
return $this->application;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Application $application
|
||||
* @return $this
|
||||
*/
|
||||
public function setApplication(Application $application)
|
||||
{
|
||||
$this->application = $application;
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -2,7 +2,10 @@
|
||||
|
||||
namespace SilverStripe\Core\Startup;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Security\RandomGenerator;
|
||||
|
||||
/**
|
||||
@ -25,6 +28,11 @@ class ParameterConfirmationToken
|
||||
*/
|
||||
protected $parameterName = null;
|
||||
|
||||
/**
|
||||
* @var HTTPRequest
|
||||
*/
|
||||
protected $request = null;
|
||||
|
||||
/**
|
||||
* The parameter given
|
||||
*
|
||||
@ -88,17 +96,19 @@ class ParameterConfirmationToken
|
||||
* Create a new ParameterConfirmationToken
|
||||
*
|
||||
* @param string $parameterName Name of the querystring parameter to check
|
||||
* @param HTTPRequest $request
|
||||
*/
|
||||
public function __construct($parameterName)
|
||||
public function __construct($parameterName, HTTPRequest $request)
|
||||
{
|
||||
// Store the parameter name
|
||||
$this->parameterName = $parameterName;
|
||||
$this->request = $request;
|
||||
|
||||
// Store the parameter value
|
||||
$this->parameter = isset($_GET[$parameterName]) ? $_GET[$parameterName] : null;
|
||||
$this->parameter = $request->getVar($parameterName);
|
||||
|
||||
// If the token provided is valid, mark it as such
|
||||
$token = isset($_GET[$parameterName.'token']) ? $_GET[$parameterName.'token'] : null;
|
||||
$token = $request->getVar($parameterName.'token');
|
||||
if ($this->checkToken($token)) {
|
||||
$this->token = $token;
|
||||
}
|
||||
@ -151,7 +161,7 @@ class ParameterConfirmationToken
|
||||
*/
|
||||
public function suppress()
|
||||
{
|
||||
unset($_GET[$this->parameterName]);
|
||||
$this->request->offsetUnset($this->parameterName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -167,81 +177,45 @@ class ParameterConfirmationToken
|
||||
);
|
||||
}
|
||||
|
||||
/** What to use instead of BASE_URL. Must not contain protocol or host. @var string */
|
||||
static public $alternateBaseURL = null;
|
||||
|
||||
protected function currentAbsoluteURL()
|
||||
/**
|
||||
* Get redirect url, excluding querystring
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function currentURL()
|
||||
{
|
||||
global $url;
|
||||
|
||||
// Are we http or https? Replicates Director::is_https() without its dependencies/
|
||||
$proto = 'http';
|
||||
// See https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
|
||||
// See https://support.microsoft.com/en-us/kb/307347
|
||||
$headerOverride = false;
|
||||
if (TRUSTED_PROXY) {
|
||||
$headers = (getenv('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) ? array(getenv('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) : null;
|
||||
if (!$headers) {
|
||||
// Backwards compatible defaults
|
||||
$headers = array('HTTP_X_FORWARDED_PROTO', 'HTTP_X_FORWARDED_PROTOCOL', 'HTTP_FRONT_END_HTTPS');
|
||||
}
|
||||
foreach ($headers as $header) {
|
||||
$headerCompareVal = ($header === 'HTTP_FRONT_END_HTTPS' ? 'on' : 'https');
|
||||
if (!empty($_SERVER[$header]) && strtolower($_SERVER[$header]) == $headerCompareVal) {
|
||||
$headerOverride = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($headerOverride) {
|
||||
$proto = 'https';
|
||||
} elseif ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) {
|
||||
$proto = 'https';
|
||||
} elseif (isset($_SERVER['SSL'])) {
|
||||
$proto = 'https';
|
||||
}
|
||||
|
||||
$parts = array_filter(array(
|
||||
// What's our host
|
||||
Director::host(),
|
||||
// SilverStripe base
|
||||
self::$alternateBaseURL !== null ? self::$alternateBaseURL : BASE_URL,
|
||||
// And URL including base script (eg: if it's index.php/page/url/)
|
||||
(defined('BASE_SCRIPT_URL') ? '/' . BASE_SCRIPT_URL : '') . $url,
|
||||
));
|
||||
|
||||
// Join together with protocol into our current absolute URL, avoiding duplicated "/" characters
|
||||
return "$proto://" . preg_replace('#/{2,}#', '/', implode('/', $parts));
|
||||
return Controller::join_links(
|
||||
BASE_URL,
|
||||
'/',
|
||||
$this->request->getURL(false)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces a reload of the request with the token included
|
||||
* This method will terminate the script with `die`
|
||||
*
|
||||
* @return HTTPResponse
|
||||
*/
|
||||
public function reloadWithToken()
|
||||
{
|
||||
$location = $this->currentAbsoluteURL();
|
||||
// Merge get params with current url
|
||||
$params = array_merge($this->request->getVars(), $this->params());
|
||||
$location = Controller::join_links(
|
||||
$this->currentURL(),
|
||||
'?'.http_build_query($params)
|
||||
);
|
||||
$locationJS = Convert::raw2js($location);
|
||||
$locationATT = Convert::raw2att($location);
|
||||
$body = <<<HTML
|
||||
<script>location.href='$locationJS';</script>
|
||||
<noscript><meta http-equiv="refresh" content="0; url=$locationATT"></noscript>
|
||||
You are being redirected. If you are not redirected soon, <a href="$locationATT">click here to continue the flush</a>
|
||||
HTML;
|
||||
|
||||
// What's our GET params (ensuring they include the original parameter + a new token)
|
||||
$params = array_merge($_GET, $this->params());
|
||||
unset($params['url']);
|
||||
|
||||
if ($params) {
|
||||
$location .= '?'.http_build_query($params);
|
||||
}
|
||||
|
||||
// And redirect
|
||||
if (headers_sent()) {
|
||||
echo "
|
||||
<script>location.href='$location';</script>
|
||||
<noscript><meta http-equiv='refresh' content='0; url=$location'></noscript>
|
||||
You are being redirected. If you are not redirected soon, <a href='$location'>click here to continue the flush</a>
|
||||
";
|
||||
} else {
|
||||
header('location: '.$location, true, 302);
|
||||
}
|
||||
die;
|
||||
// Build response
|
||||
$result = new HTTPResponse($body);
|
||||
$result->redirect($location);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -249,13 +223,14 @@ You are being redirected. If you are not redirected soon, <a href='$location'>cl
|
||||
* return the non-validated token with the highest priority
|
||||
*
|
||||
* @param array $keys List of token keys in ascending priority (low to high)
|
||||
* @param HTTPRequest $request
|
||||
* @return ParameterConfirmationToken The token container for the unvalidated $key given with the highest priority
|
||||
*/
|
||||
public static function prepare_tokens($keys)
|
||||
public static function prepare_tokens($keys, HTTPRequest $request)
|
||||
{
|
||||
$target = null;
|
||||
foreach ($keys as $key) {
|
||||
$token = new ParameterConfirmationToken($key);
|
||||
$token = new ParameterConfirmationToken($key, $request);
|
||||
// Validate this token
|
||||
if ($token->reloadRequired()) {
|
||||
$token->suppress();
|
||||
|
117
src/Core/TempFolder.php
Normal file
117
src/Core/TempFolder.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Guesses location for temp folder
|
||||
*/
|
||||
class TempFolder
|
||||
{
|
||||
/**
|
||||
* Returns the temporary folder path that silverstripe should use for its cache files.
|
||||
*
|
||||
* @param string $base The base path to use for determining the temporary path
|
||||
* @return string Path to temp
|
||||
*/
|
||||
public static function getTempFolder($base)
|
||||
{
|
||||
$parent = static::getTempParentFolder($base);
|
||||
|
||||
// The actual temp folder is a subfolder of getTempParentFolder(), named by username
|
||||
$subfolder = $parent . DIRECTORY_SEPARATOR . static::getTempFolderUsername();
|
||||
|
||||
if (!@file_exists($subfolder)) {
|
||||
mkdir($subfolder);
|
||||
}
|
||||
|
||||
return $subfolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns as best a representation of the current username as we can glean.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTempFolderUsername()
|
||||
{
|
||||
$user = getenv('APACHE_RUN_USER');
|
||||
if (!$user) {
|
||||
$user = getenv('USER');
|
||||
}
|
||||
if (!$user) {
|
||||
$user = getenv('USERNAME');
|
||||
}
|
||||
if (!$user && function_exists('posix_getpwuid') && function_exists('posix_getuid')) {
|
||||
$userDetails = posix_getpwuid(posix_getuid());
|
||||
$user = $userDetails['name'];
|
||||
}
|
||||
if (!$user) {
|
||||
$user = 'unknown';
|
||||
}
|
||||
$user = preg_replace('/[^A-Za-z0-9_\-]/', '', $user);
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parent folder of the temp folder.
|
||||
* The temp folder will be a subfolder of this, named by username.
|
||||
* This structure prevents permission problems.
|
||||
*
|
||||
* @param string $base
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static function getTempParentFolder($base)
|
||||
{
|
||||
// first, try finding a silverstripe-cache dir built off the base path
|
||||
$tempPath = $base . DIRECTORY_SEPARATOR . 'silverstripe-cache';
|
||||
if (@file_exists($tempPath)) {
|
||||
if ((fileperms($tempPath) & 0777) != 0777) {
|
||||
@chmod($tempPath, 0777);
|
||||
}
|
||||
return $tempPath;
|
||||
}
|
||||
|
||||
// failing the above, try finding a namespaced silverstripe-cache dir in the system temp
|
||||
$tempPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR .
|
||||
'silverstripe-cache-php' . preg_replace('/[^\w-\.+]+/', '-', PHP_VERSION) .
|
||||
str_replace(array(' ', '/', ':', '\\'), '-', $base);
|
||||
if (!@file_exists($tempPath)) {
|
||||
$oldUMask = umask(0);
|
||||
@mkdir($tempPath, 0777);
|
||||
umask($oldUMask);
|
||||
|
||||
// if the folder already exists, correct perms
|
||||
} else {
|
||||
if ((fileperms($tempPath) & 0777) != 0777) {
|
||||
@chmod($tempPath, 0777);
|
||||
}
|
||||
}
|
||||
|
||||
$worked = @file_exists($tempPath) && @is_writable($tempPath);
|
||||
|
||||
// failing to use the system path, attempt to create a local silverstripe-cache dir
|
||||
if (!$worked) {
|
||||
$tempPath = $base . DIRECTORY_SEPARATOR . 'silverstripe-cache';
|
||||
if (!@file_exists($tempPath)) {
|
||||
$oldUMask = umask(0);
|
||||
@mkdir($tempPath, 0777);
|
||||
umask($oldUMask);
|
||||
}
|
||||
|
||||
$worked = @file_exists($tempPath) && @is_writable($tempPath);
|
||||
}
|
||||
|
||||
if (!$worked) {
|
||||
throw new Exception(
|
||||
'Permission problem gaining access to a temp folder. ' .
|
||||
'Please create a folder named silverstripe-cache in the base folder ' .
|
||||
'of the installation and ensure it has the correct permissions'
|
||||
);
|
||||
}
|
||||
|
||||
return $tempPath;
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Returns the temporary folder path that silverstripe should use for its cache files.
|
||||
*
|
||||
* @param string $base The base path to use for determining the temporary path
|
||||
* @return string Path to temp
|
||||
*/
|
||||
function getTempFolder($base = null)
|
||||
{
|
||||
$parent = getTempParentFolder($base);
|
||||
|
||||
// The actual temp folder is a subfolder of getTempParentFolder(), named by username
|
||||
$subfolder = $parent . DIRECTORY_SEPARATOR . getTempFolderUsername();
|
||||
|
||||
if (!@file_exists($subfolder)) {
|
||||
mkdir($subfolder);
|
||||
}
|
||||
|
||||
return $subfolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns as best a representation of the current username as we can glean.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getTempFolderUsername()
|
||||
{
|
||||
$user = getenv('APACHE_RUN_USER');
|
||||
if (!$user) {
|
||||
$user = getenv('USER');
|
||||
}
|
||||
if (!$user) {
|
||||
$user = getenv('USERNAME');
|
||||
}
|
||||
if (!$user && function_exists('posix_getpwuid') && function_exists('posix_getuid')) {
|
||||
$userDetails = posix_getpwuid(posix_getuid());
|
||||
$user = $userDetails['name'];
|
||||
}
|
||||
if (!$user) {
|
||||
$user = 'unknown';
|
||||
}
|
||||
$user = preg_replace('/[^A-Za-z0-9_\-]/', '', $user);
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parent folder of the temp folder.
|
||||
* The temp folder will be a subfolder of this, named by username.
|
||||
* This structure prevents permission problems.
|
||||
*
|
||||
* @param string $base
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
function getTempParentFolder($base = null)
|
||||
{
|
||||
if (!$base && defined('BASE_PATH')) {
|
||||
$base = BASE_PATH;
|
||||
}
|
||||
|
||||
// first, try finding a silverstripe-cache dir built off the base path
|
||||
$tempPath = $base . DIRECTORY_SEPARATOR . 'silverstripe-cache';
|
||||
if (@file_exists($tempPath)) {
|
||||
if ((fileperms($tempPath) & 0777) != 0777) {
|
||||
@chmod($tempPath, 0777);
|
||||
}
|
||||
return $tempPath;
|
||||
}
|
||||
|
||||
// failing the above, try finding a namespaced silverstripe-cache dir in the system temp
|
||||
$tempPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR .
|
||||
'silverstripe-cache-php' . preg_replace('/[^\w-\.+]+/', '-', PHP_VERSION) .
|
||||
str_replace(array(' ', '/', ':', '\\'), '-', $base);
|
||||
if (!@file_exists($tempPath)) {
|
||||
$oldUMask = umask(0);
|
||||
@mkdir($tempPath, 0777);
|
||||
umask($oldUMask);
|
||||
|
||||
// if the folder already exists, correct perms
|
||||
} else {
|
||||
if ((fileperms($tempPath) & 0777) != 0777) {
|
||||
@chmod($tempPath, 0777);
|
||||
}
|
||||
}
|
||||
|
||||
$worked = @file_exists($tempPath) && @is_writable($tempPath);
|
||||
|
||||
// failing to use the system path, attempt to create a local silverstripe-cache dir
|
||||
if (!$worked) {
|
||||
$tempPath = $base . DIRECTORY_SEPARATOR . 'silverstripe-cache';
|
||||
if (!@file_exists($tempPath)) {
|
||||
$oldUMask = umask(0);
|
||||
@mkdir($tempPath, 0777);
|
||||
umask($oldUMask);
|
||||
}
|
||||
|
||||
$worked = @file_exists($tempPath) && @is_writable($tempPath);
|
||||
}
|
||||
|
||||
if (!$worked) {
|
||||
throw new Exception(
|
||||
'Permission problem gaining access to a temp folder. ' .
|
||||
'Please create a folder named silverstripe-cache in the base folder ' .
|
||||
'of the installation and ensure it has the correct permissions'
|
||||
);
|
||||
}
|
||||
|
||||
return $tempPath;
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace SilverStripe\Dev;
|
||||
|
||||
use SilverStripe\Core\Environment;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\View\ViewableData;
|
||||
|
||||
@ -142,8 +143,8 @@ abstract class BulkLoader extends ViewableData
|
||||
*/
|
||||
public function load($filepath)
|
||||
{
|
||||
increase_time_limit_to(3600);
|
||||
increase_memory_limit_to('512M');
|
||||
Environment::increaseTimeLimitTo(3600);
|
||||
Environment::increaseMemoryLimitTo('512M');
|
||||
|
||||
//get all instances of the to be imported data object
|
||||
if ($this->deleteExistingRecords) {
|
||||
|
@ -69,7 +69,7 @@ class CSSContentParser
|
||||
* See {@link getByXpath()} for a more direct selector syntax.
|
||||
*
|
||||
* @param String $selector
|
||||
* @return SimpleXMLElement
|
||||
* @return SimpleXMLElement[]
|
||||
*/
|
||||
public function getBySelector($selector)
|
||||
{
|
||||
@ -81,7 +81,7 @@ class CSSContentParser
|
||||
* Allows querying the content through XPATH selectors.
|
||||
*
|
||||
* @param String $xpath SimpleXML compatible XPATH statement
|
||||
* @return SimpleXMLElement|false
|
||||
* @return SimpleXMLElement[]
|
||||
*/
|
||||
public function getByXpath($xpath)
|
||||
{
|
||||
|
@ -21,7 +21,7 @@ class DevBuildController extends Controller
|
||||
{
|
||||
if (Director::is_cli()) {
|
||||
$da = DatabaseAdmin::create();
|
||||
return $da->handleRequest($request, $this->model);
|
||||
return $da->handleRequest($request);
|
||||
} else {
|
||||
$renderer = DebugView::create();
|
||||
echo $renderer->renderHeader();
|
||||
@ -29,7 +29,7 @@ class DevBuildController extends Controller
|
||||
echo "<div class=\"build\">";
|
||||
|
||||
$da = DatabaseAdmin::create();
|
||||
$response = $da->handleRequest($request, $this->model);
|
||||
$response = $da->handleRequest($request);
|
||||
|
||||
echo "</div>";
|
||||
echo $renderer->renderFooter();
|
||||
|
@ -42,6 +42,16 @@ class DevelopmentAdmin extends Controller
|
||||
'generatesecuretoken',
|
||||
);
|
||||
|
||||
/**
|
||||
* Assume that CLI equals admin permissions
|
||||
* If set to false, normal permission model will apply even in CLI mode
|
||||
* Applies to all development admin tasks (E.g. TaskRunner, DatabaseAdmin)
|
||||
*
|
||||
* @config
|
||||
* @var bool
|
||||
*/
|
||||
private static $allow_all_cli = true;
|
||||
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
@ -52,10 +62,11 @@ class DevelopmentAdmin extends Controller
|
||||
|
||||
// We allow access to this controller regardless of live-status or ADMIN permission only
|
||||
// if on CLI. Access to this controller is always allowed in "dev-mode", or of the user is ADMIN.
|
||||
$allowAllCLI = static::config()->get('allow_all_cli');
|
||||
$canAccess = (
|
||||
$requestedDevBuild
|
||||
|| Director::isDev()
|
||||
|| Director::is_cli()
|
||||
|| (Director::is_cli() && $allowAllCLI)
|
||||
// Its important that we don't run this check if dev/build was requested
|
||||
|| Permission::check("ADMIN")
|
||||
);
|
||||
|
@ -2,13 +2,13 @@
|
||||
|
||||
namespace SilverStripe\Dev;
|
||||
|
||||
use SilverStripe\Assets\File;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use InvalidArgumentException;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Assets\File;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\DB;
|
||||
|
||||
/**
|
||||
* A blueprint on how to create instances of a certain {@link DataObject} subclass.
|
||||
@ -94,7 +94,7 @@ class FixtureBlueprint
|
||||
try {
|
||||
$class = $this->class;
|
||||
$schema = DataObject::getSchema();
|
||||
$obj = DataModel::inst()->$class->newObject();
|
||||
$obj = Injector::inst()->create($class);
|
||||
|
||||
// If an ID is explicitly passed, then we'll sort out the initial write straight away
|
||||
// This is just in case field setters triggered by the population code in the next block
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace SilverStripe\Dev;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
@ -33,7 +34,7 @@ use SimpleXMLElement;
|
||||
* }
|
||||
* </code>
|
||||
*/
|
||||
class FunctionalTest extends SapphireTest
|
||||
class FunctionalTest extends SapphireTest implements TestOnly
|
||||
{
|
||||
/**
|
||||
* Set this to true on your sub-class to disable the use of themes in this test.
|
||||
@ -84,12 +85,13 @@ class FunctionalTest extends SapphireTest
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Skip calling FunctionalTest directly.
|
||||
if (static::class == __CLASS__) {
|
||||
$this->markTestSkipped(sprintf('Skipping %s ', static::class));
|
||||
}
|
||||
|
||||
parent::setUp();
|
||||
$this->mainSession = new TestSession();
|
||||
|
||||
// Disable theme, if necessary
|
||||
@ -114,9 +116,8 @@ class FunctionalTest extends SapphireTest
|
||||
protected function tearDown()
|
||||
{
|
||||
SecurityToken::enable();
|
||||
|
||||
parent::tearDown();
|
||||
unset($this->mainSession);
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -408,11 +409,11 @@ class FunctionalTest extends SapphireTest
|
||||
public function useDraftSite($enabled = true)
|
||||
{
|
||||
if ($enabled) {
|
||||
$this->session()->inst_set('readingMode', 'Stage.Stage');
|
||||
$this->session()->inst_set('unsecuredDraftSite', true);
|
||||
$this->session()->set('readingMode', 'Stage.Stage');
|
||||
$this->session()->set('unsecuredDraftSite', true);
|
||||
} else {
|
||||
$this->session()->inst_set('readingMode', 'Stage.Live');
|
||||
$this->session()->inst_set('unsecuredDraftSite', false);
|
||||
$this->session()->set('readingMode', 'Stage.Live');
|
||||
$this->session()->set('unsecuredDraftSite', false);
|
||||
}
|
||||
}
|
||||
|
||||
|
1131
src/Dev/Install/InstallRequirements.php
Normal file
1131
src/Dev/Install/InstallRequirements.php
Normal file
@ -0,0 +1,1131 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Dev\Install;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Core\TempFolder;
|
||||
|
||||
/**
|
||||
* This class checks requirements
|
||||
* Each of the requireXXX functions takes an argument which gives a user description of the test.
|
||||
* It's an array of 3 parts:
|
||||
* $description[0] - The test catetgory
|
||||
* $description[1] - The test title
|
||||
* $description[2] - The test error to show, if it goes wrong
|
||||
*/
|
||||
class InstallRequirements
|
||||
{
|
||||
protected $errors = [];
|
||||
protected $warnings = [];
|
||||
protected $tests = [];
|
||||
|
||||
/**
|
||||
* Backup of original ini settings
|
||||
* @var array
|
||||
*/
|
||||
protected $originalIni = [];
|
||||
|
||||
/**
|
||||
* Check the database configuration. These are done one after another
|
||||
* starting with checking the database function exists in PHP, and
|
||||
* continuing onto more difficult checks like database permissions.
|
||||
*
|
||||
* @param array $databaseConfig The list of database parameters
|
||||
* @return boolean Validity of database configuration details
|
||||
*/
|
||||
public function checkDatabase($databaseConfig)
|
||||
{
|
||||
// Check if support is available
|
||||
if (!$this->requireDatabaseFunctions(
|
||||
$databaseConfig,
|
||||
array(
|
||||
"Database Configuration",
|
||||
"Database support",
|
||||
"Database support in PHP",
|
||||
$this->getDatabaseTypeNice($databaseConfig['type'])
|
||||
)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the server is available
|
||||
$usePath = !empty($databaseConfig['path']) && empty($databaseConfig['server']);
|
||||
if (!$this->requireDatabaseServer(
|
||||
$databaseConfig,
|
||||
array(
|
||||
"Database Configuration",
|
||||
"Database server",
|
||||
$usePath
|
||||
? "I couldn't write to path '$databaseConfig[path]'"
|
||||
: "I couldn't find a database server on '$databaseConfig[server]'",
|
||||
$usePath ? $databaseConfig['path'] : $databaseConfig['server']
|
||||
)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the connection credentials allow access to the server / database
|
||||
if (!$this->requireDatabaseConnection(
|
||||
$databaseConfig,
|
||||
array(
|
||||
"Database Configuration",
|
||||
"Database access credentials",
|
||||
"That username/password doesn't work"
|
||||
)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the necessary server version is available
|
||||
if (!$this->requireDatabaseVersion(
|
||||
$databaseConfig,
|
||||
array(
|
||||
"Database Configuration",
|
||||
"Database server version requirement",
|
||||
'',
|
||||
'Version ' . $this->getDatabaseConfigurationHelper($databaseConfig['type'])->getDatabaseVersion($databaseConfig)
|
||||
)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that database creation permissions are available
|
||||
if (!$this->requireDatabaseOrCreatePermissions(
|
||||
$databaseConfig,
|
||||
array(
|
||||
"Database Configuration",
|
||||
"Can I access/create the database",
|
||||
"I can't create new databases and the database '$databaseConfig[database]' doesn't exist"
|
||||
)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check alter permission (necessary to create tables etc)
|
||||
if (!$this->requireDatabaseAlterPermissions(
|
||||
$databaseConfig,
|
||||
array(
|
||||
"Database Configuration",
|
||||
"Can I ALTER tables",
|
||||
"I don't have permission to ALTER tables"
|
||||
)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Success!
|
||||
return true;
|
||||
}
|
||||
|
||||
public function checkAdminConfig($adminConfig)
|
||||
{
|
||||
if (!$adminConfig['username']) {
|
||||
$this->error(array('', 'Please enter a username!'));
|
||||
}
|
||||
if (!$adminConfig['password']) {
|
||||
$this->error(array('', 'Please enter a password!'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the web server is IIS and version greater than the given version.
|
||||
*
|
||||
* @param int $fromVersion
|
||||
* @return bool
|
||||
*/
|
||||
public function isIIS($fromVersion = 7)
|
||||
{
|
||||
if (strpos($this->findWebserver(), 'IIS/') === false) {
|
||||
return false;
|
||||
}
|
||||
return substr(strstr($this->findWebserver(), '/'), -3, 1) >= $fromVersion;
|
||||
}
|
||||
|
||||
public function isApache()
|
||||
{
|
||||
if (strpos($this->findWebserver(), 'Apache') !== false) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the webserver software running on the PHP host.
|
||||
* @return string|boolean Server software or boolean FALSE
|
||||
*/
|
||||
public function findWebserver()
|
||||
{
|
||||
// Try finding from SERVER_SIGNATURE or SERVER_SOFTWARE
|
||||
if (!empty($_SERVER['SERVER_SIGNATURE'])) {
|
||||
$webserver = $_SERVER['SERVER_SIGNATURE'];
|
||||
} elseif (!empty($_SERVER['SERVER_SOFTWARE'])) {
|
||||
$webserver = $_SERVER['SERVER_SOFTWARE'];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return strip_tags(trim($webserver));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check everything except the database
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
$this->errors = [];
|
||||
$isApache = $this->isApache();
|
||||
$isIIS = $this->isIIS();
|
||||
$webserver = $this->findWebserver();
|
||||
|
||||
$this->requirePHPVersion('5.5.0', '5.5.0', array(
|
||||
"PHP Configuration",
|
||||
"PHP5 installed",
|
||||
null,
|
||||
"PHP version " . phpversion()
|
||||
));
|
||||
|
||||
// Check that we can identify the root folder successfully
|
||||
$this->requireFile('framework/src/Dev/Install/config-form.html', array(
|
||||
"File permissions",
|
||||
"Does the webserver know where files are stored?",
|
||||
"The webserver isn't letting me identify where files are stored.",
|
||||
$this->getBaseDir()
|
||||
));
|
||||
|
||||
$this->requireModule('mysite', array(
|
||||
"File permissions",
|
||||
"mysite/ directory exists?",
|
||||
''
|
||||
));
|
||||
$this->requireModule('framework', array(
|
||||
"File permissions",
|
||||
"framework/ directory exists?",
|
||||
'',
|
||||
));
|
||||
|
||||
if ($isApache) {
|
||||
$this->checkApacheVersion(array(
|
||||
"Webserver Configuration",
|
||||
"Webserver is not Apache 1.x",
|
||||
"SilverStripe requires Apache version 2 or greater",
|
||||
$webserver
|
||||
));
|
||||
$this->requireWriteable('.htaccess', array("File permissions", "Is the .htaccess file writeable?", null));
|
||||
} elseif ($isIIS) {
|
||||
$this->requireWriteable('web.config', array("File permissions", "Is the web.config file writeable?", null));
|
||||
}
|
||||
|
||||
$this->requireWriteable('mysite/_config.php', array(
|
||||
"File permissions",
|
||||
"Is the mysite/_config.php file writeable?",
|
||||
null
|
||||
));
|
||||
|
||||
$this->requireWriteable('mysite/_config/config.yml', array(
|
||||
"File permissions",
|
||||
"Is the mysite/_config/config.yml file writeable?",
|
||||
null
|
||||
));
|
||||
|
||||
if (!$this->checkModuleExists('cms')) {
|
||||
$this->requireWriteable('mysite/code/RootURLController.php', array(
|
||||
"File permissions",
|
||||
"Is the mysite/code/RootURLController.php file writeable?",
|
||||
null
|
||||
));
|
||||
}
|
||||
$this->requireWriteable('assets', array("File permissions", "Is the assets/ directory writeable?", null));
|
||||
|
||||
try {
|
||||
$tempFolder = TempFolder::getTempFolder(BASE_PATH);
|
||||
} catch (Exception $e) {
|
||||
$tempFolder = false;
|
||||
}
|
||||
|
||||
$this->requireTempFolder(array('File permissions', 'Is a temporary directory available?', null, $tempFolder));
|
||||
if ($tempFolder) {
|
||||
// in addition to the temp folder being available, check it is writable
|
||||
$this->requireWriteable($tempFolder, array(
|
||||
"File permissions",
|
||||
sprintf("Is the temporary directory writeable?", $tempFolder),
|
||||
null
|
||||
), true);
|
||||
}
|
||||
|
||||
// Check for web server, unless we're calling the installer from the command-line
|
||||
$this->isRunningWebServer(array("Webserver Configuration", "Server software", "Unknown", $webserver));
|
||||
|
||||
if ($isApache) {
|
||||
$this->requireApacheRewriteModule('mod_rewrite', array(
|
||||
"Webserver Configuration",
|
||||
"URL rewriting support",
|
||||
"You need mod_rewrite to use friendly URLs with SilverStripe, but it is not enabled."
|
||||
));
|
||||
} elseif ($isIIS) {
|
||||
$this->requireIISRewriteModule('IIS_UrlRewriteModule', array(
|
||||
"Webserver Configuration",
|
||||
"URL rewriting support",
|
||||
"You need to enable the IIS URL Rewrite Module to use friendly URLs with SilverStripe, "
|
||||
. "but it is not installed or enabled. Download it for IIS 7 from http://www.iis.net/expand/URLRewrite"
|
||||
));
|
||||
} else {
|
||||
$this->warning(array(
|
||||
"Webserver Configuration",
|
||||
"URL rewriting support",
|
||||
"I can't tell whether any rewriting module is running. You may need to configure a rewriting rule yourself."
|
||||
));
|
||||
}
|
||||
|
||||
$this->requireServerVariables(array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'), array(
|
||||
"Webserver Configuration",
|
||||
"Recognised webserver",
|
||||
"You seem to be using an unsupported webserver. "
|
||||
. "The server variables SCRIPT_NAME, HTTP_HOST, SCRIPT_FILENAME need to be set."
|
||||
));
|
||||
|
||||
$this->requirePostSupport(array(
|
||||
"Webserver Configuration",
|
||||
"POST Support",
|
||||
'I can\'t find $_POST, make sure POST is enabled.'
|
||||
));
|
||||
|
||||
// Check for GD support
|
||||
if (!$this->requireFunction("imagecreatetruecolor", array(
|
||||
"PHP Configuration",
|
||||
"GD2 support",
|
||||
"PHP must have GD version 2."
|
||||
))
|
||||
) {
|
||||
$this->requireFunction("imagecreate", array(
|
||||
"PHP Configuration",
|
||||
"GD2 support",
|
||||
"GD support for PHP not included."
|
||||
));
|
||||
}
|
||||
|
||||
// Check for XML support
|
||||
$this->requireFunction('xml_set_object', array(
|
||||
"PHP Configuration",
|
||||
"XML support",
|
||||
"XML support not included in PHP."
|
||||
));
|
||||
$this->requireClass('DOMDocument', array(
|
||||
"PHP Configuration",
|
||||
"DOM/XML support",
|
||||
"DOM/XML support not included in PHP."
|
||||
));
|
||||
$this->requireFunction('simplexml_load_file', array(
|
||||
'PHP Configuration',
|
||||
'SimpleXML support',
|
||||
'SimpleXML support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for token_get_all
|
||||
$this->requireFunction('token_get_all', array(
|
||||
"PHP Configuration",
|
||||
"Tokenizer support",
|
||||
"Tokenizer support not included in PHP."
|
||||
));
|
||||
|
||||
// Check for CType support
|
||||
$this->requireFunction('ctype_digit', array(
|
||||
'PHP Configuration',
|
||||
'CType support',
|
||||
'CType support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for session support
|
||||
$this->requireFunction('session_start', array(
|
||||
'PHP Configuration',
|
||||
'Session support',
|
||||
'Session support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for iconv support
|
||||
$this->requireFunction('iconv', array(
|
||||
'PHP Configuration',
|
||||
'iconv support',
|
||||
'iconv support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for hash support
|
||||
$this->requireFunction('hash', array('PHP Configuration', 'hash support', 'hash support not included in PHP.'));
|
||||
|
||||
// Check for mbstring support
|
||||
$this->requireFunction('mb_internal_encoding', array(
|
||||
'PHP Configuration',
|
||||
'mbstring support',
|
||||
'mbstring support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for Reflection support
|
||||
$this->requireClass('ReflectionClass', array(
|
||||
'PHP Configuration',
|
||||
'Reflection support',
|
||||
'Reflection support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for Standard PHP Library (SPL) support
|
||||
$this->requireFunction('spl_classes', array(
|
||||
'PHP Configuration',
|
||||
'SPL support',
|
||||
'Standard PHP Library (SPL) not included in PHP.'
|
||||
));
|
||||
|
||||
$this->requireDateTimezone(array(
|
||||
'PHP Configuration',
|
||||
'date.timezone setting and validity',
|
||||
'date.timezone option in php.ini must be set correctly.',
|
||||
$this->getOriginalIni('date.timezone')
|
||||
));
|
||||
|
||||
$this->suggestClass('finfo', array(
|
||||
'PHP Configuration',
|
||||
'fileinfo support',
|
||||
'fileinfo should be enabled in PHP. SilverStripe uses it for MIME type detection of files. '
|
||||
. 'SilverStripe will still operate, but email attachments and sending files to browser '
|
||||
. '(e.g. export data to CSV) may not work correctly without finfo.'
|
||||
));
|
||||
|
||||
$this->suggestFunction('curl_init', array(
|
||||
'PHP Configuration',
|
||||
'curl support',
|
||||
'curl should be enabled in PHP. SilverStripe uses it for consuming web services'
|
||||
. ' via the RestfulService class and many modules rely on it.'
|
||||
));
|
||||
|
||||
$this->suggestClass('tidy', array(
|
||||
'PHP Configuration',
|
||||
'tidy support',
|
||||
'Tidy provides a library of code to clean up your html. '
|
||||
. 'SilverStripe will operate fine without tidy but HTMLCleaner will not be effective.'
|
||||
));
|
||||
|
||||
$this->suggestPHPSetting('asp_tags', array(false), array(
|
||||
'PHP Configuration',
|
||||
'asp_tags option',
|
||||
'This should be turned off as it can cause issues with SilverStripe'
|
||||
));
|
||||
$this->requirePHPSetting('magic_quotes_gpc', array(false), array(
|
||||
'PHP Configuration',
|
||||
'magic_quotes_gpc option',
|
||||
'This should be turned off, as it can cause issues with cookies. '
|
||||
. 'More specifically, unserializing data stored in cookies.'
|
||||
));
|
||||
$this->suggestPHPSetting('display_errors', array(false), array(
|
||||
'PHP Configuration',
|
||||
'display_errors option',
|
||||
'Unless you\'re in a development environment, this should be turned off, '
|
||||
. 'as it can expose sensitive data to website users.'
|
||||
));
|
||||
// on some weirdly configured webservers arg_separator.output is set to &
|
||||
// which will results in links like ?param=value&foo=bar which will not be i
|
||||
$this->suggestPHPSetting('arg_separator.output', array('&', ''), array(
|
||||
'PHP Configuration',
|
||||
'arg_separator.output option',
|
||||
'This option defines how URL parameters are concatenated. '
|
||||
. 'If not set to \'&\' this may cause issues with URL GET parameters'
|
||||
));
|
||||
|
||||
// always_populate_raw_post_data should be set to -1 if PHP < 7.0
|
||||
if (version_compare(PHP_VERSION, '7.0.0', '<')) {
|
||||
$this->suggestPHPSetting('always_populate_raw_post_data', ['-1'], [
|
||||
'PHP Configuration',
|
||||
'always_populate_raw_post_data option',
|
||||
'It\'s highly recommended to set this to \'-1\' in php 5.x, as $HTTP_RAW_POST_DATA is removed in php 7'
|
||||
]);
|
||||
}
|
||||
|
||||
// Check memory allocation
|
||||
$this->requireMemory(32 * 1024 * 1024, 64 * 1024 * 1024, array(
|
||||
"PHP Configuration",
|
||||
"Memory allocation (PHP config option 'memory_limit')",
|
||||
"SilverStripe needs a minimum of 32M allocated to PHP, but recommends 64M.",
|
||||
$this->getOriginalIni("memory_limit")
|
||||
));
|
||||
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ini setting
|
||||
*
|
||||
* @param string $settingName
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getOriginalIni($settingName)
|
||||
{
|
||||
if (isset($this->originalIni[$settingName])) {
|
||||
return $this->originalIni[$settingName];
|
||||
}
|
||||
return ini_get($settingName);
|
||||
}
|
||||
|
||||
public function suggestPHPSetting($settingName, $settingValues, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
|
||||
// special case for display_errors, check the original value before
|
||||
// it was changed at the start of this script.
|
||||
$val = $this->getOriginalIni($settingName);
|
||||
|
||||
if (!in_array($val, $settingValues) && $val != $settingValues) {
|
||||
$this->warning($testDetails, "$settingName is set to '$val' in php.ini. $testDetails[2]");
|
||||
}
|
||||
}
|
||||
|
||||
public function requirePHPSetting($settingName, $settingValues, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
|
||||
$val = $this->getOriginalIni($settingName);
|
||||
if (!in_array($val, $settingValues) && $val != $settingValues) {
|
||||
$this->error($testDetails, "$settingName is set to '$val' in php.ini. $testDetails[2]");
|
||||
}
|
||||
}
|
||||
|
||||
public function suggestClass($class, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
|
||||
if (!class_exists($class)) {
|
||||
$this->warning($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public function suggestFunction($class, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
|
||||
if (!function_exists($class)) {
|
||||
$this->warning($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public function requireDateTimezone($testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
$val = $this->getOriginalIni('date.timezone');
|
||||
$result = $val && in_array($val, timezone_identifiers_list());
|
||||
if (!$result) {
|
||||
$this->error($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public function requireMemory($min, $recommended, $testDetails)
|
||||
{
|
||||
$_SESSION['forcemem'] = false;
|
||||
|
||||
$mem = $this->getPHPMemory();
|
||||
$memLimit = $this->getOriginalIni("memory_limit");
|
||||
if ($mem < (64 * 1024 * 1024)) {
|
||||
ini_set('memory_limit', '64M');
|
||||
$mem = $this->getPHPMemory();
|
||||
$testDetails[3] = $memLimit;
|
||||
}
|
||||
|
||||
$this->testing($testDetails);
|
||||
|
||||
if ($mem < $min && $mem > 0) {
|
||||
$message = $testDetails[2] . " You only have " . $memLimit . " allocated";
|
||||
$this->error($testDetails, $message);
|
||||
return false;
|
||||
} elseif ($mem < $recommended && $mem > 0) {
|
||||
$message = $testDetails[2] . " You only have " . $memLimit . " allocated";
|
||||
$this->warning($testDetails, $message);
|
||||
return false;
|
||||
} elseif ($mem == 0) {
|
||||
$message = $testDetails[2] . " We can't determine how much memory you have allocated. "
|
||||
. "Install only if you're sure you've allocated at least 20 MB.";
|
||||
$this->warning($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getPHPMemory()
|
||||
{
|
||||
$memString = $this->getOriginalIni("memory_limit");
|
||||
|
||||
switch (strtolower(substr($memString, -1))) {
|
||||
case "k":
|
||||
return round(substr($memString, 0, -1) * 1024);
|
||||
|
||||
case "m":
|
||||
return round(substr($memString, 0, -1) * 1024 * 1024);
|
||||
|
||||
case "g":
|
||||
return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
|
||||
|
||||
default:
|
||||
return round($memString);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function listErrors()
|
||||
{
|
||||
if ($this->errors) {
|
||||
echo "<p>The following problems are preventing me from installing SilverStripe CMS:</p>\n\n";
|
||||
foreach ($this->errors as $error) {
|
||||
echo "<li>" . htmlentities(implode(", ", $error), ENT_COMPAT, 'UTF-8') . "</li>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function showTable($section = null)
|
||||
{
|
||||
if ($section) {
|
||||
$tests = $this->tests[$section];
|
||||
$id = strtolower(str_replace(' ', '_', $section));
|
||||
echo "<table id=\"{$id}_results\" class=\"testResults\" width=\"100%\">";
|
||||
foreach ($tests as $test => $result) {
|
||||
echo "<tr class=\"$result[0]\"><td>$test</td><td>"
|
||||
. nl2br(htmlentities($result[1], ENT_COMPAT, 'UTF-8')) . "</td></tr>";
|
||||
}
|
||||
echo "</table>";
|
||||
} else {
|
||||
foreach ($this->tests as $section => $tests) {
|
||||
$failedRequirements = 0;
|
||||
$warningRequirements = 0;
|
||||
|
||||
$output = "";
|
||||
|
||||
foreach ($tests as $test => $result) {
|
||||
if (isset($result['0'])) {
|
||||
switch ($result['0']) {
|
||||
case 'error':
|
||||
$failedRequirements++;
|
||||
break;
|
||||
case 'warning':
|
||||
$warningRequirements++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$output .= "<tr class=\"$result[0]\"><td>$test</td><td>"
|
||||
. nl2br(htmlentities($result[1], ENT_COMPAT, 'UTF-8')) . "</td></tr>";
|
||||
}
|
||||
$className = "good";
|
||||
$text = "All Requirements Pass";
|
||||
$pluralWarnings = ($warningRequirements == 1) ? 'Warning' : 'Warnings';
|
||||
|
||||
if ($failedRequirements > 0) {
|
||||
$className = "error";
|
||||
$pluralWarnings = ($warningRequirements == 1) ? 'Warning' : 'Warnings';
|
||||
|
||||
$text = $failedRequirements . ' Failed and ' . $warningRequirements . ' ' . $pluralWarnings;
|
||||
} elseif ($warningRequirements > 0) {
|
||||
$className = "warning";
|
||||
$text = "All Requirements Pass but " . $warningRequirements . ' ' . $pluralWarnings;
|
||||
}
|
||||
|
||||
echo "<h5 class='requirement $className'>$section <a href='#'>Show All Requirements</a> <span>$text</span></h5>";
|
||||
echo "<table class=\"testResults\">";
|
||||
echo $output;
|
||||
echo "</table>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function requireFunction($funcName, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
|
||||
if (!function_exists($funcName)) {
|
||||
$this->error($testDetails);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function requireClass($className, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
if (!class_exists($className)) {
|
||||
$this->error($testDetails);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Require that the given class doesn't exist
|
||||
*
|
||||
* @param array $classNames
|
||||
* @param array $testDetails
|
||||
* @return bool
|
||||
*/
|
||||
public function requireNoClasses($classNames, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
$badClasses = array();
|
||||
foreach ($classNames as $className) {
|
||||
if (class_exists($className)) {
|
||||
$badClasses[] = $className;
|
||||
}
|
||||
}
|
||||
if ($badClasses) {
|
||||
$message = $testDetails[2] . ". The following classes are at fault: " . implode(', ', $badClasses);
|
||||
$this->error($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function checkApacheVersion($testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
|
||||
$is1pointx = preg_match('#Apache[/ ]1\.#', $testDetails[3]);
|
||||
if ($is1pointx) {
|
||||
$this->error($testDetails);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function requirePHPVersion($recommendedVersion, $requiredVersion, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
|
||||
$installedVersion = phpversion();
|
||||
|
||||
if (version_compare($installedVersion, $requiredVersion, '<')) {
|
||||
$message = "SilverStripe requires PHP version $requiredVersion or later.\n
|
||||
PHP version $installedVersion is currently installed.\n
|
||||
While SilverStripe requires at least PHP version $requiredVersion, upgrading to $recommendedVersion or later is recommended.\n
|
||||
If you are installing SilverStripe on a shared web server, please ask your web hosting provider to upgrade PHP for you.";
|
||||
$this->error($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (version_compare($installedVersion, $recommendedVersion, '<')) {
|
||||
$message = "PHP version $installedVersion is currently installed.\n
|
||||
Upgrading to at least PHP version $recommendedVersion is recommended.\n
|
||||
SilverStripe should run, but you may run into issues. Future releases may require a later version of PHP.\n";
|
||||
$this->warning($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a module exists
|
||||
*
|
||||
* @param string $dirname
|
||||
* @return bool
|
||||
*/
|
||||
public function checkModuleExists($dirname)
|
||||
{
|
||||
$path = $this->getBaseDir() . $dirname;
|
||||
return file_exists($path) && ($dirname == 'mysite' || file_exists($path . '/_config.php'));
|
||||
}
|
||||
|
||||
/**
|
||||
* The same as {@link requireFile()} but does additional checks
|
||||
* to ensure the module directory is intact.
|
||||
*
|
||||
* @param string $dirname
|
||||
* @param array $testDetails
|
||||
*/
|
||||
public function requireModule($dirname, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
$path = $this->getBaseDir() . $dirname;
|
||||
if (!file_exists($path)) {
|
||||
$testDetails[2] .= " Directory '$path' not found. Please make sure you have uploaded the SilverStripe files to your webserver correctly.";
|
||||
$this->error($testDetails);
|
||||
} elseif (!file_exists($path . '/_config.php') && $dirname != 'mysite') {
|
||||
$testDetails[2] .= " Directory '$path' exists, but is missing files. Please make sure you have uploaded "
|
||||
. "the SilverStripe files to your webserver correctly.";
|
||||
$this->error($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public function requireFile($filename, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
$filename = $this->getBaseDir() . $filename;
|
||||
if (!file_exists($filename)) {
|
||||
$testDetails[2] .= " (file '$filename' not found)";
|
||||
$this->error($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public function requireWriteable($filename, $testDetails, $absolute = false)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
|
||||
if ($absolute) {
|
||||
$filename = str_replace('/', DIRECTORY_SEPARATOR, $filename);
|
||||
} else {
|
||||
$filename = $this->getBaseDir() . str_replace('/', DIRECTORY_SEPARATOR, $filename);
|
||||
}
|
||||
|
||||
if (file_exists($filename)) {
|
||||
$isWriteable = is_writeable($filename);
|
||||
} else {
|
||||
$isWriteable = is_writeable(dirname($filename));
|
||||
}
|
||||
|
||||
if (!$isWriteable) {
|
||||
if (function_exists('posix_getgroups')) {
|
||||
$userID = posix_geteuid();
|
||||
$user = posix_getpwuid($userID);
|
||||
|
||||
$currentOwnerID = fileowner(file_exists($filename) ? $filename : dirname($filename));
|
||||
$currentOwner = posix_getpwuid($currentOwnerID);
|
||||
|
||||
$testDetails[2] .= "User '$user[name]' needs to be able to write to this file:\n$filename\n\nThe "
|
||||
. "file is currently owned by '$currentOwner[name]'. ";
|
||||
|
||||
if ($user['name'] == $currentOwner['name']) {
|
||||
$testDetails[2] .= "We recommend that you make the file writeable.";
|
||||
} else {
|
||||
$groups = posix_getgroups();
|
||||
$groupList = array();
|
||||
foreach ($groups as $group) {
|
||||
$groupInfo = posix_getgrgid($group);
|
||||
if (in_array($currentOwner['name'], $groupInfo['members'])) {
|
||||
$groupList[] = $groupInfo['name'];
|
||||
}
|
||||
}
|
||||
if ($groupList) {
|
||||
$testDetails[2] .= " We recommend that you make the file group-writeable "
|
||||
. "and change the group to one of these groups:\n - " . implode("\n - ", $groupList)
|
||||
. "\n\nFor example:\nchmod g+w $filename\nchgrp " . $groupList[0] . " $filename";
|
||||
} else {
|
||||
$testDetails[2] .= " There is no user-group that contains both the web-server user and the "
|
||||
. "owner of this file. Change the ownership of the file, create a new group, or "
|
||||
. "temporarily make the file writeable by everyone during the install process.";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$testDetails[2] .= "The webserver user needs to be able to write to this file:\n$filename";
|
||||
}
|
||||
|
||||
$this->error($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public function requireTempFolder($testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
|
||||
try {
|
||||
$tempFolder = TempFolder::getTempFolder(BASE_PATH);
|
||||
} catch (Exception $e) {
|
||||
$tempFolder = false;
|
||||
}
|
||||
|
||||
if (!$tempFolder) {
|
||||
$testDetails[2] = "Permission problem gaining access to a temp directory. " .
|
||||
"Please create a folder named silverstripe-cache in the base directory " .
|
||||
"of the installation and ensure it has the adequate permissions.";
|
||||
$this->error($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public function requireApacheModule($moduleName, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
if (!in_array($moduleName, apache_get_modules())) {
|
||||
$this->error($testDetails);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function testApacheRewriteExists($moduleName = 'mod_rewrite')
|
||||
{
|
||||
if (function_exists('apache_get_modules') && in_array($moduleName, apache_get_modules())) {
|
||||
return true;
|
||||
}
|
||||
if (isset($_SERVER['HTTP_MOD_REWRITE']) && $_SERVER['HTTP_MOD_REWRITE'] == 'On') {
|
||||
return true;
|
||||
}
|
||||
if (isset($_SERVER['REDIRECT_HTTP_MOD_REWRITE']) && $_SERVER['REDIRECT_HTTP_MOD_REWRITE'] == 'On') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function testIISRewriteModuleExists($moduleName = 'IIS_UrlRewriteModule')
|
||||
{
|
||||
if (isset($_SERVER[$moduleName]) && $_SERVER[$moduleName]) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function requireApacheRewriteModule($moduleName, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
if ($this->testApacheRewriteExists()) {
|
||||
return true;
|
||||
} else {
|
||||
$this->warning($testDetails);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the web server has any rewriting capability.
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasRewritingCapability()
|
||||
{
|
||||
return ($this->testApacheRewriteExists() || $this->testIISRewriteModuleExists());
|
||||
}
|
||||
|
||||
public function requireIISRewriteModule($moduleName, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
if ($this->testIISRewriteModuleExists()) {
|
||||
return true;
|
||||
} else {
|
||||
$this->warning($testDetails);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getDatabaseTypeNice($databaseClass)
|
||||
{
|
||||
return substr($databaseClass, 0, -8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of a helper class for the specific database.
|
||||
*
|
||||
* @param string $databaseClass e.g. MySQLDatabase or MSSQLDatabase
|
||||
* @return DatabaseConfigurationHelper
|
||||
*/
|
||||
public function getDatabaseConfigurationHelper($databaseClass)
|
||||
{
|
||||
return DatabaseAdapterRegistry::getDatabaseConfigurationHelper($databaseClass);
|
||||
}
|
||||
|
||||
public function requireDatabaseFunctions($databaseConfig, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
$helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']);
|
||||
if (!$helper) {
|
||||
$this->error($testDetails, "Couldn't load database helper code for " . $databaseConfig['type']);
|
||||
return false;
|
||||
}
|
||||
$result = $helper->requireDatabaseFunctions($databaseConfig);
|
||||
if ($result) {
|
||||
return true;
|
||||
} else {
|
||||
$this->error($testDetails);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function requireDatabaseConnection($databaseConfig, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
$helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']);
|
||||
$result = $helper->requireDatabaseConnection($databaseConfig);
|
||||
if ($result['success']) {
|
||||
return true;
|
||||
} else {
|
||||
$testDetails[2] .= ": " . $result['error'];
|
||||
$this->error($testDetails);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function requireDatabaseVersion($databaseConfig, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
$helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']);
|
||||
if (method_exists($helper, 'requireDatabaseVersion')) {
|
||||
$result = $helper->requireDatabaseVersion($databaseConfig);
|
||||
if ($result['success']) {
|
||||
return true;
|
||||
} else {
|
||||
$testDetails[2] .= $result['error'];
|
||||
$this->warning($testDetails);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Skipped test because this database has no required version
|
||||
return true;
|
||||
}
|
||||
|
||||
public function requireDatabaseServer($databaseConfig, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
$helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']);
|
||||
$result = $helper->requireDatabaseServer($databaseConfig);
|
||||
if ($result['success']) {
|
||||
return true;
|
||||
} else {
|
||||
$message = $testDetails[2] . ": " . $result['error'];
|
||||
$this->error($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function requireDatabaseOrCreatePermissions($databaseConfig, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
$helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']);
|
||||
$result = $helper->requireDatabaseOrCreatePermissions($databaseConfig);
|
||||
if ($result['success']) {
|
||||
if ($result['alreadyExists']) {
|
||||
$testDetails[3] = "Database $databaseConfig[database]";
|
||||
} else {
|
||||
$testDetails[3] = "Able to create a new database";
|
||||
}
|
||||
$this->testing($testDetails);
|
||||
return true;
|
||||
} else {
|
||||
if (empty($result['cannotCreate'])) {
|
||||
$message = $testDetails[2] . ". Please create the database manually.";
|
||||
} else {
|
||||
$message = $testDetails[2] . " (user '$databaseConfig[username]' doesn't have CREATE DATABASE permissions.)";
|
||||
}
|
||||
|
||||
$this->error($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function requireDatabaseAlterPermissions($databaseConfig, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
$helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']);
|
||||
$result = $helper->requireDatabaseAlterPermissions($databaseConfig);
|
||||
if ($result['success']) {
|
||||
return true;
|
||||
} else {
|
||||
$message = "Silverstripe cannot alter tables. This won't prevent installation, however it may "
|
||||
. "cause issues if you try to run a /dev/build once installed.";
|
||||
$this->warning($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function requireServerVariables($varNames, $testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
$missing = array();
|
||||
|
||||
foreach ($varNames as $varName) {
|
||||
if (!isset($_SERVER[$varName]) || !$_SERVER[$varName]) {
|
||||
$missing[] = '$_SERVER[' . $varName . ']';
|
||||
}
|
||||
}
|
||||
|
||||
if (!$missing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$message = $testDetails[2] . " (the following PHP variables are missing: " . implode(", ", $missing) . ")";
|
||||
$this->error($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function requirePostSupport($testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
|
||||
if (!isset($_POST)) {
|
||||
$this->error($testDetails);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isRunningWebServer($testDetails)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
if ($testDetails[3]) {
|
||||
return true;
|
||||
} else {
|
||||
$this->warning($testDetails);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Must be PHP4 compatible
|
||||
var $baseDir;
|
||||
|
||||
public function getBaseDir()
|
||||
{
|
||||
return BASE_PATH . '/';
|
||||
}
|
||||
|
||||
public function testing($testDetails)
|
||||
{
|
||||
if (!$testDetails) {
|
||||
return;
|
||||
}
|
||||
|
||||
$section = $testDetails[0];
|
||||
$test = $testDetails[1];
|
||||
|
||||
$message = "OK";
|
||||
if (isset($testDetails[3])) {
|
||||
$message .= " ($testDetails[3])";
|
||||
}
|
||||
|
||||
$this->tests[$section][$test] = array("good", $message);
|
||||
}
|
||||
|
||||
public function error($testDetails, $message = null)
|
||||
{
|
||||
if (!is_array($testDetails)) {
|
||||
throw new InvalidArgumentException("Invalid error");
|
||||
}
|
||||
$section = $testDetails[0];
|
||||
$test = $testDetails[1];
|
||||
if (!$message && isset($testDetails[2])) {
|
||||
$message = $testDetails[2];
|
||||
}
|
||||
|
||||
$this->tests[$section][$test] = array("error", $message);
|
||||
$this->errors[] = $testDetails;
|
||||
}
|
||||
|
||||
public function warning($testDetails, $message = null)
|
||||
{
|
||||
if (!is_array($testDetails)) {
|
||||
throw new InvalidArgumentException("Invalid warning");
|
||||
}
|
||||
$section = $testDetails[0];
|
||||
$test = $testDetails[1];
|
||||
if (!$message && isset($testDetails[2])) {
|
||||
$message = $testDetails[2];
|
||||
}
|
||||
|
||||
$this->tests[$section][$test] = array("warning", $message);
|
||||
$this->warnings[] = $testDetails;
|
||||
}
|
||||
|
||||
public function hasErrors()
|
||||
{
|
||||
return sizeof($this->errors);
|
||||
}
|
||||
|
||||
public function hasWarnings()
|
||||
{
|
||||
return sizeof($this->warnings);
|
||||
}
|
||||
}
|
491
src/Dev/Install/Installer.php
Normal file
491
src/Dev/Install/Installer.php
Normal file
@ -0,0 +1,491 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Dev\Install;
|
||||
|
||||
use Exception;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Core\CoreKernel;
|
||||
use SilverStripe\Control\HTTPApplication;
|
||||
use SilverStripe\Core\Kernel;
|
||||
use SilverStripe\Core\Startup\ParameterConfirmationToken;
|
||||
use SilverStripe\ORM\DatabaseAdmin;
|
||||
use SilverStripe\Security\DefaultAdminService;
|
||||
use SilverStripe\Security\Security;
|
||||
|
||||
/**
|
||||
* SilverStripe CMS SilverStripe\Dev\Install\Installer
|
||||
* This installer doesn't use any of the fancy SilverStripe stuff in case it's unsupported.
|
||||
*/
|
||||
class Installer extends InstallRequirements
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// Cache the baseDir value
|
||||
$this->getBaseDir();
|
||||
}
|
||||
|
||||
protected function installHeader()
|
||||
{
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Installing SilverStripe...</title>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="framework/src/Dev/Install/client/styles/install.css"/>
|
||||
<script src="//code.jquery.com/jquery-1.7.2.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="install-header">
|
||||
<div class="inner">
|
||||
<div class="brand">
|
||||
<span class="logo"></span>
|
||||
|
||||
<h1>SilverStripe</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="Navigation"> </div>
|
||||
<div class="clear"><!-- --></div>
|
||||
|
||||
<div class="main">
|
||||
<div class="inner">
|
||||
<h2>Installing SilverStripe...</h2>
|
||||
|
||||
<p>I am now running through the installation steps (this should take about 30 seconds)</p>
|
||||
|
||||
<p>If you receive a fatal error, refresh this page to continue the installation</p>
|
||||
<ul>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function install($config)
|
||||
{
|
||||
// Render header
|
||||
$this->installHeader();
|
||||
|
||||
$webserver = $this->findWebserver();
|
||||
$isIIS = $this->isIIS();
|
||||
$isApache = $this->isApache();
|
||||
|
||||
flush();
|
||||
|
||||
if (isset($config['stats'])) {
|
||||
if (file_exists(FRAMEWORK_PATH . '/silverstripe_version')) {
|
||||
$silverstripe_version = file_get_contents(FRAMEWORK_PATH . '/silverstripe_version');
|
||||
} else {
|
||||
$silverstripe_version = "unknown";
|
||||
}
|
||||
|
||||
$phpVersion = urlencode(phpversion());
|
||||
$encWebserver = urlencode($webserver);
|
||||
$dbType = $config['db']['type'];
|
||||
|
||||
// Try to determine the database version from the helper
|
||||
$databaseVersion = $config['db']['type'];
|
||||
$helper = $this->getDatabaseConfigurationHelper($dbType);
|
||||
if ($helper && method_exists($helper, 'getDatabaseVersion')) {
|
||||
$versionConfig = $config['db'][$dbType];
|
||||
$versionConfig['type'] = $dbType;
|
||||
$databaseVersion = urlencode($dbType . ': ' . $helper->getDatabaseVersion($versionConfig));
|
||||
}
|
||||
|
||||
$url = "http://ss2stat.silverstripe.com/Installation/add?SilverStripe=$silverstripe_version&PHP=$phpVersion&Database=$databaseVersion&WebServer=$encWebserver";
|
||||
|
||||
if (isset($_SESSION['StatsID']) && $_SESSION['StatsID']) {
|
||||
$url .= '&ID=' . $_SESSION['StatsID'];
|
||||
}
|
||||
|
||||
@$_SESSION['StatsID'] = file_get_contents($url);
|
||||
}
|
||||
|
||||
if (file_exists('mysite/_config.php')) {
|
||||
// Truncate the contents of _config instead of deleting it - we can't re-create it because Windows handles permissions slightly
|
||||
// differently to UNIX based filesystems - it takes the permissions from the parent directory instead of retaining them
|
||||
$fh = fopen('mysite/_config.php', 'wb');
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
// Escape user input for safe insertion into PHP file
|
||||
$theme = isset($_POST['template']) ? addcslashes($_POST['template'], "\'") : 'simple';
|
||||
$locale = isset($_POST['locale']) ? addcslashes($_POST['locale'], "\'") : 'en_US';
|
||||
$type = addcslashes($config['db']['type'], "\'");
|
||||
$dbConfig = $config['db'][$type];
|
||||
foreach ($dbConfig as &$configValue) {
|
||||
$configValue = addcslashes($configValue, "\\\'");
|
||||
}
|
||||
if (!isset($dbConfig['path'])) {
|
||||
$dbConfig['path'] = '';
|
||||
}
|
||||
if (!$dbConfig) {
|
||||
echo "<p style=\"color: red\">Bad config submitted</p><pre>";
|
||||
print_r($config);
|
||||
echo "</pre>";
|
||||
die();
|
||||
}
|
||||
|
||||
// Write the config file
|
||||
global $usingEnv;
|
||||
if ($usingEnv) {
|
||||
$this->statusMessage("Setting up 'mysite/_config.php' for use with environment variables...");
|
||||
$this->writeToFile("mysite/_config.php", "<?php\n ");
|
||||
} else {
|
||||
$this->statusMessage("Setting up 'mysite/_config.php'...");
|
||||
// Create databaseConfig
|
||||
$lines = array(
|
||||
$lines[] = " 'type' => '$type'"
|
||||
);
|
||||
foreach ($dbConfig as $key => $value) {
|
||||
$lines[] = " '{$key}' => '$value'";
|
||||
}
|
||||
$databaseConfigContent = implode(",\n", $lines);
|
||||
$this->writeToFile("mysite/_config.php", <<<PHP
|
||||
<?php
|
||||
|
||||
use SilverStripe\\ORM\\DB;
|
||||
|
||||
DB::setConfig([
|
||||
{$databaseConfigContent}
|
||||
]);
|
||||
|
||||
PHP
|
||||
);
|
||||
}
|
||||
|
||||
$this->statusMessage("Setting up 'mysite/_config/config.yml'");
|
||||
$this->writeToFile("mysite/_config/config.yml", <<<YML
|
||||
---
|
||||
Name: mysite
|
||||
---
|
||||
# YAML configuration for SilverStripe
|
||||
# See http://doc.silverstripe.org/framework/en/topics/configuration
|
||||
# Caution: Indentation through two spaces, not tabs
|
||||
SilverStripe\\View\\SSViewer:
|
||||
themes:
|
||||
- '$theme'
|
||||
- '\$default'
|
||||
SilverStripe\\i18n\\i18n:
|
||||
default_locale: '$locale'
|
||||
YML
|
||||
);
|
||||
|
||||
if (!$this->checkModuleExists('cms')) {
|
||||
$this->writeToFile("mysite/code/RootURLController.php", <<<PHP
|
||||
<?php
|
||||
|
||||
use SilverStripe\\Control\\Controller;
|
||||
|
||||
class RootURLController extends Controller {
|
||||
|
||||
public function index() {
|
||||
echo "<html>Your site is now set up. Start adding controllers to mysite to get started.</html>";
|
||||
}
|
||||
|
||||
}
|
||||
PHP
|
||||
);
|
||||
}
|
||||
|
||||
// Write the appropriate web server configuration file for rewriting support
|
||||
if ($this->hasRewritingCapability()) {
|
||||
if ($isApache) {
|
||||
$this->statusMessage("Setting up '.htaccess' file...");
|
||||
$this->createHtaccess();
|
||||
} elseif ($isIIS) {
|
||||
$this->statusMessage("Setting up 'web.config' file...");
|
||||
$this->createWebConfig();
|
||||
}
|
||||
}
|
||||
|
||||
// Mock request
|
||||
$session = new Session(isset($_SESSION) ? $_SESSION : array());
|
||||
$request = new HTTPRequest('GET', '/');
|
||||
$request->setSession($session);
|
||||
|
||||
// Install kernel (fix to dev)
|
||||
$kernel = new CoreKernel(BASE_PATH);
|
||||
$kernel->setEnvironment(Kernel::DEV);
|
||||
$app = new HTTPApplication($kernel);
|
||||
|
||||
// Build db within HTTPApplication
|
||||
$app->execute($request, function (HTTPRequest $request) use ($config) {
|
||||
// Start session and execute
|
||||
$request->getSession()->init();
|
||||
|
||||
// Output status
|
||||
$this->statusMessage("Building database schema...");
|
||||
|
||||
// Setup DB
|
||||
$dbAdmin = new DatabaseAdmin();
|
||||
$dbAdmin->setRequest($request);
|
||||
$dbAdmin->pushCurrent();
|
||||
$dbAdmin->doInit();
|
||||
$dbAdmin->doBuild(true);
|
||||
|
||||
// Create default administrator user and group in database
|
||||
// (not using Security::setDefaultAdmin())
|
||||
$adminMember = DefaultAdminService::singleton()->findOrCreateDefaultAdmin();
|
||||
$adminMember->Email = $config['admin']['username'];
|
||||
$adminMember->Password = $config['admin']['password'];
|
||||
$adminMember->PasswordEncryption = Security::config()->get('encryption_algorithm');
|
||||
|
||||
try {
|
||||
$this->statusMessage('Creating default CMS admin account...');
|
||||
$adminMember->write();
|
||||
} catch (Exception $e) {
|
||||
$this->statusMessage(
|
||||
sprintf('Warning: Default CMS admin account could not be created (error: %s)', $e->getMessage())
|
||||
);
|
||||
}
|
||||
|
||||
$request->getSession()->set('username', $config['admin']['username']);
|
||||
$request->getSession()->set('password', $config['admin']['password']);
|
||||
$request->getSession()->save();
|
||||
}, true);
|
||||
|
||||
// Check result of install
|
||||
if (!$this->errors) {
|
||||
if (isset($_SERVER['HTTP_HOST']) && $this->hasRewritingCapability()) {
|
||||
$this->statusMessage("Checking that friendly URLs work...");
|
||||
$this->checkRewrite();
|
||||
} else {
|
||||
$token = new ParameterConfirmationToken('flush', $request);
|
||||
$params = http_build_query($token->params());
|
||||
|
||||
$destinationURL = 'index.php/' .
|
||||
($this->checkModuleExists('cms') ? "home/successfullyinstalled?$params" : "?$params");
|
||||
|
||||
echo <<<HTML
|
||||
<li>SilverStripe successfully installed; I am now redirecting you to your SilverStripe site...</li>
|
||||
<script>
|
||||
setTimeout(function() {
|
||||
window.location = "$destinationURL";
|
||||
}, 2000);
|
||||
</script>
|
||||
<noscript>
|
||||
<li><a href="$destinationURL">Click here to access your site.</a></li>
|
||||
</noscript>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
public function writeToFile($filename, $content)
|
||||
{
|
||||
$base = $this->getBaseDir();
|
||||
$this->statusMessage("Setting up $base$filename");
|
||||
|
||||
if ((@$fh = fopen($base . $filename, 'wb')) && fwrite($fh, $content) && fclose($fh)) {
|
||||
return true;
|
||||
}
|
||||
$this->error("Couldn't write to file $base$filename");
|
||||
return false;
|
||||
}
|
||||
|
||||
public function createHtaccess()
|
||||
{
|
||||
$start = "### SILVERSTRIPE START ###\n";
|
||||
$end = "\n### SILVERSTRIPE END ###";
|
||||
|
||||
$base = dirname($_SERVER['SCRIPT_NAME']);
|
||||
if (defined('DIRECTORY_SEPARATOR')) {
|
||||
$base = str_replace(DIRECTORY_SEPARATOR, '/', $base);
|
||||
} else {
|
||||
$base = str_replace("\\", '/', $base);
|
||||
}
|
||||
|
||||
if ($base != '.') {
|
||||
$baseClause = "RewriteBase '$base'\n";
|
||||
} else {
|
||||
$baseClause = "";
|
||||
}
|
||||
if (strpos(strtolower(php_sapi_name()), "cgi") !== false) {
|
||||
$cgiClause = "RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\n";
|
||||
} else {
|
||||
$cgiClause = "";
|
||||
}
|
||||
$rewrite = <<<TEXT
|
||||
# Deny access to templates (but allow from localhost)
|
||||
<Files *.ss>
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
Allow from 127.0.0.1
|
||||
</Files>
|
||||
|
||||
# Deny access to IIS configuration
|
||||
<Files web.config>
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
</Files>
|
||||
|
||||
# Deny access to YAML configuration files which might include sensitive information
|
||||
<Files *.yml>
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</Files>
|
||||
|
||||
# Route errors to static pages automatically generated by SilverStripe
|
||||
ErrorDocument 404 /assets/error-404.html
|
||||
ErrorDocument 500 /assets/error-500.html
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
|
||||
# Turn off index.php handling requests to the homepage fixes issue in apache >=2.4
|
||||
<IfModule mod_dir.c>
|
||||
DirectoryIndex disabled
|
||||
</IfModule>
|
||||
|
||||
SetEnv HTTP_MOD_REWRITE On
|
||||
RewriteEngine On
|
||||
$baseClause
|
||||
$cgiClause
|
||||
|
||||
# Deny access to potentially sensitive files and folders
|
||||
RewriteRule ^vendor(/|$) - [F,L,NC]
|
||||
RewriteRule silverstripe-cache(/|$) - [F,L,NC]
|
||||
RewriteRule composer\.(json|lock) - [F,L,NC]
|
||||
|
||||
# Process through SilverStripe if no file with the requested name exists.
|
||||
# Pass through the original path as a query parameter, and retain the existing parameters.
|
||||
RewriteCond %{REQUEST_URI} ^(.*)$
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule .* framework/main.php?url=%1 [QSA]
|
||||
</IfModule>
|
||||
TEXT;
|
||||
|
||||
if (file_exists('.htaccess')) {
|
||||
$htaccess = file_get_contents('.htaccess');
|
||||
|
||||
if (strpos($htaccess, '### SILVERSTRIPE START ###') === false
|
||||
&& strpos($htaccess, '### SILVERSTRIPE END ###') === false
|
||||
) {
|
||||
$htaccess .= "\n### SILVERSTRIPE START ###\n### SILVERSTRIPE END ###\n";
|
||||
}
|
||||
|
||||
if (strpos($htaccess, '### SILVERSTRIPE START ###') !== false
|
||||
&& strpos($htaccess, '### SILVERSTRIPE END ###') !== false
|
||||
) {
|
||||
$start = substr($htaccess, 0, strpos($htaccess, '### SILVERSTRIPE START ###'))
|
||||
. "### SILVERSTRIPE START ###\n";
|
||||
$end = "\n" . substr($htaccess, strpos($htaccess, '### SILVERSTRIPE END ###'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->writeToFile('.htaccess', $start . $rewrite . $end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes basic configuration to the web.config for IIS
|
||||
* so that rewriting capability can be use.
|
||||
*/
|
||||
public function createWebConfig()
|
||||
{
|
||||
$content = <<<TEXT
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<system.webServer>
|
||||
<security>
|
||||
<requestFiltering>
|
||||
<hiddenSegments applyToWebDAV="false">
|
||||
<add segment="silverstripe-cache" />
|
||||
<add segment="vendor" />
|
||||
<add segment="composer.json" />
|
||||
<add segment="composer.lock" />
|
||||
</hiddenSegments>
|
||||
<fileExtensions allowUnlisted="true" >
|
||||
<add fileExtension=".ss" allowed="false"/>
|
||||
<add fileExtension=".yml" allowed="false"/>
|
||||
</fileExtensions>
|
||||
</requestFiltering>
|
||||
</security>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<rule name="SilverStripe Clean URLs" stopProcessing="true">
|
||||
<match url="^(.*)$" />
|
||||
<conditions>
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="framework/main.php?url={R:1}" appendQueryString="true" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
TEXT;
|
||||
|
||||
$this->writeToFile('web.config', $content);
|
||||
}
|
||||
|
||||
public function checkRewrite()
|
||||
{
|
||||
$token = new ParameterConfirmationToken('flush', new HTTPRequest('GET', '/'));
|
||||
$params = http_build_query($token->params());
|
||||
|
||||
$destinationURL = str_replace('install.php', '', $_SERVER['SCRIPT_NAME']) .
|
||||
($this->checkModuleExists('cms') ? "home/successfullyinstalled?$params" : "?$params");
|
||||
|
||||
echo <<<HTML
|
||||
<li id="ModRewriteResult">Testing...</li>
|
||||
<script>
|
||||
if (typeof $ == 'undefined') {
|
||||
document.getElemenyById('ModeRewriteResult').innerHTML = "I can't run jQuery ajax to set rewriting; I will redirect you to the homepage to see if everything is working.";
|
||||
setTimeout(function() {
|
||||
window.location = "$destinationURL";
|
||||
}, 10000);
|
||||
} else {
|
||||
$.ajax({
|
||||
method: 'get',
|
||||
url: 'InstallerTest/testrewrite',
|
||||
complete: function(response) {
|
||||
var r = response.responseText.replace(/[^A-Z]?/g,"");
|
||||
if (r === "OK") {
|
||||
$('#ModRewriteResult').html("Friendly URLs set up successfully; I am now redirecting you to your SilverStripe site...")
|
||||
setTimeout(function() {
|
||||
window.location = "$destinationURL";
|
||||
}, 2000);
|
||||
} else {
|
||||
$('#ModRewriteResult').html("Friendly URLs are not working. This is most likely because a rewrite module isn't configured "
|
||||
+ "correctly on your site. You may need to get your web host or server administrator to do this for you: "
|
||||
+ "<ul>"
|
||||
+ "<li><strong>mod_rewrite</strong> or other rewrite module is enabled on your web server</li>"
|
||||
+ "<li><strong>AllowOverride All</strong> is set for the directory where SilverStripe is installed</li>"
|
||||
+ "</ul>");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<noscript>
|
||||
<li><a href="$destinationURL">Click here</a> to check friendly URLs are working. If you get a 404 then something is wrong.</li>
|
||||
</noscript>
|
||||
HTML;
|
||||
}
|
||||
|
||||
public function var_export_array_nokeys($array)
|
||||
{
|
||||
$retval = "array(\n";
|
||||
foreach ($array as $item) {
|
||||
$retval .= "\t'";
|
||||
$retval .= trim($item);
|
||||
$retval .= "',\n";
|
||||
}
|
||||
$retval .= ")";
|
||||
return $retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an installation status message.
|
||||
* The output differs depending on whether this is CLI or web based
|
||||
*
|
||||
* @param string $msg
|
||||
*/
|
||||
public function statusMessage($msg)
|
||||
{
|
||||
echo "<li>$msg</li>\n";
|
||||
flush();
|
||||
}
|
||||
}
|
BIN
src/Dev/Install/client/images/logo.gif
Normal file
BIN
src/Dev/Install/client/images/logo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
@ -6,7 +6,7 @@
|
||||
<head>
|
||||
<title>SilverStripe CMS / Framework Installation</title>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<script type="application/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
|
||||
<script type="application/javascript" src="//code.jquery.com/jquery-1.7.2.min.js"></script>
|
||||
<script type="application/javascript" src="<?=FRAMEWORK_NAME; ?>/src/Dev/Install/client/js/install.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="<?=FRAMEWORK_NAME; ?>/src/Dev/Install/client/styles/install.css">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
@ -29,13 +29,13 @@
|
||||
<div id="Layout">
|
||||
<div class="typography">
|
||||
<form action="install.php" method="post">
|
||||
<?php if(isset($hasErrorOtherThanDatabase)): ?>
|
||||
<?php if($hasErrorOtherThanDatabase): ?>
|
||||
<p class="message error">
|
||||
You aren't currently able to install the software. Please <a href="#requirements">see below</a> for details.<br>
|
||||
If you are having problems meeting the requirements, see the <a href="http://doc.silverstripe.org/framework/en/installation/server-requirements" target="_blank">server requirements</a>.
|
||||
</p>
|
||||
<?php if (isset($phpIniLocation)): ?>
|
||||
<p>Your php.ini file is located at <?=$phpIniLocation; ?></p>
|
||||
<?php if ($phpIniLocation): ?>
|
||||
<p class="message warning">Your php.ini file is located at <?=$phpIniLocation; ?></p>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<?php if($alreadyInstalled): ?>
|
||||
@ -252,7 +252,7 @@
|
||||
</ul>
|
||||
<h3 class="sectionHeading" id="install">Confirm Install <small>Step 5 of 5</small></h3>
|
||||
|
||||
<?php if(isset($hasErrorOtherThanDatabase)): ?>
|
||||
<?php if($hasErrorOtherThanDatabase): ?>
|
||||
<p class="error">
|
||||
You aren't currently able to install the software. Please <a href="#requirements">see above</a> for details.<br>
|
||||
If you are having problems meeting the requirements, see the <a href="http://doc.silverstripe.org/doku.php?id=server-requirements">server requirements page</a>.
|
||||
|
@ -24,4 +24,4 @@ if (version_compare(phpversion(), '5.5.0', '<')) {
|
||||
die();
|
||||
}
|
||||
|
||||
include(__DIR__ . '/install.php5');
|
||||
include(__DIR__ . '/install5.php');
|
||||
|
@ -1,1762 +0,0 @@
|
||||
<?php
|
||||
|
||||
/************************************************************************************
|
||||
************************************************************************************
|
||||
** **
|
||||
** If you can read this text in your browser then you don't have PHP installed. **
|
||||
** Please install PHP 5.5.0 or higher. **
|
||||
** **
|
||||
************************************************************************************
|
||||
************************************************************************************/
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Core\Startup\ParameterConfirmationToken;
|
||||
use SilverStripe\Dev\Install\DatabaseAdapterRegistry;
|
||||
use SilverStripe\Dev\Install\DatabaseConfigurationHelper;
|
||||
use SilverStripe\ORM\DatabaseAdmin;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Security\DefaultAdminService;
|
||||
|
||||
/**
|
||||
* SilverStripe CMS Installer
|
||||
* This installer doesn't use any of the fancy SilverStripe stuff in case it's unsupported.
|
||||
*/
|
||||
|
||||
// speed up mysql_connect timeout if the server can't be found
|
||||
ini_set('mysql.connect_timeout', 5);
|
||||
// Don't die half was through installation; that does more harm than good
|
||||
ini_set('max_execution_time', 0);
|
||||
|
||||
// set display_errors php setting to on to force installer to avoid blank screen of death.
|
||||
// get the original value so it can be used in PHP requirement checks later in this script.
|
||||
$originalDisplayErrorsValue = ini_get('display_errors');
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
error_reporting(E_ALL | E_STRICT);
|
||||
|
||||
// Attempt to start a session so that the username and password can be sent back to the user.
|
||||
if(function_exists('session_start') && !session_id()) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// require composers autoloader
|
||||
$autoloadPaths = [
|
||||
__DIR__ . '/../../../vendor/autoload.php', // framework/vendor
|
||||
__DIR__ . '/../../../../vendor/autoload.php', // root vendor
|
||||
];
|
||||
$included = false;
|
||||
foreach($autoloadPaths as $path) {
|
||||
if (file_exists($path)) {
|
||||
$included = true;
|
||||
require_once $path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$included) {
|
||||
if (!headers_sent()) {
|
||||
header($_SERVER['SERVER_PROTOCOL'] . " 500 Server Error");
|
||||
header('Content-Type: text/plain');
|
||||
}
|
||||
echo "Failed to include composer's autoloader, unable to continue\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$usingEnv = !empty($_REQUEST['useEnv']);
|
||||
|
||||
require_once __DIR__ . '/DatabaseConfigurationHelper.php';
|
||||
require_once __DIR__ . '/DatabaseAdapterRegistry.php';
|
||||
|
||||
// Set default locale, but try and sniff from the user agent
|
||||
$defaultLocale = 'en_US';
|
||||
$locales = array(
|
||||
'af_ZA' => 'Afrikaans (South Africa)',
|
||||
'ar_EG' => 'Arabic (Egypt)',
|
||||
'hy_AM' => 'Armenian (Armenia)',
|
||||
'ast_ES' => 'Asturian (Spain)',
|
||||
'az_AZ' => 'Azerbaijani (Azerbaijan)',
|
||||
'bs_BA' => 'Bosnian (Bosnia and Herzegovina)',
|
||||
'bg_BG' => 'Bulgarian (Bulgaria)',
|
||||
'ca_ES' => 'Catalan (Spain)',
|
||||
'zh_CN' => 'Chinese (China)',
|
||||
'zh_TW' => 'Chinese (Taiwan)',
|
||||
'hr_HR' => 'Croatian (Croatia)',
|
||||
'cs_CZ' => 'Czech (Czech Republic)',
|
||||
'da_DK' => 'Danish (Denmark)',
|
||||
'nl_NL' => 'Dutch (Netherlands)',
|
||||
'en_GB' => 'English (United Kingdom)',
|
||||
'en_US' => 'English (United States)',
|
||||
'eo_XX' => 'Esperanto',
|
||||
'et_EE' => 'Estonian (Estonia)',
|
||||
'fo_FO' => 'Faroese (Faroe Islands)',
|
||||
'fi_FI' => 'Finnish (Finland)',
|
||||
'fr_FR' => 'French (France)',
|
||||
'de_DE' => 'German (Germany)',
|
||||
'el_GR' => 'Greek (Greece)',
|
||||
'he_IL' => 'Hebrew (Israel)',
|
||||
'hu_HU' => 'Hungarian (Hungary)',
|
||||
'is_IS' => 'Icelandic (Iceland)',
|
||||
'id_ID' => 'Indonesian (Indonesia)',
|
||||
'it_IT' => 'Italian (Italy)',
|
||||
'ja_JP' => 'Japanese (Japan)',
|
||||
'km_KH' => 'Khmer (Cambodia)',
|
||||
'lc_XX' => 'LOLCAT',
|
||||
'lv_LV' => 'Latvian (Latvia)',
|
||||
'lt_LT' => 'Lithuanian (Lithuania)',
|
||||
'ms_MY' => 'Malay (Malaysia)',
|
||||
'mi_NZ' => 'Maori (New Zealand)',
|
||||
'ne_NP' => 'Nepali (Nepal)',
|
||||
'nb_NO' => 'Norwegian',
|
||||
'fa_IR' => 'Persian (Iran)',
|
||||
'pl_PL' => 'Polish (Poland)',
|
||||
'pt_BR' => 'Portuguese (Brazil)',
|
||||
'pa_IN' => 'Punjabi (India)',
|
||||
'ro_RO' => 'Romanian (Romania)',
|
||||
'ru_RU' => 'Russian (Russia)',
|
||||
'sr_RS' => 'Serbian (Serbia)',
|
||||
'si_LK' => 'Sinhalese (Sri Lanka)',
|
||||
'sk_SK' => 'Slovak (Slovakia)',
|
||||
'sl_SI' => 'Slovenian (Slovenia)',
|
||||
'es_AR' => 'Spanish (Argentina)',
|
||||
'es_MX' => 'Spanish (Mexico)',
|
||||
'es_ES' => 'Spanish (Spain)',
|
||||
'sv_SE' => 'Swedish (Sweden)',
|
||||
'th_TH' => 'Thai (Thailand)',
|
||||
'tr_TR' => 'Turkish (Turkey)',
|
||||
'uk_UA' => 'Ukrainian (Ukraine)',
|
||||
'uz_UZ' => 'Uzbek (Uzbekistan)',
|
||||
'vi_VN' => 'Vietnamese (Vietnam)',
|
||||
);
|
||||
|
||||
// Discover which databases are available
|
||||
DatabaseAdapterRegistry::autodiscover();
|
||||
|
||||
// Determine which external database modules are USABLE
|
||||
$databaseClasses = DatabaseAdapterRegistry::get_adapters();
|
||||
foreach($databaseClasses as $class => $details) {
|
||||
$helper = DatabaseAdapterRegistry::getDatabaseConfigurationHelper($class);
|
||||
$databaseClasses[$class]['hasModule'] = !empty($helper);
|
||||
}
|
||||
|
||||
// Load database config
|
||||
if(isset($_REQUEST['db'])) {
|
||||
if(isset($_REQUEST['db']['type'])) {
|
||||
$type = $_REQUEST['db']['type'];
|
||||
} else {
|
||||
if ($type = getenv('SS_DATABASE_CLASS')) {
|
||||
$_REQUEST['db']['type'] = $type;
|
||||
} elseif( $databaseClasses['MySQLPDODatabase']['supported'] ) {
|
||||
$type = $_REQUEST['db']['type'] = 'MySQLPDODatabase';
|
||||
} elseif( $databaseClasses['MySQLDatabase']['supported'] ) {
|
||||
$type = $_REQUEST['db']['type'] = 'MySQLDatabase';
|
||||
} else {
|
||||
// handle error
|
||||
}
|
||||
}
|
||||
|
||||
// Disabled inputs don't submit anything - we need to use the environment (except the database name)
|
||||
if($usingEnv) {
|
||||
$_REQUEST['db'][$type] = $databaseConfig = array(
|
||||
"type" => getenv('SS_DATABASE_CLASS') ?: $type,
|
||||
"server" => getenv('SS_DATABASE_SERVER') ?: "localhost",
|
||||
"username" => getenv('SS_DATABASE_USERNAME') ?: "root",
|
||||
"password" => getenv('SS_DATABASE_PASSWORD') ?: "",
|
||||
"database" => $_REQUEST['db'][$type]['database'],
|
||||
);
|
||||
|
||||
} else {
|
||||
// Normal behaviour without the environment
|
||||
$databaseConfig = $_REQUEST['db'][$type];
|
||||
$databaseConfig['type'] = $type;
|
||||
}
|
||||
} else {
|
||||
if($type = getenv('SS_DATABASE_CLASS')) {
|
||||
$_REQUEST['db']['type'] = $type;
|
||||
} elseif( $databaseClasses['MySQLPDODatabase']['supported'] ) {
|
||||
$type = $_REQUEST['db']['type'] = 'MySQLPDODatabase';
|
||||
} elseif( $databaseClasses['MySQLDatabase']['supported'] ) {
|
||||
$type = $_REQUEST['db']['type'] = 'MySQLDatabase';
|
||||
} else {
|
||||
// handle error
|
||||
}
|
||||
$_REQUEST['db'][$type] = $databaseConfig = array(
|
||||
"type" => $type,
|
||||
"server" => getenv('SS_DATABASE_SERVER') ?: "localhost",
|
||||
"username" => getenv('SS_DATABASE_USERNAME') ?: "root",
|
||||
"password" => getenv('SS_DATABASE_PASSWORD') ?: "",
|
||||
"database" => isset($_SERVER['argv'][2]) ? $_SERVER['argv'][2] : "SS_mysite",
|
||||
);
|
||||
}
|
||||
|
||||
if(isset($_REQUEST['admin'])) {
|
||||
// Disabled inputs don't submit anything - we need to use the environment (except the database name)
|
||||
if($usingEnv) {
|
||||
$_REQUEST['admin'] = $adminConfig = array(
|
||||
'username' => getenv('SS_DEFAULT_ADMIN_USERNAME') ?: 'admin',
|
||||
'password' => getenv('SS_DEFAULT_ADMIN_PASSWORD') ?: '',
|
||||
);
|
||||
} else {
|
||||
$adminConfig = $_REQUEST['admin'];
|
||||
}
|
||||
} else {
|
||||
$_REQUEST['admin'] = $adminConfig = array(
|
||||
'username' => getenv('SS_DEFAULT_ADMIN_USERNAME') ?: 'admin',
|
||||
'password' => getenv('SS_DEFAULT_ADMIN_PASSWORD') ?: '',
|
||||
);
|
||||
}
|
||||
|
||||
$alreadyInstalled = false;
|
||||
if(file_exists('mysite/_config.php')) {
|
||||
// Find the $database variable in the relevant config file without having to execute the config file
|
||||
if(preg_match("/\\\$database\s*=\s*[^\n\r]+[\n\r]/", file_get_contents("mysite/_config.php"), $parts)) {
|
||||
eval($parts[0]);
|
||||
if(!empty($database)) {
|
||||
$alreadyInstalled = true;
|
||||
}
|
||||
// Assume that if $databaseConfig is defined in mysite/_config.php, then a non-environment-based installation has
|
||||
// already gone ahead
|
||||
} else if(preg_match("/\\\$databaseConfig\s*=\s*[^\n\r]+[\n\r]/", file_get_contents("mysite/_config.php"), $parts)) {
|
||||
$alreadyInstalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(file_exists(FRAMEWORK_NAME . '/silverstripe_version')) {
|
||||
$silverstripe_version = file_get_contents(FRAMEWORK_NAME . '/silverstripe_version');
|
||||
} else {
|
||||
$silverstripe_version = "unknown";
|
||||
}
|
||||
|
||||
// Check requirements
|
||||
$req = new InstallRequirements();
|
||||
$req->check();
|
||||
|
||||
$webserverConfigFile = '';
|
||||
if($req->isIIS()) {
|
||||
$webserverConfigFile = 'web.config';
|
||||
} else {
|
||||
$webserverConfigFile = '.htaccess';
|
||||
}
|
||||
|
||||
if($req->hasErrors()) {
|
||||
$hasErrorOtherThanDatabase = true;
|
||||
$phpIniLocation = php_ini_loaded_file();
|
||||
}
|
||||
|
||||
if($databaseConfig) {
|
||||
$dbReq = new InstallRequirements();
|
||||
$dbReq->checkDatabase($databaseConfig);
|
||||
}
|
||||
|
||||
if($adminConfig) {
|
||||
$adminReq = new InstallRequirements();
|
||||
$adminReq->checkAdminConfig($adminConfig);
|
||||
}
|
||||
|
||||
// Actual processor
|
||||
$installFromCli = (isset($_SERVER['argv'][1]) && $_SERVER['argv'][1] == 'install');
|
||||
|
||||
// CLI-install error message. exit(1) will halt any makefile.
|
||||
if($installFromCli && ($req->hasErrors() || $dbReq->hasErrors())) {
|
||||
echo "Cannot install due to errors:\n";
|
||||
$req->listErrors();
|
||||
$dbReq->listErrors();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if((isset($_REQUEST['go']) || $installFromCli) && !$req->hasErrors() && !$dbReq->hasErrors() && $adminConfig['username'] && $adminConfig['password']) {
|
||||
// Confirm before reinstalling
|
||||
if(!$installFromCli && $alreadyInstalled) {
|
||||
include(__DIR__ . '/config-form.html');
|
||||
|
||||
} else {
|
||||
$inst = new Installer();
|
||||
if($_REQUEST) $inst->install($_REQUEST);
|
||||
else $inst->install(array(
|
||||
'db' => $databaseConfig,
|
||||
'admin' => $adminConfig,
|
||||
));
|
||||
}
|
||||
|
||||
// Show the config form
|
||||
} else {
|
||||
include(__DIR__ . '/config-form.html');
|
||||
}
|
||||
|
||||
/**
|
||||
* This class checks requirements
|
||||
* Each of the requireXXX functions takes an argument which gives a user description of the test.
|
||||
* It's an array of 3 parts:
|
||||
* $description[0] - The test catetgory
|
||||
* $description[1] - The test title
|
||||
* $description[2] - The test error to show, if it goes wrong
|
||||
*/
|
||||
class InstallRequirements {
|
||||
var $errors, $warnings, $tests;
|
||||
|
||||
/**
|
||||
* Check the database configuration. These are done one after another
|
||||
* starting with checking the database function exists in PHP, and
|
||||
* continuing onto more difficult checks like database permissions.
|
||||
*
|
||||
* @param array $databaseConfig The list of database parameters
|
||||
* @return boolean Validity of database configuration details
|
||||
*/
|
||||
public function checkDatabase($databaseConfig) {
|
||||
// Check if support is available
|
||||
if(!$this->requireDatabaseFunctions(
|
||||
$databaseConfig,
|
||||
array(
|
||||
"Database Configuration",
|
||||
"Database support",
|
||||
"Database support in PHP",
|
||||
$this->getDatabaseTypeNice($databaseConfig['type'])
|
||||
)
|
||||
)) return false;
|
||||
|
||||
// Check if the server is available
|
||||
$usePath = !empty($databaseConfig['path']) && empty($databaseConfig['server']);
|
||||
if(!$this->requireDatabaseServer(
|
||||
$databaseConfig,
|
||||
array(
|
||||
"Database Configuration",
|
||||
"Database server",
|
||||
$usePath
|
||||
? "I couldn't write to path '$databaseConfig[path]'"
|
||||
: "I couldn't find a database server on '$databaseConfig[server]'",
|
||||
$usePath ? $databaseConfig['path'] : $databaseConfig['server']
|
||||
)
|
||||
)) return false;
|
||||
|
||||
// Check if the connection credentials allow access to the server / database
|
||||
if(!$this->requireDatabaseConnection(
|
||||
$databaseConfig,
|
||||
array(
|
||||
"Database Configuration",
|
||||
"Database access credentials",
|
||||
"That username/password doesn't work"
|
||||
)
|
||||
)) return false;
|
||||
|
||||
// Check the necessary server version is available
|
||||
if(!$this->requireDatabaseVersion(
|
||||
$databaseConfig,
|
||||
array(
|
||||
"Database Configuration",
|
||||
"Database server version requirement",
|
||||
'',
|
||||
'Version ' . $this->getDatabaseConfigurationHelper($databaseConfig['type'])->getDatabaseVersion($databaseConfig)
|
||||
)
|
||||
)) return false;
|
||||
|
||||
// Check that database creation permissions are available
|
||||
if(!$this->requireDatabaseOrCreatePermissions(
|
||||
$databaseConfig,
|
||||
array(
|
||||
"Database Configuration",
|
||||
"Can I access/create the database",
|
||||
"I can't create new databases and the database '$databaseConfig[database]' doesn't exist"
|
||||
)
|
||||
)) return false;
|
||||
|
||||
// Check alter permission (necessary to create tables etc)
|
||||
if(!$this->requireDatabaseAlterPermissions(
|
||||
$databaseConfig,
|
||||
array(
|
||||
"Database Configuration",
|
||||
"Can I ALTER tables",
|
||||
"I don't have permission to ALTER tables"
|
||||
)
|
||||
)) return false;
|
||||
|
||||
// Success!
|
||||
return true;
|
||||
}
|
||||
|
||||
public function checkAdminConfig($adminConfig) {
|
||||
if(!$adminConfig['username']) {
|
||||
$this->error(array('', 'Please enter a username!'));
|
||||
}
|
||||
if(!$adminConfig['password']) {
|
||||
$this->error(array('', 'Please enter a password!'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the web server is IIS and version greater than the given version.
|
||||
*
|
||||
* @param int $fromVersion
|
||||
* @return bool
|
||||
*/
|
||||
public function isIIS($fromVersion = 7) {
|
||||
if(strpos($this->findWebserver(), 'IIS/') === false) {
|
||||
return false;
|
||||
}
|
||||
return substr(strstr($this->findWebserver(), '/'), -3, 1) >= $fromVersion;
|
||||
}
|
||||
|
||||
public function isApache() {
|
||||
if(strpos($this->findWebserver(), 'Apache') !== false) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the webserver software running on the PHP host.
|
||||
* @return string|boolean Server software or boolean FALSE
|
||||
*/
|
||||
public function findWebserver() {
|
||||
// Try finding from SERVER_SIGNATURE or SERVER_SOFTWARE
|
||||
if(!empty($_SERVER['SERVER_SIGNATURE'])) {
|
||||
$webserver = $_SERVER['SERVER_SIGNATURE'];
|
||||
} elseif(!empty($_SERVER['SERVER_SOFTWARE'])) {
|
||||
$webserver = $_SERVER['SERVER_SOFTWARE'];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return strip_tags(trim($webserver));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check everything except the database
|
||||
*/
|
||||
public function check() {
|
||||
$this->errors = null;
|
||||
$isApache = $this->isApache();
|
||||
$isIIS = $this->isIIS();
|
||||
$webserver = $this->findWebserver();
|
||||
|
||||
$this->requirePHPVersion('5.5.0', '5.5.0', array(
|
||||
"PHP Configuration",
|
||||
"PHP5 installed",
|
||||
null,
|
||||
"PHP version " . phpversion()
|
||||
));
|
||||
|
||||
// Check that we can identify the root folder successfully
|
||||
$this->requireFile(FRAMEWORK_NAME . '/src/Dev/Install/config-form.html', array("File permissions",
|
||||
"Does the webserver know where files are stored?",
|
||||
"The webserver isn't letting me identify where files are stored.",
|
||||
$this->getBaseDir()
|
||||
));
|
||||
|
||||
$this->requireModule('mysite', array("File permissions", "mysite/ directory exists?"));
|
||||
$this->requireModule(FRAMEWORK_NAME, array("File permissions", FRAMEWORK_NAME . "/ directory exists?"));
|
||||
|
||||
if($isApache) {
|
||||
$this->checkApacheVersion(array(
|
||||
"Webserver Configuration",
|
||||
"Webserver is not Apache 1.x", "SilverStripe requires Apache version 2 or greater",
|
||||
$webserver
|
||||
));
|
||||
$this->requireWriteable('.htaccess', array("File permissions", "Is the .htaccess file writeable?", null));
|
||||
} elseif($isIIS) {
|
||||
$this->requireWriteable('web.config', array("File permissions", "Is the web.config file writeable?", null));
|
||||
}
|
||||
|
||||
$this->requireWriteable('mysite/_config.php', array(
|
||||
"File permissions",
|
||||
"Is the mysite/_config.php file writeable?",
|
||||
null
|
||||
));
|
||||
|
||||
$this->requireWriteable('mysite/_config/config.yml', array(
|
||||
"File permissions",
|
||||
"Is the mysite/_config/config.yml file writeable?",
|
||||
null
|
||||
));
|
||||
|
||||
if(!$this->checkModuleExists('cms')) {
|
||||
$this->requireWriteable('mysite/code/RootURLController.php', array(
|
||||
"File permissions",
|
||||
"Is the mysite/code/RootURLController.php file writeable?",
|
||||
null
|
||||
));
|
||||
}
|
||||
$this->requireWriteable('assets', array("File permissions", "Is the assets/ directory writeable?", null));
|
||||
|
||||
try {
|
||||
$tempFolder = getTempFolder();
|
||||
} catch(Exception $e) {
|
||||
$tempFolder = false;
|
||||
}
|
||||
|
||||
$this->requireTempFolder(array('File permissions', 'Is a temporary directory available?', null, $tempFolder));
|
||||
if($tempFolder) {
|
||||
// in addition to the temp folder being available, check it is writable
|
||||
$this->requireWriteable($tempFolder, array(
|
||||
"File permissions",
|
||||
sprintf("Is the temporary directory writeable?", $tempFolder),
|
||||
null
|
||||
), true);
|
||||
}
|
||||
|
||||
// Check for web server, unless we're calling the installer from the command-line
|
||||
$this->isRunningWebServer(array("Webserver Configuration", "Server software", "Unknown", $webserver));
|
||||
|
||||
if($isApache) {
|
||||
$this->requireApacheRewriteModule('mod_rewrite', array(
|
||||
"Webserver Configuration",
|
||||
"URL rewriting support",
|
||||
"You need mod_rewrite to use friendly URLs with SilverStripe, but it is not enabled."
|
||||
));
|
||||
} elseif($isIIS) {
|
||||
$this->requireIISRewriteModule('IIS_UrlRewriteModule', array(
|
||||
"Webserver Configuration",
|
||||
"URL rewriting support",
|
||||
"You need to enable the IIS URL Rewrite Module to use friendly URLs with SilverStripe, "
|
||||
. "but it is not installed or enabled. Download it for IIS 7 from http://www.iis.net/expand/URLRewrite"
|
||||
));
|
||||
} else {
|
||||
$this->warning(array(
|
||||
"Webserver Configuration",
|
||||
"URL rewriting support",
|
||||
"I can't tell whether any rewriting module is running. You may need to configure a rewriting rule yourself."));
|
||||
}
|
||||
|
||||
$this->requireServerVariables(array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'), array(
|
||||
"Webserver Configuration",
|
||||
"Recognised webserver",
|
||||
"You seem to be using an unsupported webserver. "
|
||||
. "The server variables SCRIPT_NAME, HTTP_HOST, SCRIPT_FILENAME need to be set."
|
||||
));
|
||||
|
||||
$this->requirePostSupport(array(
|
||||
"Webserver Configuration",
|
||||
"POST Support",
|
||||
'I can\'t find $_POST, make sure POST is enabled.'
|
||||
));
|
||||
|
||||
// Check for GD support
|
||||
if(!$this->requireFunction("imagecreatetruecolor", array(
|
||||
"PHP Configuration",
|
||||
"GD2 support",
|
||||
"PHP must have GD version 2."
|
||||
))) {
|
||||
$this->requireFunction("imagecreate", array(
|
||||
"PHP Configuration",
|
||||
"GD2 support",
|
||||
"GD support for PHP not included."
|
||||
));
|
||||
}
|
||||
|
||||
// Check for XML support
|
||||
$this->requireFunction('xml_set_object', array(
|
||||
"PHP Configuration",
|
||||
"XML support",
|
||||
"XML support not included in PHP."
|
||||
));
|
||||
$this->requireClass('DOMDocument', array(
|
||||
"PHP Configuration",
|
||||
"DOM/XML support",
|
||||
"DOM/XML support not included in PHP."
|
||||
));
|
||||
$this->requireFunction('simplexml_load_file', array(
|
||||
'PHP Configuration',
|
||||
'SimpleXML support',
|
||||
'SimpleXML support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for token_get_all
|
||||
$this->requireFunction('token_get_all', array(
|
||||
"PHP Configuration",
|
||||
"Tokenizer support",
|
||||
"Tokenizer support not included in PHP."
|
||||
));
|
||||
|
||||
// Check for CType support
|
||||
$this->requireFunction('ctype_digit', array(
|
||||
'PHP Configuration',
|
||||
'CType support',
|
||||
'CType support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for session support
|
||||
$this->requireFunction('session_start', array(
|
||||
'PHP Configuration',
|
||||
'Session support',
|
||||
'Session support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for iconv support
|
||||
$this->requireFunction('iconv', array(
|
||||
'PHP Configuration',
|
||||
'iconv support',
|
||||
'iconv support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for hash support
|
||||
$this->requireFunction('hash', array('PHP Configuration', 'hash support', 'hash support not included in PHP.'));
|
||||
|
||||
// Check for mbstring support
|
||||
$this->requireFunction('mb_internal_encoding', array(
|
||||
'PHP Configuration',
|
||||
'mbstring support',
|
||||
'mbstring support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for Reflection support
|
||||
$this->requireClass('ReflectionClass', array(
|
||||
'PHP Configuration',
|
||||
'Reflection support',
|
||||
'Reflection support not included in PHP.'
|
||||
));
|
||||
|
||||
// Check for Standard PHP Library (SPL) support
|
||||
$this->requireFunction('spl_classes', array(
|
||||
'PHP Configuration',
|
||||
'SPL support',
|
||||
'Standard PHP Library (SPL) not included in PHP.'
|
||||
));
|
||||
|
||||
$this->requireDateTimezone(array(
|
||||
'PHP Configuration',
|
||||
'date.timezone setting and validity',
|
||||
'date.timezone option in php.ini must be set correctly.',
|
||||
ini_get('date.timezone')
|
||||
));
|
||||
|
||||
$this->suggestClass('finfo', array(
|
||||
'PHP Configuration',
|
||||
'fileinfo support',
|
||||
'fileinfo should be enabled in PHP. SilverStripe uses it for MIME type detection of files. '
|
||||
. 'SilverStripe will still operate, but email attachments and sending files to browser '
|
||||
. '(e.g. export data to CSV) may not work correctly without finfo.'
|
||||
));
|
||||
|
||||
$this->suggestFunction('curl_init', array(
|
||||
'PHP Configuration',
|
||||
'curl support',
|
||||
'curl should be enabled in PHP. SilverStripe uses it for consuming web services'
|
||||
. ' via the RestfulService class and many modules rely on it.'
|
||||
));
|
||||
|
||||
$this->suggestClass('tidy', array(
|
||||
'PHP Configuration',
|
||||
'tidy support',
|
||||
'Tidy provides a library of code to clean up your html. '
|
||||
. 'SilverStripe will operate fine without tidy but HTMLCleaner will not be effective.'
|
||||
));
|
||||
|
||||
$this->suggestPHPSetting('asp_tags', array(false), array(
|
||||
'PHP Configuration',
|
||||
'asp_tags option',
|
||||
'This should be turned off as it can cause issues with SilverStripe'
|
||||
));
|
||||
$this->requirePHPSetting('magic_quotes_gpc', array(false), array(
|
||||
'PHP Configuration',
|
||||
'magic_quotes_gpc option',
|
||||
'This should be turned off, as it can cause issues with cookies. '
|
||||
. 'More specifically, unserializing data stored in cookies.'
|
||||
));
|
||||
$this->suggestPHPSetting('display_errors', array(false), array(
|
||||
'PHP Configuration',
|
||||
'display_errors option',
|
||||
'Unless you\'re in a development environment, this should be turned off, '
|
||||
. 'as it can expose sensitive data to website users.'
|
||||
));
|
||||
// on some weirdly configured webservers arg_separator.output is set to &
|
||||
// which will results in links like ?param=value&foo=bar which will not be i
|
||||
$this->suggestPHPSetting('arg_separator.output', array('&', ''), array(
|
||||
'PHP Configuration',
|
||||
'arg_separator.output option',
|
||||
'This option defines how URL parameters are concatenated. '
|
||||
. 'If not set to \'&\' this may cause issues with URL GET parameters'
|
||||
));
|
||||
|
||||
// always_populate_raw_post_data should be set to -1 if PHP < 7.0
|
||||
if (version_compare(PHP_VERSION, '7.0.0', '<')) {
|
||||
$this->suggestPHPSetting('always_populate_raw_post_data', ['-1'], [
|
||||
'PHP Configuration',
|
||||
'always_populate_raw_post_data option',
|
||||
'It\'s highly recommended to set this to \'-1\' in php 5.x, as $HTTP_RAW_POST_DATA is removed in php 7'
|
||||
]);
|
||||
}
|
||||
|
||||
// Check memory allocation
|
||||
$this->requireMemory(32 * 1024 * 1024, 64 * 1024 * 1024, array(
|
||||
"PHP Configuration",
|
||||
"Memory allocation (PHP config option 'memory_limit')",
|
||||
"SilverStripe needs a minimum of 32M allocated to PHP, but recommends 64M.",
|
||||
ini_get("memory_limit")
|
||||
));
|
||||
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
public function suggestPHPSetting($settingName, $settingValues, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
|
||||
// special case for display_errors, check the original value before
|
||||
// it was changed at the start of this script.
|
||||
if($settingName == 'display_errors') {
|
||||
global $originalDisplayErrorsValue;
|
||||
$val = $originalDisplayErrorsValue;
|
||||
} else {
|
||||
$val = ini_get($settingName);
|
||||
}
|
||||
|
||||
if(!in_array($val, $settingValues) && $val != $settingValues) {
|
||||
$this->warning($testDetails, "$settingName is set to '$val' in php.ini. $testDetails[2]");
|
||||
}
|
||||
}
|
||||
|
||||
public function requirePHPSetting($settingName, $settingValues, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
|
||||
$val = ini_get($settingName);
|
||||
if(!in_array($val, $settingValues) && $val != $settingValues) {
|
||||
$this->error($testDetails, "$settingName is set to '$val' in php.ini. $testDetails[2]");
|
||||
}
|
||||
}
|
||||
|
||||
public function suggestClass($class, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
|
||||
if(!class_exists($class)) {
|
||||
$this->warning($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public function suggestFunction($class, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
|
||||
if(!function_exists($class)) {
|
||||
$this->warning($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public function requireDateTimezone($testDetails) {
|
||||
$this->testing($testDetails);
|
||||
|
||||
$result = ini_get('date.timezone') && in_array(ini_get('date.timezone'), timezone_identifiers_list());
|
||||
if(!$result) {
|
||||
$this->error($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public function requireMemory($min, $recommended, $testDetails) {
|
||||
$_SESSION['forcemem'] = false;
|
||||
|
||||
$mem = $this->getPHPMemory();
|
||||
if($mem < (64 * 1024 * 1024)) {
|
||||
ini_set('memory_limit', '64M');
|
||||
$mem = $this->getPHPMemory();
|
||||
$testDetails[3] = ini_get("memory_limit");
|
||||
}
|
||||
|
||||
$this->testing($testDetails);
|
||||
|
||||
if($mem < $min && $mem > 0) {
|
||||
$message = $testDetails[2] . " You only have " . ini_get("memory_limit") . " allocated";
|
||||
$this->error($testDetails, $message);
|
||||
return false;
|
||||
} else if($mem < $recommended && $mem > 0) {
|
||||
$message = $testDetails[2] . " You only have " . ini_get("memory_limit") . " allocated";
|
||||
$this->warning($testDetails, $message);
|
||||
return false;
|
||||
} elseif($mem == 0) {
|
||||
$message = $testDetails[2] . " We can't determine how much memory you have allocated. "
|
||||
. "Install only if you're sure you've allocated at least 20 MB.";
|
||||
$this->warning($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getPHPMemory() {
|
||||
$memString = ini_get("memory_limit");
|
||||
|
||||
switch(strtolower(substr($memString, -1))) {
|
||||
case "k":
|
||||
return round(substr($memString, 0, -1) * 1024);
|
||||
|
||||
case "m":
|
||||
return round(substr($memString, 0, -1) * 1024 * 1024);
|
||||
|
||||
case "g":
|
||||
return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
|
||||
|
||||
default:
|
||||
return round($memString);
|
||||
}
|
||||
}
|
||||
|
||||
public function listErrors() {
|
||||
if($this->errors) {
|
||||
echo "<p>The following problems are preventing me from installing SilverStripe CMS:</p>\n\n";
|
||||
foreach($this->errors as $error) {
|
||||
echo "<li>" . htmlentities(implode(", ", $error), ENT_COMPAT, 'UTF-8') . "</li>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function showTable($section = null) {
|
||||
if($section) {
|
||||
$tests = $this->tests[$section];
|
||||
$id = strtolower(str_replace(' ', '_', $section));
|
||||
echo "<table id=\"{$id}_results\" class=\"testResults\" width=\"100%\">";
|
||||
foreach($tests as $test => $result) {
|
||||
echo "<tr class=\"$result[0]\"><td>$test</td><td>"
|
||||
. nl2br(htmlentities($result[1], ENT_COMPAT, 'UTF-8')) . "</td></tr>";
|
||||
}
|
||||
echo "</table>";
|
||||
|
||||
} else {
|
||||
foreach($this->tests as $section => $tests) {
|
||||
$failedRequirements = 0;
|
||||
$warningRequirements = 0;
|
||||
|
||||
$output = "";
|
||||
|
||||
foreach($tests as $test => $result) {
|
||||
if(isset($result['0'])) {
|
||||
switch($result['0']) {
|
||||
case 'error':
|
||||
$failedRequirements++;
|
||||
break;
|
||||
case 'warning':
|
||||
$warningRequirements++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$output .= "<tr class=\"$result[0]\"><td>$test</td><td>"
|
||||
. nl2br(htmlentities($result[1], ENT_COMPAT, 'UTF-8')) . "</td></tr>";
|
||||
}
|
||||
$className = "good";
|
||||
$text = "All Requirements Pass";
|
||||
$pluralWarnings = ($warningRequirements == 1) ? 'Warning' : 'Warnings';
|
||||
|
||||
if($failedRequirements > 0) {
|
||||
$className = "error";
|
||||
$pluralWarnings = ($warningRequirements == 1) ? 'Warning' : 'Warnings';
|
||||
|
||||
$text = $failedRequirements . ' Failed and ' . $warningRequirements . ' ' . $pluralWarnings;
|
||||
} else if($warningRequirements > 0) {
|
||||
$className = "warning";
|
||||
$text = "All Requirements Pass but " . $warningRequirements . ' ' . $pluralWarnings;
|
||||
}
|
||||
|
||||
echo "<h5 class='requirement $className'>$section <a href='#'>Show All Requirements</a> <span>$text</span></h5>";
|
||||
echo "<table class=\"testResults\">";
|
||||
echo $output;
|
||||
echo "</table>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function requireFunction($funcName, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
|
||||
if(!function_exists($funcName)) {
|
||||
$this->error($testDetails);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function requireClass($className, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
if(!class_exists($className)) {
|
||||
$this->error($testDetails);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Require that the given class doesn't exist
|
||||
*
|
||||
* @param array $classNames
|
||||
* @param array $testDetails
|
||||
* @return bool
|
||||
*/
|
||||
public function requireNoClasses($classNames, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
$badClasses = array();
|
||||
foreach($classNames as $className) {
|
||||
if(class_exists($className)) {
|
||||
$badClasses[] = $className;
|
||||
}
|
||||
}
|
||||
if($badClasses) {
|
||||
$message = $testDetails[2] . ". The following classes are at fault: " . implode(', ', $badClasses);
|
||||
$this->error($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function checkApacheVersion($testDetails) {
|
||||
$this->testing($testDetails);
|
||||
|
||||
$is1pointx = preg_match('#Apache[/ ]1\.#', $testDetails[3]);
|
||||
if($is1pointx) {
|
||||
$this->error($testDetails);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function requirePHPVersion($recommendedVersion, $requiredVersion, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
|
||||
$installedVersion = phpversion();
|
||||
|
||||
if(version_compare($installedVersion, $requiredVersion, '<')) {
|
||||
$message = "SilverStripe requires PHP version $requiredVersion or later.\n
|
||||
PHP version $installedVersion is currently installed.\n
|
||||
While SilverStripe requires at least PHP version $requiredVersion, upgrading to $recommendedVersion or later is recommended.\n
|
||||
If you are installing SilverStripe on a shared web server, please ask your web hosting provider to upgrade PHP for you.";
|
||||
$this->error($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(version_compare($installedVersion, $recommendedVersion, '<')) {
|
||||
$message = "PHP version $installedVersion is currently installed.\n
|
||||
Upgrading to at least PHP version $recommendedVersion is recommended.\n
|
||||
SilverStripe should run, but you may run into issues. Future releases may require a later version of PHP.\n";
|
||||
$this->warning($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a module exists
|
||||
*
|
||||
* @param string $dirname
|
||||
* @return bool
|
||||
*/
|
||||
public function checkModuleExists($dirname) {
|
||||
$path = $this->getBaseDir() . $dirname;
|
||||
return file_exists($path) && ($dirname == 'mysite' || file_exists($path . '/_config.php'));
|
||||
}
|
||||
|
||||
/**
|
||||
* The same as {@link requireFile()} but does additional checks
|
||||
* to ensure the module directory is intact.
|
||||
*
|
||||
* @param string $dirname
|
||||
* @param array $testDetails
|
||||
*/
|
||||
public function requireModule($dirname, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
$path = $this->getBaseDir() . $dirname;
|
||||
if(!file_exists($path)) {
|
||||
$testDetails[2] .= " Directory '$path' not found. Please make sure you have uploaded the SilverStripe files to your webserver correctly.";
|
||||
$this->error($testDetails);
|
||||
} elseif(!file_exists($path . '/_config.php') && $dirname != 'mysite') {
|
||||
$testDetails[2] .= " Directory '$path' exists, but is missing files. Please make sure you have uploaded "
|
||||
. "the SilverStripe files to your webserver correctly.";
|
||||
$this->error($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public function requireFile($filename, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
$filename = $this->getBaseDir() . $filename;
|
||||
if(!file_exists($filename)) {
|
||||
$testDetails[2] .= " (file '$filename' not found)";
|
||||
$this->error($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public function requireWriteable($filename, $testDetails, $absolute = false) {
|
||||
$this->testing($testDetails);
|
||||
|
||||
if($absolute) {
|
||||
$filename = str_replace('/', DIRECTORY_SEPARATOR, $filename);
|
||||
} else {
|
||||
$filename = $this->getBaseDir() . str_replace('/', DIRECTORY_SEPARATOR, $filename);
|
||||
}
|
||||
|
||||
if(file_exists($filename)) $isWriteable = is_writeable($filename);
|
||||
else $isWriteable = is_writeable(dirname($filename));
|
||||
|
||||
if(!$isWriteable) {
|
||||
if(function_exists('posix_getgroups')) {
|
||||
$userID = posix_geteuid();
|
||||
$user = posix_getpwuid($userID);
|
||||
|
||||
$currentOwnerID = fileowner(file_exists($filename) ? $filename : dirname($filename));
|
||||
$currentOwner = posix_getpwuid($currentOwnerID);
|
||||
|
||||
$testDetails[2] .= "User '$user[name]' needs to be able to write to this file:\n$filename\n\nThe "
|
||||
. "file is currently owned by '$currentOwner[name]'. ";
|
||||
|
||||
if($user['name'] == $currentOwner['name']) {
|
||||
$testDetails[2] .= "We recommend that you make the file writeable.";
|
||||
} else {
|
||||
|
||||
$groups = posix_getgroups();
|
||||
$groupList = array();
|
||||
foreach($groups as $group) {
|
||||
$groupInfo = posix_getgrgid($group);
|
||||
if(in_array($currentOwner['name'], $groupInfo['members'])) $groupList[] = $groupInfo['name'];
|
||||
}
|
||||
if($groupList) {
|
||||
$testDetails[2] .= " We recommend that you make the file group-writeable "
|
||||
. "and change the group to one of these groups:\n - " . implode("\n - ", $groupList)
|
||||
. "\n\nFor example:\nchmod g+w $filename\nchgrp " . $groupList[0] . " $filename";
|
||||
} else {
|
||||
$testDetails[2] .= " There is no user-group that contains both the web-server user and the "
|
||||
. "owner of this file. Change the ownership of the file, create a new group, or "
|
||||
. "temporarily make the file writeable by everyone during the install process.";
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
$testDetails[2] .= "The webserver user needs to be able to write to this file:\n$filename";
|
||||
}
|
||||
|
||||
$this->error($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public function requireTempFolder($testDetails) {
|
||||
$this->testing($testDetails);
|
||||
|
||||
try {
|
||||
$tempFolder = getTempFolder();
|
||||
} catch(Exception $e) {
|
||||
$tempFolder = false;
|
||||
}
|
||||
|
||||
if(!$tempFolder) {
|
||||
$testDetails[2] = "Permission problem gaining access to a temp directory. " .
|
||||
"Please create a folder named silverstripe-cache in the base directory " .
|
||||
"of the installation and ensure it has the adequate permissions.";
|
||||
$this->error($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
public function requireApacheModule($moduleName, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
if(!in_array($moduleName, apache_get_modules())) {
|
||||
$this->error($testDetails);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function testApacheRewriteExists($moduleName = 'mod_rewrite') {
|
||||
if(function_exists('apache_get_modules') && in_array($moduleName, apache_get_modules())) {
|
||||
return true;
|
||||
} elseif(isset($_SERVER['HTTP_MOD_REWRITE']) && $_SERVER['HTTP_MOD_REWRITE'] == 'On') {
|
||||
return true;
|
||||
} elseif(isset($_SERVER['REDIRECT_HTTP_MOD_REWRITE']) && $_SERVER['REDIRECT_HTTP_MOD_REWRITE'] == 'On') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function testIISRewriteModuleExists($moduleName = 'IIS_UrlRewriteModule') {
|
||||
if(isset($_SERVER[$moduleName]) && $_SERVER[$moduleName]) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function requireApacheRewriteModule($moduleName, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
if($this->testApacheRewriteExists()) {
|
||||
return true;
|
||||
} else {
|
||||
$this->warning($testDetails);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the web server has any rewriting capability.
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasRewritingCapability() {
|
||||
return ($this->testApacheRewriteExists() || $this->testIISRewriteModuleExists());
|
||||
}
|
||||
|
||||
public function requireIISRewriteModule($moduleName, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
if($this->testIISRewriteModuleExists()) {
|
||||
return true;
|
||||
} else {
|
||||
$this->warning($testDetails);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getDatabaseTypeNice($databaseClass) {
|
||||
return substr($databaseClass, 0, -8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of a helper class for the specific database.
|
||||
*
|
||||
* @param string $databaseClass e.g. MySQLDatabase or MSSQLDatabase
|
||||
* @return DatabaseConfigurationHelper
|
||||
*/
|
||||
public function getDatabaseConfigurationHelper($databaseClass) {
|
||||
return DatabaseAdapterRegistry::getDatabaseConfigurationHelper($databaseClass);
|
||||
}
|
||||
|
||||
public function requireDatabaseFunctions($databaseConfig, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
$helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']);
|
||||
if (!$helper) {
|
||||
$this->error($testDetails, "Couldn't load database helper code for ". $databaseConfig['type']);
|
||||
return false;
|
||||
}
|
||||
$result = $helper->requireDatabaseFunctions($databaseConfig);
|
||||
if($result) {
|
||||
return true;
|
||||
} else {
|
||||
$this->error($testDetails);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function requireDatabaseConnection($databaseConfig, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
$helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']);
|
||||
$result = $helper->requireDatabaseConnection($databaseConfig);
|
||||
if($result['success']) {
|
||||
return true;
|
||||
} else {
|
||||
$testDetails[2] .= ": " . $result['error'];
|
||||
$this->error($testDetails);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function requireDatabaseVersion($databaseConfig, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
$helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']);
|
||||
if(method_exists($helper, 'requireDatabaseVersion')) {
|
||||
$result = $helper->requireDatabaseVersion($databaseConfig);
|
||||
if($result['success']) {
|
||||
return true;
|
||||
} else {
|
||||
$testDetails[2] .= $result['error'];
|
||||
$this->warning($testDetails);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Skipped test because this database has no required version
|
||||
return true;
|
||||
}
|
||||
|
||||
public function requireDatabaseServer($databaseConfig, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
$helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']);
|
||||
$result = $helper->requireDatabaseServer($databaseConfig);
|
||||
if($result['success']) {
|
||||
return true;
|
||||
} else {
|
||||
$message = $testDetails[2] . ": " . $result['error'];
|
||||
$this->error($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function requireDatabaseOrCreatePermissions($databaseConfig, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
$helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']);
|
||||
$result = $helper->requireDatabaseOrCreatePermissions($databaseConfig);
|
||||
if($result['success']) {
|
||||
if($result['alreadyExists']) {
|
||||
$testDetails[3] = "Database $databaseConfig[database]";
|
||||
} else {
|
||||
$testDetails[3] = "Able to create a new database";
|
||||
}
|
||||
$this->testing($testDetails);
|
||||
return true;
|
||||
} else {
|
||||
if(empty($result['cannotCreate'])) {
|
||||
$message = $testDetails[2] . ". Please create the database manually.";
|
||||
} else {
|
||||
$message = $testDetails[2] . " (user '$databaseConfig[username]' doesn't have CREATE DATABASE permissions.)";
|
||||
}
|
||||
|
||||
$this->error($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function requireDatabaseAlterPermissions($databaseConfig, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
$helper = $this->getDatabaseConfigurationHelper($databaseConfig['type']);
|
||||
$result = $helper->requireDatabaseAlterPermissions($databaseConfig);
|
||||
if ($result['success']) {
|
||||
return true;
|
||||
} else {
|
||||
$message = "Silverstripe cannot alter tables. This won't prevent installation, however it may "
|
||||
. "cause issues if you try to run a /dev/build once installed.";
|
||||
$this->warning($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function requireServerVariables($varNames, $testDetails) {
|
||||
$this->testing($testDetails);
|
||||
$missing = array();
|
||||
|
||||
foreach($varNames as $varName) {
|
||||
if(!isset($_SERVER[$varName]) || !$_SERVER[$varName]) {
|
||||
$missing[] = '$_SERVER[' . $varName . ']';
|
||||
}
|
||||
}
|
||||
|
||||
if(!$missing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$message = $testDetails[2] . " (the following PHP variables are missing: " . implode(", ", $missing) . ")";
|
||||
$this->error($testDetails, $message);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function requirePostSupport($testDetails) {
|
||||
$this->testing($testDetails);
|
||||
|
||||
if(!isset($_POST)) {
|
||||
$this->error($testDetails);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isRunningWebServer($testDetails) {
|
||||
$this->testing($testDetails);
|
||||
if($testDetails[3]) {
|
||||
return true;
|
||||
} else {
|
||||
$this->warning($testDetails);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Must be PHP4 compatible
|
||||
var $baseDir;
|
||||
|
||||
public function getBaseDir() {
|
||||
// Cache the value so that when the installer mucks with SCRIPT_FILENAME half way through, this method
|
||||
// still returns the correct value.
|
||||
if(!$this->baseDir) $this->baseDir = realpath(dirname($_SERVER['SCRIPT_FILENAME'])) . DIRECTORY_SEPARATOR;
|
||||
return $this->baseDir;
|
||||
}
|
||||
|
||||
public function testing($testDetails) {
|
||||
if(!$testDetails) return;
|
||||
|
||||
$section = $testDetails[0];
|
||||
$test = $testDetails[1];
|
||||
|
||||
$message = "OK";
|
||||
if(isset($testDetails[3])) $message .= " ($testDetails[3])";
|
||||
|
||||
$this->tests[$section][$test] = array("good", $message);
|
||||
}
|
||||
|
||||
public function error($testDetails, $message = null) {
|
||||
if (!is_array($testDetails)) {
|
||||
throw new InvalidArgumentException("Invalid error");
|
||||
}
|
||||
$section = $testDetails[0];
|
||||
$test = $testDetails[1];
|
||||
if (!$message && isset($testDetails[2])) {
|
||||
$message = $testDetails[2];
|
||||
}
|
||||
|
||||
$this->tests[$section][$test] = array("error", $message);
|
||||
$this->errors[] = $testDetails;
|
||||
}
|
||||
|
||||
public function warning($testDetails, $message = null) {
|
||||
if (!is_array($testDetails)) {
|
||||
throw new InvalidArgumentException("Invalid warning");
|
||||
}
|
||||
$section = $testDetails[0];
|
||||
$test = $testDetails[1];
|
||||
if (!$message && isset($testDetails[2])) {
|
||||
$message = $testDetails[2];
|
||||
}
|
||||
|
||||
$this->tests[$section][$test] = array("warning", $message);
|
||||
$this->warnings[] = $testDetails;
|
||||
}
|
||||
|
||||
public function hasErrors() {
|
||||
return sizeof($this->errors);
|
||||
}
|
||||
|
||||
public function hasWarnings() {
|
||||
return sizeof($this->warnings);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Installer extends InstallRequirements {
|
||||
public function __construct() {
|
||||
// Cache the baseDir value
|
||||
$this->getBaseDir();
|
||||
}
|
||||
|
||||
public function install($config) {
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Installing SilverStripe...</title>
|
||||
<link rel="stylesheet" type="text/css" href="<?php echo FRAMEWORK_NAME; ?>/src/Dev/Install/client/styles/install.css"/>
|
||||
<script src="//code.jquery.com/jquery-1.7.2.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="install-header">
|
||||
<div class="inner">
|
||||
<div class="brand">
|
||||
<span class="logo"></span>
|
||||
|
||||
<h1>SilverStripe</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="Navigation"> </div>
|
||||
<div class="clear"><!-- --></div>
|
||||
|
||||
<div class="main">
|
||||
<div class="inner">
|
||||
<h2>Installing SilverStripe...</h2>
|
||||
|
||||
<p>I am now running through the installation steps (this should take about 30 seconds)</p>
|
||||
|
||||
<p>If you receive a fatal error, refresh this page to continue the installation</p>
|
||||
<ul>
|
||||
<?php
|
||||
|
||||
$webserver = $this->findWebserver();
|
||||
$isIIS = $this->isIIS();
|
||||
$isApache = $this->isApache();
|
||||
|
||||
flush();
|
||||
|
||||
if(isset($config['stats'])) {
|
||||
if(file_exists(FRAMEWORK_NAME . '/silverstripe_version')) {
|
||||
$silverstripe_version = file_get_contents(FRAMEWORK_NAME . '/silverstripe_version');
|
||||
} else {
|
||||
$silverstripe_version = "unknown";
|
||||
}
|
||||
|
||||
$phpVersion = urlencode(phpversion());
|
||||
$encWebserver = urlencode($webserver);
|
||||
$dbType = $config['db']['type'];
|
||||
|
||||
// Try to determine the database version from the helper
|
||||
$databaseVersion = $config['db']['type'];
|
||||
$helper = $this->getDatabaseConfigurationHelper($dbType);
|
||||
if($helper && method_exists($helper, 'getDatabaseVersion')) {
|
||||
$versionConfig = $config['db'][$dbType];
|
||||
$versionConfig['type'] = $dbType;
|
||||
$databaseVersion = urlencode($dbType . ': ' . $helper->getDatabaseVersion($versionConfig));
|
||||
}
|
||||
|
||||
$url = "http://ss2stat.silverstripe.com/Installation/add?SilverStripe=$silverstripe_version&PHP=$phpVersion&Database=$databaseVersion&WebServer=$encWebserver";
|
||||
|
||||
if(isset($_SESSION['StatsID']) && $_SESSION['StatsID']) {
|
||||
$url .= '&ID=' . $_SESSION['StatsID'];
|
||||
}
|
||||
|
||||
@$_SESSION['StatsID'] = file_get_contents($url);
|
||||
}
|
||||
|
||||
if(file_exists('mysite/_config.php')) {
|
||||
// Truncate the contents of _config instead of deleting it - we can't re-create it because Windows handles permissions slightly
|
||||
// differently to UNIX based filesystems - it takes the permissions from the parent directory instead of retaining them
|
||||
$fh = fopen('mysite/_config.php', 'wb');
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
// Escape user input for safe insertion into PHP file
|
||||
$theme = isset($_POST['template']) ? addcslashes($_POST['template'], "\'") : 'simple';
|
||||
$locale = isset($_POST['locale']) ? addcslashes($_POST['locale'], "\'") : 'en_US';
|
||||
$type = addcslashes($config['db']['type'], "\'");
|
||||
$dbConfig = $config['db'][$type];
|
||||
foreach ($dbConfig as &$configValue) {
|
||||
$configValue = addcslashes($configValue, "\\\'");
|
||||
}
|
||||
if(!isset($dbConfig['path'])) $dbConfig['path'] = '';
|
||||
if(!$dbConfig) {
|
||||
echo "<p style=\"color: red\">Bad config submitted</p><pre>";
|
||||
print_r($config);
|
||||
echo "</pre>";
|
||||
die();
|
||||
}
|
||||
|
||||
// Write the config file
|
||||
global $usingEnv;
|
||||
if($usingEnv) {
|
||||
$this->statusMessage("Setting up 'mysite/_config.php' for use with environment variables...");
|
||||
$this->writeToFile("mysite/_config.php", <<<PHP
|
||||
<?php
|
||||
|
||||
global \$project;
|
||||
\$project = 'mysite';
|
||||
|
||||
global \$database;
|
||||
\$database = '{$dbConfig['database']}';
|
||||
|
||||
require_once('conf/ConfigureFromEnv.php');
|
||||
|
||||
PHP
|
||||
);
|
||||
|
||||
} else {
|
||||
$this->statusMessage("Setting up 'mysite/_config.php'...");
|
||||
// Create databaseConfig
|
||||
$lines = array(
|
||||
$lines[] = "\t'type' => '$type'"
|
||||
);
|
||||
foreach($dbConfig as $key => $value) {
|
||||
$lines[] = "\t'{$key}' => '$value'";
|
||||
}
|
||||
$databaseConfigContent = implode(",\n", $lines);
|
||||
$this->writeToFile("mysite/_config.php", <<<PHP
|
||||
<?php
|
||||
|
||||
global \$project;
|
||||
\$project = 'mysite';
|
||||
|
||||
global \$databaseConfig;
|
||||
\$databaseConfig = array(
|
||||
{$databaseConfigContent}
|
||||
);
|
||||
|
||||
PHP
|
||||
);
|
||||
}
|
||||
|
||||
$this->statusMessage("Setting up 'mysite/_config/config.yml'");
|
||||
$this->writeToFile("mysite/_config/config.yml", <<<YML
|
||||
---
|
||||
Name: mysite
|
||||
---
|
||||
# YAML configuration for SilverStripe
|
||||
# See http://doc.silverstripe.org/framework/en/topics/configuration
|
||||
# Caution: Indentation through two spaces, not tabs
|
||||
SilverStripe\\View\\SSViewer:
|
||||
themes:
|
||||
- '$theme'
|
||||
- '\$default'
|
||||
SilverStripe\\i18n\\i18n:
|
||||
default_locale: '$locale'
|
||||
YML
|
||||
);
|
||||
|
||||
if(!$this->checkModuleExists('cms')) {
|
||||
$this->writeToFile("mysite/code/RootURLController.php", <<<PHP
|
||||
<?php
|
||||
|
||||
use SilverStripe\\Control\\Controller;
|
||||
|
||||
class RootURLController extends Controller {
|
||||
|
||||
public function index() {
|
||||
echo "<html>Your site is now set up. Start adding controllers to mysite to get started.</html>";
|
||||
}
|
||||
|
||||
}
|
||||
PHP
|
||||
);
|
||||
}
|
||||
|
||||
// Write the appropriate web server configuration file for rewriting support
|
||||
if($this->hasRewritingCapability()) {
|
||||
if($isApache) {
|
||||
$this->statusMessage("Setting up '.htaccess' file...");
|
||||
$this->createHtaccess();
|
||||
} elseif($isIIS) {
|
||||
$this->statusMessage("Setting up 'web.config' file...");
|
||||
$this->createWebConfig();
|
||||
}
|
||||
}
|
||||
|
||||
// Load the SilverStripe runtime
|
||||
$_SERVER['SCRIPT_FILENAME'] = dirname(realpath($_SERVER['SCRIPT_FILENAME'])) . '/' . FRAMEWORK_NAME . '/main.php';
|
||||
chdir(FRAMEWORK_NAME);
|
||||
|
||||
// Rebuild the manifest
|
||||
$_GET['flush'] = true;
|
||||
// Show errors as if you're in development mode
|
||||
$_SESSION['isDev'] = 1;
|
||||
|
||||
$this->statusMessage("Building database schema...");
|
||||
|
||||
require_once 'Core/Core.php';
|
||||
|
||||
// Build database
|
||||
$con = new Controller();
|
||||
$con->pushCurrent();
|
||||
|
||||
global $databaseConfig;
|
||||
DB::connect($databaseConfig);
|
||||
|
||||
$dbAdmin = new DatabaseAdmin();
|
||||
$dbAdmin->doInit();
|
||||
|
||||
$dbAdmin->doBuild(true);
|
||||
|
||||
// Create default administrator user and group in database
|
||||
// (not using Security::setDefaultAdmin())
|
||||
$adminMember = DefaultAdminService::singleton()->findOrCreateDefaultAdmin();
|
||||
$adminMember->Email = $config['admin']['username'];
|
||||
$adminMember->Password = $config['admin']['password'];
|
||||
$adminMember->PasswordEncryption = Security::config()->encryption_algorithm;
|
||||
|
||||
try {
|
||||
$this->statusMessage('Creating default CMS admin account...');
|
||||
$adminMember->write();
|
||||
} catch(Exception $e) {
|
||||
$this->statusMessage(
|
||||
sprintf('Warning: Default CMS admin account could not be created (error: %s)', $e->getMessage())
|
||||
);
|
||||
}
|
||||
|
||||
$_SESSION['username'] = $config['admin']['username'];
|
||||
$_SESSION['password'] = $config['admin']['password'];
|
||||
|
||||
if(!$this->errors) {
|
||||
if(isset($_SERVER['HTTP_HOST']) && $this->hasRewritingCapability()) {
|
||||
$this->statusMessage("Checking that friendly URLs work...");
|
||||
$this->checkRewrite();
|
||||
} else {
|
||||
require_once 'Core/Startup/ParameterConfirmationToken.php';
|
||||
$token = new ParameterConfirmationToken('flush');
|
||||
$params = http_build_query($token->params());
|
||||
|
||||
$destinationURL = 'index.php/' .
|
||||
($this->checkModuleExists('cms') ? "home/successfullyinstalled?$params" : "?$params");
|
||||
|
||||
echo <<<HTML
|
||||
<li>SilverStripe successfully installed; I am now redirecting you to your SilverStripe site...</li>
|
||||
<script>
|
||||
setTimeout(function() {
|
||||
window.location = "$destinationURL";
|
||||
}, 2000);
|
||||
</script>
|
||||
<noscript>
|
||||
<li><a href="$destinationURL">Click here to access your site.</a></li>
|
||||
</noscript>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
public function writeToFile($filename, $content) {
|
||||
$base = $this->getBaseDir();
|
||||
$this->statusMessage("Setting up $base$filename");
|
||||
|
||||
if((@$fh = fopen($base . $filename, 'wb')) && fwrite($fh, $content) && fclose($fh)) {
|
||||
return true;
|
||||
}
|
||||
$this->error("Couldn't write to file $base$filename");
|
||||
return false;
|
||||
}
|
||||
|
||||
public function createHtaccess() {
|
||||
$start = "### SILVERSTRIPE START ###\n";
|
||||
$end = "\n### SILVERSTRIPE END ###";
|
||||
|
||||
$base = dirname($_SERVER['SCRIPT_NAME']);
|
||||
if(defined('DIRECTORY_SEPARATOR')) $base = str_replace(DIRECTORY_SEPARATOR, '/', $base);
|
||||
else $base = str_replace("\\", '/', $base);
|
||||
|
||||
if($base != '.') $baseClause = "RewriteBase '$base'\n";
|
||||
else $baseClause = "";
|
||||
if(strpos(strtolower(php_sapi_name()), "cgi") !== false) $cgiClause = "RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\n";
|
||||
else $cgiClause = "";
|
||||
$modulePath = FRAMEWORK_NAME;
|
||||
$rewrite = <<<TEXT
|
||||
# Deny access to templates (but allow from localhost)
|
||||
<Files *.ss>
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
Allow from 127.0.0.1
|
||||
</Files>
|
||||
|
||||
# Deny access to IIS configuration
|
||||
<Files web.config>
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
</Files>
|
||||
|
||||
# Deny access to YAML configuration files which might include sensitive information
|
||||
<Files *.yml>
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</Files>
|
||||
|
||||
# Route errors to static pages automatically generated by SilverStripe
|
||||
ErrorDocument 404 /assets/error-404.html
|
||||
ErrorDocument 500 /assets/error-500.html
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
|
||||
# Turn off index.php handling requests to the homepage fixes issue in apache >=2.4
|
||||
<IfModule mod_dir.c>
|
||||
DirectoryIndex disabled
|
||||
</IfModule>
|
||||
|
||||
SetEnv HTTP_MOD_REWRITE On
|
||||
RewriteEngine On
|
||||
$baseClause
|
||||
$cgiClause
|
||||
|
||||
# Deny access to potentially sensitive files and folders
|
||||
RewriteRule ^vendor(/|$) - [F,L,NC]
|
||||
RewriteRule silverstripe-cache(/|$) - [F,L,NC]
|
||||
RewriteRule composer\.(json|lock) - [F,L,NC]
|
||||
|
||||
# Process through SilverStripe if no file with the requested name exists.
|
||||
# Pass through the original path as a query parameter, and retain the existing parameters.
|
||||
RewriteCond %{REQUEST_URI} ^(.*)$
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule .* $modulePath/main.php?url=%1 [QSA]
|
||||
</IfModule>
|
||||
TEXT;
|
||||
|
||||
if(file_exists('.htaccess')) {
|
||||
$htaccess = file_get_contents('.htaccess');
|
||||
|
||||
if(strpos($htaccess, '### SILVERSTRIPE START ###') === false && strpos($htaccess, '### SILVERSTRIPE END ###') === false) {
|
||||
$htaccess .= "\n### SILVERSTRIPE START ###\n### SILVERSTRIPE END ###\n";
|
||||
}
|
||||
|
||||
if(strpos($htaccess, '### SILVERSTRIPE START ###') !== false && strpos($htaccess, '### SILVERSTRIPE END ###') !== false) {
|
||||
$start = substr($htaccess, 0, strpos($htaccess, '### SILVERSTRIPE START ###')) . "### SILVERSTRIPE START ###\n";
|
||||
$end = "\n" . substr($htaccess, strpos($htaccess, '### SILVERSTRIPE END ###'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->writeToFile('.htaccess', $start . $rewrite . $end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes basic configuration to the web.config for IIS
|
||||
* so that rewriting capability can be use.
|
||||
*/
|
||||
public function createWebConfig() {
|
||||
$modulePath = FRAMEWORK_NAME;
|
||||
$content = <<<TEXT
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<system.webServer>
|
||||
<security>
|
||||
<requestFiltering>
|
||||
<hiddenSegments applyToWebDAV="false">
|
||||
<add segment="silverstripe-cache" />
|
||||
<add segment="vendor" />
|
||||
<add segment="composer.json" />
|
||||
<add segment="composer.lock" />
|
||||
</hiddenSegments>
|
||||
<fileExtensions allowUnlisted="true" >
|
||||
<add fileExtension=".ss" allowed="false"/>
|
||||
<add fileExtension=".yml" allowed="false"/>
|
||||
</fileExtensions>
|
||||
</requestFiltering>
|
||||
</security>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<rule name="SilverStripe Clean URLs" stopProcessing="true">
|
||||
<match url="^(.*)$" />
|
||||
<conditions>
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="$modulePath/main.php?url={R:1}" appendQueryString="true" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
TEXT;
|
||||
|
||||
$this->writeToFile('web.config', $content);
|
||||
}
|
||||
|
||||
public function checkRewrite() {
|
||||
require_once 'Core/Startup/ParameterConfirmationToken.php';
|
||||
$token = new ParameterConfirmationToken('flush');
|
||||
$params = http_build_query($token->params());
|
||||
|
||||
$destinationURL = str_replace('install.php', '', $_SERVER['SCRIPT_NAME']) .
|
||||
($this->checkModuleExists('cms') ? "home/successfullyinstalled?$params" : "?$params");
|
||||
|
||||
echo <<<HTML
|
||||
<li id="ModRewriteResult">Testing...</li>
|
||||
<script>
|
||||
if(typeof $ == 'undefined') {
|
||||
document.getElemenyById('ModeRewriteResult').innerHTML = "I can't run jQuery ajax to set rewriting; I will redirect you to the homepage to see if everything is working.";
|
||||
setTimeout(function() {
|
||||
window.location = "$destinationURL";
|
||||
}, 10000);
|
||||
} else {
|
||||
$.ajax({
|
||||
method: 'get',
|
||||
url: 'InstallerTest/testrewrite',
|
||||
complete: function(response) {
|
||||
var r = response.responseText.replace(/[^A-Z]?/g,"");
|
||||
if(r === "OK") {
|
||||
$('#ModRewriteResult').html("Friendly URLs set up successfully; I am now redirecting you to your SilverStripe site...")
|
||||
setTimeout(function() {
|
||||
window.location = "$destinationURL";
|
||||
}, 2000);
|
||||
} else {
|
||||
$('#ModRewriteResult').html("Friendly URLs are not working. This is most likely because a rewrite module isn't configured "
|
||||
+ "correctly on your site. You may need to get your web host or server administrator to do this for you: "
|
||||
+ "<ul>"
|
||||
+ "<li><strong>mod_rewrite</strong> or other rewrite module is enabled on your web server</li>"
|
||||
+ "<li><strong>AllowOverride All</strong> is set for the directory where SilverStripe is installed</li>"
|
||||
+ "</ul>");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<noscript>
|
||||
<li><a href="$destinationURL">Click here</a> to check friendly URLs are working. If you get a 404 then something is wrong.</li>
|
||||
</noscript>
|
||||
HTML;
|
||||
}
|
||||
|
||||
public function var_export_array_nokeys($array) {
|
||||
$retval = "array(\n";
|
||||
foreach($array as $item) {
|
||||
$retval .= "\t'";
|
||||
$retval .= trim($item);
|
||||
$retval .= "',\n";
|
||||
}
|
||||
$retval .= ")";
|
||||
return $retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an installation status message.
|
||||
* The output differs depending on whether this is CLI or web based
|
||||
*
|
||||
* @param string $msg
|
||||
*/
|
||||
public function statusMessage($msg) {
|
||||
echo "<li>$msg</li>\n";
|
||||
flush();
|
||||
}
|
||||
}
|
281
src/Dev/Install/install5.php
Executable file
281
src/Dev/Install/install5.php
Executable file
@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
/************************************************************************************
|
||||
************************************************************************************
|
||||
** **
|
||||
** If you can read this text in your browser then you don't have PHP installed. **
|
||||
** Please install PHP 5.5.0 or higher. **
|
||||
** **
|
||||
************************************************************************************
|
||||
************************************************************************************/
|
||||
|
||||
namespace SilverStripe\Dev\Install;
|
||||
|
||||
// Back up original ini config
|
||||
$originalIni = [];
|
||||
$iniSet = function ($name, $value) use (&$originalIni) {
|
||||
if (!isset($originalIni[$name])) {
|
||||
$originalIni[$name] = ini_get($name);
|
||||
}
|
||||
ini_set($name, $value);
|
||||
};
|
||||
|
||||
// speed up mysql_connect timeout if the server can't be found
|
||||
$iniSet('mysql.connect_timeout', 5);
|
||||
// Don't die half was through installation; that does more harm than good
|
||||
$iniSet('max_execution_time', 0);
|
||||
|
||||
// set display_errors php setting to on to force installer to avoid blank screen of death.
|
||||
// get the original value so it can be used in PHP requirement checks later in this script.
|
||||
$iniSet('display_errors', '1');
|
||||
|
||||
error_reporting(E_ALL | E_STRICT);
|
||||
|
||||
// Attempt to start a session so that the username and password can be sent back to the user.
|
||||
if (function_exists('session_start') && !session_id()) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// require composers autoloader
|
||||
require __DIR__ . '/../../includes/autoload.php';
|
||||
|
||||
$usingEnv = !empty($_REQUEST['useEnv']);
|
||||
|
||||
// Set default locale, but try and sniff from the user agent
|
||||
$defaultLocale = 'en_US';
|
||||
$locales = array(
|
||||
'af_ZA' => 'Afrikaans (South Africa)',
|
||||
'ar_EG' => 'Arabic (Egypt)',
|
||||
'hy_AM' => 'Armenian (Armenia)',
|
||||
'ast_ES' => 'Asturian (Spain)',
|
||||
'az_AZ' => 'Azerbaijani (Azerbaijan)',
|
||||
'bs_BA' => 'Bosnian (Bosnia and Herzegovina)',
|
||||
'bg_BG' => 'Bulgarian (Bulgaria)',
|
||||
'ca_ES' => 'Catalan (Spain)',
|
||||
'zh_CN' => 'Chinese (China)',
|
||||
'zh_TW' => 'Chinese (Taiwan)',
|
||||
'hr_HR' => 'Croatian (Croatia)',
|
||||
'cs_CZ' => 'Czech (Czech Republic)',
|
||||
'da_DK' => 'Danish (Denmark)',
|
||||
'nl_NL' => 'Dutch (Netherlands)',
|
||||
'en_GB' => 'English (United Kingdom)',
|
||||
'en_US' => 'English (United States)',
|
||||
'eo_XX' => 'Esperanto',
|
||||
'et_EE' => 'Estonian (Estonia)',
|
||||
'fo_FO' => 'Faroese (Faroe Islands)',
|
||||
'fi_FI' => 'Finnish (Finland)',
|
||||
'fr_FR' => 'French (France)',
|
||||
'de_DE' => 'German (Germany)',
|
||||
'el_GR' => 'Greek (Greece)',
|
||||
'he_IL' => 'Hebrew (Israel)',
|
||||
'hu_HU' => 'Hungarian (Hungary)',
|
||||
'is_IS' => 'Icelandic (Iceland)',
|
||||
'id_ID' => 'Indonesian (Indonesia)',
|
||||
'it_IT' => 'Italian (Italy)',
|
||||
'ja_JP' => 'Japanese (Japan)',
|
||||
'km_KH' => 'Khmer (Cambodia)',
|
||||
'lc_XX' => 'LOLCAT',
|
||||
'lv_LV' => 'Latvian (Latvia)',
|
||||
'lt_LT' => 'Lithuanian (Lithuania)',
|
||||
'ms_MY' => 'Malay (Malaysia)',
|
||||
'mi_NZ' => 'Maori (New Zealand)',
|
||||
'ne_NP' => 'Nepali (Nepal)',
|
||||
'nb_NO' => 'Norwegian',
|
||||
'fa_IR' => 'Persian (Iran)',
|
||||
'pl_PL' => 'Polish (Poland)',
|
||||
'pt_BR' => 'Portuguese (Brazil)',
|
||||
'pa_IN' => 'Punjabi (India)',
|
||||
'ro_RO' => 'Romanian (Romania)',
|
||||
'ru_RU' => 'Russian (Russia)',
|
||||
'sr_RS' => 'Serbian (Serbia)',
|
||||
'si_LK' => 'Sinhalese (Sri Lanka)',
|
||||
'sk_SK' => 'Slovak (Slovakia)',
|
||||
'sl_SI' => 'Slovenian (Slovenia)',
|
||||
'es_AR' => 'Spanish (Argentina)',
|
||||
'es_MX' => 'Spanish (Mexico)',
|
||||
'es_ES' => 'Spanish (Spain)',
|
||||
'sv_SE' => 'Swedish (Sweden)',
|
||||
'th_TH' => 'Thai (Thailand)',
|
||||
'tr_TR' => 'Turkish (Turkey)',
|
||||
'uk_UA' => 'Ukrainian (Ukraine)',
|
||||
'uz_UZ' => 'Uzbek (Uzbekistan)',
|
||||
'vi_VN' => 'Vietnamese (Vietnam)',
|
||||
);
|
||||
|
||||
// Discover which databases are available
|
||||
DatabaseAdapterRegistry::autodiscover();
|
||||
|
||||
// Determine which external database modules are USABLE
|
||||
$databaseClasses = DatabaseAdapterRegistry::get_adapters();
|
||||
foreach ($databaseClasses as $class => $details) {
|
||||
$helper = DatabaseAdapterRegistry::getDatabaseConfigurationHelper($class);
|
||||
$databaseClasses[$class]['hasModule'] = !empty($helper);
|
||||
}
|
||||
|
||||
// Load database config
|
||||
if (isset($_REQUEST['db'])) {
|
||||
if (isset($_REQUEST['db']['type'])) {
|
||||
$type = $_REQUEST['db']['type'];
|
||||
} else {
|
||||
if ($type = getenv('SS_DATABASE_CLASS')) {
|
||||
$_REQUEST['db']['type'] = $type;
|
||||
} elseif ($databaseClasses['MySQLPDODatabase']['supported']) {
|
||||
$type = $_REQUEST['db']['type'] = 'MySQLPDODatabase';
|
||||
} elseif ($databaseClasses['MySQLDatabase']['supported']) {
|
||||
$type = $_REQUEST['db']['type'] = 'MySQLDatabase';
|
||||
} else {
|
||||
// handle error
|
||||
}
|
||||
}
|
||||
|
||||
// Disabled inputs don't submit anything - we need to use the environment (except the database name)
|
||||
if ($usingEnv) {
|
||||
$_REQUEST['db'][$type] = $databaseConfig = array(
|
||||
"type" => getenv('SS_DATABASE_CLASS') ?: $type,
|
||||
"server" => getenv('SS_DATABASE_SERVER') ?: "localhost",
|
||||
"username" => getenv('SS_DATABASE_USERNAME') ?: "root",
|
||||
"password" => getenv('SS_DATABASE_PASSWORD') ?: "",
|
||||
"database" => $_REQUEST['db'][$type]['database'],
|
||||
);
|
||||
} else {
|
||||
// Normal behaviour without the environment
|
||||
$databaseConfig = $_REQUEST['db'][$type];
|
||||
$databaseConfig['type'] = $type;
|
||||
}
|
||||
} else {
|
||||
if ($type = getenv('SS_DATABASE_CLASS')) {
|
||||
$_REQUEST['db']['type'] = $type;
|
||||
} elseif ($databaseClasses['MySQLPDODatabase']['supported']) {
|
||||
$type = $_REQUEST['db']['type'] = 'MySQLPDODatabase';
|
||||
} elseif ($databaseClasses['MySQLDatabase']['supported']) {
|
||||
$type = $_REQUEST['db']['type'] = 'MySQLDatabase';
|
||||
} else {
|
||||
// handle error
|
||||
}
|
||||
$_REQUEST['db'][$type] = $databaseConfig = array(
|
||||
"type" => $type,
|
||||
"server" => getenv('SS_DATABASE_SERVER') ?: "localhost",
|
||||
"username" => getenv('SS_DATABASE_USERNAME') ?: "root",
|
||||
"password" => getenv('SS_DATABASE_PASSWORD') ?: "",
|
||||
"database" => isset($_SERVER['argv'][2]) ? $_SERVER['argv'][2] : "SS_mysite",
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($_REQUEST['admin'])) {
|
||||
// Disabled inputs don't submit anything - we need to use the environment (except the database name)
|
||||
if ($usingEnv) {
|
||||
$_REQUEST['admin'] = $adminConfig = array(
|
||||
'username' => getenv('SS_DEFAULT_ADMIN_USERNAME') ?: 'admin',
|
||||
'password' => getenv('SS_DEFAULT_ADMIN_PASSWORD') ?: '',
|
||||
);
|
||||
} else {
|
||||
$adminConfig = $_REQUEST['admin'];
|
||||
}
|
||||
} else {
|
||||
$_REQUEST['admin'] = $adminConfig = array(
|
||||
'username' => getenv('SS_DEFAULT_ADMIN_USERNAME') ?: 'admin',
|
||||
'password' => getenv('SS_DEFAULT_ADMIN_PASSWORD') ?: '',
|
||||
);
|
||||
}
|
||||
|
||||
$alreadyInstalled = false;
|
||||
if (file_exists('mysite/_config.php')) {
|
||||
// Find the $database variable in the relevant config file without having to execute the config file
|
||||
if (preg_match("/\\\$database\s*=\s*[^\n\r]+[\n\r]/", file_get_contents("mysite/_config.php"), $parts)) {
|
||||
eval($parts[0]);
|
||||
if (!empty($database)) {
|
||||
$alreadyInstalled = true;
|
||||
}
|
||||
// Assume that if $databaseConfig is defined in mysite/_config.php, then a non-environment-based installation has
|
||||
// already gone ahead
|
||||
} elseif (preg_match(
|
||||
"/\\\$databaseConfig\s*=\s*[^\n\r]+[\n\r]/",
|
||||
file_get_contents("mysite/_config.php"),
|
||||
$parts
|
||||
)) {
|
||||
$alreadyInstalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (file_exists(FRAMEWORK_NAME . '/silverstripe_version')) {
|
||||
$silverstripe_version = file_get_contents(FRAMEWORK_NAME . '/silverstripe_version');
|
||||
} else {
|
||||
$silverstripe_version = "unknown";
|
||||
}
|
||||
|
||||
// Check requirements
|
||||
$req = new InstallRequirements($originalIni);
|
||||
$req->check();
|
||||
|
||||
if ($req->isIIS()) {
|
||||
$webserverConfigFile = 'web.config';
|
||||
} else {
|
||||
$webserverConfigFile = '.htaccess';
|
||||
}
|
||||
|
||||
$hasErrorOtherThanDatabase = false;
|
||||
$hasOnlyWarnings = false;
|
||||
$phpIniLocation = php_ini_loaded_file();
|
||||
if ($req->hasErrors()) {
|
||||
$hasErrorOtherThanDatabase = true;
|
||||
} elseif ($req->hasWarnings()) {
|
||||
$hasOnlyWarnings = true;
|
||||
}
|
||||
|
||||
$dbReq = new InstallRequirements();
|
||||
if ($databaseConfig) {
|
||||
$dbReq->checkDatabase($databaseConfig);
|
||||
}
|
||||
|
||||
$adminReq = new InstallRequirements();
|
||||
if ($adminConfig) {
|
||||
$adminReq->checkAdminConfig($adminConfig);
|
||||
}
|
||||
|
||||
// Actual processor
|
||||
$installFromCli = (isset($_SERVER['argv'][1]) && $_SERVER['argv'][1] == 'install');
|
||||
|
||||
// CLI-install error message. exit(1) will halt any makefile.
|
||||
if ($installFromCli && ($req->hasErrors() || $dbReq->hasErrors())) {
|
||||
echo "Cannot install due to errors:\n";
|
||||
$req->listErrors();
|
||||
$dbReq->listErrors();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// config-form.html vars (placeholder to prevent deletion)
|
||||
[
|
||||
$defaultLocale,
|
||||
$silverstripe_version,
|
||||
$locales,
|
||||
$webserverConfigFile,
|
||||
$hasErrorOtherThanDatabase,
|
||||
$hasOnlyWarnings, // If warnings but not errors
|
||||
$phpIniLocation
|
||||
];
|
||||
|
||||
if ((isset($_REQUEST['go']) || $installFromCli)
|
||||
&& !$req->hasErrors()
|
||||
&& !$dbReq->hasErrors()
|
||||
&& $adminConfig['username']
|
||||
&& $adminConfig['password']
|
||||
) {
|
||||
// Confirm before reinstalling
|
||||
if (!$installFromCli && $alreadyInstalled) {
|
||||
include(__DIR__ . '/config-form.html');
|
||||
} else {
|
||||
$inst = new Installer();
|
||||
if ($_REQUEST) {
|
||||
$inst->install($_REQUEST);
|
||||
} else {
|
||||
$inst->install(array(
|
||||
'db' => $databaseConfig,
|
||||
'admin' => $adminConfig,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Show the config form
|
||||
} else {
|
||||
include(__DIR__ . '/config-form.html');
|
||||
}
|
@ -1,18 +1,18 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>PHP 5.5.0 is required</title>
|
||||
<title>PHP 5.6.0 is required</title>
|
||||
<link rel="stylesheet" type="text/css" href="framework/src/Dev/Install/client/styles/install.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="BgContainer">
|
||||
<div id="Container">
|
||||
<div id="Header">
|
||||
<h1>PHP 5.5.0 required</h1>
|
||||
<h1>PHP 5.6.0 required</h1>
|
||||
<div class="left">
|
||||
<h3>To run SilverStripe, please install PHP 5.5.0 or greater.</h3>
|
||||
<h3>To run SilverStripe, please install PHP 5.6.0 or greater.</h3>
|
||||
|
||||
<p>We have detected that you are running PHP version <b>$PHPVersion</b>. In order to run SilverStripe,
|
||||
you must have PHP version 5.5.0 or higher.<p/>
|
||||
you must have PHP version 5.6.0 or higher.<p/>
|
||||
|
||||
<p>If you are running on a shared host, you may need to ask your hosting provider how to do this.</p>
|
||||
</div>
|
||||
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
// Ensure this class can be autoloaded when installed without dev dependencies.
|
||||
// It's included by default through composer's autoloading.
|
||||
// class_exists() triggers PSR-4 autoloaders, which should discover if PHPUnit is installed.
|
||||
// TODO Factor out SapphireTest references from non-dev core code (avoid autoloading in the first place)
|
||||
namespace {
|
||||
|
||||
if (!class_exists('PHPUnit_Framework_TestCase')) {
|
||||
class PHPUnit_Framework_TestCase
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,62 +2,51 @@
|
||||
|
||||
namespace SilverStripe\Dev;
|
||||
|
||||
use Exception;
|
||||
use LogicException;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
use SilverStripe\CMS\Controllers\RootURLController;
|
||||
use SilverStripe\Control\CLIRequestBuilder;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Cookie;
|
||||
use SilverStripe\Control\Director;
|
||||
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\Control\HTTPRequest;
|
||||
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\Extension;
|
||||
use SilverStripe\Core\Flushable;
|
||||
use SilverStripe\Control\HTTPApplication;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Manifest\ClassManifest;
|
||||
use SilverStripe\Core\Injector\InjectorLoader;
|
||||
use SilverStripe\Core\Manifest\ClassLoader;
|
||||
use SilverStripe\Core\Resettable;
|
||||
use SilverStripe\Dev\State\SapphireTestState;
|
||||
use SilverStripe\Dev\State\TestState;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\ORM\DataExtension;
|
||||
use SilverStripe\ORM\SS_List;
|
||||
use SilverStripe\Security\IdentityStore;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
use SilverStripe\ORM\Connect\TempDatabase;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\ORM\SS_List;
|
||||
use SilverStripe\Security\Group;
|
||||
use SilverStripe\Security\IdentityStore;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Permission;
|
||||
use SilverStripe\View\Requirements;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\View\SSViewer;
|
||||
use SilverStripe\View\ThemeResourceLoader;
|
||||
use SilverStripe\View\ThemeManifest;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
use Translatable;
|
||||
use LogicException;
|
||||
use Exception;
|
||||
|
||||
if (!class_exists(PHPUnit_Framework_TestCase::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case class for the Sapphire framework.
|
||||
* Sapphire unit testing is based on PHPUnit, but provides a number of hooks into our data model that make it easier
|
||||
* to work with.
|
||||
*
|
||||
* This class should not be used anywhere outside of unit tests, as phpunit may not be installed
|
||||
* in production sites.
|
||||
*/
|
||||
class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
|
||||
{
|
||||
|
||||
/** @config */
|
||||
private static $dependencies = array(
|
||||
'fixtureFactory' => '%$FixtureFactory',
|
||||
);
|
||||
|
||||
/**
|
||||
* Path to fixture data for this test run.
|
||||
* If passed as an array, multiple fixture files will be loaded.
|
||||
@ -77,38 +66,22 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
* @var Boolean If set to TRUE, this will force a test database to be generated
|
||||
* in {@link setUp()}. Note that this flag is overruled by the presence of a
|
||||
* {@link $fixture_file}, which always forces a database build.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $usesDatabase = null;
|
||||
protected $originalMemberPasswordValidator;
|
||||
protected $originalRequirements;
|
||||
protected $originalIsRunningTest;
|
||||
protected $originalNestedURLsState;
|
||||
protected $originalMemoryLimit;
|
||||
|
||||
/**
|
||||
* @var TestMailer
|
||||
*/
|
||||
protected $mailer;
|
||||
|
||||
/**
|
||||
* Pointer to the manifest that isn't a test manifest
|
||||
*/
|
||||
protected static $regular_manifest;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
* @var bool
|
||||
*/
|
||||
protected static $is_running_test = false;
|
||||
|
||||
/**
|
||||
* @var ClassManifest
|
||||
*/
|
||||
protected static $test_class_manifest;
|
||||
|
||||
/**
|
||||
* By default, setUp() does not require default records. Pass
|
||||
* class names in here, and the require/augment default records
|
||||
* function will be called on them.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $requireDefaultRecordsFrom = array();
|
||||
|
||||
@ -120,6 +93,8 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
* the values are an array of illegal extensions on that class.
|
||||
*
|
||||
* Set a class to `*` to remove all extensions (unadvised)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $illegal_extensions = [];
|
||||
|
||||
@ -134,6 +109,8 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
* <code>
|
||||
* array("MyTreeDataObject" => array("Versioned", "Hierarchy"))
|
||||
* </code>
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $required_extensions = [];
|
||||
|
||||
@ -141,6 +118,8 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
* By default, the test database won't contain any DataObjects that have the interface TestOnly.
|
||||
* This variable lets you define additional TestOnly DataObjects to set up for this test.
|
||||
* Set it to an array of DataObject subclass names.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $extra_dataobjects = [];
|
||||
|
||||
@ -161,58 +140,60 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
protected $backupGlobals = false;
|
||||
|
||||
/**
|
||||
* Helper arrays for illegal_extensions/required_extensions code
|
||||
* State management container for SapphireTest
|
||||
*
|
||||
* @var TestState
|
||||
*/
|
||||
private static $extensions_to_reapply = [];
|
||||
|
||||
private static $extensions_to_remove = [];
|
||||
protected static $state = null;
|
||||
|
||||
/**
|
||||
* Check flushables on setupBeforeClass()
|
||||
* Temp database helper
|
||||
*
|
||||
* @var bool
|
||||
* @var TempDatabase
|
||||
*/
|
||||
protected static $flushedFlushables = false;
|
||||
protected static $tempDB = null;
|
||||
|
||||
/**
|
||||
* Determines if unit tests are currently run, flag set during test bootstrap.
|
||||
* This is used as a cheap replacement for fully mockable state
|
||||
* in certain contiditions (e.g. access checks).
|
||||
* Caution: When set to FALSE, certain controllers might bypass
|
||||
* access checks, so this is a very security sensitive setting.
|
||||
* Gets illegal extensions for this class
|
||||
*
|
||||
* @return boolean
|
||||
* @return array
|
||||
*/
|
||||
public static function is_running_test()
|
||||
public static function getIllegalExtensions()
|
||||
{
|
||||
return static::$illegal_extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets required extensions for this class
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getRequiredExtensions()
|
||||
{
|
||||
return static::$required_extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if test bootstrapping has been performed. Must not be relied on
|
||||
* outside of unit tests.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_running_test()
|
||||
{
|
||||
return self::$is_running_test;
|
||||
}
|
||||
|
||||
public static function set_is_running_test($bool)
|
||||
/**
|
||||
* Set test running state
|
||||
*
|
||||
* @param bool $bool
|
||||
*/
|
||||
protected static function set_is_running_test($bool)
|
||||
{
|
||||
self::$is_running_test = $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the manifest to be used to look up test classes by helper functions
|
||||
*
|
||||
* @param ClassManifest $manifest
|
||||
*/
|
||||
public static function set_test_class_manifest($manifest)
|
||||
{
|
||||
self::$test_class_manifest = $manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the manifest being used to look up test classes by helper functions
|
||||
*
|
||||
* @return ClassManifest
|
||||
*/
|
||||
public static function get_test_class_manifest()
|
||||
{
|
||||
return self::$test_class_manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String
|
||||
*/
|
||||
@ -221,27 +202,19 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
return static::$fixture_file;
|
||||
}
|
||||
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* State of Versioned before this test is run
|
||||
* Setup the test.
|
||||
* Always sets up in order:
|
||||
* - Reset php state
|
||||
* - Nest
|
||||
* - Custom state helpers
|
||||
*
|
||||
* @var string
|
||||
* User code should call parent::setUp() before custom setup code
|
||||
*/
|
||||
protected $originalReadingMode = null;
|
||||
|
||||
protected $originalEnv = null;
|
||||
|
||||
protected 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();
|
||||
if (class_exists(Versioned::class)) {
|
||||
$this->originalReadingMode = Versioned::get_reading_mode();
|
||||
}
|
||||
// Call state helpers
|
||||
static::$state->setUp($this);
|
||||
|
||||
// We cannot run the tests on this abstract class.
|
||||
if (static::class == __CLASS__) {
|
||||
@ -249,34 +222,18 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark test as being run
|
||||
$this->originalIsRunningTest = self::$is_running_test;
|
||||
self::$is_running_test = true;
|
||||
|
||||
// i18n needs to be set to the defaults or tests fail
|
||||
i18n::set_locale(i18n::config()->uninherited('default_locale'));
|
||||
|
||||
// Set default timezone consistently to avoid NZ-specific dependencies
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
// Remove password validation
|
||||
$this->originalMemberPasswordValidator = Member::password_validator();
|
||||
$this->originalRequirements = Requirements::backend();
|
||||
Member::set_password_validator(null);
|
||||
Cookie::config()->update('report_errors', false);
|
||||
if (class_exists(RootURLController::class)) {
|
||||
RootURLController::reset();
|
||||
}
|
||||
|
||||
// Reset all resettables
|
||||
/** @var Resettable $resettable */
|
||||
foreach (ClassInfo::implementorsOf(Resettable::class) as $resettable) {
|
||||
$resettable::reset();
|
||||
}
|
||||
|
||||
if (Controller::has_curr()) {
|
||||
Controller::curr()->setSession(Session::create(array()));
|
||||
}
|
||||
Security::clear_database_is_ready();
|
||||
|
||||
// Set up test routes
|
||||
@ -284,18 +241,15 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
|
||||
$fixtureFiles = $this->getFixturePaths();
|
||||
|
||||
// Todo: this could be a special test model
|
||||
$this->model = DataModel::inst();
|
||||
|
||||
// Set up fixture
|
||||
if ($fixtureFiles || $this->usesDatabase) {
|
||||
if (!self::using_temp_db()) {
|
||||
self::create_temp_db();
|
||||
if (!static::$tempDB->isUsed()) {
|
||||
static::$tempDB->build();
|
||||
}
|
||||
|
||||
DataObject::singleton()->flushCache();
|
||||
|
||||
self::empty_temp_db();
|
||||
static::$tempDB->clearAllData();
|
||||
|
||||
foreach ($this->requireDefaultRecordsFrom as $className) {
|
||||
$instance = singleton($className);
|
||||
@ -315,18 +269,11 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
$this->logInWithPermission("ADMIN");
|
||||
}
|
||||
|
||||
// Preserve memory settings
|
||||
$this->originalMemoryLimit = ini_get('memory_limit');
|
||||
|
||||
// turn off template debugging
|
||||
SSViewer::config()->update('source_file_comments', false);
|
||||
|
||||
// Clear requirements
|
||||
Requirements::clear();
|
||||
|
||||
// Set up the test mailer
|
||||
$this->mailer = new TestMailer();
|
||||
Injector::inst()->registerService($this->mailer, Mailer::class);
|
||||
Injector::inst()->registerService(new TestMailer(), Mailer::class);
|
||||
Email::config()->remove('send_all_emails_to');
|
||||
Email::config()->remove('send_all_emails_from');
|
||||
Email::config()->remove('cc_all_emails_to');
|
||||
@ -340,126 +287,47 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
* don't change state for any called method inside the test,
|
||||
* e.g. dynamically adding an extension. See {@link teardownAfterClass()}
|
||||
* for tearing down the state again.
|
||||
*
|
||||
* Always sets up in order:
|
||||
* - Reset php state
|
||||
* - Nest
|
||||
* - Custom state helpers
|
||||
*
|
||||
* User code should call parent::setUpBeforeClass() before custom setup code
|
||||
*/
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
// Start tests
|
||||
static::start();
|
||||
|
||||
//nest config and injector for each suite so they are effectively sandboxed
|
||||
Config::nest();
|
||||
Injector::nest();
|
||||
$isAltered = false;
|
||||
// Call state helpers
|
||||
static::$state->setUpOnce(static::class);
|
||||
|
||||
if (!Director::isDev()) {
|
||||
user_error('Tests can only run in "dev" mode', E_USER_ERROR);
|
||||
}
|
||||
|
||||
// Remove any illegal extensions that are present
|
||||
foreach (static::$illegal_extensions as $class => $extensions) {
|
||||
if (!class_exists($class)) {
|
||||
continue;
|
||||
}
|
||||
if ($extensions === '*') {
|
||||
$extensions = $class::get_extensions();
|
||||
}
|
||||
foreach ($extensions as $extension) {
|
||||
if (!class_exists($extension) || !$class::has_extension($extension)) {
|
||||
continue;
|
||||
}
|
||||
if (!isset(self::$extensions_to_reapply[$class])) {
|
||||
self::$extensions_to_reapply[$class] = array();
|
||||
}
|
||||
self::$extensions_to_reapply[$class][] = $extension;
|
||||
$class::remove_extension($extension);
|
||||
$isAltered = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Add any required extensions that aren't present
|
||||
foreach (static::$required_extensions as $class => $extensions) {
|
||||
if (!class_exists($class)) {
|
||||
$self = static::class;
|
||||
throw new LogicException("Test {$self} requires class {$class} which doesn't exist");
|
||||
}
|
||||
self::$extensions_to_remove[$class] = array();
|
||||
foreach ($extensions as $extension) {
|
||||
$extensionClass = Extension::get_classname_without_arguments($extension);
|
||||
if (!class_exists($extensionClass)) {
|
||||
$self = static::class;
|
||||
throw new LogicException("Test {$self} requires extension {$extension} which doesn't exist");
|
||||
}
|
||||
if (!$class::has_extension($extension)) {
|
||||
if (!isset(self::$extensions_to_remove[$class])) {
|
||||
self::$extensions_to_reapply[$class] = array();
|
||||
}
|
||||
self::$extensions_to_remove[$class][] = $extension;
|
||||
$class::add_extension($extension);
|
||||
$isAltered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have made changes to the extensions present, then migrate the database schema.
|
||||
if ($isAltered || self::$extensions_to_reapply || self::$extensions_to_remove || static::getExtraDataObjects()) {
|
||||
// Build DB if we have objects
|
||||
if (static::getExtraDataObjects()) {
|
||||
DataObject::reset();
|
||||
if (!self::using_temp_db()) {
|
||||
self::create_temp_db();
|
||||
}
|
||||
static::resetDBSchema(true);
|
||||
}
|
||||
// clear singletons, they're caching old extension info
|
||||
// which is used in DatabaseAdmin->doBuild()
|
||||
Injector::inst()->unregisterAllObjects();
|
||||
|
||||
// Set default timezone consistently to avoid NZ-specific dependencies
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
// Flush all flushable records
|
||||
$flush = !empty($_GET['flush']);
|
||||
if (!self::$flushedFlushables && $flush) {
|
||||
self::$flushedFlushables = true;
|
||||
foreach (ClassInfo::implementorsOf(Flushable::class) as $class) {
|
||||
$class::flush();
|
||||
}
|
||||
static::resetDBSchema(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tearDown method that's called once per test class rather once per test method.
|
||||
*
|
||||
* Always sets up in order:
|
||||
* - Custom state helpers
|
||||
* - Unnest
|
||||
* - Reset php state
|
||||
*
|
||||
* User code should call parent::tearDownAfterClass() after custom tear down code
|
||||
*/
|
||||
public static function tearDownAfterClass()
|
||||
{
|
||||
// If we have made changes to the extensions present, then migrate the database schema.
|
||||
if (self::$extensions_to_reapply || self::$extensions_to_remove) {
|
||||
// @todo: This isn't strictly necessary to restore extensions, but only to ensure that
|
||||
// Object::$extra_methods is properly flushed. This should be replaced with a simple
|
||||
// flush mechanism for each $class.
|
||||
//
|
||||
// Remove extensions added for testing
|
||||
foreach (self::$extensions_to_remove as $class => $extensions) {
|
||||
foreach ($extensions as $extension) {
|
||||
$class::remove_extension($extension);
|
||||
}
|
||||
}
|
||||
// Call state helpers
|
||||
static::$state->tearDownOnce(static::class);
|
||||
|
||||
// Reapply ones removed
|
||||
foreach (self::$extensions_to_reapply as $class => $extensions) {
|
||||
foreach ($extensions as $extension) {
|
||||
$class::add_extension($extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//unnest injector / config now that the test suite is over
|
||||
// this will reset all the extensions on the object too (see setUpBeforeClass)
|
||||
Injector::unnest();
|
||||
Config::unnest();
|
||||
|
||||
$extraDataObjects = static::getExtraDataObjects();
|
||||
if (!empty(self::$extensions_to_reapply) || !empty(self::$extensions_to_remove) || !empty($extraDataObjects)) {
|
||||
// Reset DB schema
|
||||
static::resetDBSchema();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FixtureFactory
|
||||
@ -472,6 +340,12 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
return $this->fixtureFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new fixture factory
|
||||
*
|
||||
* @param FixtureFactory $factory
|
||||
* @return $this
|
||||
*/
|
||||
public function setFixtureFactory(FixtureFactory $factory)
|
||||
{
|
||||
$this->fixtureFactory = $factory;
|
||||
@ -560,11 +434,11 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
/**
|
||||
* Useful for writing unit tests without hardcoding folder structures.
|
||||
*
|
||||
* @return String Absolute path to current class.
|
||||
* @return string Absolute path to current class.
|
||||
*/
|
||||
protected function getCurrentAbsolutePath()
|
||||
{
|
||||
$filename = self::$test_class_manifest->getItemPath(static::class);
|
||||
$filename = ClassLoader::inst()->getItemPath(static::class);
|
||||
if (!$filename) {
|
||||
throw new LogicException("getItemPath returned null for " . static::class);
|
||||
}
|
||||
@ -572,7 +446,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String File path relative to webroot
|
||||
* @return string File path relative to webroot
|
||||
*/
|
||||
protected function getCurrentRelativePath()
|
||||
{
|
||||
@ -584,28 +458,17 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the test.
|
||||
* Always sets up in order:
|
||||
* - Custom state helpers
|
||||
* - Unnest
|
||||
* - Reset php state
|
||||
*
|
||||
* User code should call parent::tearDown() after custom tear down code
|
||||
*/
|
||||
protected function tearDown()
|
||||
{
|
||||
// Preserve memory settings
|
||||
ini_set('memory_limit', ($this->originalMemoryLimit) ? $this->originalMemoryLimit : -1);
|
||||
|
||||
// Restore email configuration
|
||||
$this->mailer = null;
|
||||
|
||||
// Restore password validation
|
||||
if ($this->originalMemberPasswordValidator) {
|
||||
Member::set_password_validator($this->originalMemberPasswordValidator);
|
||||
}
|
||||
|
||||
// Restore requirements
|
||||
if ($this->originalRequirements) {
|
||||
Requirements::set_backend($this->originalRequirements);
|
||||
}
|
||||
|
||||
// Mark test as no longer being run - we use originalIsRunningTest to allow for nested SapphireTest calls
|
||||
self::$is_running_test = $this->originalIsRunningTest;
|
||||
$this->originalIsRunningTest = null;
|
||||
|
||||
// Reset mocked datetime
|
||||
DBDatetime::clear_mock_now();
|
||||
|
||||
@ -618,14 +481,8 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
$response->removeHeader('Location');
|
||||
}
|
||||
|
||||
Director::set_environment_type($this->originalEnv);
|
||||
if (class_exists(Versioned::class)) {
|
||||
Versioned::set_reading_mode($this->originalReadingMode);
|
||||
}
|
||||
|
||||
//unnest injector / config now that tests are over
|
||||
Injector::unnest();
|
||||
Config::unnest();
|
||||
// Call state helpers
|
||||
static::$state->tearDown($this);
|
||||
}
|
||||
|
||||
public static function assertContains(
|
||||
@ -658,36 +515,48 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
|
||||
/**
|
||||
* Clear the log of emails sent
|
||||
*
|
||||
* @return bool True if emails cleared
|
||||
*/
|
||||
public function clearEmails()
|
||||
{
|
||||
return $this->mailer->clearEmails();
|
||||
/** @var Mailer $mailer */
|
||||
$mailer = Injector::inst()->get(Mailer::class);
|
||||
if ($mailer instanceof TestMailer) {
|
||||
$mailer->clearEmails();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for an email that was sent.
|
||||
* All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression.
|
||||
* @param $to
|
||||
* @param $from
|
||||
* @param $subject
|
||||
* @param $content
|
||||
* @param string $to
|
||||
* @param string $from
|
||||
* @param string $subject
|
||||
* @param string $content
|
||||
* @return array Contains keys: 'type', 'to', 'from', 'subject','content', 'plainContent', 'attachedFiles',
|
||||
* 'customHeaders', 'htmlContent', 'inlineImages'
|
||||
*/
|
||||
public function findEmail($to, $from = null, $subject = null, $content = null)
|
||||
{
|
||||
return $this->mailer->findEmail($to, $from, $subject, $content);
|
||||
/** @var Mailer $mailer */
|
||||
$mailer = Injector::inst()->get(Mailer::class);
|
||||
if ($mailer instanceof TestMailer) {
|
||||
return $mailer->findEmail($to, $from, $subject, $content);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the matching email was sent since the last call to clearEmails()
|
||||
* All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression.
|
||||
* @param $to
|
||||
* @param $from
|
||||
* @param $subject
|
||||
* @param $content
|
||||
* @return array Contains the keys: 'type', 'to', 'from', 'subject', 'content', 'plainContent', 'attachedFiles',
|
||||
* 'customHeaders', 'htmlContent', inlineImages'
|
||||
*
|
||||
* @param string $to
|
||||
* @param string $from
|
||||
* @param string $subject
|
||||
* @param string $content
|
||||
*/
|
||||
public function assertEmailSent($to, $from = null, $subject = null, $content = null)
|
||||
{
|
||||
@ -1021,186 +890,62 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public static function start()
|
||||
{
|
||||
if (!static::is_running_test()) {
|
||||
new FakeController();
|
||||
static::use_test_manifest();
|
||||
if (static::is_running_test()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Health check
|
||||
if (InjectorLoader::inst()->countManifests()) {
|
||||
throw new LogicException("SapphireTest::start() cannot be called within another application");
|
||||
}
|
||||
static::set_is_running_test(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a class and template manifest instance that include tests onto the
|
||||
* top of the loader stacks.
|
||||
*/
|
||||
protected static function use_test_manifest()
|
||||
{
|
||||
$flush = !empty($_GET['flush']);
|
||||
$classManifest = new ClassManifest(
|
||||
BASE_PATH,
|
||||
true,
|
||||
$flush
|
||||
);
|
||||
// Mock request
|
||||
$_SERVER['argv'] = ['vendor/bin/phpunit', '/'];
|
||||
$request = CLIRequestBuilder::createFromEnvironment();
|
||||
|
||||
ClassLoader::inst()->pushManifest($classManifest, false);
|
||||
static::set_test_class_manifest($classManifest);
|
||||
// Test application
|
||||
$kernel = new TestKernel(BASE_PATH);
|
||||
$app = new HTTPApplication($kernel);
|
||||
|
||||
ThemeResourceLoader::inst()->addSet('$default', new ThemeManifest(
|
||||
BASE_PATH,
|
||||
project(),
|
||||
true,
|
||||
$flush
|
||||
));
|
||||
|
||||
// Once new class loader is registered, push a new uncached config
|
||||
$config = CoreConfigFactory::inst()->createCore();
|
||||
ConfigLoader::inst()->pushManifest($config);
|
||||
// Custom application
|
||||
$app->execute($request, function (HTTPRequest $request) {
|
||||
// Start session and execute
|
||||
$request->getSession()->init();
|
||||
|
||||
// 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)
|
||||
DataObject::reset();
|
||||
|
||||
// Set dummy controller;
|
||||
$controller = Controller::create();
|
||||
$controller->setRequest($request);
|
||||
$controller->pushCurrent();
|
||||
$controller->doInit();
|
||||
}, true);
|
||||
|
||||
// Register state
|
||||
static::$state = SapphireTestState::singleton();
|
||||
// Register temp DB holder
|
||||
static::$tempDB = TempDatabase::create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we are currently using a temporary database
|
||||
*/
|
||||
public static function using_temp_db()
|
||||
{
|
||||
$dbConn = DB::get_conn();
|
||||
$prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_';
|
||||
return 1 === preg_match(sprintf('/^%stmpdb_[0-9]+_[0-9]+$/i', preg_quote($prefix, '/')), $dbConn->getSelectedDatabase());
|
||||
}
|
||||
|
||||
public static function kill_temp_db()
|
||||
{
|
||||
// Delete our temporary database
|
||||
if (self::using_temp_db()) {
|
||||
$dbConn = DB::get_conn();
|
||||
$dbName = $dbConn->getSelectedDatabase();
|
||||
if ($dbName && DB::get_conn()->databaseExists($dbName)) {
|
||||
// Some DataExtensions keep a static cache of information that needs to
|
||||
// be reset whenever the database is killed
|
||||
foreach (ClassInfo::subclassesFor(DataExtension::class) as $class) {
|
||||
$toCall = array($class, 'on_db_reset');
|
||||
if (is_callable($toCall)) {
|
||||
call_user_func($toCall);
|
||||
}
|
||||
}
|
||||
|
||||
// echo "Deleted temp database " . $dbConn->currentDatabase() . "\n";
|
||||
$dbConn->dropSelectedDatabase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all content from the temporary database.
|
||||
*/
|
||||
public static function empty_temp_db()
|
||||
{
|
||||
if (self::using_temp_db()) {
|
||||
DB::get_conn()->clearAllData();
|
||||
|
||||
// Some DataExtensions keep a static cache of information that needs to
|
||||
// be reset whenever the database is cleaned out
|
||||
$classes = array_merge(ClassInfo::subclassesFor(DataExtension::class), ClassInfo::subclassesFor(DataObject::class));
|
||||
foreach ($classes as $class) {
|
||||
$toCall = array($class, 'on_db_reset');
|
||||
if (is_callable($toCall)) {
|
||||
call_user_func($toCall);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function create_temp_db()
|
||||
{
|
||||
// Disable PHPUnit error handling
|
||||
$oldErrorHandler = set_error_handler(null);
|
||||
|
||||
// Create a temporary database, and force the connection to use UTC for time
|
||||
global $databaseConfig;
|
||||
$databaseConfig['timezone'] = '+0:00';
|
||||
DB::connect($databaseConfig);
|
||||
$dbConn = DB::get_conn();
|
||||
$prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_';
|
||||
do {
|
||||
$dbname = strtolower(sprintf('%stmpdb_%s_%s', $prefix, time(), rand(1000000, 9999999)));
|
||||
} while ($dbConn->databaseExists($dbname));
|
||||
|
||||
$dbConn->selectDatabase($dbname, true);
|
||||
|
||||
static::resetDBSchema();
|
||||
|
||||
// Reinstate PHPUnit error handling
|
||||
set_error_handler($oldErrorHandler);
|
||||
|
||||
// Ensure test db is killed on exit
|
||||
register_shutdown_function(function () {
|
||||
static::kill_temp_db();
|
||||
});
|
||||
|
||||
return $dbname;
|
||||
}
|
||||
|
||||
public static function delete_all_temp_dbs()
|
||||
{
|
||||
$prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_';
|
||||
foreach (DB::get_schema()->databaseList() as $dbName) {
|
||||
if (1 === preg_match(sprintf('/^%stmpdb_[0-9]+_[0-9]+$/i', preg_quote($prefix, '/')), $dbName)) {
|
||||
DB::get_schema()->dropDatabase($dbName);
|
||||
if (Director::is_cli()) {
|
||||
echo "Dropped database \"$dbName\"" . PHP_EOL;
|
||||
} else {
|
||||
echo "<li>Dropped database \"$dbName\"</li>" . PHP_EOL;
|
||||
}
|
||||
flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the testing database's schema.
|
||||
* Reset the testing database's schema, but only if it is active
|
||||
* @param bool $includeExtraDataObjects If true, the extraDataObjects tables will also be included
|
||||
* @param bool $forceCreate Force DB to be created if it doesn't exist
|
||||
*/
|
||||
public static function resetDBSchema($includeExtraDataObjects = false)
|
||||
public static function resetDBSchema($includeExtraDataObjects = false, $forceCreate = false)
|
||||
{
|
||||
if (self::using_temp_db()) {
|
||||
DataObject::reset();
|
||||
|
||||
// clear singletons, they're caching old extension info which is used in DatabaseAdmin->doBuild()
|
||||
Injector::inst()->unregisterAllObjects();
|
||||
|
||||
$dataClasses = ClassInfo::subclassesFor(DataObject::class);
|
||||
array_shift($dataClasses);
|
||||
|
||||
DB::quiet();
|
||||
$schema = DB::get_schema();
|
||||
$extraDataObjects = $includeExtraDataObjects ? static::getExtraDataObjects() : null;
|
||||
$schema->schemaUpdate(function () use ($dataClasses, $extraDataObjects) {
|
||||
foreach ($dataClasses as $dataClass) {
|
||||
// Check if class exists before trying to instantiate - this sidesteps any manifest weirdness
|
||||
if (class_exists($dataClass)) {
|
||||
$SNG = singleton($dataClass);
|
||||
if (!($SNG instanceof TestOnly)) {
|
||||
$SNG->requireTable();
|
||||
// Check if DB is active before reset
|
||||
if (!static::$tempDB->isUsed()) {
|
||||
if (!$forceCreate) {
|
||||
return;
|
||||
}
|
||||
static::$tempDB->build();
|
||||
}
|
||||
}
|
||||
|
||||
// If we have additional dataobjects which need schema, do so here:
|
||||
if ($extraDataObjects) {
|
||||
foreach ($extraDataObjects as $dataClass) {
|
||||
$SNG = singleton($dataClass);
|
||||
if (singleton($dataClass) instanceof DataObject) {
|
||||
$SNG->requireTable();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ClassInfo::reset_db_cache();
|
||||
DataObject::singleton()->flushCache();
|
||||
}
|
||||
$extraDataObjects = $includeExtraDataObjects ? static::getExtraDataObjects() : [];
|
||||
static::$tempDB->resetDBSchema((array)$extraDataObjects);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1275,7 +1020,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function logOut()
|
||||
{
|
||||
Injector::inst()->get(IdentityStore::class)->logOut();
|
||||
/** @var IdentityStore $store */
|
||||
$store = Injector::inst()->get(IdentityStore::class);
|
||||
$store->logOut();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1283,7 +1030,6 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
*/
|
||||
protected $cache_generatedMembers = array();
|
||||
|
||||
|
||||
/**
|
||||
* Test against a theme.
|
||||
*
|
||||
@ -1295,25 +1041,16 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
protected function useTestTheme($themeBaseDir, $theme, $callback)
|
||||
{
|
||||
Config::nest();
|
||||
|
||||
if (strpos($themeBaseDir, BASE_PATH) === 0) {
|
||||
$themeBaseDir = substr($themeBaseDir, strlen(BASE_PATH));
|
||||
}
|
||||
SSViewer::config()->update('theme_enabled', true);
|
||||
SSViewer::set_themes([$themeBaseDir.'/themes/'.$theme, '$default']);
|
||||
|
||||
$e = null;
|
||||
|
||||
try {
|
||||
$callback();
|
||||
} catch (Exception $e) {
|
||||
/* NOP for now, just save $e */
|
||||
}
|
||||
|
||||
} finally {
|
||||
Config::unnest();
|
||||
|
||||
if ($e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1340,7 +1077,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
* Return all extra objects to scaffold for this test
|
||||
* @return array
|
||||
*/
|
||||
protected static function getExtraDataObjects()
|
||||
public static function getExtraDataObjects()
|
||||
{
|
||||
return static::$extra_dataobjects;
|
||||
}
|
||||
@ -1350,7 +1087,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function getExtraControllers()
|
||||
public static function getExtraControllers()
|
||||
{
|
||||
return static::$extra_controllers;
|
||||
}
|
||||
|
124
src/Dev/State/ExtensionTestState.php
Normal file
124
src/Dev/State/ExtensionTestState.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Dev\State;
|
||||
|
||||
use LogicException;
|
||||
use SilverStripe\Core\Extension;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
/**
|
||||
* Manages illegal and required extensions for sapphiretest
|
||||
*/
|
||||
class ExtensionTestState implements TestState
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $extensionsToReapply = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $extensionsToRemove = [];
|
||||
|
||||
/**
|
||||
* Called on setup
|
||||
*
|
||||
* @param SapphireTest $test
|
||||
*/
|
||||
public function setUp(SapphireTest $test)
|
||||
{
|
||||
}
|
||||
|
||||
public function tearDown(SapphireTest $test)
|
||||
{
|
||||
}
|
||||
|
||||
public function setUpOnce($class)
|
||||
{
|
||||
// May be altered by another class
|
||||
$isAltered = false;
|
||||
$this->extensionsToReapply = [];
|
||||
$this->extensionsToRemove = [];
|
||||
|
||||
/** @var string|SapphireTest $class */
|
||||
/** @var string|DataObject $dataClass */
|
||||
// Remove any illegal extensions that are present
|
||||
foreach ($class::getIllegalExtensions() as $dataClass => $extensions) {
|
||||
if (!class_exists($dataClass)) {
|
||||
continue;
|
||||
}
|
||||
if ($extensions === '*') {
|
||||
$extensions = $dataClass::get_extensions();
|
||||
}
|
||||
foreach ($extensions as $extension) {
|
||||
if (!class_exists($extension) || !$dataClass::has_extension($extension)) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($this->extensionsToReapply[$dataClass])) {
|
||||
$this->extensionsToReapply[$dataClass] = [];
|
||||
}
|
||||
$this->extensionsToReapply[$dataClass][] = $extension;
|
||||
$dataClass::remove_extension($extension);
|
||||
$isAltered = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Add any required extensions that aren't present
|
||||
foreach ($class::getRequiredExtensions() as $dataClass => $extensions) {
|
||||
if (!class_exists($dataClass)) {
|
||||
throw new LogicException("Test {$class} requires dataClass {$dataClass} which doesn't exist");
|
||||
}
|
||||
foreach ($extensions as $extension) {
|
||||
$extension = Extension::get_classname_without_arguments($extension);
|
||||
if (!class_exists($extension)) {
|
||||
throw new LogicException("Test {$class} requires extension {$extension} which doesn't exist");
|
||||
}
|
||||
if (!$dataClass::has_extension($extension)) {
|
||||
if (!isset($this->extensionsToRemove[$dataClass])) {
|
||||
$this->extensionsToRemove[$dataClass] = [];
|
||||
}
|
||||
$this->extensionsToRemove[$dataClass][] = $extension;
|
||||
$dataClass::add_extension($extension);
|
||||
$isAltered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clear singletons, they're caching old extension info
|
||||
// which is used in DatabaseAdmin->doBuild()
|
||||
Injector::inst()->unregisterObjects(DataObject::class);
|
||||
|
||||
// If we have altered the schema, but SapphireTest::setUpBeforeClass() would not otherwise
|
||||
// reset the schema (if there were extra objects) then force a reset
|
||||
if ($isAltered && empty($class::getExtraDataObjects())) {
|
||||
DataObject::reset();
|
||||
$class::resetDBSchema(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function tearDownOnce($class)
|
||||
{
|
||||
// @todo: This isn't strictly necessary to restore extensions, but only to ensure that
|
||||
// Object::$extra_methods is properly flushed. This should be replaced with a simple
|
||||
// flush mechanism for each $class.
|
||||
/** @var string|DataObject $dataClass */
|
||||
|
||||
// Remove extensions added for testing
|
||||
foreach ($this->extensionsToRemove as $dataClass => $extensions) {
|
||||
foreach ($extensions as $extension) {
|
||||
$dataClass::remove_extension($extension);
|
||||
}
|
||||
}
|
||||
|
||||
// Reapply ones removed
|
||||
foreach ($this->extensionsToReapply as $dataClass => $extensions) {
|
||||
foreach ($extensions as $extension) {
|
||||
$dataClass::add_extension($extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
50
src/Dev/State/FlushableTestState.php
Normal file
50
src/Dev/State/FlushableTestState.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Dev\State;
|
||||
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Flushable;
|
||||
use SilverStripe\Core\Resettable;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
/**
|
||||
* Clears flushable / resettable objects
|
||||
*/
|
||||
class FlushableTestState implements TestState
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $flushed = false;
|
||||
|
||||
public function setUp(SapphireTest $test)
|
||||
{
|
||||
// Reset all resettables
|
||||
/** @var Resettable $resettable */
|
||||
foreach (ClassInfo::implementorsOf(Resettable::class) as $resettable) {
|
||||
$resettable::reset();
|
||||
}
|
||||
}
|
||||
|
||||
public function tearDown(SapphireTest $test)
|
||||
{
|
||||
}
|
||||
|
||||
public function setUpOnce($class)
|
||||
{
|
||||
if ($this->flushed) {
|
||||
return;
|
||||
}
|
||||
$this->flushed = true;
|
||||
|
||||
// Flush all flushable records
|
||||
/** @var Flushable $class */
|
||||
foreach (ClassInfo::implementorsOf(Flushable::class) as $class) {
|
||||
$class::flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function tearDownOnce($class)
|
||||
{
|
||||
}
|
||||
}
|
45
src/Dev/State/GlobalsTestState.php
Normal file
45
src/Dev/State/GlobalsTestState.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Dev\State;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Environment;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
/**
|
||||
* Cleans up and reset global env vars between tests
|
||||
*/
|
||||
class GlobalsTestState implements TestState
|
||||
{
|
||||
/**
|
||||
* Var backed up for the class
|
||||
* @var array
|
||||
*/
|
||||
protected $staticVars = [];
|
||||
|
||||
/**
|
||||
* Vars backed up for the test
|
||||
* @var array
|
||||
*/
|
||||
protected $vars = [];
|
||||
|
||||
public function setUp(SapphireTest $test)
|
||||
{
|
||||
$this->vars = Environment::getVariables();
|
||||
}
|
||||
|
||||
public function tearDown(SapphireTest $test)
|
||||
{
|
||||
Environment::setVariables($this->vars);
|
||||
}
|
||||
|
||||
public function setUpOnce($class)
|
||||
{
|
||||
$this->staticVars = Environment::getVariables();
|
||||
}
|
||||
|
||||
public function tearDownOnce($class)
|
||||
{
|
||||
Environment::setVariables($this->staticVars);
|
||||
}
|
||||
}
|
94
src/Dev/State/KernelTestState.php
Normal file
94
src/Dev/State/KernelTestState.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Dev\State;
|
||||
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Kernel;
|
||||
use SilverStripe\Dev\TestKernel;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
/**
|
||||
* Handles nesting of kernel before / after tests
|
||||
*/
|
||||
class KernelTestState implements TestState
|
||||
{
|
||||
/**
|
||||
* Stack of kernels
|
||||
*
|
||||
* @var TestKernel[]
|
||||
*/
|
||||
protected $kernels = [];
|
||||
|
||||
/**
|
||||
* Get active Kernel instance
|
||||
*
|
||||
* @return \SilverStripe\Dev\TestKernel
|
||||
*/
|
||||
protected function kernel()
|
||||
{
|
||||
return end($this->kernels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on setup
|
||||
*
|
||||
* @param SapphireTest $test
|
||||
*/
|
||||
public function setUp(SapphireTest $test)
|
||||
{
|
||||
$this->nest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on tear down
|
||||
*
|
||||
* @param SapphireTest $test
|
||||
*/
|
||||
public function tearDown(SapphireTest $test)
|
||||
{
|
||||
$this->unnest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once on setup
|
||||
*
|
||||
* @param string $class Class being setup
|
||||
*/
|
||||
public function setUpOnce($class)
|
||||
{
|
||||
// If first run, get initial kernel
|
||||
if (empty($this->kernels)) {
|
||||
$this->kernels[] = Injector::inst()->get(Kernel::class);
|
||||
}
|
||||
|
||||
$this->nest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once on tear down
|
||||
*
|
||||
* @param string $class Class being torn down
|
||||
*/
|
||||
public function tearDownOnce($class)
|
||||
{
|
||||
$this->unnest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Nest the current kernel
|
||||
*/
|
||||
protected function nest()
|
||||
{
|
||||
// Reset state
|
||||
$this->kernel()->reset();
|
||||
$this->kernels[] = $this->kernel()->nest();
|
||||
}
|
||||
|
||||
protected function unnest()
|
||||
{
|
||||
// Unnest and reset
|
||||
array_pop($this->kernels);
|
||||
$this->kernel()->activate();
|
||||
$this->kernel()->reset();
|
||||
}
|
||||
}
|
72
src/Dev/State/SapphireTestState.php
Normal file
72
src/Dev/State/SapphireTestState.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Dev\State;
|
||||
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
class SapphireTestState implements TestState
|
||||
{
|
||||
use Injectable;
|
||||
|
||||
/**
|
||||
* @var TestState[]
|
||||
*/
|
||||
protected $states = [];
|
||||
|
||||
/**
|
||||
* @return TestState[]
|
||||
*/
|
||||
public function getStates()
|
||||
{
|
||||
return $this->states;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TestState[] $states
|
||||
* @return $this
|
||||
*/
|
||||
public function setStates(array $states)
|
||||
{
|
||||
$this->states = $states;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUp(SapphireTest $test)
|
||||
{
|
||||
foreach ($this->states as $state) {
|
||||
$state->setUp($test);
|
||||
}
|
||||
}
|
||||
|
||||
public function tearDown(SapphireTest $test)
|
||||
{
|
||||
// Tear down in reverse order
|
||||
/** @var TestState $state */
|
||||
foreach (array_reverse($this->states) as $state) {
|
||||
$state->tearDown($test);
|
||||
}
|
||||
}
|
||||
|
||||
public function setUpOnce($class)
|
||||
{
|
||||
foreach ($this->states as $state) {
|
||||
$state->setUpOnce($class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once on tear down
|
||||
*
|
||||
* @param string $class Class being torn down
|
||||
*/
|
||||
public function tearDownOnce($class)
|
||||
{
|
||||
// Tear down in reverse order
|
||||
/** @var TestState $state */
|
||||
foreach (array_reverse($this->states) as $state) {
|
||||
$state->tearDownOnce($class);
|
||||
}
|
||||
}
|
||||
}
|
42
src/Dev/State/TestState.php
Normal file
42
src/Dev/State/TestState.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Dev\State;
|
||||
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
|
||||
/**
|
||||
* Helper for resetting, booting, or cleaning up test state.
|
||||
*
|
||||
* SapphireTest will detect all implementors of this interface during test execution
|
||||
*/
|
||||
interface TestState extends TestOnly
|
||||
{
|
||||
/**
|
||||
* Called on setup
|
||||
*
|
||||
* @param SapphireTest $test
|
||||
*/
|
||||
public function setUp(SapphireTest $test);
|
||||
|
||||
/**
|
||||
* Called on tear down
|
||||
*
|
||||
* @param SapphireTest $test
|
||||
*/
|
||||
public function tearDown(SapphireTest $test);
|
||||
|
||||
/**
|
||||
* Called once on setup
|
||||
*
|
||||
* @param string $class Class being setup
|
||||
*/
|
||||
public function setUpOnce($class);
|
||||
|
||||
/**
|
||||
* Called once on tear down
|
||||
*
|
||||
* @param string $class Class being torn down
|
||||
*/
|
||||
public function tearDownOnce($class);
|
||||
}
|
@ -29,12 +29,12 @@ class TaskRunner extends Controller
|
||||
{
|
||||
parent::init();
|
||||
|
||||
$isRunningTests = (class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test());
|
||||
$allowAllCLI = DevelopmentAdmin::config()->get('allow_all_cli');
|
||||
$canAccess = (
|
||||
Director::isDev()
|
||||
// We need to ensure that DevelopmentAdminTest can simulate permission failures when running
|
||||
// "dev/tasks" from CLI.
|
||||
|| (Director::is_cli() && !$isRunningTests)
|
||||
|| (Director::is_cli() && $allowAllCLI)
|
||||
|| Permission::check("ADMIN")
|
||||
);
|
||||
if (!$canAccess) {
|
||||
|
@ -3,8 +3,8 @@
|
||||
namespace SilverStripe\Dev\Tasks;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Dev\BuildTask;
|
||||
use SilverStripe\ORM\Connect\TempDatabase;
|
||||
use SilverStripe\Security\Permission;
|
||||
use SilverStripe\Security\Security;
|
||||
|
||||
@ -31,6 +31,7 @@ class CleanupTestDatabasesTask extends BuildTask
|
||||
die;
|
||||
}
|
||||
|
||||
SapphireTest::delete_all_temp_dbs();
|
||||
// Delete all temp DBs
|
||||
TempDatabase::create()->deleteAll();
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace SilverStripe\Dev\Tasks;
|
||||
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Core\Environment;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\Dev\BuildTask;
|
||||
@ -40,7 +41,7 @@ class i18nTextCollectorTask extends BuildTask
|
||||
*/
|
||||
public function run($request)
|
||||
{
|
||||
increase_time_limit_to();
|
||||
Environment::increaseTimeLimitTo();
|
||||
$collector = i18nTextCollector::create($request->getVar('locale'));
|
||||
|
||||
$merge = $this->getIsMerge($request);
|
||||
|
49
src/Dev/TestKernel.php
Normal file
49
src/Dev/TestKernel.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Dev;
|
||||
|
||||
use SilverStripe\Core\CoreKernel;
|
||||
|
||||
/**
|
||||
* Kernel for running unit tests
|
||||
*/
|
||||
class TestKernel extends CoreKernel
|
||||
{
|
||||
public function __construct($basePath)
|
||||
{
|
||||
$this->setEnvironment(self::DEV);
|
||||
parent::__construct($basePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset kernel between tests.
|
||||
* Note: this avoids resetting services (See TestState for service specific reset)
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->setEnvironment(self::DEV);
|
||||
$this->bootPHP();
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function bootPHP()
|
||||
{
|
||||
parent::bootPHP();
|
||||
|
||||
// Set default timezone consistently to avoid NZ-specific dependencies
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
|
||||
protected function getIncludeTests()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function bootErrorHandling()
|
||||
{
|
||||
// Leave phpunit to capture errors
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace SilverStripe\Dev;
|
||||
|
||||
use SilverStripe\Control\Cookie_Backend;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Director;
|
||||
@ -53,11 +54,14 @@ class TestSession
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->session = Injector::inst()->create('SilverStripe\\Control\\Session', array());
|
||||
$this->cookies = Injector::inst()->create('SilverStripe\\Control\\Cookie_Backend');
|
||||
$this->session = Injector::inst()->create(Session::class, array());
|
||||
$this->cookies = Injector::inst()->create(Cookie_Backend::class);
|
||||
$request = new HTTPRequest('GET', '/');
|
||||
$request->setSession($this->session());
|
||||
$this->controller = new Controller();
|
||||
$this->controller->setSession($this->session);
|
||||
$this->controller->setRequest($request);
|
||||
$this->controller->pushCurrent();
|
||||
$this->controller->doInit();
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
|
@ -26,6 +26,12 @@ class DefaultFormFactory implements FormFactory
|
||||
$this->constructExtensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestHandler $controller
|
||||
* @param string $name
|
||||
* @param array $context
|
||||
* @return Form
|
||||
*/
|
||||
public function getForm(RequestHandler $controller = null, $name = FormFactory::DEFAULT_NAME, $context = [])
|
||||
{
|
||||
// Validate context
|
||||
|
@ -2,8 +2,12 @@
|
||||
|
||||
namespace SilverStripe\Forms;
|
||||
|
||||
use BadMethodCallException;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HasRequestHandler;
|
||||
use SilverStripe\Control\HTTP;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\NullHTTPRequest;
|
||||
use SilverStripe\Control\RequestHandler;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
@ -333,11 +337,46 @@ class Form extends ViewableData implements HasRequestHandler
|
||||
*/
|
||||
public function clearFormState()
|
||||
{
|
||||
Session::clear("FormInfo.{$this->FormName()}.result");
|
||||
Session::clear("FormInfo.{$this->FormName()}.data");
|
||||
$this
|
||||
->getSession()
|
||||
->clear("FormInfo.{$this->FormName()}.result")
|
||||
->clear("FormInfo.{$this->FormName()}.data");
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get current request for this form
|
||||
*
|
||||
* @return HTTPRequest
|
||||
*/
|
||||
protected function getRequest()
|
||||
{
|
||||
// Check if current request handler has a request object
|
||||
$controller = $this->getController();
|
||||
if ($controller && !($controller->getRequest() instanceof NullHTTPRequest)) {
|
||||
return $controller->getRequest();
|
||||
}
|
||||
// Fall back to current controller
|
||||
if (Controller::has_curr() && !(Controller::curr()->getRequest() instanceof NullHTTPRequest)) {
|
||||
return Controller::curr()->getRequest();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session for this form
|
||||
*
|
||||
* @return Session
|
||||
*/
|
||||
protected function getSession()
|
||||
{
|
||||
$request = $this->getRequest();
|
||||
if ($request) {
|
||||
return $request->getSession();
|
||||
}
|
||||
throw new BadMethodCallException("Session not available in the current context");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any form data stored in the session
|
||||
*
|
||||
@ -345,7 +384,7 @@ class Form extends ViewableData implements HasRequestHandler
|
||||
*/
|
||||
public function getSessionData()
|
||||
{
|
||||
return Session::get("FormInfo.{$this->FormName()}.data");
|
||||
return $this->getSession()->get("FormInfo.{$this->FormName()}.data");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -356,7 +395,7 @@ class Form extends ViewableData implements HasRequestHandler
|
||||
*/
|
||||
public function setSessionData($data)
|
||||
{
|
||||
Session::set("FormInfo.{$this->FormName()}.data", $data);
|
||||
$this->getSession()->set("FormInfo.{$this->FormName()}.data", $data);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -367,7 +406,7 @@ class Form extends ViewableData implements HasRequestHandler
|
||||
*/
|
||||
public function getSessionValidationResult()
|
||||
{
|
||||
$resultData = Session::get("FormInfo.{$this->FormName()}.result");
|
||||
$resultData = $this->getSession()->get("FormInfo.{$this->FormName()}.result");
|
||||
if (isset($resultData)) {
|
||||
return unserialize($resultData);
|
||||
}
|
||||
@ -396,7 +435,7 @@ class Form extends ViewableData implements HasRequestHandler
|
||||
|
||||
// Serialise
|
||||
$resultData = $result ? serialize($result) : null;
|
||||
Session::set("FormInfo.{$this->FormName()}.result", $resultData);
|
||||
$this->getSession()->set("FormInfo.{$this->FormName()}.result", $resultData);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -2,23 +2,21 @@
|
||||
|
||||
namespace SilverStripe\Forms\GridField;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use SilverStripe\Control\HasRequestHandler;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\HTTPResponse_Exception;
|
||||
use SilverStripe\Control\RequestHandler;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\Forms\FormField;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\ORM\DataList;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\SS_List;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\ORM\DataObjectInterface;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Control\HTTPResponse_Exception;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\RequestHandler;
|
||||
use SilverStripe\Forms\FormField;
|
||||
use SilverStripe\Forms\Form;
|
||||
use LogicException;
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use SilverStripe\ORM\SS_List;
|
||||
use SilverStripe\View\HTML;
|
||||
|
||||
/**
|
||||
@ -910,7 +908,7 @@ class GridField extends FormField
|
||||
|
||||
foreach ($data as $dataKey => $dataValue) {
|
||||
if (preg_match('/^action_gridFieldAlterAction\?StateID=(.*)/', $dataKey, $matches)) {
|
||||
$stateChange = Session::get($matches[1]);
|
||||
$stateChange = $request->getSession()->get($matches[1]);
|
||||
$actionName = $stateChange['actionName'];
|
||||
|
||||
$arguments = array();
|
||||
@ -972,11 +970,10 @@ class GridField extends FormField
|
||||
* @todo copy less code from RequestHandler.
|
||||
*
|
||||
* @param HTTPRequest $request
|
||||
* @param DataModel $model
|
||||
* @return array|RequestHandler|HTTPResponse|string
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
public function handleRequest(HTTPRequest $request, DataModel $model)
|
||||
public function handleRequest(HTTPRequest $request)
|
||||
{
|
||||
if ($this->brokenOnConstruct) {
|
||||
user_error(
|
||||
@ -989,7 +986,6 @@ class GridField extends FormField
|
||||
}
|
||||
|
||||
$this->setRequest($request);
|
||||
$this->setDataModel($model);
|
||||
|
||||
$fieldData = $this->getRequest()->requestVar($this->getName());
|
||||
|
||||
@ -1038,7 +1034,7 @@ class GridField extends FormField
|
||||
if ($result instanceof HasRequestHandler) {
|
||||
$result = $result->getRequestHandler();
|
||||
}
|
||||
$returnValue = $result->handleRequest($request, $model);
|
||||
$returnValue = $result->handleRequest($request);
|
||||
|
||||
if (is_array($returnValue)) {
|
||||
throw new LogicException(
|
||||
@ -1066,7 +1062,7 @@ class GridField extends FormField
|
||||
}
|
||||
}
|
||||
|
||||
return parent::handleRequest($request, $model);
|
||||
return parent::handleRequest($request);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,17 +2,16 @@
|
||||
|
||||
namespace SilverStripe\Forms\GridField;
|
||||
|
||||
use Closure;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\RequestHandler;
|
||||
use SilverStripe\Core\Extensible;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Extensible;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Forms\Validator;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use Closure;
|
||||
use SilverStripe\Forms\Validator;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\Filterable;
|
||||
|
||||
/**
|
||||
@ -118,7 +117,7 @@ class GridFieldDetailForm implements GridField_URLHandler
|
||||
$this->setValidator($record->getCMSValidator());
|
||||
}
|
||||
|
||||
return $handler->handleRequest($request, DataModel::inst());
|
||||
return $handler->handleRequest($request);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,9 +4,9 @@ namespace SilverStripe\Forms\GridField;
|
||||
|
||||
use SilverStripe\Admin\LeftAndMain;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\RequestHandler;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\RequestHandler;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\Forms\FormAction;
|
||||
|
@ -134,9 +134,7 @@ class GridFieldExportButton implements GridField_HTMLProvider, GridField_ActionP
|
||||
}
|
||||
|
||||
/** @var GridFieldDataColumns $dataCols */
|
||||
$dataCols = $gridField->getConfig()->getComponentByType(
|
||||
'SilverStripe\\Forms\\GridField\\GridFieldDataColumns'
|
||||
);
|
||||
$dataCols = $gridField->getConfig()->getComponentByType(GridFieldDataColumns::class);
|
||||
if ($dataCols) {
|
||||
return $dataCols->getDisplayFields($gridField);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace SilverStripe\Forms\GridField;
|
||||
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\Forms\FormAction;
|
||||
|
||||
@ -89,7 +89,9 @@ class GridField_FormAction extends FormAction
|
||||
|
||||
// Ensure $id doesn't contain only numeric characters
|
||||
$id = 'gf_' . substr(md5(serialize($state)), 0, 8);
|
||||
Session::set($id, $state);
|
||||
|
||||
$session = Controller::curr()->getRequest()->getSession();
|
||||
$session->set($id, $state);
|
||||
$actionData['StateID'] = $id;
|
||||
|
||||
return array_merge(
|
||||
|
@ -365,7 +365,7 @@ class TinyMCEConfig extends HTMLEditorConfig
|
||||
|
||||
/**
|
||||
* Enable one or several plugins. Will properly handle being passed a plugin that is already disabled
|
||||
* @param string $plugin,... a string, or several strings, or a single array of strings - The plugins to enable
|
||||
* @param string|array $plugin,... a string, or several strings, or a single array of strings - The plugins to enable
|
||||
* @return $this
|
||||
*/
|
||||
public function disablePlugins($plugin)
|
||||
|
@ -24,7 +24,7 @@ class DetailedErrorFormatter implements FormatterInterface
|
||||
'trace' => $exception->getTrace(),
|
||||
);
|
||||
} else {
|
||||
$context = $record['context'];
|
||||
$context = isset($record['context']) ? $record['context'] : $record;
|
||||
foreach (array('code','message','file','line') as $key) {
|
||||
if (!isset($context[$key])) {
|
||||
$context[$key] = isset($record[$key]) ? $record[$key] : null;
|
||||
|
220
src/ORM/Connect/TempDatabase.php
Normal file
220
src/ORM/Connect/TempDatabase.php
Normal file
@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Connect;
|
||||
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataExtension;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\DB;
|
||||
|
||||
class TempDatabase
|
||||
{
|
||||
use Injectable;
|
||||
|
||||
/**
|
||||
* Connection name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = null;
|
||||
|
||||
/**
|
||||
* Create a new temp database
|
||||
*
|
||||
* @param string $name DB Connection name to use
|
||||
*/
|
||||
public function __construct($name = 'default')
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given name matches the temp_db pattern
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
protected function isDBTemp($name)
|
||||
{
|
||||
$prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_';
|
||||
$result = preg_match(
|
||||
sprintf('/^%stmpdb_[0-9]+_[0-9]+$/i', preg_quote($prefix, '/')),
|
||||
$name
|
||||
);
|
||||
return $result === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Database
|
||||
*/
|
||||
protected function getConn()
|
||||
{
|
||||
return DB::get_conn($this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we are currently using a temporary database
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isUsed()
|
||||
{
|
||||
$selected = $this->getConn()->getSelectedDatabase();
|
||||
return $this->isDBTemp($selected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the current temp database
|
||||
*/
|
||||
public function kill()
|
||||
{
|
||||
// Delete our temporary database
|
||||
if (!$this->isUsed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the database actually exists
|
||||
$dbConn = $this->getConn();
|
||||
$dbName = $dbConn->getSelectedDatabase();
|
||||
if (!$dbConn->databaseExists($dbName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Some DataExtensions keep a static cache of information that needs to
|
||||
// be reset whenever the database is killed
|
||||
foreach (ClassInfo::subclassesFor(DataExtension::class) as $class) {
|
||||
$toCall = array($class, 'on_db_reset');
|
||||
if (is_callable($toCall)) {
|
||||
call_user_func($toCall);
|
||||
}
|
||||
}
|
||||
|
||||
// echo "Deleted temp database " . $dbConn->currentDatabase() . "\n";
|
||||
$dbConn->dropSelectedDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all content from the temporary database.
|
||||
*/
|
||||
public function clearAllData()
|
||||
{
|
||||
if (!$this->isUsed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getConn()->clearAllData();
|
||||
|
||||
// Some DataExtensions keep a static cache of information that needs to
|
||||
// be reset whenever the database is cleaned out
|
||||
$classes = array_merge(
|
||||
ClassInfo::subclassesFor(DataExtension::class),
|
||||
ClassInfo::subclassesFor(DataObject::class)
|
||||
);
|
||||
foreach ($classes as $class) {
|
||||
$toCall = array($class, 'on_db_reset');
|
||||
if (is_callable($toCall)) {
|
||||
call_user_func($toCall);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create temp DB without creating extra objects
|
||||
*
|
||||
* @return string DB name
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
// Disable PHPUnit error handling
|
||||
$oldErrorHandler = set_error_handler(null);
|
||||
|
||||
// Create a temporary database, and force the connection to use UTC for time
|
||||
$dbConn = $this->getConn();
|
||||
$prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_';
|
||||
do {
|
||||
$dbname = strtolower(sprintf('%stmpdb_%s_%s', $prefix, time(), rand(1000000, 9999999)));
|
||||
} while ($dbConn->databaseExists($dbname));
|
||||
|
||||
$dbConn->selectDatabase($dbname, true);
|
||||
|
||||
$this->resetDBSchema();
|
||||
|
||||
// Reinstate PHPUnit error handling
|
||||
set_error_handler($oldErrorHandler);
|
||||
|
||||
// Ensure test db is killed on exit
|
||||
register_shutdown_function(function () {
|
||||
$this->kill();
|
||||
});
|
||||
|
||||
return $dbname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all temp DBs on this connection
|
||||
*
|
||||
* Note: This will output results to stdout unless suppressOutput
|
||||
* is set on the current db schema
|
||||
*/
|
||||
public function deleteAll()
|
||||
{
|
||||
$schema = $this->getConn()->getSchemaManager();
|
||||
foreach ($schema->databaseList() as $dbName) {
|
||||
if ($this->isDBTemp($dbName)) {
|
||||
$schema->dropDatabase($dbName);
|
||||
$schema->alterationMessage("Dropped database \"$dbName\"", 'deleted');
|
||||
flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the testing database's schema.
|
||||
*
|
||||
* @param array $extraDataObjects List of extra dataobjects to build
|
||||
*/
|
||||
public function resetDBSchema(array $extraDataObjects = [])
|
||||
{
|
||||
if (!$this->isUsed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DataObject::reset();
|
||||
|
||||
// clear singletons, they're caching old extension info which is used in DatabaseAdmin->doBuild()
|
||||
Injector::inst()->unregisterObjects(DataObject::class);
|
||||
|
||||
$dataClasses = ClassInfo::subclassesFor(DataObject::class);
|
||||
array_shift($dataClasses);
|
||||
|
||||
$schema = $this->getConn()->getSchemaManager();
|
||||
$schema->quiet();
|
||||
$schema->schemaUpdate(function () use ($dataClasses, $extraDataObjects) {
|
||||
foreach ($dataClasses as $dataClass) {
|
||||
// Check if class exists before trying to instantiate - this sidesteps any manifest weirdness
|
||||
if (class_exists($dataClass)) {
|
||||
$SNG = singleton($dataClass);
|
||||
if (!($SNG instanceof TestOnly)) {
|
||||
$SNG->requireTable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have additional dataobjects which need schema, do so here:
|
||||
if ($extraDataObjects) {
|
||||
foreach ($extraDataObjects as $dataClass) {
|
||||
$SNG = singleton($dataClass);
|
||||
if (singleton($dataClass) instanceof DataObject) {
|
||||
$SNG->requireTable();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ClassInfo::reset_db_cache();
|
||||
DataObject::singleton()->flushCache();
|
||||
}
|
||||
}
|
@ -2,19 +2,19 @@
|
||||
|
||||
namespace SilverStripe\ORM;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use SilverStripe\Control\Cookie;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\Cookie;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\ORM\Connect\Database;
|
||||
use SilverStripe\ORM\Connect\DBConnector;
|
||||
use SilverStripe\ORM\Connect\DBSchemaManager;
|
||||
use SilverStripe\ORM\Connect\Query;
|
||||
use SilverStripe\ORM\Queries\SQLExpression;
|
||||
use SilverStripe\ORM\Connect\Database;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* Global database interface, complete with static methods.
|
||||
@ -34,9 +34,19 @@ class DB
|
||||
|
||||
/**
|
||||
* The global database connection.
|
||||
*
|
||||
* @var Database
|
||||
*/
|
||||
private static $connections = array();
|
||||
protected static $connections = [];
|
||||
|
||||
/**
|
||||
* List of configurations for each connection
|
||||
*
|
||||
* @var array List of configs each in the $databaseConfig format
|
||||
*/
|
||||
protected static $configs = [];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The last SQL query run.
|
||||
@ -77,6 +87,13 @@ class DB
|
||||
if (isset(self::$connections[$name])) {
|
||||
return self::$connections[$name];
|
||||
}
|
||||
|
||||
// lazy connect
|
||||
$config = static::getConfig($name);
|
||||
if ($config) {
|
||||
return static::connect($config, $name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -247,7 +264,7 @@ class DB
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a database.
|
||||
* Specify connection to a database
|
||||
*
|
||||
* Given the database configuration, this method will create the correct
|
||||
* subclass of {@link SS_Database}.
|
||||
@ -259,14 +276,13 @@ class DB
|
||||
*/
|
||||
public static function connect($databaseConfig, $label = 'default')
|
||||
{
|
||||
|
||||
// This is used by the "testsession" module to test up a test session using an alternative name
|
||||
if ($name = self::get_alternative_database_name()) {
|
||||
$databaseConfig['database'] = $name;
|
||||
}
|
||||
|
||||
if (!isset($databaseConfig['type']) || empty($databaseConfig['type'])) {
|
||||
user_error("DB::connect: Not passed a valid database config", E_USER_ERROR);
|
||||
throw new InvalidArgumentException("DB::connect: Not passed a valid database config");
|
||||
}
|
||||
|
||||
self::$connection_attempted = true;
|
||||
@ -283,6 +299,30 @@ class DB
|
||||
return $conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set config for a lazy-connected database
|
||||
*
|
||||
* @param array $databaseConfig
|
||||
* @param string $name
|
||||
*/
|
||||
public static function setConfig($databaseConfig, $name = 'default')
|
||||
{
|
||||
static::$configs[$name] = $databaseConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the named connection config
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getConfig($name = 'default')
|
||||
{
|
||||
if (isset(static::$configs[$name])) {
|
||||
return static::$configs[$name];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a database connection has been attempted.
|
||||
* In particular, it lets the caller know if we're still so early in the execution pipeline that
|
||||
|
@ -49,13 +49,6 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li
|
||||
*/
|
||||
protected $dataQuery;
|
||||
|
||||
/**
|
||||
* The DataModel from which this DataList comes.
|
||||
*
|
||||
* @var DataModel
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Create a new DataList.
|
||||
* No querying is done on construction, but the initial query schema is set up.
|
||||
@ -70,16 +63,6 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DataModel
|
||||
*
|
||||
* @param DataModel $model
|
||||
*/
|
||||
public function setDataModel(DataModel $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dataClass name for this DataList, ie the DataObject ClassName
|
||||
*
|
||||
@ -796,7 +779,7 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li
|
||||
$class = $row['RecordClassName'];
|
||||
}
|
||||
|
||||
$item = Injector::inst()->create($class, $row, false, $this->model, $this->getQueryParams());
|
||||
$item = Injector::inst()->create($class, $row, false, $this->getQueryParams());
|
||||
|
||||
return $item;
|
||||
}
|
||||
@ -1119,7 +1102,7 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li
|
||||
public function newObject($initialFields = null)
|
||||
{
|
||||
$class = $this->dataClass;
|
||||
return Injector::inst()->create($class, $initialFields, false, $this->model);
|
||||
return Injector::inst()->create($class, $initialFields, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM;
|
||||
|
||||
/**
|
||||
* Representation of a DataModel - a collection of DataLists for each different
|
||||
* data type.
|
||||
*
|
||||
* Usage:
|
||||
* <code>
|
||||
* $model = new DataModel;
|
||||
* $mainMenu = $model->SiteTree->where('"ParentID" = 0 AND "ShowInMenus" = 1');
|
||||
* </code>
|
||||
*/
|
||||
class DataModel
|
||||
{
|
||||
|
||||
/**
|
||||
* @var DataModel
|
||||
*/
|
||||
protected static $inst;
|
||||
|
||||
/**
|
||||
* @var array $customDataLists
|
||||
*/
|
||||
protected $customDataLists = array();
|
||||
|
||||
/**
|
||||
* Get the global DataModel.
|
||||
*
|
||||
* @return DataModel
|
||||
*/
|
||||
public static function inst()
|
||||
{
|
||||
if (!self::$inst) {
|
||||
self::$inst = new self;
|
||||
}
|
||||
|
||||
return self::$inst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the global DataModel, used when data is requested from static
|
||||
* methods.
|
||||
*
|
||||
* @param DataModel $inst
|
||||
*/
|
||||
public static function set_inst(DataModel $inst)
|
||||
{
|
||||
self::$inst = $inst;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string
|
||||
*
|
||||
* @return DataList
|
||||
*/
|
||||
public function __get($class)
|
||||
{
|
||||
if (isset($this->customDataLists[$class])) {
|
||||
return clone $this->customDataLists[$class];
|
||||
} else {
|
||||
$list = DataList::create($class);
|
||||
$list->setDataModel($this);
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param DataList $item
|
||||
*/
|
||||
public function __set($class, $item)
|
||||
{
|
||||
$item = clone $item;
|
||||
$item->setDataModel($this);
|
||||
$this->customDataLists[$class] = $item;
|
||||
}
|
||||
}
|
@ -141,11 +141,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public $destroyed = false;
|
||||
|
||||
/**
|
||||
* The DataModel from this this object comes
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Data stored in this objects database record. An array indexed by fieldname.
|
||||
*
|
||||
@ -289,10 +284,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* for populating data on new records.
|
||||
* @param boolean $isSingleton This this to true if this is a singleton() object, a stub for calling methods.
|
||||
* Singletons don't have their defaults set.
|
||||
* @param DataModel $model
|
||||
* @param array $queryParams List of DataQuery params necessary to lazy load, or load related objects.
|
||||
*/
|
||||
public function __construct($record = null, $isSingleton = false, $model = null, $queryParams = array())
|
||||
public function __construct($record = null, $isSingleton = false, $queryParams = array())
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
@ -365,10 +359,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
HTTP::register_modification_date($record['LastEdited']);
|
||||
}
|
||||
|
||||
// this must be called before populateDefaults(), as field getters on a DataObject
|
||||
// may call getComponent() and others, which rely on $this->model being set.
|
||||
$this->model = $model ? $model : DataModel::inst();
|
||||
|
||||
// Must be called after parent constructor
|
||||
if (!$isSingleton && (!isset($this->record['ID']) || !$this->record['ID'])) {
|
||||
$this->populateDefaults();
|
||||
@ -378,17 +368,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$this->changed = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DataModel
|
||||
* @param DataModel $model
|
||||
* @return DataObject $this
|
||||
*/
|
||||
public function setDataModel(DataModel $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy all of this objects dependant objects and local caches.
|
||||
* You'll need to call this to get the memory of an object that has components or extensions freed.
|
||||
@ -415,7 +394,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$map = $this->toMap();
|
||||
unset($map['Created']);
|
||||
/** @var static $clone */
|
||||
$clone = Injector::inst()->create(static::class, $map, false, $this->model);
|
||||
$clone = Injector::inst()->create(static::class, $map, false, $this->getSourceQueryParams());
|
||||
$clone->ID = 0;
|
||||
|
||||
$clone->invokeWithExtensions('onBeforeDuplicate', $this, $doWrite, $manyMany);
|
||||
@ -549,7 +528,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$originalClass = $this->ClassName;
|
||||
|
||||
/** @var DataObject $newInstance */
|
||||
$newInstance = Injector::inst()->create($newClassName, $this->record, false, $this->model);
|
||||
$newInstance = Injector::inst()->create($newClassName, $this->record, false);
|
||||
|
||||
// Modify ClassName
|
||||
if ($newClassName != $originalClass) {
|
||||
@ -1437,7 +1416,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// - move the details of the delete code in the DataQuery system
|
||||
// - update the code to just delete the base table, and rely on cascading deletes in the DB to do the rest
|
||||
// obviously, that means getting requireTable() to configure cascading deletes ;-)
|
||||
$srcQuery = DataList::create(static::class, $this->model)
|
||||
$srcQuery = DataList::create(static::class)
|
||||
->filter('ID', $this->ID)
|
||||
->dataQuery()
|
||||
->query();
|
||||
@ -1520,7 +1499,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
if (empty($component)) {
|
||||
$component = $this->model->$class->newObject();
|
||||
$component = Injector::inst()->create($class);
|
||||
}
|
||||
} elseif ($class = $schema->belongsToComponent(static::class, $componentName)) {
|
||||
$joinField = $schema->getRemoteJoinField(static::class, $componentName, 'belongs_to', $polymorphic);
|
||||
@ -1547,7 +1526,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
if (empty($component)) {
|
||||
$component = $this->model->$class->newObject();
|
||||
$component = Injector::inst()->create($class);
|
||||
if ($polymorphic) {
|
||||
$component->{$joinField.'ID'} = $this->ID;
|
||||
$component->{$joinField.'Class'} = static::class;
|
||||
@ -1603,10 +1582,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$result = HasManyList::create($componentClass, $joinField);
|
||||
}
|
||||
|
||||
if ($this->model) {
|
||||
$result->setDataModel($this->model);
|
||||
}
|
||||
|
||||
return $result
|
||||
->setDataQueryParam($this->getInheritableQueryParams())
|
||||
->forForeignID($this->ID);
|
||||
@ -1728,9 +1703,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$joinField = "{$remoteRelation}ID";
|
||||
$componentClass = $schema->classForField($remoteClass, $joinField);
|
||||
$result = HasManyList::create($componentClass, $joinField);
|
||||
if ($this->model) {
|
||||
$result->setDataModel($this->model);
|
||||
}
|
||||
return $result
|
||||
->setDataQueryParam($this->getInheritableQueryParams())
|
||||
->forForeignID($this->ID);
|
||||
@ -1774,9 +1746,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$manyMany['childField'], // Reversed parent / child field
|
||||
$extraFields
|
||||
);
|
||||
if ($this->model) {
|
||||
$result->setDataModel($this->model);
|
||||
}
|
||||
$this->extend('updateManyManyComponents', $result);
|
||||
|
||||
// If this is called on a singleton, then we return an 'orphaned relation' that can have the
|
||||
@ -1835,10 +1804,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$query->setQueryParam('Component.ExtraFields', $extraFields);
|
||||
});
|
||||
|
||||
if ($this->model) {
|
||||
$result->setDataModel($this->model);
|
||||
}
|
||||
|
||||
$this->extend('updateManyManyComponents', $result);
|
||||
|
||||
// If this is called on a singleton, then we return an 'orphaned relation' that can have the
|
||||
@ -2827,9 +2792,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
. ' arguments');
|
||||
}
|
||||
|
||||
$result = DataList::create(get_called_class());
|
||||
$result->setDataModel(DataModel::inst());
|
||||
return $result;
|
||||
return DataList::create(get_called_class());
|
||||
}
|
||||
|
||||
if ($join) {
|
||||
@ -2847,7 +2810,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$result = $result->limit($limit);
|
||||
}
|
||||
|
||||
$result->setDataModel(DataModel::inst());
|
||||
return $result;
|
||||
}
|
||||
|
||||
@ -3150,7 +3112,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if (!$hasData) {
|
||||
$className = static::class;
|
||||
foreach ($defaultRecords as $record) {
|
||||
$obj = $this->model->$className->newObject($record);
|
||||
$obj = Injector::inst()->create($className, $record);
|
||||
$obj->write();
|
||||
}
|
||||
DB::alteration_message("Added default records to $className table", "created");
|
||||
|
@ -2,19 +2,17 @@
|
||||
|
||||
namespace SilverStripe\ORM;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
use BadMethodCallException;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Environment;
|
||||
use SilverStripe\Core\Manifest\ClassLoader;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Dev\DevelopmentAdmin;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Security\Permission;
|
||||
|
||||
// Include the DB class
|
||||
require_once("DB.php");
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
|
||||
/**
|
||||
* DatabaseAdmin class
|
||||
@ -58,13 +56,13 @@ class DatabaseAdmin extends Controller
|
||||
// if on CLI or with the database not ready. The latter makes it less errorprone to do an
|
||||
// initial schema build without requiring a default-admin login.
|
||||
// Access to this controller is always allowed in "dev-mode", or of the user is ADMIN.
|
||||
$isRunningTests = (class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test());
|
||||
$allowAllCLI = DevelopmentAdmin::config()->get('allow_all_cli');
|
||||
$canAccess = (
|
||||
Director::isDev()
|
||||
|| !Security::database_is_ready()
|
||||
// We need to ensure that DevelopmentAdminTest can simulate permission failures when running
|
||||
// "dev/tests" from CLI.
|
||||
|| (Director::is_cli() && !$isRunningTests)
|
||||
|| (Director::is_cli() && $allowAllCLI)
|
||||
|| Permission::check("ADMIN")
|
||||
);
|
||||
if (!$canAccess) {
|
||||
@ -87,14 +85,14 @@ class DatabaseAdmin extends Controller
|
||||
$allClasses = get_declared_classes();
|
||||
$rootClasses = [];
|
||||
foreach ($allClasses as $class) {
|
||||
if (get_parent_class($class) == 'SilverStripe\ORM\DataObject') {
|
||||
if (get_parent_class($class) == DataObject::class) {
|
||||
$rootClasses[$class] = array();
|
||||
}
|
||||
}
|
||||
|
||||
// Assign every other data object one of those
|
||||
foreach ($allClasses as $class) {
|
||||
if (!isset($rootClasses[$class]) && is_subclass_of($class, 'SilverStripe\ORM\DataObject')) {
|
||||
if (!isset($rootClasses[$class]) && is_subclass_of($class, DataObject::class)) {
|
||||
foreach ($rootClasses as $rootClass => $dummy) {
|
||||
if (is_subclass_of($class, $rootClass)) {
|
||||
$rootClasses[$rootClass][] = $class;
|
||||
@ -122,10 +120,10 @@ class DatabaseAdmin extends Controller
|
||||
public function build()
|
||||
{
|
||||
// The default time limit of 30 seconds is normally not enough
|
||||
increase_time_limit_to(600);
|
||||
Environment::increaseTimeLimitTo(600);
|
||||
|
||||
// Get all our classes
|
||||
ClassLoader::inst()->getManifest()->regenerate();
|
||||
ClassLoader::inst()->getManifest()->regenerate(false);
|
||||
|
||||
$url = $this->getReturnURL();
|
||||
if ($url) {
|
||||
@ -224,17 +222,16 @@ class DatabaseAdmin extends Controller
|
||||
}
|
||||
|
||||
// Load parameters from existing configuration
|
||||
global $databaseConfig;
|
||||
$databaseConfig = DB::getConfig();
|
||||
if (empty($databaseConfig) && empty($_REQUEST['db'])) {
|
||||
user_error("No database configuration available", E_USER_ERROR);
|
||||
throw new BadMethodCallException("No database configuration available");
|
||||
}
|
||||
$parameters = (!empty($databaseConfig)) ? $databaseConfig : $_REQUEST['db'];
|
||||
|
||||
// Check database name is given
|
||||
if (empty($parameters['database'])) {
|
||||
user_error(
|
||||
"No database name given; please give a value for \$databaseConfig['database']",
|
||||
E_USER_ERROR
|
||||
throw new BadMethodCallException(
|
||||
"No database name given; please give a value for SS_DATABASE_NAME or set SS_DATABASE_CHOOSE_NAME"
|
||||
);
|
||||
}
|
||||
$database = $parameters['database'];
|
||||
@ -260,7 +257,6 @@ class DatabaseAdmin extends Controller
|
||||
// Initiate schema update
|
||||
$dbSchema = DB::get_schema();
|
||||
$dbSchema->schemaUpdate(function () use ($dataClasses, $testMode, $quiet) {
|
||||
/** @var SilverStripe\ORM\DataObjectSchema $dataObjectSchema */
|
||||
$dataObjectSchema = DataObject::getSchema();
|
||||
|
||||
foreach ($dataClasses as $dataClass) {
|
||||
|
@ -70,7 +70,7 @@ class ManyManyThroughList extends RelationList
|
||||
if ($joinRow) {
|
||||
$joinClass = $this->manipulator->getJoinClass();
|
||||
$joinQueryParams = $this->manipulator->extractInheritableQueryParameters($this->dataQuery);
|
||||
$joinRecord = Injector::inst()->create($joinClass, $joinRow, false, $this->model, $joinQueryParams);
|
||||
$joinRecord = Injector::inst()->create($joinClass, $joinRow, false, $joinQueryParams);
|
||||
$record->setJoin($joinRecord, $joinAlias);
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,7 @@ use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\HTTPResponse_Exception;
|
||||
use SilverStripe\Control\RequestFilter;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\ORM\ValidationException;
|
||||
|
||||
class AuthenticationRequestFilter implements RequestFilter
|
||||
@ -42,13 +40,15 @@ class AuthenticationRequestFilter implements RequestFilter
|
||||
* Identify the current user from the request
|
||||
*
|
||||
* @param HTTPRequest $request
|
||||
* @param Session $session
|
||||
* @param DataModel $model
|
||||
* @return bool|void
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
public function preRequest(HTTPRequest $request, Session $session, DataModel $model)
|
||||
public function preRequest(HTTPRequest $request)
|
||||
{
|
||||
if (!Security::database_is_ready()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this
|
||||
->getAuthenticationHandler()
|
||||
@ -66,10 +66,9 @@ class AuthenticationRequestFilter implements RequestFilter
|
||||
*
|
||||
* @param HTTPRequest $request
|
||||
* @param HTTPResponse $response
|
||||
* @param DataModel $model
|
||||
* @return bool|void
|
||||
*/
|
||||
public function postRequest(HTTPRequest $request, HTTPResponse $response, DataModel $model)
|
||||
public function postRequest(HTTPRequest $request, HTTPResponse $response)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace SilverStripe\Security;
|
||||
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\ORM\ValidationResult;
|
||||
use SilverStripe\Security\MemberAuthenticator\LoginHandler;
|
||||
use SilverStripe\Security\MemberAuthenticator\LogoutHandler;
|
||||
@ -105,10 +106,11 @@ interface Authenticator
|
||||
* Method to authenticate an user.
|
||||
*
|
||||
* @param array $data Raw data to authenticate the user.
|
||||
* @param HTTPRequest $request
|
||||
* @param ValidationResult $result A validationresult which is either valid or contains the error message(s)
|
||||
* @return Member The matched member, or null if the authentication fails
|
||||
*/
|
||||
public function authenticate($data, ValidationResult &$result = null);
|
||||
public function authenticate(array $data, HTTPRequest $request, ValidationResult &$result = null);
|
||||
|
||||
/**
|
||||
* Check if the passed password matches the stored one (if the member is not locked out).
|
||||
|
@ -8,7 +8,7 @@ use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\HTTPResponse_Exception;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator;
|
||||
|
||||
/**
|
||||
@ -30,6 +30,13 @@ class BasicAuth
|
||||
*/
|
||||
private static $entire_site_protected = false;
|
||||
|
||||
/**
|
||||
* Set to true to ignore in CLI mode
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $ignore_cli = true;
|
||||
|
||||
/**
|
||||
* @config
|
||||
* @var String|array Holds a {@link Permission} code that is required
|
||||
@ -65,8 +72,7 @@ class BasicAuth
|
||||
$permissionCode = null,
|
||||
$tryUsingSessionLogin = true
|
||||
) {
|
||||
$isRunningTests = (class_exists(SapphireTest::class, false) && SapphireTest::is_running_test());
|
||||
if (!Security::database_is_ready() || (Director::is_cli() && !$isRunningTests)) {
|
||||
if (!Security::database_is_ready() || (Director::is_cli() && static::config()->get('ignore_cli'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -96,7 +102,7 @@ class BasicAuth
|
||||
$member = $authenticator->authenticate([
|
||||
'Email' => $request->getHeader('PHP_AUTH_USER'),
|
||||
'Password' => $request->getHeader('PHP_AUTH_PW'),
|
||||
]);
|
||||
], $request);
|
||||
if ($member instanceof Member) {
|
||||
break;
|
||||
}
|
||||
@ -180,10 +186,13 @@ class BasicAuth
|
||||
*/
|
||||
public static function protect_entire_site($protect = true, $code = 'ADMIN', $message = null)
|
||||
{
|
||||
static::config()->set('entire_site_protected', $protect);
|
||||
static::config()->set('entire_site_protected_code', $code);
|
||||
static::config()
|
||||
->set('entire_site_protected', $protect)
|
||||
->set('entire_site_protected_code', $code);
|
||||
if ($message) {
|
||||
static::config()->set('entire_site_protected_message', $message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@link BasicAuth::requireLogin()} if {@link BasicAuth::protect_entire_site()} has been called.
|
||||
@ -197,7 +206,6 @@ class BasicAuth
|
||||
$config = static::config();
|
||||
$request = Controller::curr()->getRequest();
|
||||
if ($config->get('entire_site_protected')) {
|
||||
/** @noinspection ExceptionsAnnotatingAndHandlingInspection */
|
||||
static::requireLogin(
|
||||
$request,
|
||||
$config->get('entire_site_protected_message'),
|
||||
|
@ -187,7 +187,7 @@ PHP
|
||||
$controller = $this->getResponseController(_t(__CLASS__.'.SUCCESS', 'Success'));
|
||||
$backURLs = array(
|
||||
$this->getRequest()->requestVar('BackURL'),
|
||||
Session::get('BackURL'),
|
||||
$this->getRequest()->getSession()->get('BackURL'),
|
||||
Director::absoluteURL(AdminRootController::config()->get('url_base'), true),
|
||||
);
|
||||
$backURL = null;
|
||||
|
@ -18,9 +18,12 @@ class DefaultAdminService
|
||||
use Injectable;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* Can be set to explicitly true or false, or left null.
|
||||
* If null, hasDefaultAdmin() will be inferred from environment.
|
||||
*
|
||||
* @var bool|null
|
||||
*/
|
||||
protected static $has_default_admin = false;
|
||||
protected static $has_default_admin = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
@ -72,7 +75,7 @@ class DefaultAdminService
|
||||
"No default admin configured. Please call hasDefaultAdmin() before getting default admin username"
|
||||
);
|
||||
}
|
||||
return static::$default_username;
|
||||
return static::$default_username ?: getenv('SS_DEFAULT_ADMIN_USERNAME');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,7 +89,7 @@ class DefaultAdminService
|
||||
"No default admin configured. Please call hasDefaultAdmin() before getting default admin password"
|
||||
);
|
||||
}
|
||||
return static::$default_password;
|
||||
return static::$default_password ?: getenv('SS_DEFAULT_ADMIN_PASSWORD');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,11 +99,16 @@ class DefaultAdminService
|
||||
*/
|
||||
public static function hasDefaultAdmin()
|
||||
{
|
||||
// Check environment if not explicitly set
|
||||
if (!isset(static::$has_default_admin)) {
|
||||
return !empty(getenv('SS_DEFAULT_ADMIN_USERNAME'))
|
||||
&& !empty(getenv('SS_DEFAULT_ADMIN_PASSWORD'));
|
||||
}
|
||||
return static::$has_default_admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the default admin credentials
|
||||
* Flush the default admin credentials.
|
||||
*/
|
||||
public static function clearDefaultAdmin()
|
||||
{
|
||||
|
@ -10,6 +10,7 @@ use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\Email\Email;
|
||||
use SilverStripe\Control\Email\Mailer;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
@ -176,14 +177,6 @@ class Member extends DataObject
|
||||
*/
|
||||
private static $unique_identifier_field = 'Email';
|
||||
|
||||
/**
|
||||
* Object for validating user's password
|
||||
*
|
||||
* @config
|
||||
* @var PasswordValidator
|
||||
*/
|
||||
private static $password_validator = null;
|
||||
|
||||
/**
|
||||
* @config
|
||||
* The number of days that a password should be valid for.
|
||||
@ -369,23 +362,31 @@ class Member extends DataObject
|
||||
/**
|
||||
* Set a {@link PasswordValidator} object to use to validate member's passwords.
|
||||
*
|
||||
* @param PasswordValidator $pv
|
||||
* @param PasswordValidator $validator
|
||||
*/
|
||||
public static function set_password_validator($pv)
|
||||
public static function set_password_validator(PasswordValidator $validator = null)
|
||||
{
|
||||
self::$password_validator = $pv;
|
||||
// Override existing config
|
||||
Config::modify()->remove(Injector::class, PasswordValidator::class);
|
||||
if ($validator) {
|
||||
Injector::inst()->registerService($validator, PasswordValidator::class);
|
||||
} else {
|
||||
Injector::inst()->unregisterNamedObject(PasswordValidator::class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current {@link PasswordValidator}
|
||||
* Returns the default {@link PasswordValidator}
|
||||
*
|
||||
* @return PasswordValidator
|
||||
*/
|
||||
public static function password_validator()
|
||||
{
|
||||
return self::$password_validator;
|
||||
if (Injector::inst()->has(PasswordValidator::class)) {
|
||||
return Injector::inst()->get(PasswordValidator::class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public function isPasswordExpired()
|
||||
{
|
||||
@ -1605,16 +1606,17 @@ class Member extends DataObject
|
||||
public function validate()
|
||||
{
|
||||
$valid = parent::validate();
|
||||
$validator = static::password_validator();
|
||||
|
||||
if (!$this->ID || $this->isChanged('Password')) {
|
||||
if ($this->Password && self::$password_validator) {
|
||||
$valid->combineAnd(self::$password_validator->validate($this->Password, $this));
|
||||
if ($this->Password && $validator) {
|
||||
$valid->combineAnd($validator->validate($this->Password, $this));
|
||||
}
|
||||
}
|
||||
|
||||
if ((!$this->ID && $this->SetPassword) || $this->isChanged('SetPassword')) {
|
||||
if ($this->SetPassword && self::$password_validator) {
|
||||
$valid->combineAnd(self::$password_validator->validate($this->SetPassword, $this));
|
||||
if ($this->SetPassword && $validator) {
|
||||
$valid->combineAnd($validator->validate($this->SetPassword, $this));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,8 @@ class ChangePasswordForm extends Form
|
||||
*/
|
||||
public function __construct($controller, $name, $fields = null, $actions = null)
|
||||
{
|
||||
$backURL = $controller->getBackURL() ?: Session::get('BackURL');
|
||||
$backURL = $controller->getBackURL()
|
||||
?: $controller->getRequest()->getSession()->get('BackURL');
|
||||
|
||||
if (!$fields) {
|
||||
$fields = $this->getFormFields();
|
||||
|
@ -6,7 +6,6 @@ namespace SilverStripe\Security\MemberAuthenticator;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\RequestHandler;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
@ -79,7 +78,8 @@ class ChangePasswordHandler extends RequestHandler
|
||||
return $this->redirect($this->link);
|
||||
}
|
||||
|
||||
if (Session::get('AutoLoginHash')) {
|
||||
$session = $this->getRequest()->getSession();
|
||||
if ($session->get('AutoLoginHash')) {
|
||||
$message = DBField::create_field(
|
||||
'HTMLFragment',
|
||||
'<p>' . _t(
|
||||
@ -157,7 +157,7 @@ class ChangePasswordHandler extends RequestHandler
|
||||
}
|
||||
|
||||
// Store the hash for the change password form. Will be unset after reload within the ChangePasswordForm.
|
||||
Session::set('AutoLoginHash', $member->encryptWithUserSettings($token));
|
||||
$this->getRequest()->getSession()->set('AutoLoginHash', $member->encryptWithUserSettings($token));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,14 +214,15 @@ class ChangePasswordHandler extends RequestHandler
|
||||
return $this->redirectBackToForm();
|
||||
}
|
||||
|
||||
$session = $this->getRequest()->getSession();
|
||||
if (!$member) {
|
||||
if (Session::get('AutoLoginHash')) {
|
||||
$member = Member::member_from_autologinhash(Session::get('AutoLoginHash'));
|
||||
if ($session->get('AutoLoginHash')) {
|
||||
$member = Member::member_from_autologinhash($session->get('AutoLoginHash'));
|
||||
}
|
||||
|
||||
// The user is not logged in and no valid auto login hash is available
|
||||
if (!$member) {
|
||||
Session::clear('AutoLoginHash');
|
||||
$session->clear('AutoLoginHash');
|
||||
|
||||
return $this->redirect($this->addBackURLParam(Security::singleton()->Link('login')));
|
||||
}
|
||||
@ -278,7 +279,7 @@ class ChangePasswordHandler extends RequestHandler
|
||||
}
|
||||
|
||||
// TODO Add confirmation message to login redirect
|
||||
Session::clear('AutoLoginHash');
|
||||
$session->clear('AutoLoginHash');
|
||||
|
||||
// Redirect to backurl
|
||||
$backURL = $this->getBackURL();
|
||||
|
@ -6,7 +6,6 @@ use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\RequestHandler;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\ORM\ValidationResult;
|
||||
use SilverStripe\Security\Authenticator;
|
||||
@ -105,17 +104,18 @@ class LoginHandler extends RequestHandler
|
||||
*
|
||||
* @param array $data Submitted data
|
||||
* @param MemberLoginForm $form
|
||||
* @param HTTPRequest $request
|
||||
* @return HTTPResponse
|
||||
*/
|
||||
public function doLogin($data, $form)
|
||||
public function doLogin($data, MemberLoginForm $form, HTTPRequest $request)
|
||||
{
|
||||
$failureMessage = null;
|
||||
|
||||
$this->extend('beforeLogin');
|
||||
// Successful login
|
||||
/** @var ValidationResult $result */
|
||||
if ($member = $this->checkLogin($data, $result)) {
|
||||
$this->performLogin($member, $data, $form->getRequestHandler()->getRequest());
|
||||
if ($member = $this->checkLogin($data, $request, $result)) {
|
||||
$this->performLogin($member, $data, $request);
|
||||
// Allow operations on the member after successful login
|
||||
$this->extend('afterLogin', $member);
|
||||
|
||||
@ -138,8 +138,11 @@ class LoginHandler extends RequestHandler
|
||||
/** @skipUpgrade */
|
||||
if (array_key_exists('Email', $data)) {
|
||||
$rememberMe = (isset($data['Remember']) && Security::config()->get('autologin_enabled') === true);
|
||||
Session::set('SessionForms.MemberLoginForm.Email', $data['Email']);
|
||||
Session::set('SessionForms.MemberLoginForm.Remember', $rememberMe);
|
||||
$this
|
||||
->getRequest()
|
||||
->getSession()
|
||||
->set('SessionForms.MemberLoginForm.Email', $data['Email'])
|
||||
->set('SessionForms.MemberLoginForm.Remember', $rememberMe);
|
||||
}
|
||||
|
||||
// Fail to login redirects back to form
|
||||
@ -167,8 +170,11 @@ class LoginHandler extends RequestHandler
|
||||
*/
|
||||
protected function redirectAfterSuccessfulLogin()
|
||||
{
|
||||
Session::clear('SessionForms.MemberLoginForm.Email');
|
||||
Session::clear('SessionForms.MemberLoginForm.Remember');
|
||||
$this
|
||||
->getRequest()
|
||||
->getSession()
|
||||
->clear('SessionForms.MemberLoginForm.Email')
|
||||
->clear('SessionForms.MemberLoginForm.Remember');
|
||||
|
||||
$member = Security::getCurrentUser();
|
||||
if ($member->isPasswordExpired()) {
|
||||
@ -206,13 +212,14 @@ class LoginHandler extends RequestHandler
|
||||
* Try to authenticate the user
|
||||
*
|
||||
* @param array $data Submitted data
|
||||
* @param HTTPRequest $request
|
||||
* @param ValidationResult $result
|
||||
* @return Member Returns the member object on successful authentication
|
||||
* or NULL on failure.
|
||||
*/
|
||||
public function checkLogin($data, ValidationResult &$result = null)
|
||||
public function checkLogin($data, HTTPRequest $request, ValidationResult &$result = null)
|
||||
{
|
||||
$member = $this->authenticator->authenticate($data, $result);
|
||||
$member = $this->authenticator->authenticate($data, $request, $result);
|
||||
if ($member instanceof Member) {
|
||||
return $member;
|
||||
}
|
||||
@ -229,11 +236,13 @@ class LoginHandler extends RequestHandler
|
||||
* @return Member Returns the member object on successful authentication
|
||||
* or NULL on failure.
|
||||
*/
|
||||
public function performLogin($member, $data, $request)
|
||||
public function performLogin($member, $data, HTTPRequest $request)
|
||||
{
|
||||
/** IdentityStore */
|
||||
$rememberMe = (isset($data['Remember']) && Security::config()->get('autologin_enabled'));
|
||||
Injector::inst()->get(IdentityStore::class)->logIn($member, $rememberMe, $request);
|
||||
/** @var IdentityStore $identityStore */
|
||||
$identityStore = Injector::inst()->get(IdentityStore::class);
|
||||
$identityStore->logIn($member, $rememberMe, $request);
|
||||
|
||||
return $member;
|
||||
}
|
||||
|
@ -3,16 +3,16 @@
|
||||
namespace SilverStripe\Security\MemberAuthenticator;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Core\Extensible;
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\ORM\ValidationResult;
|
||||
use SilverStripe\Security\Authenticator;
|
||||
use SilverStripe\Security\DefaultAdminService;
|
||||
use SilverStripe\Security\LoginAttempt;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\PasswordEncryptor;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Security\DefaultAdminService;
|
||||
|
||||
/**
|
||||
* Authenticator for the default "member" method
|
||||
@ -31,21 +31,16 @@ class MemberAuthenticator implements Authenticator
|
||||
| Authenticator::RESET_PASSWORD | Authenticator::CHECK_PASSWORD;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param null|ValidationResult $result
|
||||
* @return null|Member
|
||||
*/
|
||||
public function authenticate($data, ValidationResult &$result = null)
|
||||
public function authenticate(array $data, HTTPRequest $request, ValidationResult &$result = null)
|
||||
{
|
||||
// Find authenticated member
|
||||
$member = $this->authenticateMember($data, $result);
|
||||
|
||||
// Optionally record every login attempt as a {@link LoginAttempt} object
|
||||
$this->recordLoginAttempt($data, $member, $result->isValid());
|
||||
$this->recordLoginAttempt($data, $request, $member, $result->isValid());
|
||||
|
||||
if ($member) {
|
||||
Session::clear('BackURL');
|
||||
$request->getSession()->clear('BackURL');
|
||||
}
|
||||
|
||||
return $result->isValid() ? $member : null;
|
||||
@ -163,10 +158,11 @@ class MemberAuthenticator implements Authenticator
|
||||
* TODO We could handle this with an extension
|
||||
*
|
||||
* @param array $data
|
||||
* @param HTTPRequest $request
|
||||
* @param Member $member
|
||||
* @param boolean $success
|
||||
*/
|
||||
protected function recordLoginAttempt($data, $member, $success)
|
||||
protected function recordLoginAttempt($data, HTTPRequest $request, $member, $success)
|
||||
{
|
||||
if (!Security::config()->get('login_recording')) {
|
||||
return;
|
||||
@ -193,15 +189,16 @@ class MemberAuthenticator implements Authenticator
|
||||
if ($member) {
|
||||
// Audit logging hook
|
||||
$attempt->MemberID = $member->ID;
|
||||
$member->extend('authenticationFailed');
|
||||
$member->extend('authenticationFailed', $data, $request);
|
||||
} else {
|
||||
// Audit logging hook
|
||||
Member::singleton()->extend('authenticationFailedUnknownUser', $data);
|
||||
Member::singleton()
|
||||
->extend('authenticationFailedUnknownUser', $data, $request);
|
||||
}
|
||||
}
|
||||
|
||||
$attempt->Email = $email;
|
||||
$attempt->IP = Controller::curr()->getRequest()->getIP();
|
||||
$attempt->IP = $request->getIP();
|
||||
$attempt->write();
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ namespace SilverStripe\Security\MemberAuthenticator;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\RequestHandler;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Forms\CheckboxField;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\FormAction;
|
||||
@ -124,11 +123,11 @@ class MemberLoginForm extends BaseLoginForm
|
||||
*/
|
||||
protected function getFormFields()
|
||||
{
|
||||
$request = $this->getController()->getRequest();
|
||||
$request = $this->getRequest();
|
||||
if ($request->getVar('BackURL')) {
|
||||
$backURL = $request->getVar('BackURL');
|
||||
} else {
|
||||
$backURL = Session::get('BackURL');
|
||||
$backURL = $request->getSession()->get('BackURL');
|
||||
}
|
||||
|
||||
$label = Member::singleton()->fieldLabel(Member::config()->get('unique_identifier_field'));
|
||||
@ -143,7 +142,7 @@ class MemberLoginForm extends BaseLoginForm
|
||||
$emailField->setAttribute('autofocus', 'true');
|
||||
|
||||
if (Security::config()->get('remember_username')) {
|
||||
$emailField->setValue(Session::get('SessionForms.MemberLoginForm.Email'));
|
||||
$emailField->setValue($this->getSession()->get('SessionForms.MemberLoginForm.Email'));
|
||||
} else {
|
||||
// Some browsers won't respect this attribute unless it's added to the form
|
||||
$this->setAttribute('autocomplete', 'off');
|
||||
@ -191,11 +190,14 @@ class MemberLoginForm extends BaseLoginForm
|
||||
return $actions;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function restoreFormState()
|
||||
{
|
||||
parent::restoreFormState();
|
||||
|
||||
$forceMessage = Session::get('MemberLoginForm.force_message');
|
||||
$session = $this->getSession();
|
||||
$forceMessage = $session->get('MemberLoginForm.force_message');
|
||||
if (($member = Security::getCurrentUser()) && !$forceMessage) {
|
||||
$message = _t(
|
||||
'SilverStripe\\Security\\Member.LOGGEDINAS',
|
||||
@ -207,7 +209,7 @@ class MemberLoginForm extends BaseLoginForm
|
||||
|
||||
// Reset forced message
|
||||
if ($forceMessage) {
|
||||
Session::set('MemberLoginForm.force_message', false);
|
||||
$session->set('MemberLoginForm.force_message', false);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
namespace SilverStripe\Security\MemberAuthenticator;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Cookie;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Security\AuthenticationHandler;
|
||||
use SilverStripe\Security\Member;
|
||||
|
||||
@ -47,7 +47,7 @@ class SessionAuthenticationHandler implements AuthenticationHandler
|
||||
{
|
||||
// If ID is a bad ID it will be treated as if the user is not logged in, rather than throwing a
|
||||
// ValidationException
|
||||
$id = Session::get($this->getSessionVariable());
|
||||
$id = $request->getSession()->get($this->getSessionVariable());
|
||||
if (!$id) {
|
||||
return null;
|
||||
}
|
||||
@ -64,7 +64,8 @@ class SessionAuthenticationHandler implements AuthenticationHandler
|
||||
public function logIn(Member $member, $persistent = false, HTTPRequest $request = null)
|
||||
{
|
||||
static::regenerateSessionId();
|
||||
Session::set($this->getSessionVariable(), $member->ID);
|
||||
$request = $request ?: Controller::curr()->getRequest();
|
||||
$request->getSession()->set($this->getSessionVariable(), $member->ID);
|
||||
|
||||
// This lets apache rules detect whether the user has logged in
|
||||
// @todo make this a setting on the authentication handler
|
||||
@ -102,6 +103,7 @@ class SessionAuthenticationHandler implements AuthenticationHandler
|
||||
*/
|
||||
public function logOut(HTTPRequest $request = null)
|
||||
{
|
||||
Session::clear($this->getSessionVariable());
|
||||
$request = $request ?: Controller::curr()->getRequest();
|
||||
$request->getSession()->clear($this->getSessionVariable());
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
@ -329,7 +328,7 @@ class Security extends Controller implements TemplateGlobalProvider
|
||||
{
|
||||
self::set_ignore_disallowed_actions(true);
|
||||
|
||||
if (!$controller) {
|
||||
if (!$controller && Controller::has_curr()) {
|
||||
$controller = Controller::curr();
|
||||
}
|
||||
|
||||
@ -393,7 +392,11 @@ class Security extends Controller implements TemplateGlobalProvider
|
||||
}
|
||||
|
||||
static::singleton()->setSessionMessage($message, ValidationResult::TYPE_WARNING);
|
||||
$loginResponse = static::singleton()->login();
|
||||
$request = new HTTPRequest('GET', '/');
|
||||
if ($controller) {
|
||||
$request->setSession($controller->getRequest()->getSession());
|
||||
}
|
||||
$loginResponse = static::singleton()->login($request);
|
||||
if ($loginResponse instanceof HTTPResponse) {
|
||||
return $loginResponse;
|
||||
}
|
||||
@ -409,7 +412,7 @@ class Security extends Controller implements TemplateGlobalProvider
|
||||
|
||||
static::singleton()->setSessionMessage($message, ValidationResult::TYPE_WARNING);
|
||||
|
||||
Session::set("BackURL", $_SERVER['REQUEST_URI']);
|
||||
$controller->getRequest()->getSession()->set("BackURL", $_SERVER['REQUEST_URI']);
|
||||
|
||||
// TODO AccessLogEntry needs an extension to handle permission denied errors
|
||||
// Audit logging hook
|
||||
@ -522,6 +525,21 @@ class Security extends Controller implements TemplateGlobalProvider
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRequest()
|
||||
{
|
||||
// Support Security::singleton() where a request isn't always injected
|
||||
$request = parent::getRequest();
|
||||
if ($request) {
|
||||
return $request;
|
||||
}
|
||||
|
||||
if (Controller::has_curr() && Controller::curr() !== $this) {
|
||||
return Controller::curr()->getRequest();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the controller for handling the response to this request
|
||||
*
|
||||
@ -546,7 +564,6 @@ class Security extends Controller implements TemplateGlobalProvider
|
||||
$holderPage->ID = -1 * random_int(1, 10000000);
|
||||
|
||||
$controller = ModelAsController::controller_for($holderPage);
|
||||
$controller->setDataModel($this->model);
|
||||
$controller->doInit();
|
||||
|
||||
return $controller;
|
||||
@ -581,14 +598,15 @@ class Security extends Controller implements TemplateGlobalProvider
|
||||
*/
|
||||
protected function getSessionMessage(&$messageType = null)
|
||||
{
|
||||
$message = Session::get('Security.Message.message');
|
||||
$session = $this->getRequest()->getSession();
|
||||
$message = $session->get('Security.Message.message');
|
||||
$messageType = null;
|
||||
if (empty($message)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$messageType = Session::get('Security.Message.type');
|
||||
$messageCast = Session::get('Security.Message.cast');
|
||||
$messageType = $session->get('Security.Message.type');
|
||||
$messageCast = $session->get('Security.Message.cast');
|
||||
if ($messageCast !== ValidationResult::CAST_HTML) {
|
||||
$message = Convert::raw2xml($message);
|
||||
}
|
||||
@ -608,9 +626,12 @@ class Security extends Controller implements TemplateGlobalProvider
|
||||
$messageType = ValidationResult::TYPE_WARNING,
|
||||
$messageCast = ValidationResult::CAST_TEXT
|
||||
) {
|
||||
Session::set('Security.Message.message', $message);
|
||||
Session::set('Security.Message.type', $messageType);
|
||||
Session::set('Security.Message.cast', $messageCast);
|
||||
Controller::curr()
|
||||
->getRequest()
|
||||
->getSession()
|
||||
->set("Security.Message.message", $message)
|
||||
->set("Security.Message.type", $messageType)
|
||||
->set("Security.Message.cast", $messageCast);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -618,7 +639,10 @@ class Security extends Controller implements TemplateGlobalProvider
|
||||
*/
|
||||
public static function clearSessionMessage()
|
||||
{
|
||||
Session::clear('Security.Message');
|
||||
Controller::curr()
|
||||
->getRequest()
|
||||
->getSession()
|
||||
->clear("Security.Message");
|
||||
}
|
||||
|
||||
|
||||
@ -634,16 +658,20 @@ class Security extends Controller implements TemplateGlobalProvider
|
||||
*/
|
||||
public function login($request = null, $service = Authenticator::LOGIN)
|
||||
{
|
||||
if ($request) {
|
||||
$this->setRequest($request);
|
||||
} elseif ($request) {
|
||||
$request = $this->getRequest();
|
||||
} else {
|
||||
throw new HTTPResponse_Exception("No request available", 500);
|
||||
}
|
||||
|
||||
// Check pre-login process
|
||||
if ($response = $this->preLogin()) {
|
||||
return $response;
|
||||
}
|
||||
$authName = null;
|
||||
|
||||
if (!$request) {
|
||||
$request = $this->getRequest();
|
||||
}
|
||||
|
||||
$handlers = $this->getServiceAuthenticatorsFromRequest($service, $request);
|
||||
|
||||
$link = $this->Link('login');
|
||||
@ -798,7 +826,7 @@ class Security extends Controller implements TemplateGlobalProvider
|
||||
// Process each of the handlers
|
||||
$results = array_map(
|
||||
function (RequestHandler $handler) {
|
||||
return $handler->handleRequest($this->getRequest(), DataModel::inst());
|
||||
return $handler->handleRequest($this->getRequest());
|
||||
},
|
||||
$handlers
|
||||
);
|
||||
@ -818,7 +846,7 @@ class Security extends Controller implements TemplateGlobalProvider
|
||||
*/
|
||||
protected function delegateToHandler(RequestHandler $handler, $title, array $templates = [])
|
||||
{
|
||||
$result = $handler->handleRequest($this->getRequest(), DataModel::inst());
|
||||
$result = $handler->handleRequest($this->getRequest());
|
||||
|
||||
// Return the customised controller - may be used to render a Form (e.g. login form)
|
||||
if (is_array($result)) {
|
||||
@ -998,7 +1026,6 @@ class Security extends Controller implements TemplateGlobalProvider
|
||||
DefaultAdminService::clearDefaultAdmin();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a default admin in dev-mode
|
||||
*
|
||||
|
@ -150,7 +150,8 @@ class SecurityToken implements TemplateGlobalProvider
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
$value = Session::get($this->getName());
|
||||
$session = Controller::curr()->getRequest()->getSession();
|
||||
$value = $session->get($this->getName());
|
||||
|
||||
// only regenerate if the token isn't already set in the session
|
||||
if (!$value) {
|
||||
@ -166,7 +167,8 @@ class SecurityToken implements TemplateGlobalProvider
|
||||
*/
|
||||
public function setValue($val)
|
||||
{
|
||||
Session::set($this->getName(), $val);
|
||||
$session = Controller::curr()->getRequest()->getSession();
|
||||
$session->set($this->getName(), $val);
|
||||
}
|
||||
|
||||
/**
|
||||
|
39
src/View/Dev/RequirementsTestState.php
Normal file
39
src/View/Dev/RequirementsTestState.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace SilverStripe\View\Dev;
|
||||
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Dev\State\TestState;
|
||||
use SilverStripe\View\Requirements;
|
||||
use SilverStripe\View\Requirements_Backend;
|
||||
|
||||
/**
|
||||
* Resets requirements for test state
|
||||
*/
|
||||
class RequirementsTestState implements TestState
|
||||
{
|
||||
/**
|
||||
* @var Requirements_Backend
|
||||
*/
|
||||
protected $backend = null;
|
||||
|
||||
public function setUp(SapphireTest $test)
|
||||
{
|
||||
$this->backend = Requirements::backend();
|
||||
Requirements::set_backend(Requirements_Backend::create());
|
||||
}
|
||||
|
||||
public function tearDown(SapphireTest $test)
|
||||
{
|
||||
Requirements::set_backend($this->backend);
|
||||
}
|
||||
|
||||
public function setUpOnce($class)
|
||||
{
|
||||
}
|
||||
|
||||
public function tearDownOnce($class)
|
||||
{
|
||||
}
|
||||
}
|
@ -2,9 +2,8 @@
|
||||
|
||||
namespace SilverStripe\View;
|
||||
|
||||
use SilverStripe\ORM\DataList;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\ORM\DataList;
|
||||
|
||||
class GenericTemplateGlobalProvider implements TemplateGlobalProvider
|
||||
{
|
||||
@ -61,8 +60,6 @@ class GenericTemplateGlobalProvider implements TemplateGlobalProvider
|
||||
*/
|
||||
public static function getDataList($className)
|
||||
{
|
||||
$list = new DataList($className);
|
||||
$list->setDataModel(DataModel::inst());
|
||||
return $list;
|
||||
return DataList::create($className);
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,11 @@
|
||||
|
||||
namespace SilverStripe\View;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Flushable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Requirements tracker for JavaScript and CSS.
|
||||
@ -104,7 +103,7 @@ class Requirements implements Flushable
|
||||
public static function backend()
|
||||
{
|
||||
if (!self::$backend) {
|
||||
self::$backend = Injector::inst()->create('SilverStripe\\View\\Requirements_Backend');
|
||||
self::$backend = Requirements_Backend::create();
|
||||
}
|
||||
return self::$backend;
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
namespace SilverStripe\View;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Assets\File;
|
||||
use SilverStripe\Assets\Storage\GeneratedAssetHandler;
|
||||
use SilverStripe\Control\Director;
|
||||
@ -13,7 +13,6 @@ use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\i18n\i18n;
|
||||
|
||||
class Requirements_Backend
|
||||
@ -33,9 +32,9 @@ class Requirements_Backend
|
||||
/**
|
||||
* Whether to combine CSS and JavaScript files
|
||||
*
|
||||
* @var bool
|
||||
* @var bool|null
|
||||
*/
|
||||
protected $combinedFilesEnabled = true;
|
||||
protected $combinedFilesEnabled = null;
|
||||
|
||||
/**
|
||||
* Determine if files should be combined automatically on dev mode.
|
||||
@ -1326,9 +1325,12 @@ class Requirements_Backend
|
||||
if ($minify && !$this->minifier) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'Cannot minify files without a minification service defined.
|
||||
<<<MESSAGE
|
||||
Cannot minify files without a minification service defined.
|
||||
Set %s::minifyCombinedFiles to false, or inject a %s service on
|
||||
%s.properties.minifier',
|
||||
%s.properties.minifier
|
||||
MESSAGE
|
||||
,
|
||||
__CLASS__,
|
||||
Requirements_Minifier::class,
|
||||
__CLASS__
|
||||
@ -1394,18 +1396,8 @@ class Requirements_Backend
|
||||
*/
|
||||
public function getCombinedFilesEnabled()
|
||||
{
|
||||
if (!$this->combinedFilesEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tests should be combined
|
||||
if (class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if specified via querystring
|
||||
if (isset($_REQUEST['combine'])) {
|
||||
return true;
|
||||
if (isset($this->combinedFilesEnabled)) {
|
||||
return $this->combinedFilesEnabled;
|
||||
}
|
||||
|
||||
// Non-dev sites are always combined
|
||||
|
@ -21,13 +21,6 @@ class ThemeManifest implements ThemeList
|
||||
*/
|
||||
protected $base;
|
||||
|
||||
/**
|
||||
* Include tests
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $tests;
|
||||
|
||||
/**
|
||||
* Path to application code
|
||||
*
|
||||
@ -56,39 +49,44 @@ class ThemeManifest implements ThemeList
|
||||
*/
|
||||
protected $themes = null;
|
||||
|
||||
/**
|
||||
* @var CacheFactory
|
||||
*/
|
||||
protected $cacheFactory= null;
|
||||
|
||||
/**
|
||||
* Constructs a new template manifest. The manifest is not actually built
|
||||
* or loaded from cache until needed.
|
||||
*
|
||||
* @param string $base The base path.
|
||||
* @param string $project Path to application code
|
||||
*
|
||||
* @param bool $includeTests Include tests in the manifest.
|
||||
* @param bool $forceRegen Force the manifest to be regenerated.
|
||||
* @param CacheFactory $cacheFactory Cache factory to generate backend cache with
|
||||
*/
|
||||
public function __construct(
|
||||
$base,
|
||||
$project,
|
||||
$includeTests = false,
|
||||
$forceRegen = false,
|
||||
CacheFactory $cacheFactory = null
|
||||
) {
|
||||
public function __construct($base, $project, CacheFactory $cacheFactory = null)
|
||||
{
|
||||
$this->base = $base;
|
||||
$this->tests = $includeTests;
|
||||
$this->project = $project;
|
||||
$this->cacheFactory = $cacheFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $includeTests Include tests in the manifest
|
||||
* @param bool $forceRegen Force the manifest to be regenerated.
|
||||
*/
|
||||
public function init($includeTests = false, $forceRegen = false)
|
||||
{
|
||||
// build cache from factory
|
||||
if ($cacheFactory) {
|
||||
$this->cache = $cacheFactory->create(
|
||||
if ($this->cacheFactory) {
|
||||
$this->cache = $this->cacheFactory->create(
|
||||
CacheInterface::class.'.thememanifest',
|
||||
[ 'namespace' => 'thememanifest' . ($includeTests ? '_tests' : '') ]
|
||||
);
|
||||
}
|
||||
$this->cacheKey = $this->getCacheKey();
|
||||
|
||||
if ($forceRegen) {
|
||||
$this->regenerate();
|
||||
$this->cacheKey = $this->getCacheKey($includeTests);
|
||||
if (!$forceRegen && $this->cache && ($data = $this->cache->get($this->cacheKey))) {
|
||||
$this->themes = $data;
|
||||
} else {
|
||||
$this->regenerate($includeTests);
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,36 +102,37 @@ class ThemeManifest implements ThemeList
|
||||
* Generate a unique cache key to avoid manifest cache collisions.
|
||||
* We compartmentalise based on the base path, the given project, and whether
|
||||
* or not we intend to include tests.
|
||||
*
|
||||
* @param bool $includeTests
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheKey()
|
||||
public function getCacheKey($includeTests = false)
|
||||
{
|
||||
return sha1(sprintf(
|
||||
"manifest-%s-%s-%u",
|
||||
$this->base,
|
||||
$this->project,
|
||||
$this->tests
|
||||
$includeTests
|
||||
));
|
||||
}
|
||||
|
||||
public function getThemes()
|
||||
{
|
||||
if ($this->themes === null) {
|
||||
$this->init();
|
||||
}
|
||||
return $this->themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerates the manifest by scanning the base path.
|
||||
*
|
||||
* @param bool $includeTests
|
||||
*/
|
||||
public function regenerate()
|
||||
public function regenerate($includeTests = false)
|
||||
{
|
||||
$finder = new ManifestFileFinder();
|
||||
$finder->setOptions(array(
|
||||
'include_themes' => false,
|
||||
'ignore_dirs' => array('node_modules', THEMES_DIR),
|
||||
'ignore_tests' => !$this->tests,
|
||||
'ignore_tests' => !$includeTests,
|
||||
'dir_callback' => array($this, 'handleDirectory')
|
||||
));
|
||||
|
||||
@ -172,16 +171,4 @@ class ThemeManifest implements ThemeList
|
||||
array_push($this->themes, $path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the manifest
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
if ($this->cache && ($data = $this->cache->get($this->cacheKey))) {
|
||||
$this->themes = $data;
|
||||
} else {
|
||||
$this->regenerate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace SilverStripe\View;
|
||||
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Dev\Debug;
|
||||
|
||||
/**
|
||||
* Handles finding templates from a stack of template manifest objects.
|
||||
|
@ -385,7 +385,7 @@ class ViewableData implements IteratorAggregate
|
||||
$template = new SSViewer($template);
|
||||
}
|
||||
|
||||
$data = ($this->customisedObject) ? $this->customisedObject : $this;
|
||||
$data = $this->getCustomisedObj() ?: $this;
|
||||
|
||||
if ($customFields instanceof ViewableData) {
|
||||
$data = $data->customise($customFields);
|
||||
|
@ -1,172 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Configure SilverStripe from the environment variables.
|
||||
* Usage: Put "require_once('conf/ConfigureFromEnv.php');" into your _config.php file.
|
||||
* @deprecated 4.0...5.0
|
||||
*
|
||||
* If you include this file, you will be able to use the following variables to control
|
||||
* your site.
|
||||
*
|
||||
* Your database connection will be set up using these defines:
|
||||
* - SS_DATABASE_CLASS: The database class to use, MySQLDatabase, MSSQLDatabase, etc. defaults to
|
||||
* MySQLPDODatabase
|
||||
* - SS_DATABASE_SERVER: The database server to use, defaulting to localhost
|
||||
* - SS_DATABASE_USERNAME: The database username (mandatory)
|
||||
* - SS_DATABASE_PASSWORD: The database password (mandatory)
|
||||
* - SS_DATABASE_PORT: The database port
|
||||
* - SS_DATABASE_SUFFIX: A suffix to add to the database name.
|
||||
* - SS_DATABASE_PREFIX: A prefix to add to the database name.
|
||||
* - SS_DATABASE_TIMEZONE: Set the database timezone to something other than the system timezone.
|
||||
* - SS_DATABASE_MEMORY: Use in-memory state if possible. Useful for testing, currently only
|
||||
* supported by the SQLite database adapter.
|
||||
*
|
||||
* There is one more setting that is intended to be used by people who work on SilverStripe.
|
||||
* - SS_DATABASE_CHOOSE_NAME: Boolean/Int. If set, then the system will choose a default database name for you if
|
||||
* one isn't give in the $database variable. The database name will be "SS_" followed by the name of the folder
|
||||
* into which you have installed SilverStripe. If this is enabled, it means that the phpinstaller will work out of
|
||||
* the box without the installer needing to alter any files. This helps prevent accidental changes to the
|
||||
* environment.
|
||||
*
|
||||
* If SS_DATABASE_CHOOSE_NAME is an integer greater than one, then an ancestor folder will be used for the database
|
||||
* name. This is handy for a site that's hosted from /sites/examplesite/www or /buildbot/allmodules-2.3/build. If
|
||||
* it's 2, the parent folder will be chosen; if it's 3 the grandparent, and so on.
|
||||
*
|
||||
* You can configure the environment with this define:
|
||||
*
|
||||
* - SS_ENVIRONMENT_TYPE: The environment type: dev, test or live.
|
||||
*
|
||||
* You can configure the default admin with these defines:
|
||||
*
|
||||
* - SS_DEFAULT_ADMIN_USERNAME: The username of the default admin - this is a non-database user with administrative
|
||||
* privileges.
|
||||
* - SS_DEFAULT_ADMIN_PASSWORD: The password of the default admin.
|
||||
* - SS_USE_BASIC_AUTH: Protect the site with basic auth (good for test sites)
|
||||
*
|
||||
* Email:
|
||||
* - SS_SEND_ALL_EMAILS_TO: If you set this define, all emails will be redirected to this address.
|
||||
* - SS_SEND_ALL_EMAILS_FROM: If you set this define, all emails will be send from this address.
|
||||
* Placeholder empty file
|
||||
*/
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use SilverStripe\Control\Email\Email;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\Install\DatabaseAdapterRegistry;
|
||||
use SilverStripe\Security\BasicAuth;
|
||||
use SilverStripe\Security\DefaultAdminService;
|
||||
|
||||
global $database;
|
||||
|
||||
// No database provided
|
||||
if (!isset($database) || !$database) {
|
||||
if (!($database = getenv('SS_DATABASE_NAME')) && $chooseName = getenv('SS_DATABASE_CHOOSE_NAME')) {
|
||||
$loopCount = (int)$chooseName;
|
||||
$databaseDir = BASE_PATH;
|
||||
for ($i=0; $i<$loopCount-1; $i++) {
|
||||
$databaseDir = dirname($databaseDir);
|
||||
}
|
||||
$database = getenv('SS_DATABASE_PREFIX') ?: 'SS_';
|
||||
$database .= basename($databaseDir);
|
||||
$database = str_replace('.', '', $database);
|
||||
}
|
||||
}
|
||||
|
||||
if ($dbUser = getenv('SS_DATABASE_USERNAME')) {
|
||||
global $databaseConfig;
|
||||
|
||||
// Checks if the database global is defined (if present, wraps with prefix and suffix)
|
||||
$databaseNameWrapper = function ($name) {
|
||||
if (!$name) {
|
||||
return '';
|
||||
} else {
|
||||
return (getenv('SS_DATABASE_PREFIX') ?: '')
|
||||
. $name
|
||||
. (getenv('SS_DATABASE_SUFFIX') ?: '');
|
||||
}
|
||||
};
|
||||
|
||||
/** @skipUpgrade */
|
||||
$databaseConfig = array(
|
||||
"type" => getenv('SS_DATABASE_CLASS') ?: 'MySQLPDODatabase',
|
||||
"server" => getenv('SS_DATABASE_SERVER') ?: 'localhost',
|
||||
"username" => $dbUser,
|
||||
"password" => getenv('SS_DATABASE_PASSWORD'),
|
||||
"database" => $databaseNameWrapper($database),
|
||||
);
|
||||
|
||||
// Set the port if called for
|
||||
if ($dbPort = getenv('SS_DATABASE_PORT')) {
|
||||
$databaseConfig['port'] = $dbPort;
|
||||
}
|
||||
|
||||
// Set the timezone if called for
|
||||
if ($dbTZ = getenv('SS_DATABASE_TIMEZONE')) {
|
||||
$databaseConfig['timezone'] = $dbTZ;
|
||||
}
|
||||
|
||||
// For schema enabled drivers:
|
||||
if ($dbSchema = getenv('SS_DATABASE_SCHEMA')) {
|
||||
$databaseConfig["schema"] = $dbSchema;
|
||||
}
|
||||
|
||||
// For SQlite3 memory databases (mainly for testing purposes)
|
||||
if ($dbMemory = getenv('SS_DATABASE_MEMORY')) {
|
||||
$databaseConfig["memory"] = $dbMemory;
|
||||
}
|
||||
}
|
||||
|
||||
if ($sendAllEmailsTo = getenv('SS_SEND_ALL_EMAILS_TO')) {
|
||||
Email::config()->send_all_emails_to = $sendAllEmailsTo;
|
||||
}
|
||||
if ($sendAllEmailsFrom = getenv('SS_SEND_ALL_EMAILS_FROM')) {
|
||||
Email::config()->send_all_emails_from = $sendAllEmailsFrom;
|
||||
}
|
||||
|
||||
if ($defaultAdminUser = getenv('SS_DEFAULT_ADMIN_USERNAME')) {
|
||||
if (!$defaultAdminPass = getenv('SS_DEFAULT_ADMIN_PASSWORD')) {
|
||||
user_error(
|
||||
"SS_DEFAULT_ADMIN_PASSWORD must be defined in your environment,"
|
||||
. "if SS_DEFAULT_ADMIN_USERNAME is defined. See "
|
||||
. "http://doc.silverstripe.org/framework/en/topics/environment-management for more information",
|
||||
E_USER_ERROR
|
||||
);
|
||||
} else {
|
||||
DefaultAdminService::setDefaultAdmin($defaultAdminUser, $defaultAdminPass);
|
||||
}
|
||||
}
|
||||
if ($useBasicAuth = getenv('SS_USE_BASIC_AUTH')) {
|
||||
BasicAuth::config()->entire_site_protected = $useBasicAuth;
|
||||
}
|
||||
|
||||
if ($errorLog = getenv('SS_ERROR_LOG')) {
|
||||
$logger = Injector::inst()->get(LoggerInterface::class);
|
||||
if ($logger instanceof Logger) {
|
||||
$logger->pushHandler(new StreamHandler(BASE_PATH . '/' . $errorLog, Logger::WARNING));
|
||||
} else {
|
||||
user_error("SS_ERROR_LOG setting only works with Monolog, you are using another logger", E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
// Allow database adapters to handle their own configuration
|
||||
DatabaseAdapterRegistry::autoconfigure();
|
||||
|
||||
unset(
|
||||
$envType,
|
||||
$chooseName,
|
||||
$loopCount,
|
||||
$databaseDir,
|
||||
$i,
|
||||
$databaseNameWrapper,
|
||||
$dbUser,
|
||||
$dbPort,
|
||||
$dbTZ,
|
||||
$dbSchema,
|
||||
$dbMemory,
|
||||
$sendAllEmailsTo,
|
||||
$sendAllEmailsFrom,
|
||||
$defaultAdminUser,
|
||||
$defaultAdminPass,
|
||||
$useBasicAuth,
|
||||
$errorLog,
|
||||
$logger
|
||||
);
|
||||
Deprecation::notice('5.0', 'ConfigureFromEnv.php is deprecated', Deprecation::SCOPE_GLOBAL);
|
||||
|
17
src/includes/autoload.php
Normal file
17
src/includes/autoload.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
// Init composer autoload
|
||||
call_user_func(function () {
|
||||
$candidates = [
|
||||
__DIR__ . '/../../vendor/autoload.php',
|
||||
__DIR__ . '/../../../vendor/autoload.php',
|
||||
getcwd() . '/vendor/autoload.php',
|
||||
];
|
||||
foreach ($candidates as $candidate) {
|
||||
if (file_exists($candidate)) {
|
||||
require_once $candidate;
|
||||
return;
|
||||
}
|
||||
}
|
||||
die("Failed to include composer's autoloader, unable to continue");
|
||||
});
|
@ -3,6 +3,7 @@
|
||||
use Dotenv\Dotenv;
|
||||
use Dotenv\Exception\InvalidPathException;
|
||||
use SilverStripe\Control\Util\IPUtils;
|
||||
use SilverStripe\Core\TempFolder;
|
||||
|
||||
/**
|
||||
* This file is the Framework constants bootstrap. It will prepare some basic common constants.
|
||||
@ -27,6 +28,8 @@ use SilverStripe\Control\Util\IPUtils;
|
||||
* headers from the given client are trustworthy (e.g. from a reverse proxy).
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/functions.php';
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// ENVIRONMENT CONFIG
|
||||
|
||||
@ -57,6 +60,7 @@ if (!defined('BASE_PATH')) {
|
||||
|
||||
// Allow a first class env var to be set that disables .env file loading
|
||||
if (!getenv('SS_IGNORE_DOT_ENV')) {
|
||||
call_user_func(function () {
|
||||
foreach ([BASE_PATH, dirname(BASE_PATH)] as $path) {
|
||||
try {
|
||||
(new Dotenv($path))->load();
|
||||
@ -66,6 +70,7 @@ if (!getenv('SS_IGNORE_DOT_ENV')) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -142,11 +147,7 @@ if (defined('CUSTOM_INCLUDE_PATH')) {
|
||||
set_include_path(CUSTOM_INCLUDE_PATH . PATH_SEPARATOR . get_include_path());
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the temporary folder if it wasn't defined yet
|
||||
*/
|
||||
require_once __DIR__ . '/TempPath.php';
|
||||
|
||||
// Define the temporary folder if it wasn't defined yet
|
||||
if (!defined('TEMP_FOLDER')) {
|
||||
define('TEMP_FOLDER', getTempFolder(BASE_PATH));
|
||||
define('TEMP_FOLDER', TempFolder::getTempFolder(BASE_PATH));
|
||||
}
|
68
src/includes/functions.php
Normal file
68
src/includes/functions.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\i18n\i18n;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// HELPER FUNCTIONS
|
||||
|
||||
/**
|
||||
* Creates a class instance by the "singleton" design pattern.
|
||||
* It will always return the same instance for this class,
|
||||
* which can be used for performance reasons and as a simple
|
||||
* way to access instance methods which don't rely on instance
|
||||
* data (e.g. the custom SilverStripe static handling).
|
||||
*
|
||||
* @param string $className
|
||||
* @return mixed
|
||||
*/
|
||||
function singleton($className)
|
||||
{
|
||||
if ($className === Config::class) {
|
||||
throw new InvalidArgumentException("Don't pass Config to singleton()");
|
||||
}
|
||||
if (!isset($className)) {
|
||||
throw new InvalidArgumentException("singleton() Called without a class");
|
||||
}
|
||||
if (!is_string($className)) {
|
||||
throw new InvalidArgumentException(
|
||||
"singleton() passed bad class_name: " . var_export($className, true)
|
||||
);
|
||||
}
|
||||
return Injector::inst()->get($className);
|
||||
}
|
||||
|
||||
function project()
|
||||
{
|
||||
global $project;
|
||||
// Set default project
|
||||
if (empty($project) && file_exists(BASE_PATH . '/mysite')) {
|
||||
$project = 'mysite';
|
||||
}
|
||||
return $project;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main translator function. Returns the string defined by $entity according to the
|
||||
* currently set locale.
|
||||
*
|
||||
* Also supports pluralisation of strings. Pass in a `count` argument, as well as a
|
||||
* default value with `|` pipe-delimited options for each plural form.
|
||||
*
|
||||
* @param string $entity Entity that identifies the string. It must be in the form
|
||||
* "Namespace.Entity" where Namespace will be usually the class name where this
|
||||
* string is used and Entity identifies the string inside the namespace.
|
||||
* @param mixed $arg,... Additional arguments are parsed as such:
|
||||
* - Next string argument is a default. Pass in a `|` pipe-delimeted value with `{count}`
|
||||
* to do pluralisation.
|
||||
* - Any other string argument after default is context for i18nTextCollector
|
||||
* - Any array argument in any order is an injection parameter list. Pass in a `count`
|
||||
* injection parameter to pluralise.
|
||||
* @return string
|
||||
*/
|
||||
function _t($entity, $arg = null)
|
||||
{
|
||||
// Pass args directly to handle deprecation
|
||||
return call_user_func_array([i18n::class, '_t'], func_get_args());
|
||||
}
|
@ -2,4 +2,3 @@
|
||||
|
||||
require __DIR__ . '/../bootstrap/init.php';
|
||||
require __DIR__ . '/../bootstrap/environment.php';
|
||||
require __DIR__ . '/../bootstrap/mysite.php';
|
||||
|
@ -3,4 +3,3 @@
|
||||
require __DIR__ . '/bootstrap/init.php';
|
||||
require __DIR__ . '/bootstrap/cli.php';
|
||||
require __DIR__ . '/bootstrap/environment.php';
|
||||
require __DIR__ . '/bootstrap/phpunit.php';
|
||||
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
// Default database settings
|
||||
global $project;
|
||||
$project = 'mysite';
|
||||
|
||||
global $database;
|
||||
$database = '';
|
||||
|
||||
require_once('conf/ConfigureFromEnv.php');
|
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
// Bootstrap for running SapphireTests
|
||||
|
||||
// Connect to database
|
||||
use SilverStripe\ORM\DB;
|
||||
|
||||
require_once __DIR__ . '/../../src/Core/Core.php';
|
||||
require_once __DIR__ . '/../php/Control/FakeController.php';
|
||||
|
||||
// Bootstrap a mock project configuration
|
||||
require __DIR__ . '/mysite.php';
|
||||
|
||||
global $databaseConfig;
|
||||
DB::connect($databaseConfig);
|
||||
|
||||
// Now set a fake REQUEST_URI
|
||||
$_SERVER['REQUEST_URI'] = BASE_URL;
|
||||
|
||||
// Fake a session
|
||||
$_SESSION = null;
|
||||
|
||||
// Remove the error handler so that PHPUnit can add its own
|
||||
restore_error_handler();
|
@ -3,8 +3,8 @@
|
||||
namespace SilverStripe\Control\Tests;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit_Framework_Error;
|
||||
use SilverStripe\Control\RequestHandler;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\Tests\ControllerTest\AccessBaseController;
|
||||
use SilverStripe\Control\Tests\ControllerTest\AccessSecuredController;
|
||||
use SilverStripe\Control\Tests\ControllerTest\AccessWildcardSecuredController;
|
||||
@ -15,13 +15,8 @@ use SilverStripe\Control\Tests\ControllerTest\IndexSecuredController;
|
||||
use SilverStripe\Control\Tests\ControllerTest\SubController;
|
||||
use SilverStripe\Control\Tests\ControllerTest\TestController;
|
||||
use SilverStripe\Control\Tests\ControllerTest\UnsecuredController;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Dev\FunctionalTest;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\View\SSViewer;
|
||||
@ -526,22 +521,12 @@ class ControllerTest extends FunctionalTest
|
||||
|
||||
public function testSubActions()
|
||||
{
|
||||
/* If a controller action returns another controller, ensure that the $action variable is correctly forwarded */
|
||||
// If a controller action returns another controller, ensure that the $action variable is correctly forwarded
|
||||
$response = $this->get("ContainerController/subcontroller/subaction");
|
||||
$this->assertEquals('subaction', $response->getBody());
|
||||
|
||||
$request = new HTTPRequest(
|
||||
'GET',
|
||||
'ContainerController/subcontroller/substring/subvieweraction'
|
||||
);
|
||||
/* Shift to emulate the director selecting the controller */
|
||||
$request->shift();
|
||||
/* Handle the request to create conditions where improperly passing the action to the viewer might fail */
|
||||
$controller = new ControllerTest\ContainerController();
|
||||
try {
|
||||
$controller->handleRequest($request, DataModel::inst());
|
||||
} catch (ControllerTest\SubController_Exception $e) {
|
||||
$this->fail($e->getMessage());
|
||||
}
|
||||
// Handle nested action
|
||||
$response = $this->get('ContainerController/subcontroller/substring/subvieweraction');
|
||||
$this->assertEquals('Hope this works', $response->getBody());
|
||||
}
|
||||
}
|
||||
|
@ -34,10 +34,6 @@ class SubController extends Controller implements TestOnly
|
||||
|
||||
public function subvieweraction()
|
||||
{
|
||||
return $this->customise(
|
||||
array(
|
||||
'Thoughts' => 'Hope this works',
|
||||
)
|
||||
);
|
||||
return 'Hope this works';
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,14 @@ use SilverStripe\Dev\TestOnly;
|
||||
*/
|
||||
class TestController extends Controller implements TestOnly
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
if (Controller::has_curr()) {
|
||||
$this->setRequest(Controller::curr()->getRequest());
|
||||
}
|
||||
}
|
||||
|
||||
private static $url_segment = 'TestController';
|
||||
|
||||
public $Content = "default content";
|
||||
|
@ -3,31 +3,22 @@
|
||||
namespace SilverStripe\Control\Tests;
|
||||
|
||||
use SilverStripe\Control\Cookie_Backend;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPRequestBuilder;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\HTTPResponse_Exception;
|
||||
use SilverStripe\Control\Tests\DirectorTest\TestController;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\RequestProcessor;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Control\Tests\DirectorTest\TestController;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Kernel;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
/**
|
||||
* @todo test Director::alternateBaseFolder()
|
||||
*/
|
||||
class DirectorTest extends SapphireTest
|
||||
{
|
||||
|
||||
protected static $originalRequestURI;
|
||||
|
||||
protected $originalProtocolHeaders = array();
|
||||
|
||||
protected $originalGet = array();
|
||||
|
||||
protected $originalSession = array();
|
||||
|
||||
protected static $extra_controllers = [
|
||||
TestController::class,
|
||||
];
|
||||
@ -35,28 +26,8 @@ class DirectorTest extends SapphireTest
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Hold the original request URI once so it doesn't get overwritten
|
||||
if (!self::$originalRequestURI) {
|
||||
self::$originalRequestURI = $_SERVER['REQUEST_URI'];
|
||||
}
|
||||
$_SERVER['REQUEST_URI'] = 'http://www.mysite.com';
|
||||
|
||||
$this->originalGet = $_GET;
|
||||
$this->originalSession = $_SESSION;
|
||||
$_SESSION = array();
|
||||
|
||||
$headers = array(
|
||||
'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'
|
||||
);
|
||||
|
||||
foreach ($headers as $header) {
|
||||
if (isset($_SERVER[$header])) {
|
||||
$this->originalProtocolHeaders[$header] = $_SERVER[$header];
|
||||
}
|
||||
}
|
||||
|
||||
Config::modify()->set(Director::class, 'alternate_base_url', '/');
|
||||
Director::config()->set('alternate_base_url', 'http://www.mysite.com/');
|
||||
$this->expectedRedirect = null;
|
||||
}
|
||||
|
||||
protected function getExtraRoutes()
|
||||
@ -77,24 +48,6 @@ class DirectorTest extends SapphireTest
|
||||
Director::config()->set('rules', $this->getExtraRoutes());
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
$_GET = $this->originalGet;
|
||||
$_SESSION = $this->originalSession;
|
||||
|
||||
// Reinstate the original REQUEST_URI after it was modified by some tests
|
||||
$_SERVER['REQUEST_URI'] = self::$originalRequestURI;
|
||||
|
||||
if ($this->originalProtocolHeaders) {
|
||||
foreach ($this->originalProtocolHeaders as $header => $value) {
|
||||
$_SERVER[$header] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testFileExists()
|
||||
{
|
||||
$tempFileName = 'DirectorTest_testFileExists.tmp';
|
||||
@ -118,55 +71,53 @@ class DirectorTest extends SapphireTest
|
||||
|
||||
public function testAbsoluteURL()
|
||||
{
|
||||
|
||||
$rootURL = Director::protocolAndHost();
|
||||
$_SERVER['REQUEST_URI'] = "$rootURL/mysite/sub-page/";
|
||||
Director::config()->set('alternate_base_url', '/mysite/');
|
||||
Director::config()->set('alternate_base_url', 'http://www.mysite.com/mysite/');
|
||||
$_SERVER['REQUEST_URI'] = "http://www.mysite.com/mysite/sub-page/";
|
||||
|
||||
//test empty / local urls
|
||||
foreach (array('', './', '.') as $url) {
|
||||
$this->assertEquals("$rootURL/mysite/", Director::absoluteURL($url, Director::BASE));
|
||||
$this->assertEquals("$rootURL/", Director::absoluteURL($url, Director::ROOT));
|
||||
$this->assertEquals("$rootURL/mysite/sub-page/", Director::absoluteURL($url, Director::REQUEST));
|
||||
$this->assertEquals("http://www.mysite.com/mysite/", Director::absoluteURL($url, Director::BASE));
|
||||
$this->assertEquals("http://www.mysite.com/", Director::absoluteURL($url, Director::ROOT));
|
||||
$this->assertEquals("http://www.mysite.com/mysite/sub-page/", Director::absoluteURL($url, Director::REQUEST));
|
||||
}
|
||||
|
||||
// Test site root url
|
||||
$this->assertEquals("$rootURL/", Director::absoluteURL('/'));
|
||||
$this->assertEquals("http://www.mysite.com/", Director::absoluteURL('/'));
|
||||
|
||||
// Test Director::BASE
|
||||
$this->assertEquals($rootURL, Director::absoluteURL($rootURL, Director::BASE));
|
||||
$this->assertEquals('http://www.mysite.com/', Director::absoluteURL('http://www.mysite.com/', Director::BASE));
|
||||
$this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::BASE));
|
||||
$this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test", Director::BASE));
|
||||
$this->assertEquals("$rootURL/root", Director::absoluteURL("/root", Director::BASE));
|
||||
$this->assertEquals("$rootURL/root/url", Director::absoluteURL("/root/url", Director::BASE));
|
||||
$this->assertEquals("http://www.mysite.com/test", Director::absoluteURL("http://www.mysite.com/test", Director::BASE));
|
||||
$this->assertEquals("http://www.mysite.com/root", Director::absoluteURL("/root", Director::BASE));
|
||||
$this->assertEquals("http://www.mysite.com/root/url", Director::absoluteURL("/root/url", Director::BASE));
|
||||
|
||||
// Test Director::ROOT
|
||||
$this->assertEquals($rootURL, Director::absoluteURL($rootURL, Director::ROOT));
|
||||
$this->assertEquals('http://www.mysite.com/', Director::absoluteURL('http://www.mysite.com/', Director::ROOT));
|
||||
$this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::ROOT));
|
||||
$this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test", Director::ROOT));
|
||||
$this->assertEquals("$rootURL/root", Director::absoluteURL("/root", Director::ROOT));
|
||||
$this->assertEquals("$rootURL/root/url", Director::absoluteURL("/root/url", Director::ROOT));
|
||||
$this->assertEquals("http://www.mysite.com/test", Director::absoluteURL("http://www.mysite.com/test", Director::ROOT));
|
||||
$this->assertEquals("http://www.mysite.com/root", Director::absoluteURL("/root", Director::ROOT));
|
||||
$this->assertEquals("http://www.mysite.com/root/url", Director::absoluteURL("/root/url", Director::ROOT));
|
||||
|
||||
// Test Director::REQUEST
|
||||
$this->assertEquals($rootURL, Director::absoluteURL($rootURL, Director::REQUEST));
|
||||
$this->assertEquals('http://www.mysite.com/', Director::absoluteURL('http://www.mysite.com/', Director::REQUEST));
|
||||
$this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::REQUEST));
|
||||
$this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test", Director::REQUEST));
|
||||
$this->assertEquals("$rootURL/root", Director::absoluteURL("/root", Director::REQUEST));
|
||||
$this->assertEquals("$rootURL/root/url", Director::absoluteURL("/root/url", Director::REQUEST));
|
||||
$this->assertEquals("http://www.mysite.com/test", Director::absoluteURL("http://www.mysite.com/test", Director::REQUEST));
|
||||
$this->assertEquals("http://www.mysite.com/root", Director::absoluteURL("/root", Director::REQUEST));
|
||||
$this->assertEquals("http://www.mysite.com/root/url", Director::absoluteURL("/root/url", Director::REQUEST));
|
||||
|
||||
// Test evaluating relative urls relative to base (default)
|
||||
$this->assertEquals("$rootURL/mysite/test", Director::absoluteURL("test"));
|
||||
$this->assertEquals("$rootURL/mysite/test/url", Director::absoluteURL("test/url"));
|
||||
$this->assertEquals("$rootURL/mysite/test", Director::absoluteURL("test", Director::BASE));
|
||||
$this->assertEquals("$rootURL/mysite/test/url", Director::absoluteURL("test/url", Director::BASE));
|
||||
$this->assertEquals("http://www.mysite.com/mysite/test", Director::absoluteURL("test"));
|
||||
$this->assertEquals("http://www.mysite.com/mysite/test/url", Director::absoluteURL("test/url"));
|
||||
$this->assertEquals("http://www.mysite.com/mysite/test", Director::absoluteURL("test", Director::BASE));
|
||||
$this->assertEquals("http://www.mysite.com/mysite/test/url", Director::absoluteURL("test/url", Director::BASE));
|
||||
|
||||
// Test evaluting relative urls relative to root
|
||||
$this->assertEquals("$rootURL/test", Director::absoluteURL("test", Director::ROOT));
|
||||
$this->assertEquals("$rootURL/test/url", Director::absoluteURL("test/url", Director::ROOT));
|
||||
$this->assertEquals("http://www.mysite.com/test", Director::absoluteURL("test", Director::ROOT));
|
||||
$this->assertEquals("http://www.mysite.com/test/url", Director::absoluteURL("test/url", Director::ROOT));
|
||||
|
||||
// Test relative to requested page
|
||||
$this->assertEquals("$rootURL/mysite/sub-page/test", Director::absoluteURL("test", Director::REQUEST));
|
||||
$this->assertEquals("$rootURL/mysite/sub-page/test/url", Director::absoluteURL("test/url", Director::REQUEST));
|
||||
$this->assertEquals("http://www.mysite.com/mysite/sub-page/test", Director::absoluteURL("test", Director::REQUEST));
|
||||
$this->assertEquals("http://www.mysite.com/mysite/sub-page/test/url", Director::absoluteURL("test/url", Director::REQUEST));
|
||||
|
||||
// Test that javascript links are not left intact
|
||||
$this->assertStringStartsNotWith('javascript', Director::absoluteURL('javascript:alert("attack")'));
|
||||
@ -177,17 +128,15 @@ class DirectorTest extends SapphireTest
|
||||
|
||||
public function testAlternativeBaseURL()
|
||||
{
|
||||
// Get original protocol and hostname
|
||||
$rootURL = Director::protocolAndHost();
|
||||
|
||||
// relative base URLs - you should end them in a /
|
||||
Director::config()->set('alternate_base_url', '/relativebase/');
|
||||
$_SERVER['REQUEST_URI'] = "$rootURL/relativebase/sub-page/";
|
||||
$_SERVER['HTTP_HOST'] = 'www.somesite.com';
|
||||
$_SERVER['REQUEST_URI'] = "/relativebase/sub-page/";
|
||||
|
||||
$this->assertEquals('/relativebase/', Director::baseURL());
|
||||
$this->assertEquals($rootURL . '/relativebase/', Director::absoluteBaseURL());
|
||||
$this->assertEquals('http://www.somesite.com/relativebase/', Director::absoluteBaseURL());
|
||||
$this->assertEquals(
|
||||
$rootURL . '/relativebase/subfolder/test',
|
||||
'http://www.somesite.com/relativebase/subfolder/test',
|
||||
Director::absoluteURL('subfolder/test')
|
||||
);
|
||||
|
||||
@ -336,6 +285,10 @@ class DirectorTest extends SapphireTest
|
||||
unset($_GET['isTest']);
|
||||
unset($_GET['isDev']);
|
||||
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
$kernel->setEnvironment(null);
|
||||
|
||||
// Test isDev=1
|
||||
$_GET['isDev'] = '1';
|
||||
$this->assertTrue(Director::isDev());
|
||||
@ -399,14 +352,28 @@ class DirectorTest extends SapphireTest
|
||||
);
|
||||
}
|
||||
|
||||
public function testTestRequestCarriesGlobals()
|
||||
public function providerTestTestRequestCarriesGlobals()
|
||||
{
|
||||
$fixture = array('somekey' => 'sometestvalue');
|
||||
$tests = [];
|
||||
$fixture = [ 'somekey' => 'sometestvalue' ];
|
||||
foreach (array('get', 'post') as $method) {
|
||||
foreach (array('return%sValue', 'returnRequestValue', 'returnCookieValue') as $testfunction) {
|
||||
$url = 'TestController/' . sprintf($testfunction, ucfirst($method))
|
||||
. '?' . http_build_query($fixture);
|
||||
$tests[] = [$url, $fixture, $method];
|
||||
}
|
||||
}
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerTestTestRequestCarriesGlobals
|
||||
* @param $url
|
||||
* @param $fixture
|
||||
* @param $method
|
||||
*/
|
||||
public function testTestRequestCarriesGlobals($url, $fixture, $method)
|
||||
{
|
||||
$getresponse = Director::test(
|
||||
$url,
|
||||
$fixture,
|
||||
@ -418,9 +385,7 @@ class DirectorTest extends SapphireTest
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(HTTPResponse::class, $getresponse, 'Director::test() returns HTTPResponse');
|
||||
$this->assertEquals($fixture['somekey'], $getresponse->getBody(), 'Director::test() ' . $testfunction);
|
||||
}
|
||||
}
|
||||
$this->assertEquals($fixture['somekey'], $getresponse->getBody(), "Director::test({$url}, {$method})");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -446,46 +411,87 @@ class DirectorTest extends SapphireTest
|
||||
|
||||
public function testForceSSLProtectsEntireSite()
|
||||
{
|
||||
$_SERVER['REQUEST_URI'] = '/admin';
|
||||
$output = Director::forceSSL();
|
||||
$this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
|
||||
|
||||
$_SERVER['REQUEST_URI'] = Director::baseURL() . 'some-url';
|
||||
$output = Director::forceSSL();
|
||||
$this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
|
||||
$this->expectExceptionRedirect('https://www.mysite.com/some-url');
|
||||
Director::mockRequest(function () {
|
||||
Director::forceSSL();
|
||||
}, '/some-url');
|
||||
}
|
||||
|
||||
public function testForceSSLOnTopLevelPagePattern()
|
||||
{
|
||||
$_SERVER['REQUEST_URI'] = Director::baseURL() . 'admin';
|
||||
$output = Director::forceSSL(array('/^admin/'));
|
||||
$this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
|
||||
// Expect admin to trigger redirect
|
||||
$this->expectExceptionRedirect('https://www.mysite.com/admin');
|
||||
Director::mockRequest(function () {
|
||||
Director::forceSSL(array('/^admin/'));
|
||||
}, '/admin');
|
||||
}
|
||||
|
||||
public function testForceSSLOnSubPagesPattern()
|
||||
{
|
||||
$_SERVER['REQUEST_URI'] = Director::baseURL() . Config::inst()->get(Security::class, 'login_url');
|
||||
$output = Director::forceSSL(array('/^Security/'));
|
||||
$this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
|
||||
// Expect to redirect to security login page
|
||||
$this->expectExceptionRedirect('https://www.mysite.com/Security/login');
|
||||
Director::mockRequest(function () {
|
||||
Director::forceSSL(array('/^Security/'));
|
||||
}, '/Security/login');
|
||||
}
|
||||
|
||||
public function testForceSSLWithPatternDoesNotMatchOtherPages()
|
||||
{
|
||||
$_SERVER['REQUEST_URI'] = Director::baseURL() . 'normal-page';
|
||||
$output = Director::forceSSL(array('/^admin/'));
|
||||
$this->assertFalse($output);
|
||||
// Not on same url should not trigger redirect
|
||||
Director::mockRequest(function () {
|
||||
$this->assertFalse(Director::forceSSL(array('/^admin/')));
|
||||
}, Director::baseURL() . 'normal-page');
|
||||
|
||||
$_SERVER['REQUEST_URI'] = Director::baseURL() . 'just-another-page/sub-url';
|
||||
$output = Director::forceSSL(array('/^admin/', '/^Security/'));
|
||||
$this->assertFalse($output);
|
||||
// nested url should not triger redirect either
|
||||
Director::mockRequest(function () {
|
||||
$this->assertFalse(Director::forceSSL(array('/^admin/', '/^Security/')));
|
||||
}, Director::baseURL() . 'just-another-page/sub-url');
|
||||
}
|
||||
|
||||
public function testForceSSLAlternateDomain()
|
||||
{
|
||||
Director::config()->set('alternate_base_url', '/');
|
||||
$_SERVER['REQUEST_URI'] = Director::baseURL() . 'admin';
|
||||
$output = Director::forceSSL(array('/^admin/'), 'secure.mysite.com');
|
||||
$this->assertEquals($output, 'https://secure.mysite.com/admin');
|
||||
// Ensure that forceSSL throws the appropriate exception
|
||||
$this->expectExceptionRedirect('https://secure.mysite.com/admin');
|
||||
Director::mockRequest(function (HTTPRequest $request) {
|
||||
return Director::forceSSL(array('/^admin/'), 'secure.mysite.com');
|
||||
}, Director::baseURL() . 'admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set url to redirect to
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $expectedRedirect = null;
|
||||
|
||||
/**
|
||||
* Expects this test to throw a HTTPResponse_Exception with the given redirect
|
||||
*
|
||||
* @param string $url
|
||||
*/
|
||||
protected function expectExceptionRedirect($url)
|
||||
{
|
||||
$this->expectedRedirect = $url;
|
||||
}
|
||||
|
||||
protected function runTest()
|
||||
{
|
||||
try {
|
||||
$result = parent::runTest();
|
||||
if ($this->expectedRedirect) {
|
||||
$this->fail("Expected to redirect to {$this->expectedRedirect} but no redirect found");
|
||||
}
|
||||
return $result;
|
||||
} catch (HTTPResponse_Exception $exception) {
|
||||
// Check URL
|
||||
if ($this->expectedRedirect) {
|
||||
$url = $exception->getResponse()->getHeader('Location');
|
||||
$this->assertEquals($this->expectedRedirect, $url, "Expected to redirect to {$this->expectedRedirect}");
|
||||
return null;
|
||||
} else {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -518,7 +524,7 @@ class DirectorTest extends SapphireTest
|
||||
'Content-Length' => '10'
|
||||
);
|
||||
|
||||
$this->assertEquals($headers, Director::extract_request_headers($request));
|
||||
$this->assertEquals($headers, HTTPRequestBuilder::extractRequestHeaders($request));
|
||||
}
|
||||
|
||||
public function testUnmatchedRequestReturns404()
|
||||
@ -614,28 +620,30 @@ class DirectorTest extends SapphireTest
|
||||
$processor = new RequestProcessor(array($filter));
|
||||
|
||||
Injector::inst()->registerService($processor, RequestProcessor::class);
|
||||
|
||||
Director::test('some-dummy-url');
|
||||
$response = Director::test('some-dummy-url');
|
||||
$this->assertEquals(404, $response->getStatusCode());
|
||||
|
||||
$this->assertEquals(1, $filter->preCalls);
|
||||
$this->assertEquals(1, $filter->postCalls);
|
||||
|
||||
$filter->failPost = true;
|
||||
|
||||
$this->setExpectedException(HTTPResponse_Exception::class);
|
||||
|
||||
Director::test('some-dummy-url');
|
||||
$response = Director::test('some-dummy-url');
|
||||
$this->assertEquals(500, $response->getStatusCode());
|
||||
$this->assertEquals(_t(Director::class.'.REQUEST_ABORTED', 'Request aborted'), $response->getBody());
|
||||
|
||||
$this->assertEquals(2, $filter->preCalls);
|
||||
$this->assertEquals(2, $filter->postCalls);
|
||||
|
||||
$filter->failPre = true;
|
||||
|
||||
Director::test('some-dummy-url');
|
||||
$response = Director::test('some-dummy-url');
|
||||
$this->assertEquals(400, $response->getStatusCode());
|
||||
$this->assertEquals(_t(Director::class.'.INVALID_REQUEST', 'Invalid request'), $response->getBody());
|
||||
|
||||
$this->assertEquals(3, $filter->preCalls);
|
||||
|
||||
// preCall 'false' will trigger an exception and prevent post call execution
|
||||
// preCall 'true' will trigger an exception and prevent post call execution
|
||||
$this->assertEquals(2, $filter->postCalls);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,14 @@ use SilverStripe\Dev\TestOnly;
|
||||
|
||||
class TestController extends Controller implements TestOnly
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
if (Controller::has_curr()) {
|
||||
$this->setRequest(Controller::curr()->getRequest());
|
||||
}
|
||||
}
|
||||
|
||||
private static $url_segment = 'TestController';
|
||||
|
||||
private static $allowed_actions = array(
|
||||
@ -18,21 +26,33 @@ class TestController extends Controller implements TestOnly
|
||||
|
||||
public function returnGetValue($request)
|
||||
{
|
||||
if (isset($_GET['somekey'])) {
|
||||
return $_GET['somekey'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function returnPostValue($request)
|
||||
{
|
||||
if (isset($_POST['somekey'])) {
|
||||
return $_POST['somekey'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function returnRequestValue($request)
|
||||
{
|
||||
if (isset($_REQUEST['somekey'])) {
|
||||
return $_REQUEST['somekey'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function returnCookieValue($request)
|
||||
{
|
||||
if (isset($_COOKIE['somekey'])) {
|
||||
return $_COOKIE['somekey'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,7 @@ namespace SilverStripe\Control\Tests\DirectorTest;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\RequestFilter;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
|
||||
class TestRequestFilter implements RequestFilter, TestOnly
|
||||
{
|
||||
@ -17,22 +15,24 @@ class TestRequestFilter implements RequestFilter, TestOnly
|
||||
public $failPre = false;
|
||||
public $failPost = false;
|
||||
|
||||
public function preRequest(HTTPRequest $request, Session $session, DataModel $model)
|
||||
public function preRequest(HTTPRequest $request)
|
||||
{
|
||||
++$this->preCalls;
|
||||
|
||||
if ($this->failPre) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function postRequest(HTTPRequest $request, HTTPResponse $response, DataModel $model)
|
||||
public function postRequest(HTTPRequest $request, HTTPResponse $response)
|
||||
{
|
||||
++$this->postCalls;
|
||||
|
||||
if ($this->failPost) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Control\Tests;
|
||||
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\Controller;
|
||||
|
||||
// Fake a current controller. Way harder than it should be
|
||||
class FakeController extends Controller
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$session = Injector::inst()->create(Session::class, isset($_SESSION) ? $_SESSION : array());
|
||||
$this->setSession($session);
|
||||
|
||||
$this->pushCurrent();
|
||||
|
||||
$request = new HTTPRequest('GET', '/');
|
||||
$this->setRequest($request);
|
||||
|
||||
$this->setResponse(new HTTPResponse());
|
||||
|
||||
$this->doInit();
|
||||
}
|
||||
}
|
@ -13,9 +13,7 @@ class FlushRequestFilterTest extends FunctionalTest
|
||||
public function testImplementorsAreCalled()
|
||||
{
|
||||
TestFlushable::$flushed = false;
|
||||
|
||||
$this->get('?flush=1');
|
||||
|
||||
$this->assertTrue(TestFlushable::$flushed);
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ use SilverStripe\Dev\TestOnly;
|
||||
|
||||
class TestFlushable implements Flushable, TestOnly
|
||||
{
|
||||
|
||||
public static $flushed = false;
|
||||
|
||||
public static function flush()
|
||||
|
@ -2,10 +2,14 @@
|
||||
|
||||
namespace SilverStripe\Control\Tests;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Dev\FunctionalTest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\HTTP;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Kernel;
|
||||
use SilverStripe\Dev\FunctionalTest;
|
||||
|
||||
/**
|
||||
* Tests the {@link HTTP} class
|
||||
@ -27,13 +31,15 @@ class HTTPTest extends FunctionalTest
|
||||
$this->assertNotEmpty($response->getHeader('Cache-Control'));
|
||||
|
||||
// Ensure max-age is zero for development.
|
||||
Director::set_environment_type('dev');
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
$kernel->setEnvironment(Kernel::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::set_environment_type('live');
|
||||
$kernel->setEnvironment(Kernel::LIVE);
|
||||
$response = new HTTPResponse($body, 200);
|
||||
HTTP::add_cache_headers($response);
|
||||
$this->assertContains('max-age=30', explode(', ', $response->getHeader('Cache-Control')));
|
||||
@ -57,9 +63,11 @@ class HTTPTest extends FunctionalTest
|
||||
|
||||
public function testConfigVary()
|
||||
{
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
$body = "<html><head></head><body><h1>Mysite</h1></body></html>";
|
||||
$response = new HTTPResponse($body, 200);
|
||||
Director::set_environment_type('live');
|
||||
$kernel->setEnvironment(Kernel::LIVE);
|
||||
HTTP::set_cache_age(30);
|
||||
HTTP::add_cache_headers($response);
|
||||
|
||||
@ -124,14 +132,20 @@ class HTTPTest extends FunctionalTest
|
||||
{
|
||||
// Hackery to work around volatile URL formats in test invocation,
|
||||
// and the inability of Director::absoluteBaseURL() to produce consistent URLs.
|
||||
$origURI = $_SERVER['REQUEST_URI'];
|
||||
$_SERVER['REQUEST_URI'] = 'relative/url/';
|
||||
Director::mockRequest(function (HTTPRequest $request) {
|
||||
$controller = new Controller();
|
||||
$controller->setRequest($request);
|
||||
$controller->pushCurrent();
|
||||
try {
|
||||
$this->assertContains(
|
||||
'relative/url/?foo=bar',
|
||||
'relative/url?foo=bar',
|
||||
HTTP::setGetVar('foo', 'bar'),
|
||||
'Omitting a URL falls back to current URL'
|
||||
);
|
||||
$_SERVER['REQUEST_URI'] = $origURI;
|
||||
} finally {
|
||||
$controller->popCurrent();
|
||||
}
|
||||
}, 'relative/url/');
|
||||
|
||||
$this->assertEquals(
|
||||
'relative/url?foo=bar',
|
||||
@ -206,30 +220,30 @@ class HTTPTest extends FunctionalTest
|
||||
{
|
||||
$this->withBaseURL(
|
||||
'http://www.silverstripe.org/',
|
||||
function ($test) {
|
||||
function () {
|
||||
|
||||
// background-image
|
||||
// Note that using /./ in urls is absolutely acceptable
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<div style="background-image: url(\'http://www.silverstripe.org/./images/mybackground.gif\');">'.
|
||||
'Content</div>',
|
||||
HTTP::absoluteURLs('<div style="background-image: url(\'./images/mybackground.gif\');">Content</div>')
|
||||
);
|
||||
|
||||
// background
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<div style="background: url(\'http://www.silverstripe.org/images/mybackground.gif\');">Content</div>',
|
||||
HTTP::absoluteURLs('<div style="background: url(\'images/mybackground.gif\');">Content</div>')
|
||||
);
|
||||
|
||||
// list-style-image
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<div style=\'background: url(http://www.silverstripe.org/list.png);\'>Content</div>',
|
||||
HTTP::absoluteURLs('<div style=\'background: url(list.png);\'>Content</div>')
|
||||
);
|
||||
|
||||
// list-style
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<div style=\'background: url("http://www.silverstripe.org/./assets/list.png");\'>Content</div>',
|
||||
HTTP::absoluteURLs('<div style=\'background: url("./assets/list.png");\'>Content</div>')
|
||||
);
|
||||
@ -244,37 +258,37 @@ class HTTPTest extends FunctionalTest
|
||||
{
|
||||
$this->withBaseURL(
|
||||
'http://www.silverstripe.org/',
|
||||
function ($test) {
|
||||
function () {
|
||||
//empty links
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<a href="http://www.silverstripe.org/">test</a>',
|
||||
HTTP::absoluteURLs('<a href="">test</a>')
|
||||
);
|
||||
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<a href="http://www.silverstripe.org/">test</a>',
|
||||
HTTP::absoluteURLs('<a href="/">test</a>')
|
||||
);
|
||||
|
||||
//relative
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<a href="http://www.silverstripe.org/">test</a>',
|
||||
HTTP::absoluteURLs('<a href="./">test</a>')
|
||||
);
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<a href="http://www.silverstripe.org/">test</a>',
|
||||
HTTP::absoluteURLs('<a href=".">test</a>')
|
||||
);
|
||||
|
||||
// links
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<a href=\'http://www.silverstripe.org/blog/\'>SS Blog</a>',
|
||||
HTTP::absoluteURLs('<a href=\'/blog/\'>SS Blog</a>')
|
||||
);
|
||||
|
||||
// background
|
||||
// Note that using /./ in urls is absolutely acceptable
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<div background="http://www.silverstripe.org/./themes/silverstripe/images/nav-bg-repeat-2.png">'.
|
||||
'SS Blog</div>',
|
||||
HTTP::absoluteURLs('<div background="./themes/silverstripe/images/nav-bg-repeat-2.png">SS Blog</div>')
|
||||
@ -283,25 +297,25 @@ class HTTPTest extends FunctionalTest
|
||||
//check dot segments
|
||||
// Assumption: dots are not removed
|
||||
//if they were, the url should be: http://www.silverstripe.org/abc
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<a href="http://www.silverstripe.org/test/page/../../abc">Test</a>',
|
||||
HTTP::absoluteURLs('<a href="test/page/../../abc">Test</a>')
|
||||
);
|
||||
|
||||
// image
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<img src=\'http://www.silverstripe.org/themes/silverstripe/images/logo-org.png\' />',
|
||||
HTTP::absoluteURLs('<img src=\'themes/silverstripe/images/logo-org.png\' />')
|
||||
);
|
||||
|
||||
// link
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<link href=http://www.silverstripe.org/base.css />',
|
||||
HTTP::absoluteURLs('<link href=base.css />')
|
||||
);
|
||||
|
||||
// Test special characters are retained
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<a href="http://www.silverstripe.org/Security/changepassword?m=3&t=7214fdfde">password reset link</a>',
|
||||
HTTP::absoluteURLs('<a href="/Security/changepassword?m=3&t=7214fdfde">password reset link</a>')
|
||||
);
|
||||
@ -319,14 +333,14 @@ class HTTPTest extends FunctionalTest
|
||||
function ($test) {
|
||||
|
||||
// mailto
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<a href=\'mailto:admin@silverstripe.org\'>Email Us</a>',
|
||||
HTTP::absoluteURLs('<a href=\'mailto:admin@silverstripe.org\'>Email Us</a>'),
|
||||
'Email links are not rewritten'
|
||||
);
|
||||
|
||||
// data uri
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38'.
|
||||
'GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />',
|
||||
HTTP::absoluteURLs(
|
||||
@ -337,7 +351,7 @@ class HTTPTest extends FunctionalTest
|
||||
);
|
||||
|
||||
// call
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
'<a href="callto:12345678" />',
|
||||
HTTP::absoluteURLs('<a href="callto:12345678" />'),
|
||||
'Call to links are not rewritten'
|
||||
@ -352,7 +366,7 @@ class HTTPTest extends FunctionalTest
|
||||
'http://www.silverstripe.org/',
|
||||
function ($test) {
|
||||
$frameworkTests = ltrim(FRAMEWORK_DIR . '/tests', '/');
|
||||
$test->assertEquals(
|
||||
$this->assertEquals(
|
||||
"http://www.silverstripe.org/$frameworkTests/php/Control/HTTPTest.php",
|
||||
HTTP::filename2url(__FILE__)
|
||||
);
|
||||
|
@ -2,9 +2,10 @@
|
||||
|
||||
namespace SilverStripe\Control\Tests;
|
||||
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Control\PjaxResponseNegotiator;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\PjaxResponseNegotiator;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
class PjaxResponseNegotiatorTest extends SapphireTest
|
||||
{
|
||||
@ -19,6 +20,7 @@ class PjaxResponseNegotiatorTest extends SapphireTest
|
||||
)
|
||||
);
|
||||
$request = new HTTPRequest('GET', '/'); // not setting pjax header
|
||||
$request->setSession(new Session([]));
|
||||
$response = $negotiator->respond($request);
|
||||
$this->assertEquals('default response', $response->getBody());
|
||||
}
|
||||
@ -36,6 +38,7 @@ class PjaxResponseNegotiatorTest extends SapphireTest
|
||||
)
|
||||
);
|
||||
$request = new HTTPRequest('GET', '/');
|
||||
$request->setSession(new Session([]));
|
||||
$request->addHeader('X-Pjax', 'myfragment');
|
||||
$response = $negotiator->respond($request);
|
||||
$this->assertEquals('{"myfragment":"myfragment response"}', $response->getBody());
|
||||
@ -57,6 +60,7 @@ class PjaxResponseNegotiatorTest extends SapphireTest
|
||||
)
|
||||
);
|
||||
$request = new HTTPRequest('GET', '/');
|
||||
$request->setSession(new Session([]));
|
||||
$request->addHeader('X-Pjax', 'myfragment,otherfragment');
|
||||
$request->addHeader('Accept', 'text/json');
|
||||
$response = $negotiator->respond($request);
|
||||
@ -81,6 +85,7 @@ class PjaxResponseNegotiatorTest extends SapphireTest
|
||||
);
|
||||
|
||||
$request = new HTTPRequest('GET', '/');
|
||||
$request->setSession(new Session([]));
|
||||
$request->addHeader('X-Pjax', 'alpha');
|
||||
$request->addHeader('Accept', 'text/json');
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace SilverStripe\Control\Tests\RequestHandlingTest;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\HTTPResponse_Exception;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
@ -41,25 +42,28 @@ class TestController extends Controller implements TestOnly
|
||||
{
|
||||
$this->failover = new ControllerFailover();
|
||||
parent::__construct();
|
||||
if (Controller::has_curr()) {
|
||||
$this->setRequest(Controller::curr()->getRequest());
|
||||
}
|
||||
}
|
||||
|
||||
public function index($request)
|
||||
public function index(HTTPRequest $request)
|
||||
{
|
||||
return "This is the controller";
|
||||
}
|
||||
|
||||
public function method($request)
|
||||
public function method(HTTPRequest $request)
|
||||
{
|
||||
return "This is a method on the controller: " . $request->param('ID') . ', ' . $request->param('OtherID');
|
||||
}
|
||||
|
||||
public function legacymethod($request)
|
||||
public function legacymethod(HTTPRequest $request)
|
||||
{
|
||||
return "\$this->urlParams can be used, for backward compatibility: " . $this->urlParams['ID'] . ', '
|
||||
. $this->urlParams['OtherID'];
|
||||
}
|
||||
|
||||
public function virtualfile($request)
|
||||
public function virtualfile(HTTPRequest $request)
|
||||
{
|
||||
return "This is the virtualfile method";
|
||||
}
|
||||
|
@ -2,53 +2,61 @@
|
||||
|
||||
namespace SilverStripe\Control\Tests;
|
||||
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
/**
|
||||
* Tests to cover the {@link Session} class
|
||||
*/
|
||||
class SessionTest extends SapphireTest
|
||||
{
|
||||
/**
|
||||
* @var Session
|
||||
*/
|
||||
protected $session = null;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->session = new Session([]);
|
||||
return parent::setUp();
|
||||
}
|
||||
|
||||
public function testGetSetBasics()
|
||||
{
|
||||
Session::set('Test', 'Test');
|
||||
$this->session->set('Test', 'Test');
|
||||
|
||||
$this->assertEquals(Session::get('Test'), 'Test');
|
||||
$this->assertEquals($this->session->get('Test'), 'Test');
|
||||
}
|
||||
|
||||
public function testClearElement()
|
||||
{
|
||||
Session::set('Test', 'Test');
|
||||
Session::clear('Test');
|
||||
$this->session->set('Test', 'Test');
|
||||
$this->session->clear('Test');
|
||||
|
||||
$this->assertEquals(Session::get('Test'), '');
|
||||
$this->assertEquals($this->session->get('Test'), '');
|
||||
}
|
||||
|
||||
public function testClearAllElements()
|
||||
{
|
||||
Session::set('Test', 'Test');
|
||||
Session::set('Test-1', 'Test-1');
|
||||
$this->session->set('Test', 'Test');
|
||||
$this->session->set('Test-1', 'Test-1');
|
||||
|
||||
Session::clear_all();
|
||||
$this->session->clearAll();
|
||||
|
||||
// should session get return null? The array key should probably be
|
||||
// unset from the data array
|
||||
$this->assertEquals(Session::get('Test'), '');
|
||||
$this->assertEquals(Session::get('Test-1'), '');
|
||||
$this->assertEquals($this->session->get('Test'), '');
|
||||
$this->assertEquals($this->session->get('Test-1'), '');
|
||||
}
|
||||
|
||||
public function testGetAllElements()
|
||||
{
|
||||
Session::clear_all(); // Remove all session that might've been set by the test harness
|
||||
$this->session->clearAll(); // Remove all session that might've been set by the test harness
|
||||
|
||||
Session::set('Test', 'Test');
|
||||
Session::set('Test-2', 'Test-2');
|
||||
$this->session->set('Test', 'Test');
|
||||
$this->session->set('Test-2', 'Test-2');
|
||||
|
||||
$session = Session::get_all();
|
||||
$session = $this->session->getAll();
|
||||
unset($session['HTTP_USER_AGENT']);
|
||||
|
||||
$this->assertEquals($session, array('Test' => 'Test', 'Test-2' => 'Test-2'));
|
||||
@ -56,10 +64,10 @@ class SessionTest extends SapphireTest
|
||||
|
||||
public function testSettingExistingDoesntClear()
|
||||
{
|
||||
$s = Injector::inst()->create('SilverStripe\\Control\\Session', array('something' => array('does' => 'exist')));
|
||||
$s = new Session(array('something' => array('does' => 'exist')));
|
||||
|
||||
$s->inst_set('something.does', 'exist');
|
||||
$result = $s->inst_changedData();
|
||||
$s->set('something.does', 'exist');
|
||||
$result = $s->changedData();
|
||||
unset($result['HTTP_USER_AGENT']);
|
||||
$this->assertEquals(array(), $result);
|
||||
}
|
||||
@ -69,16 +77,16 @@ class SessionTest extends SapphireTest
|
||||
*/
|
||||
public function testClearElementThatDoesntExist()
|
||||
{
|
||||
$s = Injector::inst()->create('SilverStripe\\Control\\Session', array('something' => array('does' => 'exist')));
|
||||
$s = new Session(array('something' => array('does' => 'exist')));
|
||||
|
||||
$s->inst_clear('something.doesnt.exist');
|
||||
$result = $s->inst_changedData();
|
||||
$s->clear('something.doesnt.exist');
|
||||
$result = $s->changedData();
|
||||
unset($result['HTTP_USER_AGENT']);
|
||||
$this->assertEquals(array(), $result);
|
||||
|
||||
$s->inst_set('something-else', 'val');
|
||||
$s->inst_clear('something-new');
|
||||
$result = $s->inst_changedData();
|
||||
$s->set('something-else', 'val');
|
||||
$s->clear('something-new');
|
||||
$result = $s->changedData();
|
||||
unset($result['HTTP_USER_AGENT']);
|
||||
$this->assertEquals(array('something-else' => 'val'), $result);
|
||||
}
|
||||
@ -88,37 +96,31 @@ class SessionTest extends SapphireTest
|
||||
*/
|
||||
public function testClearElementThatDoesExist()
|
||||
{
|
||||
$s = Injector::inst()->create('SilverStripe\\Control\\Session', array('something' => array('does' => 'exist')));
|
||||
$s = new Session(array('something' => array('does' => 'exist')));
|
||||
|
||||
$s->inst_clear('something.does');
|
||||
$result = $s->inst_changedData();
|
||||
$s->clear('something.does');
|
||||
$result = $s->changedData();
|
||||
unset($result['HTTP_USER_AGENT']);
|
||||
$this->assertEquals(array('something' => array('does' => null)), $result);
|
||||
}
|
||||
|
||||
public function testNonStandardPath()
|
||||
{
|
||||
Config::inst()->update('SilverStripe\\Control\\Session', 'store_path', (realpath(dirname($_SERVER['DOCUMENT_ROOT']) . '/../session')));
|
||||
Session::start();
|
||||
|
||||
$this->assertEquals(Config::inst()->get('SilverStripe\\Control\\Session', 'store_path'), '');
|
||||
}
|
||||
|
||||
public function testUserAgentLockout()
|
||||
{
|
||||
// Set a user agent
|
||||
$_SERVER['HTTP_USER_AGENT'] = 'Test Agent';
|
||||
|
||||
// Generate our session
|
||||
$s = Injector::inst()->create('SilverStripe\\Control\\Session', array());
|
||||
$s->inst_set('val', 123);
|
||||
$s->inst_finalize();
|
||||
$s = new Session(array());
|
||||
$s->init();
|
||||
$s->set('val', 123);
|
||||
$s->finalize();
|
||||
|
||||
// Change our UA
|
||||
$_SERVER['HTTP_USER_AGENT'] = 'Fake Agent';
|
||||
|
||||
// Verify the new session reset our values
|
||||
$s2 = Injector::inst()->create('SilverStripe\\Control\\Session', $s);
|
||||
$this->assertNotEquals($s2->inst_get('val'), 123);
|
||||
$s2 = new Session($s);
|
||||
$s2->init();
|
||||
$this->assertNotEquals($s2->get('val'), 123);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace SilverStripe\Core\Tests;
|
||||
|
||||
use SilverStripe\Core\TempFolder;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Control\Director;
|
||||
|
||||
@ -22,31 +23,31 @@ class CoreTest extends SapphireTest
|
||||
|
||||
public function testGetTempPathInProject()
|
||||
{
|
||||
$user = getTempFolderUsername();
|
||||
$user = TempFolder::getTempFolderUsername();
|
||||
|
||||
if (file_exists($this->tempPath)) {
|
||||
$this->assertEquals(getTempFolder(BASE_PATH), $this->tempPath . DIRECTORY_SEPARATOR . $user);
|
||||
$this->assertEquals(TempFolder::getTempFolder(BASE_PATH), $this->tempPath . DIRECTORY_SEPARATOR . $user);
|
||||
} else {
|
||||
$user = getTempFolderUsername();
|
||||
$user = TempFolder::getTempFolderUsername();
|
||||
$base = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'silverstripe-cache-php' .
|
||||
preg_replace('/[^\w-\.+]+/', '-', PHP_VERSION);
|
||||
|
||||
// A typical Windows location for where sites are stored on IIS
|
||||
$this->assertEquals(
|
||||
$base . 'C--inetpub-wwwroot-silverstripe-test-project' . DIRECTORY_SEPARATOR . $user,
|
||||
getTempFolder('C:\\inetpub\\wwwroot\\silverstripe-test-project')
|
||||
TempFolder::getTempFolder('C:\\inetpub\\wwwroot\\silverstripe-test-project')
|
||||
);
|
||||
|
||||
// A typical Mac OS X location for where sites are stored
|
||||
$this->assertEquals(
|
||||
$base . '-Users-joebloggs-Sites-silverstripe-test-project' . DIRECTORY_SEPARATOR . $user,
|
||||
getTempFolder('/Users/joebloggs/Sites/silverstripe-test-project')
|
||||
TempFolder::getTempFolder('/Users/joebloggs/Sites/silverstripe-test-project')
|
||||
);
|
||||
|
||||
// A typical Linux location for where sites are stored
|
||||
$this->assertEquals(
|
||||
$base . '-var-www-silverstripe-test-project' . DIRECTORY_SEPARATOR . $user,
|
||||
getTempFolder('/var/www/silverstripe-test-project')
|
||||
TempFolder::getTempFolder('/var/www/silverstripe-test-project')
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -54,7 +55,7 @@ class CoreTest extends SapphireTest
|
||||
protected function tearDown()
|
||||
{
|
||||
parent::tearDown();
|
||||
$user = getTempFolderUsername();
|
||||
$user = TempFolder::getTempFolderUsername();
|
||||
$base = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'silverstripe-cache-php' .
|
||||
preg_replace('/[^\w-\.+]+/', '-', PHP_VERSION);
|
||||
foreach (array(
|
||||
|
@ -967,7 +967,10 @@ class InjectorTest extends SapphireTest
|
||||
// Test that nested injector values can be overridden
|
||||
Injector::nest();
|
||||
$this->nestingLevel++;
|
||||
Injector::inst()->unregisterAllObjects();
|
||||
Injector::inst()->unregisterObjects([
|
||||
TestStaticInjections::class,
|
||||
MyParentClass::class,
|
||||
]);
|
||||
$newsi = Injector::inst()->get(TestStaticInjections::class);
|
||||
$newsi->backend = new InjectorTest\OriginalRequirementsBackend();
|
||||
Injector::inst()->registerService($newsi, TestStaticInjections::class);
|
||||
@ -990,7 +993,10 @@ class InjectorTest extends SapphireTest
|
||||
$this->assertInstanceOf(MyChildClass::class, Injector::inst()->get(MyChildClass::class));
|
||||
|
||||
// Test reset of cache
|
||||
Injector::inst()->unregisterAllObjects();
|
||||
Injector::inst()->unregisterObjects([
|
||||
TestStaticInjections::class,
|
||||
MyParentClass::class,
|
||||
]);
|
||||
$si = Injector::inst()->get(TestStaticInjections::class);
|
||||
$this->assertInstanceOf(TestStaticInjections::class, $si);
|
||||
$this->assertInstanceOf(NewRequirementsBackend::class, $si->backend);
|
||||
|
@ -6,11 +6,9 @@ use SilverStripe\Dev\TestOnly;
|
||||
|
||||
class TestStaticInjections implements TestOnly
|
||||
{
|
||||
|
||||
public $backend;
|
||||
/**
|
||||
* @config
|
||||
*/
|
||||
|
||||
/** @config */
|
||||
private static $dependencies = array(
|
||||
'backend' => '%$SilverStripe\\Core\\Tests\\Injector\\InjectorTest\\NewRequirementsBackend'
|
||||
);
|
||||
|
81
tests/php/Core/KernelTest.php
Normal file
81
tests/php/Core/KernelTest.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Tests;
|
||||
|
||||
use BadMethodCallException;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Config\ConfigLoader;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Injector\InjectorLoader;
|
||||
use SilverStripe\Core\Kernel;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
class KernelTest extends SapphireTest
|
||||
{
|
||||
public function testNesting()
|
||||
{
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
|
||||
$nested1 = $kernel->nest();
|
||||
Director::config()->set('alternate_base_url', '/mysite/');
|
||||
$this->assertEquals($nested1->getConfigLoader(), ConfigLoader::inst());
|
||||
$this->assertEquals($nested1->getInjectorLoader(), InjectorLoader::inst());
|
||||
$this->assertEquals(1, ConfigLoader::inst()->countManifests());
|
||||
$this->assertEquals(1, InjectorLoader::inst()->countManifests());
|
||||
|
||||
// Re-nest
|
||||
$nested2 = $nested1->nest();
|
||||
|
||||
// Nesting config / injector should increase this count
|
||||
Injector::nest();
|
||||
Config::nest();
|
||||
$this->assertEquals($nested2->getConfigLoader(), ConfigLoader::inst());
|
||||
$this->assertEquals($nested2->getInjectorLoader(), InjectorLoader::inst());
|
||||
$this->assertEquals(2, ConfigLoader::inst()->countManifests());
|
||||
$this->assertEquals(2, InjectorLoader::inst()->countManifests());
|
||||
Director::config()->set('alternate_base_url', '/anothersite/');
|
||||
|
||||
// Nesting always resets sub-loaders to 1
|
||||
$nested2->nest();
|
||||
$this->assertEquals(1, ConfigLoader::inst()->countManifests());
|
||||
$this->assertEquals(1, InjectorLoader::inst()->countManifests());
|
||||
|
||||
// Calling ->activate() on a previous kernel restores
|
||||
$nested1->activate();
|
||||
$this->assertEquals($nested1->getConfigLoader(), ConfigLoader::inst());
|
||||
$this->assertEquals($nested1->getInjectorLoader(), InjectorLoader::inst());
|
||||
$this->assertEquals('/mysite/', Director::config()->get('alternate_base_url'));
|
||||
$this->assertEquals(1, ConfigLoader::inst()->countManifests());
|
||||
$this->assertEquals(1, InjectorLoader::inst()->countManifests());
|
||||
}
|
||||
|
||||
public function testInvalidInjectorDetection()
|
||||
{
|
||||
$this->expectException(BadMethodCallException::class);
|
||||
$this->expectExceptionMessage(
|
||||
"Non-current injector manifest cannot be accessed. Please call ->activate() first"
|
||||
);
|
||||
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
$kernel->nest(); // $kernel is no longer current kernel
|
||||
|
||||
$kernel->getInjectorLoader()->getManifest();
|
||||
}
|
||||
|
||||
public function testInvalidConfigDetection()
|
||||
{
|
||||
$this->expectException(BadMethodCallException::class);
|
||||
$this->expectExceptionMessage(
|
||||
"Non-current config manifest cannot be accessed. Please call ->activate() first"
|
||||
);
|
||||
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
$kernel->nest(); // $kernel is no longer current kernel
|
||||
|
||||
$kernel->getConfigLoader()->getManifest();
|
||||
}
|
||||
}
|
@ -36,10 +36,12 @@ class ClassLoaderTest extends SapphireTest
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->baseManifest1 = dirname(__FILE__) . '/fixtures/classmanifest';
|
||||
$this->baseManifest2 = dirname(__FILE__) . '/fixtures/classmanifest_other';
|
||||
$this->testManifest1 = new ClassManifest($this->baseManifest1, false);
|
||||
$this->testManifest2 = new ClassManifest($this->baseManifest2, false);
|
||||
$this->baseManifest1 = __DIR__ . '/fixtures/classmanifest';
|
||||
$this->baseManifest2 = __DIR__ . '/fixtures/classmanifest_other';
|
||||
$this->testManifest1 = new ClassManifest($this->baseManifest1);
|
||||
$this->testManifest2 = new ClassManifest($this->baseManifest2);
|
||||
$this->testManifest1->init();
|
||||
$this->testManifest2->init();
|
||||
}
|
||||
|
||||
public function testExclusive()
|
||||
|
@ -32,8 +32,10 @@ class ClassManifestTest extends SapphireTest
|
||||
parent::setUp();
|
||||
|
||||
$this->base = dirname(__FILE__) . '/fixtures/classmanifest';
|
||||
$this->manifest = new ClassManifest($this->base, false);
|
||||
$this->manifestTests = new ClassManifest($this->base, true);
|
||||
$this->manifest = new ClassManifest($this->base);
|
||||
$this->manifest->init(false);
|
||||
$this->manifestTests = new ClassManifest($this->base);
|
||||
$this->manifestTests->init(true);
|
||||
}
|
||||
|
||||
public function testGetItemPath()
|
||||
@ -155,6 +157,7 @@ class ClassManifestTest extends SapphireTest
|
||||
public function testManifestWarnsAboutDuplicateClasses()
|
||||
{
|
||||
$this->expectException(Exception::class);
|
||||
new ClassManifest(dirname(__FILE__) . '/fixtures/classmanifest_duplicates', false);
|
||||
$manifest = new ClassManifest(dirname(__FILE__) . '/fixtures/classmanifest_duplicates');
|
||||
$manifest->init();
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,9 @@ namespace SilverStripe\Core\Tests\Manifest;
|
||||
|
||||
use Dotenv\Loader;
|
||||
use SilverStripe\Config\Collections\MemoryConfigCollection;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Config\CoreConfigFactory;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Kernel;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Core\Manifest\ModuleManifest;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
@ -17,6 +18,7 @@ class ConfigManifestTest extends SapphireTest
|
||||
parent::setUp();
|
||||
|
||||
$moduleManifest = new ModuleManifest(dirname(__FILE__) . '/fixtures/configmanifest');
|
||||
$moduleManifest->init();
|
||||
ModuleLoader::inst()->pushManifest($moduleManifest);
|
||||
}
|
||||
|
||||
@ -45,7 +47,8 @@ class ConfigManifestTest extends SapphireTest
|
||||
public function getTestConfig()
|
||||
{
|
||||
$config = new MemoryConfigCollection();
|
||||
$transformer = CoreConfigFactory::inst()->buildYamlTransformerForPath(dirname(__FILE__) . '/fixtures/configmanifest');
|
||||
$factory = new CoreConfigFactory();
|
||||
$transformer = $factory->buildYamlTransformerForPath(dirname(__FILE__) . '/fixtures/configmanifest');
|
||||
$config->transform([$transformer]);
|
||||
return $config;
|
||||
}
|
||||
@ -173,8 +176,10 @@ class ConfigManifestTest extends SapphireTest
|
||||
|
||||
public function testEnvironmentRules()
|
||||
{
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
foreach (array('dev', 'test', 'live') as $env) {
|
||||
Director::set_environment_type($env);
|
||||
$kernel->setEnvironment($env);
|
||||
$config = $this->getConfigFixtureValue('Environment');
|
||||
|
||||
foreach (array('dev', 'test', 'live') as $check) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace SilverStripe\Core\Tests\Manifest;
|
||||
|
||||
use SilverStripe\Core\Manifest\ModuleManifest;
|
||||
@ -23,7 +22,8 @@ class ModuleManifestTest extends SapphireTest
|
||||
parent::setUp();
|
||||
|
||||
$this->base = dirname(__FILE__) . '/fixtures/classmanifest';
|
||||
$this->manifest = new ModuleManifest($this->base, false);
|
||||
$this->manifest = new ModuleManifest($this->base);
|
||||
$this->manifest->init();
|
||||
}
|
||||
|
||||
public function testGetModules()
|
||||
|
@ -28,7 +28,8 @@ class NamespacedClassManifestTest extends SapphireTest
|
||||
parent::setUp();
|
||||
|
||||
$this->base = dirname(__FILE__) . '/fixtures/namespaced_classmanifest';
|
||||
$this->manifest = new ClassManifest($this->base, false);
|
||||
$this->manifest = new ClassManifest($this->base);
|
||||
$this->manifest->init();
|
||||
ClassLoader::inst()->pushManifest($this->manifest, false);
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,8 @@ class ThemeResourceLoaderTest extends SapphireTest
|
||||
// Fake project root
|
||||
$this->base = dirname(__FILE__) . '/fixtures/templatemanifest';
|
||||
// New ThemeManifest for that root
|
||||
$this->manifest = new ThemeManifest($this->base, 'myproject', false);
|
||||
$this->manifest = new ThemeManifest($this->base, 'myproject');
|
||||
$this->manifest->init();
|
||||
// New Loader for that root
|
||||
$this->loader = new ThemeResourceLoader($this->base);
|
||||
$this->loader->addSet('$default', $this->manifest);
|
||||
|
@ -1,6 +1 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
|
||||
// Dynamically change environment
|
||||
Director::set_environment_type('dev');
|
||||
|
@ -2,117 +2,96 @@
|
||||
|
||||
namespace SilverStripe\Core\Tests;
|
||||
|
||||
use SilverStripe\Core\Environment;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
class MemoryLimitTest extends SapphireTest
|
||||
{
|
||||
|
||||
public function testIncreaseMemoryLimitTo()
|
||||
{
|
||||
if (!$this->canChangeMemory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ini_set('memory_limit', '64M');
|
||||
|
||||
// It can go up
|
||||
increase_memory_limit_to('128M');
|
||||
$this->assertEquals('128M', ini_get('memory_limit'));
|
||||
|
||||
// But not down
|
||||
increase_memory_limit_to('64M');
|
||||
$this->assertEquals('128M', ini_get('memory_limit'));
|
||||
|
||||
// Test the different kinds of syntaxes
|
||||
increase_memory_limit_to(1024*1024*200);
|
||||
$this->assertEquals(1024*1024*200, ini_get('memory_limit'));
|
||||
|
||||
increase_memory_limit_to('409600K');
|
||||
$this->assertEquals('409600K', ini_get('memory_limit'));
|
||||
|
||||
increase_memory_limit_to('1G');
|
||||
|
||||
// If memory limit was left at 409600K, that means that the current testbox doesn't have
|
||||
// 1G of memory available. That's okay; let's not report a failure for that.
|
||||
if (ini_get('memory_limit') != '409600K') {
|
||||
$this->assertEquals('1G', ini_get('memory_limit'));
|
||||
}
|
||||
|
||||
// No argument means unlimited
|
||||
increase_memory_limit_to();
|
||||
$this->assertEquals(-1, ini_get('memory_limit'));
|
||||
}
|
||||
|
||||
public function testIncreaseTimeLimitTo()
|
||||
{
|
||||
if (!$this->canChangeMemory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Can't change time limit
|
||||
if (!set_time_limit(6000)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// It can go up
|
||||
$this->assertTrue(increase_time_limit_to(7000));
|
||||
$this->assertEquals(7000, ini_get('max_execution_time'));
|
||||
|
||||
// But not down
|
||||
$this->assertTrue(increase_time_limit_to(5000));
|
||||
$this->assertEquals(7000, ini_get('max_execution_time'));
|
||||
|
||||
// 0/nothing means infinity
|
||||
$this->assertTrue(increase_time_limit_to());
|
||||
$this->assertEquals(0, ini_get('max_execution_time'));
|
||||
|
||||
// Can't go down from there
|
||||
$this->assertTrue(increase_time_limit_to(10000));
|
||||
$this->assertEquals(0, ini_get('max_execution_time'));
|
||||
}
|
||||
|
||||
|
||||
///////////////////
|
||||
|
||||
private $origMemLimit, $origTimeLimit;
|
||||
protected $origMemLimitMax;
|
||||
protected $origTimeLimitMax;
|
||||
protected $origMemLimit;
|
||||
protected $origTimeLimit;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// see http://www.hardened-php.net/suhosin/configuration.html#suhosin.memory_limit
|
||||
if (in_array('suhosin', get_loaded_extensions())) {
|
||||
$this->markTestSkipped("This test cannot be run with suhosin installed");
|
||||
} else {
|
||||
$this->origMemLimit = ini_get('memory_limit');
|
||||
$this->origTimeLimit = ini_get('max_execution_time');
|
||||
$this->origMemLimitMax = get_increase_memory_limit_max();
|
||||
$this->origTimeLimitMax = get_increase_time_limit_max();
|
||||
set_increase_memory_limit_max(-1);
|
||||
set_increase_time_limit_max(-1);
|
||||
$this->origMemLimitMax = Environment::getMemoryLimitMax();
|
||||
$this->origTimeLimitMax = Environment::getTimeLimitMax();
|
||||
Environment::setMemoryLimitMax(null);
|
||||
Environment::setTimeLimitMax(null);
|
||||
}
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
if (!in_array('suhosin', get_loaded_extensions())) {
|
||||
ini_set('memory_limit', $this->origMemLimit);
|
||||
set_time_limit($this->origTimeLimit);
|
||||
set_increase_memory_limit_max($this->origMemLimitMax);
|
||||
set_increase_time_limit_max($this->origTimeLimitMax);
|
||||
Environment::setMemoryLimitMax($this->origMemLimitMax);
|
||||
Environment::setTimeLimitMax($this->origTimeLimitMax);
|
||||
}
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines wether the environment generally allows
|
||||
* to change the memory limits, which is not always the case.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
protected function canChangeMemory()
|
||||
public function testIncreaseMemoryLimitTo()
|
||||
{
|
||||
$exts = get_loaded_extensions();
|
||||
// see http://www.hardened-php.net/suhosin/configuration.html#suhosin.memory_limit
|
||||
if (in_array('suhosin', $exts)) {
|
||||
return false;
|
||||
ini_set('memory_limit', '64M');
|
||||
Environment::setMemoryLimitMax('256M');
|
||||
|
||||
// It can go up
|
||||
Environment::increaseMemoryLimitTo('128M');
|
||||
$this->assertEquals('128M', ini_get('memory_limit'));
|
||||
|
||||
// But not down
|
||||
Environment::increaseMemoryLimitTo('64M');
|
||||
$this->assertEquals('128M', ini_get('memory_limit'));
|
||||
|
||||
// Test the different kinds of syntaxes
|
||||
Environment::increaseMemoryLimitTo(1024*1024*200);
|
||||
$this->assertEquals('200M', ini_get('memory_limit'));
|
||||
|
||||
Environment::increaseMemoryLimitTo('109600K');
|
||||
$this->assertEquals('200M', ini_get('memory_limit'));
|
||||
|
||||
// Attempting to increase past max size only sets to max
|
||||
Environment::increaseMemoryLimitTo('1G');
|
||||
$this->assertEquals('256M', ini_get('memory_limit'));
|
||||
|
||||
// No argument means unlimited (but only if originally allowed)
|
||||
if (is_numeric($this->origMemLimitMax) && $this->origMemLimitMax < 0) {
|
||||
Environment::increaseMemoryLimitTo();
|
||||
$this->assertEquals(-1, ini_get('memory_limit'));
|
||||
}
|
||||
}
|
||||
|
||||
// We can't change memory limit in safe mode
|
||||
if (ini_get('safe_mode')) {
|
||||
return false;
|
||||
public function testIncreaseTimeLimitTo()
|
||||
{
|
||||
// Can't change time limit
|
||||
if (!set_time_limit(6000)) {
|
||||
$this->markTestSkipped("Cannot change time limit");
|
||||
}
|
||||
|
||||
return true;
|
||||
// It can go up
|
||||
$this->assertTrue(Environment::increaseTimeLimitTo(7000));
|
||||
$this->assertEquals(7000, ini_get('max_execution_time'));
|
||||
|
||||
// But not down
|
||||
$this->assertTrue(Environment::increaseTimeLimitTo(5000));
|
||||
$this->assertEquals(7000, ini_get('max_execution_time'));
|
||||
|
||||
// 0/nothing means infinity
|
||||
$this->assertTrue(Environment::increaseTimeLimitTo());
|
||||
$this->assertEquals(0, ini_get('max_execution_time'));
|
||||
|
||||
// Can't go down from there
|
||||
$this->assertTrue(Environment::increaseTimeLimitTo(10000));
|
||||
$this->assertEquals(0, ini_get('max_execution_time'));
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,13 @@
|
||||
|
||||
namespace SilverStripe\Core\Tests;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Extension;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Manifest\ClassLoader;
|
||||
use SilverStripe\Core\Tests\ObjectTest\BaseObject;
|
||||
use SilverStripe\Core\Tests\ObjectTest\ExtendTest1;
|
||||
use SilverStripe\Core\Tests\ObjectTest\ExtendTest2;
|
||||
use SilverStripe\Core\Tests\ObjectTest\ExtendTest3;
|
||||
@ -16,7 +21,6 @@ use SilverStripe\Core\Tests\ObjectTest\MyObject;
|
||||
use SilverStripe\Core\Tests\ObjectTest\MySubObject;
|
||||
use SilverStripe\Core\Tests\ObjectTest\TestExtension;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
|
||||
/**
|
||||
@ -30,7 +34,10 @@ class ObjectTest extends SapphireTest
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
Injector::inst()->unregisterAllObjects();
|
||||
Injector::inst()->unregisterObjects([
|
||||
Extension::class,
|
||||
BaseObject::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testHasmethodBehaviour()
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
namespace SilverStripe\Core\Tests\Startup;
|
||||
|
||||
use Exception;
|
||||
use Foo;
|
||||
use SilverStripe\Core\Startup\ErrorControlChain;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use Foo;
|
||||
use Exception;
|
||||
|
||||
class ErrorControlChainTest extends SapphireTest
|
||||
{
|
||||
|
@ -2,6 +2,9 @@
|
||||
|
||||
namespace SilverStripe\Core\Tests\Startup;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Core\Startup\ParameterConfirmationToken;
|
||||
use SilverStripe\Core\Tests\Startup\ParameterConfirmationTokenTest\ParameterConfirmationTokenTest_Token;
|
||||
use SilverStripe\Core\Tests\Startup\ParameterConfirmationTokenTest\ParameterConfirmationTokenTest_ValidToken;
|
||||
@ -9,56 +12,35 @@ use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
class ParameterConfirmationTokenTest extends SapphireTest
|
||||
{
|
||||
|
||||
private function addPart($answer, $slash, $part)
|
||||
{
|
||||
$bare = str_replace('/', '', $part);
|
||||
|
||||
if ($bare) {
|
||||
$answer = array_merge($answer, array($bare));
|
||||
}
|
||||
if ($part) {
|
||||
$slash = (substr($part, -1) == '/') ? '/' : '';
|
||||
}
|
||||
|
||||
return array($answer, $slash);
|
||||
}
|
||||
|
||||
protected $oldHost = null;
|
||||
/**
|
||||
* @var HTTPRequest
|
||||
*/
|
||||
protected $request = null;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->oldHost = $_SERVER['HTTP_HOST'];
|
||||
$_GET['parameterconfirmationtokentest_notoken'] = 'value';
|
||||
$_GET['parameterconfirmationtokentest_empty'] = '';
|
||||
$_GET['parameterconfirmationtokentest_withtoken'] = '1';
|
||||
$_GET['parameterconfirmationtokentest_withtokentoken'] = 'dummy';
|
||||
$_GET['parameterconfirmationtokentest_nulltoken'] = '1';
|
||||
$_GET['parameterconfirmationtokentest_nulltokentoken'] = null;
|
||||
$_GET['parameterconfirmationtokentest_emptytoken'] = '1';
|
||||
$_GET['parameterconfirmationtokentest_emptytokentoken'] = '';
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
foreach ($_GET as $param => $value) {
|
||||
if (stripos($param, 'parameterconfirmationtokentest_') === 0) {
|
||||
unset($_GET[$param]);
|
||||
}
|
||||
}
|
||||
$_SERVER['HTTP_HOST'] = $this->oldHost;
|
||||
parent::tearDown();
|
||||
$get = [];
|
||||
$get['parameterconfirmationtokentest_notoken'] = 'value';
|
||||
$get['parameterconfirmationtokentest_empty'] = '';
|
||||
$get['parameterconfirmationtokentest_withtoken'] = '1';
|
||||
$get['parameterconfirmationtokentest_withtokentoken'] = 'dummy';
|
||||
$get['parameterconfirmationtokentest_nulltoken'] = '1';
|
||||
$get['parameterconfirmationtokentest_nulltokentoken'] = null;
|
||||
$get['parameterconfirmationtokentest_emptytoken'] = '1';
|
||||
$get['parameterconfirmationtokentest_emptytokentoken'] = '';
|
||||
$this->request = new HTTPRequest('GET', '/', $get);
|
||||
$this->request->setSession(new Session([]));
|
||||
}
|
||||
|
||||
public function testParameterDetectsParameters()
|
||||
{
|
||||
$withoutToken = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_notoken');
|
||||
$emptyParameter = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_empty');
|
||||
$withToken = new ParameterConfirmationTokenTest_ValidToken('parameterconfirmationtokentest_withtoken');
|
||||
$withoutParameter = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_noparam');
|
||||
$nullToken = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_nulltoken');
|
||||
$emptyToken = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_emptytoken');
|
||||
$withoutToken = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_notoken', $this->request);
|
||||
$emptyParameter = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_empty', $this->request);
|
||||
$withToken = new ParameterConfirmationTokenTest_ValidToken('parameterconfirmationtokentest_withtoken', $this->request);
|
||||
$withoutParameter = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_noparam', $this->request);
|
||||
$nullToken = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_nulltoken', $this->request);
|
||||
$emptyToken = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_emptytoken', $this->request);
|
||||
|
||||
// Check parameter
|
||||
$this->assertTrue($withoutToken->parameterProvided());
|
||||
@ -85,27 +67,27 @@ class ParameterConfirmationTokenTest extends SapphireTest
|
||||
$this->assertTrue($emptyToken->reloadRequired());
|
||||
|
||||
// Check suppression
|
||||
$this->assertTrue(isset($_GET['parameterconfirmationtokentest_notoken']));
|
||||
$this->assertEquals('value', $this->request->getVar('parameterconfirmationtokentest_notoken'));
|
||||
$withoutToken->suppress();
|
||||
$this->assertFalse(isset($_GET['parameterconfirmationtokentest_notoken']));
|
||||
$this->assertNull($this->request->getVar('parameterconfirmationtokentest_notoken'));
|
||||
}
|
||||
|
||||
public function testPrepareTokens()
|
||||
{
|
||||
// Test priority ordering
|
||||
$token = ParameterConfirmationToken::prepare_tokens(
|
||||
array(
|
||||
[
|
||||
'parameterconfirmationtokentest_notoken',
|
||||
'parameterconfirmationtokentest_empty',
|
||||
'parameterconfirmationtokentest_noparam'
|
||||
)
|
||||
],
|
||||
$this->request
|
||||
);
|
||||
// Test no invalid tokens
|
||||
$this->assertEquals('parameterconfirmationtokentest_empty', $token->getName());
|
||||
$token = ParameterConfirmationToken::prepare_tokens(
|
||||
array(
|
||||
'parameterconfirmationtokentest_noparam'
|
||||
)
|
||||
[ 'parameterconfirmationtokentest_noparam' ],
|
||||
$this->request
|
||||
);
|
||||
$this->assertEmpty($token);
|
||||
}
|
||||
@ -118,25 +100,15 @@ class ParameterConfirmationTokenTest extends SapphireTest
|
||||
*/
|
||||
public function testCurrentAbsoluteURLHandlesSlashes()
|
||||
{
|
||||
global $url;
|
||||
$token = new ParameterConfirmationTokenTest_Token(
|
||||
'parameterconfirmationtokentest_parameter',
|
||||
$this->request
|
||||
);
|
||||
|
||||
$token = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_parameter');
|
||||
|
||||
foreach (array('foo','foo/') as $host) {
|
||||
list($hostAnswer, $hostSlash) = $this->addPart(array(), '', $host);
|
||||
|
||||
foreach (array('', '/', 'bar', 'bar/', '/bar', '/bar/') as $base) {
|
||||
list($baseAnswer, $baseSlash) = $this->addPart($hostAnswer, $hostSlash, $base);
|
||||
|
||||
foreach (array('', '/', 'baz', 'baz/', '/baz', '/baz/') as $url) {
|
||||
list($urlAnswer, $urlSlash) = $this->addPart($baseAnswer, $baseSlash, $url);
|
||||
|
||||
$_SERVER['HTTP_HOST'] = $host;
|
||||
ParameterConfirmationToken::$alternateBaseURL = $base;
|
||||
|
||||
$this->assertEquals('http://'.implode('/', $urlAnswer) . $urlSlash, $token->currentAbsoluteURL());
|
||||
}
|
||||
}
|
||||
foreach (array('', '/', 'bar', 'bar/', '/bar', '/bar/') as $url) {
|
||||
$this->request->setUrl($url);
|
||||
$expected = rtrim(Controller::join_links(BASE_URL, '/', $url), '/') ?: '/';
|
||||
$this->assertEquals($expected, $token->currentURL(), "Invalid redirect for request url $url");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ use SilverStripe\Dev\TestOnly;
|
||||
class ParameterConfirmationTokenTest_Token extends ParameterConfirmationToken implements TestOnly
|
||||
{
|
||||
|
||||
public function currentAbsoluteURL()
|
||||
public function currentURL()
|
||||
{
|
||||
return parent::currentAbsoluteURL();
|
||||
return parent::currentURL();
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
namespace SilverStripe\Core\Tests\Startup\ParameterConfirmationTokenTest;
|
||||
|
||||
use SilverStripe\Core\Tests\Startup\ParameterConfirmationTokenTest\ParameterConfirmationTokenTest_Token;
|
||||
|
||||
/**
|
||||
* A token that always validates a given token
|
||||
*/
|
||||
|
@ -169,17 +169,14 @@ class CheckboxSetFieldTest extends SapphireTest
|
||||
|
||||
public function testLoadDataFromObject()
|
||||
{
|
||||
$article = $this->objFromFixture(Article::class, 'articlewithouttags');
|
||||
$articleWithTags = $this->objFromFixture(Article::class, 'articlewithtags');
|
||||
$tag1 = $this->objFromFixture(Tag::class, 'tag1');
|
||||
$tag2 = $this->objFromFixture(Tag::class, 'tag2');
|
||||
|
||||
$field = new CheckboxSetField("Tags", "Test field", DataObject::get(Tag::class)->map());
|
||||
/**
|
||||
* @skipUpgrade
|
||||
*/
|
||||
/** @skipUpgrade */
|
||||
$form = new Form(
|
||||
new Controller(),
|
||||
Controller::curr(),
|
||||
'Form',
|
||||
new FieldList($field),
|
||||
new FieldList()
|
||||
|
@ -2,14 +2,13 @@
|
||||
|
||||
namespace SilverStripe\Forms\Tests;
|
||||
|
||||
use SilverStripe\Control\Tests\ControllerTest\TestController;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Forms\ConfirmedPasswordField;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\Forms\RequiredFields;
|
||||
use SilverStripe\Security\Member;
|
||||
|
||||
class ConfirmedPasswordFieldTest extends SapphireTest
|
||||
{
|
||||
@ -39,10 +38,8 @@ class ConfirmedPasswordFieldTest extends SapphireTest
|
||||
$member->Password = "valueB";
|
||||
$member->write();
|
||||
|
||||
/**
|
||||
* @skipUpgrade
|
||||
*/
|
||||
$form = new Form(new TestController(), 'Form', new FieldList($field), new FieldList());
|
||||
/** @skipUpgrade */
|
||||
$form = new Form(Controller::curr(), 'Form', new FieldList($field), new FieldList());
|
||||
$form->loadDataFrom($member);
|
||||
|
||||
$this->assertEquals('', $field->Value());
|
||||
@ -92,10 +89,8 @@ class ConfirmedPasswordFieldTest extends SapphireTest
|
||||
)
|
||||
);
|
||||
$validator = new RequiredFields();
|
||||
/**
|
||||
* @skipUpgrade
|
||||
*/
|
||||
$form = new Form(new TestController(), 'Form', new FieldList($field), new FieldList(), $validator);
|
||||
/** @skipUpgrade */
|
||||
new Form(Controller::curr(), 'Form', new FieldList($field), new FieldList(), $validator);
|
||||
$this->assertTrue(
|
||||
$field->validate($validator),
|
||||
"Validates when both passwords are the same"
|
||||
@ -120,11 +115,9 @@ class ConfirmedPasswordFieldTest extends SapphireTest
|
||||
|
||||
public function testFormValidation()
|
||||
{
|
||||
/**
|
||||
* @skipUpgrade
|
||||
*/
|
||||
/** @skipUpgrade */
|
||||
$form = new Form(
|
||||
new Controller(),
|
||||
Controller::curr(),
|
||||
'Form',
|
||||
new FieldList($field = new ConfirmedPasswordField('Password')),
|
||||
new FieldList()
|
||||
|
@ -2,16 +2,14 @@
|
||||
|
||||
namespace SilverStripe\Forms\Tests;
|
||||
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Forms\DatetimeField;
|
||||
use SilverStripe\Forms\RequiredFields;
|
||||
use SilverStripe\Forms\DateField;
|
||||
use SilverStripe\Forms\Tests\DatetimeFieldTest\Model;
|
||||
use SilverStripe\Forms\TimeField;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\FormAction;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\Forms\FormAction;
|
||||
use SilverStripe\Forms\RequiredFields;
|
||||
use SilverStripe\Forms\Tests\DatetimeFieldTest\Model;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
|
||||
@ -452,7 +450,7 @@ class DatetimeFieldTest extends SapphireTest
|
||||
{
|
||||
/** @skipUpgrade */
|
||||
return new Form(
|
||||
new Controller(),
|
||||
Controller::curr(),
|
||||
'Form',
|
||||
new FieldList(),
|
||||
new FieldList(
|
||||
|
@ -479,7 +479,7 @@ class DropdownFieldTest extends SapphireTest
|
||||
)
|
||||
);
|
||||
$validator = new RequiredFields();
|
||||
$form = new Form(null, 'Form', new FieldList($field), new FieldList(), $validator);
|
||||
new Form(null, 'Form', new FieldList($field), new FieldList(), $validator);
|
||||
$field->setValue("One");
|
||||
$this->assertTrue($field->validate($validator));
|
||||
$field->setName("TestNew"); //try changing name of field
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace SilverStripe\Forms\Tests\EmailFieldTest;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\Forms\EmailField;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
@ -16,6 +17,13 @@ use SilverStripe\View\SSViewer;
|
||||
*/
|
||||
class TestController extends Controller implements TestOnly
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
if (Controller::has_curr()) {
|
||||
$this->setRequest(Controller::curr()->getRequest());
|
||||
}
|
||||
}
|
||||
|
||||
private static $allowed_actions = array('Form');
|
||||
|
||||
@ -25,11 +33,9 @@ class TestController extends Controller implements TestOnly
|
||||
|
||||
protected $template = 'BlankPage';
|
||||
|
||||
function Link($action = null)
|
||||
public function Link($action = null)
|
||||
{
|
||||
/**
|
||||
* @skipUpgrade
|
||||
*/
|
||||
/** @skipUpgrade */
|
||||
return Controller::join_links(
|
||||
'EmailFieldTest_Controller',
|
||||
$this->getRequest()->latestParam('Action'),
|
||||
@ -38,7 +44,10 @@ class TestController extends Controller implements TestOnly
|
||||
);
|
||||
}
|
||||
|
||||
function Form()
|
||||
/**
|
||||
* @return Form
|
||||
*/
|
||||
public function Form()
|
||||
{
|
||||
$form = new Form(
|
||||
$this,
|
||||
@ -60,13 +69,13 @@ class TestController extends Controller implements TestOnly
|
||||
return $form;
|
||||
}
|
||||
|
||||
function doSubmit($data, $form, $request)
|
||||
public function doSubmit($data, Form $form, HTTPRequest $request)
|
||||
{
|
||||
$form->sessionMessage('Test save was successful', 'good');
|
||||
return $this->redirectBack();
|
||||
}
|
||||
|
||||
function getViewer($action = null)
|
||||
public function getViewer($action = null)
|
||||
{
|
||||
return new SSViewer('BlankPage');
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ class FileFieldTest extends FunctionalTest
|
||||
public function testUploadRequiredFile()
|
||||
{
|
||||
$form = new Form(
|
||||
new Controller(),
|
||||
Controller::curr(),
|
||||
'Form',
|
||||
new FieldList(
|
||||
$fileField = new FileField('cv', 'Upload your CV')
|
||||
@ -46,7 +46,7 @@ class FileFieldTest extends FunctionalTest
|
||||
public function testUploadMissingRequiredFile()
|
||||
{
|
||||
$form = new Form(
|
||||
new Controller(),
|
||||
Controller::curr(),
|
||||
'Form',
|
||||
new FieldList(
|
||||
$fileField = new FileField('cv', 'Upload your CV')
|
||||
|
@ -19,7 +19,7 @@ class FormFactoryTest extends SapphireTest
|
||||
|
||||
protected static $fixture_file = 'FormFactoryTest.yml';
|
||||
|
||||
protected static function getExtraDataObjects()
|
||||
public static function getExtraDataObjects()
|
||||
{
|
||||
// Prevent setup breaking if versioned module absent
|
||||
if (class_exists(Versioned::class)) {
|
||||
@ -51,7 +51,6 @@ class FormFactoryTest extends SapphireTest
|
||||
$this->assertInstanceOf(HiddenField::class, $form->Fields()->fieldByName('ID'));
|
||||
$this->assertInstanceOf(HiddenField::class, $form->Fields()->fieldByName('SecurityID'));
|
||||
|
||||
|
||||
// Check preview link
|
||||
/** @var LiteralField $previewLink */
|
||||
$previewLink = $form->Fields()->fieldByName('PreviewLink');
|
||||
|
@ -12,6 +12,14 @@ use SilverStripe\Versioned\Versioned;
|
||||
*/
|
||||
class TestController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
if (Controller::has_curr()) {
|
||||
$this->setRequest(Controller::curr()->getRequest());
|
||||
}
|
||||
}
|
||||
|
||||
private static $extensions = [
|
||||
ControllerExtension::class,
|
||||
];
|
||||
@ -25,6 +33,9 @@ class TestController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Form
|
||||
*/
|
||||
public function Form()
|
||||
{
|
||||
// Simple example; Just get the first draft record
|
||||
|
@ -2,16 +2,18 @@
|
||||
|
||||
namespace SilverStripe\Forms\Tests;
|
||||
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use ReflectionClass;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Forms\FormField;
|
||||
use SilverStripe\Forms\Tests\FormFieldTest\TestExtension;
|
||||
use SilverStripe\Forms\TextField;
|
||||
use SilverStripe\Forms\RequiredFields;
|
||||
use SilverStripe\Forms\CompositeField;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\Form;
|
||||
use ReflectionClass;
|
||||
use SilverStripe\Forms\FormField;
|
||||
use SilverStripe\Forms\NullableField;
|
||||
use SilverStripe\Forms\RequiredFields;
|
||||
use SilverStripe\Forms\Tests\FormFieldTest\TestExtension;
|
||||
use SilverStripe\Forms\TextField;
|
||||
|
||||
class FormFieldTest extends SapphireTest
|
||||
{
|
||||
@ -205,7 +207,7 @@ class FormFieldTest extends SapphireTest
|
||||
|
||||
public function testEveryFieldTransformsReadonlyAsClone()
|
||||
{
|
||||
$fieldClasses = ClassInfo::subclassesFor('SilverStripe\\Forms\\FormField');
|
||||
$fieldClasses = ClassInfo::subclassesFor(FormField::class);
|
||||
foreach ($fieldClasses as $fieldClass) {
|
||||
$reflectionClass = new ReflectionClass($fieldClass);
|
||||
if (!$reflectionClass->isInstantiable()) {
|
||||
@ -215,12 +217,13 @@ class FormFieldTest extends SapphireTest
|
||||
if ($constructor->getNumberOfRequiredParameters() > 1) {
|
||||
continue;
|
||||
}
|
||||
if (is_a($fieldClass, 'SilverStripe\\Forms\\CompositeField', true)) {
|
||||
if (is_a($fieldClass, CompositeField::class, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldName = $reflectionClass->getShortName() . '_instance';
|
||||
if ($fieldClass = 'SilverStripe\\Forms\\NullableField') {
|
||||
/** @var FormField $instance */
|
||||
if ($fieldClass = NullableField::class) {
|
||||
$instance = new $fieldClass(new TextField($fieldName));
|
||||
} else {
|
||||
$instance = new $fieldClass($fieldName);
|
||||
@ -246,7 +249,7 @@ class FormFieldTest extends SapphireTest
|
||||
|
||||
public function testEveryFieldTransformsDisabledAsClone()
|
||||
{
|
||||
$fieldClasses = ClassInfo::subclassesFor('SilverStripe\\Forms\\FormField');
|
||||
$fieldClasses = ClassInfo::subclassesFor(FormField::class);
|
||||
foreach ($fieldClasses as $fieldClass) {
|
||||
$reflectionClass = new ReflectionClass($fieldClass);
|
||||
if (!$reflectionClass->isInstantiable()) {
|
||||
@ -256,12 +259,13 @@ class FormFieldTest extends SapphireTest
|
||||
if ($constructor->getNumberOfRequiredParameters() > 1) {
|
||||
continue;
|
||||
}
|
||||
if (is_a($fieldClass, 'SilverStripe\\Forms\\CompositeField', true)) {
|
||||
if (is_a($fieldClass, CompositeField::class, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldName = $reflectionClass->getShortName() . '_instance';
|
||||
if ($fieldClass = 'SilverStripe\\Forms\\NullableField') {
|
||||
/** @var FormField $instance */
|
||||
if ($fieldClass = NullableField::class) {
|
||||
$instance = new $fieldClass(new TextField($fieldName));
|
||||
} else {
|
||||
$instance = new $fieldClass($fieldName);
|
||||
@ -324,7 +328,7 @@ class FormFieldTest extends SapphireTest
|
||||
$field = new FormField('MyField');
|
||||
|
||||
// Make sure the user can update values.
|
||||
$field = $field->setSchemaData(['name' => 'MyUpdatedField']);
|
||||
$field->setSchemaData(['name' => 'MyUpdatedField']);
|
||||
$schema = $field->getSchemaData();
|
||||
$this->assertEquals($schema['name'], 'MyUpdatedField');
|
||||
|
||||
@ -347,12 +351,12 @@ class FormFieldTest extends SapphireTest
|
||||
$field = new FormField('MyField');
|
||||
|
||||
// Make sure the user can update values.
|
||||
$field = $field->setSchemaState(['value' => 'My custom value']);
|
||||
$field->setSchemaState(['value' => 'My custom value']);
|
||||
$schema = $field->getSchemaState();
|
||||
$this->assertEquals($schema['value'], 'My custom value');
|
||||
|
||||
// Make user the user can't define custom keys on the schema.
|
||||
$field = $field->setSchemaState(['myCustomKey' => 'yolo']);
|
||||
$field->setSchemaState(['myCustomKey' => 'yolo']);
|
||||
$schema = $field->getSchemaState();
|
||||
$this->assertEquals(array_key_exists('myCustomKey', $schema), false);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\Forms\Tests;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\FormAction;
|
||||
@ -16,7 +17,7 @@ class FormRequestHandlerTest extends SapphireTest
|
||||
public function testCallsActionOnFormHandler()
|
||||
{
|
||||
$form = new TestForm(
|
||||
new Controller(),
|
||||
Controller::curr(),
|
||||
'Form',
|
||||
new FieldList(),
|
||||
new FieldList(new FormAction('mySubmitOnFormHandler'))
|
||||
@ -24,6 +25,7 @@ class FormRequestHandlerTest extends SapphireTest
|
||||
$form->disableSecurityToken();
|
||||
$handler = new TestFormRequestHandler($form);
|
||||
$request = new HTTPRequest('POST', '/', null, ['action_mySubmitOnFormHandler' => 1]);
|
||||
$request->setSession(new Session([]));
|
||||
$response = $handler->httpSubmission($request);
|
||||
$this->assertFalse($response->isError());
|
||||
}
|
||||
@ -31,7 +33,7 @@ class FormRequestHandlerTest extends SapphireTest
|
||||
public function testCallsActionOnForm()
|
||||
{
|
||||
$form = new TestForm(
|
||||
new Controller(),
|
||||
Controller::curr(),
|
||||
'Form',
|
||||
new FieldList(),
|
||||
new FieldList(new FormAction('mySubmitOnForm'))
|
||||
@ -39,6 +41,7 @@ class FormRequestHandlerTest extends SapphireTest
|
||||
$form->disableSecurityToken();
|
||||
$handler = new FormRequestHandler($form);
|
||||
$request = new HTTPRequest('POST', '/', null, ['action_mySubmitOnForm' => 1]);
|
||||
$request->setSession(new Session([]));
|
||||
$response = $handler->httpSubmission($request);
|
||||
$this->assertFalse($response->isError());
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace SilverStripe\Forms\Tests;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Forms\CurrencyField;
|
||||
use SilverStripe\Forms\DateField;
|
||||
use SilverStripe\Forms\NumericField;
|
||||
@ -16,6 +17,16 @@ use SilverStripe\Forms\PopoverField;
|
||||
|
||||
class FormSchemaTest extends SapphireTest
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Clear old messages
|
||||
$session = Controller::curr()->getRequest()->getSession();
|
||||
$session
|
||||
->clear("FormInfo.TestForm.result")
|
||||
->clear("FormInfo.TestForm.data");
|
||||
}
|
||||
|
||||
public function testGetSchema()
|
||||
{
|
||||
@ -86,6 +97,7 @@ class FormSchemaTest extends SapphireTest
|
||||
$actions = new FieldList();
|
||||
$validator = new RequiredFields('Title');
|
||||
$form = new Form(null, 'TestForm', $fields, $actions, $validator);
|
||||
$form->clearMessage();
|
||||
$form->loadDataFrom(
|
||||
[
|
||||
'Title' => null,
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace SilverStripe\Forms\Tests;
|
||||
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Forms\Tests\FormTest\TestController;
|
||||
use SilverStripe\Forms\Tests\FormTest\ControllerWithSecurityToken;
|
||||
use SilverStripe\Forms\Tests\FormTest\ControllerWithStrictPostCheck;
|
||||
@ -61,7 +62,7 @@ class FormTest extends FunctionalTest
|
||||
public function testLoadDataFromRequest()
|
||||
{
|
||||
$form = new Form(
|
||||
new Controller(),
|
||||
Controller::curr(),
|
||||
'Form',
|
||||
new FieldList(
|
||||
new TextField('key1'),
|
||||
@ -130,7 +131,7 @@ class FormTest extends FunctionalTest
|
||||
public function testLoadDataFromUnchangedHandling()
|
||||
{
|
||||
$form = new Form(
|
||||
new Controller(),
|
||||
Controller::curr(),
|
||||
'Form',
|
||||
new FieldList(
|
||||
new TextField('key1'),
|
||||
@ -158,7 +159,7 @@ class FormTest extends FunctionalTest
|
||||
public function testLoadDataFromObject()
|
||||
{
|
||||
$form = new Form(
|
||||
new Controller(),
|
||||
Controller::curr(),
|
||||
'Form',
|
||||
new FieldList(
|
||||
new HeaderField('MyPlayerHeader', 'My Player'),
|
||||
@ -200,7 +201,7 @@ class FormTest extends FunctionalTest
|
||||
public function testLoadDataFromClearMissingFields()
|
||||
{
|
||||
$form = new Form(
|
||||
new Controller(),
|
||||
Controller::curr(),
|
||||
'Form',
|
||||
new FieldList(
|
||||
new HeaderField('MyPlayerHeader', 'My Player'),
|
||||
@ -250,7 +251,7 @@ class FormTest extends FunctionalTest
|
||||
{
|
||||
$object = new Team();
|
||||
$form = new Form(
|
||||
new Controller(),
|
||||
Controller::curr(),
|
||||
'Form',
|
||||
new FieldList(
|
||||
new LookupField('Players', 'Players')
|
||||
@ -280,7 +281,7 @@ class FormTest extends FunctionalTest
|
||||
public function testLoadDataFromIgnoreFalseish()
|
||||
{
|
||||
$form = new Form(
|
||||
new Controller(),
|
||||
Controller::curr(),
|
||||
'Form',
|
||||
new FieldList(
|
||||
new TextField('Biography', 'Biography', 'Custom Default')
|
||||
@ -447,7 +448,7 @@ class FormTest extends FunctionalTest
|
||||
{
|
||||
$this->get('FormTest_Controller');
|
||||
|
||||
$result = $this->post(
|
||||
$this->post(
|
||||
'FormTest_Controller/Form',
|
||||
array(
|
||||
'Email' => 'test@test.com',
|
||||
@ -778,6 +779,7 @@ class FormTest extends FunctionalTest
|
||||
'action_doSubmit' => 1
|
||||
)
|
||||
);
|
||||
$request->setSession(new Session([]));
|
||||
|
||||
$form->getRequestHandler()->httpSubmission($request);
|
||||
$button = $form->getRequestHandler()->buttonClicked();
|
||||
@ -798,6 +800,7 @@ class FormTest extends FunctionalTest
|
||||
'action_doSubmit' => 1
|
||||
)
|
||||
);
|
||||
$request->setSession(new Session([]));
|
||||
|
||||
$form->getRequestHandler()->httpSubmission($request);
|
||||
$button = $form->getRequestHandler()->buttonClicked();
|
||||
|
@ -21,6 +21,13 @@ use SilverStripe\View\SSViewer;
|
||||
*/
|
||||
class TestController extends Controller implements TestOnly
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
if (Controller::has_curr()) {
|
||||
$this->setRequest(Controller::curr()->getRequest());
|
||||
}
|
||||
}
|
||||
|
||||
private static $allowed_actions = array('Form');
|
||||
|
||||
|
@ -2,17 +2,16 @@
|
||||
|
||||
namespace SilverStripe\Forms\Tests\GridField;
|
||||
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Dev\CSSContentParser;
|
||||
use SilverStripe\Dev\FunctionalTest;
|
||||
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
|
||||
use SilverStripe\Forms\Tests\GridField\GridFieldAddExistingAutocompleterTest\TestController;
|
||||
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Cheerleader;
|
||||
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Permissions;
|
||||
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Player;
|
||||
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Team;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Dev\CSSContentParser;
|
||||
use SilverStripe\Dev\FunctionalTest;
|
||||
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
|
||||
|
||||
class GridFieldAddExistingAutocompleterTest extends FunctionalTest
|
||||
{
|
||||
|
@ -17,6 +17,13 @@ use SilverStripe\Forms\Tests\GridField\GridFieldTest\Player;
|
||||
*/
|
||||
class TestController extends Controller implements TestOnly
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
if (Controller::has_curr()) {
|
||||
$this->setRequest(Controller::curr()->getRequest());
|
||||
}
|
||||
}
|
||||
|
||||
private static $allowed_actions = array('Form');
|
||||
|
||||
@ -29,9 +36,7 @@ class TestController extends Controller implements TestOnly
|
||||
|
||||
public function Form()
|
||||
{
|
||||
/**
|
||||
* @var Player $player
|
||||
*/
|
||||
/** @var Player $player */
|
||||
$player = Player::get()->find('Email', 'player1@test.com');
|
||||
$config = GridFieldConfig::create()->addComponents(
|
||||
$relationComponent = new GridFieldAddExistingAutocompleter('before'),
|
||||
|
@ -2,7 +2,17 @@
|
||||
|
||||
namespace SilverStripe\Forms\Tests\GridField;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse_Exception;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Dev\CSSContentParser;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\Forms\GridField\GridField;
|
||||
use SilverStripe\Forms\GridField\GridFieldConfig;
|
||||
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
|
||||
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Cheerleader;
|
||||
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Permissions;
|
||||
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Player;
|
||||
@ -12,15 +22,6 @@ use SilverStripe\ORM\DataList;
|
||||
use SilverStripe\ORM\ValidationException;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Security\SecurityToken;
|
||||
use SilverStripe\Dev\CSSContentParser;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\Forms\GridField\GridFieldConfig;
|
||||
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
|
||||
use SilverStripe\Forms\GridField\GridField;
|
||||
|
||||
class GridFieldDeleteActionTest extends SapphireTest
|
||||
{
|
||||
@ -91,15 +92,13 @@ class GridFieldDeleteActionTest extends SapphireTest
|
||||
public function testActionsRequireCSRF()
|
||||
{
|
||||
$this->logInWithPermission('ADMIN');
|
||||
$this->setExpectedException(
|
||||
HTTPResponse_Exception::class,
|
||||
_t(
|
||||
$this->expectException(HTTPResponse_Exception::class);
|
||||
$this->expectExceptionMessage(_t(
|
||||
"SilverStripe\\Forms\\Form.CSRF_FAILED_MESSAGE",
|
||||
"There seems to have been a technical problem. Please click the back button, ".
|
||||
"refresh your browser, and try again."
|
||||
),
|
||||
400
|
||||
);
|
||||
));
|
||||
$this->expectExceptionCode(400);
|
||||
$stateID = 'testGridStateActionField';
|
||||
$request = new HTTPRequest(
|
||||
'POST',
|
||||
@ -110,6 +109,7 @@ class GridFieldDeleteActionTest extends SapphireTest
|
||||
'SecurityID' => null,
|
||||
)
|
||||
);
|
||||
$request->setSession(new Session([]));
|
||||
$this->gridField->gridFieldAlterAction(array('StateID'=>$stateID), $this->form, $request);
|
||||
}
|
||||
|
||||
@ -118,10 +118,11 @@ class GridFieldDeleteActionTest extends SapphireTest
|
||||
if (Security::getCurrentUser()) {
|
||||
Security::setCurrentUser(null);
|
||||
}
|
||||
$this->setExpectedException(ValidationException::class);
|
||||
$this->expectException(ValidationException::class);
|
||||
|
||||
$stateID = 'testGridStateActionField';
|
||||
Session::set(
|
||||
$session = Controller::curr()->getRequest()->getSession();
|
||||
$session->set(
|
||||
$stateID,
|
||||
array(
|
||||
'grid' => '',
|
||||
@ -141,6 +142,7 @@ class GridFieldDeleteActionTest extends SapphireTest
|
||||
$token->getName() => $token->getValue(),
|
||||
)
|
||||
);
|
||||
$request->setSession($session);
|
||||
$this->gridField->gridFieldAlterAction(array('StateID'=>$stateID), $this->form, $request);
|
||||
$this->assertEquals(
|
||||
3,
|
||||
@ -153,7 +155,8 @@ class GridFieldDeleteActionTest extends SapphireTest
|
||||
{
|
||||
$this->logInWithPermission('ADMIN');
|
||||
$stateID = 'testGridStateActionField';
|
||||
Session::set(
|
||||
$session = Controller::curr()->getRequest()->getSession();
|
||||
$session->set(
|
||||
$stateID,
|
||||
array(
|
||||
'grid'=>'',
|
||||
@ -173,6 +176,7 @@ class GridFieldDeleteActionTest extends SapphireTest
|
||||
$token->getName() => $token->getValue(),
|
||||
)
|
||||
);
|
||||
$request->setSession($session);
|
||||
$this->gridField->gridFieldAlterAction(array('StateID'=>$stateID), $this->form, $request);
|
||||
$this->assertEquals(2, $this->list->count(), 'User should be able to delete records with ADMIN permission.');
|
||||
}
|
||||
@ -183,11 +187,11 @@ class GridFieldDeleteActionTest extends SapphireTest
|
||||
|
||||
$config = GridFieldConfig::create()->addComponent(new GridFieldDeleteAction(true));
|
||||
|
||||
$session = Controller::curr()->getRequest()->getSession();
|
||||
$gridField = new GridField('testfield', 'testfield', $this->list, $config);
|
||||
$form = new Form(null, 'mockform', new FieldList(array($this->gridField)), new FieldList());
|
||||
|
||||
new Form(null, 'mockform', new FieldList(array($gridField)), new FieldList());
|
||||
$stateID = 'testGridStateActionField';
|
||||
Session::set(
|
||||
$session->set(
|
||||
$stateID,
|
||||
array(
|
||||
'grid'=>'',
|
||||
@ -207,7 +211,8 @@ class GridFieldDeleteActionTest extends SapphireTest
|
||||
$token->getName() => $token->getValue(),
|
||||
)
|
||||
);
|
||||
$this->gridField->gridFieldAlterAction(array('StateID'=>$stateID), $this->form, $request);
|
||||
$request->setSession($session);
|
||||
$gridField->gridFieldAlterAction(array('StateID'=>$stateID), $this->form, $request);
|
||||
$this->assertEquals(2, $this->list->count(), 'User should be able to delete records with ADMIN permission.');
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace SilverStripe\Forms\Tests\GridField;
|
||||
|
||||
use SilverStripe\Dev\CSSContentParser;
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\Dev\FunctionalTest;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Forms\HiddenField;
|
||||
@ -244,7 +245,6 @@ class GridFieldDetailFormTest extends FunctionalTest
|
||||
)
|
||||
);
|
||||
$this->assertFalse($response->isError());
|
||||
|
||||
$person = Person::get()->sort('FirstName')->First();
|
||||
$category = $person->Categories()->filter(array('Name' => 'Updated Category'))->First();
|
||||
$this->assertEquals(
|
||||
@ -363,18 +363,17 @@ class GridFieldDetailFormTest extends FunctionalTest
|
||||
}
|
||||
);
|
||||
// Note: A lot of scaffolding to execute the tested logic,
|
||||
// due to the coupling of form creation with request handling (and its context)
|
||||
/**
|
||||
* @skipUpgrade
|
||||
*/
|
||||
$request = new GridFieldDetailForm_ItemRequest(
|
||||
// due to the coupling of form creation with itemRequest handling (and its context)
|
||||
/** @skipUpgrade */
|
||||
$itemRequest = new GridFieldDetailForm_ItemRequest(
|
||||
GridField::create('Categories', 'Categories'),
|
||||
$component,
|
||||
$category,
|
||||
new Controller(),
|
||||
Controller::curr(),
|
||||
'Form'
|
||||
);
|
||||
$form = $request->ItemEditForm();
|
||||
$itemRequest->setRequest(Controller::curr()->getRequest());
|
||||
$form = $itemRequest->ItemEditForm();
|
||||
$this->assertNotNull($form->Fields()->fieldByName('Callback'));
|
||||
}
|
||||
|
||||
|
@ -50,18 +50,14 @@ class CategoryController extends Controller implements TestOnly
|
||||
$categoriesField->getConfig()->addComponent(new GridFieldEditButton());
|
||||
|
||||
$favGroupsField = new GridField('testgroupsfield', 'testgroupsfield', $person->FavouriteGroups());
|
||||
/**
|
||||
* @skipUpgrade
|
||||
*/
|
||||
/** @skipUpgrade */
|
||||
$favGroupsField->getConfig()->addComponent(new GridFieldDetailForm($this, 'Form'));
|
||||
$favGroupsField->getConfig()->addComponent(new GridFieldToolbarHeader());
|
||||
$favGroupsField->getConfig()->addComponent(new GridFieldAddNewButton('toolbar-header-right'));
|
||||
$favGroupsField->getConfig()->addComponent(new GridFieldEditButton());
|
||||
|
||||
$fields = new FieldList($categoriesField, $favGroupsField);
|
||||
/**
|
||||
* @skipUpgrade
|
||||
*/
|
||||
/** @skipUpgrade */
|
||||
return new Form($this, 'Form', $fields, new FieldList());
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,13 @@ use SilverStripe\Forms\GridField\GridFieldViewButton;
|
||||
|
||||
class TestController extends Controller implements TestOnly
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
if (Controller::has_curr()) {
|
||||
$this->setRequest(Controller::curr()->getRequest());
|
||||
}
|
||||
}
|
||||
|
||||
public function Link($action = null)
|
||||
{
|
||||
|
@ -43,11 +43,8 @@ class GridFieldPrintButtonTest extends SapphireTest
|
||||
->addComponent(new GridFieldPaginator(10))
|
||||
->addComponent($button);
|
||||
$gridField = new GridField('testfield', 'testfield', $list, $config);
|
||||
$controller = new Controller();
|
||||
/**
|
||||
* @skipUpgrade
|
||||
*/
|
||||
new Form($controller, 'Form', new FieldList($gridField), new FieldList());
|
||||
/** @skipUpgrade */
|
||||
new Form(Controller::curr(), 'Form', new FieldList($gridField), new FieldList());
|
||||
|
||||
// Printed data should ignore pagination limit
|
||||
$printData = $button->generatePrintData($gridField);
|
||||
|
@ -25,6 +25,7 @@ use SilverStripe\Forms\Tests\GridField\GridFieldTest\Permissions;
|
||||
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Player;
|
||||
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Team;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\Security\Group;
|
||||
use SilverStripe\Security\Member;
|
||||
|
||||
class GridFieldTest extends SapphireTest
|
||||
@ -115,9 +116,9 @@ class GridFieldTest extends SapphireTest
|
||||
public function testGridFieldModelClass()
|
||||
{
|
||||
$obj = new GridField('testfield', 'testfield', Member::get());
|
||||
$this->assertEquals('SilverStripe\\Security\\Member', $obj->getModelClass(), 'Should return Member');
|
||||
$obj->setModelClass('SilverStripe\\ORM\\DataModel');
|
||||
$this->assertEquals('SilverStripe\\ORM\\DataModel', $obj->getModelClass(), 'Should return Member');
|
||||
$this->assertEquals(Member::class, $obj->getModelClass(), 'Should return Member');
|
||||
$obj->setModelClass(Group::class);
|
||||
$this->assertEquals(Group::class, $obj->getModelClass(), 'Should return Group');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,7 +17,7 @@ class GridField_URLHandlerTest extends FunctionalTest
|
||||
|
||||
public function testFormSubmission()
|
||||
{
|
||||
$result = $this->get("GridField_URLHandlerTest_Controller/Form/field/Grid/showform");
|
||||
$this->get("GridField_URLHandlerTest_Controller/Form/field/Grid/showform");
|
||||
$formResult = $this->submitForm('Form_Form', 'action_doAction', array('Test' => 'foo bar'));
|
||||
$this->assertEquals("Submitted foo bar to component", $formResult->getBody());
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace SilverStripe\Forms\Tests\GridField\GridField_URLHandlerTest;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\RequestHandler;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\Form;
|
||||
@ -38,8 +39,9 @@ class TestComponent extends RequestHandler implements GridField_URLHandler
|
||||
);
|
||||
}
|
||||
|
||||
public function handleItem($gridField, $request)
|
||||
public function handleItem(GridField $gridField, HTTPRequest $request)
|
||||
{
|
||||
$this->setRequest($request);
|
||||
$id = $request->param("ID");
|
||||
return new TestComponent_ItemRequest(
|
||||
$gridField,
|
||||
@ -53,16 +55,21 @@ class TestComponent extends RequestHandler implements GridField_URLHandler
|
||||
return $this->gridField->Link();
|
||||
}
|
||||
|
||||
public function showform($gridField, $request)
|
||||
public function showform(GridField $gridField, HTTPRequest $request)
|
||||
{
|
||||
$this->setRequest($request);
|
||||
return "<head>" . SSViewer::get_base_tag("") . "</head>" . $this->Form($gridField, $request)->forTemplate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @skipUpgrade
|
||||
* @param GridField $gridField
|
||||
* @param HTTPRequest $request
|
||||
* @return Form
|
||||
*/
|
||||
public function Form($gridField, $request)
|
||||
public function Form(GridField $gridField, HTTPRequest $request)
|
||||
{
|
||||
$this->setRequest($request);
|
||||
$this->gridField = $gridField;
|
||||
return new Form(
|
||||
$this,
|
||||
@ -81,8 +88,9 @@ class TestComponent extends RequestHandler implements GridField_URLHandler
|
||||
return "Submitted " . $data['Test'] . " to component";
|
||||
}
|
||||
|
||||
public function testpage($gridField, $request)
|
||||
public function testpage(GridField $gridField, HTTPRequest $request)
|
||||
{
|
||||
$this->setRequest($request);
|
||||
return "Test page for component";
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,14 @@ use SilverStripe\ORM\ArrayList;
|
||||
*/
|
||||
class TestController extends Controller implements TestOnly
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
if (Controller::has_curr()) {
|
||||
$this->setRequest(Controller::curr()->getRequest());
|
||||
}
|
||||
}
|
||||
|
||||
public function Link($action = null)
|
||||
{
|
||||
return Controller::join_links('GridField_URLHandlerTest_Controller', $action, '/');
|
||||
|
@ -2,16 +2,17 @@
|
||||
|
||||
namespace SilverStripe\Forms\Tests\HTMLEditor;
|
||||
|
||||
use Exception;
|
||||
use PHPUnit_Framework_MockObject_MockObject;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Core\Manifest\ModuleManifest;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Forms\HTMLEditor\HTMLEditorConfig;
|
||||
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
|
||||
use SilverStripe\Forms\HTMLEditor\TinyMCEConfig;
|
||||
use SilverStripe\Forms\HTMLEditor\HTMLEditorConfig;
|
||||
use \Exception;
|
||||
|
||||
class HTMLEditorConfigTest extends SapphireTest
|
||||
{
|
||||
@ -48,7 +49,7 @@ class HTMLEditorConfigTest extends SapphireTest
|
||||
|
||||
public function testEnablePluginsByArrayWithPaths()
|
||||
{
|
||||
Config::inst()->update(Director::class, 'alternate_base_url', 'http://mysite.com/subdir');
|
||||
Config::modify()->set(Director::class, 'alternate_base_url', 'http://mysite.com/subdir');
|
||||
$c = new TinyMCEConfig();
|
||||
$c->setTheme('modern');
|
||||
$c->setOption('language', 'es');
|
||||
@ -106,7 +107,7 @@ class HTMLEditorConfigTest extends SapphireTest
|
||||
$this->markTestSkipped('No silverstripe/admin module loaded');
|
||||
}
|
||||
TinyMCEConfig::config()->remove('base_dir');
|
||||
Config::inst()->update(Director::class, 'alternate_base_url', 'http://mysite.com/subdir');
|
||||
Config::modify()->set(Director::class, 'alternate_base_url', 'http://mysite.com/subdir');
|
||||
$c = new TinyMCEConfig();
|
||||
$c->setTheme('modern');
|
||||
$c->setOption('language', 'es');
|
||||
@ -191,36 +192,30 @@ class HTMLEditorConfigTest extends SapphireTest
|
||||
public function testExceptionThrownWhenTinyMCEPathCannotBeComputed()
|
||||
{
|
||||
TinyMCEConfig::config()->remove('base_dir');
|
||||
ModuleLoader::inst()->pushManifest(new ModuleManifest(
|
||||
dirname(__FILE__),
|
||||
false
|
||||
));
|
||||
$c = new TinyMCEConfig();
|
||||
|
||||
$this->setExpectedExceptionRegExp(
|
||||
Exception::class,
|
||||
'/module is not installed/'
|
||||
);
|
||||
|
||||
$c->getScriptURL();
|
||||
ModuleLoader::inst()->pushManifest(new ModuleManifest(__DIR__));
|
||||
|
||||
try {
|
||||
$config = new TinyMCEConfig();
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessageRegExp('/module is not installed/');
|
||||
$config->getScriptURL();
|
||||
} finally {
|
||||
ModuleLoader::inst()->popManifest();
|
||||
}
|
||||
}
|
||||
|
||||
public function testExceptionThrownWhenTinyMCEGZipPathDoesntExist()
|
||||
{
|
||||
HTMLEditorField::config()->set('use_gzip', true);
|
||||
/** @var TinyMCEConfig|PHPUnit_Framework_MockObject_MockObject $stub */
|
||||
$stub = $this->getMockBuilder(TinyMCEConfig::class)
|
||||
->setMethods(['getTinyMCEPath'])
|
||||
->getMock();
|
||||
$stub->method('getTinyMCEPath')
|
||||
->willReturn('fail');
|
||||
|
||||
$this->setExpectedExceptionRegExp(
|
||||
Exception::class,
|
||||
'/does not exist/'
|
||||
);
|
||||
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessageRegExp('/does not exist/');
|
||||
$stub->getScriptURL();
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\Forms\Tests;
|
||||
|
||||
use SilverStripe\Assets\File;
|
||||
use SilverStripe\Assets\Folder;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Dev\CSSContentParser;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
@ -21,6 +22,7 @@ class TreeDropdownFieldTest extends SapphireTest
|
||||
|
||||
// case insensitive search against keyword 'sub' for folders
|
||||
$request = new HTTPRequest('GET', 'url', array('search'=>'sub'));
|
||||
$request->setSession(new Session([]));
|
||||
$response = $field->tree($request);
|
||||
$tree = $response->getBody();
|
||||
|
||||
@ -58,6 +60,7 @@ class TreeDropdownFieldTest extends SapphireTest
|
||||
|
||||
// case insensitive search against keyword 'sub' for files
|
||||
$request = new HTTPRequest('GET', 'url', array('search'=>'sub'));
|
||||
$request->setSession(new Session([]));
|
||||
$response = $field->tree($request);
|
||||
$tree = $response->getBody();
|
||||
|
||||
|
@ -30,7 +30,7 @@ class ValidatorTest extends SapphireTest
|
||||
$fieldList->add(new TextField($name));
|
||||
}
|
||||
|
||||
return new Form(new Controller(), "testForm", $fieldList, new FieldList([/* no actions */]));
|
||||
return new Form(Controller::curr(), "testForm", $fieldList, new FieldList([/* no actions */]));
|
||||
}
|
||||
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
namespace SilverStripe\ORM\Tests;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Kernel;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
@ -11,19 +13,20 @@ class DBTest extends SapphireTest
|
||||
|
||||
public function testValidAlternativeDatabaseName()
|
||||
{
|
||||
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
$prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_';
|
||||
|
||||
Director::set_environment_type('dev');
|
||||
$kernel->setEnvironment(Kernel::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::set_environment_type('live');
|
||||
$kernel->setEnvironment(Kernel::LIVE);
|
||||
$this->assertFalse(DB::valid_alternative_database_name($prefix.'tmpdb1234567'));
|
||||
|
||||
Director::set_environment_type('dev');
|
||||
$kernel->setEnvironment(Kernel::DEV);
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class DataListTest extends SapphireTest
|
||||
// Borrow the model from DataObjectTest
|
||||
protected static $fixture_file = 'DataObjectTest.yml';
|
||||
|
||||
protected static function getExtraDataObjects()
|
||||
public static function getExtraDataObjects()
|
||||
{
|
||||
return array_merge(
|
||||
DataObjectTest::$extra_data_objects,
|
||||
|
@ -15,7 +15,7 @@ class DataObjectLazyLoadingTest extends SapphireTest
|
||||
'DataObjectTest.yml',
|
||||
);
|
||||
|
||||
protected static function getExtraDataObjects()
|
||||
public static function getExtraDataObjects()
|
||||
{
|
||||
return array_merge(
|
||||
DataObjectTest::$extra_data_objects,
|
||||
|
@ -57,7 +57,7 @@ class DataObjectTest extends SapphireTest
|
||||
DataObjectTest\RelationChildSecond::class,
|
||||
);
|
||||
|
||||
protected static function getExtraDataObjects()
|
||||
public static function getExtraDataObjects()
|
||||
{
|
||||
return array_merge(
|
||||
DataObjectTest::$extra_data_objects,
|
||||
|
@ -11,7 +11,7 @@ class HasManyListTest extends SapphireTest
|
||||
// Borrow the model from DataObjectTest
|
||||
protected static $fixture_file = 'DataObjectTest.yml';
|
||||
|
||||
protected static function getExtraDataObjects()
|
||||
public static function getExtraDataObjects()
|
||||
{
|
||||
return array_merge(
|
||||
DataObjectTest::$extra_data_objects,
|
||||
|
@ -16,7 +16,7 @@ class HierarchyTest extends SapphireTest
|
||||
HierarchyTest\HideTestSubObject::class,
|
||||
);
|
||||
|
||||
protected static function getExtraDataObjects()
|
||||
public static function getExtraDataObjects()
|
||||
{
|
||||
// Prevent setup breaking if versioned module absent
|
||||
if (class_exists(Versioned::class)) {
|
||||
|
@ -20,7 +20,7 @@ class ManyManyListTest extends SapphireTest
|
||||
ManyManyListTest\Product::class,
|
||||
];
|
||||
|
||||
protected static function getExtraDataObjects()
|
||||
public static function getExtraDataObjects()
|
||||
{
|
||||
return array_merge(
|
||||
DataObjectTest::$extra_data_objects,
|
||||
|
@ -14,7 +14,7 @@ class MapTest extends SapphireTest
|
||||
// Borrow the model from DataObjectTest
|
||||
protected static $fixture_file = 'DataObjectTest.yml';
|
||||
|
||||
protected static function getExtraDataObjects()
|
||||
public static function getExtraDataObjects()
|
||||
{
|
||||
return array_merge(
|
||||
DataObjectTest::$extra_data_objects,
|
||||
|
@ -22,7 +22,7 @@ class MarkedSetTest extends SapphireTest
|
||||
HierarchyTest\HideTestSubObject::class,
|
||||
);
|
||||
|
||||
protected static function getExtraDataObjects()
|
||||
public static function getExtraDataObjects()
|
||||
{
|
||||
// Prevent setup breaking if versioned module absent
|
||||
if (class_exists(Versioned::class)) {
|
||||
|
@ -18,7 +18,7 @@ class PaginatedListTest extends SapphireTest
|
||||
|
||||
protected static $fixture_file = 'DataObjectTest.yml';
|
||||
|
||||
protected static function getExtraDataObjects()
|
||||
public static function getExtraDataObjects()
|
||||
{
|
||||
return array_merge(
|
||||
DataObjectTest::$extra_data_objects,
|
||||
|
@ -19,7 +19,7 @@ class PolymorphicHasManyListTest extends SapphireTest
|
||||
// Borrow the model from DataObjectTest
|
||||
protected static $fixture_file = 'DataObjectTest.yml';
|
||||
|
||||
protected static function getExtraDataObjects()
|
||||
public static function getExtraDataObjects()
|
||||
{
|
||||
return array_merge(
|
||||
DataObjectTest::$extra_data_objects,
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace SilverStripe\Security\Tests;
|
||||
|
||||
use SilverStripe\Security\BasicAuth;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Dev\FunctionalTest;
|
||||
@ -29,24 +30,21 @@ class BasicAuthTest extends FunctionalTest
|
||||
parent::setUp();
|
||||
|
||||
// Fixtures assume Email is the field used to identify the log in identity
|
||||
Member::config()->unique_identifier_field = 'Email';
|
||||
Member::config()->set('unique_identifier_field', 'Email');
|
||||
Security::force_database_is_ready(true); // Prevents Member test subclasses breaking ready test
|
||||
Member::config()->lock_out_after_incorrect_logins = 10;
|
||||
Member::config()->set('lock_out_after_incorrect_logins', 10);
|
||||
|
||||
// Temp disable is_cli() exemption for tests
|
||||
BasicAuth::config()->set('ignore_cli', false);
|
||||
}
|
||||
|
||||
public function testBasicAuthEnabledWithoutLogin()
|
||||
{
|
||||
$origUser = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
|
||||
$origPw = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
|
||||
|
||||
unset($_SERVER['PHP_AUTH_USER']);
|
||||
unset($_SERVER['PHP_AUTH_PW']);
|
||||
|
||||
$response = Director::test('BasicAuthTest_ControllerSecuredWithPermission', null, $_SESSION, null, null, $_SERVER);
|
||||
$response = Director::test('BasicAuthTest_ControllerSecuredWithPermission');
|
||||
$this->assertEquals(401, $response->getStatusCode());
|
||||
|
||||
$_SERVER['PHP_AUTH_USER'] = $origUser;
|
||||
$_SERVER['PHP_AUTH_PW'] = $origPw;
|
||||
}
|
||||
|
||||
public function testBasicAuthDoesntCallActionOrFurtherInitOnAuthFailure()
|
||||
|
@ -29,6 +29,7 @@ class ControllerSecuredWithPermission extends Controller implements TestOnly
|
||||
public function index()
|
||||
{
|
||||
self::$index_called = true;
|
||||
return "index";
|
||||
}
|
||||
|
||||
public function Link($action = null)
|
||||
|
@ -2,18 +2,18 @@
|
||||
|
||||
namespace SilverStripe\Security\Tests;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Dev\FunctionalTest;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\Security\Group;
|
||||
use SilverStripe\Dev\FunctionalTest;
|
||||
use SilverStripe\Control\Session;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Permission;
|
||||
use SilverStripe\Security\Tests\GroupTest\TestMember;
|
||||
|
||||
class GroupTest extends FunctionalTest
|
||||
{
|
||||
|
||||
protected static $fixture_file = 'GroupTest.yml';
|
||||
|
||||
protected static $extra_dataobjects = [
|
||||
@ -44,13 +44,14 @@ class GroupTest extends FunctionalTest
|
||||
*/
|
||||
public function testMemberGroupRelationForm()
|
||||
{
|
||||
Session::set('loggedInAs', $this->idFromFixture(TestMember::class, 'admin'));
|
||||
$this->logInAs($this->idFromFixture(TestMember::class, 'admin'));
|
||||
|
||||
$adminGroup = $this->objFromFixture(Group::class, 'admingroup');
|
||||
$parentGroup = $this->objFromFixture(Group::class, 'parentgroup');
|
||||
|
||||
// Test single group relation through checkboxsetfield
|
||||
$form = new GroupTest\MemberForm(new Controller(), 'Form');
|
||||
$form = new GroupTest\MemberForm(Controller::curr(), 'Form');
|
||||
/** @var Member $member */
|
||||
$member = $this->objFromFixture(TestMember::class, 'admin');
|
||||
$form->loadDataFrom($member);
|
||||
$checkboxSetField = $form->Fields()->fieldByName('Groups');
|
||||
@ -102,13 +103,17 @@ class GroupTest extends FunctionalTest
|
||||
|
||||
// Persists after writing to DB
|
||||
$group->write();
|
||||
|
||||
/** @var Group $group */
|
||||
$group = Group::get()->byID($group->ID);
|
||||
$this->assertEquals(array($member->ID), array_values($group->Members()->getIDList()));
|
||||
}
|
||||
|
||||
public function testCollateAncestorIDs()
|
||||
{
|
||||
/** @var Group $parentGroup */
|
||||
$parentGroup = $this->objFromFixture(Group::class, 'parentgroup');
|
||||
/** @var Group $childGroup */
|
||||
$childGroup = $this->objFromFixture(Group::class, 'childgroup');
|
||||
$orphanGroup = new Group();
|
||||
$orphanGroup->ParentID = 99999;
|
||||
@ -142,6 +147,7 @@ class GroupTest extends FunctionalTest
|
||||
*/
|
||||
public function testCollateFamilyIds()
|
||||
{
|
||||
/** @var Group $group */
|
||||
$group = $this->objFromFixture(Group::class, 'parentgroup');
|
||||
$groupIds = $this->allFixtureIDs(Group::class);
|
||||
$ids = array_intersect_key($groupIds, array_flip(['parentgroup', 'childgroup', 'grandchildgroup']));
|
||||
@ -150,11 +156,11 @@ class GroupTest extends FunctionalTest
|
||||
|
||||
/**
|
||||
* Test that an exception is thrown if collateFamilyIDs is called on an unsaved Group
|
||||
* @expectedException InvalidArgumentException
|
||||
* @expectedExceptionMessage Cannot call collateFamilyIDs on unsaved Group.
|
||||
*/
|
||||
public function testCannotCollateUnsavedGroupFamilyIds()
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Cannot call collateFamilyIDs on unsaved Group.');
|
||||
$group = new Group;
|
||||
$group->collateFamilyIDs();
|
||||
}
|
||||
@ -164,8 +170,8 @@ class GroupTest extends FunctionalTest
|
||||
*/
|
||||
public function testGetAllChildren()
|
||||
{
|
||||
/** @var Group $group */
|
||||
$group = $this->objFromFixture(Group::class, 'parentgroup');
|
||||
|
||||
$children = $group->getAllChildren();
|
||||
$this->assertInstanceOf(ArrayList::class, $children);
|
||||
$this->assertSame(['childgroup', 'grandchildgroup'], $children->column('Code'));
|
||||
@ -202,7 +208,9 @@ class GroupTest extends FunctionalTest
|
||||
|
||||
public function testValidatesPrivilegeLevelOfParent()
|
||||
{
|
||||
/** @var Group $nonAdminGroup */
|
||||
$nonAdminGroup = $this->objFromFixture(Group::class, 'childgroup');
|
||||
/** @var Group $adminGroup */
|
||||
$adminGroup = $this->objFromFixture(Group::class, 'admingroup');
|
||||
|
||||
// Making admin group parent of a non-admin group, effectively expanding is privileges
|
||||
@ -224,6 +232,7 @@ class GroupTest extends FunctionalTest
|
||||
$nonAdminGroup->write();
|
||||
|
||||
$this->logInWithPermission('ADMIN');
|
||||
/** @var Group $inheritedAdminGroup */
|
||||
$inheritedAdminGroup = $this->objFromFixture(Group::class, 'group1');
|
||||
$inheritedAdminGroup->ParentID = $adminGroup->ID;
|
||||
$inheritedAdminGroup->write(); // only works with ADMIN login
|
||||
|
@ -2,22 +2,21 @@
|
||||
|
||||
namespace SilverStripe\Security\Tests;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\ORM\DataModel;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
use SilverStripe\ORM\ValidationResult;
|
||||
use SilverStripe\Security\Authenticator;
|
||||
use SilverStripe\Security\DefaultAdminService;
|
||||
use SilverStripe\Security\IdentityStore;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\MemberAuthenticator\CMSMemberAuthenticator;
|
||||
use SilverStripe\Security\MemberAuthenticator\CMSMemberLoginForm;
|
||||
use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\MemberAuthenticator\MemberLoginForm;
|
||||
use SilverStripe\Security\IdentityStore;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Security\DefaultAdminService;
|
||||
use SilverStripe\Security\Security;
|
||||
|
||||
class MemberAuthenticatorTest extends SapphireTest
|
||||
{
|
||||
@ -71,7 +70,7 @@ class MemberAuthenticatorTest extends SapphireTest
|
||||
// Create basic login form
|
||||
$frontendResponse = $authenticator
|
||||
->getLoginHandler($controller->link())
|
||||
->handleRequest(new HTTPRequest('get', '/'), DataModel::inst());
|
||||
->handleRequest(Controller::curr()->getRequest());
|
||||
|
||||
$this->assertTrue(is_array($frontendResponse));
|
||||
$this->assertTrue(isset($frontendResponse['Form']));
|
||||
@ -116,10 +115,11 @@ class MemberAuthenticatorTest extends SapphireTest
|
||||
// Test correct login
|
||||
/** @var ValidationResult $message */
|
||||
$result = $authenticator->authenticate(
|
||||
array(
|
||||
[
|
||||
'tempid' => $tempID,
|
||||
'Password' => 'mypassword'
|
||||
),
|
||||
],
|
||||
Controller::curr()->getRequest(),
|
||||
$message
|
||||
);
|
||||
|
||||
@ -129,10 +129,11 @@ class MemberAuthenticatorTest extends SapphireTest
|
||||
|
||||
// Test incorrect login
|
||||
$result = $authenticator->authenticate(
|
||||
array(
|
||||
[
|
||||
'tempid' => $tempID,
|
||||
'Password' => 'notmypassword'
|
||||
),
|
||||
],
|
||||
Controller::curr()->getRequest(),
|
||||
$message
|
||||
);
|
||||
|
||||
@ -154,10 +155,11 @@ class MemberAuthenticatorTest extends SapphireTest
|
||||
// Test correct login
|
||||
/** @var ValidationResult $message */
|
||||
$result = $authenticator->authenticate(
|
||||
array(
|
||||
[
|
||||
'Email' => 'admin',
|
||||
'Password' => 'password'
|
||||
),
|
||||
],
|
||||
Controller::curr()->getRequest(),
|
||||
$message
|
||||
);
|
||||
$this->assertNotEmpty($result);
|
||||
@ -166,10 +168,11 @@ class MemberAuthenticatorTest extends SapphireTest
|
||||
|
||||
// Test incorrect login
|
||||
$result = $authenticator->authenticate(
|
||||
array(
|
||||
[
|
||||
'Email' => 'admin',
|
||||
'Password' => 'notmypassword'
|
||||
),
|
||||
],
|
||||
Controller::curr()->getRequest(),
|
||||
$message
|
||||
);
|
||||
$messages = $message->getMessages();
|
||||
@ -193,7 +196,8 @@ class MemberAuthenticatorTest extends SapphireTest
|
||||
[
|
||||
'Email' => 'admin',
|
||||
'Password' => 'wrongpassword'
|
||||
]
|
||||
],
|
||||
Controller::curr()->getRequest()
|
||||
);
|
||||
|
||||
$defaultAdmin = DefaultAdminService::singleton()->findOrCreateDefaultAdmin();
|
||||
|
@ -34,9 +34,9 @@ class MemberTest extends FunctionalTest
|
||||
Member::class => '*',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
parent::__construct();
|
||||
parent::setUpBeforeClass();
|
||||
|
||||
//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
|
||||
|
@ -21,10 +21,10 @@ class SecurityDefaultAdminTest extends SapphireTest
|
||||
|
||||
// TODO Workaround to force database clearing with no fixture present,
|
||||
// and avoid sideeffects from other tests
|
||||
if (!self::using_temp_db()) {
|
||||
self::create_temp_db();
|
||||
if (!static::$tempDB->isUsed()) {
|
||||
static::$tempDB->build();
|
||||
}
|
||||
self::empty_temp_db();
|
||||
static::$tempDB->clearAllData();
|
||||
|
||||
if (DefaultAdminService::hasDefaultAdmin()) {
|
||||
$this->defaultUsername = DefaultAdminService::getDefaultAdminUsername();
|
||||
|
@ -2,11 +2,17 @@
|
||||
|
||||
namespace SilverStripe\Security\Tests;
|
||||
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Dev\FunctionalTest;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
use SilverStripe\ORM\FieldType\DBClassName;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\ORM\FieldType\DBClassName;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
use SilverStripe\ORM\ValidationResult;
|
||||
use SilverStripe\Security\LoginAttempt;
|
||||
use SilverStripe\Security\Member;
|
||||
@ -46,11 +52,11 @@ class SecurityTest extends FunctionalTest
|
||||
/**
|
||||
* @skipUpgrade
|
||||
*/
|
||||
Member::config()->unique_identifier_field = 'Email';
|
||||
Member::config()->set('unique_identifier_field', 'Email');
|
||||
|
||||
parent::setUp();
|
||||
|
||||
Config::modify()->merge('SilverStripe\\Control\\Director', 'alternate_base_url', '/');
|
||||
Director::config()->set('alternate_base_url', '/');
|
||||
}
|
||||
|
||||
public function testAccessingAuthenticatedPageRedirectsToLoginForm()
|
||||
@ -74,21 +80,21 @@ class SecurityTest extends FunctionalTest
|
||||
|
||||
public function testPermissionFailureSetsCorrectFormMessages()
|
||||
{
|
||||
Config::nest();
|
||||
|
||||
// Controller that doesn't attempt redirections
|
||||
$controller = new SecurityTest\NullController();
|
||||
$controller->setRequest(Controller::curr()->getRequest());
|
||||
$controller->setResponse(new HTTPResponse());
|
||||
|
||||
$session = Controller::curr()->getRequest()->getSession();
|
||||
Security::permissionFailure($controller, array('default' => 'Oops, not allowed'));
|
||||
$this->assertEquals('Oops, not allowed', Session::get('Security.Message.message'));
|
||||
$this->assertEquals('Oops, not allowed', $session->get('Security.Message.message'));
|
||||
|
||||
// Test that config values are used correctly
|
||||
Config::inst()->update(Security::class, 'default_message_set', 'stringvalue');
|
||||
Config::modify()->set(Security::class, 'default_message_set', 'stringvalue');
|
||||
Security::permissionFailure($controller);
|
||||
$this->assertEquals(
|
||||
'stringvalue',
|
||||
Session::get('Security.Message.message'),
|
||||
$session->get('Security.Message.message'),
|
||||
'Default permission failure message value was not present'
|
||||
);
|
||||
|
||||
@ -97,7 +103,7 @@ class SecurityTest extends FunctionalTest
|
||||
Security::permissionFailure($controller);
|
||||
$this->assertEquals(
|
||||
'arrayvalue',
|
||||
Session::get('Security.Message.message'),
|
||||
$session->get('Security.Message.message'),
|
||||
'Default permission failure message value was not present'
|
||||
);
|
||||
|
||||
@ -106,7 +112,7 @@ class SecurityTest extends FunctionalTest
|
||||
// been fetched and output as part of it, so has been removed from the session
|
||||
$this->logInWithPermission('EDITOR');
|
||||
|
||||
Config::inst()->update(
|
||||
Config::modify()->set(
|
||||
Security::class,
|
||||
'default_message_set',
|
||||
array('default' => 'default', 'alreadyLoggedIn' => 'You are already logged in!')
|
||||
@ -127,8 +133,6 @@ class SecurityTest extends FunctionalTest
|
||||
$controller->getResponse()->getBody(),
|
||||
"Message set passed to Security::permissionFailure() didn't override Config values"
|
||||
);
|
||||
|
||||
Config::unnest();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -200,7 +204,7 @@ class SecurityTest extends FunctionalTest
|
||||
Security::setCurrentUser($member);
|
||||
|
||||
/* View the Security/login page */
|
||||
$response = $this->get(Config::inst()->get(Security::class, 'login_url'));
|
||||
$this->get(Config::inst()->get(Security::class, 'login_url'));
|
||||
|
||||
$items = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm input.action');
|
||||
|
||||
@ -229,12 +233,12 @@ class SecurityTest extends FunctionalTest
|
||||
public function testMemberIDInSessionDoesntExistInDatabaseHasToLogin()
|
||||
{
|
||||
/* Log in with a Member ID that doesn't exist in the DB */
|
||||
$this->session()->inst_set('loggedInAs', 500);
|
||||
$this->session()->set('loggedInAs', 500);
|
||||
|
||||
$this->autoFollowRedirection = true;
|
||||
|
||||
/* Attempt to get into the admin section */
|
||||
$response = $this->get(Config::inst()->get(Security::class, 'login_url'));
|
||||
$this->get(Config::inst()->get(Security::class, 'login_url'));
|
||||
|
||||
$items = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm input.text');
|
||||
|
||||
@ -244,14 +248,14 @@ class SecurityTest extends FunctionalTest
|
||||
$this->autoFollowRedirection = false;
|
||||
|
||||
/* Log the user out */
|
||||
$this->session()->inst_set('loggedInAs', null);
|
||||
$this->session()->set('loggedInAs', null);
|
||||
}
|
||||
|
||||
public function testLoginUsernamePersists()
|
||||
{
|
||||
// Test that username does not persist
|
||||
$this->session()->inst_set('SessionForms.MemberLoginForm.Email', 'myuser@silverstripe.com');
|
||||
Security::config()->remember_username = false;
|
||||
$this->session()->set('SessionForms.MemberLoginForm.Email', 'myuser@silverstripe.com');
|
||||
Security::config()->set('remember_username', false);
|
||||
$this->get(Config::inst()->get(Security::class, 'login_url'));
|
||||
$items = $this
|
||||
->cssParser()
|
||||
@ -264,8 +268,8 @@ class SecurityTest extends FunctionalTest
|
||||
$this->assertEquals('off', (string)$form[0]->attributes()->autocomplete);
|
||||
|
||||
// Test that username does persist when necessary
|
||||
$this->session()->inst_set('SessionForms.MemberLoginForm.Email', 'myuser@silverstripe.com');
|
||||
Security::config()->remember_username = true;
|
||||
$this->session()->set('SessionForms.MemberLoginForm.Email', 'myuser@silverstripe.com');
|
||||
Security::config()->set('remember_username', true);
|
||||
$this->get(Config::inst()->get(Security::class, 'login_url'));
|
||||
$items = $this
|
||||
->cssParser()
|
||||
@ -343,7 +347,7 @@ class SecurityTest extends FunctionalTest
|
||||
"Internal relative BackURLs work when passed through to login form"
|
||||
);
|
||||
// Log the user out
|
||||
$this->session()->inst_set('loggedInAs', null);
|
||||
$this->session()->set('loggedInAs', null);
|
||||
|
||||
// Test internal absolute redirect
|
||||
$response = $this->doTestLoginForm(
|
||||
@ -358,7 +362,7 @@ class SecurityTest extends FunctionalTest
|
||||
"Internal absolute BackURLs work when passed through to login form"
|
||||
);
|
||||
// Log the user out
|
||||
$this->session()->inst_set('loggedInAs', null);
|
||||
$this->session()->set('loggedInAs', null);
|
||||
|
||||
// Test external redirect
|
||||
$response = $this->doTestLoginForm('noexpiry@silverstripe.com', '1nitialPassword', 'http://myspoofedhost.com');
|
||||
@ -378,7 +382,7 @@ class SecurityTest extends FunctionalTest
|
||||
);
|
||||
|
||||
// Log the user out
|
||||
$this->session()->inst_set('loggedInAs', null);
|
||||
$this->session()->set('loggedInAs', null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -390,7 +394,7 @@ class SecurityTest extends FunctionalTest
|
||||
$badResponse = $this->doTestLoginForm('testuser@example.com', 'badpassword');
|
||||
$this->assertEquals(302, $badResponse->getStatusCode());
|
||||
$this->assertRegExp('/Security\/login/', $badResponse->getHeader('Location'));
|
||||
$this->assertNull($this->session()->inst_get('loggedInAs'));
|
||||
$this->assertNull($this->session()->get('loggedInAs'));
|
||||
|
||||
/* UNEXPIRED PASSWORD GO THROUGH WITHOUT A HITCH */
|
||||
$goodResponse = $this->doTestLoginForm('testuser@example.com', '1nitialPassword');
|
||||
@ -399,7 +403,7 @@ class SecurityTest extends FunctionalTest
|
||||
Controller::join_links(Director::absoluteBaseURL(), 'test/link'),
|
||||
$goodResponse->getHeader('Location')
|
||||
);
|
||||
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->inst_get('loggedInAs'));
|
||||
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->get('loggedInAs'));
|
||||
|
||||
$this->logOut();
|
||||
|
||||
@ -412,7 +416,7 @@ class SecurityTest extends FunctionalTest
|
||||
);
|
||||
$this->assertEquals(
|
||||
$this->idFromFixture(Member::class, 'expiredpassword'),
|
||||
$this->session()->inst_get('loggedInAs')
|
||||
$this->session()->get('loggedInAs')
|
||||
);
|
||||
|
||||
// Make sure it redirects correctly after the password has been changed
|
||||
@ -427,7 +431,7 @@ class SecurityTest extends FunctionalTest
|
||||
|
||||
public function testChangePasswordForLoggedInUsers()
|
||||
{
|
||||
$goodResponse = $this->doTestLoginForm('testuser@example.com', '1nitialPassword');
|
||||
$this->doTestLoginForm('testuser@example.com', '1nitialPassword');
|
||||
|
||||
// Change the password
|
||||
$this->get('Security/changepassword?BackURL=test/back');
|
||||
@ -437,7 +441,7 @@ class SecurityTest extends FunctionalTest
|
||||
Controller::join_links(Director::absoluteBaseURL(), 'test/back'),
|
||||
$changedResponse->getHeader('Location')
|
||||
);
|
||||
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->inst_get('loggedInAs'));
|
||||
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->get('loggedInAs'));
|
||||
|
||||
// Check if we can login with the new password
|
||||
$this->logOut();
|
||||
@ -447,11 +451,12 @@ class SecurityTest extends FunctionalTest
|
||||
Controller::join_links(Director::absoluteBaseURL(), 'test/link'),
|
||||
$goodResponse->getHeader('Location')
|
||||
);
|
||||
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->inst_get('loggedInAs'));
|
||||
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->get('loggedInAs'));
|
||||
}
|
||||
|
||||
public function testChangePasswordFromLostPassword()
|
||||
{
|
||||
/** @var Member $admin */
|
||||
$admin = $this->objFromFixture(Member::class, 'test');
|
||||
$admin->FailedLoginCount = 99;
|
||||
$admin->LockedOutUntil = DBDatetime::now()->getValue();
|
||||
@ -460,8 +465,8 @@ class SecurityTest extends FunctionalTest
|
||||
$this->assertNull($admin->AutoLoginHash, 'Hash is empty before lost password');
|
||||
|
||||
// Request new password by email
|
||||
$response = $this->get('Security/lostpassword');
|
||||
$response = $this->post('Security/lostpassword/LostPasswordForm', array('Email' => 'testuser@example.com'));
|
||||
$this->get('Security/lostpassword');
|
||||
$this->post('Security/lostpassword/LostPasswordForm', array('Email' => 'testuser@example.com'));
|
||||
|
||||
$this->assertEmailSent('testuser@example.com');
|
||||
|
||||
@ -481,15 +486,15 @@ class SecurityTest extends FunctionalTest
|
||||
);
|
||||
|
||||
// Follow redirection to form without hash in GET parameter
|
||||
$response = $this->get('Security/changepassword');
|
||||
$changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword');
|
||||
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->inst_get('loggedInAs'));
|
||||
$this->get('Security/changepassword');
|
||||
$this->doTestChangepasswordForm('1nitialPassword', 'changedPassword');
|
||||
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->get('loggedInAs'));
|
||||
|
||||
// Check if we can login with the new password
|
||||
$this->logOut();
|
||||
$goodResponse = $this->doTestLoginForm('testuser@example.com', 'changedPassword');
|
||||
$this->assertEquals(302, $goodResponse->getStatusCode());
|
||||
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->inst_get('loggedInAs'));
|
||||
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->get('loggedInAs'));
|
||||
|
||||
$admin = DataObject::get_by_id(Member::class, $admin->ID, false);
|
||||
$this->assertNull($admin->LockedOutUntil);
|
||||
@ -498,18 +503,17 @@ class SecurityTest extends FunctionalTest
|
||||
|
||||
public function testRepeatedLoginAttemptsLockingPeopleOut()
|
||||
{
|
||||
$local = i18n::get_locale();
|
||||
i18n::set_locale('en_US');
|
||||
|
||||
Member::config()->lock_out_after_incorrect_logins = 5;
|
||||
Member::config()->lock_out_delay_mins = 15;
|
||||
Member::config()->set('lock_out_after_incorrect_logins', 5);
|
||||
Member::config()->set('lock_out_delay_mins', 15);
|
||||
|
||||
// Login with a wrong password for more than the defined threshold
|
||||
for ($i = 1; $i <= (Member::config()->lock_out_after_incorrect_logins+1); $i++) {
|
||||
for ($i = 1; $i <= 6; $i++) {
|
||||
$this->doTestLoginForm('testuser@example.com', 'incorrectpassword');
|
||||
/** @var Member $member */
|
||||
$member = DataObject::get_by_id(Member::class, $this->idFromFixture(Member::class, 'test'));
|
||||
|
||||
if ($i < Member::config()->get('lock_out_after_incorrect_logins')) {
|
||||
if ($i < 5) {
|
||||
$this->assertNull(
|
||||
$member->LockedOutUntil,
|
||||
'User does not have a lockout time set if under threshold for failed attempts'
|
||||
@ -534,14 +538,14 @@ class SecurityTest extends FunctionalTest
|
||||
'Your account has been temporarily disabled because of too many failed attempts at ' .
|
||||
'logging in. Please try again in {count} minutes.',
|
||||
null,
|
||||
array('count' => Member::config()->lock_out_delay_mins)
|
||||
array('count' => 15)
|
||||
);
|
||||
$this->assertHasMessage($msg);
|
||||
|
||||
|
||||
$this->doTestLoginForm('testuser@example.com', '1nitialPassword');
|
||||
$this->assertNull(
|
||||
$this->session()->inst_get('loggedInAs'),
|
||||
$this->session()->get('loggedInAs'),
|
||||
'The user can\'t log in after being locked out, even with the right password'
|
||||
);
|
||||
|
||||
@ -551,7 +555,7 @@ class SecurityTest extends FunctionalTest
|
||||
$member->write();
|
||||
$this->doTestLoginForm('testuser@example.com', '1nitialPassword');
|
||||
$this->assertEquals(
|
||||
$this->session()->inst_get('loggedInAs'),
|
||||
$this->session()->get('loggedInAs'),
|
||||
$member->ID,
|
||||
'After lockout expires, the user can login again'
|
||||
);
|
||||
@ -560,10 +564,10 @@ class SecurityTest extends FunctionalTest
|
||||
$this->logOut();
|
||||
|
||||
// Login again with wrong password, but less attempts than threshold
|
||||
for ($i = 1; $i < Member::config()->lock_out_after_incorrect_logins; $i++) {
|
||||
for ($i = 1; $i < 5; $i++) {
|
||||
$this->doTestLoginForm('testuser@example.com', 'incorrectpassword');
|
||||
}
|
||||
$this->assertNull($this->session()->inst_get('loggedInAs'));
|
||||
$this->assertNull($this->session()->get('loggedInAs'));
|
||||
$this->assertHasMessage(
|
||||
_t('SilverStripe\\Security\\Member.ERRORWRONGCRED', 'The provided details don\'t seem to be correct. Please try again.'),
|
||||
'The user can retry with a wrong password after the lockout expires'
|
||||
@ -571,17 +575,15 @@ class SecurityTest extends FunctionalTest
|
||||
|
||||
$this->doTestLoginForm('testuser@example.com', '1nitialPassword');
|
||||
$this->assertEquals(
|
||||
$this->session()->inst_get('loggedInAs'),
|
||||
$this->session()->get('loggedInAs'),
|
||||
$member->ID,
|
||||
'The user can login successfully after lockout expires, if staying below the threshold'
|
||||
);
|
||||
|
||||
i18n::set_locale($local);
|
||||
}
|
||||
|
||||
public function testAlternatingRepeatedLoginAttempts()
|
||||
{
|
||||
Member::config()->lock_out_after_incorrect_logins = 3;
|
||||
Member::config()->set('lock_out_after_incorrect_logins', 3);
|
||||
|
||||
// ATTEMPTING LOG-IN TWICE WITH ONE ACCOUNT AND TWICE WITH ANOTHER SHOULDN'T LOCK ANYBODY OUT
|
||||
|
||||
@ -591,7 +593,9 @@ class SecurityTest extends FunctionalTest
|
||||
$this->doTestLoginForm('noexpiry@silverstripe.com', 'incorrectpassword');
|
||||
$this->doTestLoginForm('noexpiry@silverstripe.com', 'incorrectpassword');
|
||||
|
||||
/** @var Member $member1 */
|
||||
$member1 = DataObject::get_by_id(Member::class, $this->idFromFixture(Member::class, 'test'));
|
||||
/** @var Member $member2 */
|
||||
$member2 = DataObject::get_by_id(Member::class, $this->idFromFixture(Member::class, 'noexpiry'));
|
||||
|
||||
$this->assertNull($member1->LockedOutUntil);
|
||||
@ -611,17 +615,18 @@ class SecurityTest extends FunctionalTest
|
||||
|
||||
public function testUnsuccessfulLoginAttempts()
|
||||
{
|
||||
Security::config()->login_recording = true;
|
||||
Security::config()->set('login_recording', true);
|
||||
|
||||
/* UNSUCCESSFUL ATTEMPTS WITH WRONG PASSWORD FOR EXISTING USER ARE LOGGED */
|
||||
$this->doTestLoginForm('testuser@example.com', 'wrongpassword');
|
||||
/** @var LoginAttempt $attempt */
|
||||
$attempt = DataObject::get_one(
|
||||
LoginAttempt::class,
|
||||
array(
|
||||
'"LoginAttempt"."Email"' => 'testuser@example.com'
|
||||
)
|
||||
);
|
||||
$this->assertTrue(is_object($attempt));
|
||||
$this->assertInstanceOf(LoginAttempt::class, $attempt);
|
||||
$member = DataObject::get_one(
|
||||
Member::class,
|
||||
array(
|
||||
@ -648,16 +653,18 @@ class SecurityTest extends FunctionalTest
|
||||
|
||||
public function testSuccessfulLoginAttempts()
|
||||
{
|
||||
Security::config()->login_recording = true;
|
||||
Security::config()->set('login_recording', true);
|
||||
|
||||
/* SUCCESSFUL ATTEMPTS ARE LOGGED */
|
||||
$this->doTestLoginForm('testuser@example.com', '1nitialPassword');
|
||||
/** @var LoginAttempt $attempt */
|
||||
$attempt = DataObject::get_one(
|
||||
LoginAttempt::class,
|
||||
array(
|
||||
'"LoginAttempt"."Email"' => 'testuser@example.com'
|
||||
)
|
||||
);
|
||||
/** @var Member $member */
|
||||
$member = DataObject::get_one(
|
||||
Member::class,
|
||||
array(
|
||||
@ -700,7 +707,7 @@ class SecurityTest extends FunctionalTest
|
||||
|
||||
public function testDoNotSendEmptyRobotsHeaderIfNotDefined()
|
||||
{
|
||||
Config::inst()->remove(Security::class, 'robots_tag');
|
||||
Config::modify()->remove(Security::class, 'robots_tag');
|
||||
$response = $this->get(Config::inst()->get(Security::class, 'login_url'));
|
||||
$robotsHeader = $response->getHeader('X-Robots-Tag');
|
||||
$this->assertNull($robotsHeader);
|
||||
@ -709,11 +716,16 @@ class SecurityTest extends FunctionalTest
|
||||
/**
|
||||
* Execute a log-in form using Director::test().
|
||||
* Helper method for the tests above
|
||||
*
|
||||
* @param string $email
|
||||
* @param string $password
|
||||
* @param string $backURL
|
||||
* @return HTTPResponse
|
||||
*/
|
||||
public function doTestLoginForm($email, $password, $backURL = 'test/link')
|
||||
{
|
||||
$this->get(Config::inst()->get(Security::class, 'logout_url'));
|
||||
$this->session()->inst_set('BackURL', $backURL);
|
||||
$this->session()->set('BackURL', $backURL);
|
||||
$this->get(Config::inst()->get(Security::class, 'login_url'));
|
||||
|
||||
return $this->submitForm(
|
||||
@ -730,6 +742,10 @@ class SecurityTest extends FunctionalTest
|
||||
|
||||
/**
|
||||
* Helper method to execute a change password form
|
||||
*
|
||||
* @param string $oldPassword
|
||||
* @param string $newPassword
|
||||
* @return HTTPResponse
|
||||
*/
|
||||
public function doTestChangepasswordForm($oldPassword, $newPassword)
|
||||
{
|
||||
@ -771,7 +787,7 @@ class SecurityTest extends FunctionalTest
|
||||
*/
|
||||
protected function getValidationResult()
|
||||
{
|
||||
$result = $this->session()->inst_get('FormInfo.MemberLoginForm_LoginForm.result');
|
||||
$result = $this->session()->get('FormInfo.MemberLoginForm_LoginForm.result');
|
||||
if ($result) {
|
||||
return unserialize($result);
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ class RequirementsTest extends SapphireTest
|
||||
$backend->clearCombinedFiles();
|
||||
$backend->setCombinedFilesFolder('_combinedfiles');
|
||||
$backend->setMinifyCombinedFiles(false);
|
||||
$backend->setCombinedFilesEnabled(true);
|
||||
Requirements::flush();
|
||||
}
|
||||
|
||||
@ -193,6 +194,7 @@ class RequirementsTest extends SapphireTest
|
||||
{
|
||||
/** @var Requirements_Backend $backend */
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$backend->setCombinedFilesEnabled(true);
|
||||
$this->setupCombinedRequirements($backend);
|
||||
|
||||
$combinedFileName = '/_combinedfiles/RequirementsTest_bc-2a55d56.js';
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace SilverStripe\View\Tests;
|
||||
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\TempFolder;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
@ -19,7 +20,7 @@ class SSViewerCacheBlockTest extends SapphireTest
|
||||
SSViewerCacheBlockTest\TestModel::class
|
||||
);
|
||||
|
||||
protected static function getExtraDataObjects()
|
||||
public static function getExtraDataObjects()
|
||||
{
|
||||
$classes = parent::getExtraDataObjects();
|
||||
|
||||
@ -41,7 +42,7 @@ class SSViewerCacheBlockTest extends SapphireTest
|
||||
|
||||
$cache = null;
|
||||
if ($cacheOn) {
|
||||
$cache = new FilesystemCache('cacheblock', 0, getTempFolder()); // cache indefinitely
|
||||
$cache = new FilesystemCache('cacheblock', 0, TempFolder::getTempFolder(BASE_PATH)); // cache indefinitely
|
||||
} else {
|
||||
$cache = new NullCache();
|
||||
}
|
||||
|
@ -2,35 +2,35 @@
|
||||
|
||||
namespace SilverStripe\View\Tests;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit_Framework_MockObject_MockObject;
|
||||
use SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore;
|
||||
use SilverStripe\Control\ContentNegotiator;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\ContentNegotiator;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\ORM\PaginatedList;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Permission;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Security\SecurityToken;
|
||||
use SilverStripe\Security\Permission;
|
||||
use SilverStripe\View\ArrayData;
|
||||
use SilverStripe\View\Requirements;
|
||||
use SilverStripe\View\Requirements_Backend;
|
||||
use SilverStripe\View\Requirements_Minifier;
|
||||
use SilverStripe\View\SSTemplateParser;
|
||||
use SilverStripe\View\SSViewer;
|
||||
use SilverStripe\View\Requirements;
|
||||
use SilverStripe\View\SSViewer_FromString;
|
||||
use SilverStripe\View\Tests\SSViewerTest\SSViewerTestModel;
|
||||
use SilverStripe\View\Tests\SSViewerTest\SSViewerTestModelController;
|
||||
use SilverStripe\View\ViewableData;
|
||||
use SilverStripe\View\SSViewer_FromString;
|
||||
use SilverStripe\View\SSTemplateParser;
|
||||
use SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore;
|
||||
use Exception;
|
||||
|
||||
class SSViewerTest extends SapphireTest
|
||||
{
|
||||
@ -213,8 +213,11 @@ class SSViewerTest extends SapphireTest
|
||||
|
||||
public function testRequirementsCombine()
|
||||
{
|
||||
/** @var Requirements_Backend $testBackend */
|
||||
$testBackend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$testBackend->setSuffixRequirements(false);
|
||||
$testBackend->setCombinedFilesEnabled(true);
|
||||
|
||||
//$combinedTestFilePath = BASE_PATH . '/' . $testBackend->getCombinedFilesFolder() . '/testRequirementsCombine.js';
|
||||
|
||||
$jsFile = $this->getCurrentRelativePath() . '/SSViewerTest/javascript/bad.js';
|
||||
@ -237,9 +240,12 @@ class SSViewerTest extends SapphireTest
|
||||
|
||||
public function testRequirementsMinification()
|
||||
{
|
||||
/** @var Requirements_Backend $testBackend */
|
||||
$testBackend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$testBackend->setSuffixRequirements(false);
|
||||
$testBackend->setMinifyCombinedFiles(true);
|
||||
$testBackend->setCombinedFilesEnabled(true);
|
||||
|
||||
$testFile = $this->getCurrentRelativePath() . '/SSViewerTest/javascript/RequirementsTest_a.js';
|
||||
$testFileContent = file_get_contents($testFile);
|
||||
|
||||
@ -263,10 +269,8 @@ class SSViewerTest extends SapphireTest
|
||||
->method('minify');
|
||||
$testBackend->processCombinedFiles();
|
||||
|
||||
$this->setExpectedExceptionRegExp(
|
||||
Exception::class,
|
||||
'/minification service/'
|
||||
);
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessageRegExp('/^Cannot minify files without a minification service defined./');
|
||||
|
||||
$testBackend->setMinifyCombinedFiles(true);
|
||||
$testBackend->setMinifier(null);
|
||||
@ -1614,8 +1618,8 @@ after'
|
||||
);
|
||||
|
||||
// Let's throw something random in there.
|
||||
$this->setExpectedException('InvalidArgumentException');
|
||||
SSViewer::get_templates_by_class(array());
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
SSViewer::get_templates_by_class(null);
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -1732,7 +1736,6 @@ EOC;
|
||||
|
||||
public function testRenderWithSourceFileComments()
|
||||
{
|
||||
Director::set_environment_type('dev');
|
||||
SSViewer::config()->update('source_file_comments', true);
|
||||
$i = __DIR__ . '/SSViewerTest/templates/Includes';
|
||||
$f = __DIR__ . '/SSViewerTest/templates/SSViewerTestComments';
|
||||
@ -1853,13 +1856,13 @@ EOC;
|
||||
|
||||
Requirements::set_backend($backend);
|
||||
|
||||
$this->assertEquals(1, substr_count($template->process(array()), "a.css"));
|
||||
$this->assertEquals(1, substr_count($template->process(array()), "b.css"));
|
||||
$this->assertEquals(1, substr_count($template->process(new ViewableData()), "a.css"));
|
||||
$this->assertEquals(1, substr_count($template->process(new ViewableData()), "b.css"));
|
||||
|
||||
// if we disable the requirements then we should get nothing
|
||||
$template->includeRequirements(false);
|
||||
$this->assertEquals(0, substr_count($template->process(array()), "a.css"));
|
||||
$this->assertEquals(0, substr_count($template->process(array()), "b.css"));
|
||||
$this->assertEquals(0, substr_count($template->process(new ViewableData()), "a.css"));
|
||||
$this->assertEquals(0, substr_count($template->process(new ViewableData()), "b.css"));
|
||||
}
|
||||
|
||||
public function testRequireCallInTemplateInclude()
|
||||
@ -1873,7 +1876,7 @@ EOC;
|
||||
$this->assertEquals(
|
||||
1,
|
||||
substr_count(
|
||||
$template->process(array()),
|
||||
$template->process(new ViewableData()),
|
||||
"tests/php/View/SSViewerTest/javascript/RequirementsTest_a.js"
|
||||
)
|
||||
);
|
||||
|
@ -5,6 +5,10 @@ namespace SilverStripe\View\Tests\SSViewerTest;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\View\ViewableData;
|
||||
|
||||
/**
|
||||
* @property string $TextValue
|
||||
* @property string $HTMLValue
|
||||
*/
|
||||
class TestViewableData extends ViewableData implements TestOnly
|
||||
{
|
||||
|
||||
|
@ -50,7 +50,7 @@ trait i18nTestManifest
|
||||
*/
|
||||
protected $moduleManifests = 0;
|
||||
|
||||
protected static function getExtraDataObjects()
|
||||
public static function getExtraDataObjects()
|
||||
{
|
||||
return [
|
||||
TestDataObject::class,
|
||||
@ -82,7 +82,8 @@ trait i18nTestManifest
|
||||
Director::config()->update('alternate_base_folder', $this->alternateBasePath);
|
||||
|
||||
// New module manifest
|
||||
$moduleManifest = new ModuleManifest($this->alternateBasePath, false);
|
||||
$moduleManifest = new ModuleManifest($this->alternateBasePath);
|
||||
$moduleManifest->init(true);
|
||||
$this->pushModuleManifest($moduleManifest);
|
||||
|
||||
// Replace old template loader with new one with alternate base path
|
||||
@ -90,8 +91,9 @@ trait i18nTestManifest
|
||||
ThemeResourceLoader::set_instance($loader = new ThemeResourceLoader($this->alternateBasePath));
|
||||
$loader->addSet(
|
||||
'$default',
|
||||
new ThemeManifest($this->alternateBasePath, project(), false)
|
||||
$default = new ThemeManifest($this->alternateBasePath, project())
|
||||
);
|
||||
$default->init(true);
|
||||
|
||||
SSViewer::set_themes([
|
||||
'testtheme1',
|
||||
@ -102,7 +104,8 @@ trait i18nTestManifest
|
||||
i18n::set_locale('en_US');
|
||||
|
||||
// Set new manifest against the root
|
||||
$classManifest = new ClassManifest($this->alternateBasePath, true);
|
||||
$classManifest = new ClassManifest($this->alternateBasePath);
|
||||
$classManifest->init(true);
|
||||
$this->pushManifest($classManifest);
|
||||
|
||||
// Setup uncached translator
|
||||
|
Loading…
Reference in New Issue
Block a user