Completed
Push — master ( fdd9ad...2548bf )
by Sam
11:48
created

Director::baseURL()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 0
dl 0
loc 20
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control;
4
5
use InvalidArgumentException;
6
use SilverStripe\CMS\Model\SiteTree;
7
use SilverStripe\Core\Config\Config;
8
use SilverStripe\Core\Config\Configurable;
9
use SilverStripe\Core\Injector\Injector;
10
use SilverStripe\Dev\Deprecation;
11
use SilverStripe\Dev\SapphireTest;
12
use SilverStripe\ORM\ArrayLib;
13
use SilverStripe\ORM\DataModel;
14
use SilverStripe\Versioned\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
     * @config
59
     * @var array
60
     */
61
    private static $rules = array();
62
63
    /**
64
     * Set current page
65
     *
66
     * @internal
67
     * @var SiteTree
68
     */
69
    private static $current_page;
70
71
    /**
72
     * @config
73
     * @var string
74
     */
75
    private static $alternate_base_folder;
76
77
    /**
78
     * Force the base_url to a specific value.
79
     * If assigned, default_base_url and the value in the $_SERVER
80
     * global is ignored.
81
     * Supports back-ticked vars; E.g. '`SS_BASE_URL`'
82
     *
83
     * @config
84
     * @var string
85
     */
86
    private static $alternate_base_url;
87
88
    /**
89
     * Base url to populate if cannot be determined otherwise.
90
     * Supports back-ticked vars; E.g. '`SS_BASE_URL`'
91
     *
92
     * @config
93
     * @var string
94
     */
95
    private static $default_base_url = '`SS_BASE_URL`';
96
97
    /**
98
     * Assigned environment type
99
     *
100
     * @internal
101
     * @var string
102
     */
103
    protected static $environment_type;
104
105
    /**
106
     * Process the given URL, creating the appropriate controller and executing it.
107
     *
108
     * Request processing is handled as follows:
109
     * - Director::direct() creates a new HTTPResponse object and passes this to
110
     *   Director::handleRequest().
111
     * - Director::handleRequest($request) checks each of the Director rules and identifies a controller
112
     *   to handle this request.
113
     * - Controller::handleRequest($request) is then called.  This will find a rule to handle the URL,
114
     *   and call the rule handling method.
115
     * - RequestHandler::handleRequest($request) is recursively called whenever a rule handling method
116
     *   returns a RequestHandler object.
117
     *
118
     * In addition to request processing, Director will manage the session, and perform the output of
119
     * the actual response to the browser.
120
     *
121
     * @uses handleRequest() rule-lookup logic is handled by this.
122
     * @uses TestController::handleRequest() This handles the page logic for a Director::direct() call.
123
     * @param string $url
124
     * @param DataModel $model
125
     * @throws HTTPResponse_Exception
126
     */
127
    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...
128
    {
129
        // check allowed hosts
130
        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...
131
            $all_allowed_hosts = explode(',', getenv('SS_ALLOWED_HOSTS'));
132
            if (!in_array(static::host(), $all_allowed_hosts)) {
133
                throw new HTTPResponse_Exception('Invalid Host', 400);
134
            }
135
        }
136
137
138
        // Validate $_FILES array before merging it with $_POST
139
        foreach ($_FILES as $k => $v) {
140
            if (is_array($v['tmp_name'])) {
141
                $v = ArrayLib::array_values_recursive($v['tmp_name']);
142
                foreach ($v as $tmpFile) {
143
                    if ($tmpFile && !is_uploaded_file($tmpFile)) {
144
                        user_error("File upload '$k' doesn't appear to be a valid upload", E_USER_ERROR);
145
                    }
146
                }
147
            } else {
148
                if ($v['tmp_name'] && !is_uploaded_file($v['tmp_name'])) {
149
                    user_error("File upload '$k' doesn't appear to be a valid upload", E_USER_ERROR);
150
                }
151
            }
152
        }
153
154
        $req = new HTTPRequest(
155
            (isset($_SERVER['X-HTTP-Method-Override']))
156
                ? $_SERVER['X-HTTP-Method-Override']
157
                : $_SERVER['REQUEST_METHOD'],
158
            $url,
159
            $_GET,
160
            ArrayLib::array_merge_recursive((array) $_POST, (array) $_FILES),
161
            @file_get_contents('php://input')
162
        );
163
164
        $headers = self::extract_request_headers($_SERVER);
165
        foreach ($headers as $header => $value) {
166
            $req->addHeader($header, $value);
167
        }
168
169
        // Initiate an empty session - doesn't initialize an actual PHP session until saved (see below)
170
        $session = Session::create(isset($_SESSION) ? $_SESSION : array());
171
172
        // Only resume a session if its not started already, and a session identifier exists
173
        if (!isset($_SESSION) && Session::request_contains_session_id()) {
174
            $session->inst_start();
175
        }
176
177
        $output = RequestProcessor::singleton()->preRequest($req, $session, $model);
178
179
        if ($output === false) {
180
            // @TODO Need to NOT proceed with the request in an elegant manner
181
            throw new HTTPResponse_Exception(_t('Director.INVALID_REQUEST', 'Invalid request'), 400);
182
        }
183
184
        $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...
185
186
        // Save session data. Note that inst_save() will start/resume the session if required.
187
        $session->inst_save();
188
189
        // Return code for a redirection request
