Completed
Pull Request — master (#6337)
by Daniel
10:15
created

Director::isTest()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 4
nop 0
dl 0
loc 21
rs 9.0534
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control;
4
5
use SilverStripe\CMS\Model\SiteTree;
6
use SilverStripe\Core\Config\Config;
7
use SilverStripe\Core\Config\Configurable;
8
use SilverStripe\Core\Injector\Injector;
9
use SilverStripe\Dev\Debug;
10
use SilverStripe\Dev\Deprecation;
11
use SilverStripe\Dev\SapphireTest;
12
use SilverStripe\ORM\ArrayLib;
13
use SilverStripe\ORM\DataModel;
14
use SilverStripe\ORM\Versioning\Versioned;
15
use SilverStripe\View\Requirements;
16
use SilverStripe\View\Requirements_Backend;
17
use SilverStripe\View\TemplateGlobalProvider;
18
19
/**
20
 * Director is responsible for processing URLs, and providing environment information.
21
 *
22
 * The most important part of director is {@link Director::direct()}, which is passed a URL and will
23
 * execute the appropriate controller.
24
 *
25
 * Director also has a number of static methods that provide information about the environment, such as
26
 * {@link Director::$environment_type}.
27
 *
28
 * @see Director::direct()
29
 * @see Director::$rules
30
 * @see Director::$environment_type
31
 */
32
class Director implements TemplateGlobalProvider
33
{
34
    use Configurable;
35
36
    /**
37
     * Specifies this url is relative to the base.
38
     *
39
     * @var string
40
     */
41
    const BASE = 'BASE';
42
43
    /**
44
     * Specifies this url is relative to the site root.
45
     *
46
     * @var string
47
     */
48
    const ROOT = 'ROOT';
49
50
    /**
51
     * specifies this url is relative to the current request.
52
     *
53
     * @var string
54
     */
55
    const REQUEST = 'REQUEST';
56
57
    /**
58
     * @var array
59
     */
60
    static private $urlParams;
61
62
    /**
63
     * @var array
64
     */
65
    static private $rules = array();
66
67
    /**
68
     * @var SiteTree
69
     */
70
    private static $current_page;
71
72
    /**
73
     * @config
74
     *
75
     * @var string
76
     */
77
    private static $alternate_base_folder;
78
79
    /**
80
     * @config
81
     *
82
     * @var array
83
     */
84
    private static $dev_servers = array();
85
86
    /**
87
     * @config
88
     *
89
     * @var array
90
     */
91
    private static $test_servers = array();
92
93
    /**
94
     * Setting this explicitly specifies the protocol ("http" or "https") used, overriding the normal
95
     * behaviour of Director::is_https introspecting it from the request. False values imply default
96
     * introspection.
97
     *
98
     * @config
99
     *
100
     * @var string
101
     */
102
    private static $alternate_protocol;
103
104
    /**
105
     * @config
106
     *
107
     * @var string
108
     */
109
    private static $alternate_host;
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
     * Process the given URL, creating the appropriate controller and executing it.
127
     *
128
     * Request processing is handled as follows:
129
     * - Director::direct() creates a new HTTPResponse object and passes this to
130
     *   Director::handleRequest().
131
     * - Director::handleRequest($request) checks each of the Director rules and identifies a controller
132
     *   to handle this request.
133
     * - Controller::handleRequest($request) is then called.  This will find a rule to handle the URL,
134
     *   and call the rule handling method.
135
     * - RequestHandler::handleRequest($request) is recursively called whenever a rule handling method
136
     *   returns a RequestHandler object.
137
     *
138
     * In addition to request processing, Director will manage the session, and perform the output of
139
     * the actual response to the browser.
140
     *
141
     * @uses handleRequest() rule-lookup logic is handled by this.
142
     * @uses TestController::handleRequest() This handles the page logic for a Director::direct() call.
143
     * @param string $url
144
     * @param DataModel $model
145
     * @throws HTTPResponse_Exception
146
     */
147
    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...
148
    {
149
        // check allowed hosts
150
        if (getenv('SS_ALLOWED_HOSTS') && !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...
151
            $all_allowed_hosts = explode(',', getenv('SS_ALLOWED_HOSTS'));
152
            if (!in_array(static::host(), $all_allowed_hosts)) {
153
                throw new HTTPResponse_Exception('Invalid Host', 400);
154
            }
155
        }
156
157
158
        // Validate $_FILES array before merging it with $_POST
159
        foreach ($_FILES as $k => $v) {
160
            if (is_array($v['tmp_name'])) {
161
                $v = ArrayLib::array_values_recursive($v['tmp_name']);
162
                foreach ($v as $tmpFile) {
163
                    if ($tmpFile && !is_uploaded_file($tmpFile)) {
164
                        user_error("File upload '$k' doesn't appear to be a valid upload", E_USER_ERROR);
165
                    }
166
                }
167
            } else {
168
                if ($v['tmp_name'] && !is_uploaded_file($v['tmp_name'])) {
169
                    user_error("File upload '$k' doesn't appear to be a valid upload", E_USER_ERROR);
170
                }
171
            }
172
        }
173
174
        $req = new HTTPRequest(
175
            (isset($_SERVER['X-HTTP-Method-Override']))
176
                ? $_SERVER['X-HTTP-Method-Override']
177
                : $_SERVER['REQUEST_METHOD'],
178
            $url,
179
            $_GET,
180
            ArrayLib::array_merge_recursive((array) $_POST, (array) $_FILES),
181
            @file_get_contents('php://input')
182
        );
183
184
        $headers = self::extract_request_headers($_SERVER);
185
        foreach ($headers as $header => $value) {
186
            $req->addHeader($header, $value);
187
        }
188
189
        // Initiate an empty session - doesn't initialize an actual PHP session until saved (see below)
190
        $session = Session::create(isset($_SESSION) ? $_SESSION : array());
191
192
        // Only resume a session if its not started already, and a session identifier exists
193
        if (!isset($_SESSION) && Session::request_contains_session_id()) {
194
            $session->inst_start();
195
        }
196
197
        $output = RequestProcessor::singleton()->preRequest($req, $session, $model);
198
199
        if ($output === false) {
200
            // @TODO Need to NOT proceed with the request in an elegant manner
201
            throw new HTTPResponse_Exception(_t('Director.INVALID_REQUEST', 'Invalid request'), 400);
202
        }
203
204
        $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...
205
206
        // Save session data. Note that inst_save() will start/resume the session if required.
207
        $session->inst_save();
208
209
        // Return code for a redirection request
210
        if (is_string($result) && substr($result, 0, 9) == 'redirect:') {
211
            $url = substr($result, 9);
212
213
            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...
214
                // on cli, follow SilverStripe redirects automatically
215
                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...
216
                    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...
217
                    DataModel::inst()
218
                );
219
                return;
220
            } else {
221
                $response = new HTTPResponse();
222
                $response->redirect($url);
223
                $res = RequestProcessor::singleton()->postRequest($req, $response, $model);
224
225
                if ($res !== false) {
226
                    $response->output();
227
                }
228
            }
229
        // Handle a controller
230
        } elseif ($result) {
231
            if ($result instanceof HTTPResponse) {
232
                $response = $result;
233
            } else {
234
                $response = new HTTPResponse();
235
                $response->setBody($result);
236
            }
237
238
            $res = RequestProcessor::singleton()->postRequest($req, $response, $model);
239
            if ($res !== false) {
240
                $response->output();
241
            } else {
242
                // @TODO Proper response here.
243
                throw new HTTPResponse_Exception("Invalid response");
244
            }
245
246
247
            //$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...
248
        }
249
    }
