Completed
Push — namespace-model ( 476d0e...5d15d8 )
by Sam
08:12
created

Director   F

Complexity

Total Complexity 197

Size/Duplication

Total Lines 1190
Duplicated Lines 5.21 %

Coupling/Cohesion

Components 2
Dependencies 15

Importance

Changes 2
Bugs 2 Features 0
Metric Value
wmc 197
c 2
b 2
f 0
lcom 2
cbo 15
dl 62
loc 1190
rs 0.5217

38 Methods

Rating   Name   Duplication   Size   Complexity  
A addRules() 0 5 1
D direct() 0 93 21
F test() 3 128 24
C handleRequest() 0 52 12
A setUrlParams() 0 3 1
A get_current_page() 0 3 2
A set_current_page() 0 3 1
C absoluteURL() 0 42 12
C protocolAndHost() 0 28 7
A protocol() 0 3 2
C is_https() 14 31 13
B baseURL() 0 21 6
A setBaseURL() 0 4 1
A baseFolder() 0 4 2
A setBaseFolder() 0 4 1
D makeRelative() 6 45 10
A is_absolute() 0 5 4
A is_root_relative_url() 0 3 2
C is_absolute_url() 6 25 7
A is_relative_url() 0 3 1
A is_site_url() 0 9 4
B extract_request_headers() 0 17 5
A getAbsFile() 0 3 2
A fileExists() 0 5 1
A absoluteBaseURL() 0 6 1
A absoluteBaseURLWithAuth() 0 8 2
A force_redirect() 0 10 1
C forceSSL() 0 44 11
A forceWWW() 0 8 4
A is_ajax() 0 10 4
A is_cli() 0 3 1
A set_environment_type() 0 9 4
A get_environment_type() 0 11 4
A isLive() 0 3 2
B isDev() 15 15 5
B isTest() 18 18 6
C session_environment() 0 24 9
A get_template_global_variables() 0 9 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Director often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Director, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\Control;
4
5
use SilverStripe\Model\DataModel;
6
use Deprecation;
7
use Config;
8
use ArrayLib;
9
10
use Injector;
11
12
13
use Versioned;
14
use Requirements;
15
use Debug;
16
use SapphireTest;
17
use TemplateGlobalProvider;
18
use SilverStripe\Control\HTTPRequest;
19
use SilverStripe\Control\HTTPResponse_Exception;
20
use SilverStripe\Control\HTTPResponse;
21
22
23
24
/**
25
 * Director is responsible for processing URLs, and providing environment information.
26
 *
27
 * The most important part of director is {@link Director::direct()}, which is passed a URL and will
28
 * execute the appropriate controller.
29
 *
30
 * Director also has a number of static methods that provide information about the environment, such as
31
 * {@link Director::$environment_type}.
32
 *
33
 * @package framework
34
 *
35
 * @subpackage control
36
 *
37
 * @see Director::direct()
38
 * @see Director::$rules
39
 * @see Director::$environment_type
40
 */
41
class Director implements TemplateGlobalProvider {
42
43
	/**
44
	 * Specifies this url is relative to the base.
45
	 *
46
	 * @var string
47
	 */
48
	const BASE = 'BASE';
49
50
	/**
51
	 * Specifies this url is relative to the site root.
52
	 *
53
	 * @var string
54
	 */
55
	const ROOT = 'ROOT';
56
57
	/**
58
	 * specifies this url is relative to the current request.
59
	 *
60
	 * @var string
61
	 */
62
	const REQUEST = 'REQUEST';
63
64
	/**
65
	 * @var array
66
	 */
67
	static private $urlParams;
68
69
	/**
70
	 * @var array
71
	 */
72
	static private $rules = array();
73
74
	/**
75
	 * @var SiteTree
76
	 */
77
	private static $current_page;
78
79
	/**
80
	 * @config
81
	 *
82
	 * @var string
83
	 */
84
	private static $alternate_base_folder;
85
86
	/**
87
	 * @config
88
	 *
89
	 * @var array
90
	 */
91
	private static $dev_servers = array();
92
93
	/**
94
	 * @config
95
	 *
96
	 * @var array
97
	 */
98
	private static $test_servers = array();
99
100
	/**
101
	 * Setting this explicitly specifies the protocol ("http" or "https") used, overriding the normal
102
	 * behaviour of Director::is_https introspecting it from the request. False values imply default
103
	 * introspection.
104
	 *
105
	 * @config
106
	 *
107
	 * @var string
108
	 */
109
	private static $alternate_protocol;
110
111
	/**
112
	 * @config
113
	 *
114
	 * @var string
115
	 */
116
	private static $alternate_base_url;
117
118
	/**
119
	 * @config
120
	 *
121
	 * @var string
122
	 */
123
	private static $environment_type;
124
125
	/**
126
	 * Add URL matching rules to the Director. The director is responsible for turning URLs into
127
	 * Controller objects.
128
	 *
129
	 * Higher $priority values will get your rule checked first. We recommend priority 100 for your
130
	 * site's rules. The built-in rules are priority 10, standard modules are priority 50.
131
	 *
132
	 * @deprecated 4.0 Use the "Director.rules" config setting instead
133
	 *
134
	 * @param int $priority
135
	 * @param array $rules
136
	 */
137
	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...
138
		Deprecation::notice('4.0', 'Use the "Director.rules" config setting instead');
139
140
		Config::inst()->update('SilverStripe\Control\Director', 'rules', $rules);
141
	}
142
143
	/**
144
	 * Process the given URL, creating the appropriate controller and executing it.
145
	 *
146
	 * Request processing is handled as follows:
147
	 * - Director::direct() creates a new SS_HTTPResponse object and passes this to
148
	 *   Director::handleRequest().
149
	 * - Director::handleRequest($request) checks each of the Director rules and identifies a controller
150
	 *   to handle this request.
151
	 * - Controller::handleRequest($request) is then called.  This will find a rule to handle the URL,
152
	 *   and call the rule handling method.
153
	 * - RequestHandler::handleRequest($request) is recursively called whenever a rule handling method
154
	 *   returns a RequestHandler object.
155
	 *
156
	 * In addition to request processing, Director will manage the session, and perform the output of
157
	 * the actual response to the browser.
158
	 *
159
	 * @uses handleRequest() rule-lookup logic is handled by this.
160
	 * @uses Controller::run() Controller::run() handles the page logic for a Director::direct() call.
161
	 *
162
	 * @param string $url
163
	 * @param DataModel $model
164
	 *
165
	 * @throws SilverStripe\Control\HTTPResponse_Exception
166
	 */
