Completed
Push — master ( d7289a...fcaa57 )
by Damian
11:50
created

Director::makeRelative()   D

Complexity

Conditions 10
Paths 12

Size

Total Lines 45
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 45
rs 4.8196
cc 10
eloc 23
nc 12
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Director is responsible for processing URLs, and providing environment information.
4
 *
5
 * The most important part of director is {@link Director::direct()}, which is passed a URL and will
6
 * execute the appropriate controller.
7
 *
8
 * Director also has a number of static methods that provide information about the environment, such as
9
 * {@link Director::$environment_type}.
10
 *
11
 * @package framework
12
 *
13
 * @subpackage control
14
 *
15
 * @see Director::direct()
16
 * @see Director::$rules
17
 * @see Director::$environment_type
18
 */
19
class Director implements TemplateGlobalProvider {
20
21
	/**
22
	 * Specifies this url is relative to the base.
23
	 *
24
	 * @var string
25
	 */
26
	const BASE = 'BASE';
27
28
	/**
29
	 * Specifies this url is relative to the site root.
30
	 *
31
	 * @var string
32
	 */
33
	const ROOT = 'ROOT';
34
35
	/**
36
	 * specifies this url is relative to the current request.
37
	 *
38
	 * @var string
39
	 */
40
	const REQUEST = 'REQUEST';
41
42
	/**
43
	 * @var array
44
	 */
45
	static private $urlParams;
46
47
	/**
48
	 * @var array
49
	 */
50
	static private $rules = array();
51
52
	/**
53
	 * @var SiteTree
54
	 */
55
	private static $current_page;
56
57
	/**
58
	 * @config
59
	 *
60
	 * @var string
61
	 */
62
	private static $alternate_base_folder;
63
64
	/**
65
	 * @config
66
	 *
67
	 * @var array
68
	 */
69
	private static $dev_servers = array();
70
71
	/**
72
	 * @config
73
	 *
74
	 * @var array
75
	 */
76
	private static $test_servers = array();
77
78
	/**
79
	 * Setting this explicitly specifies the protocol ("http" or "https") used, overriding the normal
80
	 * behaviour of Director::is_https introspecting it from the request. False values imply default
81
	 * introspection.
82
	 *
83
	 * @config
84
	 *
85
	 * @var string
86
	 */
87
	private static $alternate_protocol;
88
89
	/**
90
	 * @config
91
	 *
92
	 * @var string
93
	 */
94
	private static $alternate_base_url;
95
96
	/**
97
	 * @config
98
	 *
99
	 * @var string
100
	 */
101
	private static $environment_type;
102
103
	/**
104
	 * Add URL matching rules to the Director. The director is responsible for turning URLs into
105
	 * Controller objects.
106
	 *
107
	 * Higher $priority values will get your rule checked first. We recommend priority 100 for your
108
	 * site's rules. The built-in rules are priority 10, standard modules are priority 50.
109
	 *
110
	 * @deprecated 4.0 Use the "Director.rules" config setting instead
111
	 *
112
	 * @param int $priority
113
	 * @param array $rules
114
	 */
115
	public static function addRules($priority, $rules) {
0 ignored issues
show
Unused Code introduced by
The parameter $priority is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
116
		Deprecation::notice('4.0', 'Use the "Director.rules" config setting instead');
117
118
		Config::inst()->update('Director', 'rules', $rules);
119
	}
120
121
	/**
122
	 * Process the given URL, creating the appropriate controller and executing it.
123
	 *
124
	 * Request processing is handled as follows:
125
	 * - Director::direct() creates a new SS_HTTPResponse object and passes this to
126
	 *   Director::handleRequest().
127
	 * - Director::handleRequest($request) checks each of the Director rules and identifies a controller
128
	 *   to handle this request.
129
	 * - Controller::handleRequest($request) is then called.  This will find a rule to handle the URL,
130
	 *   and call the rule handling method.
131
	 * - RequestHandler::handleRequest($request) is recursively called whenever a rule handling method
132
	 *   returns a RequestHandler object.
133
	 *
134
	 * In addition to request processing, Director will manage the session, and perform the output of
135
	 * the actual response to the browser.
136
	 *
137
	 * @uses handleRequest() rule-lookup logic is handled by this.
138
	 * @uses Controller::run() Controller::run() handles the page logic for a Director::direct() call.
139
	 *
140
	 * @param string $url
141
	 * @param DataModel $model
142
	 *
143
	 * @throws SS_HTTPResponse_Exception
144
	 */
145
	public static function direct($url, DataModel $model) {
0 ignored issues
show
Coding Style introduced by
direct uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
direct uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
direct uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
direct uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
direct uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
146
		// Validate $_FILES array before merging it with $_POST
147
		foreach($_FILES as $k => $v) {
148
			if (is_array($v['tmp_name'])) {
149
				$v = ArrayLib::array_values_recursive($v['tmp_name']);
150
				foreach($v as $tmpFile) {
151
					if ($tmpFile && !is_uploaded_file($tmpFile)) {
152
						user_error("File upload '$k' doesn't appear to be a valid upload", E_USER_ERROR);
153
					}
154
				}
155
			} else {
156
				if ($v['tmp_name'] && !is_uploaded_file($v['tmp_name'])) {
157
					user_error("File upload '$k' doesn't appear to be a valid upload", E_USER_ERROR);
158
				}
159
			}
160
		}
161
162
		$req = new SS_HTTPRequest(
163
			(isset($_SERVER['X-HTTP-Method-Override']))
164
				? $_SERVER['X-HTTP-Method-Override']
165
				: $_SERVER['REQUEST_METHOD'],
166
			$url,
167
			$_GET,
168
			ArrayLib::array_merge_recursive((array) $_POST, (array) $_FILES),
0 ignored issues
show
Bug introduced by
It seems like \ArrayLib::array_merge_r..._POST, (array) $_FILES) targeting ArrayLib::array_merge_recursive() can also be of type null; however, SS_HTTPRequest::__construct() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
169
			@file_get_contents('php://input')
170
		);
171
172
		$headers = self::extract_request_headers($_SERVER);
173
		foreach ($headers as $header => $value) {
174
			$req->addHeader($header, $value);
175
		}
176
177
		// Initiate an empty session - doesn't initialize an actual PHP session until saved (see below)
178
		$session = Injector::inst()->create('Session', isset($_SESSION) ? $_SESSION : array());
179
180
		// Only resume a session if its not started already, and a session identifier exists
181
		if (!isset($_SESSION) && Session::request_contains_session_id()) {
182
			$session->inst_start();
183
		}
184
185
		$output = Injector::inst()->get('RequestProcessor')->preRequest($req, $session, $model);
186
187
		if ($output === false) {
188
			// @TODO Need to NOT proceed with the request in an elegant manner
189
			throw new SS_HTTPResponse_Exception(_t('Director.INVALID_REQUEST', 'Invalid request'), 400);
190
		}
191
192
		$result = Director::handleRequest($req, $session, $model);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
193
194
		// Save session data. Note that inst_save() will start/resume the session if required.
195
		$session->inst_save();
196
197
		// Return code for a redirection request
198
		if (is_string($result) && substr($result, 0, 9) == 'redirect:') {
199
			$url = substr($result, 9);
200
201
			if (Director::is_cli()) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
202
				// on cli, follow SilverStripe redirects automatically
203
				return Director::direct(
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
204
					str_replace(Director::absoluteBaseURL(), '', $url),
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
205
					DataModel::inst()
206
				);
207
			} else {
208
				$response = new SS_HTTPResponse();
209
				$response->redirect($url);
210
				$res = Injector::inst()->get('RequestProcessor')->postRequest($req, $response, $model);
211
212
				if ($res !== false) {
213
					$response->output();
214
				}
215
			}
216
		// Handle a controller
217
		} elseif ($result) {
218
			if ($result instanceof SS_HTTPResponse) {
219
				$response = $result;
220
221
			} else {
222
				$response = new SS_HTTPResponse();
223
				$response->setBody($result);
224
			}
225
226
			$res = Injector::inst()->get('RequestProcessor')->postRequest($req, $response, $model);
227
			if ($res !== false) {
228
					$response->output();
229
			} else {
230
				// @TODO Proper response here.
231
				throw new SS_HTTPResponse_Exception("Invalid response");
232
			}
233
234
235
			//$controllerObj->getSession()->inst_save();
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
236
		}