250
251
    /**
252
     * Test a URL request, returning a response object. This method is the counterpart of
253
     * Director::direct() that is used in functional testing. It will execute the URL given, and
254
     * return the result as an HTTPResponse object.
255
     *
256
     * @uses TestController::handleRequest() Handles the page logic for a Director::direct() call.
257
     *
258
     * @param string $url The URL to visit.
259
     * @param array $postVars The $_POST & $_FILES variables.
260
     * @param array|Session $session The {@link Session} object representing the current session.
261
     * By passing the same object to multiple  calls of Director::test(), you can simulate a persisted
262
     * session.
263
     * @param string $httpMethod The HTTP method, such as GET or POST.  It will default to POST if
264
     * postVars is set, GET otherwise. Overwritten by $postVars['_method'] if present.
265
     * @param string $body The HTTP body.
266
     * @param array $headers HTTP headers with key-value pairs.
267
     * @param array|Cookie_Backend $cookies to populate $_COOKIE.
268
     * @param HTTPRequest $request The {@see SS_HTTP_Request} object generated as a part of this request.
269
     *
270
     * @return HTTPResponse
271
     *
272
     * @throws HTTPResponse_Exception
273
     */
274
    public static function test(
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...
275
        $url,
276
        $postVars = null,
277
        $session = array(),
278
        $httpMethod = null,
279
        $body = null,
280
        $headers = array(),
281
        $cookies = array(),
282
        &$request = null
283
    ) {
284
285
        Config::nest();
286
        Injector::nest();
287
288
        // These are needed so that calling Director::test() does not muck with whoever is calling it.
289
        // Really, it's some inappropriate coupling and should be resolved by making less use of statics.
290
        $oldReadingMode = Versioned::get_reading_mode();
291
        $getVars = array();
292
293
        if (!$httpMethod) {
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...
294
            $httpMethod = ($postVars || is_array($postVars)) ? "POST" : "GET";
295
        }
296
297
        if (!$session) {
298
            $session = Injector::inst()->create('SilverStripe\\Control\\Session', array());
299
        }
300
        $cookieJar = $cookies instanceof Cookie_Backend
301
            ? $cookies
302
            : Injector::inst()->createWithArgs('SilverStripe\\Control\\Cookie_Backend', array($cookies ?: array()));
303
304
        // Back up the current values of the superglobals
305
        $existingRequestVars = isset($_REQUEST) ? $_REQUEST : array();
306
        $existingGetVars = isset($_GET) ? $_GET : array();
307
        $existingPostVars = isset($_POST) ? $_POST : array();
308
        $existingSessionVars = isset($_SESSION) ? $_SESSION : array();
309
        $existingCookies = isset($_COOKIE) ? $_COOKIE : array();
310
        $existingServer     = isset($_SERVER) ? $_SERVER : array();
311
312
        $existingRequirementsBackend = Requirements::backend();
313
314
        Cookie::config()->update('report_errors', false);
315
        Requirements::set_backend(Requirements_Backend::create());
316
317
        // Set callback to invoke prior to return
318
        $onCleanup = function () use (
319
            $existingRequestVars,
320
            $existingGetVars,
321
            $existingPostVars,
322
            $existingSessionVars,
323
            $existingCookies,
324
            $existingServer,
325
            $existingRequirementsBackend,
326
            $oldReadingMode
327
        ) {
328
            // Restore the super globals
329
            $_REQUEST = $existingRequestVars;
330
            $_GET = $existingGetVars;
331
            $_POST = $existingPostVars;
332
            $_SESSION = $existingSessionVars;
333
            $_COOKIE = $existingCookies;
334
            $_SERVER = $existingServer;
335
336
            Requirements::set_backend($existingRequirementsBackend);
337
338
            // These are needed so that calling Director::test() does not muck with whoever is calling it.
339
            // Really, it's some inappropriate coupling and should be resolved by making less use of statics
340
            Versioned::set_reading_mode($oldReadingMode);
341
342
            Injector::unnest(); // Restore old CookieJar, etc
343
            Config::unnest();
344
        };
345
346
        if (strpos($url, '#') !== false) {
347
            $url = substr($url, 0, strpos($url, '#'));
348
        }
349
350
        // Handle absolute URLs
351
        if (parse_url($url, PHP_URL_HOST)) {
352
            $bits = parse_url($url);
353
            // If a port is mentioned in the absolute URL, be sure to add that into the HTTP host
354
            if (isset($bits['port'])) {
355
                $_SERVER['HTTP_HOST'] = $bits['host'].':'.$bits['port'];
356
            } else {
357
                $_SERVER['HTTP_HOST'] = $bits['host'];
358
            }
359
        }
360
361
        // Ensure URL is properly made relative.
362
        // Example: url passed is "/ss31/my-page" (prefixed with BASE_URL), this should be changed to "my-page"
363
        $url = self::makeRelative($url);
364
365
        $urlWithQuerystring = $url;
366
        if (strpos($url, '?') !== false) {
367
            list($url, $getVarsEncoded) = explode('?', $url, 2);
368
            parse_str($getVarsEncoded, $getVars);
369
        }
370
371
        // Replace the super globals with appropriate test values
372
        $_REQUEST = ArrayLib::array_merge_recursive((array) $getVars, (array) $postVars);
373
        $_GET = (array) $getVars;
374
        $_POST = (array) $postVars;
375
        $_SESSION = $session ? $session->inst_getAll() : array();
376
        $_COOKIE = $cookieJar->getAll(false);
377
        Injector::inst()->registerService($cookieJar, 'SilverStripe\\Control\\Cookie_Backend');
378
        $_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...
379
380
        $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 276 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...
381
        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...
382
            foreach ($headers as $k => $v) {
383
                $request->addHeader($k, $v);
384
            }
385
        }