167
	public static function direct($url, DataModel $model) {
168
		// Validate $_FILES array before merging it with $_POST
169
		foreach($_FILES as $k => $v) {
170
			if (is_array($v['tmp_name'])) {
171
				$v = ArrayLib::array_values_recursive($v['tmp_name']);
172
				foreach($v as $tmpFile) {
173
					if ($tmpFile && !is_uploaded_file($tmpFile)) {
174
						user_error("File upload '$k' doesn't appear to be a valid upload", E_USER_ERROR);
175
					}
176
				}
177
			} else {
178
				if ($v['tmp_name'] && !is_uploaded_file($v['tmp_name'])) {
179
					user_error("File upload '$k' doesn't appear to be a valid upload", E_USER_ERROR);
180
				}
181
			}
182
		}
183
184
		$req = new HTTPRequest(
185
			(isset($_SERVER['X-HTTP-Method-Override']))
186
				? $_SERVER['X-HTTP-Method-Override']
187
				: $_SERVER['REQUEST_METHOD'],
188
			$url,
189
			$_GET,
190
			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, SilverStripe\Control\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...
191
			@file_get_contents('php://input')
192
		);
193
194
		$headers = self::extract_request_headers($_SERVER);
195
		foreach ($headers as $header => $value) {
196
			$req->addHeader($header, $value);
197
		}
198
199
		// Initiate an empty session - doesn't initialize an actual PHP session until saved (see below)
200
		$session = Injector::inst()->create('SilverStripe\Control\Session', isset($_SESSION) ? $_SESSION : array());
201
202
		// Only resume a session if its not started already, and a session identifier exists
203
		if (!isset($_SESSION) && Session::request_contains_session_id()) {
204
			$session->inst_start();
205
		}
206
207
		$output = Injector::inst()->get('SilverStripe\Control\RequestProcessor')->preRequest($req, $session, $model);
208
209
		if ($output === false) {
210
			// @TODO Need to NOT proceed with the request in an elegant manner
211
			throw new HTTPResponse_Exception(_t('Director.INVALID_REQUEST', 'Invalid request'), 400);
212
		}
213
214
		$result = Director::handleRequest($req, $session, $model);
215
216
		// Save session data. Note that inst_save() will start/resume the session if required.
217
		$session->inst_save();
218
219
		// Return code for a redirection request
220
		if (is_string($result) && substr($result, 0, 9) == 'redirect:') {
221
			$url = substr($result, 9);
222
223
			if (Director::is_cli()) {
224
				// on cli, follow SilverStripe redirects automatically
225
				return Director::direct(
226
					str_replace(Director::absoluteBaseURL(), '', $url),
227
					DataModel::inst()
228
				);
229
			} else {
230
				$response = new HTTPResponse();
231
				$response->redirect($url);
232
				$res = Injector::inst()->get('SilverStripe\Control\RequestProcessor')->postRequest($req, $response, $model);
233
234
				if ($res !== false) {
235
					$response->output();
236
				}
237
			}
238
		// Handle a controller
239
		} elseif ($result) {
240
			if ($result instanceof HTTPResponse) {
241
				$response = $result;
242
243
			} else {
244
				$response = new HTTPResponse();
245
				$response->setBody($result);
246
			}
247
248
			$res = Injector::inst()->get('SilverStripe\Control\RequestProcessor')->postRequest($req, $response, $model);
249
			if ($res !== false) {
250
					$response->output();
251
			} else {
252
				// @TODO Proper response here.
253
				throw new HTTPResponse_Exception("Invalid response");
254
			}
255
256
257
			//$controllerObj->getSession()->inst_save();
258
		}
259
	}
260
261
	/**
262
	 * Test a URL request, returning a response object. This method is the counterpart of
263
	 * Director::direct() that is used in functional testing. It will execute the URL given, and
264
	 * return the result as an SS_HTTPResponse object.
265
	 *
266
	 * @uses getControllerForURL() The rule-lookup logic is handled by this.
267
	 * @uses Controller::run() Handles the page logic for a Director::direct() call.
268
	 *
269
	 * @param string $url The URL to visit.
270
	 * @param array $postVars The $_POST & $_FILES variables.
271
	 * @param array|Session $session The {@link Session} object representing the current session.
272
	 * By passing the same object to multiple  calls of Director::test(), you can simulate a persisted
273
	 * session.
274
	 * @param string $httpMethod The HTTP method, such as GET or POST.  It will default to POST if
275
	 * postVars is set, GET otherwise. Overwritten by $postVars['_method'] if present.
276
	 * @param string $body The HTTP body.
277
	 * @param array $headers HTTP headers with key-value pairs.
278
	 * @param array|Cookie_Backend $cookies to populate $_COOKIE.
279
	 * @param HTTP_Request $request The {@see HTTP_Request} object generated as a part of this request.
280
	 *
281
	 * @return SilverStripe\Control\HTTPResponse
282
	 *
283
	 * @throws SilverStripe\Control\HTTPResponse_Exception
284
	 */