237
	}
238
239
	/**
240
	 * Test a URL request, returning a response object. This method is the counterpart of
241
	 * Director::direct() that is used in functional testing. It will execute the URL given, and
242
	 * return the result as an SS_HTTPResponse object.
243
	 *
244
	 * @uses getControllerForURL() The rule-lookup logic is handled by this.
245
	 * @uses Controller::run() Handles the page logic for a Director::direct() call.
246
	 *
247
	 * @param string $url The URL to visit.
248
	 * @param array $postVars The $_POST & $_FILES variables.
249
	 * @param array|Session $session The {@link Session} object representing the current session.
250
	 * By passing the same object to multiple  calls of Director::test(), you can simulate a persisted
251
	 * session.
252
	 * @param string $httpMethod The HTTP method, such as GET or POST.  It will default to POST if
253
	 * postVars is set, GET otherwise. Overwritten by $postVars['_method'] if present.
254
	 * @param string $body The HTTP body.
255
	 * @param array $headers HTTP headers with key-value pairs.
256
	 * @param array|Cookie_Backend $cookies to populate $_COOKIE.
257
	 * @param HTTP_Request $request The {@see HTTP_Request} object generated as a part of this request.
258
	 *
259
	 * @return SS_HTTPResponse
260
	 *
261
	 * @throws SS_HTTPResponse_Exception
262
	 */