386
387
        // Pre-request filtering
388
        // @see issue #2517
389
        $model = DataModel::inst();
390
        $output = Injector::inst()->get('SilverStripe\\Control\\RequestProcessor')->preRequest($request, $session, $model);
391
        if ($output === false) {
392
            $onCleanup();
393
            throw new HTTPResponse_Exception(_t('Director.INVALID_REQUEST', 'Invalid request'), 400);
394
        }
395
396
        // TODO: Pass in the DataModel
397
        $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...
398
399
        // Ensure that the result is an HTTPResponse object
400
        if (is_string($result)) {
401
            if (substr($result, 0, 9) == 'redirect:') {
402
                $response = new HTTPResponse();
403
                $response->redirect(substr($result, 9));
404
                $result = $response;
405
            } else {
406
                $result = new HTTPResponse($result);
407
            }
408
        }
409
410
        $output = Injector::inst()->get('SilverStripe\\Control\\RequestProcessor')->postRequest($request, $result, $model);
411
        if ($output === false) {
412
            $onCleanup();
413
            throw new HTTPResponse_Exception("Invalid response");
414
        }
415
416
        // Return valid response
417
        $onCleanup();
418
        return $result;
419
    }
420
421
    /**
422
     * Handle an HTTP request, defined with a HTTPRequest object.
423
     *
424
     * @skipUpgrade
425
     * @param HTTPRequest $request
426
     * @param Session $session
427
     * @param DataModel $model
428
     * @return HTTPResponse|string
429
     */
