API Implement APP object

API Refactor of Session
This commit is contained in:
Damian Mooyman 2017-06-07 18:07:33 +12:00
parent 306d801258
commit de079c041d
71 changed files with 2083 additions and 1671 deletions

View File

@ -1,8 +1,7 @@
<?php <?php
use SilverStripe\ORM\DB;
use SilverStripe\ORM\DataModel;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\ORM\DB;
/** /**
@ -145,8 +144,6 @@ if(!$url) {
$_SERVER['REQUEST_URI'] = BASE_URL . '/' . $url; $_SERVER['REQUEST_URI'] = BASE_URL . '/' . $url;
// Direct away - this is the "main" function, that hands control to the apporopriate controller Director::direct($url);
DataModel::set_inst(new DataModel());
Director::direct($url, DataModel::inst());

View File

@ -82,12 +82,12 @@
"SilverStripe\\Framework\\Tests\\Behaviour\\": "tests/behat/src/" "SilverStripe\\Framework\\Tests\\Behaviour\\": "tests/behat/src/"
}, },
"files": [ "files": [
"src/Core/Constants.php", "src/includes/constants.php"
"src/Dev/PhpUnitShim.php"
] ]
}, },
"include-path": [ "include-path": [
"src/", "src/",
"src/includes/",
"thirdparty/" "thirdparty/"
], ],
"scripts": { "scripts": {

View File

@ -1335,6 +1335,7 @@ After (`mysite/_config/config.yml`):
* `findAnAdministrator` use `DefaultAdminService::findOrCreateDefaultAdmin()` instead * `findAnAdministrator` use `DefaultAdminService::findOrCreateDefaultAdmin()` instead
* `Member` methods deprecated: * `Member` methods deprecated:
* `checkPassword`. Use Authenticator::checkPassword() instead * `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 #### <a name="overview-general-removed"></a>General and Core Removed API
@ -1523,6 +1524,7 @@ The below methods have been added or had their functionality updated to `DBDate`
#### <a name="overview-orm-removed"></a>ORM Removed API #### <a name="overview-orm-removed"></a>ORM Removed API
* `DataModel` removed
* `DataObject::can*` methods no longer accept a member ID. These must now be passed a Member object or left null * `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::db` removed and replaced with `DataObjectSchema::fieldSpec` and `DataObjectSchema::fieldSpecs`
* `DataObject::manyManyComponent` moved to `DataObjectSchema` * `DataObject::manyManyComponent` moved to `DataObjectSchema`

228
main.php
View File

@ -1,226 +1,26 @@
<?php <?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;
/************************************************************************************ /************************************************************************************
************************************************************************************ ************************************************************************************
** ** ** **
** If you can read this text in your browser then you don't have PHP installed. ** ** If you can read this text in your browser then you don't have PHP installed. **
** Please install PHP 5.5.0 or higher . ** ** Please install PHP 5.6.0 or higher **
** ** ** **
************************************************************************************ ************************************************************************************
************************************************************************************/ ************************************************************************************/
if (version_compare(phpversion(), '5.5.0', '<')) { use SilverStripe\Core\AppKernel;
header($_SERVER['SERVER_PROTOCOL'] . " 500 Server Error"); use SilverStripe\Core\HTTPApplication;
echo str_replace('$PHPVersion', phpversion(), file_get_contents("Dev/Install/php5-required.html")); use SilverStripe\Core\Startup\ErrorControlChainMiddleware;
die(); use SilverStripe\Core\Startup\OutputMiddleware;
} use SilverStripe\Control\HTTPRequest;
/** require __DIR__ . '/src/includes/autoload.php';
* 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 // Default application
if(!class_exists('Composer\\Autoload\\ClassLoader', false)) { $request = HTTPRequest::createFromEnvironment();
if (file_exists($autoloadPath = dirname(__DIR__) . '/vendor/autoload.php')) { $kernel = new AppKernel();
require_once $autoloadPath; $app = new HTTPApplication($kernel);
} $app->addMiddleware(new OutputMiddleware());
else { $app->addMiddleware(new ErrorControlChainMiddleware($app, $request));
if (!headers_sent()) { $app->handle($request);
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());

View File

@ -1,9 +0,0 @@
<?php
/**
* @package framework
* @subpackage core
*
* Alternative main.php file for servers that need the php5 extension
*/
include("main.php");
?>

View File