263
	public static function test($url, $postVars = null, $session = array(), $httpMethod = null, $body = null,
0 ignored issues
show
Coding Style introduced by
test uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
test uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
test uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
test uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
test uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
test uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
264
			$headers = array(), $cookies = array(), &$request = null) {
265
266
		Config::nest();
267
		Injector::nest();
268
269
		// These are needed so that calling Director::test() does not muck with whoever is calling it.
270
		// Really, it's some inappropriate coupling and should be resolved by making less use of statics.
271
		$oldStage = Versioned::get_stage();
272
		$getVars = array();
273
274
		if (!$httpMethod) $httpMethod = ($postVars || is_array($postVars)) ? "POST" : "GET";
0 ignored issues
show
Bug Best Practice introduced by
The expression $httpMethod of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
275
276
		if (!$session) $session = Injector::inst()->create('Session', array());
277
		$cookieJar = $cookies instanceof Cookie_Backend
278
			? $cookies
279
			: Injector::inst()->createWithArgs('Cookie_Backend', array($cookies ?: array()));
280
281
		// Back up the current values of the superglobals
282
		$existingRequestVars = isset($_REQUEST) ? $_REQUEST : array();
283
		$existingGetVars = isset($_GET) ? $_GET : array();
284
		$existingPostVars = isset($_POST) ? $_POST : array();
285
		$existingSessionVars = isset($_SESSION) ? $_SESSION : array();
286
		$existingCookies = isset($_COOKIE) ? $_COOKIE : array();
287
		$existingServer	= isset($_SERVER) ? $_SERVER : array();
288
289
		$existingRequirementsBackend = Requirements::backend();
290
291
		Config::inst()->update('Cookie', 'report_errors', false);
292
		Requirements::set_backend(Injector::inst()->create('Requirements_Backend'));
293
294
		// Set callback to invoke prior to return
295
		$onCleanup = function() use(
296
			$existingRequestVars, $existingGetVars, $existingPostVars, $existingSessionVars,
297
			$existingCookies, $existingServer, $existingRequirementsBackend, $oldStage
298
		) {
299
			// Restore the super globals
300
			$_REQUEST = $existingRequestVars;
301
			$_GET = $existingGetVars;
302
			$_POST = $existingPostVars;
303
			$_SESSION = $existingSessionVars;
304
			$_COOKIE = $existingCookies;
305
			$_SERVER = $existingServer;
306
307
			Requirements::set_backend($existingRequirementsBackend);
308
309
			// These are needed so that calling Director::test() does not muck with whoever is calling it.
310
			// Really, it's some inappropriate coupling and should be resolved by making less use of statics
311
			Versioned::set_stage($oldStage);
312
313
			Injector::unnest(); // Restore old CookieJar, etc
314
			Config::unnest();
315
		};
316
317
		if (strpos($url, '#') !== false) {
318
			$url = substr($url, 0, strpos($url, '#'));
319
		}
320
321
		// Handle absolute URLs
322
		if (parse_url($url, PHP_URL_HOST)) {
323
			$bits = parse_url($url);
324
			// If a port is mentioned in the absolute URL, be sure to add that into the HTTP host
325
			if (isset($bits['port'])) {
326
				$_SERVER['HTTP_HOST'] = $bits['host'].':'.$bits['port'];
327
			} else {
328
				$_SERVER['HTTP_HOST'] = $bits['host'];
329
			}
330
		}
331
332
		// Ensure URL is properly made relative.
333
		// Example: url passed is "/ss31/my-page" (prefixed with BASE_URL), this should be changed to "my-page"
334
		$url = self::makeRelative($url);
335
336
		$urlWithQuerystring = $url;
337
		if (strpos($url, '?') !== false) {
338
			list($url, $getVarsEncoded) = explode('?', $url, 2);
339
			parse_str($getVarsEncoded, $getVars);
340
		}
341
342
		// Replace the super globals with appropriate test values
343
		$_REQUEST = ArrayLib::array_merge_recursive((array) $getVars, (array) $postVars);
344
		$_GET = (array) $getVars;
345
		$_POST = (array) $postVars;
346
		$_SESSION = $session ? $session->inst_getAll() : array();
347
		$_COOKIE = $cookieJar->getAll(false);
348
		Injector::inst()->registerService($cookieJar, 'Cookie_Backend');
349
		$_SERVER['REQUEST_URI'] = Director::baseURL() . $urlWithQuerystring;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
350
351
		$request = new SS_HTTPRequest($httpMethod, $url, $getVars, $postVars, $body);
0 ignored issues
show
Bug introduced by
It seems like $getVars can also be of type null; however, SS_HTTPRequest::__construct() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $postVars defined by parameter $postVars on line 263 can also be of type null; however, SS_HTTPRequest::__construct() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
352
		if ($headers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $headers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
353
			foreach($headers as $k => $v) {
354
				$request->addHeader($k, $v);
355
			}
356
		}
357
358
		// Pre-request filtering
359
		// @see issue #2517
360
		$model = DataModel::inst();
361
		$output = Injector::inst()->get('RequestProcessor')->preRequest($request, $session, $model);
362
		if ($output === false) {
363
			$onCleanup();
364
			throw new SS_HTTPResponse_Exception(_t('Director.INVALID_REQUEST', 'Invalid request'), 400);
365
		}
366
367
		// TODO: Pass in the DataModel
368
		$result = Director::handleRequest($request, $session, $model);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
369
370
		// Ensure that the result is an SS_HTTPResponse object
371
		if (is_string($result)) {
372
			if (substr($result, 0, 9) == 'redirect:') {
373
				$response = new SS_HTTPResponse();
374
				$response->redirect(substr($result, 9));
375
				$result = $response;
376
			} else {
377
				$result = new SS_HTTPResponse($result);
378
			}
379
		}
380
381
		$output = Injector::inst()->get('RequestProcessor')->postRequest($request, $result, $model);
382
		if ($output === false) {
383
			$onCleanup();
384
			throw new SS_HTTPResponse_Exception("Invalid response");
385
		}
386
387
		// Return valid response
388
		$onCleanup();
389
		return $result;
390
	}
391
392
	/**
393
	 * Handle an HTTP request, defined with a SS_HTTPRequest object.
394
	 *
395
	 * @param SS_HTTPRequest $request
396
	 * @param Session $session
397
	 * @param DataModel $model
398
	 *
399
	 * @return SS_HTTPResponse|string
400
	 */
401
	protected static function handleRequest(SS_HTTPRequest $request, Session $session, DataModel $model) {
0 ignored issues
show
Coding Style introduced by
handleRequest uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
402
		$rules = Config::inst()->get('Director', 'rules');
403
404
		if (isset($_REQUEST['debug'])) Debug::show($rules);
405
406
		foreach($rules as $pattern => $controllerOptions) {
0 ignored issues
show
Bug introduced by
The expression $rules of type array|integer|double|string|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
407
			if (is_string($controllerOptions)) {
408
				if (substr($controllerOptions, 0, 2) == '->') {
409
					$controllerOptions = array('Redirect' => substr($controllerOptions, 2));
410
				} else {
411
					$controllerOptions = array('Controller' => $controllerOptions);
412
				}
413
			}
414
415
			if (($arguments = $request->match($pattern, true)) !== false) {
416
				$request->setRouteParams($controllerOptions);
417
				// controllerOptions provide some default arguments
418
				$arguments = array_merge($controllerOptions, $arguments);
419
420
				// Find the controller name
421
				if (isset($arguments['Controller'])) $controller = $arguments['Controller'];
422
423
				// Pop additional tokens from the tokenizer if necessary
424
				if (isset($controllerOptions['_PopTokeniser'])) {
425
					$request->shift($controllerOptions['_PopTokeniser']);
426
				}
427
428
				// Handle redirection
429
				if (isset($arguments['Redirect'])) {
430
					return "redirect:" . Director::absoluteURL($arguments['Redirect'], true);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
431
432
				} else {
433
					Director::$urlParams = $arguments;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
434
					$controllerObj = Injector::inst()->create($controller);
0 ignored issues
show
Bug introduced by
The variable $controller does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
435
					$controllerObj->setSession($session);
436
437
					try {
438
						$result = $controllerObj->handleRequest($request, $model);
439
					} catch(SS_HTTPResponse_Exception $responseException) {
440
						$result = $responseException->getResponse();
441
					}
442
					if (!is_object($result) || $result instanceof SS_HTTPResponse) return $result;
443
444
					user_error("Bad result from url " . $request->getURL() . " handled by " .
445
						get_class($controllerObj)." controller: ".get_class($result), E_USER_WARNING);
446
				}
447
			}
448
		}
449
450
		// No URL rules matched, so return a 404 error.
451
		return new SS_HTTPResponse('No URL rule was matched', 404);
452
	}