430
    protected static function handleRequest(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...
431
    {
432
        $rules = Director::config()->get('rules');
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...
433
434
        if (isset($_REQUEST['debug'])) {
435
            Debug::show($rules);
436
        }
437
438
        foreach ($rules as $pattern => $controllerOptions) {
439
            if (is_string($controllerOptions)) {
440
                if (substr($controllerOptions, 0, 2) == '->') {
441
                    $controllerOptions = array('Redirect' => substr($controllerOptions, 2));
442
                } else {
443
                    $controllerOptions = array('Controller' => $controllerOptions);
444
                }
445
            }
446
447
            if (($arguments = $request->match($pattern, true)) !== false) {
448
                $request->setRouteParams($controllerOptions);
449
                // controllerOptions provide some default arguments
450
                $arguments = array_merge($controllerOptions, $arguments);
451
452
                // Pop additional tokens from the tokenizer if necessary
453
                if (isset($controllerOptions['_PopTokeniser'])) {
454
                    $request->shift($controllerOptions['_PopTokeniser']);
455
                }
456
457
                // Handle redirection
458
                if (isset($arguments['Redirect'])) {
459
                    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...
460
                } else {
461
                    // Find the controller name
462
                    $controller = $arguments['Controller'];
463
                    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...
464
                    $controllerObj = Injector::inst()->create($controller);
465
                    $controllerObj->setSession($session);
466
467
                    try {
468
                        $result = $controllerObj->handleRequest($request, $model);
469
                    } catch (HTTPResponse_Exception $responseException) {
470
                        $result = $responseException->getResponse();
471
                    }
472
                    if (!is_object($result) || $result instanceof HTTPResponse) {
473
                        return $result;
474
                    }
475
476
                    user_error("Bad result from url " . $request->getURL() . " handled by " .
477
                        get_class($controllerObj)." controller: ".get_class($result), E_USER_WARNING);
478
                }
479
            }
480
        }
481
482
        // No URL rules matched, so return a 404 error.
483
        return new HTTPResponse('No URL rule was matched', 404);
484
    }
485
486
    /**
487
     * Set url parameters (should only be called internally by RequestHandler->handleRequest()).
488
     *
489
     * @param array $params
490
     */
491
    public static function setUrlParams($params)
492
    {
493
        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...
494
    }
495
496
    /**
497
     * Return the {@link SiteTree} object that is currently being viewed. If there is no SiteTree
498
     * object to return, then this will return the current controller.
499
     *
500
     * @return SiteTree|Controller
501
     */
502
    public static function get_current_page()
503
    {
504
        return self::$current_page ? self::$current_page : Controller::curr();
505
    }
506
507
    /**
508
     * Set the currently active {@link SiteTree} object that is being used to respond to the request.
509
     *
510
     * @param SiteTree $page
511
     */
512
    public static function set_current_page($page)
513
    {
514
        self::$current_page = $page;
515
    }
516
517
    /**
518
     * Turns the given URL into an absolute URL. By default non-site root relative urls will be
519
     * evaluated relative to the current base_url.
520
     *
521
     * @param string $url URL To transform to absolute.
522
     * @param string $relativeParent Method to use for evaluating relative urls.
523
     * Either one of BASE (baseurl), ROOT (site root), or REQUEST (requested page).
524
     * Defaults to BASE, which is the same behaviour as template url resolution.
525
     * Ignored if the url is absolute or site root.
526
     *
527
     * @return string
528
     */
529
    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...
530
    {
531
        if (is_bool($relativeParent)) {
532
            // Deprecate old boolean second parameter
533
            Deprecation::notice('5.0', 'Director::absoluteURL takes an explicit parent for relative url');
534
            $relativeParent = $relativeParent ? self::BASE : self::REQUEST;
535
        }
536
537
        // Check if there is already a protocol given
538
        if (preg_match('/^http(s?):\/\//', $url)) {
539
            return $url;
540
        }
541
542
        // Absolute urls without protocol are added
543
        // E.g. //google.com -> http://google.com
544
        if (strpos($url, '//') === 0) {
545
            return self::protocol() . substr($url, 2);
546
        }
547
548
        // Determine method for mapping the parent to this relative url
549
        if ($relativeParent === self::ROOT || self::is_root_relative_url($url)) {
550
            // Root relative urls always should be evaluated relative to the root
551
            $parent = self::protocolAndHost();
552
        } elseif ($relativeParent === self::REQUEST) {
553
            // Request relative urls rely on the REQUEST_URI param (old default behaviour)
554
            if (!isset($_SERVER['REQUEST_URI'])) {
555
                return false;
556
            }
557
            $parent = dirname($_SERVER['REQUEST_URI'] . 'x');
558
        } else {
559
            // Default to respecting site base_url
560
            $parent = self::absoluteBaseURL();
561
        }
562
563
        // Map empty urls to relative slash and join to base
564
        if (empty($url) || $url === '.' || $url === './') {
565
            $url = '/';
566
        }
567
        return Controller::join_links($parent, $url);
568
    }
569
570
    /**
571
     * A helper to determine the current hostname used to access the site.
572
     * The following are used to determine the host (in order)
573
     *  - Director.alternate_host
574
     *  - Director.alternate_base_url (if it contains a domain name)
575
     *  - Trusted proxy headers
576
     *  - HTTP Host header
577
     *  - SS_HOST env var
578
     *  - SERVER_NAME
579
     *  - gethostname()
580
     *
581
     * @return string
582
     */
583
    public static function host()
0 ignored issues
show
Coding Style introduced by
host 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...
584
    {
585
        $headerOverride = false;
586
        if (TRUSTED_PROXY) {
587
            $headers = (getenv('SS_TRUSTED_PROXY_HOST_HEADER')) ? array(getenv('SS_TRUSTED_PROXY_HOST_HEADER')) : null;
588
            if (!$headers) {
589
                // Backwards compatible defaults
590
                $headers = array('HTTP_X_FORWARDED_HOST');
591
            }
592
            foreach ($headers as $header) {
593
                if (!empty($_SERVER[$header])) {
594
                    // Get the first host, in case there's multiple separated through commas
595
                    $headerOverride = strtok($_SERVER[$header], ',');
596
                    break;
597
                }
598
            }
599
        }
600
601
        if ($host = static::config()->get('alternate_host')) {
602
            return $host;
603
        }
604
605
        if ($baseURL = static::config()->get('alternate_base_url')) {
606
            if (preg_match('/^(http[^:]*:\/\/[^\/]+)(\/|$)/', $baseURL, $matches)) {
607
                return parse_url($baseURL, PHP_URL_HOST);
608
            }
609
        }
610
611
        if ($headerOverride) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $headerOverride 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...
612
            return $headerOverride;
613
        }
614
615
        if (isset($_SERVER['HTTP_HOST'])) {
616
            return $_SERVER['HTTP_HOST'];
617
        }
618
619
        if ($host = getenv('SS_HOST')) {
620
            return $host;
621
        }
622
623
        return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : gethostname();
624
    }
625
626
    /**
627
     * Returns the domain part of the URL 'http://www.mysite.com'. Returns FALSE is this environment
628
     * variable isn't set.
629
     *
630
     * @return bool|string
631
     */
632
    public static function protocolAndHost()
633
    {
634
        return static::protocol() . static::host();
635
    }
636
637
    /**
638
     * Return the current protocol that the site is running under.
639
     *
640
     * @return string
641
     */
642
    public static function protocol()
643
    {
644
        return (self::is_https()) ? 'https://' : 'http://';
645
    }
646
647
    /**
648
     * Return whether the site is running as under HTTPS.
649
     *
650
     * @return bool
651
     */
652
    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...