190
        if (is_string($result) && substr($result, 0, 9) == 'redirect:') {
191
            $url = substr($result, 9);
192
193
            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...
194
                // on cli, follow SilverStripe redirects automatically
195
                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...
196
                    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...
197
                    DataModel::inst()
198
                );
199
                return;
200
            } else {
201
                $response = new HTTPResponse();
202
                $response->redirect($url);
203
                $res = RequestProcessor::singleton()->postRequest($req, $response, $model);
204
205
                if ($res !== false) {
206
                    $response->output();
207
                }
208
            }
209
            // Handle a controller
210
        } elseif ($result) {
211
            if ($result instanceof HTTPResponse) {
212
                $response = $result;
213
            } else {
214
                $response = new HTTPResponse();
215
                $response->setBody($result);
216
            }
217
218
            $res = RequestProcessor::singleton()->postRequest($req, $response, $model);
219
            if ($res !== false) {
220
                $response->output();
221
            } else {
222
                // @TODO Proper response here.
223
                throw new HTTPResponse_Exception("Invalid response");
224
            }
225
226
227
            //$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...
228
        }
229
    }
230
231
    /**
232
     * Test a URL request, returning a response object. This method is the counterpart of
233
     * Director::direct() that is used in functional testing. It will execute the URL given, and
234
     * return the result as an HTTPResponse object.
235
     *
236
     * @uses TestController::handleRequest() Handles the page logic for a Director::direct() call.
237
     *
238
     * @param string $url The URL to visit.
239
     * @param array $postVars The $_POST & $_FILES variables.
240
     * @param array|Session $session The {@link Session} object representing the current session.
241
     * By passing the same object to multiple  calls of Director::test(), you can simulate a persisted
242
     * session.
243
     * @param string $httpMethod The HTTP method, such as GET or POST.  It will default to POST if
244
     * postVars is set, GET otherwise. Overwritten by $postVars['_method'] if present.
245
     * @param string $body The HTTP body.
246
     * @param array $headers HTTP headers with key-value pairs.
247
     * @param array|Cookie_Backend $cookies to populate $_COOKIE.
248
     * @param HTTPRequest $request The {@see SS_HTTP_Request} object generated as a part of this request.
249
     *
250
     * @return HTTPResponse
251
     *
252
     * @throws HTTPResponse_Exception
253
     */
254
    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...
255
        $url,
256
        $postVars = null,
257
        $session = array(),
258
        $httpMethod = null,
259
        $body = null,
260
        $headers = array(),
261
        $cookies = array(),
262
        &$request = null
263
    ) {
264
265
        Config::nest();
266
        Injector::nest();
267
268
        // These are needed so that calling Director::test() does not muck with whoever is calling it.
269
        // Really, it's some inappropriate coupling and should be resolved by making less use of statics.
270
        $oldReadingMode = null;
271
        if (class_exists(Versioned::class)) {
272
            $oldReadingMode = Versioned::get_reading_mode();
273
        }
274
        $getVars = array();
275
276
        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...
277
            $httpMethod = ($postVars || is_array($postVars)) ? "POST" : "GET";
278
        }
279
280
        if (!$session) {
281
            $session = Session::create([]);
282
        }
283
        $cookieJar = $cookies instanceof Cookie_Backend
284
            ? $cookies
285
            : Injector::inst()->createWithArgs(Cookie_Backend::class, array($cookies ?: []));
286
287
        // Back up the current values of the superglobals
288
        $existingRequestVars = isset($_REQUEST) ? $_REQUEST : array();
289
        $existingGetVars = isset($_GET) ? $_GET : array();
290
        $existingPostVars = isset($_POST) ? $_POST : array();
291
        $existingSessionVars = isset($_SESSION) ? $_SESSION : array();
292
        $existingCookies = isset($_COOKIE) ? $_COOKIE : array();
293
        $existingServer = isset($_SERVER) ? $_SERVER : array();
294
295
        $existingRequirementsBackend = Requirements::backend();
296
297
        Cookie::config()->update('report_errors', false);
298
        Requirements::set_backend(Requirements_Backend::create());
299
300
        if (strpos($url, '#') !== false) {
301
            $url = substr($url, 0, strpos($url, '#'));
302
        }
303
304
        // Handle absolute URLs
305
        if (parse_url($url, PHP_URL_HOST)) {
306
            $bits = parse_url($url);
307
            // If a port is mentioned in the absolute URL, be sure to add that into the HTTP host
308
            if (isset($bits['port'])) {
309
                $_SERVER['HTTP_HOST'] = $bits['host'].':'.$bits['port'];
310
            } else {
311
                $_SERVER['HTTP_HOST'] = $bits['host'];
312
            }
313
        }
314
315
        // Ensure URL is properly made relative.
316
        // Example: url passed is "/ss31/my-page" (prefixed with BASE_URL), this should be changed to "my-page"
317
        $url = self::makeRelative($url);
318
319
        $urlWithQuerystring = $url;
320
        if (strpos($url, '?') !== false) {
321
            list($url, $getVarsEncoded) = explode('?', $url, 2);
322
            parse_str($getVarsEncoded, $getVars);
323
        }
324
325
        // Replace the super globals with appropriate test values
326
        $_REQUEST = ArrayLib::array_merge_recursive((array) $getVars, (array) $postVars);
327
        $_GET = (array) $getVars;