285
	public static function test($url, $postVars = null, $session = array(), $httpMethod = null, $body = null,
286
			$headers = array(), $cookies = array(), &$request = null) {
287
288
		Config::nest();
289
		Injector::nest();
290
291
		// These are needed so that calling Director::test() does not muck with whoever is calling it.
292
		// Really, it's some inappropriate coupling and should be resolved by making less use of statics.
293
		$oldStage = Versioned::get_stage();
294
		$getVars = array();
295
296
		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...
297
298
		if (!$session) $session = Injector::inst()->create('SilverStripe\Control\Session', array());
299
		$cookieJar = $cookies instanceof Cookie_Backend
300
			? $cookies
301
			: Injector::inst()->createWithArgs('SilverStripe\Control\Cookie_Backend', array($cookies ?: array()));
302
303
		// Back up the current values of the superglobals
304
		$existingRequestVars = isset($_REQUEST) ? $_REQUEST : array();
305
		$existingGetVars = isset($_GET) ? $_GET : array();
306
		$existingPostVars = isset($_POST) ? $_POST : array();
307
		$existingSessionVars = isset($_SESSION) ? $_SESSION : array();
308
		$existingCookies = isset($_COOKIE) ? $_COOKIE : array();
309
		$existingServer	= isset($_SERVER) ? $_SERVER : array();
310
311
		$existingRequirementsBackend = Requirements::backend();
312
313
		Config::inst()->update('SilverStripe\Control\Cookie', 'report_errors', false);
314
		Requirements::set_backend(Injector::inst()->create('Requirements_Backend'));
315
316
		// Set callback to invoke prior to return
317
		$onCleanup = function() use(
318
			$existingRequestVars, $existingGetVars, $existingPostVars, $existingSessionVars,
319
			$existingCookies, $existingServer, $existingRequirementsBackend, $oldStage
320
		) {
321
			// Restore the super globals
322
			$_REQUEST = $existingRequestVars;
323
			$_GET = $existingGetVars;
324
			$_POST = $existingPostVars;
325
			$_SESSION = $existingSessionVars;
326
			$_COOKIE = $existingCookies;
327
			$_SERVER = $existingServer;
328
329
			Requirements::set_backend($existingRequirementsBackend);
330
331
			// These are needed so that calling Director::test() does not muck with whoever is calling it.
332
			// Really, it's some inappropriate coupling and should be resolved by making less use of statics
333
			Versioned::set_stage($oldStage);
334
335
			Injector::unnest(); // Restore old CookieJar, etc
336
			Config::unnest();
337
		};
338
339 View Code Duplication
		if (strpos($url, '#') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
340
			$url = substr($url, 0, strpos($url, '#'));
341
		}
342
343
		// Handle absolute URLs
344
		if (parse_url($url, PHP_URL_HOST)) {
345
			$bits = parse_url($url);
346
			// If a port is mentioned in the absolute URL, be sure to add that into the HTTP host
347
			if (isset($bits['port'])) {
348
				$_SERVER['HTTP_HOST'] = $bits['host'].':'.$bits['port'];
349
			} else {
350
				$_SERVER['HTTP_HOST'] = $bits['host'];
351
			}
352
		}
353
354
		// Ensure URL is properly made relative.
355
		// Example: url passed is "/ss31/my-page" (prefixed with BASE_URL), this should be changed to "my-page"
356
		$url = self::makeRelative($url);
357
358
		$urlWithQuerystring = $url;
359
		if (strpos($url, '?') !== false) {
360
			list($url, $getVarsEncoded) = explode('?', $url, 2);
361
			parse_str($getVarsEncoded, $getVars);
362
		}
363
364
		// Replace the super globals with appropriate test values
365
		$_REQUEST = ArrayLib::array_merge_recursive((array) $getVars, (array) $postVars);
366
		$_GET = (array) $getVars;
367
		$_POST = (array) $postVars;
368
		$_SESSION = $session ? $session->inst_getAll() : array();
369
		$_COOKIE = $cookieJar->getAll(false);
370
		Injector::inst()->registerService($cookieJar, 'SilverStripe\Control\Cookie_Backend');
371
		$_SERVER['REQUEST_URI'] = Director::baseURL() . $urlWithQuerystring;
372
373
		$request = new HTTPRequest($httpMethod, $url, $getVars, $postVars, $body);
0 ignored issues
show
Bug introduced by
It seems like $getVars can also be of type null; however, SilverStripe\Control\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 285 can also be of type null; however, SilverStripe\Control\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...
374
		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...
375
			foreach($headers as $k => $v) {
376
				$request->addHeader($k, $v);
377
			}
378
		}
379
380
		// Pre-request filtering
381
		// @see issue #2517
382
		$model = DataModel::inst();
383
		$output = Injector::inst()->get('SilverStripe\Control\RequestProcessor')->preRequest($request, $session, $model);
384
		if ($output === false) {
385
			$onCleanup();
386
			throw new HTTPResponse_Exception(_t('Director.INVALID_REQUEST', 'Invalid request'), 400);
387
		}
388
389
		// TODO: Pass in the DataModel
390
		$result = Director::handleRequest($request, $session, $model);
391
392
		// Ensure that the result is an SS_HTTPResponse object
393
		if (is_string($result)) {
394
			if (substr($result, 0, 9) == 'redirect:') {
395
				$response = new HTTPResponse();
396
				$response->redirect(substr($result, 9));
397
				$result = $response;
398
			} else {
399
				$result = new HTTPResponse($result);
400
			}
401
		}
402
403
		$output = Injector::inst()->get('SilverStripe\Control\RequestProcessor')->postRequest($request, $result, $model);
404
		if ($output === false) {
405
			$onCleanup();
406
			throw new HTTPResponse_Exception("Invalid response");
407
		}
408
409
		// Return valid response
410
		$onCleanup();
411
		return $result;
412
	}
413
414
	/**
415
	 * Handle an HTTP request, defined with a SS_HTTPRequest object.
416
	 *
417
	 * @param SS_HTTPRequest $request
418
	 * @param Session $session
419
	 * @param DataModel $model
420
	 *
421
	 * @return HTTPResponse|string
422
	 */
423
	protected static function handleRequest(HTTPRequest $request, Session $session, DataModel $model) {
424
		$rules = Config::inst()->get('SilverStripe\Control\Director', 'rules');
425
426
		if (isset($_REQUEST['debug'])) Debug::show($rules);
427
428
		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...
429
			if (is_string($controllerOptions)) {
430
				if (substr($controllerOptions, 0, 2) == '->') {
431
					$controllerOptions = array('Redirect' => substr($controllerOptions, 2));
432
				} else {
433
					$controllerOptions = array('Controller' => $controllerOptions);
434
				}
435
			}
436
437
			if (($arguments = $request->match($pattern, true)) !== false) {
438
				$request->setRouteParams($controllerOptions);
439
				// controllerOptions provide some default arguments
440
				$arguments = array_merge($controllerOptions, $arguments);
441
442
				// Find the controller name
443
				if (isset($arguments['Controller'])) $controller = $arguments['Controller'];
444
445
				// Pop additional tokens from the tokenizer if necessary
446
				if (isset($controllerOptions['_PopTokeniser'])) {
447
					$request->shift($controllerOptions['_PopTokeniser']);
448
				}
449
450
				// Handle redirection
451
				if (isset($arguments['Redirect'])) {
452
					return "redirect:" . Director::absoluteURL($arguments['Redirect'], true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
453
454
				} else {
455
					Director::$urlParams = $arguments;
456
					$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...
457
					$controllerObj->setSession($session);
458
459
					try {
460
						$result = $controllerObj->handleRequest($request, $model);
461
					} catch(HTTPResponse_Exception $responseException) {
462
						$result = $responseException->getResponse();
463
					}
464
					if (!is_object($result) || $result instanceof HTTPResponse) return $result;
465
466
					user_error("Bad result from url " . $request->getURL() . " handled by " .
467
						get_class($controllerObj)." controller: ".get_class($result), E_USER_WARNING);
468
				}
469
			}
470
		}
471
472
		// No URL rules matched, so return a 404 error.
473
		return new HTTPResponse('No URL rule was matched', 404);
474
	}