653
    {
654
        // See https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
655
        // See https://support.microsoft.com/en-us/kb/307347
656
        $headerOverride = false;
657
        if (TRUSTED_PROXY) {
658
            $headers = (getenv('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) ? array(getenv('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) : null;
659
            if (!$headers) {
660
                // Backwards compatible defaults
661
                $headers = array('HTTP_X_FORWARDED_PROTO', 'HTTP_X_FORWARDED_PROTOCOL', 'HTTP_FRONT_END_HTTPS');
662
            }
663
            foreach ($headers as $header) {
664
                $headerCompareVal = ($header === 'HTTP_FRONT_END_HTTPS' ? 'on' : 'https');
665
                if (!empty($_SERVER[$header]) && strtolower($_SERVER[$header]) == $headerCompareVal) {
666
                    $headerOverride = true;
667
                    break;
668
                }
669
            }
670
        }
671
672
        if ($protocol = Config::inst()->get('SilverStripe\\Control\\Director', 'alternate_protocol')) {
673
            return ($protocol == 'https');
674
        } elseif ($headerOverride) {
675
            return true;
676
        } elseif ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) {
677
            return true;
678
        } elseif (isset($_SERVER['SSL'])) {
679
            return true;
680
        } else {
681
            return false;
682
        }
683
    }
684
685
    /**
686
     * Returns the root URL for the site. It will be automatically calculated unless it is overridden
687
     * with {@link setBaseURL()}.
688
     *
689
     * @return string
690
     */
691
    public static function baseURL()
692
    {
693
        $alternate = Config::inst()->get('SilverStripe\\Control\\Director', 'alternate_base_url');
694
695
        if ($alternate) {
696
            return $alternate;
697
        } else {
698
            $base = BASE_URL;
699
700
            if ($base == '/' || $base == '/.' || $base == '\\') {
701
                $baseURL = '/';
702
            } else {
703
                $baseURL = $base . '/';
704
            }
705
706
            if (defined('BASE_SCRIPT_URL')) {
707
                return $baseURL . BASE_SCRIPT_URL;
708
            }
709
710
            return $baseURL;
711
        }
712
    }
713
714
    /**
715
     * Sets the root URL for the website. If the site isn't accessible from the URL you provide,
716
     * weird things will happen.
717
     *
718
     * @deprecated 4.0 Use the "Director.alternate_base_url" config setting instead.
719
     *
720
     * @param string $baseURL
721
     */
722
    public static function setBaseURL($baseURL)
723
    {
724
        Deprecation::notice('4.0', 'Use the "Director.alternate_base_url" config setting instead');
725
        Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', $baseURL);
726
    }
727
728
    /**
729
     * Returns the root filesystem folder for the site. It will be automatically calculated unless
730
     * it is overridden with {@link setBaseFolder()}.
731
     *
732
     * @return string
733
     */
734
    public static function baseFolder()
735
    {
736
        $alternate = Config::inst()->get('SilverStripe\\Control\\Director', 'alternate_base_folder');
737
        return ($alternate) ? $alternate : BASE_PATH;
738
    }
739
740
    /**
741
     * Sets the root folder for the website. If the site isn't accessible from the folder you provide,
742
     * weird things will happen.
743
     *
744
     * @deprecated 4.0 Use the "Director.alternate_base_folder" config setting instead.
745
     *
746
     * @param string $baseFolder
747
     */
748
    public static function setBaseFolder($baseFolder)
749
    {
750
        Deprecation::notice('4.0', 'Use the "Director.alternate_base_folder" config setting instead');
751
        Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_folder', $baseFolder);
752
    }
753
754
    /**
755
     * Turns an absolute URL or folder into one that's relative to the root of the site. This is useful
756
     * when turning a URL into a filesystem reference, or vice versa.
757
     *
758
     * @param string $url Accepts both a URL or a filesystem path.
759
     *
760
     * @return string
761
     */
762
    public static function makeRelative($url)
763
    {
764
        // Allow for the accidental inclusion whitespace and // in the URL
765
        $url = trim(preg_replace('#([^:])//#', '\\1/', $url));
766
767
        $base1 = self::absoluteBaseURL();
768
        $baseDomain = substr($base1, strlen(self::protocol()));
769
770
        // Only bother comparing the URL to the absolute version if $url looks like a URL.
771
        if (preg_match('/^https?[^:]*:\/\//', $url, $matches)) {
772
            $urlProtocol = $matches[0];
773
            $urlWithoutProtocol = substr($url, strlen($urlProtocol));
774
775
            // If we are already looking at baseURL, return '' (substr will return false)
776
            if ($url == $base1) {
777
                return '';
778
            } elseif (substr($url, 0, strlen($base1)) == $base1) {
779
                return substr($url, strlen($base1));
780
            } elseif (substr($base1, -1) == "/" && $url == substr($base1, 0, -1)) {
781
                // Convert http://www.mydomain.com/mysitedir to ''
782
                return "";
783
            }
784
785
            if (substr($urlWithoutProtocol, 0, strlen($baseDomain)) == $baseDomain) {
786
                return substr($urlWithoutProtocol, strlen($baseDomain));
787
            }
788
        }
789
790
        // test for base folder, e.g. /var/www
791
        $base2 = self::baseFolder();
792
        if (substr($url, 0, strlen($base2)) == $base2) {
793
            return substr($url, strlen($base2));
794
        }
795
796
        // Test for relative base url, e.g. mywebsite/ if the full URL is http://localhost/mywebsite/
797
        $base3 = self::baseURL();
798
        if (substr($url, 0, strlen($base3)) == $base3) {
799
            return substr($url, strlen($base3));
800
        }
801
802
        // Test for relative base url, e.g mywebsite/ if the full url is localhost/myswebsite
803
        if (substr($url, 0, strlen($baseDomain)) == $baseDomain) {
804
            return substr($url, strlen($baseDomain));
805
        }
806
807
        // Nothing matched, fall back to returning the original URL
808
        return $url;
809
    }
810
811
    /**
812
     * Returns true if a given path is absolute. Works under both *nix and windows systems.
813
     *
814
     * @param string $path
815
     *
816
     * @return bool
817
     */