453
454
	/**
455
	 * Set url parameters (should only be called internally by RequestHandler->handleRequest()).
456
	 *
457
	 * @param array $params
458
	 */
459
	public static function setUrlParams($params) {
460
		Director::$urlParams = $params;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
461
	}
462
463
	/**
464
	 * Return the {@link SiteTree} object that is currently being viewed. If there is no SiteTree
465
	 * object to return, then this will return the current controller.
466
	 *
467
	 * @return SiteTree
468
	 */
469
	public static function get_current_page() {
470
		return self::$current_page ? self::$current_page : Controller::curr();
471
	}
472
473
	/**
474
	 * Set the currently active {@link SiteTree} object that is being used to respond to the request.
475
	 *
476
	 * @param SiteTree $page
477
	 */
478
	public static function set_current_page($page) {
479
		self::$current_page = $page;
480
	}
481
482
	/**
483
	 * Turns the given URL into an absolute URL. By default non-site root relative urls will be
484
	 * evaluated relative to the current base_url.
485
	 *
486
	 * @param string $url URL To transform to absolute.
487
	 * @param string $relativeParent Method to use for evaluating relative urls.
488
	 * Either one of BASE (baseurl), ROOT (site root), or REQUEST (requested page).
489
	 * Defaults to BASE, which is the same behaviour as template url resolution.
490
	 * Ignored if the url is absolute or site root.
491
	 *
492
	 * @return string
493
	 */
494
	public static function absoluteURL($url, $relativeParent = self::BASE) {
0 ignored issues
show
Coding Style introduced by
absoluteURL uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
495
		if (is_bool($relativeParent)) {
496
			// Deprecate old boolean second parameter
497
			Deprecation::notice('5.0', 'Director::absoluteURL takes an explicit parent for relative url');
498
			$relativeParent = $relativeParent ? self::BASE : self::REQUEST;
499
		}
500
501
		// Check if there is already a protocol given
502
		if (preg_match('/^http(s?):\/\//', $url)) {
503
			return $url;
504
		}
505
506
		// Absolute urls without protocol are added
507
		// E.g. //google.com -> http://google.com
508
		if (strpos($url, '//') === 0) {
509
			return self::protocol() . substr($url, 2);
510
		}
511
512
		// Determine method for mapping the parent to this relative url
513
		if ($relativeParent === self::ROOT || self::is_root_relative_url($url)) {
514
			// Root relative urls always should be evaluated relative to the root
515
			$parent = self::protocolAndHost();
516
517
		} elseif ($relativeParent === self::REQUEST) {
518
			// Request relative urls rely on the REQUEST_URI param (old default behaviour)
519
			if (!isset($_SERVER['REQUEST_URI'])) {
520
				return false;
521
			}
522
			$parent = dirname($_SERVER['REQUEST_URI'] . 'x');
523
524
		} else {
525
			// Default to respecting site base_url
526
			$parent = self::absoluteBaseURL();
527
		}
528
529
		// Map empty urls to relative slash and join to base
530
		if (empty($url) || $url === '.' || $url === './') {
531
			$url = '/';
532
		}
533
		return Controller::join_links($parent, $url);
534
535
	}
536
537
	/**
538
	 * Returns the domain part of the URL 'http://www.mysite.com'. Returns FALSE is this environment
539
	 * variable isn't set.
540
	 *
541
	 * @return bool|string
542
	 */
543
	public static function protocolAndHost() {
0 ignored issues
show
Coding Style introduced by
protocolAndHost uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
544
		$alternate = Config::inst()->get('Director', 'alternate_base_url');
545
		if ($alternate) {
546
			if (preg_match('/^(http[^:]*:\/\/[^\/]+)(\/|$)/', $alternate, $matches)) {
547
				return $matches[1];
548
			}
549
		}
550
551
		if (isset($_SERVER['HTTP_HOST'])) {
552
			return Director::protocol() . $_SERVER['HTTP_HOST'];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
553
		} else {
554
			global $_FILE_TO_URL_MAPPING;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
555
			if (Director::is_cli() && isset($_FILE_TO_URL_MAPPING)) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
556
				$errorSuggestion = '  You probably want to define ' .
557
				'an entry in $_FILE_TO_URL_MAPPING that covers "' . Director::baseFolder() . '"';
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
558
			} elseif (Director::is_cli()) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
559
				$errorSuggestion = '  You probably want to define $_FILE_TO_URL_MAPPING in ' .
560
				'your _ss_environment.php as instructed on the "sake" page of the doc.silverstripe.com wiki';
561
			} else {
562
				$errorSuggestion = "";
563
			}
564
565
			user_error("Director::protocolAndHost() lacks sufficient information - HTTP_HOST not set."
566
				. $errorSuggestion, E_USER_WARNING);
567
			return false;
568
569
		}
570
	}
571
572
	/**
573
	 * Return the current protocol that the site is running under.
574
	 *
575
	 * @return string
576
	 */
577
	public static function protocol() {
578
		return (self::is_https()) ? 'https://' : 'http://';
579
	}
580
581
	/**
582
	 * Return whether the site is running as under HTTPS.
583
	 *
584
	 * @return bool
585
	 */