475
476
	/**
477
	 * Set url parameters (should only be called internally by RequestHandler->handleRequest()).
478
	 *
479
	 * @param array $params
480
	 */
481
	public static function setUrlParams($params) {
482
		Director::$urlParams = $params;
483
	}
484
485
	/**
486
	 * Return the {@link SiteTree} object that is currently being viewed. If there is no SiteTree
487
	 * object to return, then this will return the current controller.
488
	 *
489
	 * @return SiteTree
490
	 */
491
	public static function get_current_page() {
492
		return self::$current_page ? self::$current_page : Controller::curr();
0 ignored issues
show
Bug Compatibility introduced by
The expression self::$current_page ? se...rol\Controller::curr(); of type SilverStripe\Control\Sit...ripe\Control\Controller adds the type SilverStripe\Control\Controller to the return on line 492 which is incompatible with the return type documented by SilverStripe\Control\Director::get_current_page of type SilverStripe\Control\SiteTree.
Loading history...
493
	}
494
495
	/**
496
	 * Set the currently active {@link SiteTree} object that is being used to respond to the request.
497
	 *
498
	 * @param SiteTree $page
499
	 */
500
	public static function set_current_page($page) {
501
		self::$current_page = $page;
502
	}
503
504
	/**
505
	 * Turns the given URL into an absolute URL. By default non-site root relative urls will be
506
	 * evaluated relative to the current base_url.
507
	 *
508
	 * @param string $url URL To transform to absolute.
509
	 * @param string $relativeParent Method to use for evaluating relative urls.
510
	 * Either one of BASE (baseurl), ROOT (site root), or REQUEST (requested page).
511
	 * Defaults to BASE, which is the same behaviour as template url resolution.
512
	 * Ignored if the url is absolute or site root.
513
	 *
514
	 * @return string
515
	 */
516
	public static function absoluteURL($url, $relativeParent = self::BASE) {
517
		if (is_bool($relativeParent)) {
518
			// Deprecate old boolean second parameter
519
			Deprecation::notice('5.0', 'Director::absoluteURL takes an explicit parent for relative url');
520
			$relativeParent = $relativeParent ? self::BASE : self::REQUEST;
521
		}
522
523
		// Check if there is already a protocol given
524
		if (preg_match('/^http(s?):\/\//', $url)) {
525
			return $url;
526
		}
527
528
		// Absolute urls without protocol are added
529
		// E.g. //google.com -> http://google.com
530
		if (strpos($url, '//') === 0) {
531
			return self::protocol() . substr($url, 2);
532
		}
533
534
		// Determine method for mapping the parent to this relative url
535
		if ($relativeParent === self::ROOT || self::is_root_relative_url($url)) {
536
			// Root relative urls always should be evaluated relative to the root
537
			$parent = self::protocolAndHost();
538
539
		} elseif ($relativeParent === self::REQUEST) {
540
			// Request relative urls rely on the REQUEST_URI param (old default behaviour)
541
			if (!isset($_SERVER['REQUEST_URI'])) {
542
				return false;
543
			}
544
			$parent = dirname($_SERVER['REQUEST_URI'] . 'x');
545
546
		} else {
547
			// Default to respecting site base_url
548
			$parent = self::absoluteBaseURL();
549
		}
550
551
		// Map empty urls to relative slash and join to base
552
		if (empty($url) || $url === '.' || $url === './') {
553
			$url = '/';
554
		}
555
		return Controller::join_links($parent, $url);
556
557
	}
558
559
	/**
560
	 * Returns the domain part of the URL 'http://www.mysite.com'. Returns FALSE is this environment
561
	 * variable isn't set.
562
	 *
563
	 * @return bool|string
564
	 */
565
	public static function protocolAndHost() {
566
		$alternate = Config::inst()->get('SilverStripe\Control\Director', 'alternate_base_url');
567
		if ($alternate) {
568
			if (preg_match('/^(http[^:]*:\/\/[^\/]+)(\/|$)/', $alternate, $matches)) {
569
				return $matches[1];
570
			}
571
		}
572
573
		if (isset($_SERVER['HTTP_HOST'])) {
574
			return Director::protocol() . $_SERVER['HTTP_HOST'];
575
		} else {
576
			global $_FILE_TO_URL_MAPPING;
577
			if (Director::is_cli() && isset($_FILE_TO_URL_MAPPING)) {
578
				$errorSuggestion = '  You probably want to define ' .
579
				'an entry in $_FILE_TO_URL_MAPPING that covers "' . Director::baseFolder() . '"';
580
			} elseif (Director::is_cli()) {
581
				$errorSuggestion = '  You probably want to define $_FILE_TO_URL_MAPPING in ' .
582
				'your _ss_environment.php as instructed on the "sake" page of the doc.silverstripe.com wiki';
583
			} else {
584
				$errorSuggestion = "";
585
			}
586
587
			user_error("Director::protocolAndHost() lacks sufficient information - HTTP_HOST not set."
588
				. $errorSuggestion, E_USER_WARNING);
589
			return false;
590
591
		}
592
	}
593
594
	/**
595
	 * Return the current protocol that the site is running under.
596
	 *
597
	 * @return string
598
	 */
599
	public static function protocol() {
600
		return (self::is_https()) ? 'https://' : 'http://';
601
	}