818
    public static function is_absolute($path)
819
    {
820
        if (empty($path)) {
821
            return false;
822
        }
823
        if ($path[0] == '/' || $path[0] == '\\') {
824
            return true;
825
        }
826
        return preg_match('/^[a-zA-Z]:[\\\\\/]/', $path) == 1;
827
    }
828
829
    /**
830
     * Determine if the url is root relative (i.e. starts with /, but not with //) SilverStripe
831
     * considers root relative urls as a subset of relative urls.
832
     *
833
     * @param string $url
834
     *
835
     * @return bool
836
     */
837
    public static function is_root_relative_url($url)
838
    {
839
        return strpos($url, '/') === 0 && strpos($url, '//') !== 0;
840
    }
841
842
    /**
843
     * Checks if a given URL is absolute (e.g. starts with 'http://' etc.). URLs beginning with "//"
844
     * are treated as absolute, as browsers take this to mean the same protocol as currently being used.
845
     *
846
     * Useful to check before redirecting based on a URL from user submissions through $_GET or $_POST,
847
     * and avoid phishing attacks by redirecting to an attackers server.
848
     *
849
     * Note: Can't solely rely on PHP's parse_url() , since it is not intended to work with relative URLs
850
     * or for security purposes. filter_var($url, FILTER_VALIDATE_URL) has similar problems.
851
     *
852
     * @param string $url
853
     *
854
     * @return bool
855
     */
856
    public static function is_absolute_url($url)
857
    {
858
        // Strip off the query and fragment parts of the URL before checking
859
        if (($queryPosition = strpos($url, '?')) !== false) {
860
            $url = substr($url, 0, $queryPosition-1);
861
        }
862
        if (($hashPosition = strpos($url, '#')) !== false) {
863
            $url = substr($url, 0, $hashPosition-1);
864
        }
865
        $colonPosition = strpos($url, ':');
866
        $slashPosition = strpos($url, '/');
867
        return (
868
            // Base check for existence of a host on a compliant URL
869
            parse_url($url, PHP_URL_HOST)
870
            // Check for more than one leading slash without a protocol.
871
            // While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers,
872
            // and hence a potential security risk. Single leading slashes are not an issue though.
873
            || preg_match('%^\s*/{2,}%', $url)
874
            || (
875
                // If a colon is found, check if it's part of a valid scheme definition
876
                // (meaning its not preceded by a slash).
877
                $colonPosition !== false
878
                && ($slashPosition === false || $colonPosition < $slashPosition)
879
            )
880
        );
881
    }
882
883
    /**
884
     * Checks if a given URL is relative (or root relative) by checking {@link is_absolute_url()}.
885
     *
886
     * @param string $url
887
     *
888
     * @return bool
889
     */
890
    public static function is_relative_url($url)
891
    {
892
        return !static::is_absolute_url($url);
893
    }
894
895
    /**
896
     * Checks if the given URL is belonging to this "site" (not an external link). That's the case if
897
     * the URL is relative, as defined by {@link is_relative_url()}, or if the host matches
898
     * {@link protocolAndHost()}.
899
     *
900
     * Useful to check before redirecting based on a URL from user submissions through $_GET or $_POST,
901
     * and avoid phishing attacks by redirecting to an attackers server.
902
     *
903
     * @param string $url
904
     *
905
     * @return bool
906
     */
907
    public static function is_site_url($url)
908
    {
909
        $urlHost = parse_url($url, PHP_URL_HOST);
910
        $actualHost = parse_url(self::protocolAndHost(), PHP_URL_HOST);
911
        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...
912
            return true;
913
        } else {
914
            return self::is_relative_url($url);
915
        }
916
    }
917
918
    /**
919
     * Takes a $_SERVER data array and extracts HTTP request headers.
920
     *
921
     * @param array $server
922
     *
923
     * @return array
924
     */
925
    public static function extract_request_headers(array $server)
926
    {
927
        $headers = array();
928
929
        foreach ($server as $key => $value) {
930
            if (substr($key, 0, 5) == 'HTTP_') {
931
                $key = substr($key, 5);
932
                $key = strtolower(str_replace('_', ' ', $key));
933
                $key = str_replace(' ', '-', ucwords($key));
934
                $headers[$key] = $value;
935
            }
936
        }
937
938
        if (isset($server['CONTENT_TYPE'])) {
939
            $headers['Content-Type'] = $server['CONTENT_TYPE'];
940
        }
941
        if (isset($server['CONTENT_LENGTH'])) {
942
            $headers['Content-Length'] = $server['CONTENT_LENGTH'];
943
        }
944
945
        return $headers;
946
    }
947
948
    /**
949
     * Given a filesystem reference relative to the site root, return the full file-system path.
950
     *
951
     * @param string $file
952
     *
953
     * @return string
954
     */
955
    public static function getAbsFile($file)
956
    {
957
        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...
958
    }
959
960
    /**
961
     * Returns true if the given file exists. Filename should be relative to the site root.
962
     *
963
     * @param $file
964
     *
965
     * @return bool
966
     */
967
    public static function fileExists($file)
968
    {
969
        // replace any appended query-strings, e.g. /path/to/foo.php?bar=1 to /path/to/foo.php
970
        $file = preg_replace('/([^\?]*)?.*/', '$1', $file);
971
        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...
972
    }
973
974
    /**
975
     * Returns the Absolute URL of the site root.
976
     *
977
     * @return string
978
     */
979
    public static function absoluteBaseURL()
980
    {
981
        return self::absoluteURL(
982
            self::baseURL(),
983
            self::ROOT
984
        );
985
    }
986
987
    /**
988
     * Returns the Absolute URL of the site root, embedding the current basic-auth credentials into
989
     * the URL.
990
     *
991
     * @return string
992
     */