@ -3,9 +3,7 @@
namespace SilverStripe\Control; namespace SilverStripe\Control;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\Debug; use SilverStripe\Dev\Debug;
use SilverStripe\ORM\DataModel;
use SilverStripe\ORM\FieldType\DBHTMLText; use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\Security\BasicAuth; use SilverStripe\Security\BasicAuth;
use SilverStripe\Security\Member; use SilverStripe\Security\Member;
@ -47,13 +45,6 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
*/ */
protected $action; 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. * Stack of current controllers. Controller::$controller_stack[0] is the current controller.
* *
@ -152,16 +143,14 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
* @todo setDataModel and setRequest are redundantly called in parent::handleRequest() - sort this out * @todo setDataModel and setRequest are redundantly called in parent::handleRequest() - sort this out
* *
* @param HTTPRequest $request * @param HTTPRequest $request
* @param DataModel $model
*/ */
protected function beforeHandleRequest(HTTPRequest $request, DataModel $model) protected function beforeHandleRequest(HTTPRequest $request)
{ {
//Push the current controller to protect against weird session issues //Push the current controller to protect against weird session issues
$this->pushCurrent(); $this->pushCurrent();
//Set up the internal dependencies (request, response, datamodel) //Set up the internal dependencies (request, response)
$this->setRequest($request); $this->setRequest($request);
$this->setResponse(new HTTPResponse()); $this->setResponse(new HTTPResponse());
$this->setDataModel($model);
//kick off the init functionality //kick off the init functionality
$this->doInit(); $this->doInit();
} }
@ -192,24 +181,22 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
* and end the method with $this->afterHandleRequest() * and end the method with $this->afterHandleRequest()
* *
* @param HTTPRequest $request * @param HTTPRequest $request
* @param DataModel $model
*
* @return HTTPResponse * @return HTTPResponse
*/ */
public function handleRequest(HTTPRequest $request, DataModel $model) public function handleRequest(HTTPRequest $request)
{ {
if (!$request) { if (!$request) {
user_error("Controller::handleRequest() not passed a request!", E_USER_ERROR); user_error("Controller::handleRequest() not passed a request!", E_USER_ERROR);
} }
//set up the controller for the incoming request //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 //if the before handler manipulated the response in a way that we shouldn't proceed, then skip our request
// handling // handling
if (!$this->getResponse()->isFinished()) { if (!$this->getResponse()->isFinished()) {
//retrieve the response for the request //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) //prepare the response (we can receive an assortment of response types (strings/objects/HTTPResponses)
$this->prepareResponse($response); $this->prepareResponse($response);
@ -597,14 +584,6 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
public function pushCurrent() public function pushCurrent()
{ {
array_unshift(self::$controller_stack, $this); 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 +632,6 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
return $this->getResponse() && $this->getResponse()->getHeader('Location'); 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 * 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, * for building the results of {@link Link()} methods. If either of the links have query strings,

View File

@ -2,15 +2,14 @@
namespace SilverStripe\Control; namespace SilverStripe\Control;
use InvalidArgumentException;
use SilverStripe\CMS\Model\SiteTree; use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Kernel;
use SilverStripe\Dev\Deprecation; use SilverStripe\Dev\Deprecation;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\ArrayLib; use SilverStripe\ORM\ArrayLib;
use SilverStripe\ORM\DataModel;
use SilverStripe\Versioned\Versioned; use SilverStripe\Versioned\Versioned;
use SilverStripe\View\Requirements; use SilverStripe\View\Requirements;
use SilverStripe\View\Requirements_Backend; use SilverStripe\View\Requirements_Backend;
@ -120,112 +119,41 @@ class Director implements TemplateGlobalProvider
* *
* @uses handleRequest() rule-lookup logic is handled by this. * @uses handleRequest() rule-lookup logic is handled by this.
* @uses TestController::handleRequest() This handles the page logic for a Director::direct() call. * @uses TestController::handleRequest() This handles the page logic for a Director::direct() call.
* @param string $url * @param HTTPRequest $request
* @param DataModel $model * @return HTTPResponse
* @throws HTTPResponse_Exception * @throws HTTPResponse_Exception
*/ */
public static function direct($url, DataModel $model) public static function direct(HTTPRequest $request)
{ {
// check allowed hosts // Pre-request
if (getenv('SS_ALLOWED_HOSTS') && !Director::is_cli()) { $output = RequestProcessor::singleton()->preRequest($request);
$all_allowed_hosts = explode(',', getenv('SS_ALLOWED_HOSTS'));
if (!in_array(static::host(), $all_allowed_hosts)) {
throw new HTTPResponse_Exception('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);
if ($output === false) { if ($output === false) {
// @TODO Need to NOT proceed with the request in an elegant manner return new HTTPResponse(_t(__CLASS__.'.INVALID_REQUEST', 'Invalid request'), 400);
throw new HTTPResponse_Exception(_t('SilverStripe\\Control\\Director.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. // Save session data. Note that inst_save() will start/resume the session if required.
$session->inst_save(); $request->getSession()->save();
// Return code for a redirection request // Return code for a redirection request
if (is_string($result) && substr($result, 0, 9) == 'redirect:') { // @todo: Refactor into CLIApplication
$url = substr($result, 9); if ($result->isRedirect() && static::is_cli()) {
$url = Director::makeRelative($result->getHeader('Location'));
if (Director::is_cli()) { $request = clone $request;
// on cli, follow SilverStripe redirects automatically $request->setUrl($url);
Director::direct( return static::direct($request);
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);
}
$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();
} }
// Post-request handling
$postRequest = RequestProcessor::singleton()->postRequest($request, $result);
if ($postRequest === false) {
return new HTTPResponse(_t(__CLASS__ . '.REQUEST_ABORTED', 'Request aborted'), 500);
}
// Return
return $result;
} }
/** /**
@ -278,7 +206,7 @@ class Director implements TemplateGlobalProvider
} }
if (!$session) { if (!$session) {
$session = Session::create([]); $session = new Session([]);
} }
$cookieJar = $cookies instanceof Cookie_Backend $cookieJar = $cookies instanceof Cookie_Backend
? $cookies ? $cookies
@ -340,15 +268,14 @@ class Director implements TemplateGlobalProvider
try { try {
// Pre-request filtering // Pre-request filtering
$model = DataModel::inst();
$requestProcessor = Injector::inst()->get(RequestProcessor::class); $requestProcessor = Injector::inst()->get(RequestProcessor::class);
$output = $requestProcessor->preRequest($request, $session, $model); $output = $requestProcessor->preRequest($request);
if ($output === false) { if ($output === false) {
throw new HTTPResponse_Exception(_t('SilverStripe\\Control\\Director.INVALID_REQUEST', 'Invalid request'), 400); throw new HTTPResponse_Exception(_t('SilverStripe\\Control\\Director.INVALID_REQUEST', 'Invalid request'), 400);
} }
// Process request // Process request
$result = Director::handleRequest($request, $session, $model); $result = Director::handleRequest($request);
// Ensure that the result is an HTTPResponse object // Ensure that the result is an HTTPResponse object
if (is_string($result)) { if (is_string($result)) {
@ -395,15 +322,14 @@ class Director implements TemplateGlobalProvider
* *
* @skipUpgrade * @skipUpgrade
* @param HTTPRequest $request * @param HTTPRequest $request
* @param Session $session * @return HTTPResponse
* @param DataModel $model
* @return HTTPResponse|string
*/ */
protected static function handleRequest(HTTPRequest $request, Session $session, DataModel $model) protected static function handleRequest(HTTPRequest $request)
{ {
$rules = Director::config()->uninherited('rules'); $rules = Director::config()->uninherited('rules');
foreach ($rules as $pattern => $controllerOptions) { foreach ($rules as $pattern => $controllerOptions) {
// Normalise route rule
if (is_string($controllerOptions)) { if (is_string($controllerOptions)) {
if (substr($controllerOptions, 0, 2) == '->') { if (substr($controllerOptions, 0, 2) == '->') {
$controllerOptions = array('Redirect' => substr($controllerOptions, 2)); $controllerOptions = array('Redirect' => substr($controllerOptions, 2));
@ -412,7 +338,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); $request->setRouteParams($controllerOptions);
// controllerOptions provide some default arguments // controllerOptions provide some default arguments
$arguments = array_merge($controllerOptions, $arguments); $arguments = array_merge($controllerOptions, $arguments);
@ -424,24 +352,20 @@ class Director implements TemplateGlobalProvider
// Handle redirection // Handle redirection
if (isset($arguments['Redirect'])) { if (isset($arguments['Redirect'])) {
return "redirect:" . Director::absoluteURL($arguments['Redirect'], true); // Redirection
} else { $response = new HTTPResponse();
// Find the controller name $response->redirect(static::absoluteURL($arguments['Redirect']));
$controller = $arguments['Controller']; return $response;
$controllerObj = Injector::inst()->create($controller); }
$controllerObj->setSession($session);
try { // Find the controller name
$result = $controllerObj->handleRequest($request, $model); $controller = $arguments['Controller'];
} catch (HTTPResponse_Exception $responseException) { $controllerObj = Injector::inst()->create($controller);
$result = $responseException->getResponse();
}
if (!is_object($result) || $result instanceof HTTPResponse) {
return $result;
}
user_error("Bad result from url " . $request->getURL() . " handled by " . try {
get_class($controllerObj)." controller: ".get_class($result), E_USER_WARNING); return $controllerObj->handleRequest($request);
} catch (HTTPResponse_Exception $responseException) {
return $responseException->getResponse();
} }
} }
} }
@ -852,36 +776,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. * Given a filesystem reference relative to the site root, return the full file-system path.
* *
@ -1073,47 +967,7 @@ class Director implements TemplateGlobalProvider
*/ */
public static function is_cli() public static function is_cli()
{ {
return (php_sapi_name() == "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;
} }
/** /**
@ -1124,22 +978,9 @@ class Director implements TemplateGlobalProvider
*/ */
public static function get_environment_type() public static function get_environment_type()
{ {
// Check saved session /** @var Kernel $kernel */
if ($env = self::session_environment()) { $kernel = Injector::inst()->get(Kernel::class);
return $env; return $kernel->getEnvironment();
}
// Check set
if (self::$environment_type) {
return self::$environment_type;
}
// Check getenv
if ($env = getenv('SS_ENVIRONMENT_TYPE')) {
return $env;
}
return 'live';
} }
/** /**
@ -1175,37 +1016,6 @@ class Director implements TemplateGlobalProvider
return self::get_environment_type() === 'test'; 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 * Returns an array of strings of the method names of methods on the call that should be exposed
* as global variables in the templates. * as global variables in the templates.

View File

@ -96,6 +96,72 @@ class Email extends ViewableData
return \Swift_Validate::email($address); 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. * Encode an email-address to protect it from spambots.
* At the moment only simple string substitutions, * At the moment only simple string substitutions,

View File

@ -11,36 +11,29 @@ class SwiftPlugin implements \Swift_Events_SendListener
*/ */
public function beforeSendPerformed(\Swift_Events_SendEvent $evt) public function beforeSendPerformed(\Swift_Events_SendEvent $evt)
{ {
/** @var \Swift_Message $message */ /** @var \Swift_Message $message */
$message = $evt->getMessage(); $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)) { if (!empty($sendAllTo)) {
$this->setTo($message, $sendAllTo); $this->setTo($message, $sendAllTo);
} }
$ccAllTo = Email::getCCAllEmailsTo();
if (!empty($ccAllTo)) { if (!empty($ccAllTo)) {
if (!is_array($ccAllTo)) {
$ccAllTo = array($ccAllTo => null);
}
foreach ($ccAllTo as $address => $name) { foreach ($ccAllTo as $address => $name) {
$message->addCc($address, $name); $message->addCc($address, $name);
} }
} }
$bccAllTo = Email::getBCCAllEmailsTo();
if (!empty($bccAllTo)) { if (!empty($bccAllTo)) {
if (!is_array($bccAllTo)) {
$bccAllTo = array($bccAllTo => null);
}
foreach ($bccAllTo as $address => $name) { foreach ($bccAllTo as $address => $name) {
$message->addBcc($address, $name); $message->addBcc($address, $name);
} }
} }
$sendAllFrom = Email::getSendAllEmailsFrom();
if (!empty($sendAllFrom)) { if (!empty($sendAllFrom)) {
$this->setFrom($message, $sendAllFrom); $this->setFrom($message, $sendAllFrom);
} }
@ -48,7 +41,7 @@ class SwiftPlugin implements \Swift_Events_SendListener
/** /**
* @param \Swift_Mime_Message $message * @param \Swift_Mime_Message $message
* @param string $to * @param array|string $to
*/ */
protected function setTo($message, $to) protected function setTo($message, $to)
{ {
@ -70,7 +63,7 @@ class SwiftPlugin implements \Swift_Events_SendListener
/** /**
* @param \Swift_Mime_Message $message * @param \Swift_Mime_Message $message
* @param string $from * @param array|string $from
*/ */
protected function setFrom($message, $from) protected function setFrom($message, $from)
{ {

View File

@ -3,7 +3,6 @@
namespace SilverStripe\Control; namespace SilverStripe\Control;
use SilverStripe\Core\Flushable; use SilverStripe\Core\Flushable;
use SilverStripe\ORM\DataModel;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
/** /**
@ -11,37 +10,17 @@ use SilverStripe\Core\ClassInfo;
*/ */
class FlushRequestFilter implements RequestFilter class FlushRequestFilter implements RequestFilter
{ {
public function preRequest(HTTPRequest $request)
/**
* @inheritdoc
*
* @param HTTPRequest $request
* @param Session $session
* @param DataModel $model
*
* @return bool
*/
public function preRequest(HTTPRequest $request, Session $session, DataModel $model)
{ {
if (array_key_exists('flush', $request->getVars())) { if (array_key_exists('flush', $request->getVars())) {
foreach (ClassInfo::implementorsOf(Flushable::class) as $class) { foreach (ClassInfo::implementorsOf(Flushable::class) as $class) {
$class::flush(); $class::flush();
} }
} }
return true; return true;
} }
/** public function postRequest(HTTPRequest $request, HTTPResponse $response)
* @inheritdoc
*
* @param HTTPRequest $request
* @param HTTPResponse $response
* @param DataModel $model
*
* @return bool
*/
public function postRequest(HTTPRequest $request, HTTPResponse $response, DataModel $model)
{ {
return true; return true;
} }

View File

@ -125,6 +125,11 @@ class HTTPRequest implements ArrayAccess
*/ */
protected $unshiftedButParsedParts = 0; protected $unshiftedButParsedParts = 0;
/**
* @var Session
*/
protected $session;
/** /**
* Construct a HTTPRequest from a URL relative to the site root. * Construct a HTTPRequest from a URL relative to the site root.
* *
@ -138,12 +143,194 @@ class HTTPRequest implements ArrayAccess
{ {
$this->httpMethod = strtoupper(self::detect_method($httpMethod, $postVars)); $this->httpMethod = strtoupper(self::detect_method($httpMethod, $postVars));
$this->setUrl($url); $this->setUrl($url);
$this->getVars = (array) $getVars; $this->getVars = (array) $getVars;
$this->postVars = (array) $postVars; $this->postVars = (array) $postVars;
$this->body = $body; $this->body = $body;
} }
/**
* Create HTTPRequest instance from the current environment variables.
* May throw errors if request is invalid.
*
* @throws HTTPResponse_Exception
* @return static
*/
public static function createFromEnvironment()
{
// Health-check prior to creating environment
$variables = static::variablesFromEnvironment();
return self::createFromVariables($variables, @file_get_contents('php://input'));
}
/**
* 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;
}
/**
* Validate environment vars prior to building HTTPRequest
* Error conditions will raise HTTPResponse_Exceptions
*
* @throws HTTPResponse_Exception
* @return array
*/
protected static function variablesFromEnvironment()
{
// Validate $_FILES array before merging it with $_POST
foreach ($_FILES as $key => $value) {
if (is_array($value['tmp_name'])) {
$tempFiles = ArrayLib::array_values_recursive($value['tmp_name']);
} else {
$tempFiles = [ $value['tmp_name'] ];
}
foreach ($tempFiles as $tmpFile) {
if ($tmpFile && !is_uploaded_file($tmpFile)) {
throw new HTTPResponse_Exception(
"File upload '{$key}' doesn't appear to be a valid upload",
400
);
}
}
}
// Validate hostname
if (getenv('SS_ALLOWED_HOSTS') && !Director::is_cli()) {
$all_allowed_hosts = explode(',', getenv('SS_ALLOWED_HOSTS'));
if (!in_array(Director::host(), $all_allowed_hosts)) {
throw new HTTPResponse_Exception('Invalid Host', 400);
}
}
return [
'_SERVER' => $_SERVER,
'_GET' => $_GET,
'_POST' => $_POST,
'_FILES' => $_FILES,
'_SESSION' => isset($_SESSION) ? $_SESSION : null,
'_COOKIE' => $_COOKIE
];
}
/**
* Build HTTPRequest from given variables
*
* @param array $variables
* @param string $input Request body
* @return HTTPRequest
*/
public static function createFromVariables(array $variables, $input)
{
$variables = static::cleanEnvironment($variables);
// 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($variables['_SESSION']);
$request->setSession($session);
return $request;
}
/** /**
* Allow the setting of a URL * Allow the setting of a URL
* *
@ -435,21 +622,15 @@ class HTTPRequest implements ArrayAccess
return $this->requestVar($offset); return $this->requestVar($offset);
} }
/**
* @ignore
* @param string $offset
* @param mixed $value
*/
public function offsetSet($offset, $value) public function offsetSet($offset, $value)
{ {
$this->getVars[$offset] = $value;
} }
/**
* @ignore
* @param mixed $offset
*/
public function offsetUnset($offset) public function offsetUnset($offset)
{ {
unset($this->getVars[$offset]);
unset($this->postVars[$offset]);
} }
/** /**
@ -866,4 +1047,22 @@ class HTTPRequest implements ArrayAccess
return $origMethod; return $origMethod;
} }
} }
/**
* @return Session
*/
public function getSession()
{
return $this->session;
}
/**
* @param Session $session
* @return $this
*/
public function setSession(Session $session)
{
$this->session = $session;
return $this;
}
} }

View File

@ -2,8 +2,6 @@
namespace SilverStripe\Control; namespace SilverStripe\Control;
use SilverStripe\ORM\DataModel;
/** /**
* A request filter is an object that's executed before and after a * A request filter is an object that's executed before and after a
* request occurs. By returning 'false' from the preRequest method, * request occurs. By returning 'false' from the preRequest method,
@ -14,24 +12,20 @@ use SilverStripe\ORM\DataModel;
*/ */
interface RequestFilter interface RequestFilter
{ {
/** /**
* Filter executed before a request processes * Filter executed before a request processes
* *
* @param HTTPRequest $request Request container object * @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) * @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 * Filter executed AFTER a request
* *
* @param HTTPRequest $request Request container object * @param HTTPRequest $request Request container object
* @param HTTPResponse $response Response output object * @param HTTPResponse $response
* @param DataModel $model Current DataModel * @return bool Whether to continue processing other filters. Null or true will continue processing (optional)
* @return boolean 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);
} }

View File

@ -6,7 +6,6 @@ use InvalidArgumentException;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\Debug; use SilverStripe\Dev\Debug;
use SilverStripe\ORM\DataModel;
use SilverStripe\Security\Security; use SilverStripe\Security\Security;
use SilverStripe\Security\PermissionFailureException; use SilverStripe\Security\PermissionFailureException;
use SilverStripe\Security\Permission; use SilverStripe\Security\Permission;
@ -123,22 +122,9 @@ class RequestHandler extends ViewableData
$this->setRequest(new NullHTTPRequest()); $this->setRequest(new NullHTTPRequest());
// This will prevent bugs if setDataModel() isn't called.
$this->model = DataModel::inst();
parent::__construct(); parent::__construct();
} }
/**
* Set the DataModel for this request.
*
* @param DataModel $model
*/
public function setDataModel($model)
{
$this->model = $model;
}
/** /**
* Handles URL requests. * Handles URL requests.
* *
@ -156,10 +142,9 @@ class RequestHandler extends ViewableData
* customise the controller. * customise the controller.
* *
* @param HTTPRequest $request The object that is reponsible for distributing URL parsing * @param HTTPRequest $request The object that is reponsible for distributing URL parsing
* @param DataModel $model
* @return HTTPResponse|RequestHandler|string|array * @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 // $handlerClass is used to step up the class hierarchy to implement url_handlers inheritance
if ($this->brokenOnConstruct) { if ($this->brokenOnConstruct) {
@ -170,7 +155,6 @@ class RequestHandler extends ViewableData
} }
$this->setRequest($request); $this->setRequest($request);
$this->setDataModel($model);
$match = $this->findAction($request); $match = $this->findAction($request);
@ -237,7 +221,7 @@ class RequestHandler extends ViewableData
if ($result instanceof HasRequestHandler) { if ($result instanceof HasRequestHandler) {
$result = $result->getRequestHandler(); $result = $result->getRequestHandler();
} }
$returnValue = $result->handleRequest($request, $model); $returnValue = $result->handleRequest($request);
// Array results can be used to handle // Array results can be used to handle
if (is_array($returnValue)) { if (is_array($returnValue)) {

View File

@ -3,7 +3,6 @@
namespace SilverStripe\Control; namespace SilverStripe\Control;
use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Injector\Injectable;
use SilverStripe\ORM\DataModel;
/** /**
* Represents a request processer that delegates pre and post request handling to nested request filters * 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; $this->filters = $filters;
} }
public function preRequest(HTTPRequest $request, Session $session, DataModel $model) public function preRequest(HTTPRequest $request)
{ {
foreach ($this->filters as $filter) { foreach ($this->filters as $filter) {
$res = $filter->preRequest($request, $session, $model); $res = $filter->preRequest($request);
if ($res === false) { if ($res === false) {
return false; return false;
} }
@ -45,10 +44,10 @@ class RequestProcessor implements RequestFilter
return null; return null;
} }
public function postRequest(HTTPRequest $request, HTTPResponse $response, DataModel $model) public function postRequest(HTTPRequest $request, HTTPResponse $response)
{ {
foreach ($this->filters as $filter) { foreach ($this->filters as $filter) {
$res = $filter->postRequest($request, $response, $model); $res = $filter->postRequest($request, $response);
if ($res === false) { if ($res === false) {
return false; return false;
} }

View File

@ -2,10 +2,8 @@
namespace SilverStripe\Control; namespace SilverStripe\Control;
use SilverStripe\Core\Config\Config; use BadMethodCallException;
use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\Deprecation;
/** /**
* Handles all manipulation of the session. * Handles all manipulation of the session.
@ -89,7 +87,7 @@ use SilverStripe\Dev\Deprecation;
*/ */
class Session class Session
{ {
use Injectable; use Configurable;
/** /**
* Set session timeout in seconds. * Set session timeout in seconds.
@ -130,12 +128,23 @@ class Session
private static $cookie_secure = false; 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(); protected $changedData = array();
/**
* Get user agent for this request
*
* @return string
*/
protected function userAgent() protected function userAgent()
{ {
if (isset($_SERVER['HTTP_USER_AGENT'])) { 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. * 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) public function __construct($data)
{ {
if ($data instanceof Session) { if ($data instanceof Session) {
$data = $data->inst_getAll(); $data = $data->getAll();
} }
$this->data = $data; $this->data = $data;
}
/**
* Init this session instance before usage
*/
public function init()
{
if (!$this->isStarted()) {
$this->start();
}
// Funny business detected!
if (isset($this->data['HTTP_USER_AGENT'])) { if (isset($this->data['HTTP_USER_AGENT'])) {
if ($this->data['HTTP_USER_AGENT'] != $this->userAgent()) { if ($this->data['HTTP_USER_AGENT'] !== $this->userAgent()) {
// Funny business detected! $this->clearAll();
$this->inst_clearAll(); $this->destroy();
$this->inst_destroy(); $this->start();
$this->inst_start();
} }
} }
} }
/** /**
* Add a value to a specific key in the session array * Destroy existing session and restart
*
* @param string $name
* @param mixed $val
*/ */
public static function add_to_array($name, $val) public function restart()
{ {
return self::current_session()->inst_addToArray($name, $val); $this->destroy();
$this->init();
} }
/** /**
* Set a key/value pair in the session * Determine if this session has started
* *
* @param string $name Key * @return bool
* @param string|array $val Value
*/ */
public static function set($name, $val) public function isStarted()
{ {
return self::current_session()->inst_set($name, $val); return isset($this->data);
} }
/** /**
* Return a specific value by session key * Begin session
* *
* @param string $name Key to lookup * @param string $sid
* @return mixed
*/ */
public static function get($name) public function start($sid = null)
{ {
return self::current_session()->inst_get($name); if ($this->isStarted()) {
} throw new BadMethodCallException("Session has already started");
/**
* 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 = $this->config()->get('cookie_path');
{
$path = Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_path');
if (!$path) { if (!$path) {
$path = Director::baseURL(); $path = Director::baseURL();
} }
$domain = Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_domain'); $domain = $this->config()->get('cookie_domain');
$secure = Director::is_https() && Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_secure'); $secure = Director::is_https() && $this->config()->get('cookie_secure');
$session_path = Config::inst()->get('SilverStripe\\Control\\Session', 'session_store_path'); $session_path = $this->config()->get('session_store_path');
$timeout = Config::inst()->get('SilverStripe\\Control\\Session', 'timeout'); $timeout = $this->config()->get('timeout');
// Director::baseURL can return absolute domain names - this extracts the relevant parts // Director::baseURL can return absolute domain names - this extracts the relevant parts
// for the session otherwise we can get broken session cookies // for the session otherwise we can get broken session cookies
@ -300,6 +261,8 @@ class Session
session_start(); session_start();
$this->data = isset($_SESSION) ? $_SESSION : array(); $this->data = isset($_SESSION) ? $_SESSION : array();
} else {
$this->data = [];
} }
// Modify the timeout behaviour so it's the *inactive* time before the session expires. // 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 (session_id()) {
if ($removeCookie) { if ($removeCookie) {
$path = Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_path') ?: Director::baseURL(); $path = $this->config()->get('cookie_path') ?: Director::baseURL();
$domain = Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_domain'); $domain = $this->config()->get('cookie_domain');
$secure = Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_secure'); $secure = $this->config()->get('cookie_secure');
Cookie::force_expiry(session_name(), $path, $domain, $secure, true); Cookie::force_expiry(session_name(), $path, $domain, $secure, true);
} }
session_destroy(); 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();
} }
// Clean up the superglobal - session_destroy does not do it.
// http://nz1.php.net/manual/en/function.session-destroy.php
unset($_SESSION);
$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 // Quicker execution path for "."-free names
if (strpos($name, '.') === false) { if (strpos($name, '.') === false) {
$this->data[$name] = $val; $this->data[$name] = $val;
@ -360,10 +336,21 @@ class Session
$diffVar = $val; $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); $names = explode('.', $name);
// We still want to do this even if we have strict path checking for legacy code // 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; $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 // Quicker execution path for "."-free names
if (strpos($name, '.') === false) { if (strpos($name, '.') === false) {
if (isset($this->data[$name])) { 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); $names = explode('.', $name);
// We still want to do this even if we have strict path checking for legacy code // 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) { foreach ($names as $n) {
// don't clear a record that doesn't exist // don't clear a record that doesn't exist
if (!isset($var[$n])) { if (!isset($var[$n])) {
return; return $this;
} }
$var = &$var[$n]; $var = &$var[$n];
} }
@ -432,38 +439,54 @@ class Session
$var = null; $var = null;
$diffVar = 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)) { if ($this->data && is_array($this->data)) {
foreach (array_keys($this->data) as $key) { 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; 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 * Save data to session
* Only save the changes, so that anyone manipulating $_SESSION directly doesn't get burned. * Only save the changes, so that anyone manipulating $_SESSION directly doesn't get burned.
*/ */
public function inst_save() public function save()
{ {
if ($this->changedData) { if ($this->changedData) {
$this->inst_finalize(); $this->finalize();
if (!isset($_SESSION)) { if (!$this->isStarted()) {
$this->inst_start(); $this->start();
} }
$this->recursivelyApply($this->changedData, $_SESSION); $this->recursivelyApply($this->changedData, $_SESSION);
@ -493,55 +516,11 @@ class Session
/** /**
* Return the changed data, for debugging purposes. * Return the changed data, for debugging purposes.
*
* @return array * @return array
*/ */
public function inst_changedData() public function changedData()
{ {
return $this->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);
}
} }

413
src/Core/AppKernel.php Normal file
View File

@ -0,0 +1,413 @@
<?php
namespace SilverStripe\Core;
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\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;
class AppKernel extends CoreKernel
{
public function __construct()
{
// 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));
$this->setContainer($injector);
Injector::set_inst($injector);
// Manifest cache factory
$manifestCacheFactory = $this->buildManifestCacheFactory();
// Class loader
$classLoader = ClassLoader::inst();
$classLoader->pushManifest(new ClassManifest(BASE_PATH, $manifestCacheFactory));
$this->setClassLoader($classLoader);
// Module loader
$moduleLoader = ModuleLoader::inst();
$moduleManifest = new ModuleManifest(BASE_PATH, $manifestCacheFactory);
$moduleLoader->pushManifest($moduleManifest);
$this->setModuleLoader($moduleLoader);
// Config loader
// @todo refactor CoreConfigFactory
$configFactory = new CoreConfigFactory($manifestCacheFactory, $this->getEnvironment());
$configManifest = $configFactory->createRoot();
$configLoader = ConfigLoader::inst();
$configLoader->pushManifest($configManifest);
$this->setConfigLoader($configLoader);
// Load template manifest
$themeResourceLoader = ThemeResourceLoader::inst();
$themeResourceLoader->addSet('$default', new ThemeManifest(
BASE_PATH,
project(),
$manifestCacheFactory
));
$this->setThemeResourceLoader($themeResourceLoader);
}
public function getEnvironment()
{
// Check saved session
$env = $this->sessionEnvironment();
if ($env) {
return $env;
}
// Check set
if ($this->enviroment) {
return $this->enviroment;
}
// 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;
}
/**
* @throws HTTPResponse_Exception
*/
public function boot()
{
$this->bootPHP();
$this->bootManifests();
$this->bootErrorHandling();
$this->bootDatabase();
}
/**
* 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(BASE_PATH . '/_ss_environment.php') &&
!file_exists(dirname(BASE_PATH) . '/_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(BASE_PATH . '/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 = BASE_PATH;
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);
}
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();
}
/**
* @return ManifestCacheFactory
*/
protected function buildManifestCacheFactory()
{
return new ManifestCacheFactory([
'namespace' => 'manifestcache',
'directory' => getTempFolder(),
]);
}
/**
* Boot all manifests
*/
protected function bootManifests()
{
// 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.
$flush = isset($_GET['flush']) ||
trim($_GET['url'], '/') === trim(BASE_URL . '/dev/build', '/');
// Setup autoloader
$this->getClassLoader()->init(false, $flush);
// Find modules
$this->getModuleLoader()->init(false, $flush);
// Flush config
if ($flush) {
$config = $this->getConfigLoader()->getManifest();
if ($config instanceof CachedConfigCollection) {
$config->setFlush($flush);
}
}
// After loading config, boot _config.php files
$this->getModuleLoader()->getManifest()->activateConfig();
// Find default templates
$defaultSet = $this->getThemeResourceLoader()->getSet('$default');
if ($defaultSet instanceof ThemeManifest) {
$defaultSet->init(false, $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(BASE_PATH . '/' . $errorLog, Logger::WARNING));
} else {
user_error("SS_ERROR_LOG setting only works with Monolog, you are using another logger", E_USER_WARNING);
}
}
}
}

23
src/Core/Application.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace SilverStripe\Core;
/**
* Identifies a class as a root silverstripe application
*/
interface Application
{
/**
* Get the kernel for this application
*
* @return Kernel
*/
public function getKernel();
/**
* Invoke the application control chain
*
* @param callable $callback
*/
public function execute(callable $callback);
}

View File

@ -2,6 +2,7 @@
namespace SilverStripe\Core\Config; namespace SilverStripe\Core\Config;
use BadMethodCallException;
use SilverStripe\Config\Collections\ConfigCollectionInterface; use SilverStripe\Config\Collections\ConfigCollectionInterface;
/** /**
@ -36,6 +37,9 @@ class ConfigLoader
*/ */
public function getManifest() public function getManifest()
{ {
if (empty($this->manifests)) {
throw new BadMethodCallException("No config manifests available");
}
return $this->manifests[count($this->manifests) - 1]; return $this->manifests[count($this->manifests) - 1];
} }

View File

@ -2,22 +2,16 @@
namespace SilverStripe\Core\Config; namespace SilverStripe\Core\Config;
use Monolog\Handler\ErrorLogHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface; use Psr\SimpleCache\CacheInterface;
use SilverStripe\Config\Collections\CachedConfigCollection; use SilverStripe\Config\Collections\CachedConfigCollection;
use SilverStripe\Config\Collections\MemoryConfigCollection; use SilverStripe\Config\Collections\MemoryConfigCollection;
use SilverStripe\Config\Transformer\PrivateStaticTransformer; use SilverStripe\Config\Transformer\PrivateStaticTransformer;
use SilverStripe\Config\Transformer\YamlTransformer; use SilverStripe\Config\Transformer\YamlTransformer;
use SilverStripe\Control\Director;
use SilverStripe\Core\Cache\CacheFactory; use SilverStripe\Core\Cache\CacheFactory;
use SilverStripe\Core\Config\Middleware\ExtensionMiddleware; use SilverStripe\Core\Config\Middleware\ExtensionMiddleware;
use SilverStripe\Core\Config\Middleware\InheritanceMiddleware; use SilverStripe\Core\Config\Middleware\InheritanceMiddleware;
use SilverStripe\Core\Manifest\ClassLoader; use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Manifest\ModuleLoader; use SilverStripe\Core\Manifest\ModuleLoader;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\Finder;
/** /**
@ -26,19 +20,19 @@ use Symfony\Component\Finder\Finder;
class CoreConfigFactory class CoreConfigFactory
{ {
/** /**
* @var static * @var CacheFactory
*/ */
protected static $inst = null; protected $cacheFactory = null;
/** /**
* @return static * @var string
*/ */
public static function inst() protected $environment = null;
public function __construct(CacheFactory $cacheFactory, $environment)
{ {
if (!self::$inst) { $this->cacheFactory = $cacheFactory;
self::$inst = new static(); $this->environment = $environment;
}
return self::$inst;
} }
/** /**
@ -46,20 +40,17 @@ class CoreConfigFactory
* This will be an immutable cached config, * This will be an immutable cached config,
* which conditionally generates a nested "core" config. * which conditionally generates a nested "core" config.
* *
* @param bool $flush
* @param CacheFactory $cacheFactory
* @return CachedConfigCollection * @return CachedConfigCollection
*/ */
public function createRoot($flush, CacheFactory $cacheFactory) public function createRoot()
{ {
$instance = new CachedConfigCollection(); $instance = new CachedConfigCollection();
// Create config cache // Create config cache
$cache = $cacheFactory->create(CacheInterface::class.'.configcache', [ $cache = $this->cacheFactory->create(CacheInterface::class.'.configcache', [
'namespace' => 'configcache' 'namespace' => 'configcache'
]); ]);
$instance->setCache($cache); $instance->setCache($cache);
$instance->setFlush($flush);
// Set collection creator // Set collection creator
$instance->setCollectionCreator(function () { $instance->setCollectionCreator(function () {
@ -171,8 +162,7 @@ class CoreConfigFactory
} }
) )
->addRule('environment', function ($env) { ->addRule('environment', function ($env) {
$current = Director::get_environment_type(); return strtolower($this->environment) === strtolower($env);
return strtolower($current) === strtolower($env);
}) })
->addRule('moduleexists', function ($module) { ->addRule('moduleexists', function ($module) {
return ModuleLoader::inst()->getManifest()->moduleExists($module); return ModuleLoader::inst()->getManifest()->moduleExists($module);

131
src/Core/CoreKernel.php Normal file
View File

@ -0,0 +1,131 @@
<?php
namespace SilverStripe\Core;
use InvalidArgumentException;
use SilverStripe\Core\Config\ConfigLoader;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\View\ThemeResourceLoader;
/**
* Simple Kernel container
*/
class CoreKernel implements Kernel
{
/**
* @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 ThemeResourceLoader
*/
protected $themeResourceLoader = null;
public function boot()
{
}
public function shutdown()
{
}
public function nest()
{
// TODO: Implement nest() method.
}
public function getContainer()
{
return $this->container;
}
public function setContainer(Injector $container)
{
$this->container = $container;
$container->registerService($this, Kernel::class);
return $this;
}
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 getEnvironment()
{
return $this->enviroment ?: self::LIVE;
}
public function setEnvironment($environment)
{
if (!in_array($environment, [self::DEV, self::TEST, self::LIVE])) {
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;
}
}

View File

@ -0,0 +1,115 @@
<?php
namespace SilverStripe\Core;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
/**
* Invokes the HTTP application within an ErrorControlChain
*/
class HTTPApplication implements Application
{
/**
* @var callable[]
*/
protected $middlewares = [];
/**
* @return callable[]
*/
public function getMiddlewares()
{
return $this->middlewares;
}
/**
* @param callable[] $middlewares
* @return $this
*/
public function setMiddlewares($middlewares)
{
$this->middlewares = $middlewares;
return $this;
}
/**
* @param callable $middleware
* @return $this
*/
public function addMiddleware($middleware)
{
$this->middlewares[] = $middleware;
return $this;
}
/**
* Call middleware
*
* @param callable $last Last config to call
* @return HTTPResponse
*/
protected function callMiddleware($last)
{
// Reverse middlewares
$next = $last;
foreach (array_reverse($this->getMiddlewares()) as $middleware) {
$next = function () use ($middleware, $next) {
return call_user_func($middleware, $next);
};
}
return call_user_func($next);
}
/**
* @var Kernel
*/
protected $kernel;
public function __construct(Kernel $kernel)
{
$this->kernel = $kernel;
}
/**
* 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)
{
// Ensure boot is invoked
return $this->execute(function () use ($request) {
// Start session and execute
$request->getSession()->init();
return Director::direct($request);
});
}
/**
* Safely boot the application and execute the given main action
*
* @param callable $callback
* @return HTTPResponse
*/
public function execute(callable $callback)
{
return $this->callMiddleware(function () use ($callback) {
// Pre-request boot
$this->getKernel()->boot();
return call_user_func($callback);
});
}
}

View File

@ -202,21 +202,18 @@ class Injector implements ContainerInterface
{ {
$this->injectMap = array(); $this->injectMap = array();
$this->serviceCache = array( $this->serviceCache = array(
'Injector' => $this, 'Injector' => $this,
); );
$this->specs = array( $this->specs = [
'Injector' => array('class' => 'SilverStripe\\Core\\Injector\\Injector') 'Injector' => ['class' => static::class]
); ];
$this->autoProperties = array(); $this->autoProperties = array();
$creatorClass = isset($config['creator']) $creatorClass = isset($config['creator'])
? $config['creator'] ? $config['creator']
: 'SilverStripe\\Core\\Injector\\InjectionCreator'; : InjectionCreator::class;
$locatorClass = isset($config['locator']) $locatorClass = isset($config['locator'])
? $config['locator'] ? $config['locator']
: 'SilverStripe\\Core\\Injector\\SilverStripeServiceConfigurationLocator'; : SilverStripeServiceConfigurationLocator::class;
$this->objectCreator = new $creatorClass; $this->objectCreator = new $creatorClass;
$this->configLocator = new $locatorClass; $this->configLocator = new $locatorClass;
@ -860,7 +857,6 @@ class Injector implements ContainerInterface
$this->specs[$registerAt] = array('class' => get_class($service)); $this->specs[$registerAt] = array('class' => get_class($service));
$this->serviceCache[$registerAt] = $service; $this->serviceCache[$registerAt] = $service;
$this->inject($service);
} }
/** /**

View File

@ -51,7 +51,7 @@ class SilverStripeServiceConfigurationLocator implements ServiceConfigurationLoc
return $this->configs[$name]; return $this->configs[$name];
} }
$config = Config::inst()->get('SilverStripe\\Core\\Injector\\Injector', $name); $config = Config::inst()->get(Injector::class, $name);
$this->configs[$name] = $config; $this->configs[$name] = $config;
return $config; return $config;
} }

120
src/Core/Kernel.php Normal file
View File

@ -0,0 +1,120 @@
<?php
namespace SilverStripe\Core;
use SilverStripe\Core\Config\ConfigLoader;
use SilverStripe\Core\Injector\Injector;
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
*/
public function boot();
/**
* Shutdowns the kernel.
*/
public function shutdown();
/**
* Nests this kernel, all components, and returns the nested value.
*
* @return static
*/
public function nest();
/**
* @return Injector
*/
public function getContainer();
/**
* Sets injector
*
* @param Injector $container
* @return $this
*/
public function setContainer(Injector $container);
/**
* @return ClassLoader
*/
public function getClassLoader();
/**
* @param ClassLoader $classLoader
* @return $this
*/
public function setClassLoader(ClassLoader $classLoader);
/**
* @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);
}

View File

@ -19,7 +19,9 @@ class ClassLoader
private static $instance; private static $instance;
/** /**
* @var array Map of 'instance' (ClassManifest) and other options. * Map of 'instance' (ClassManifest) and other options.
*
* @var array
*/ */
protected $manifests = array(); protected $manifests = array();
@ -113,6 +115,23 @@ class ClassLoader
return false; 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. * Returns true if a class or interface name exists in the manifest.
* *

View File

@ -28,6 +28,13 @@ class ClassManifest
*/ */
protected $base; protected $base;
/**
* Used to build cache during boot
*
* @var CacheFactory
*/
protected $cacheFactory;
/** /**
* Set if including test classes * Set if including test classes
* *
@ -56,7 +63,7 @@ class ClassManifest
* *
* @var array * @var array
*/ */
protected $classes = array(); protected $classes = array();
/** /**
* List of root classes with no parent class * List of root classes with no parent class
@ -122,27 +129,30 @@ class ClassManifest
* from the cache or re-scanning for classes. * from the cache or re-scanning for classes.
* *
* @param string $base The manifest base path. * @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. * @param CacheFactory $cacheFactory Optional cache to use. Set to null to not cache.
*/ */
public function __construct( public function __construct($base, CacheFactory $cacheFactory = null)
$base, {
$includeTests = false,
$forceRegen = false,
CacheFactory $cacheFactory = null
) {
$this->base = $base; $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 // build cache from factory
if ($cacheFactory) { if ($this->cacheFactory) {
$this->cache = $cacheFactory->create( $this->cache = $this->cacheFactory->create(
CacheInterface::class.'.classmanifest', CacheInterface::class.'.classmanifest',
[ 'namespace' => 'classmanifest' . ($includeTests ? '_tests' : '') ] [ 'namespace' => 'classmanifest' . ($includeTests ? '_tests' : '') ]
); );
} }
$this->cacheKey = 'manifest';
if (!$forceRegen && $this->cache && ($data = $this->cache->get($this->cacheKey))) { if (!$forceRegen && $this->cache && ($data = $this->cache->get($this->cacheKey))) {
$this->classes = $data['classes']; $this->classes = $data['classes'];

View File

@ -85,4 +85,17 @@ class ModuleLoader
{ {
return count($this->manifests); 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);
}
}
} }

View File

@ -26,11 +26,11 @@ class ModuleManifest
protected $cacheKey; 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 * @var CacheInterface
@ -87,19 +87,24 @@ class ModuleManifest
* from the cache or re-scanning for classes. * from the cache or re-scanning for classes.
* *
* @param string $base The project base path. * @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 * @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->base = $base;
$this->cacheKey = sha1($base).'_modules'; $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 // build cache from factory
if ($cacheFactory) { if ($this->cacheFactory) {
$this->cache = $cacheFactory->create( $this->cache = $this->cacheFactory->create(
CacheInterface::class.'.modulemanifest', CacheInterface::class.'.modulemanifest',
[ 'namespace' => 'modulemanifest' . ($includeTests ? '_tests' : '') ] [ 'namespace' => 'modulemanifest' . ($includeTests ? '_tests' : '') ]
); );

View File

@ -0,0 +1,146 @@
<?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\Security\Permission;
use SilverStripe\Security\Security;
/**
* Decorates application bootstrapping with errorcontrolchain
*/
class ErrorControlChainMiddleware
{
/**
* @var Application
*/
protected $application = null;
/**
* @var HTTPRequest
*/
protected $request = null;
/**
* Build error control chain for an application
*
* @param Application $application
* @param HTTPRequest $request
*/
public function __construct(Application $application, HTTPRequest $request)
{
$this->application = $application;
$this->request = $request;
}
/**
* @param callable $next
* @return HTTPResponse
*/
public function __invoke(callable $next)
{
$result = null;
// Prepare tokens and execute chain
$reloadToken = ParameterConfirmationToken::prepare_tokens(
['isTest', 'isDev', 'flush'],
$this->getRequest()
);
$chain = new ErrorControlChain();
$chain
->then(function () use ($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($reloadToken);
} else {
// If no reload necessary, process application
$result = call_user_func($next);
}
} 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 ParameterConfirmationToken $reloadToken
* @return HTTPResponse
*/
protected function safeReloadWithToken($reloadToken)
{
// Safe reload requires manual boot
$this->getApplication()->getKernel()->boot();
// Ensure session is started
$this->getRequest()->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($this->getRequest()->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;
}
/**
* @return HTTPRequest
*/
public function getRequest()
{
return $this->request;
}
/**
* @param HTTPRequest $request
* @return $this
*/
public function setRequest(HTTPRequest $request)
{
$this->request = $request;
return $this;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace SilverStripe\Core\Startup;
use SilverStripe\Control\HTTPResponse;
/**
* Emits response to the browser
*/
class OutputMiddleware
{
protected $defaultResponse = null;
/**
* Construct output middleware with a default response
* (prevent WSOD)
*
* @param string $defaultResponse Provide default text to echo
* if no response could be generated
*/
public function __construct($defaultResponse = null)
{
$this->defaultResponse = $defaultResponse;
}
public function __invoke(callable $next)
{
/** @var HTTPResponse $response */
$response = call_user_func($next);
if ($response) {
$response->output();
} elseif ($this->defaultResponse) {
echo $this->defaultResponse;
}
return $response;
}
}

View File

@ -2,7 +2,10 @@
namespace SilverStripe\Core\Startup; 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; use SilverStripe\Security\RandomGenerator;
/** /**
@ -25,6 +28,11 @@ class ParameterConfirmationToken
*/ */
protected $parameterName = null; protected $parameterName = null;
/**
* @var HTTPRequest
*/
protected $request = null;
/** /**
* The parameter given * The parameter given
* *
@ -88,17 +96,19 @@ class ParameterConfirmationToken
* Create a new ParameterConfirmationToken * Create a new ParameterConfirmationToken
* *
* @param string $parameterName Name of the querystring parameter to check * @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 // Store the parameter name
$this->parameterName = $parameterName; $this->parameterName = $parameterName;
$this->request = $request;
// Store the parameter value // 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 // 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)) { if ($this->checkToken($token)) {
$this->token = $token; $this->token = $token;
} }
@ -151,7 +161,7 @@ class ParameterConfirmationToken
*/ */
public function suppress() 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; * Get redirect url, excluding querystring
*
protected function currentAbsoluteURL() * @return string
*/
protected function currentURL()
{ {
global $url; return Controller::join_links(
BASE_URL,
// Are we http or https? Replicates Director::is_https() without its dependencies/ '/',
$proto = 'http'; $this->request->getURL(false)
// 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));
} }
/** /**
* Forces a reload of the request with the token included * Forces a reload of the request with the token included
* This method will terminate the script with `die` *
* @return HTTPResponse
*/ */
public function reloadWithToken() 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) // Build response
$params = array_merge($_GET, $this->params()); $result = new HTTPResponse($body);
unset($params['url']); $result->redirect($location);
return $result;
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;
} }
/** /**
@ -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 * return the non-validated token with the highest priority
* *
* @param array $keys List of token keys in ascending priority (low to high) * @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 * @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; $target = null;
foreach ($keys as $key) { foreach ($keys as $key) {
$token = new ParameterConfirmationToken($key); $token = new ParameterConfirmationToken($key, $request);
// Validate this token // Validate this token
if ($token->reloadRequired()) { if ($token->reloadRequired()) {
$token->suppress(); $token->suppress();

View File

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

View File

@ -21,7 +21,7 @@ class DevBuildController extends Controller
{ {
if (Director::is_cli()) { if (Director::is_cli()) {
$da = DatabaseAdmin::create(); $da = DatabaseAdmin::create();
return $da->handleRequest($request, $this->model); return $da->handleRequest($request);
} else { } else {
$renderer = DebugView::create(); $renderer = DebugView::create();
echo $renderer->renderHeader(); echo $renderer->renderHeader();
@ -29,7 +29,7 @@ class DevBuildController extends Controller
echo "<div class=\"build\">"; echo "<div class=\"build\">";
$da = DatabaseAdmin::create(); $da = DatabaseAdmin::create();
$response = $da->handleRequest($request, $this->model); $response = $da->handleRequest($request);
echo "</div>"; echo "</div>";
echo $renderer->renderFooter(); echo $renderer->renderFooter();

View File

@ -2,13 +2,13 @@
namespace SilverStripe\Dev; 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 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. * A blueprint on how to create instances of a certain {@link DataObject} subclass.
@ -94,7 +94,7 @@ class FixtureBlueprint
try { try {
$class = $this->class; $class = $this->class;
$schema = DataObject::getSchema(); $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 // 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 // This is just in case field setters triggered by the population code in the next block

View File

@ -1,18 +1,18 @@
<html> <html>
<head> <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"> <link rel="stylesheet" type="text/css" href="framework/src/Dev/Install/client/styles/install.css">
</head> </head>
<body> <body>
<div id="BgContainer"> <div id="BgContainer">
<div id="Container"> <div id="Container">
<div id="Header"> <div id="Header">
<h1>PHP 5.5.0 required</h1> <h1>PHP 5.6.0 required</h1>
<div class="left"> <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, <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> <p>If you are running on a shared host, you may need to ask your hosting provider how to do this.</p>
</div> </div>

View File

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

View File

@ -2,25 +2,27 @@
namespace SilverStripe\Dev; namespace SilverStripe\Dev;
use Exception;
use LogicException;
use PHPUnit_Framework_TestCase;
use SilverStripe\CMS\Controllers\RootURLController; use SilverStripe\CMS\Controllers\RootURLController;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Cookie; use SilverStripe\Control\Cookie;
use SilverStripe\Control\Director;
use SilverStripe\Control\Email\Email; use SilverStripe\Control\Email\Email;
use SilverStripe\Control\Email\Mailer; use SilverStripe\Control\Email\Mailer;
use SilverStripe\Control\Session; use SilverStripe\Control\Session;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Control\Tests\FakeController; use SilverStripe\Control\Tests\FakeController;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\ConfigLoader; use SilverStripe\Core\Config\ConfigLoader;
use SilverStripe\Core\Config\CoreConfigFactory; use SilverStripe\Core\Config\CoreConfigFactory;
use SilverStripe\Core\Config\DefaultConfig; use SilverStripe\Core\Config\DefaultConfig;
use SilverStripe\Core\Config\Middleware\ExtensionMiddleware;
use SilverStripe\Core\Extension; use SilverStripe\Core\Extension;
use SilverStripe\Core\Flushable; use SilverStripe\Core\Flushable;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Manifest\ClassManifest;
use SilverStripe\Core\Manifest\ClassLoader; use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Manifest\ClassManifest;
use SilverStripe\Core\Resettable; use SilverStripe\Core\Resettable;
use SilverStripe\i18n\i18n; use SilverStripe\i18n\i18n;
use SilverStripe\ORM\DataExtension; use SilverStripe\ORM\DataExtension;
@ -28,22 +30,20 @@ use SilverStripe\ORM\SS_List;
use SilverStripe\Security\IdentityStore; use SilverStripe\Security\IdentityStore;
use SilverStripe\Versioned\Versioned; use SilverStripe\Versioned\Versioned;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataModel; use SilverStripe\ORM\DB;
use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\DB; use SilverStripe\ORM\SS_List;
use SilverStripe\Security\Member;
use SilverStripe\Security\Security;
use SilverStripe\Security\Group; use SilverStripe\Security\Group;
use SilverStripe\Security\Member;
use SilverStripe\Security\Permission; use SilverStripe\Security\Permission;
use SilverStripe\Security\Security;
use SilverStripe\Versioned\Versioned;
use SilverStripe\View\Requirements; use SilverStripe\View\Requirements;
use SilverStripe\View\SSViewer; use SilverStripe\View\SSViewer;
use SilverStripe\View\ThemeResourceLoader;
use SilverStripe\View\ThemeManifest; use SilverStripe\View\ThemeManifest;
use PHPUnit_Framework_TestCase; use SilverStripe\View\ThemeResourceLoader;
use Translatable; use Translatable;
use LogicException;
use Exception;
/** /**
* Test case class for the Sapphire framework. * Test case class for the Sapphire framework.
@ -274,9 +274,6 @@ class SapphireTest extends PHPUnit_Framework_TestCase
$resettable::reset(); $resettable::reset();
} }
if (Controller::has_curr()) {
Controller::curr()->setSession(Session::create(array()));
}
Security::clear_database_is_ready(); Security::clear_database_is_ready();
// Set up test routes // Set up test routes
@ -284,9 +281,6 @@ class SapphireTest extends PHPUnit_Framework_TestCase
$fixtureFiles = $this->getFixturePaths(); $fixtureFiles = $this->getFixturePaths();
// Todo: this could be a special test model
$this->model = DataModel::inst();
// Set up fixture // Set up fixture
if ($fixtureFiles || $this->usesDatabase) { if ($fixtureFiles || $this->usesDatabase) {
if (!self::using_temp_db()) { if (!self::using_temp_db()) {

View File

@ -333,11 +333,23 @@ class Form extends ViewableData implements HasRequestHandler
*/ */
public function clearFormState() public function clearFormState()
{ {
Session::clear("FormInfo.{$this->FormName()}.result"); $this
Session::clear("FormInfo.{$this->FormName()}.data"); ->getSession()
->clear("FormInfo.{$this->FormName()}.result")
->clear("FormInfo.{$this->FormName()}.data");
return $this; return $this;
} }
/**
* Get session for this form
*
* @return Session
*/
protected function getSession()
{
return $this->getRequestHandler()->getRequest()->getSession();
}
/** /**
* Return any form data stored in the session * Return any form data stored in the session
* *
@ -345,7 +357,7 @@ class Form extends ViewableData implements HasRequestHandler
*/ */
public function getSessionData() public function getSessionData()
{ {
return Session::get("FormInfo.{$this->FormName()}.data"); return $this->getSession()->get("FormInfo.{$this->FormName()}.data");
} }
/** /**
@ -356,7 +368,7 @@ class Form extends ViewableData implements HasRequestHandler
*/ */
public function setSessionData($data) public function setSessionData($data)
{ {
Session::set("FormInfo.{$this->FormName()}.data", $data); $this->getSession()->set("FormInfo.{$this->FormName()}.data", $data);
return $this; return $this;
} }
@ -367,7 +379,7 @@ class Form extends ViewableData implements HasRequestHandler
*/ */
public function getSessionValidationResult() public function getSessionValidationResult()
{ {
$resultData = Session::get("FormInfo.{$this->FormName()}.result"); $resultData = $this->getSession()->get("FormInfo.{$this->FormName()}.result");
if (isset($resultData)) { if (isset($resultData)) {
return unserialize($resultData); return unserialize($resultData);
} }
@ -396,7 +408,7 @@ class Form extends ViewableData implements HasRequestHandler
// Serialise // Serialise
$resultData = $result ? serialize($result) : null; $resultData = $result ? serialize($result) : null;
Session::set("FormInfo.{$this->FormName()}.result", $resultData); $this->getSession()->set("FormInfo.{$this->FormName()}.result", $resultData);
return $this; return $this;
} }

View File

@ -2,23 +2,21 @@
namespace SilverStripe\Forms\GridField; namespace SilverStripe\Forms\GridField;
use InvalidArgumentException;
use LogicException;
use SilverStripe\Control\HasRequestHandler; 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\ArrayList;
use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\SS_List;
use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\DataModel;
use SilverStripe\ORM\DataObjectInterface; use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\Control\HTTPRequest; use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\Control\Session; use SilverStripe\ORM\SS_List;
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\View\HTML; use SilverStripe\View\HTML;
/** /**
@ -910,7 +908,7 @@ class GridField extends FormField
foreach ($data as $dataKey => $dataValue) { foreach ($data as $dataKey => $dataValue) {
if (preg_match('/^action_gridFieldAlterAction\?StateID=(.*)/', $dataKey, $matches)) { if (preg_match('/^action_gridFieldAlterAction\?StateID=(.*)/', $dataKey, $matches)) {
$stateChange = Session::get($matches[1]); $stateChange = $request->getSession()->get($matches[1]);
$actionName = $stateChange['actionName']; $actionName = $stateChange['actionName'];
$arguments = array(); $arguments = array();
@ -972,11 +970,10 @@ class GridField extends FormField
* @todo copy less code from RequestHandler. * @todo copy less code from RequestHandler.
* *
* @param HTTPRequest $request * @param HTTPRequest $request
* @param DataModel $model
* @return array|RequestHandler|HTTPResponse|string * @return array|RequestHandler|HTTPResponse|string
* @throws HTTPResponse_Exception * @throws HTTPResponse_Exception
*/ */
public function handleRequest(HTTPRequest $request, DataModel $model) public function handleRequest(HTTPRequest $request)
{ {
if ($this->brokenOnConstruct) { if ($this->brokenOnConstruct) {
user_error( user_error(
@ -989,7 +986,6 @@ class GridField extends FormField
} }
$this->setRequest($request); $this->setRequest($request);
$this->setDataModel($model);
$fieldData = $this->getRequest()->requestVar($this->getName()); $fieldData = $this->getRequest()->requestVar($this->getName());
@ -1038,7 +1034,7 @@ class GridField extends FormField
if ($result instanceof HasRequestHandler) { if ($result instanceof HasRequestHandler) {
$result = $result->getRequestHandler(); $result = $result->getRequestHandler();
} }
$returnValue = $result->handleRequest($request, $model); $returnValue = $result->handleRequest($request);
if (is_array($returnValue)) { if (is_array($returnValue)) {
throw new LogicException( throw new LogicException(
@ -1066,7 +1062,7 @@ class GridField extends FormField
} }
} }
return parent::handleRequest($request, $model); return parent::handleRequest($request);
} }
/** /**

View File

@ -2,17 +2,16 @@
namespace SilverStripe\Forms\GridField; namespace SilverStripe\Forms\GridField;
use Closure;
use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\RequestHandler; use SilverStripe\Control\RequestHandler;
use SilverStripe\Core\Extensible;
use SilverStripe\ORM\DataModel;
use SilverStripe\ORM\DataObject;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Extensible;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\Validator;
use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FieldList;
use Closure; use SilverStripe\Forms\Validator;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Filterable; use SilverStripe\ORM\Filterable;
/** /**
@ -118,7 +117,7 @@ class GridFieldDetailForm implements GridField_URLHandler
$this->setValidator($record->getCMSValidator()); $this->setValidator($record->getCMSValidator());
} }
return $handler->handleRequest($request, DataModel::inst()); return $handler->handleRequest($request);
} }
/** /**

View File

@ -2,6 +2,7 @@
namespace SilverStripe\Forms\GridField; namespace SilverStripe\Forms\GridField;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Session; use SilverStripe\Control\Session;
use SilverStripe\Forms\Form; use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction; use SilverStripe\Forms\FormAction;
@ -89,7 +90,9 @@ class GridField_FormAction extends FormAction
// Ensure $id doesn't contain only numeric characters // Ensure $id doesn't contain only numeric characters
$id = 'gf_' . substr(md5(serialize($state)), 0, 8); $id = 'gf_' . substr(md5(serialize($state)), 0, 8);
Session::set($id, $state);
$session = Controller::curr()->getRequest()->getSession();
$session->set($id, $state);
$actionData['StateID'] = $id; $actionData['StateID'] = $id;
return array_merge( return array_merge(

View File

@ -2,19 +2,19 @@
namespace SilverStripe\ORM; namespace SilverStripe\ORM;
use InvalidArgumentException;
use LogicException;
use SilverStripe\Control\Cookie;
use SilverStripe\Control\Director;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Control\Director;
use SilverStripe\Control\Cookie;
use SilverStripe\Dev\Deprecation; use SilverStripe\Dev\Deprecation;
use SilverStripe\ORM\Connect\Database;
use SilverStripe\ORM\Connect\DBConnector; use SilverStripe\ORM\Connect\DBConnector;
use SilverStripe\ORM\Connect\DBSchemaManager; use SilverStripe\ORM\Connect\DBSchemaManager;
use SilverStripe\ORM\Connect\Query; use SilverStripe\ORM\Connect\Query;
use SilverStripe\ORM\Queries\SQLExpression; use SilverStripe\ORM\Queries\SQLExpression;
use SilverStripe\ORM\Connect\Database;
use InvalidArgumentException;
use LogicException;
/** /**
* Global database interface, complete with static methods. * Global database interface, complete with static methods.
@ -34,9 +34,19 @@ class DB
/** /**
* The global database connection. * The global database connection.
*
* @var Database * @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. * The last SQL query run.
@ -77,6 +87,13 @@ class DB
if (isset(self::$connections[$name])) { if (isset(self::$connections[$name])) {
return self::$connections[$name]; return self::$connections[$name];
} }
// lazy connect
$config = static::getConfig($name);
if ($config) {
return static::connect($config, $name);
}
return null; 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 * Given the database configuration, this method will create the correct
* subclass of {@link SS_Database}. * subclass of {@link SS_Database}.
@ -259,14 +276,13 @@ class DB
*/ */
public static function connect($databaseConfig, $label = 'default') public static function connect($databaseConfig, $label = 'default')
{ {
// This is used by the "testsession" module to test up a test session using an alternative name // This is used by the "testsession" module to test up a test session using an alternative name
if ($name = self::get_alternative_database_name()) { if ($name = self::get_alternative_database_name()) {
$databaseConfig['database'] = $name; $databaseConfig['database'] = $name;
} }
if (!isset($databaseConfig['type']) || empty($databaseConfig['type'])) { 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; self::$connection_attempted = true;
@ -283,6 +299,30 @@ class DB
return $conn; 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)
{
if (isset(static::$configs[$name])) {
return static::$configs[$name];
}
}
/** /**
* Returns true if a database connection has been attempted. * 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 * In particular, it lets the caller know if we're still so early in the execution pipeline that

View File

@ -49,13 +49,6 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li
*/ */
protected $dataQuery; protected $dataQuery;
/**
* The DataModel from which this DataList comes.
*
* @var DataModel
*/
protected $model;
/** /**
* Create a new DataList. * Create a new DataList.
* No querying is done on construction, but the initial query schema is set up. * 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(); 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 * 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']; $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; return $item;
} }
@ -1119,7 +1102,7 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li
public function newObject($initialFields = null) public function newObject($initialFields = null)
{ {
$class = $this->dataClass; $class = $this->dataClass;
return Injector::inst()->create($class, $initialFields, false, $this->model); return Injector::inst()->create($class, $initialFields, false);
} }
/** /**

View File

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

View File

@ -141,11 +141,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/ */
public $destroyed = false; public $destroyed = false;
/**
* The DataModel from this this object comes
*/
protected $model;
/** /**
* Data stored in this objects database record. An array indexed by fieldname. * 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. * for populating data on new records.
* @param boolean $isSingleton This this to true if this is a singleton() object, a stub for calling methods. * @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. * 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. * @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(); parent::__construct();
@ -365,10 +359,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
HTTP::register_modification_date($record['LastEdited']); 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 // Must be called after parent constructor
if (!$isSingleton && (!isset($this->record['ID']) || !$this->record['ID'])) { if (!$isSingleton && (!isset($this->record['ID']) || !$this->record['ID'])) {
$this->populateDefaults(); $this->populateDefaults();
@ -378,17 +368,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$this->changed = array(); $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. * 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. * 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(); $map = $this->toMap();
unset($map['Created']); unset($map['Created']);
/** @var static $clone */ /** @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->ID = 0;
$clone->invokeWithExtensions('onBeforeDuplicate', $this, $doWrite, $manyMany); $clone->invokeWithExtensions('onBeforeDuplicate', $this, $doWrite, $manyMany);
@ -1437,7 +1416,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// - move the details of the delete code in the DataQuery system // - 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 // - 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 ;-) // 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) ->filter('ID', $this->ID)
->dataQuery() ->dataQuery()
->query(); ->query();
@ -1520,7 +1499,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
} }
if (empty($component)) { if (empty($component)) {
$component = $this->model->$class->newObject(); $component = Injector::inst()->create($class);
} }
} elseif ($class = $schema->belongsToComponent(static::class, $componentName)) { } elseif ($class = $schema->belongsToComponent(static::class, $componentName)) {
$joinField = $schema->getRemoteJoinField(static::class, $componentName, 'belongs_to', $polymorphic); $joinField = $schema->getRemoteJoinField(static::class, $componentName, 'belongs_to', $polymorphic);
@ -2827,9 +2806,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
. ' arguments'); . ' arguments');
} }
$result = DataList::create(get_called_class()); return DataList::create(get_called_class());
$result->setDataModel(DataModel::inst());
return $result;
} }
if ($join) { if ($join) {
@ -2847,7 +2824,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$result = $result->limit($limit); $result = $result->limit($limit);
} }
$result->setDataModel(DataModel::inst());
return $result; return $result;
} }

View File

@ -70,7 +70,7 @@ class ManyManyThroughList extends RelationList
if ($joinRow) { if ($joinRow) {
$joinClass = $this->manipulator->getJoinClass(); $joinClass = $this->manipulator->getJoinClass();
$joinQueryParams = $this->manipulator->extractInheritableQueryParameters($this->dataQuery); $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); $record->setJoin($joinRecord, $joinAlias);
} }

View File

@ -180,9 +180,12 @@ class BasicAuth
*/ */
public static function protect_entire_site($protect = true, $code = 'ADMIN', $message = null) public static function protect_entire_site($protect = true, $code = 'ADMIN', $message = null)
{ {
static::config()->set('entire_site_protected', $protect); static::config()
static::config()->set('entire_site_protected_code', $code); ->set('entire_site_protected', $protect)
static::config()->set('entire_site_protected_message', $message); ->set('entire_site_protected_code', $code);
if ($message) {
static::config()->set('entire_site_protected_message', $message);
}
} }
/** /**
@ -197,7 +200,6 @@ class BasicAuth
$config = static::config(); $config = static::config();
$request = Controller::curr()->getRequest(); $request = Controller::curr()->getRequest();
if ($config->get('entire_site_protected')) { if ($config->get('entire_site_protected')) {
/** @noinspection ExceptionsAnnotatingAndHandlingInspection */
static::requireLogin( static::requireLogin(
$request, $request,
$config->get('entire_site_protected_message'), $config->get('entire_site_protected_message'),

View File

@ -94,14 +94,14 @@ class CMSSecurity extends Security
$message = parent::getLoginMessage($messageType); $message = parent::getLoginMessage($messageType);
if ($message) { if ($message) {
return $message; return $message;
} }
// Format // Format
return _t( return _t(
__CLASS__.'.LOGIN_MESSAGE', __CLASS__.'.LOGIN_MESSAGE',
'<p>Your session has timed out due to inactivity</p>' '<p>Your session has timed out due to inactivity</p>'
); );
} }
/** /**
* Check if there is a logged in member * Check if there is a logged in member
@ -187,7 +187,7 @@ PHP
$controller = $this->getResponseController(_t(__CLASS__.'.SUCCESS', 'Success')); $controller = $this->getResponseController(_t(__CLASS__.'.SUCCESS', 'Success'));
$backURLs = array( $backURLs = array(
$this->getRequest()->requestVar('BackURL'), $this->getRequest()->requestVar('BackURL'),
Session::get('BackURL'), $this->getRequest()->getSession()->get('BackURL'),
Director::absoluteURL(AdminRootController::config()->get('url_base'), true), Director::absoluteURL(AdminRootController::config()->get('url_base'), true),
); );
$backURL = null; $backURL = null;
@ -201,7 +201,7 @@ PHP
$controller = $controller->customise(array( $controller = $controller->customise(array(
'Content' => DBField::create_field(DBHTMLText::class, _t( 'Content' => DBField::create_field(DBHTMLText::class, _t(
__CLASS__.'.SUCCESSCONTENT', __CLASS__.'.SUCCESSCONTENT',
'<p>Login success. If you are not automatically redirected '. '<p>Login success. If you are not automatically redirected ' .
'<a target="_top" href="{link}">click here</a></p>', '<a target="_top" href="{link}">click here</a></p>',
'Login message displayed in the cms popup once a user has re-authenticated themselves', 'Login message displayed in the cms popup once a user has re-authenticated themselves',
array('link' => Convert::raw2att($backURL)) array('link' => Convert::raw2att($backURL))

View File

@ -275,14 +275,14 @@ class Member extends DataObject
{ {
Deprecation::notice('5.0', 'Use DefaultAdminService::findOrCreateDefaultAdmin() instead'); Deprecation::notice('5.0', 'Use DefaultAdminService::findOrCreateDefaultAdmin() instead');
return DefaultAdminService::singleton()->findOrCreateDefaultAdmin(); return DefaultAdminService::singleton()->findOrCreateDefaultAdmin();
} }
/** /**
* Check if the passed password matches the stored one (if the member is not locked out). * Check if the passed password matches the stored one (if the member is not locked out).
* *
* @deprecated 4.0.0...5.0.0 Use Authenticator::checkPassword() instead * @deprecated 4.0.0...5.0.0 Use Authenticator::checkPassword() instead
* *
* @param string $password * @param string $password
* @return ValidationResult * @return ValidationResult
*/ */
public function checkPassword($password) public function checkPassword($password)
@ -294,12 +294,12 @@ class Member extends DataObject
$authenticators = Security::singleton()->getApplicableAuthenticators(Authenticator::CHECK_PASSWORD); $authenticators = Security::singleton()->getApplicableAuthenticators(Authenticator::CHECK_PASSWORD);
foreach ($authenticators as $authenticator) { foreach ($authenticators as $authenticator) {
$authenticator->checkPassword($this, $password, $result); $authenticator->checkPassword($this, $password, $result);
if (!$result->isValid()) { if (!$result->isValid()) {
break; break;
}
} }
return $result; }
} return $result;
}
/** /**
* Check if this user is the currently configured default admin * Check if this user is the currently configured default admin

View File

@ -28,7 +28,8 @@ class ChangePasswordForm extends Form
*/ */
public function __construct($controller, $name, $fields = null, $actions = null) 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) { if (!$fields) {
$fields = $this->getFormFields(); $fields = $this->getFormFields();

View File

@ -6,7 +6,6 @@ namespace SilverStripe\Security\MemberAuthenticator;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\RequestHandler; use SilverStripe\Control\RequestHandler;
use SilverStripe\Control\Session;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBField;
@ -79,7 +78,8 @@ class ChangePasswordHandler extends RequestHandler
return $this->redirect($this->link); return $this->redirect($this->link);
} }
if (Session::get('AutoLoginHash')) { $session = $this->getRequest()->getSession();
if ($session->get('AutoLoginHash')) {
$message = DBField::create_field( $message = DBField::create_field(
'HTMLFragment', 'HTMLFragment',
'<p>' . _t( '<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. // 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(); return $this->redirectBackToForm();
} }
$session = $this->getRequest()->getSession();
if (!$member) { if (!$member) {
if (Session::get('AutoLoginHash')) { if ($session->get('AutoLoginHash')) {
$member = Member::member_from_autologinhash(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 // The user is not logged in and no valid auto login hash is available
if (!$member) { if (!$member) {
Session::clear('AutoLoginHash'); $session->clear('AutoLoginHash');
return $this->redirect($this->addBackURLParam(Security::singleton()->Link('login'))); return $this->redirect($this->addBackURLParam(Security::singleton()->Link('login')));
} }
@ -278,7 +279,7 @@ class ChangePasswordHandler extends RequestHandler
} }
// TODO Add confirmation message to login redirect // TODO Add confirmation message to login redirect
Session::clear('AutoLoginHash'); $session->clear('AutoLoginHash');
// Redirect to backurl // Redirect to backurl
$backURL = $this->getBackURL(); $backURL = $this->getBackURL();

View File

@ -143,7 +143,7 @@ class MemberLoginForm extends BaseLoginForm
$emailField->setAttribute('autofocus', 'true'); $emailField->setAttribute('autofocus', 'true');
if (Security::config()->get('remember_username')) { if (Security::config()->get('remember_username')) {
$emailField->setValue(Session::get('SessionForms.MemberLoginForm.Email')); $emailField->setValue($this->getSession()->get('SessionForms.MemberLoginForm.Email'));
} else { } else {
// Some browsers won't respect this attribute unless it's added to the form // Some browsers won't respect this attribute unless it's added to the form
$this->setAttribute('autocomplete', 'off'); $this->setAttribute('autocomplete', 'off');
@ -195,7 +195,8 @@ class MemberLoginForm extends BaseLoginForm
{ {
parent::restoreFormState(); parent::restoreFormState();
$forceMessage = Session::get('MemberLoginForm.force_message'); $session = Controller::curr()->getRequest()->getSession();
$forceMessage = $session->get('MemberLoginForm.force_message');
if (($member = Security::getCurrentUser()) && !$forceMessage) { if (($member = Security::getCurrentUser()) && !$forceMessage) {
$message = _t( $message = _t(
'SilverStripe\\Security\\Member.LOGGEDINAS', 'SilverStripe\\Security\\Member.LOGGEDINAS',
@ -207,7 +208,7 @@ class MemberLoginForm extends BaseLoginForm
// Reset forced message // Reset forced message
if ($forceMessage) { if ($forceMessage) {
Session::set('MemberLoginForm.force_message', false); $session->set('MemberLoginForm.force_message', false);
} }
return $this; return $this;

View File

@ -2,10 +2,10 @@
namespace SilverStripe\Security\MemberAuthenticator; namespace SilverStripe\Security\MemberAuthenticator;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Cookie; use SilverStripe\Control\Cookie;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\Session;
use SilverStripe\Security\AuthenticationHandler; use SilverStripe\Security\AuthenticationHandler;
use SilverStripe\Security\Member; 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 // If ID is a bad ID it will be treated as if the user is not logged in, rather than throwing a
// ValidationException // ValidationException
$id = Session::get($this->getSessionVariable()); $id = $request->getSession()->get($this->getSessionVariable());
if (!$id) { if (!$id) {
return null; return null;
} }
@ -64,7 +64,8 @@ class SessionAuthenticationHandler implements AuthenticationHandler
public function logIn(Member $member, $persistent = false, HTTPRequest $request = null) public function logIn(Member $member, $persistent = false, HTTPRequest $request = null)
{ {
static::regenerateSessionId(); 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 // This lets apache rules detect whether the user has logged in
// @todo make this a setting on the authentication handler // @todo make this a setting on the authentication handler
@ -102,6 +103,7 @@ class SessionAuthenticationHandler implements AuthenticationHandler
*/ */
public function logOut(HTTPRequest $request = null) public function logOut(HTTPRequest $request = null)
{ {
Session::clear($this->getSessionVariable()); $request = $request ?: Controller::curr()->getRequest();
$request->getSession()->clear($this->getSessionVariable());
} }
} }

View File

@ -19,7 +19,6 @@ use SilverStripe\Dev\Deprecation;
use SilverStripe\Dev\TestOnly; use SilverStripe\Dev\TestOnly;
use SilverStripe\Forms\Form; use SilverStripe\Forms\Form;
use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataModel;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBField;
@ -328,7 +327,7 @@ class Security extends Controller implements TemplateGlobalProvider
{ {
self::set_ignore_disallowed_actions(true); self::set_ignore_disallowed_actions(true);
if (!$controller) { if (!$controller && Controller::has_curr()) {
$controller = Controller::curr(); $controller = Controller::curr();
} }
@ -357,7 +356,7 @@ class Security extends Controller implements TemplateGlobalProvider
$messageSet = $configMessageSet; $messageSet = $configMessageSet;
} else { } else {
$messageSet = array( $messageSet = array(
'default' => _t( 'default' => _t(
'SilverStripe\\Security\\Security.NOTEPAGESECURED', 'SilverStripe\\Security\\Security.NOTEPAGESECURED',
"That page is secured. Enter your credentials below and we will send " "That page is secured. Enter your credentials below and we will send "
. "you right along." . "you right along."
@ -408,7 +407,7 @@ class Security extends Controller implements TemplateGlobalProvider
static::singleton()->setLoginMessage($message, ValidationResult::TYPE_WARNING); static::singleton()->setLoginMessage($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 // TODO AccessLogEntry needs an extension to handle permission denied errors
// Audit logging hook // Audit logging hook
@ -569,6 +568,21 @@ class Security extends Controller implements TemplateGlobalProvider
return null; 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 * Prepare the controller for handling the response to this request
* *
@ -593,7 +607,6 @@ class Security extends Controller implements TemplateGlobalProvider
$holderPage->ID = -1 * random_int(1, 10000000); $holderPage->ID = -1 * random_int(1, 10000000);
$controller = ModelAsController::controller_for($holderPage); $controller = ModelAsController::controller_for($holderPage);
$controller->setDataModel($this->model);
$controller->doInit(); $controller->doInit();
return $controller; return $controller;
@ -628,14 +641,15 @@ class Security extends Controller implements TemplateGlobalProvider
*/ */
protected function getLoginMessage(&$messageType = null) protected function getLoginMessage(&$messageType = null)
{ {
$message = Session::get('Security.Message.message'); $session = $this->getRequest()->getSession();
$message = $session->get('Security.Message.message');
$messageType = null; $messageType = null;
if (empty($message)) { if (empty($message)) {
return null; return null;
} }
$messageType = Session::get('Security.Message.type'); $messageType = $session->get('Security.Message.type');
$messageCast = Session::get('Security.Message.cast'); $messageCast = $session->get('Security.Message.cast');
if ($messageCast !== ValidationResult::CAST_HTML) { if ($messageCast !== ValidationResult::CAST_HTML) {
$message = Convert::raw2xml($message); $message = Convert::raw2xml($message);
} }
@ -655,9 +669,12 @@ class Security extends Controller implements TemplateGlobalProvider
$messageType = ValidationResult::TYPE_WARNING, $messageType = ValidationResult::TYPE_WARNING,
$messageCast = ValidationResult::CAST_TEXT $messageCast = ValidationResult::CAST_TEXT
) { ) {
Session::set('Security.Message.message', $message); Controller::curr()
Session::set('Security.Message.type', $messageType); ->getRequest()
Session::set('Security.Message.cast', $messageCast); ->getSession()
->set("Security.Message.message", $message)
->set("Security.Message.type", $messageType)
->set("Security.Message.cast", $messageCast);
} }
/** /**
@ -665,7 +682,10 @@ class Security extends Controller implements TemplateGlobalProvider
*/ */
public static function clearLoginMessage() public static function clearLoginMessage()
{ {
Session::clear('Security.Message'); Controller::curr()
->getRequest()
->getSession()
->clear("Security.Message");
} }
@ -752,7 +772,7 @@ class Security extends Controller implements TemplateGlobalProvider
// Process each of the handlers // Process each of the handlers
$results = array_map( $results = array_map(
function (RequestHandler $handler) { function (RequestHandler $handler) {
return $handler->handleRequest($this->getRequest(), DataModel::inst()); return $handler->handleRequest($this->getRequest());
}, },
$handlers $handlers
); );
@ -794,7 +814,7 @@ class Security extends Controller implements TemplateGlobalProvider
*/ */
protected function delegateToHandler(RequestHandler $handler, $title, array $templates = []) protected function delegateToHandler(RequestHandler $handler, $title, array $templates = [])
{ {
$result = $handler->handleRequest($this->getRequest(), DataModel::inst()); $result = $handler->handleRequest($this->getRequest());
// Return the customised controller - used to render in a Form // Return the customised controller - used to render in a Form
// Post requests are expected to be login posts, so they'll be handled downstairs // Post requests are expected to be login posts, so they'll be handled downstairs
@ -958,7 +978,7 @@ class Security extends Controller implements TemplateGlobalProvider
$service = DefaultAdminService::singleton(); $service = DefaultAdminService::singleton();
return $service->findOrCreateDefaultAdmin(); return $service->findOrCreateDefaultAdmin();
} }
/** /**
* Flush the default admin credentials * Flush the default admin credentials
@ -972,7 +992,6 @@ class Security extends Controller implements TemplateGlobalProvider
DefaultAdminService::clearDefaultAdmin(); DefaultAdminService::clearDefaultAdmin();
} }
/** /**
* Set a default admin in dev-mode * Set a default admin in dev-mode
* *
@ -1092,7 +1111,7 @@ class Security extends Controller implements TemplateGlobalProvider
return [ return [
'password' => $encryptor->encrypt($password, $salt, $member), 'password' => $encryptor->encrypt($password, $salt, $member),
'salt' => $salt, 'salt' => $salt,
'algorithm' => $algorithm, 'algorithm' => $algorithm,
'encryptor' => $encryptor 'encryptor' => $encryptor
]; ];
@ -1243,11 +1262,11 @@ class Security extends Controller implements TemplateGlobalProvider
public static function get_template_global_variables() public static function get_template_global_variables()
{ {
return [ return [
"LoginURL" => "login_url", "LoginURL" => "login_url",
"LogoutURL" => "logout_url", "LogoutURL" => "logout_url",
"LostPasswordURL" => "lost_password_url", "LostPasswordURL" => "lost_password_url",
"CurrentMember" => "getCurrentUser", "CurrentMember" => "getCurrentUser",
"currentUser" => "getCurrentUser" "currentUser" => "getCurrentUser"
]; ];
} }
} }

View File

@ -150,7 +150,8 @@ class SecurityToken implements TemplateGlobalProvider
*/ */
public function getValue() 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 // only regenerate if the token isn't already set in the session
if (!$value) { if (!$value) {
@ -166,7 +167,8 @@ class SecurityToken implements TemplateGlobalProvider
*/ */
public function setValue($val) public function setValue($val)
{ {
Session::set($this->getName(), $val); $session = Controller::curr()->getRequest()->getSession();
$session->set($this->getName(), $val);
} }
/** /**

View File

@ -2,9 +2,8 @@
namespace SilverStripe\View; namespace SilverStripe\View;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataModel;
use InvalidArgumentException; use InvalidArgumentException;
use SilverStripe\ORM\DataList;
class GenericTemplateGlobalProvider implements TemplateGlobalProvider class GenericTemplateGlobalProvider implements TemplateGlobalProvider
{ {
@ -61,8 +60,6 @@ class GenericTemplateGlobalProvider implements TemplateGlobalProvider
*/ */
public static function getDataList($className) public static function getDataList($className)
{ {
$list = new DataList($className); return DataList::create($className);
$list->setDataModel(DataModel::inst());
return $list;
} }
} }

View File

@ -21,13 +21,6 @@ class ThemeManifest implements ThemeList
*/ */
protected $base; protected $base;
/**
* Include tests
*
* @var bool
*/
protected $tests;
/** /**
* Path to application code * Path to application code
* *
@ -56,39 +49,44 @@ class ThemeManifest implements ThemeList
*/ */
protected $themes = null; protected $themes = null;
/**
* @var CacheFactory
*/
protected $cacheFactory= null;
/** /**
* Constructs a new template manifest. The manifest is not actually built * Constructs a new template manifest. The manifest is not actually built
* or loaded from cache until needed. * or loaded from cache until needed.
* *
* @param string $base The base path. * @param string $base The base path.
* @param string $project Path to application code * @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 * @param CacheFactory $cacheFactory Cache factory to generate backend cache with
*/ */
public function __construct( public function __construct($base, $project, CacheFactory $cacheFactory = null)
$base, {
$project, $this->base = $base;
$includeTests = false,
$forceRegen = false,
CacheFactory $cacheFactory = null
) {
$this->base = $base;
$this->tests = $includeTests;
$this->project = $project; $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 // build cache from factory
if ($cacheFactory) { if ($this->cacheFactory) {
$this->cache = $cacheFactory->create( $this->cache = $this->cacheFactory->create(
CacheInterface::class.'.thememanifest', CacheInterface::class.'.thememanifest',
[ 'namespace' => 'thememanifest' . ($includeTests ? '_tests' : '') ] [ 'namespace' => 'thememanifest' . ($includeTests ? '_tests' : '') ]
); );
} }
$this->cacheKey = $this->getCacheKey(); $this->cacheKey = $this->getCacheKey($includeTests);
if (!$forceRegen && $this->cache && ($data = $this->cache->get($this->cacheKey))) {
if ($forceRegen) { $this->themes = $data;
$this->regenerate(); } else {
$this->regenerate($includeTests);
} }
} }
@ -104,36 +102,37 @@ class ThemeManifest implements ThemeList
* Generate a unique cache key to avoid manifest cache collisions. * Generate a unique cache key to avoid manifest cache collisions.
* We compartmentalise based on the base path, the given project, and whether * We compartmentalise based on the base path, the given project, and whether
* or not we intend to include tests. * or not we intend to include tests.
*
* @param bool $includeTests
* @return string * @return string
*/ */
public function getCacheKey() public function getCacheKey($includeTests = false)
{ {
return sha1(sprintf( return sha1(sprintf(
"manifest-%s-%s-%u", "manifest-%s-%s-%u",
$this->base, $this->base,
$this->project, $this->project,
$this->tests $includeTests
)); ));
} }
public function getThemes() public function getThemes()
{ {
if ($this->themes === null) {
$this->init();
}
return $this->themes; return $this->themes;
} }
/** /**
* Regenerates the manifest by scanning the base path. * Regenerates the manifest by scanning the base path.
*
* @param bool $includeTests
*/ */
public function regenerate() public function regenerate($includeTests = false)
{ {
$finder = new ManifestFileFinder(); $finder = new ManifestFileFinder();
$finder->setOptions(array( $finder->setOptions(array(
'include_themes' => false, 'include_themes' => false,
'ignore_dirs' => array('node_modules', THEMES_DIR), 'ignore_dirs' => array('node_modules', THEMES_DIR),
'ignore_tests' => !$this->tests, 'ignore_tests' => !$includeTests,
'dir_callback' => array($this, 'handleDirectory') 'dir_callback' => array($this, 'handleDirectory')
)); ));
@ -172,16 +171,4 @@ class ThemeManifest implements ThemeList
array_push($this->themes, $path); 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();
}
}
} }

View File

@ -1,172 +1,7 @@
<?php <?php
/** /**
* Configure SilverStripe from the environment variables. * @deprecated 4.0...5.0
* Usage: Put "require_once('conf/ConfigureFromEnv.php');" into your _config.php file.
* *
* If you include this file, you will be able to use the following variables to control * Placeholder empty file
* 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.
*/ */
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
);

28
src/includes/autoload.php Normal file
View File

@ -0,0 +1,28 @@
<?php
// Check PHP version
if (version_compare(phpversion(), '5.6.0', '<')) {
header($_SERVER['SERVER_PROTOCOL'] . " 500 Server Error");
echo str_replace(
'$PHPVersion',
phpversion(),
file_get_contents(__DIR__ . "/../Dev/Install/php5-required.html")
);
die();
}
// 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");
});

View File

@ -27,6 +27,8 @@ use SilverStripe\Control\Util\IPUtils;
* headers from the given client are trustworthy (e.g. from a reverse proxy). * headers from the given client are trustworthy (e.g. from a reverse proxy).
*/ */
require_once __DIR__ . '/functions.php';
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// ENVIRONMENT CONFIG // ENVIRONMENT CONFIG
@ -57,15 +59,17 @@ if (!defined('BASE_PATH')) {
// Allow a first class env var to be set that disables .env file loading // Allow a first class env var to be set that disables .env file loading
if (!getenv('SS_IGNORE_DOT_ENV')) { if (!getenv('SS_IGNORE_DOT_ENV')) {
foreach ([ BASE_PATH, dirname(BASE_PATH) ] as $path) { call_user_func(function () {
try { foreach ([BASE_PATH, dirname(BASE_PATH)] as $path) {
(new Dotenv($path))->load(); try {
} catch (InvalidPathException $e) { (new Dotenv($path))->load();
// no .env found - no big deal } catch (InvalidPathException $e) {
continue; // no .env found - no big deal
continue;
}
break;
} }
break; });
}
} }
/** /**
@ -142,11 +146,7 @@ if (defined('CUSTOM_INCLUDE_PATH')) {
set_include_path(CUSTOM_INCLUDE_PATH . PATH_SEPARATOR . get_include_path()); set_include_path(CUSTOM_INCLUDE_PATH . PATH_SEPARATOR . get_include_path());
} }
/** // Define the temporary folder if it wasn't defined yet
* Define the temporary folder if it wasn't defined yet
*/
require_once __DIR__ . '/TempPath.php';
if (!defined('TEMP_FOLDER')) { if (!defined('TEMP_FOLDER')) {
define('TEMP_FOLDER', getTempFolder(BASE_PATH)); define('TEMP_FOLDER', getTempFolder(BASE_PATH));
} }

View File

@ -1,124 +1,8 @@
<?php <?php
use SilverStripe\Core\Cache\ManifestCacheFactory;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\CoreConfigFactory;
use SilverStripe\Core\Config\ConfigLoader;
use SilverStripe\Core\Injector\Injector; 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\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 // HELPER FUNCTIONS
@ -156,23 +40,23 @@ function project()
} }
/** /**
* This is the main translator function. Returns the string defined by $entity according to the * This is the main translator function. Returns the string defined by $entity according to the
* currently set locale. * currently set locale.
* *
* Also supports pluralisation of strings. Pass in a `count` argument, as well as a * Also supports pluralisation of strings. Pass in a `count` argument, as well as a
* default value with `|` pipe-delimited options for each plural form. * default value with `|` pipe-delimited options for each plural form.
* *
* @param string $entity Entity that identifies the string. It must be in the 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 * "Namespace.Entity" where Namespace will be usually the class name where this
* string is used and Entity identifies the string inside the namespace. * string is used and Entity identifies the string inside the namespace.
* @param mixed $arg,... Additional arguments are parsed as such: * @param mixed $arg,... Additional arguments are parsed as such:
* - Next string argument is a default. Pass in a `|` pipe-delimeted value with `{count}` * - Next string argument is a default. Pass in a `|` pipe-delimeted value with `{count}`
* to do pluralisation. * to do pluralisation.
* - Any other string argument after default is context for i18nTextCollector * - 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` * - Any array argument in any order is an injection parameter list. Pass in a `count`
* injection parameter to pluralise. * injection parameter to pluralise.
* @return string * @return string
*/ */
function _t($entity, $arg = null) function _t($entity, $arg = null)
{ {
// Pass args directly to handle deprecation // Pass args directly to handle deprecation
@ -261,11 +145,11 @@ function translate_memstring($memString)
{ {
switch (strtolower(substr($memString, -1))) { switch (strtolower(substr($memString, -1))) {
case "k": case "k":
return round(substr($memString, 0, -1)*1024); return round(substr($memString, 0, -1) * 1024);
case "m": case "m":
return round(substr($memString, 0, -1)*1024*1024); return round(substr($memString, 0, -1) * 1024 * 1024);
case "g": case "g":
return round(substr($memString, 0, -1)*1024*1024*1024); return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
default: default:
return round($memString); return round($memString);
} }
@ -322,3 +206,114 @@ function get_increase_time_limit_max()
global $_increase_time_limit_max; global $_increase_time_limit_max;
return $_increase_time_limit_max; return $_increase_time_limit_max;
} }
/**
* 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;
}

View File

@ -5,7 +5,7 @@
// Connect to database // Connect to database
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
require_once __DIR__ . '/../../src/Core/Core.php'; require_once __DIR__ . '/../../src/Core/functions.php';
require_once __DIR__ . '/../php/Control/FakeController.php'; require_once __DIR__ . '/../php/Control/FakeController.php';
// Bootstrap a mock project configuration // Bootstrap a mock project configuration

View File

@ -3,8 +3,9 @@
namespace SilverStripe\Control\Tests; namespace SilverStripe\Control\Tests;
use InvalidArgumentException; use InvalidArgumentException;
use PHPUnit_Framework_Error; use SilverStripe\Control\Controller;
use SilverStripe\Control\RequestHandler; use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\Tests\ControllerTest\AccessBaseController; use SilverStripe\Control\Tests\ControllerTest\AccessBaseController;
use SilverStripe\Control\Tests\ControllerTest\AccessSecuredController; use SilverStripe\Control\Tests\ControllerTest\AccessSecuredController;
use SilverStripe\Control\Tests\ControllerTest\AccessWildcardSecuredController; use SilverStripe\Control\Tests\ControllerTest\AccessWildcardSecuredController;
@ -15,13 +16,8 @@ use SilverStripe\Control\Tests\ControllerTest\IndexSecuredController;
use SilverStripe\Control\Tests\ControllerTest\SubController; use SilverStripe\Control\Tests\ControllerTest\SubController;
use SilverStripe\Control\Tests\ControllerTest\TestController; use SilverStripe\Control\Tests\ControllerTest\TestController;
use SilverStripe\Control\Tests\ControllerTest\UnsecuredController; 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\Deprecation;
use SilverStripe\Dev\FunctionalTest; use SilverStripe\Dev\FunctionalTest;
use SilverStripe\ORM\DataModel;
use SilverStripe\Security\Member; use SilverStripe\Security\Member;
use SilverStripe\Security\Security; use SilverStripe\Security\Security;
use SilverStripe\View\SSViewer; use SilverStripe\View\SSViewer;
@ -539,7 +535,7 @@ class ControllerTest extends FunctionalTest
/* Handle the request to create conditions where improperly passing the action to the viewer might fail */ /* Handle the request to create conditions where improperly passing the action to the viewer might fail */
$controller = new ControllerTest\ContainerController(); $controller = new ControllerTest\ContainerController();
try { try {
$controller->handleRequest($request, DataModel::inst()); $controller->handleRequest($request);
} catch (ControllerTest\SubController_Exception $e) { } catch (ControllerTest\SubController_Exception $e) {
$this->fail($e->getMessage()); $this->fail($e->getMessage());
} }

View File

@ -5,9 +5,7 @@ namespace SilverStripe\Control\Tests\DirectorTest;
use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\RequestFilter; use SilverStripe\Control\RequestFilter;
use SilverStripe\Control\Session;
use SilverStripe\Dev\TestOnly; use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataModel;
class TestRequestFilter implements RequestFilter, TestOnly class TestRequestFilter implements RequestFilter, TestOnly
{ {
@ -17,22 +15,24 @@ class TestRequestFilter implements RequestFilter, TestOnly
public $failPre = false; public $failPre = false;
public $failPost = false; public $failPost = false;
public function preRequest(HTTPRequest $request, Session $session, DataModel $model) public function preRequest(HTTPRequest $request)
{ {
++$this->preCalls; ++$this->preCalls;
if ($this->failPre) { if ($this->failPre) {
return false; return false;
} }
return true;
} }
public function postRequest(HTTPRequest $request, HTTPResponse $response, DataModel $model) public function postRequest(HTTPRequest $request, HTTPResponse $response)
{ {
++$this->postCalls; ++$this->postCalls;
if ($this->failPost) { if ($this->failPost) {
return false; return false;
} }
return true;
} }
public function reset() public function reset()

View File

@ -16,14 +16,11 @@ class FakeController extends Controller
{ {
parent::__construct(); parent::__construct();
$session = Injector::inst()->create(Session::class, isset($_SESSION) ? $_SESSION : array());
$this->setSession($session);
$this->pushCurrent(); $this->pushCurrent();
$session = new Session(isset($_SESSION) ? $_SESSION : array());
$request = new HTTPRequest('GET', '/'); $request = new HTTPRequest('GET', '/');
$request->setSession($session);
$this->setRequest($request); $this->setRequest($request);
$this->setResponse(new HTTPResponse()); $this->setResponse(new HTTPResponse());
$this->doInit(); $this->doInit();

View File

@ -134,7 +134,7 @@ class ParameterConfirmationTokenTest extends SapphireTest
$_SERVER['HTTP_HOST'] = $host; $_SERVER['HTTP_HOST'] = $host;
ParameterConfirmationToken::$alternateBaseURL = $base; ParameterConfirmationToken::$alternateBaseURL = $base;
$this->assertEquals('http://'.implode('/', $urlAnswer) . $urlSlash, $token->currentAbsoluteURL()); $this->assertEquals('http://'.implode('/', $urlAnswer) . $urlSlash, $token->currentURL());
} }
} }
} }

View File

@ -11,8 +11,8 @@ use SilverStripe\Dev\TestOnly;
class ParameterConfirmationTokenTest_Token extends ParameterConfirmationToken implements TestOnly class ParameterConfirmationTokenTest_Token extends ParameterConfirmationToken implements TestOnly
{ {
public function currentAbsoluteURL() public function currentURL()
{ {
return parent::currentAbsoluteURL(); return parent::currentURL();
} }
} }

View File

@ -118,10 +118,10 @@ class GridFieldDeleteActionTest extends SapphireTest
if (Security::getCurrentUser()) { if (Security::getCurrentUser()) {
Security::setCurrentUser(null); Security::setCurrentUser(null);
} }
$this->setExpectedException(ValidationException::class); $this->expectException(ValidationException::class);
$stateID = 'testGridStateActionField'; $stateID = 'testGridStateActionField';
Session::set( Controller::curr()->getRequest()->getSession()->set(
$stateID, $stateID,
array( array(
'grid' => '', 'grid' => '',

View File

@ -25,6 +25,7 @@ use SilverStripe\Forms\Tests\GridField\GridFieldTest\Permissions;
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Player; use SilverStripe\Forms\Tests\GridField\GridFieldTest\Player;
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Team; use SilverStripe\Forms\Tests\GridField\GridFieldTest\Team;
use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\ArrayList;
use SilverStripe\Security\Group;
use SilverStripe\Security\Member; use SilverStripe\Security\Member;
class GridFieldTest extends SapphireTest class GridFieldTest extends SapphireTest
@ -115,9 +116,9 @@ class GridFieldTest extends SapphireTest
public function testGridFieldModelClass() public function testGridFieldModelClass()
{ {
$obj = new GridField('testfield', 'testfield', Member::get()); $obj = new GridField('testfield', 'testfield', Member::get());
$this->assertEquals('SilverStripe\\Security\\Member', $obj->getModelClass(), 'Should return Member'); $this->assertEquals(Member::class, $obj->getModelClass(), 'Should return Member');
$obj->setModelClass('SilverStripe\\ORM\\DataModel'); $obj->setModelClass(Group::class);
$this->assertEquals('SilverStripe\\ORM\\DataModel', $obj->getModelClass(), 'Should return Member'); $this->assertEquals(Group::class, $obj->getModelClass(), 'Should return Group');
} }
/** /**

View File

@ -79,15 +79,16 @@ class SecurityTest extends FunctionalTest
$controller = new SecurityTest\NullController(); $controller = new SecurityTest\NullController();
$controller->setResponse(new HTTPResponse()); $controller->setResponse(new HTTPResponse());
$session = Controller::curr()->getRequest()->getSession();
Security::permissionFailure($controller, array('default' => 'Oops, not allowed')); 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 // Test that config values are used correctly
Config::inst()->update(Security::class, 'default_message_set', 'stringvalue'); Config::inst()->update(Security::class, 'default_message_set', 'stringvalue');
Security::permissionFailure($controller); Security::permissionFailure($controller);
$this->assertEquals( $this->assertEquals(
'stringvalue', 'stringvalue',
Session::get('Security.Message.message'), $session->get('Security.Message.message'),
'Default permission failure message value was not present' 'Default permission failure message value was not present'
); );
@ -96,7 +97,7 @@ class SecurityTest extends FunctionalTest
Security::permissionFailure($controller); Security::permissionFailure($controller);
$this->assertEquals( $this->assertEquals(
'arrayvalue', 'arrayvalue',
Session::get('Security.Message.message'), $session->get('Security.Message.message'),
'Default permission failure message value was not present' 'Default permission failure message value was not present'
); );
@ -474,14 +475,14 @@ class SecurityTest extends FunctionalTest
); );
} }
} }
$msg = _t( $msg = _t(
'SilverStripe\\Security\\Member.ERRORLOCKEDOUT2', 'SilverStripe\\Security\\Member.ERRORLOCKEDOUT2',
'Your account has been temporarily disabled because of too many failed attempts at ' . 'Your account has been temporarily disabled because of too many failed attempts at ' .
'logging in. Please try again in {count} minutes.', 'logging in. Please try again in {count} minutes.',
null, null,
array('count' => Member::config()->lock_out_delay_mins) array('count' => Member::config()->lock_out_delay_mins)
); );
$this->assertHasMessage($msg); $this->assertHasMessage($msg);
$this->doTestLoginForm('testuser@example.com', '1nitialPassword'); $this->doTestLoginForm('testuser@example.com', '1nitialPassword');
@ -563,14 +564,14 @@ class SecurityTest extends FunctionalTest
$attempt = DataObject::get_one( $attempt = DataObject::get_one(
LoginAttempt::class, LoginAttempt::class,
array( array(
'"LoginAttempt"."Email"' => 'testuser@example.com' '"LoginAttempt"."Email"' => 'testuser@example.com'
) )
); );
$this->assertTrue(is_object($attempt)); $this->assertTrue(is_object($attempt));
$member = DataObject::get_one( $member = DataObject::get_one(
Member::class, Member::class,
array( array(
'"Member"."Email"' => 'testuser@example.com' '"Member"."Email"' => 'testuser@example.com'
) )
); );
$this->assertEquals($attempt->Status, 'Failure'); $this->assertEquals($attempt->Status, 'Failure');