API Replace SS_HOST with SS_BASE_URL

API Remove Director::$test_servers / $dev_servers
API Remove MODULES_PATH / MODULES_DIR constants
ENHANCEMENT Injector backtick syntax now supports environment variables as well as constants
Fixes #6588
This commit is contained in:
Damian Mooyman 2017-04-13 13:33:29 +12:00 committed by Sam Minnée
parent fdd9ad6dbc
commit 2548bfba1e
8 changed files with 277 additions and 285 deletions

View File

@ -83,8 +83,8 @@ SilverStripe core environment variables are listed here, though you're free to d
| `SS_TRUSTED_PROXY_PROTOCOL_HEADER` | Used to define the proxy header to be used to determine HTTPS status |
| `SS_TRUSTED_PROXY_IP_HEADER` | Used to define the proxy header to be used to determine request IPs |
| `SS_TRUSTED_PROXY_HOST_HEADER` | Used to define the proxy header to be used to determine the requested host name |
| `SS_TRUSTED_PROXY_IPS` | IP address or CIDR range to trust proxy headers from |
| `SS_TRUSTED_PROXY_IPS` | IP address or CIDR range to trust proxy headers from. If left blank no proxy headers are trusted. Can be set to 'none' (trust none) or '*' (trust all) |
| `SS_ALLOWED_HOSTS` | A comma deliminated list of hostnames the site is allowed to respond to |
| `SS_MANIFESTCACHE` | The manifest cache to use (defaults to file based caching). Must be a CacheInterface or CacheFactory class name |
| `SS_IGNORE_DOT_ENV` | If set the .env file will be ignored. This is good for live to mitigate any performance implications of loading the .env file |
| `SS_HOST` | The hostname to use when it isn't determinable by other means (eg: for CLI commands) |
| `SS_BASE_URL` | The url to use when it isn't determinable by other means (eg: for CLI commands) |

View File

@ -44,20 +44,12 @@ This currently only works on UNIX like systems, not on Windows.
Sometimes SilverStripe needs to know the URL of your site. For example, when sending an email or generating static
files. When you're visiting the site in a web browser this is easy to work out, but when executing scripts on the
command line, it has no way of knowing. To work this out, there are several ways to resolve this. You can set alternate
base URLs, hosts, and protocol.
command line, it has no way of knowing.
eg:
```yml
SilverStripe\Control\Director:
alternate_base_url: 'https://example.com/'
```
Alternatively you can use the `SS_HOST` environment variable to set a fallback hostname:
You can use the `SS_BASE_URL` environment variable to specify this.
```
SS_HOST="localhost"
SS_BASE_URL="http://localhost/base-url"
```
### Usage

View File

@ -141,7 +141,8 @@ For example, if you have the below `_ss_environment.php` file, your `.env` would
SS_ENVIRONMENT_TYPE="dev"
SS_DEFAULT_ADMIN_USERNAME="admin"
SS_DEFAULT_ADMIN_PASSWORD="password"
SS_HOST="localhost"
SS_BASE_URL="http://localhost/"
### Database
SS_DATABASE_CHOOSE_NAME="true"
SS_DATABASE_CLASS="MySQLDatabase"
@ -154,7 +155,7 @@ The removal of the `_ss_environment.php` file means that conditional logic is no
variable set-up process. This generally encouraged bad practice and should be avoided. If you still require conditional
logic early in the bootstrap, this is best placed in the `_config.php` files.
Note also that `$_FILE_TO_URL_MAPPING` has been removed and replaced with `SS_HOST` env var.
Note also that `$_FILE_TO_URL_MAPPING` has been removed and replaced with `SS_BASE_URL` env var.
See [Environment Management docs](/getting-started/environment_management/) for full details.
@ -1200,6 +1201,19 @@ After (`mysite/_config/config.yml`):
* `getsubtree()` -> moved to `CMSMain`
* `updatetreenodes()` -> moved to `CMSMain`
* `savetreenodes()` -> moved to `CMSMain`
* Some `Director` API have been removed.
* $dev_servers
* $test_servers
* $urlParams and setUrlParams()
* `Director.alternate_host` removed. Use `Director.alternate_base_url` instead.
* `Director.alternate_protocol` removed. Use `Director.alternate_base_url` instead.
* Global enviromnent variables changed:
* 'BlockUntrustedIPS' env setting has been removed.
All IPs are untrusted unless `SS_TRUSTED_PROXY_IPS` is set to '*'
See [Environment Management docs](/getting-started/environment_management/) for full details.
* `MODULES_PATH` removed
* `MODULES_DIR` removed
* `SS_HOST` removed. Use `SS_BASE_URL` instead.
#### <a name="overview-general-removed"></a>General and Core Removed API