328
        $_POST = (array) $postVars;
329
        $_SESSION = $session ? $session->inst_getAll() : array();
0 ignored issues
show
Bug introduced by
It seems like $session is not always an object, but can also be of type array. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
330
        $_COOKIE = $cookieJar->getAll(false);
331
        Injector::inst()->registerService($cookieJar, Cookie_Backend::class);
332
        $_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...
333
334
        $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 256 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...
335
        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...
336
            foreach ($headers as $k => $v) {
337
                $request->addHeader($k, $v);
338
            }
339
        }
340
341
        try {
342
            // Pre-request filtering
343
            $model = DataModel::inst();
344
            $requestProcessor = Injector::inst()->get(RequestProcessor::class);
345
            $output = $requestProcessor->preRequest($request, $session, $model);
346
            if ($output === false) {
347
                throw new HTTPResponse_Exception(_t('Director.INVALID_REQUEST', 'Invalid request'), 400);
348
            }
349
350
            // Process request
351
            $result = Director::handleRequest($request, $session, $model);
0 ignored issues
show
Bug introduced by
It seems like $session defined by parameter $session on line 257 can also be of type array; however, SilverStripe\Control\Director::handleRequest() does only seem to accept object<SilverStripe\Control\Session>, 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...
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...
352
353
            // Ensure that the result is an HTTPResponse object
354
            if (is_string($result)) {
355
                if (substr($result, 0, 9) == 'redirect:') {
356
                    $response = new HTTPResponse();
357
                    $response->redirect(substr($result, 9));
358
                    $result = $response;
359
                } else {
360
                    $result = new HTTPResponse($result);
361
                }
362
            }
363
364
            $output = $requestProcessor->postRequest($request, $result, $model);
365
            if ($output === false) {
366
                throw new HTTPResponse_Exception("Invalid response");
367
            }
368
369
            // Return valid response
370
            return $result;
371
        } finally {
372
            // Restore the super globals
373
            $_REQUEST = $existingRequestVars;
374
            $_GET = $existingGetVars;
375
            $_POST = $existingPostVars;
376
            $_SESSION = $existingSessionVars;
377
            $_COOKIE = $existingCookies;
378
            $_SERVER = $existingServer;
379
380
            Requirements::set_backend($existingRequirementsBackend);
381
382
            // These are needed so that calling Director::test() does not muck with whoever is calling it.
383
            // Really, it's some inappropriate coupling and should be resolved by making less use of statics
384
            if (class_exists(Versioned::class)) {
385
                Versioned::set_reading_mode($oldReadingMode);
386
            }
387
388
            Injector::unnest(); // Restore old CookieJar, etc
389
            Config::unnest();
390
        }
391
    }
392
393
    /**
394
     * Handle an HTTP request, defined with a HTTPRequest object.
395
     *
396
     * @skipUpgrade
397
     * @param HTTPRequest $request
398
     * @param Session $session
399
     * @param DataModel $model
400
     * @return HTTPResponse|string
401
     */
402
    protected static function handleRequest(HTTPRequest $request, Session $session, DataModel $model)