602
603
	/**
604
	 * Return whether the site is running as under HTTPS.
605
	 *
606
	 * @return bool
607
	 */
608
	public static function is_https() {
609
		// See https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
610
		// See https://support.microsoft.com/en-us/kb/307347
611
		$headerOverride = false;
612 View Code Duplication
		if (TRUSTED_PROXY) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
613
			$headers = (defined('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) ? array(SS_TRUSTED_PROXY_PROTOCOL_HEADER) : null;
614
			if (!$headers) {
615
				// Backwards compatible defaults
616
				$headers = array('HTTP_X_FORWARDED_PROTO', 'HTTP_X_FORWARDED_PROTOCOL', 'HTTP_FRONT_END_HTTPS');
617
			}
618
			foreach($headers as $header) {
619
				$headerCompareVal = ($header === 'HTTP_FRONT_END_HTTPS' ? 'on' : 'https');
620
				if (!empty($_SERVER[$header]) && strtolower($_SERVER[$header]) == $headerCompareVal) {
621
					$headerOverride = true;
622
					break;
623
				}
624
			}
625
		}
626
627
		if ($protocol = Config::inst()->get('SilverStripe\Control\Director', 'alternate_protocol')) {
628
			return ($protocol == 'https');
629
		} elseif ($headerOverride) {
630
			return true;
631
		} elseif ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) {
632
			return true;
633
		} elseif (isset($_SERVER['SSL'])) {
634
			return true;
635
		} else {
636
			return false;
637
		}
638
	}
639
640
	/**
641
	 * Returns the root URL for the site. It will be automatically calculated unless it is overridden
642
	 * with {@link setBaseURL()}.
643
	 *
644
	 * @return string
645
	 */
646
	public static function baseURL() {
647
		$alternate = Config::inst()->get('SilverStripe\Control\Director', 'alternate_base_url');
648
649
		if ($alternate) {
650
			return $alternate;
651
		} else {
652
			$base = BASE_URL;
653
654
			if ($base == '/' || $base == '/.' || $base == '\\') {
655
				$baseURL = '/';
656
			} else {
657
				$baseURL = $base . '/';
658
			}
659
660
			if (defined('BASE_SCRIPT_URL')) {
661
				return $baseURL . BASE_SCRIPT_URL;
662
			}
663
664
			return $baseURL;
665
		}
666
	}
667
668
	/**
669
	 * Sets the root URL for the website. If the site isn't accessible from the URL you provide,
670
	 * weird things will happen.
671
	 *
672
	 * @deprecated 4.0 Use the "Director.alternate_base_url" config setting instead.
673
	 *
674
	 * @param string $baseURL
675
	 */
676
	public static function setBaseURL($baseURL) {
677
		Deprecation::notice('4.0', 'Use the "Director.alternate_base_url" config setting instead');
678
		Config::inst()->update('SilverStripe\Control\Director', 'alternate_base_url', $baseURL);
679
	}
680
681
	/**
682
	 * Returns the root filesystem folder for the site. It will be automatically calculated unless
683
	 * it is overridden with {@link setBaseFolder()}.
684
	 *
685
	 * @return string
686
	 */
687
	public static function baseFolder() {
688
		$alternate = Config::inst()->get('SilverStripe\Control\Director', 'alternate_base_folder');
689
		return ($alternate) ? $alternate : BASE_PATH;
690
	}
691
692
	/**
693
	 * Sets the root folder for the website. If the site isn't accessible from the folder you provide,
694
	 * weird things will happen.
695
	 *
696
	 * @deprecated 4.0 Use the "Director.alternate_base_folder" config setting instead.
697
	 *
698
	 * @param string $baseFolder
699
	 */
700
	public static function setBaseFolder($baseFolder) {
701
		Deprecation::notice('4.0', 'Use the "Director.alternate_base_folder" config setting instead');
702
		Config::inst()->update('SilverStripe\Control\Director', 'alternate_base_folder', $baseFolder);
703
	}
704
705
	/**
706
	 * Turns an absolute URL or folder into one that's relative to the root of the site. This is useful
707
	 * when turning a URL into a filesystem reference, or vice versa.
708
	 *
709
	 * @param string $url Accepts both a URL or a filesystem path.
710
	 *
711
	 * @return string
712
	 */
713
	public static function makeRelative($url) {
714
		// Allow for the accidental inclusion whitespace and // in the URL
715
		$url = trim(preg_replace('#([^:])//#', '\\1/', $url));
716
717
		$base1 = self::absoluteBaseURL();
718
		$baseDomain = substr($base1, strlen(self::protocol()));
719
720
		// Only bother comparing the URL to the absolute version if $url looks like a URL.
721
		if (preg_match('/^https?[^:]*:\/\//', $url, $matches)) {
722
			$urlProtocol = $matches[0];
723
			$urlWithoutProtocol = substr($url, strlen($urlProtocol));
724
725
			// If we are already looking at baseURL, return '' (substr will return false)
726
			if ($url == $base1) {
727
				return '';
728
			} elseif (substr($url, 0, strlen($base1)) == $base1) {
729
				return substr($url, strlen($base1));
730
			} elseif (substr($base1, -1) == "/" && $url == substr($base1, 0, -1)) {
731
				// Convert http://www.mydomain.com/mysitedir to ''
732
				return "";
733
			}
734
735 View Code Duplication
			if (substr($urlWithoutProtocol, 0, strlen($baseDomain)) == $baseDomain) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
736
				return substr($urlWithoutProtocol, strlen($baseDomain));
737
			}
738
		}
739
740
		// test for base folder, e.g. /var/www
741
		$base2 = self::baseFolder();
742
		if (substr($url, 0, strlen($base2)) == $base2) return substr($url, strlen($base2));
743
744
		// Test for relative base url, e.g. mywebsite/ if the full URL is http://localhost/mywebsite/
745
		$base3 = self::baseURL();
746
		if (substr($url, 0, strlen($base3)) == $base3) {
747
			return substr($url, strlen($base3));
748
		}
749
750
		// Test for relative base url, e.g mywebsite/ if the full url is localhost/myswebsite
751 View Code Duplication
		if (substr($url, 0, strlen($baseDomain)) == $baseDomain) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
752
			return substr($url, strlen($baseDomain));
753
		}
754
755
		// Nothing matched, fall back to returning the original URL
756
		return $url;