993
    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...
994
    {
995
        $login = "";
996
997
        if (isset($_SERVER['PHP_AUTH_USER'])) {
998
            $login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@";
999
        }
1000
1001
        return Director::protocol() . $login .  static::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...
1002
    }
1003
1004
    /**
1005
     * Skip any further processing and immediately respond with a redirect to the passed URL.
1006
     *
1007
     * @param string $destURL
1008
     */
1009
    protected static function force_redirect($destURL)
1010
    {
1011
        $response = new HTTPResponse();
1012
        $response->redirect($destURL, 301);
1013
1014
        HTTP::add_cache_headers($response);
1015
1016
        // TODO: Use an exception - ATM we can be called from _config.php, before Director#handleRequest's try block
1017
        $response->output();
1018
        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...
1019
    }
1020
1021
    /**
1022
     * Force the site to run on SSL.
1023
     *
1024
     * To use, call from _config.php. For example:
1025
     * <code>
1026
     * if (Director::isLive()) Director::forceSSL();
1027
     * </code>
1028
     *
1029
     * If you don't want your entire site to be on SSL, you can pass an array of PCRE regular expression
1030
     * patterns for matching relative URLs. For example:
1031
     * <code>
1032
     * if (Director::isLive()) Director::forceSSL(array('/^admin/', '/^Security/'));
1033
     * </code>
1034
     *
1035
     * If you want certain parts of your site protected under a different domain, you can specify
1036
     * the domain as an argument:
1037
     * <code>
1038
     * if (Director::isLive()) Director::forceSSL(array('/^admin/', '/^Security/'), 'secure.mysite.com');
1039
     * </code>
1040
     *
1041
     * Note that the session data will be lost when moving from HTTP to HTTPS. It is your responsibility
1042
     * to ensure that this won't cause usability problems.
1043
     *
1044
     * CAUTION: This does not respect the site environment mode. You should check this
1045
     * as per the above examples using Director::isLive() or Director::isTest() for example.
1046
     *
1047
     * @param array $patterns Array of regex patterns to match URLs that should be HTTPS.
1048
     * @param string $secureDomain Secure domain to redirect to. Defaults to the current domain.
1049
     *
1050
     * @return bool|string String of URL when unit tests running, boolean FALSE if patterns don't match request URI.
1051
     */
1052
    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...
1053
    {
1054
        // Calling from the command-line?
1055
        if (!isset($_SERVER['REQUEST_URI'])) {
1056
            return false;
1057
        }
1058
1059
        $matched = false;
1060
1061
        if ($patterns) {
1062
            $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...
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...
1063
1064
            // protect portions of the site based on the pattern
1065
            foreach ($patterns as $pattern) {
1066
                if (preg_match($pattern, $relativeURL)) {
1067
                    $matched = true;
1068
                    break;
1069
                }
1070
            }
1071
        } else {
1072
            // protect the entire site
1073
            $matched = true;
1074
        }
1075
1076
        if ($matched && !self::is_https()) {
1077
            // if an domain is specified, redirect to that instead of the current domain
1078
            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...
1079
                $url = 'https://' . $secureDomain . $_SERVER['REQUEST_URI'];
1080
            } else {
1081
                $url = $_SERVER['REQUEST_URI'];
1082
            }
1083
1084
            $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...
1085
1086
            // This coupling to SapphireTest is necessary to test the destination URL and to not interfere with tests
1087
            if (class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test()) {
1088
                return $destURL;
1089
            } else {
1090
                self::force_redirect($destURL);
1091
                return true;
1092
            }
1093
        } else {
1094
            return false;
1095
        }
1096
    }
1097
1098
    /**
1099
     * Force a redirect to a domain starting with "www."
1100
     */
1101
    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...
1102
    {
1103
        if (!Director::isDev() && !Director::isTest() && strpos(static::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...
1104
            $destURL = str_replace(
1105
                Director::protocol(),
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...
1106
                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...
1107
                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...
1108
            );
1109
1110
            self::force_redirect($destURL);
1111
        }
1112
    }
1113
1114
    /**
1115
     * Checks if the current HTTP-Request is an "Ajax-Request" by checking for a custom header set by
1116
     * jQuery or whether a manually set request-parameter 'ajax' is present.
1117
     *
1118
     * @return bool
1119
     */
1120
    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...
1121
    {
1122
        if (Controller::has_curr()) {
1123
            return Controller::curr()->getRequest()->isAjax();
1124
        } else {
1125
            return (
1126
                isset($_REQUEST['ajax']) ||
1127
                (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == "XMLHttpRequest")
1128
            );
1129
        }
1130
    }
1131
1132
    /**
1133
     * Returns true if this script is being run from the command line rather than the web server.
1134
     *
1135
     * @return bool
1136
     */
1137
    public static function is_cli()
1138
    {
1139
        return (php_sapi_name() == "cli");
1140
    }
1141
1142
    /**
1143
     * Set the environment type of the current site.
1144
     *
1145
     * Typically, a SilverStripe site have a number of environments:
1146
     *  - Development environments, such a copy on your local machine.
1147
     *  - Test sites, such as the one you show the client before going live.
1148
     *  - The live site itself.
1149
     *
1150
     * The behaviour of these environments often varies slightly.  For example, development sites may
1151
     * have errors dumped to the screen, and order confirmation emails might be sent to the developer
1152
     * instead of the client.
1153
     *
1154
     * To help with this, SilverStripe supports the notion of an environment type.  The environment
1155
     * type can be dev, test, or live.
1156
     *
1157
     * You can set it explicitly with {@link Director::set_environment_type()}. Or you can use
1158
     * {@link Director::$dev_servers} and {@link Director::$test_servers} to set it implicitly, based
1159
     * on the value of $_SERVER['HTTP_HOST'].  If the HTTP_HOST value is one of the servers listed,
1160
     * then the environment type will be test or dev.  Otherwise, the environment type will be live.
1161
     *
1162
     * Dev mode can also be forced by putting ?isDev=1 in your URL, which will ask you to log in and
1163
     * then push the site into dev mode for the remainder of the session. Putting ?isDev=0 onto the URL
1164
     * can turn it back.
1165
     *
1166
     * Test mode can also be forced by putting ?isTest=1 in your URL, which will ask you to log in and
1167
     * then push the site into test mode for the remainder of the session. Putting ?isTest=0 onto the URL
1168
     * can turn it back.
1169
     *
1170
     * Generally speaking, these methods will be called from your _config.php file.
1171
     *
1172
     * Once the environment type is set, it can be checked with {@link Director::isDev()},
1173
     * {@link Director::isTest()}, and {@link Director::isLive()}.
1174
     *
1175
     * @deprecated 4.0 Use the "Director.environment_type" config setting instead
1176
     *
1177
     * @param $et string
1178
     */
1179
    public static function set_environment_type($et)
1180
    {
1181
        if ($et != 'dev' && $et != 'test' && $et != 'live') {
1182
            user_error(
1183
                "Director::set_environment_type passed '$et'.  It should be passed dev, test, or live",
1184
                E_USER_WARNING
1185
            );
1186
        } else {
1187
            Deprecation::notice('4.0', 'Use the "Director.environment_type" config setting instead');
1188
            Config::inst()->update('SilverStripe\\Control\\Director', 'environment_type', $et);
1189
        }
1190
    }
1191
1192
    /**
1193
     * Can also be checked with {@link Director::isDev()}, {@link Director::isTest()}, and
1194
     * {@link Director::isLive()}.
1195
     *
1196
     * @return bool|string
1197
     */
1198
    public static function get_environment_type()
1199
    {
1200
        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...
1201
            return 'live';
1202
        } 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...
1203
            return 'test';
1204
        } 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...