403
    {
404
        $rules = Director::config()->uninherited('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...
405
406
        foreach ($rules as $pattern => $controllerOptions) {
407
            if (is_string($controllerOptions)) {
408
                if (substr($controllerOptions, 0, 2) == '->') {
409
                    $controllerOptions = array('Redirect' => substr($controllerOptions, 2));
410
                } else {
411
                    $controllerOptions = array('Controller' => $controllerOptions);
412
                }
413
            }
414
415
            if (($arguments = $request->match($pattern, true)) !== false) {
416
                $request->setRouteParams($controllerOptions);
417
                // controllerOptions provide some default arguments
418
                $arguments = array_merge($controllerOptions, $arguments);
419
420
                // Pop additional tokens from the tokenizer if necessary
421
                if (isset($controllerOptions['_PopTokeniser'])) {
422
                    $request->shift($controllerOptions['_PopTokeniser']);
423
                }
424
425
                // Handle redirection
426
                if (isset($arguments['Redirect'])) {
427
                    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...
428
                } else {
429
                    // Find the controller name
430
                    $controller = $arguments['Controller'];
431
                    $controllerObj = Injector::inst()->create($controller);
432
                    $controllerObj->setSession($session);
433
434
                    try {
435
                        $result = $controllerObj->handleRequest($request, $model);
436
                    } catch (HTTPResponse_Exception $responseException) {
437
                        $result = $responseException->getResponse();
438
                    }
439
                    if (!is_object($result) || $result instanceof HTTPResponse) {
440
                        return $result;
441
                    }
442
443
                    user_error("Bad result from url " . $request->getURL() . " handled by " .
444
                        get_class($controllerObj)." controller: ".get_class($result), E_USER_WARNING);
445
                }
446
            }
447
        }
448
449
        // No URL rules matched, so return a 404 error.
450
        return new HTTPResponse('No URL rule was matched', 404);
451
    }
452
453
    /**
454
     * Return the {@link SiteTree} object that is currently being viewed. If there is no SiteTree
455
     * object to return, then this will return the current controller.
456
     *
457
     * @return SiteTree|Controller
458
     */
459
    public static function get_current_page()
460
    {
461
        return self::$current_page ? self::$current_page : Controller::curr();
462
    }
463
464
    /**
465
     * Set the currently active {@link SiteTree} object that is being used to respond to the request.
466
     *
467
     * @param SiteTree $page
468
     */
469
    public static function set_current_page($page)
470
    {
471
        self::$current_page = $page;
472
    }
473
474
    /**
475
     * Turns the given URL into an absolute URL. By default non-site root relative urls will be
476
     * evaluated relative to the current base_url.
477
     *
478
     * @param string $url URL To transform to absolute.
479
     * @param string $relativeParent Method to use for evaluating relative urls.
480
     * Either one of BASE (baseurl), ROOT (site root), or REQUEST (requested page).
481
     * Defaults to BASE, which is the same behaviour as template url resolution.
482
     * Ignored if the url is absolute or site root.
483
     *
484
     * @return string
485
     */
486
    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...
487
    {
488
        if (is_bool($relativeParent)) {
489
            // Deprecate old boolean second parameter
490
            Deprecation::notice('5.0', 'Director::absoluteURL takes an explicit parent for relative url');
491
            $relativeParent = $relativeParent ? self::BASE : self::REQUEST;
492
        }
493
494
        // Check if there is already a protocol given
495
        if (preg_match('/^http(s?):\/\//', $url)) {
496
            return $url;
497
        }
498
499
        // Absolute urls without protocol are added
500
        // E.g. //google.com -> http://google.com
501
        if (strpos($url, '//') === 0) {
502
            return self::protocol() . substr($url, 2);
503
        }
504
505
        // Determine method for mapping the parent to this relative url
506
        if ($relativeParent === self::ROOT || self::is_root_relative_url($url)) {
507
            // Root relative urls always should be evaluated relative to the root
508
            $parent = self::protocolAndHost();
509
        } elseif ($relativeParent === self::REQUEST) {
510
            // Request relative urls rely on the REQUEST_URI param (old default behaviour)
511
            if (!isset($_SERVER['REQUEST_URI'])) {
512
                return false;
513
            }
514
            $parent = dirname($_SERVER['REQUEST_URI'] . 'x');
515
        } else {
516
            // Default to respecting site base_url
517
            $parent = self::absoluteBaseURL();
518
        }
519
520
        // Map empty urls to relative slash and join to base
521
        if (empty($url) || $url === '.' || $url === './') {
522
            $url = '/';
523
        }
524
        return Controller::join_links($parent, $url);
525
    }
526
527
    /**
528
     * A helper to determine the current hostname used to access the site.
529
     * The following are used to determine the host (in order)
530
     *  - Director.alternate_base_url (if it contains a domain name)
531
     *  - Trusted proxy headers
532
     *  - HTTP Host header
533
     *  - SS_BASE_URL env var
534
     *  - SERVER_NAME
535
     *  - gethostname()
536
     *
537
     * @return string
538
     */
539
    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...
540
    {
541
        // Check if overridden by alternate_base_url
542
        if ($baseURL = self::config()->get('alternate_base_url')) {
543
            $baseURL = Injector::inst()->convertServiceProperty($baseURL);
544
            $host = parse_url($baseURL, PHP_URL_HOST);
545
            if ($host) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $host 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...
546
                return $host;
547
            }
548
        }
549
550
        // Validate proxy-specific headers
551
        if (TRUSTED_PROXY) {
552
            // Check headers to validate
553
            $headers = getenv('SS_TRUSTED_PROXY_HOST_HEADER')
554
                ? explode(',', getenv('SS_TRUSTED_PROXY_HOST_HEADER'))
555
                : ['HTTP_X_FORWARDED_HOST']; // Backwards compatible defaults
556
            foreach ($headers as $header) {
557
                if (!empty($_SERVER[$header])) {
558
                    // Get the first host, in case there's multiple separated through commas
559
                    return strtok($_SERVER[$header], ',');
560
                }
561
            }
562
        }
563
564
        // Check given header
565
        if (isset($_SERVER['HTTP_HOST'])) {
566
            return $_SERVER['HTTP_HOST'];
567
        }
568
569
        // Check base url
570
        if ($baseURL = self::config()->uninherited('default_base_url')) {
571
            $baseURL = Injector::inst()->convertServiceProperty($baseURL);
572
            $host = parse_url($baseURL, PHP_URL_HOST);
573
            if ($host) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $host 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...
574
                return $host;
575
            }
576
        }
577
578
        // Legacy ss4-alpha environment var
579
        /**
580
         * @todo remove at 4.0.0-beta1
581
         * @deprecated 4.0.0-beta1
582
         */
583
        $legacyHostname = getenv('SS_HOST');
584
        if ($legacyHostname) {
585
            Deprecation::notice('4.0', 'SS_HOST will be removed in ss 4.0.0-beta1');
586
            return $legacyHostname;
587
        }
588
589
        // Fail over to server_name (least reliable)
590
        return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : gethostname();
591
    }
592
593
    /**
594
     * Returns the domain part of the URL 'http://www.mysite.com'. Returns FALSE is this environment
595
     * variable isn't set.
596
     *
597
     * @return bool|string
598
     */
599
    public static function protocolAndHost()
600
    {
601
        return static::protocol() . static::host();
602
    }
603
604
    /**
605
     * Return the current protocol that the site is running under.
606
     *
607
     * @return string
608
     */
609
    public static function protocol()
610
    {
611
        return (self::is_https()) ? 'https://' : 'http://';
612
    }