757
	}
758
759
	/**
760
	 * Returns true if a given path is absolute. Works under both *nix and windows systems.
761
	 *
762
	 * @param string $path
763
	 *
764
	 * @return bool
765
	 */
766
	public static function is_absolute($path) {
767
		if (empty($path)) return false;
768
		if ($path[0] == '/' || $path[0] == '\\') return true;
769
		return preg_match('/^[a-zA-Z]:[\\\\\/]/', $path) == 1;
770
	}
771
772
	/**
773
	 * Determine if the url is root relative (i.e. starts with /, but not with //) SilverStripe
774
	 * considers root relative urls as a subset of relative urls.
775
	 *
776
	 * @param string $url
777
	 *
778
	 * @return bool
779
	 */
780
	public static function is_root_relative_url($url) {
781
		return strpos($url, '/') === 0 && strpos($url, '//') !== 0;
782
	}
783
784
	/**
785
	 * Checks if a given URL is absolute (e.g. starts with 'http://' etc.). URLs beginning with "//"
786
	 * are treated as absolute, as browsers take this to mean the same protocol as currently being used.
787
	 *
788
	 * Useful to check before redirecting based on a URL from user submissions through $_GET or $_POST,
789
	 * and avoid phishing attacks by redirecting to an attackers server.
790
	 *
791
	 * Note: Can't solely rely on PHP's parse_url() , since it is not intended to work with relative URLs
792
	 * or for security purposes. filter_var($url, FILTER_VALIDATE_URL) has similar problems.
793
	 *
794
	 * @param string $url
795
	 *
796
	 * @return bool
797
	 */
798
	public static function is_absolute_url($url) {
799
		// Strip off the query and fragment parts of the URL before checking
800 View Code Duplication
		if (($queryPosition = strpos($url, '?')) !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
801
			$url = substr($url, 0, $queryPosition-1);
802
		}
803 View Code Duplication
		if (($hashPosition = strpos($url, '#')) !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
804
			$url = substr($url, 0, $hashPosition-1);
805
		}
806
		$colonPosition = strpos($url, ':');
807
		$slashPosition = strpos($url, '/');
808
		return (
809
			// Base check for existence of a host on a compliant URL
810
			parse_url($url, PHP_URL_HOST)
811
			// Check for more than one leading slash without a protocol.
812
			// While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers,
813
			// and hence a potential security risk. Single leading slashes are not an issue though.
814
			|| preg_match('%^\s*/{2,}%', $url)
815
			|| (
816
				// If a colon is found, check if it's part of a valid scheme definition
817
				// (meaning its not preceded by a slash).
818
				$colonPosition !== FALSE
819
				&& ($slashPosition === FALSE || $colonPosition < $slashPosition)
820
			)
821
		);
822
	}
823
824
	/**
825
	 * Checks if a given URL is relative (or root relative) by checking {@link is_absolute_url()}.
826
	 *
827
	 * @param string $url
828
	 *
829
	 * @return bool
830
	 */
831
	public static function is_relative_url($url) {
832
		return !static::is_absolute_url($url);
833
	}
834
835
	/**
836
	 * Checks if the given URL is belonging to this "site" (not an external link). That's the case if
837
	 * the URL is relative, as defined by {@link is_relative_url()}, or if the host matches
838
	 * {@link protocolAndHost()}.
839
	 *
840
	 * Useful to check before redirecting based on a URL from user submissions through $_GET or $_POST,
841
	 * and avoid phishing attacks by redirecting to an attackers server.
842
	 *
843
	 * @param string $url
844
	 *
845
	 * @return bool
846
	 */
847
	public static function is_site_url($url) {
848
		$urlHost = parse_url($url, PHP_URL_HOST);
849
		$actualHost = parse_url(self::protocolAndHost(), PHP_URL_HOST);
850
		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...
851
			return true;
852
		} else {
853
			return self::is_relative_url($url);
854
		}
855
	}
856
857
	/**
858
	 * Takes a $_SERVER data array and extracts HTTP request headers.
859
	 *
860
	 * @param array $server
861
	 *
862
	 * @return array
863
	 */
864
	public static function extract_request_headers(array $server) {
865
		$headers = array();
866
867
		foreach($server as $key => $value) {
868
			if (substr($key, 0, 5) == 'HTTP_') {
869
				$key = substr($key, 5);
870
				$key = strtolower(str_replace('_', ' ', $key));
871
				$key = str_replace(' ', '-', ucwords($key));
872
				$headers[$key] = $value;
873
			}
874
		}
875
876
		if (isset($server['CONTENT_TYPE'])) $headers['Content-Type'] = $server['CONTENT_TYPE'];
877
		if (isset($server['CONTENT_LENGTH'])) $headers['Content-Length'] = $server['CONTENT_LENGTH'];
878
879
		return $headers;
880
	}
881
882
	/**
883
	 * Given a filesystem reference relative to the site root, return the full file-system path.
884
	 *
885
	 * @param string $file
886
	 *
887
	 * @return string
888
	 */
889
	public static function getAbsFile($file) {
890
		return self::is_absolute($file) ? $file : Director::baseFolder() . '/' . $file;
891
	}
892
893
	/**
894
	 * Returns true if the given file exists. Filename should be relative to the site root.
895
	 *
896
	 * @param $file
897
	 *
898
	 * @return bool
899
	 */
900
	public static function fileExists($file) {
901
		// replace any appended query-strings, e.g. /path/to/foo.php?bar=1 to /path/to/foo.php
902
		$file = preg_replace('/([^\?]*)?.*/', '$1', $file);
903
		return file_exists(Director::getAbsFile($file));
904
	}
905
906
	/**
907
	 * Returns the Absolute URL of the site root.
908
	 *
909
	 * @return string
910
	 */
911
	public static function absoluteBaseURL() {
912
		return self::absoluteURL(
913
			self::baseURL(),
914
			self::ROOT
915
		);
916
	}
917
918
	/**
919
	 * Returns the Absolute URL of the site root, embedding the current basic-auth credentials into
920
	 * the URL.
921
	 *
922
	 * @return string
923
	 */