1205
            return 'dev';
1206
        } else {
1207
            return false;
1208
        }
1209
    }
1210
1211
    /**
1212
     * This function will return true if the site is in a live environment. For information about
1213
     * environment types, see {@link Director::set_environment_type()}.
1214
     *
1215
     * @return bool
1216
     */
1217
    public static function isLive()
1218
    {
1219
        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...
1220
    }
1221
1222
    /**
1223
     * This function will return true if the site is in a development environment. For information about
1224
     * environment types, see {@link Director::set_environment_type()}.
1225
     *
1226
     * @return bool
1227
     */
1228
    public static function isDev()
1229
    {
1230
        // Check session
1231
        if ($env = self::session_environment()) {
1232
            return $env === 'dev';
1233
        }
1234
1235
        // Check config
1236
        if (Config::inst()->get('SilverStripe\\Control\\Director', 'environment_type') === 'dev') {
1237
            return true;
1238
        }
1239
1240
        // Check if we are running on one of the test servers
1241
        $devServers = (array)Config::inst()->get('SilverStripe\\Control\\Director', 'dev_servers');
1242
        return in_array(static::host(), $devServers);
1243
    }
1244
1245
    /**
1246
     * This function will return true if the site is in a test environment. For information about
1247
     * environment types, see {@link Director::set_environment_type()}.
1248
     *
1249
     * @return bool
1250
     */
1251
    public static function isTest()
1252
    {
1253
        // In case of isDev and isTest both being set, dev has higher priority
1254
        if (self::isDev()) {
1255
            return false;
1256
        }
1257
1258
        // Check saved session
1259
        if ($env = self::session_environment()) {
1260
            return $env === 'test';
1261
        }
1262
1263
        // Check config
1264
        if (Config::inst()->get('SilverStripe\\Control\\Director', 'environment_type') === 'test') {
1265
            return true;
1266
        }
1267
1268
        // Check if we are running on one of the test servers
1269
        $testServers = (array)Config::inst()->get('SilverStripe\\Control\\Director', 'test_servers');
1270
        return in_array(static::host(), $testServers);
1271
    }
1272
1273
    /**
1274
     * Check or update any temporary environment specified in the session.
1275
     *
1276
     * @return null|string
1277
     */
1278
    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...
1279
    {
1280
        // Set session from querystring
1281
        if (isset($_GET['isDev'])) {
1282
            if (isset($_SESSION)) {
1283
                unset($_SESSION['isTest']); // In case we are changing from test mode
1284
                $_SESSION['isDev'] = $_GET['isDev'];
1285
            }
1286
            return 'dev';
1287
        } elseif (isset($_GET['isTest'])) {
1288
            if (isset($_SESSION)) {
1289
                unset($_SESSION['isDev']); // In case we are changing from dev mode
1290
                $_SESSION['isTest'] = $_GET['isTest'];
1291
            }
1292
            return 'test';
1293
        }
1294
        // Check session
1295
        if (isset($_SESSION['isDev']) && $_SESSION['isDev']) {
1296
            return 'dev';
1297
        } elseif (isset($_SESSION['isTest']) && $_SESSION['isTest']) {
1298
            return 'test';
1299
        } else {
1300
            return null;
1301
        }
1302
    }
1303
1304
    /**
1305
     * Returns an array of strings of the method names of methods on the call that should be exposed
1306
     * as global variables in the templates.
1307
     *
1308
     * @return array
1309
     */
1310
    public static function get_template_global_variables()
1311
    {
1312
        return array(
1313
            'absoluteBaseURL',
1314
            'baseURL',
1315
            'is_ajax',
1316
            'isAjax' => 'is_ajax',
1317
            'BaseHref' => 'absoluteBaseURL',    //@deprecated 3.0
1318
        );
1319
    }
1320
}
1321