613
614
    /**
615
     * Return whether the site is running as under HTTPS.
616
     *
617
     * @return bool
618
     */
619
    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...
620
    {
621
        // Check override from alternate_base_url
622
        if ($baseURL = self::config()->uninherited('alternate_base_url')) {
623
            $baseURL = Injector::inst()->convertServiceProperty($baseURL);
624
            $protocol = parse_url($baseURL, PHP_URL_SCHEME);
625
            if ($protocol) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $protocol 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...
626
                return $protocol === 'https';
627
            }
628
        }
629
630
        // See https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
631
        // See https://support.microsoft.com/en-us/kb/307347
632
        if (TRUSTED_PROXY) {
633
            $headers = getenv('SS_TRUSTED_PROXY_PROTOCOL_HEADER')
634
                ? explode(',', getenv('SS_TRUSTED_PROXY_PROTOCOL_HEADER'))
635
                : ['HTTP_X_FORWARDED_PROTO', 'HTTP_X_FORWARDED_PROTOCOL', 'HTTP_FRONT_END_HTTPS'];
636
            foreach ($headers as $header) {
637
                $headerCompareVal = ($header === 'HTTP_FRONT_END_HTTPS' ? 'on' : 'https');
638
                if (!empty($_SERVER[$header]) && strtolower($_SERVER[$header]) == $headerCompareVal) {
639
                    return true;
640
                }
641
            }
642
        }
643
644
        // Check common $_SERVER
645
        if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) {
646
            return true;
647
        }
648
        if (isset($_SERVER['SSL'])) {
649
            return true;
650
        }
651
652
        // Check default_base_url
653
        if ($baseURL = self::config()->uninherited('default_base_url')) {
654
            $baseURL = Injector::inst()->convertServiceProperty($baseURL);
655
            $protocol = parse_url($baseURL, PHP_URL_SCHEME);
656
            if ($protocol) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $protocol 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...
657
                return $protocol === 'https';
658
            }
659
        }
660
661
        return false;
662
    }
663
664
    /**
665
     * Return the root-relative url for the baseurl
666
     *
667
     * @return string Root-relative url with trailing slash.
668
     */
669
    public static function baseURL()
670
    {
671
        // Check override base_url
672
        $alternate = self::config()->get('alternate_base_url');
673
        if ($alternate) {
674
            $alternate = Injector::inst()->convertServiceProperty($alternate);
675
            return rtrim(parse_url($alternate, PHP_URL_PATH), '/') . '/';
676
        }
677
678
        // Get env base url
679
        $baseURL = rtrim(BASE_URL, '/') . '/';
680
681
        // Check if BASE_SCRIPT_URL is defined
682
        // e.g. `index.php/`
683
        if (defined('BASE_SCRIPT_URL')) {
684
            return $baseURL . BASE_SCRIPT_URL;
685
        }
686
687
        return $baseURL;
688
    }
689
690
    /**
691
     * Returns the root filesystem folder for the site. It will be automatically calculated unless
692
     * it is overridden with {@link setBaseFolder()}.
693
     *
694
     * @return string
695
     */
696
    public static function baseFolder()
697
    {
698
        $alternate = Director::config()->uninherited('alternate_base_folder');
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...
699
        return ($alternate) ? $alternate : BASE_PATH;
700
    }
701
702
    /**
703
     * Turns an absolute URL or folder into one that's relative to the root of the site. This is useful
704
     * when turning a URL into a filesystem reference, or vice versa.
705
     *
706
     * @param string $url Accepts both a URL or a filesystem path.
707
     *
708
     * @return string
709
     */
710
    public static function makeRelative($url)
711
    {
712
        // Allow for the accidental inclusion whitespace and // in the URL
713
        $url = trim(preg_replace('#([^:])//#', '\\1/', $url));
714
715
        $base1 = self::absoluteBaseURL();
716
        $baseDomain = substr($base1, strlen(self::protocol()));
717
718
        // Only bother comparing the URL to the absolute version if $url looks like a URL.
719
        if (preg_match('/^https?[^:]*:\/\//', $url, $matches)) {
720
            $urlProtocol = $matches[0];
721
            $urlWithoutProtocol = substr($url, strlen($urlProtocol));
722
723
            // If we are already looking at baseURL, return '' (substr will return false)
724
            if ($url == $base1) {
725
                return '';
726
            } elseif (substr($url, 0, strlen($base1)) == $base1) {
727
                return substr($url, strlen($base1));
728
            } elseif (substr($base1, -1) == "/" && $url == substr($base1, 0, -1)) {
729
                // Convert http://www.mydomain.com/mysitedir to ''
730
                return "";
731
            }
732
733
            if (substr($urlWithoutProtocol, 0, strlen($baseDomain)) == $baseDomain) {
734
                return substr($urlWithoutProtocol, strlen($baseDomain));
735
            }
736
        }
737
738
        // test for base folder, e.g. /var/www
739
        $base2 = self::baseFolder();
740
        if (substr($url, 0, strlen($base2)) == $base2) {
741
            return substr($url, strlen($base2));
742
        }
743
744
        // Test for relative base url, e.g. mywebsite/ if the full URL is http://localhost/mywebsite/
745
        $base3 = self::baseURL();
746
        if (substr($url, 0, strlen($base3)) == $base3) {
747
            return substr($url, strlen($base3));
748
        }
749
750
        // Test for relative base url, e.g mywebsite/ if the full url is localhost/myswebsite
751
        if (substr($url, 0, strlen($baseDomain)) == $baseDomain) {
752
            return substr($url, strlen($baseDomain));
753
        }
754
755
        // Nothing matched, fall back to returning the original URL
756
        return $url;
757
    }