586
	public static function is_https() {
0 ignored issues
show
Coding Style introduced by
is_https uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
587
		// See https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
588
		// See https://support.microsoft.com/en-us/kb/307347
589
		$headerOverride = false;
590
		if (TRUSTED_PROXY) {
591
			$headers = (defined('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) ? array(SS_TRUSTED_PROXY_PROTOCOL_HEADER) : null;
592
			if (!$headers) {
593
				// Backwards compatible defaults
594
				$headers = array('HTTP_X_FORWARDED_PROTO', 'HTTP_X_FORWARDED_PROTOCOL', 'HTTP_FRONT_END_HTTPS');
595
			}
596
			foreach($headers as $header) {
597
				$headerCompareVal = ($header === 'HTTP_FRONT_END_HTTPS' ? 'on' : 'https');
598
				if (!empty($_SERVER[$header]) && strtolower($_SERVER[$header]) == $headerCompareVal) {
599
					$headerOverride = true;
600
					break;
601
				}
602
			}
603
		}
604
605
		if ($protocol = Config::inst()->get('Director', 'alternate_protocol')) {
606
			return ($protocol == 'https');
607
		} elseif ($headerOverride) {
608
			return true;
609
		} elseif ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) {
610
			return true;
611
		} elseif (isset($_SERVER['SSL'])) {
612
			return true;
613
		} else {
614
			return false;
615
		}
616
	}
617
618
	/**
619
	 * Returns the root URL for the site. It will be automatically calculated unless it is overridden
620
	 * with {@link setBaseURL()}.
621
	 *
622
	 * @return string
623
	 */
624
	public static function baseURL() {
625
		$alternate = Config::inst()->get('Director', 'alternate_base_url');
626
627
		if ($alternate) {
628
			return $alternate;
629
		} else {
630
			$base = BASE_URL;
631
632
			if ($base == '/' || $base == '/.' || $base == '\\') {
633
				$baseURL = '/';
634
			} else {
635
				$baseURL = $base . '/';
636
			}
637
638
			if (defined('BASE_SCRIPT_URL')) {
639
				return $baseURL . BASE_SCRIPT_URL;
640
			}
641
642
			return $baseURL;
643
		}
644
	}
645
646
	/**
647
	 * Sets the root URL for the website. If the site isn't accessible from the URL you provide,
648
	 * weird things will happen.
649
	 *
650
	 * @deprecated 4.0 Use the "Director.alternate_base_url" config setting instead.
651
	 *
652
	 * @param string $baseURL
653
	 */
654
	public static function setBaseURL($baseURL) {
655
		Deprecation::notice('4.0', 'Use the "Director.alternate_base_url" config setting instead');
656
		Config::inst()->update('Director', 'alternate_base_url', $baseURL);
657
	}
658
659
	/**
660
	 * Returns the root filesystem folder for the site. It will be automatically calculated unless
661
	 * it is overridden with {@link setBaseFolder()}.
662
	 *
663
	 * @return string
664
	 */
665
	public static function baseFolder() {
666
		$alternate = Config::inst()->get('Director', 'alternate_base_folder');
667
		return ($alternate) ? $alternate : BASE_PATH;
668
	}
669
670
	/**
671
	 * Sets the root folder for the website. If the site isn't accessible from the folder you provide,
672
	 * weird things will happen.
673
	 *
674
	 * @deprecated 4.0 Use the "Director.alternate_base_folder" config setting instead.
675
	 *
676
	 * @param string $baseFolder
677
	 */
678
	public static function setBaseFolder($baseFolder) {
679
		Deprecation::notice('4.0', 'Use the "Director.alternate_base_folder" config setting instead');
680
		Config::inst()->update('Director', 'alternate_base_folder', $baseFolder);
681
	}
682
683
	/**
684
	 * Turns an absolute URL or folder into one that's relative to the root of the site. This is useful
685
	 * when turning a URL into a filesystem reference, or vice versa.
686
	 *
687
	 * @param string $url Accepts both a URL or a filesystem path.
688
	 *
689
	 * @return string
690
	 */
691
	public static function makeRelative($url) {
692
		// Allow for the accidental inclusion whitespace and // in the URL
693
		$url = trim(preg_replace('#([^:])//#', '\\1/', $url));
694
695
		$base1 = self::absoluteBaseURL();
696
		$baseDomain = substr($base1, strlen(self::protocol()));
697
698
		// Only bother comparing the URL to the absolute version if $url looks like a URL.
699
		if (preg_match('/^https?[^:]*:\/\//', $url, $matches)) {
700
			$urlProtocol = $matches[0];
701
			$urlWithoutProtocol = substr($url, strlen($urlProtocol));
702
703
			// If we are already looking at baseURL, return '' (substr will return false)
704
			if ($url == $base1) {
705
				return '';
706
			} elseif (substr($url, 0, strlen($base1)) == $base1) {
707
				return substr($url, strlen($base1));
708
			} elseif (substr($base1, -1) == "/" && $url == substr($base1, 0, -1)) {
709
				// Convert http://www.mydomain.com/mysitedir to ''
710
				return "";
711
			}
712
713
			if (substr($urlWithoutProtocol, 0, strlen($baseDomain)) == $baseDomain) {
714
				return substr($urlWithoutProtocol, strlen($baseDomain));
715
			}
716
		}
717
718
		// test for base folder, e.g. /var/www
719
		$base2 = self::baseFolder();
720
		if (substr($url, 0, strlen($base2)) == $base2) return substr($url, strlen($base2));
721
722
		// Test for relative base url, e.g. mywebsite/ if the full URL is http://localhost/mywebsite/
723
		$base3 = self::baseURL();
724
		if (substr($url, 0, strlen($base3)) == $base3) {
725
			return substr($url, strlen($base3));
726
		}
727
728
		// Test for relative base url, e.g mywebsite/ if the full url is localhost/myswebsite
729
		if (substr($url, 0, strlen($baseDomain)) == $baseDomain) {
730
			return substr($url, strlen($baseDomain));
731
		}
732
733
		// Nothing matched, fall back to returning the original URL
734
		return $url;
735
	}
736
737
	/**
738
	 * Returns true if a given path is absolute. Works under both *nix and windows systems.
739
	 *
740
	 * @param string $path
741
	 *
742
	 * @return bool
743
	 */
744
	public static function is_absolute($path) {
745
		if (empty($path)) return false;
746
		if ($path[0] == '/' || $path[0] == '\\') return true;
747
		return preg_match('/^[a-zA-Z]:[\\\\\/]/', $path) == 1;
748
	}
749
750
	/**
751
	 * Determine if the url is root relative (i.e. starts with /, but not with //) SilverStripe
752
	 * considers root relative urls as a subset of relative urls.
753
	 *
754
	 * @param string $url
755
	 *
756
	 * @return bool
757
	 */
758
	public static function is_root_relative_url($url) {
759
		return strpos($url, '/') === 0 && strpos($url, '//') !== 0;
760
	}
761
762
	/**
763
	 * Checks if a given URL is absolute (e.g. starts with 'http://' etc.). URLs beginning with "//"
764
	 * are treated as absolute, as browsers take this to mean the same protocol as currently being used.
765
	 *
766
	 * Useful to check before redirecting based on a URL from user submissions through $_GET or $_POST,
767
	 * and avoid phishing attacks by redirecting to an attackers server.
768
	 *
769
	 * Note: Can't solely rely on PHP's parse_url() , since it is not intended to work with relative URLs
770
	 * or for security purposes. filter_var($url, FILTER_VALIDATE_URL) has similar problems.
771
	 *
772
	 * @param string $url
773
	 *
774
	 * @return bool
775
	 */
776
	public static function is_absolute_url($url) {
777
		// Strip off the query and fragment parts of the URL before checking
778
		if (($queryPosition = strpos($url, '?')) !== false) {
779
			$url = substr($url, 0, $queryPosition-1);
780
		}
781
		if (($hashPosition = strpos($url, '#')) !== false) {
782
			$url = substr($url, 0, $hashPosition-1);
783
		}
784
		$colonPosition = strpos($url, ':');
785
		$slashPosition = strpos($url, '/');
786
		return (
787
			// Base check for existence of a host on a compliant URL
788
			parse_url($url, PHP_URL_HOST)
789
			// Check for more than one leading slash without a protocol.
790
			// While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers,
791
			// and hence a potential security risk. Single leading slashes are not an issue though.
792
			|| preg_match('%^\s*/{2,}%', $url)
793
			|| (
794
				// If a colon is found, check if it's part of a valid scheme definition
795
				// (meaning its not preceded by a slash).
796
				$colonPosition !== FALSE
797
				&& ($slashPosition === FALSE || $colonPosition < $slashPosition)
798
			)
799
		);
800
	}
801
802
	/**
803
	 * Checks if a given URL is relative (or root relative) by checking {@link is_absolute_url()}.
804
	 *
805
	 * @param string $url
806
	 *
807
	 * @return bool
808
	 */
809
	public static function is_relative_url($url) {
810
		return !static::is_absolute_url($url);
811
	}
812
813
	/**
814
	 * Checks if the given URL is belonging to this "site" (not an external link). That's the case if
815
	 * the URL is relative, as defined by {@link is_relative_url()}, or if the host matches
816
	 * {@link protocolAndHost()}.
817
	 *
818
	 * Useful to check before redirecting based on a URL from user submissions through $_GET or $_POST,
819
	 * and avoid phishing attacks by redirecting to an attackers server.
820
	 *
821
	 * @param string $url
822
	 *
823
	 * @return bool
824
	 */
825
	public static function is_site_url($url) {
826
		$urlHost = parse_url($url, PHP_URL_HOST);
827
		$actualHost = parse_url(self::protocolAndHost(), PHP_URL_HOST);
828
		if ($urlHost && $actualHost && $urlHost == $actualHost) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $urlHost of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Bug Best Practice introduced by
The expression $actualHost of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
829
			return true;
830
		} else {
831
			return self::is_relative_url($url);
832
		}
833
	}