924
	public static function absoluteBaseURLWithAuth() {
925
		$s = "";
0 ignored issues
show
Unused Code introduced by
$s is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
926
		$login = "";
927
928
		if (isset($_SERVER['PHP_AUTH_USER'])) $login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@";
929
930
		return Director::protocol() . $login .  $_SERVER['HTTP_HOST'] . Director::baseURL();
931
	}
932
933
	/**
934
	 * Skip any further processing and immediately respond with a redirect to the passed URL.
935
	 *
936
	 * @param string $destURL
937
	 */
938
	protected static function force_redirect($destURL) {
939
		$response = new HTTPResponse();
940
		$response->redirect($destURL, 301);
941
942
		HTTP::add_cache_headers($response);
0 ignored issues
show
Documentation introduced by
$response is of type object<SilverStripe\Control\HTTPResponse>, but the function expects a object<SilverStripe\Control\SS_HTTPResponse>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
943
944
		// TODO: Use an exception - ATM we can be called from _config.php, before Director#handleRequest's try block
945
		$response->output();
946
		die;
947
	}
948
949
	/**
950
	 * Force the site to run on SSL.
951
	 *
952
	 * To use, call from _config.php. For example:
953
	 * <code>
954
	 * if (Director::isLive()) Director::forceSSL();
955
	 * </code>
956
	 *
957
	 * If you don't want your entire site to be on SSL, you can pass an array of PCRE regular expression
958
	 * patterns for matching relative URLs. For example:
959
	 * <code>
960
	 * if (Director::isLive()) Director::forceSSL(array('/^admin/', '/^Security/'));
961
	 * </code>
962
	 *
963
	 * If you want certain parts of your site protected under a different domain, you can specify
964
	 * the domain as an argument:
965
	 * <code>
966
	 * if (Director::isLive()) Director::forceSSL(array('/^admin/', '/^Security/'), 'secure.mysite.com');
967
	 * </code>
968
	 *
969
	 * Note that the session data will be lost when moving from HTTP to HTTPS. It is your responsibility
970
	 * to ensure that this won't cause usability problems.
971
	 *
972
	 * CAUTION: This does not respect the site environment mode. You should check this
973
	 * as per the above examples using Director::isLive() or Director::isTest() for example.
974
	 *
975
	 * @param array $patterns Array of regex patterns to match URLs that should be HTTPS.
976
	 * @param string $secureDomain Secure domain to redirect to. Defaults to the current domain.
977
	 *
978
	 * @return bool|string String of URL when unit tests running, boolean FALSE if patterns don't match request URI.
979
	 */
980
	public static function forceSSL($patterns = null, $secureDomain = null) {
981
		if (!isset($_SERVER['REQUEST_URI'])) return false;
982
983
		$matched = false;
984
985
		if ($patterns) {
986
			// Calling from the command-line?
987
			if (!isset($_SERVER['REQUEST_URI'])) return;
988
989
			$relativeURL = self::makeRelative(Director::absoluteURL($_SERVER['REQUEST_URI']));
0 ignored issues
show
Security Bug introduced by
It seems like \SilverStripe\Control\Di..._SERVER['REQUEST_URI']) targeting SilverStripe\Control\Director::absoluteURL() can also be of type false; however, SilverStripe\Control\Director::makeRelative() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
990
991
			// protect portions of the site based on the pattern
992
			foreach($patterns as $pattern) {
993
				if (preg_match($pattern, $relativeURL)) {
994
					$matched = true;
995
					break;
996
				}
997
			}
998
		} else {
999
			// protect the entire site
1000
			$matched = true;
1001
		}
1002
1003
		if ($matched && !self::is_https()) {
1004
1005
			// if an domain is specified, redirect to that instead of the current domain
1006
			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...
1007
				$url = 'https://' . $secureDomain . $_SERVER['REQUEST_URI'];
1008
			} else {
1009
				$url = $_SERVER['REQUEST_URI'];
1010
			}
1011
1012
			$destURL = str_replace('http:', 'https:', Director::absoluteURL($url));
1013
1014
			// This coupling to SapphireTest is necessary to test the destination URL and to not interfere with tests
1015
			if (class_exists('SapphireTest', false) && SapphireTest::is_running_test()) {
1016
				return $destURL;
1017
			} else {
1018
				self::force_redirect($destURL);
1019
			}
1020
		} else {
1021
			return false;
1022
		}
1023
	}
1024
1025
	/**
1026
	 * Force a redirect to a domain starting with "www."
1027
	 */
1028
	public static function forceWWW() {
1029
		if (!Director::isDev() && !Director::isTest() && strpos($_SERVER['HTTP_HOST'], 'www') !== 0) {
1030
			$destURL = str_replace(Director::protocol(), Director::protocol() . 'www.',
1031
				Director::absoluteURL($_SERVER['REQUEST_URI']));
1032
1033
			self::force_redirect($destURL);
1034
		}
1035
	}
1036
1037
	/**
1038
	 * Checks if the current HTTP-Request is an "Ajax-Request" by checking for a custom header set by
1039
	 * jQuery or whether a manually set request-parameter 'ajax' is present.
1040
	 *
1041
	 * @return bool
1042
	 */
1043
	public static function is_ajax() {
1044
		if (Controller::has_curr()) {
1045
			return Controller::curr()->getRequest()->isAjax();
1046
		} else {
1047
			return (
1048
				isset($_REQUEST['ajax']) ||
1049
				(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == "XMLHttpRequest")
1050
			);
1051
		}
1052
	}
1053
1054
	/**
1055
	 * Returns true if this script is being run from the command line rather than the web server.
1056
	 *
1057
	 * @return bool
1058
	 */
1059
	public static function is_cli() {
1060
		return (php_sapi_name() == "cli");
1061
	}