758
759
    /**
760
     * Returns true if a given path is absolute. Works under both *nix and windows systems.
761
     *
762
     * @param string $path
763
     *
764
     * @return bool
765
     */
766
    public static function is_absolute($path)
767
    {
768
        if (empty($path)) {
769
            return false;
770
        }
771
        if ($path[0] == '/' || $path[0] == '\\') {
772
            return true;
773
        }
774
        return preg_match('/^[a-zA-Z]:[\\\\\/]/', $path) == 1;
775
    }
776
777
    /**
778
     * Determine if the url is root relative (i.e. starts with /, but not with //) SilverStripe
779
     * considers root relative urls as a subset of relative urls.
780
     *
781
     * @param string $url
782
     *
783
     * @return bool
784
     */
785
    public static function is_root_relative_url($url)
786
    {
787
        return strpos($url, '/') === 0 && strpos($url, '//') !== 0;
788
    }
789
790
    /**
791
     * Checks if a given URL is absolute (e.g. starts with 'http://' etc.). URLs beginning with "//"
792
     * are treated as absolute, as browsers take this to mean the same protocol as currently being used.
793
     *
794
     * Useful to check before redirecting based on a URL from user submissions through $_GET or $_POST,
795
     * and avoid phishing attacks by redirecting to an attackers server.
796
     *
797
     * Note: Can't solely rely on PHP's parse_url() , since it is not intended to work with relative URLs
798
     * or for security purposes. filter_var($url, FILTER_VALIDATE_URL) has similar problems.
799
     *
800
     * @param string $url
801
     *
802
     * @return bool
803
     */
804
    public static function is_absolute_url($url)
805
    {
806
        // Strip off the query and fragment parts of the URL before checking
807
        if (($queryPosition = strpos($url, '?')) !== false) {
808
            $url = substr($url, 0, $queryPosition-1);
809
        }
810
        if (($hashPosition = strpos($url, '#')) !== false) {
811
            $url = substr($url, 0, $hashPosition-1);
812
        }
813
        $colonPosition = strpos($url, ':');
814
        $slashPosition = strpos($url, '/');
815
        return (
816
            // Base check for existence of a host on a compliant URL
817
            parse_url($url, PHP_URL_HOST)
818
            // Check for more than one leading slash without a protocol.
819
            // While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers,
820
            // and hence a potential security risk. Single leading slashes are not an issue though.
821
            || preg_match('%^\s*/{2,}%', $url)
822
            || (
823
                // If a colon is found, check if it's part of a valid scheme definition
824
                // (meaning its not preceded by a slash).
825
                $colonPosition !== false
826
                && ($slashPosition === false || $colonPosition < $slashPosition)
827
            )
828
        );
829
    }
830
831
    /**
832
     * Checks if a given URL is relative (or root relative) by checking {@link is_absolute_url()}.
833
     *
834
     * @param string $url
835
     *
836
     * @return bool
837
     */
838
    public static function is_relative_url($url)
839
    {
840
        return !static::is_absolute_url($url);
841
    }
842
843
    /**
844
     * Checks if the given URL is belonging to this "site" (not an external link). That's the case if
845
     * the URL is relative, as defined by {@link is_relative_url()}, or if the host matches
846
     * {@link protocolAndHost()}.
847
     *
848
     * Useful to check before redirecting based on a URL from user submissions through $_GET or $_POST,
849
     * and avoid phishing attacks by redirecting to an attackers server.
850
     *
851
     * @param string $url
852
     *
853
     * @return bool
854
     */
855
    public static function is_site_url($url)
856
    {
857
        $urlHost = parse_url($url, PHP_URL_HOST);
858
        $actualHost = parse_url(self::protocolAndHost(), PHP_URL_HOST);
859
        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...
860
            return true;
861
        } else {
862
            return self::is_relative_url($url);
863
        }
864
    }
865
866
    /**
867
     * Takes a $_SERVER data array and extracts HTTP request headers.
868
     *
869
     * @param array $server
870
     *
871
     * @return array
872
     */
873
    public static function extract_request_headers(array $server)
874
    {
875
        $headers = array();
876
877
        foreach ($server as $key => $value) {
878
            if (substr($key, 0, 5) == 'HTTP_') {
879
                $key = substr($key, 5);
880
                $key = strtolower(str_replace('_', ' ', $key));
881
                $key = str_replace(' ', '-', ucwords($key));
882
                $headers[$key] = $value;
883
            }
884
        }
885
886
        if (isset($server['CONTENT_TYPE'])) {
887
            $headers['Content-Type'] = $server['CONTENT_TYPE'];
888
        }
889
        if (isset($server['CONTENT_LENGTH'])) {
890
            $headers['Content-Length'] = $server['CONTENT_LENGTH'];
891
        }
892
893
        return $headers;
894
    }
895
896
    /**
897
     * Given a filesystem reference relative to the site root, return the full file-system path.
898
     *
899
     * @param string $file
900
     *
901
     * @return string
902
     */
903
    public static function getAbsFile($file)