834
835
	/**
836
	 * Takes a $_SERVER data array and extracts HTTP request headers.
837
	 *
838
	 * @param array $server
839
	 *
840
	 * @return array
841
	 */
842
	public static function extract_request_headers(array $server) {
843
		$headers = array();
844
845
		foreach($server as $key => $value) {
846
			if (substr($key, 0, 5) == 'HTTP_') {
847
				$key = substr($key, 5);
848
				$key = strtolower(str_replace('_', ' ', $key));
849
				$key = str_replace(' ', '-', ucwords($key));
850
				$headers[$key] = $value;
851
			}
852
		}
853
854
		if (isset($server['CONTENT_TYPE'])) $headers['Content-Type'] = $server['CONTENT_TYPE'];
855
		if (isset($server['CONTENT_LENGTH'])) $headers['Content-Length'] = $server['CONTENT_LENGTH'];
856
857
		return $headers;
858
	}
859
860
	/**
861
	 * Given a filesystem reference relative to the site root, return the full file-system path.
862
	 *
863
	 * @param string $file
864
	 *
865
	 * @return string
866
	 */
867
	public static function getAbsFile($file) {
868
		return self::is_absolute($file) ? $file : Director::baseFolder() . '/' . $file;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
869
	}
870
871
	/**
872
	 * Returns true if the given file exists. Filename should be relative to the site root.
873
	 *
874
	 * @param $file
875
	 *
876
	 * @return bool
877
	 */
878
	public static function fileExists($file) {
879
		// replace any appended query-strings, e.g. /path/to/foo.php?bar=1 to /path/to/foo.php
880
		$file = preg_replace('/([^\?]*)?.*/', '$1', $file);
881
		return file_exists(Director::getAbsFile($file));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
882
	}
883
884
	/**
885
	 * Returns the Absolute URL of the site root.
886
	 *
887
	 * @return string
888
	 */
889
	public static function absoluteBaseURL() {
890
		return self::absoluteURL(
891
			self::baseURL(),
892
			self::ROOT
893
		);
894
	}
895
896
	/**
897
	 * Returns the Absolute URL of the site root, embedding the current basic-auth credentials into
898
	 * the URL.
899
	 *
900
	 * @return string
901
	 */
902
	public static function absoluteBaseURLWithAuth() {
0 ignored issues
show
Coding Style introduced by
absoluteBaseURLWithAuth uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
903
		$s = "";
904
		$login = "";
905
906
		if (isset($_SERVER['PHP_AUTH_USER'])) $login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@";
907
908
		return Director::protocol() . $login .  $_SERVER['HTTP_HOST'] . Director::baseURL();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
909
	}
910
911
	/**
912
	 * Skip any further processing and immediately respond with a redirect to the passed URL.
913
	 *
914
	 * @param string $destURL
915
	 */