1062
1063
	/**
1064
	 * Set the environment type of the current site.
1065
	 *
1066
	 * Typically, a SilverStripe site have a number of environments:
1067
	 *  - Development environments, such a copy on your local machine.
1068
	 *  - Test sites, such as the one you show the client before going live.
1069
	 *  - The live site itself.
1070
	 *
1071
	 * The behaviour of these environments often varies slightly.  For example, development sites may
1072
	 * have errors dumped to the screen, and order confirmation emails might be sent to the developer
1073
	 * instead of the client.
1074
	 *
1075
	 * To help with this, SilverStripe supports the notion of an environment type.  The environment
1076
	 * type can be dev, test, or live.
1077
	 *
1078
	 * You can set it explicitly with {@link Director::set_environment_type()}. Or you can use
1079
	 * {@link Director::$dev_servers} and {@link Director::$test_servers} to set it implicitly, based
1080
	 * on the value of $_SERVER['HTTP_HOST'].  If the HTTP_HOST value is one of the servers listed,
1081
	 * then the environment type will be test or dev.  Otherwise, the environment type will be live.
1082
	 *
1083
	 * Dev mode can also be forced by putting ?isDev=1 in your URL, which will ask you to log in and
1084
	 * then push the site into dev mode for the remainder of the session. Putting ?isDev=0 onto the URL
1085
	 * can turn it back.
1086
	 *
1087
	 * Test mode can also be forced by putting ?isTest=1 in your URL, which will ask you to log in and
1088
	 * then push the site into test mode for the remainder of the session. Putting ?isTest=0 onto the URL
1089
	 * can turn it back.
1090
	 *
1091
	 * Generally speaking, these methods will be called from your _config.php file.
1092
	 *
1093
	 * Once the environment type is set, it can be checked with {@link Director::isDev()},
1094
	 * {@link Director::isTest()}, and {@link Director::isLive()}.
1095
	 *
1096
	 * @deprecated 4.0 Use the "Director.environment_type" config setting instead
1097
	 *
1098
	 * @param $et string
1099
	 */
1100
	public static function set_environment_type($et) {
1101
		if ($et != 'dev' && $et != 'test' && $et != 'live') {
1102
			user_error("Director::set_environment_type passed '$et'.  It should be passed dev, test, or live",
1103
				E_USER_WARNING);
1104
		} else {
1105
			Deprecation::notice('4.0', 'Use the "Director.environment_type" config setting instead');
1106
			Config::inst()->update('SilverStripe\Control\Director', 'environment_type', $et);
1107
		}
1108
	}
1109
1110
	/**
1111
	 * Can also be checked with {@link Director::isDev()}, {@link Director::isTest()}, and
1112
	 * {@link Director::isLive()}.
1113
	 *
1114
	 * @return bool|string
1115
	 */
1116
	public static function get_environment_type() {
1117
		if (Director::isLive()) {
1118
			return 'live';
1119
		} elseif (Director::isTest()) {
1120
			return 'test';
1121
		} elseif (Director::isDev()) {
1122
			return 'dev';
1123
		} else {
1124
			return false;
1125
		}
1126
	}
1127
1128
	/**
1129
	 * This function will return true if the site is in a live environment. For information about
1130
	 * environment types, see {@link Director::set_environment_type()}.
1131
	 *
1132
	 * @return bool
1133
	 */
1134
	public static function isLive() {
1135
		return !(Director::isDev() || Director::isTest());
1136
	}
1137
1138
	/**
1139
	 * This function will return true if the site is in a development environment. For information about
1140
	 * environment types, see {@link Director::set_environment_type()}.
1141
	 *
1142
	 * @return bool
1143
	 */
1144 View Code Duplication
	public static function isDev() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1145
		// Check session
1146
		if ($env = self::session_environment()) return $env === 'dev';
1147
1148
		// Check config
1149
		if (Config::inst()->get('SilverStripe\Control\Director', 'environment_type') === 'dev') return true;
1150
1151
		// Check if we are running on one of the test servers
1152
		$devServers = (array)Config::inst()->get('SilverStripe\Control\Director', 'dev_servers');
1153
		if (isset($_SERVER['HTTP_HOST']) && in_array($_SERVER['HTTP_HOST'], $devServers))  {
1154
			return true;
1155
		}
1156
1157
		return false;
1158
	}
1159
1160
	/**
1161
	 * This function will return true if the site is in a test environment. For information about
1162
	 * environment types, see {@link Director::set_environment_type()}.
1163
	 *
1164
	 * @return bool
1165
	 */
1166 View Code Duplication
	public static function isTest() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1167
		// In case of isDev and isTest both being set, dev has higher priority
1168
		if (self::isDev()) return false;
1169
1170
		// Check saved session
1171
		if ($env = self::session_environment()) return $env === 'test';
1172
1173
		// Check config
1174
		if (Config::inst()->get('SilverStripe\Control\Director', 'environment_type') === 'test') return true;
1175
1176
		// Check if we are running on one of the test servers
1177
		$testServers = (array)Config::inst()->get('SilverStripe\Control\Director', 'test_servers');
1178
		if (isset($_SERVER['HTTP_HOST']) && in_array($_SERVER['HTTP_HOST'], $testServers))  {
1179
			return true;
1180
		}
1181
1182
		return false;
1183
	}
1184
1185
	/**
1186
	 * Check or update any temporary environment specified in the session.
1187
	 *
1188
	 * @return null|string
1189
	 */
1190
	protected static function session_environment() {
1191
		// Set session from querystring
1192
		if (isset($_GET['isDev'])) {
1193
			if (isset($_SESSION)) {
1194
				unset($_SESSION['isTest']); // In case we are changing from test mode
1195
				$_SESSION['isDev'] = $_GET['isDev'];
1196
			}
1197
			return 'dev';
1198
		} elseif (isset($_GET['isTest'])) {
1199
			if (isset($_SESSION)) {
1200
				unset($_SESSION['isDev']); // In case we are changing from dev mode
1201
				$_SESSION['isTest'] = $_GET['isTest'];
1202
			}
1203
			return 'test';
1204
		}
1205
		// Check session
1206
		if (isset($_SESSION['isDev']) && $_SESSION['isDev']) {
1207
			return 'dev';
1208
		} elseif (isset($_SESSION['isTest']) && $_SESSION['isTest']) {
1209
			return 'test';
1210
		} else {
1211
			return null;
1212
		}
1213
	}
1214
1215
	/**
1216
	 * Returns an array of strings of the method names of methods on the call that should be exposed
1217
	 * as global variables in the templates.
1218
	 *
1219
	 * @return array
1220
	 */
1221
	public static function get_template_global_variables() {
1222
		return array(
1223
			'absoluteBaseURL',
1224
			'baseURL',
1225
			'is_ajax',
1226
			'isAjax' => 'is_ajax',
1227
			'BaseHref' => 'absoluteBaseURL',    //@deprecated 3.0
1228
		);
1229
	}
1230
}
1231