904
    {
905
        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...
906
    }
907
908
    /**
909
     * Returns true if the given file exists. Filename should be relative to the site root.
910
     *
911
     * @param $file
912
     *
913
     * @return bool
914
     */
915
    public static function fileExists($file)
916
    {
917
        // replace any appended query-strings, e.g. /path/to/foo.php?bar=1 to /path/to/foo.php
918
        $file = preg_replace('/([^\?]*)?.*/', '$1', $file);
919
        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...
920
    }
921
922
    /**
923
     * Returns the Absolute URL of the site root.
924
     *
925
     * @return string
926
     */
927
    public static function absoluteBaseURL()
928
    {
929
        return self::absoluteURL(
930
            self::baseURL(),
931
            self::ROOT
932
        );
933
    }
934
935
    /**
936
     * Returns the Absolute URL of the site root, embedding the current basic-auth credentials into
937
     * the URL.
938
     *
939
     * @return string
940
     */
941
    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...
942
    {
943
        $login = "";
944
945
        if (isset($_SERVER['PHP_AUTH_USER'])) {
946
            $login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@";
947
        }
948
949
        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...
950
    }
951
952
    /**
953
     * Skip any further processing and immediately respond with a redirect to the passed URL.
954
     *
955
     * @param string $destURL
956
     */
957
    protected static function force_redirect($destURL)
958
    {
959
        $response = new HTTPResponse();
960
        $response->redirect($destURL, 301);
961
962
        HTTP::add_cache_headers($response);
963
964
        // TODO: Use an exception - ATM we can be called from _config.php, before Director#handleRequest's try block
965
        $response->output();
966
        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...
967
    }
968
969
    /**
970
     * Force the site to run on SSL.
971
     *
972
     * To use, call from _config.php. For example:
973
     * <code>
974
     * if (Director::isLive()) Director::forceSSL();
975
     * </code>
976
     *
977
     * If you don't want your entire site to be on SSL, you can pass an array of PCRE regular expression
978
     * patterns for matching relative URLs. For example:
979
     * <code>
980
     * if (Director::isLive()) Director::forceSSL(array('/^admin/', '/^Security/'));
981
     * </code>
982
     *
983
     * If you want certain parts of your site protected under a different domain, you can specify
984
     * the domain as an argument:
985
     * <code>
986
     * if (Director::isLive()) Director::forceSSL(array('/^admin/', '/^Security/'), 'secure.mysite.com');
987
     * </code>
988
     *
989
     * Note that the session data will be lost when moving from HTTP to HTTPS. It is your responsibility
990
     * to ensure that this won't cause usability problems.
991
     *
992
     * CAUTION: This does not respect the site environment mode. You should check this
993
     * as per the above examples using Director::isLive() or Director::isTest() for example.
994
     *
995
     * @param array $patterns Array of regex patterns to match URLs that should be HTTPS.
996
     * @param string $secureDomain Secure domain to redirect to. Defaults to the current domain.
997
     *
998
     * @return bool|string String of URL when unit tests running, boolean FALSE if patterns don't match request URI.
999
     */
1000
    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...
1001
    {
1002
        // Calling from the command-line?
1003
        if (!isset($_SERVER['REQUEST_URI'])) {
1004
            return false;
1005
        }
1006
1007
        $matched = false;
1008
1009
        if ($patterns) {
1010
            $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...
1011
1012
            // protect portions of the site based on the pattern
1013
            foreach ($patterns as $pattern) {
1014
                if (preg_match($pattern, $relativeURL)) {
1015
                    $matched = true;
1016
                    break;
1017
                }
1018
            }
1019
        } else {
1020
            // protect the entire site
1021
            $matched = true;
1022
        }
1023
1024
        if ($matched && !self::is_https()) {
1025
            // if an domain is specified, redirect to that instead of the current domain
1026
            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...
1027
                $url = 'https://' . $secureDomain . $_SERVER['REQUEST_URI'];
1028
            } else {
1029
                $url = $_SERVER['REQUEST_URI'];
1030
            }
1031
1032
            $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...
1033
1034
            // This coupling to SapphireTest is necessary to test the destination URL and to not interfere with tests
1035
            if (class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test()) {
1036
                return $destURL;
1037
            } else {
1038
                self::force_redirect($destURL);
1039
                return true;
1040
            }
1041
        } else {
1042
            return false;
1043
        }
1044
    }
1045
1046
    /**
1047
     * Force a redirect to a domain starting with "www."
1048
     */
1049
    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...
1050
    {
1051
        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...
1052
            $destURL = str_replace(
1053
                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...
1054
                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...
1055
                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...
1056
            );
1057
1058
            self::force_redirect($destURL);
1059
        }
1060
    }
1061
1062
    /**
1063
     * Checks if the current HTTP-Request is an "Ajax-Request" by checking for a custom header set by
1064
     * jQuery or whether a manually set request-parameter 'ajax' is present.
1065
     *
1066
     * @return bool
1067
     */
1068
    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...
1069
    {
1070
        if (Controller::has_curr()) {
1071
            return Controller::curr()->getRequest()->isAjax();
1072
        } else {
1073
            return (
1074
                isset($_REQUEST['ajax']) ||
1075
                (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == "XMLHttpRequest")
1076
            );
1077
        }
1078
    }
1079
1080
    /**
1081
     * Returns true if this script is being run from the command line rather than the web server.
1082
     *
1083
     * @return bool
1084
     */