916
	protected static function force_redirect($destURL) {
917
		$response = new SS_HTTPResponse();
918
		$response->redirect($destURL, 301);
919
920
		HTTP::add_cache_headers($response);
921
922
		// TODO: Use an exception - ATM we can be called from _config.php, before Director#handleRequest's try block
923
		$response->output();
924
		die;
0 ignored issues
show
Coding Style Compatibility introduced by
The method force_redirect() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
925
	}
926
927
	/**
928
	 * Force the site to run on SSL.
929
	 *
930
	 * To use, call from _config.php. For example:
931
	 * <code>
932
	 * if (Director::isLive()) Director::forceSSL();
933
	 * </code>
934
	 *
935
	 * If you don't want your entire site to be on SSL, you can pass an array of PCRE regular expression
936
	 * patterns for matching relative URLs. For example:
937
	 * <code>
938
	 * if (Director::isLive()) Director::forceSSL(array('/^admin/', '/^Security/'));
939
	 * </code>
940
	 *
941
	 * If you want certain parts of your site protected under a different domain, you can specify
942
	 * the domain as an argument:
943
	 * <code>
944
	 * if (Director::isLive()) Director::forceSSL(array('/^admin/', '/^Security/'), 'secure.mysite.com');
945
	 * </code>
946
	 *
947
	 * Note that the session data will be lost when moving from HTTP to HTTPS. It is your responsibility
948
	 * to ensure that this won't cause usability problems.
949
	 *
950
	 * CAUTION: This does not respect the site environment mode. You should check this
951
	 * as per the above examples using Director::isLive() or Director::isTest() for example.
952
	 *
953
	 * @param array $patterns Array of regex patterns to match URLs that should be HTTPS.
954
	 * @param string $secureDomain Secure domain to redirect to. Defaults to the current domain.
955
	 *
956
	 * @return bool|string String of URL when unit tests running, boolean FALSE if patterns don't match request URI.
957
	 */
958
	public static function forceSSL($patterns = null, $secureDomain = null) {
0 ignored issues
show
Coding Style introduced by
forceSSL uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
959
		if (!isset($_SERVER['REQUEST_URI'])) return false;
960
961
		$matched = false;
962
963
		if ($patterns) {
964
			// Calling from the command-line?
965
			if (!isset($_SERVER['REQUEST_URI'])) return;
966
967
			$relativeURL = self::makeRelative(Director::absoluteURL($_SERVER['REQUEST_URI']));
0 ignored issues
show
Security Bug introduced by
It seems like \Director::absoluteURL($_SERVER['REQUEST_URI']) targeting Director::absoluteURL() can also be of type false; however, Director::makeRelative() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
968
969
			// protect portions of the site based on the pattern
970
			foreach($patterns as $pattern) {
971
				if (preg_match($pattern, $relativeURL)) {
972
					$matched = true;
973
					break;
974
				}
975
			}
976
		} else {
977
			// protect the entire site
978
			$matched = true;
979
		}
980
981
		if ($matched && !self::is_https()) {
982
983
			// if an domain is specified, redirect to that instead of the current domain
984
			if ($secureDomain) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $secureDomain of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
985
				$url = 'https://' . $secureDomain . $_SERVER['REQUEST_URI'];
986
			} else {
987
				$url = $_SERVER['REQUEST_URI'];
988
			}
989
990
			$destURL = str_replace('http:', 'https:', Director::absoluteURL($url));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
991
992
			// This coupling to SapphireTest is necessary to test the destination URL and to not interfere with tests
993
			if (class_exists('SapphireTest', false) && SapphireTest::is_running_test()) {
994
				return $destURL;
995
			} else {
996
				self::force_redirect($destURL);
997
			}
998
		} else {
999
			return false;
1000
		}
1001
	}
1002
1003
	/**
1004
	 * Force a redirect to a domain starting with "www."
1005
	 */
1006
	public static function forceWWW() {
0 ignored issues
show
Coding Style introduced by
forceWWW uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1007
		if (!Director::isDev() && !Director::isTest() && strpos($_SERVER['HTTP_HOST'], 'www') !== 0) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1008
			$destURL = str_replace(Director::protocol(), Director::protocol() . 'www.',
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1009
				Director::absoluteURL($_SERVER['REQUEST_URI']));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1010
1011
			self::force_redirect($destURL);
1012
		}
1013
	}
1014
1015
	/**
1016
	 * Checks if the current HTTP-Request is an "Ajax-Request" by checking for a custom header set by
1017
	 * jQuery or whether a manually set request-parameter 'ajax' is present.
1018
	 *
1019
	 * @return bool
1020
	 */
1021
	public static function is_ajax() {
0 ignored issues
show
Coding Style introduced by
is_ajax uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
is_ajax uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1022
		if (Controller::has_curr()) {
1023
			return Controller::curr()->getRequest()->isAjax();
1024
		} else {
1025
			return (
1026
				isset($_REQUEST['ajax']) ||
1027
				(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == "XMLHttpRequest")
1028
			);
1029
		}
1030
	}
1031
1032
	/**
1033
	 * Returns true if this script is being run from the command line rather than the web server.
1034
	 *
1035
	 * @return bool
1036
	 */
1037
	public static function is_cli() {
1038
		return (php_sapi_name() == "cli");
1039
	}