View File

@ -57,8 +57,8 @@ Example `.env`:
SS_DEFAULT_ADMIN_USERNAME="admin"
SS_DEFAULT_ADMIN_PASSWORD="password"
# Basic CLI hostname
SS_HOST="localhost";
# Basic CLI request url default
SS_BASE_URL="http://localhost/"
You will also need to be assigned the following permissions. Contact one of the SS staff from

View File

@ -2,6 +2,7 @@
namespace SilverStripe\Control;
use InvalidArgumentException;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable;
@ -54,62 +55,49 @@ class Director implements TemplateGlobalProvider
const REQUEST = 'REQUEST';
/**
* @config
* @var array
*/
static private $urlParams;
/**
* @var array
*/
static private $rules = array();
private static $rules = array();
/**
* Set current page
*
* @internal
* @var SiteTree
*/
private static $current_page;
/**
* @config
*
* @var string
*/
private static $alternate_base_folder;
/**
* @config
*
* @var array
*/
protected static $dev_servers = array();
/**
* @config
*
* @var array
*/
protected static $test_servers = array();
/**
* Setting this explicitly specifies the protocol ("http" or "https") used, overriding the normal
* behaviour of Director::is_https introspecting it from the request. False values imply default
* introspection.
* Force the base_url to a specific value.
* If assigned, default_base_url and the value in the $_SERVER
* global is ignored.
* Supports back-ticked vars; E.g. '`SS_BASE_URL`'
*
* @config
*
* @var string
*/
private static $alternate_protocol;
/**
* @config
*
* @var string
*/
private static $alternate_base_url;
/**
* @config
* Base url to populate if cannot be determined otherwise.
* Supports back-ticked vars; E.g. '`SS_BASE_URL`'
*
* @config
* @var string
*/
private static $default_base_url = '`SS_BASE_URL`';
/**
* Assigned environment type
*
* @internal
* @var string
*/
protected static $environment_type;
@ -279,6 +267,7 @@ class Director implements TemplateGlobalProvider
// These are needed so that calling Director::test() does not muck with whoever is calling it.
// Really, it's some inappropriate coupling and should be resolved by making less use of statics.
$oldReadingMode = null;
if (class_exists(Versioned::class)) {
$oldReadingMode = Versioned::get_reading_mode();
}
@ -289,11 +278,11 @@ class Director implements TemplateGlobalProvider
}
if (!$session) {
$session = Injector::inst()->create('SilverStripe\\Control\\Session', array());
$session = Session::create([]);
}
$cookieJar = $cookies instanceof Cookie_Backend
? $cookies
: Injector::inst()->createWithArgs('SilverStripe\\Control\\Cookie_Backend', array($cookies ?: array()));
: Injector::inst()->createWithArgs(Cookie_Backend::class, array($cookies ?: []));
// Back up the current values of the superglobals
$existingRequestVars = isset($_REQUEST) ? $_REQUEST : array();
@ -301,44 +290,13 @@ class Director implements TemplateGlobalProvider
$existingPostVars = isset($_POST) ? $_POST : array();
$existingSessionVars = isset($_SESSION) ? $_SESSION : array();
$existingCookies = isset($_COOKIE) ? $_COOKIE : array();
$existingServer = isset($_SERVER) ? $_SERVER : array();
$existingServer = isset($_SERVER) ? $_SERVER : array();
$existingRequirementsBackend = Requirements::backend();
Cookie::config()->update('report_errors', false);
Requirements::set_backend(Requirements_Backend::create());
// Set callback to invoke prior to return
$onCleanup = function () use (
$existingRequestVars,
$existingGetVars,
$existingPostVars,
$existingSessionVars,
$existingCookies,
$existingServer,
$existingRequirementsBackend,
$oldReadingMode
) {
// Restore the super globals
$_REQUEST = $existingRequestVars;
$_GET = $existingGetVars;
$_POST = $existingPostVars;
$_SESSION = $existingSessionVars;
$_COOKIE = $existingCookies;
$_SERVER = $existingServer;
Requirements::set_backend($existingRequirementsBackend);
// These are needed so that calling Director::test() does not muck with whoever is calling it.
// Really, it's some inappropriate coupling and should be resolved by making less use of statics
if (class_exists(Versioned::class)) {
Versioned::set_reading_mode($oldReadingMode);
}
Injector::unnest(); // Restore old CookieJar, etc
Config::unnest();
};
if (strpos($url, '#') !== false) {
$url = substr($url, 0, strpos($url, '#'));
}
@ -370,7 +328,7 @@ class Director implements TemplateGlobalProvider
$_POST = (array) $postVars;
$_SESSION = $session ? $session->inst_getAll() : array();
$_COOKIE = $cookieJar->getAll(false);
Injector::inst()->registerService($cookieJar, 'SilverStripe\\Control\\Cookie_Backend');
Injector::inst()->registerService($cookieJar, Cookie_Backend::class);
$_SERVER['REQUEST_URI'] = Director::baseURL() . $urlWithQuerystring;
$request = new HTTPRequest($httpMethod, $url, $getVars, $postVars, $body);
@ -380,38 +338,56 @@ class Director implements TemplateGlobalProvider
}
}
// Pre-request filtering
// @see issue #2517
$model = DataModel::inst();
$output = Injector::inst()->get('SilverStripe\\Control\\RequestProcessor')->preRequest($request, $session, $model);
if ($output === false) {
$onCleanup();
throw new HTTPResponse_Exception(_t('Director.INVALID_REQUEST', 'Invalid request'), 400);
}
// TODO: Pass in the DataModel
$result = Director::handleRequest($request, $session, $model);
// Ensure that the result is an HTTPResponse object
if (is_string($result)) {
if (substr($result, 0, 9) == 'redirect:') {
$response = new HTTPResponse();
$response->redirect(substr($result, 9));
$result = $response;
} else {
$result = new HTTPResponse($result);
try {
// Pre-request filtering
$model = DataModel::inst();
$requestProcessor = Injector::inst()->get(RequestProcessor::class);
$output = $requestProcessor->preRequest($request, $session, $model);
if ($output === false) {
throw new HTTPResponse_Exception(_t('Director.INVALID_REQUEST', 'Invalid request'), 400);
}
}
$output = Injector::inst()->get('SilverStripe\\Control\\RequestProcessor')->postRequest($request, $result, $model);
if ($output === false) {
$onCleanup();
throw new HTTPResponse_Exception("Invalid response");
}
// Process request
$result = Director::handleRequest($request, $session, $model);
// Return valid response
$onCleanup();
return $result;
// Ensure that the result is an HTTPResponse object
if (is_string($result)) {
if (substr($result, 0, 9) == 'redirect:') {
$response = new HTTPResponse();
$response->redirect(substr($result, 9));
$result = $response;
} else {
$result = new HTTPResponse($result);
}
}
$output = $requestProcessor->postRequest($request, $result, $model);
if ($output === false) {
throw new HTTPResponse_Exception("Invalid response");
}
// Return valid response
return $result;
} finally {
// Restore the super globals
$_REQUEST = $existingRequestVars;
$_GET = $existingGetVars;
$_POST = $existingPostVars;
$_SESSION = $existingSessionVars;
$_COOKIE = $existingCookies;
$_SERVER = $existingServer;
Requirements::set_backend($existingRequirementsBackend);
// These are needed so that calling Director::test() does not muck with whoever is calling it.
// Really, it's some inappropriate coupling and should be resolved by making less use of statics
if (class_exists(Versioned::class)) {
Versioned::set_reading_mode($oldReadingMode);
}
Injector::unnest(); // Restore old CookieJar, etc
Config::unnest();
}
}
/**
@ -452,7 +428,6 @@ class Director implements TemplateGlobalProvider
} else {
// Find the controller name
$controller = $arguments['Controller'];
Director::$urlParams = $arguments;
$controllerObj = Injector::inst()->create($controller);
$controllerObj->setSession($session);
@ -475,16 +450,6 @@ class Director implements TemplateGlobalProvider
return new HTTPResponse('No URL rule was matched', 404);
}
/**
* Set url parameters (should only be called internally by RequestHandler->handleRequest()).
*
* @param array $params
*/
public static function setUrlParams($params)
{
Director::$urlParams = $params;
}
/**
* Return the {@link SiteTree} object that is currently being viewed. If there is no SiteTree
* object to return, then this will return the current controller.
@ -562,60 +527,66 @@ class Director implements TemplateGlobalProvider
/**
* A helper to determine the current hostname used to access the site.
* The following are used to determine the host (in order)
* - Director.alternate_host
* - Director.alternate_base_url (if it contains a domain name)
* - Trusted proxy headers
* - HTTP Host header
* - SS_HOST env var
* - SS_BASE_URL env var
* - SERVER_NAME
* - gethostname()
*
* @param bool $respectConfig Set to false to ignore config override
* (Necessary for checking host pre-config)
* @return string
*/
public static function host($respectConfig = true)
public static function host()
{
$headerOverride = false;
if (TRUSTED_PROXY) {
$headers = (getenv('SS_TRUSTED_PROXY_HOST_HEADER')) ? array(getenv('SS_TRUSTED_PROXY_HOST_HEADER')) : null;
if (!$headers) {
// Backwards compatible defaults
$headers = array('HTTP_X_FORWARDED_HOST');
// Check if overridden by alternate_base_url
if ($baseURL = self::config()->get('alternate_base_url')) {
$baseURL = Injector::inst()->convertServiceProperty($baseURL);
$host = parse_url($baseURL, PHP_URL_HOST);
if ($host) {
return $host;
}
}
// Validate proxy-specific headers
if (TRUSTED_PROXY) {
// Check headers to validate
$headers = getenv('SS_TRUSTED_PROXY_HOST_HEADER')
? explode(',', getenv('SS_TRUSTED_PROXY_HOST_HEADER'))
: ['HTTP_X_FORWARDED_HOST']; // Backwards compatible defaults
foreach ($headers as $header) {
if (!empty($_SERVER[$header])) {
// Get the first host, in case there's multiple separated through commas
$headerOverride = strtok($_SERVER[$header], ',');
break;
return strtok($_SERVER[$header], ',');
}
}
}
if ($respectConfig) {
if ($host = Director::config()->uninherited('alternate_host')) {
return $host;
}
if ($baseURL = Director::config()->uninherited('alternate_base_url')) {
if (preg_match('/^(http[^:]*:\/\/[^\/]+)(\/|$)/', $baseURL, $matches)) {
return parse_url($baseURL, PHP_URL_HOST);
}
}
}
if ($headerOverride) {
return $headerOverride;
}
// Check given header
if (isset($_SERVER['HTTP_HOST'])) {
return $_SERVER['HTTP_HOST'];
}
if ($host = getenv('SS_HOST')) {
return $host;
// Check base url
if ($baseURL = self::config()->uninherited('default_base_url')) {
$baseURL = Injector::inst()->convertServiceProperty($baseURL);
$host = parse_url($baseURL, PHP_URL_HOST);
if ($host) {
return $host;
}
}
// Legacy ss4-alpha environment var
/**
* @todo remove at 4.0.0-beta1
* @deprecated 4.0.0-beta1
*/
$legacyHostname = getenv('SS_HOST');
if ($legacyHostname) {
Deprecation::notice('4.0', 'SS_HOST will be removed in ss 4.0.0-beta1');
return $legacyHostname;
}
// Fail over to server_name (least reliable)
return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : gethostname();
}
@ -647,64 +618,73 @@ class Director implements TemplateGlobalProvider
*/
public static function is_https()
{
// Check override from alternate_base_url
if ($baseURL = self::config()->uninherited('alternate_base_url')) {
$baseURL = Injector::inst()->convertServiceProperty($baseURL);
$protocol = parse_url($baseURL, PHP_URL_SCHEME);
if ($protocol) {
return $protocol === 'https';
}
}
// 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');
}
$headers = getenv('SS_TRUSTED_PROXY_PROTOCOL_HEADER')
? explode(',', getenv('SS_TRUSTED_PROXY_PROTOCOL_HEADER'))
: ['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;
return true;
}
}
}
if ($protocol = Config::inst()->get('SilverStripe\\Control\\Director', 'alternate_protocol')) {
return ($protocol == 'https');
} elseif ($headerOverride) {
// Check common $_SERVER
if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) {
return true;
} elseif ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) {
return true;
} elseif (isset($_SERVER['SSL'])) {
return true;
} else {
return false;
}
if (isset($_SERVER['SSL'])) {
return true;
}
// Check default_base_url
if ($baseURL = self::config()->uninherited('default_base_url')) {
$baseURL = Injector::inst()->convertServiceProperty($baseURL);
$protocol = parse_url($baseURL, PHP_URL_SCHEME);
if ($protocol) {
return $protocol === 'https';
}
}
return false;
}
/**
* Returns the root URL for the site. It will be automatically calculated unless it is overridden
* with {@link setBaseURL()}.
* Return the root-relative url for the baseurl
*
* @return string
* @return string Root-relative url with trailing slash.
*/
public static function baseURL()
{
$alternate = Config::inst()->get('SilverStripe\\Control\\Director', 'alternate_base_url');
// Check override base_url
$alternate = self::config()->get('alternate_base_url');
if ($alternate) {
return $alternate;
} else {
$base = BASE_URL;
if ($base == '/' || $base == '/.' || $base == '\\') {
$baseURL = '/';
} else {
$baseURL = $base . '/';
}
if (defined('BASE_SCRIPT_URL')) {
return $baseURL . BASE_SCRIPT_URL;
}
return $baseURL;
$alternate = Injector::inst()->convertServiceProperty($alternate);
return rtrim(parse_url($alternate, PHP_URL_PATH), '/') . '/';
}
// Get env base url
$baseURL = rtrim(BASE_URL, '/') . '/';
// Check if BASE_SCRIPT_URL is defined
// e.g. `index.php/`
if (defined('BASE_SCRIPT_URL')) {
return $baseURL . BASE_SCRIPT_URL;
}
return $baseURL;
}
/**
@ -1122,11 +1102,6 @@ class Director implements TemplateGlobalProvider
* To help with this, SilverStripe supports the notion of an environment type. The environment
* type can be dev, test, or live.
*
* You can set it explicitly with {@link Director::set_environment_type()}. Or you can use
* {@link Director::$dev_servers} and {@link Director::$test_servers} to set it implicitly, based
* on the value of $_SERVER['HTTP_HOST']. If the HTTP_HOST value is one of the servers listed,
* then the environment type will be test or dev. Otherwise, the environment type will be 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.
@ -1145,7 +1120,7 @@ class Director implements TemplateGlobalProvider
public static function set_environment_type($environment)
{
if (!in_array($environment, ['dev', 'test', 'live'])) {
throw new \InvalidArgumentException(
throw new InvalidArgumentException(
"Director::set_environment_type passed '$environment'. It should be passed dev, test, or live"
);
}
@ -1175,14 +1150,6 @@ class Director implements TemplateGlobalProvider
return $env;
}
// Check if we are running on one of the test servers
// Note: Bypass config for checking environment type
if (in_array(static::host(false), self::$dev_servers)) {
return 'dev';
}
if (in_array(static::host(false), self::$test_servers)) {
return 'test';
}
return 'live';
}

View File

@ -1,4 +1,9 @@
<?php
use Dotenv\Dotenv;
use Dotenv\Exception\InvalidPathException;
use SilverStripe\Control\Util\IPUtils;
/**
* This file is the Framework constants bootstrap. It will prepare some basic common constants.
*
@ -12,8 +17,6 @@
* See Director::baseFolder(). Can be overwritten by Config::inst()->update('Director', 'alternate_base_folder', ).
* - TEMP_FOLDER: Absolute path to temporary folder, used for manifest and template caches. Example: "/var/tmp"
* See getTempFolder(). No trailing slash.
* - MODULES_DIR: Not used at the moment
* - MODULES_PATH: Not used at the moment
* - THEMES_DIR: Path relative to webroot, e.g. "themes"
* - THEMES_PATH: Absolute filepath, e.g. "/var/www/my-webroot/themes"
* - FRAMEWORK_DIR: Path relative to webroot, e.g. "framework"
@ -33,66 +36,53 @@
* or Client-IP HTTP headers can be trusted
*/
if (!defined('TRUSTED_PROXY')) {
$trusted = true; // will be false by default in a future release
if (getenv('BlockUntrustedProxyHeaders') // Legacy setting (reverted from documentation)
|| getenv('BlockUntrustedIPs') // Documented setting
|| getenv('SS_TRUSTED_PROXY_IPS')
) {
$trusted = false;
if (getenv('SS_TRUSTED_PROXY_IPS') !== 'none') {
if (getenv('SS_TRUSTED_PROXY_IPS') === '*') {
$trusted = true;
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
if (!class_exists('SilverStripe\\Control\\Util\\IPUtils')) {
require_once 'Control/IPUtils.php';
};
$trusted = SilverStripe\Control\Util\IPUtils::checkIP($_SERVER['REMOTE_ADDR'], explode(',', getenv('SS_TRUSTED_PROXY_IPS')));
}
define('TRUSTED_PROXY', call_user_func(function () {
$trustedIPs = getenv('SS_TRUSTED_PROXY_IPS');
if (empty($trustedIPs) || $trustedIPs === 'none') {
return false;
}
}
/**
* Declare whether or not the connecting server is a trusted proxy
*/
define('TRUSTED_PROXY', $trusted);
if ($trustedIPs === '*') {
return true;
}
// Validate IP address
if (isset($_SERVER['REMOTE_ADDR'])) {
return IPUtils::checkIP($_SERVER['REMOTE_ADDR'], explode(',', $trustedIPs));
}
return false;
}));
}
/**
* Define system paths
*/
// Determine BASE_PATH based on the composer autoloader
if (!defined('BASE_PATH')) {
foreach (debug_backtrace() as $backtraceItem) {
if (preg_match('#^(.*)\/vendor/composer/autoload_real.php#', $backtraceItem['file'], $matches)) {
define('BASE_PATH', $matches[1]);
break;
define('BASE_PATH', call_user_func(function () {
// Determine BASE_PATH based on the composer autoloader
foreach (debug_backtrace() as $backtraceItem) {
if (isset($backtraceItem['file']) && preg_match(
'#^(?<base>.*)(/|\\\\)vendor(/|\\\\)composer(/|\\\\)autoload_real.php#',
$backtraceItem['file'],
$matches
)) {
return realpath($matches['base']) ?: DIRECTORY_SEPARATOR;
}
}
}
}
// Determine BASE_PATH by assuming that this file is framework/src/Core/Constants.php
if (!defined('BASE_PATH')) {
// we can then determine the base path
$candidateBasePath = rtrim(dirname(dirname(dirname(dirname(__FILE__)))), DIRECTORY_SEPARATOR);
// We can't have an empty BASE_PATH. Making it / means that double-slashes occur in places but that's benign.
// This likely only happens on chrooted environemnts
if ($candidateBasePath == '') {
$candidateBasePath = DIRECTORY_SEPARATOR;
}
define('BASE_PATH', $candidateBasePath);
// Determine BASE_PATH by assuming that this file is framework/src/Core/Constants.php
// we can then determine the base path
$candidateBasePath = rtrim(dirname(dirname(dirname(__DIR__))), DIRECTORY_SEPARATOR);
// We can't have an empty BASE_PATH. Making it / means that double-slashes occur in places but that's benign.
// This likely only happens on chrooted environemnts
return $candidateBasePath ?: DIRECTORY_SEPARATOR;
}));
}
// Allow a first class env var to be set that disables .env file loading
if (!getenv('SS_IGNORE_DOT_ENV')) {
foreach (array(
BASE_PATH,
dirname(BASE_PATH),
) as $path) {
foreach ([ BASE_PATH, dirname(BASE_PATH) ] as $path) {
try {
(new \Dotenv\Dotenv($path))->load();
} catch (\Dotenv\Exception\InvalidPathException $e) {
(new Dotenv($path))->load();
} catch (InvalidPathException $e) {
// no .env found - no big deal
continue;
}
@ -101,48 +91,50 @@ if (!getenv('SS_IGNORE_DOT_ENV')) {
}
if (!defined('BASE_URL')) {
// Determine the base URL by comparing SCRIPT_NAME to SCRIPT_FILENAME and getting common elements
$path = realpath($_SERVER['SCRIPT_FILENAME']);
if (substr($path, 0, strlen(BASE_PATH)) == BASE_PATH) {
$urlSegmentToRemove = substr($path, strlen(BASE_PATH));
if (substr($_SERVER['SCRIPT_NAME'], -strlen($urlSegmentToRemove)) == $urlSegmentToRemove) {
$baseURL = substr($_SERVER['SCRIPT_NAME'], 0, -strlen($urlSegmentToRemove));
define('BASE_URL', rtrim($baseURL, DIRECTORY_SEPARATOR));
define('BASE_URL', call_user_func(function () {
// Determine the base URL by comparing SCRIPT_NAME to SCRIPT_FILENAME and getting common elements
// This tends not to work on CLI
$path = realpath($_SERVER['SCRIPT_FILENAME']);
if (substr($path, 0, strlen(BASE_PATH)) == BASE_PATH) {
$urlSegmentToRemove = substr($path, strlen(BASE_PATH));
if (substr($_SERVER['SCRIPT_NAME'], -strlen($urlSegmentToRemove)) == $urlSegmentToRemove) {
$baseURL = substr($_SERVER['SCRIPT_NAME'], 0, -strlen($urlSegmentToRemove));
// Normalise slashes to '/' and rtrim('/')
return rtrim(str_replace('\\', '/', $baseURL), '/');
}
}
}
// If that didn't work, failover to the old syntax. Hopefully this isn't necessary, and maybe
// if can be phased out?
if (!defined('BASE_URL')) {
$dir = (strpos($_SERVER['SCRIPT_NAME'], 'index.php') !== false)
? dirname($_SERVER['SCRIPT_NAME'])
: dirname(dirname($_SERVER['SCRIPT_NAME']));
define('BASE_URL', rtrim($dir, DIRECTORY_SEPARATOR));
}
// Fall back to SS_BASE_URL
$base = getenv('SS_BASE_URL');
if ($base) {
// Strip relative path from SS_BASE_URL
return rtrim(parse_url($base, PHP_URL_PATH), '/');
}
// Assume no base_url
return '';
}));
}
define('MODULES_DIR', 'modules');
define('MODULES_PATH', BASE_PATH . '/' . MODULES_DIR);
define('THEMES_DIR', 'themes');
define('THEMES_PATH', BASE_PATH . '/' . THEMES_DIR);
define('THEMES_PATH', BASE_PATH . DIRECTORY_SEPARATOR . THEMES_DIR);
// Relies on this being in a subdir of the framework.
// If it isn't, or is symlinked to a folder with a different name, you must define FRAMEWORK_DIR
define('FRAMEWORK_PATH', realpath(__DIR__ . '/../../'));
if (strpos(FRAMEWORK_PATH, BASE_PATH) === 0) {
define('FRAMEWORK_DIR', trim(substr(FRAMEWORK_PATH, strlen(BASE_PATH)), DIRECTORY_SEPARATOR));
$frameworkDirSlashSuffix = FRAMEWORK_DIR ? FRAMEWORK_DIR . '/' : '';
} else {
if (strpos(FRAMEWORK_PATH, BASE_PATH) !== 0) {
throw new Exception("Path error: FRAMEWORK_PATH " . FRAMEWORK_PATH . " not within BASE_PATH " . BASE_PATH);
}
define('THIRDPARTY_DIR', $frameworkDirSlashSuffix . 'thirdparty');
define('THIRDPARTY_PATH', FRAMEWORK_PATH . '/thirdparty');
define('FRAMEWORK_DIR', trim(substr(FRAMEWORK_PATH, strlen(BASE_PATH)), DIRECTORY_SEPARATOR));
define('THIRDPARTY_DIR', FRAMEWORK_DIR ? (FRAMEWORK_DIR . '/thirdparty') : 'thirdparty');
define('THIRDPARTY_PATH', FRAMEWORK_PATH . DIRECTORY_SEPARATOR . 'thirdparty');
if (!defined('ASSETS_DIR')) {
define('ASSETS_DIR', 'assets');
}
if (!defined('ASSETS_PATH')) {
define('ASSETS_PATH', BASE_PATH . '/' . ASSETS_DIR);
define('ASSETS_PATH', BASE_PATH . DIRECTORY_SEPARATOR . ASSETS_DIR);
}
// Custom include path - deprecated
@ -153,7 +145,7 @@ if (defined('CUSTOM_INCLUDE_PATH')) {
/**
* Define the temporary folder if it wasn't defined yet
*/
require_once 'Core/TempPath.php';
require_once __DIR__ . '/TempPath.php';
if (!defined('TEMP_FOLDER')) {
define('TEMP_FOLDER', getTempFolder(BASE_PATH));

View File

@ -527,7 +527,13 @@ class Injector
// Evaluate constants surrounded by back ticks
if (preg_match('/^`(?<name>[^`]+)`$/', $value, $matches)) {
$value = defined($matches['name']) ? constant($matches['name']) : null;
if (getenv($matches['name']) !== false) {
$value = getenv($matches['name']);
} elseif (defined($matches['name'])) {
$value = constant($matches['name']);
} else {
$value = null;
}
}
return $value;

View File

@ -191,10 +191,31 @@ class DirectorTest extends SapphireTest
Director::absoluteURL('subfolder/test')
);
// absolute base URLS with subdirectory - You should end them in a /
Director::config()->set('alternate_base_url', 'http://www.example.org/relativebase/');
$_SERVER['REQUEST_URI'] = "http://www.example.org/relativebase/sub-page/";
$this->assertEquals('/relativebase/', Director::baseURL()); // Non-absolute url
$this->assertEquals('http://www.example.org/relativebase/', Director::absoluteBaseURL());
$this->assertEquals('http://www.example.org/relativebase/sub-page/', Director::absoluteURL('', Director::REQUEST));
$this->assertEquals('http://www.example.org/relativebase/', Director::absoluteURL('', Director::BASE));
$this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::ROOT));
$this->assertEquals(
'http://www.example.org/relativebase/sub-page/subfolder/test',
Director::absoluteURL('subfolder/test', Director::REQUEST)
);
$this->assertEquals(
'http://www.example.org/subfolder/test',
Director::absoluteURL('subfolder/test', Director::ROOT)
);
$this->assertEquals(
'http://www.example.org/relativebase/subfolder/test',
Director::absoluteURL('subfolder/test', Director::BASE)
);
// absolute base URLs - you should end them in a /
Director::config()->set('alternate_base_url', 'http://www.example.org/');
$_SERVER['REQUEST_URI'] = "http://www.example.org/sub-page/";
$this->assertEquals('http://www.example.org/', Director::baseURL());
$this->assertEquals('/', Director::baseURL()); // Non-absolute url
$this->assertEquals('http://www.example.org/', Director::absoluteBaseURL());
$this->assertEquals('http://www.example.org/sub-page/', Director::absoluteURL('', Director::REQUEST));
$this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::BASE));