1085
    public static function is_cli()
1086
    {
1087
        return (php_sapi_name() == "cli");
1088
    }
1089
1090
    /**
1091
     * Set the environment type of the current site.
1092
     *
1093
     * Typically, a SilverStripe site have a number of environments:
1094
     *  - Development environments, such a copy on your local machine.
1095
     *  - Test sites, such as the one you show the client before going live.
1096
     *  - The live site itself.
1097
     *
1098
     * The behaviour of these environments often varies slightly.  For example, development sites may
1099
     * have errors dumped to the screen, and order confirmation emails might be sent to the developer
1100
     * instead of the client.
1101
     *
1102
     * To help with this, SilverStripe supports the notion of an environment type.  The environment
1103
     * type can be dev, test, or live.
1104
     *
1105
     * Dev mode can also be forced by putting ?isDev=1 in your URL, which will ask you to log in and
1106
     * then push the site into dev mode for the remainder of the session. Putting ?isDev=0 onto the URL
1107
     * can turn it back.
1108
     *
1109
     * Test mode can also be forced by putting ?isTest=1 in your URL, which will ask you to log in and
1110
     * then push the site into test mode for the remainder of the session. Putting ?isTest=0 onto the URL
1111
     * can turn it back.
1112
     *
1113
     * Generally speaking, these methods will be called from your _config.php file.
1114
     *
1115
     * Once the environment type is set, it can be checked with {@link Director::isDev()},
1116
     * {@link Director::isTest()}, and {@link Director::isLive()}.
1117
     *
1118
     * @param string $environment
1119
     */
1120
    public static function set_environment_type($environment)
1121
    {
1122
        if (!in_array($environment, ['dev', 'test', 'live'])) {
1123
            throw new InvalidArgumentException(
1124
                "Director::set_environment_type passed '$environment'.  It should be passed dev, test, or live"
1125
            );
1126
        }
1127
        self::$environment_type = $environment;
1128
    }
1129
1130
    /**
1131
     * Can also be checked with {@link Director::isDev()}, {@link Director::isTest()}, and
1132
     * {@link Director::isLive()}.
1133
     *
1134
     * @return bool
1135
     */
1136
    public static function get_environment_type()
1137
    {
1138
        // Check saved session
1139
        if ($env = self::session_environment()) {
1140
            return $env;
1141
        }
1142
1143
        // Check set
1144
        if (self::$environment_type) {
1145
            return self::$environment_type;
1146
        }
1147
1148
        // Check getenv
1149
        if ($env = getenv('SS_ENVIRONMENT_TYPE')) {
1150
            return $env;
1151
        }
1152
1153
        return 'live';
1154
    }
1155
1156
    /**
1157
     * This function will return true if the site is in a live environment. For information about
1158
     * environment types, see {@link Director::set_environment_type()}.
1159
     *
1160
     * @return bool
1161
     */
1162
    public static function isLive()
1163
    {
1164
        return self::get_environment_type() === 'live';
1165
    }
1166
1167
    /**
1168
     * This function will return true if the site is in a development environment. For information about
1169
     * environment types, see {@link Director::set_environment_type()}.
1170
     *
1171
     * @return bool
1172
     */
1173
    public static function isDev()
1174
    {
1175
        return self::get_environment_type() === 'dev';
1176
    }
1177
1178
    /**
1179
     * This function will return true if the site is in a test environment. For information about
1180
     * environment types, see {@link Director::set_environment_type()}.
1181
     *
1182
     * @return bool
1183
     */
1184
    public static function isTest()
1185
    {
1186
        return self::get_environment_type() === 'test';
1187
    }
1188
1189
    /**
1190
     * Check or update any temporary environment specified in the session.
1191
     *
1192
     * @return null|string
1193
     */
1194
    public 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...
1195
    {
1196
        // Set session from querystring
1197
        if (isset($_GET['isDev'])) {
1198
            if (isset($_SESSION)) {
1199
                unset($_SESSION['isTest']); // In case we are changing from test mode
1200
                $_SESSION['isDev'] = $_GET['isDev'];
1201
            }
1202
            return 'dev';
1203
        } elseif (isset($_GET['isTest'])) {
1204
            if (isset($_SESSION)) {
1205
                unset($_SESSION['isDev']); // In case we are changing from dev mode
1206
                $_SESSION['isTest'] = $_GET['isTest'];
1207
            }
1208
            return 'test';
1209
        }
1210
        // Check session
1211
        if (isset($_SESSION['isDev']) && $_SESSION['isDev']) {
1212
            return 'dev';
1213
        } elseif (isset($_SESSION['isTest']) && $_SESSION['isTest']) {
1214
            return 'test';
1215
        } else {
1216
            return null;
1217
        }
1218
    }
1219
1220
    /**
1221
     * Returns an array of strings of the method names of methods on the call that should be exposed
1222
     * as global variables in the templates.
1223
     *
1224
     * @return array
1225
     */
1226
    public static function get_template_global_variables()
1227
    {
1228
        return array(
1229
            'absoluteBaseURL',
1230
            'baseURL',
1231
            'is_ajax',
1232
            'isAjax' => 'is_ajax',
1233
            'BaseHref' => 'absoluteBaseURL',    //@deprecated 3.0
1234
        );
1235
    }
1236
}
1237