1040
1041
	/**
1042
	 * Set the environment type of the current site.
1043
	 *
1044
	 * Typically, a SilverStripe site have a number of environments:
1045
	 *  - Development environments, such a copy on your local machine.
1046
	 *  - Test sites, such as the one you show the client before going live.
1047
	 *  - The live site itself.
1048
	 *
1049
	 * The behaviour of these environments often varies slightly.  For example, development sites may
1050
	 * have errors dumped to the screen, and order confirmation emails might be sent to the developer
1051
	 * instead of the client.
1052
	 *
1053
	 * To help with this, SilverStripe supports the notion of an environment type.  The environment
1054
	 * type can be dev, test, or live.
1055
	 *
1056
	 * You can set it explicitly with {@link Director::set_environment_type()}. Or you can use
1057
	 * {@link Director::$dev_servers} and {@link Director::$test_servers} to set it implicitly, based
1058
	 * on the value of $_SERVER['HTTP_HOST'].  If the HTTP_HOST value is one of the servers listed,
1059
	 * then the environment type will be test or dev.  Otherwise, the environment type will be live.
1060
	 *
1061
	 * Dev mode can also be forced by putting ?isDev=1 in your URL, which will ask you to log in and
1062
	 * then push the site into dev mode for the remainder of the session. Putting ?isDev=0 onto the URL
1063
	 * can turn it back.
1064
	 *
1065
	 * Test mode can also be forced by putting ?isTest=1 in your URL, which will ask you to log in and
1066
	 * then push the site into test mode for the remainder of the session. Putting ?isTest=0 onto the URL
1067
	 * can turn it back.
1068
	 *
1069
	 * Generally speaking, these methods will be called from your _config.php file.
1070
	 *
1071
	 * Once the environment type is set, it can be checked with {@link Director::isDev()},
1072
	 * {@link Director::isTest()}, and {@link Director::isLive()}.
1073
	 *
1074
	 * @deprecated 4.0 Use the "Director.environment_type" config setting instead
1075
	 *
1076
	 * @param $et string
1077
	 */
1078
	public static function set_environment_type($et) {
1079
		if ($et != 'dev' && $et != 'test' && $et != 'live') {
1080
			user_error("Director::set_environment_type passed '$et'.  It should be passed dev, test, or live",
1081
				E_USER_WARNING);
1082
		} else {
1083
			Deprecation::notice('4.0', 'Use the "Director.environment_type" config setting instead');
1084
			Config::inst()->update('Director', 'environment_type', $et);
1085
		}
1086
	}
1087
1088
	/**
1089
	 * Can also be checked with {@link Director::isDev()}, {@link Director::isTest()}, and
1090
	 * {@link Director::isLive()}.
1091
	 *
1092
	 * @return bool|string
1093
	 */
1094
	public static function get_environment_type() {
1095
		if (Director::isLive()) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1096
			return 'live';
1097
		} elseif (Director::isTest()) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1098
			return 'test';
1099
		} elseif (Director::isDev()) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1100
			return 'dev';
1101
		} else {
1102
			return false;
1103
		}
1104
	}
1105
1106
	/**
1107
	 * This function will return true if the site is in a live environment. For information about
1108
	 * environment types, see {@link Director::set_environment_type()}.
1109
	 *
1110
	 * @return bool
1111
	 */
1112
	public static function isLive() {
1113
		return !(Director::isDev() || Director::isTest());
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1114
	}
1115
1116
	/**
1117
	 * This function will return true if the site is in a development environment. For information about
1118
	 * environment types, see {@link Director::set_environment_type()}.
1119
	 *
1120
	 * @return bool
1121
	 */
1122
	public static function isDev() {
0 ignored issues
show
Coding Style introduced by
isDev uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1123
		// Check session
1124
		if ($env = self::session_environment()) return $env === 'dev';
1125
1126
		// Check config
1127
		if (Config::inst()->get('Director', 'environment_type') === 'dev') return true;
1128
1129
		// Check if we are running on one of the test servers
1130
		$devServers = (array)Config::inst()->get('Director', 'dev_servers');
1131
		if (isset($_SERVER['HTTP_HOST']) && in_array($_SERVER['HTTP_HOST'], $devServers))  {
1132
			return true;
1133
		}
1134
1135
		return false;
1136
	}
1137
1138
	/**
1139
	 * This function will return true if the site is in a test environment. For information about
1140
	 * environment types, see {@link Director::set_environment_type()}.
1141
	 *
1142
	 * @return bool
1143
	 */
1144
	public static function isTest() {
0 ignored issues
show
Coding Style introduced by
isTest uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1145
		// In case of isDev and isTest both being set, dev has higher priority
1146
		if (self::isDev()) return false;
1147
1148
		// Check saved session
1149
		if ($env = self::session_environment()) return $env === 'test';
1150
1151
		// Check config
1152
		if (Config::inst()->get('Director', 'environment_type') === 'test') return true;
1153
1154
		// Check if we are running on one of the test servers
1155
		$testServers = (array)Config::inst()->get('Director', 'test_servers');
1156
		if (isset($_SERVER['HTTP_HOST']) && in_array($_SERVER['HTTP_HOST'], $testServers))  {
1157
			return true;
1158
		}
1159
1160
		return false;
1161
	}
1162
1163
	/**
1164
	 * Check or update any temporary environment specified in the session.
1165
	 *
1166
	 * @return null|string
1167
	 */
1168
	protected static function session_environment() {
0 ignored issues
show
Coding Style introduced by
session_environment uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
session_environment uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1169
		// Set session from querystring
1170
		if (isset($_GET['isDev'])) {
1171
			if (isset($_SESSION)) {
1172
				unset($_SESSION['isTest']); // In case we are changing from test mode
1173
				$_SESSION['isDev'] = $_GET['isDev'];
1174
			}
1175
			return 'dev';
1176
		} elseif (isset($_GET['isTest'])) {
1177
			if (isset($_SESSION)) {
1178
				unset($_SESSION['isDev']); // In case we are changing from dev mode
1179
				$_SESSION['isTest'] = $_GET['isTest'];
1180
			}
1181
			return 'test';
1182
		}
1183
		// Check session
1184
		if (isset($_SESSION['isDev']) && $_SESSION['isDev']) {
1185
			return 'dev';
1186
		} elseif (isset($_SESSION['isTest']) && $_SESSION['isTest']) {
1187
			return 'test';
1188
		} else {
1189
			return null;
1190
		}
1191
	}
1192
1193
	/**
1194
	 * Returns an array of strings of the method names of methods on the call that should be exposed
1195
	 * as global variables in the templates.
1196
	 *
1197
	 * @return array
1198
	 */
1199
	public static function get_template_global_variables() {
1200
		return array(
1201
			'absoluteBaseURL',
1202
			'baseURL',
1203
			'is_ajax',
1204
			'isAjax' => 'is_ajax',
1205
			'BaseHref' => 'absoluteBaseURL',    //@deprecated 3.0
1206
		);
1207
	}
1208
}
1209