Completed
Pull Request — master (#7057)
by Damian
08:49
created

Director::host()   D

Complexity

Conditions 9
Paths 27

Size

Total Lines 37
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 19
nc 27
nop 1
dl 0
loc 37
rs 4.909
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control;
4
5
use SilverStripe\CMS\Model\SiteTree;
6
use SilverStripe\Control\Middleware\HTTPMiddleware;
7
use SilverStripe\Core\Config\Configurable;
8
use SilverStripe\Core\Environment;
9
use SilverStripe\Core\Injector\Injector;
10
use SilverStripe\Core\Kernel;
11
use SilverStripe\Dev\Deprecation;
12
use SilverStripe\Versioned\Versioned;
13
use SilverStripe\View\Requirements;
14
use SilverStripe\View\Requirements_Backend;
15
use SilverStripe\View\TemplateGlobalProvider;
16
17
/**
18
 * Director is responsible for processing URLs, and providing environment information.
19
 *
20
 * The most important part of director is {@link Director::handleRequest()}, which is passed an HTTPRequest and will
21
 * execute the appropriate controller.
22
 *
23
 * Director also has a number of static methods that provide information about the environment, such as
24
 * {@link Director::$environment_type}.
25
 *
26
 * @see Director::handleRequest()
27
 * @see Director::$rules
28
 * @see Director::$environment_type
29
 */
30
class Director implements TemplateGlobalProvider
31
{
32
    use Configurable;
33
    use HTTPMiddlewareAware;
34
35
    /**
36
     * Specifies this url is relative to the base.
37
     *
38
     * @var string
39
     */
40
    const BASE = 'BASE';
41
42
    /**
43
     * Specifies this url is relative to the site root.
44
     *
45
     * @var string
46
     */
47
    const ROOT = 'ROOT';
48
49
    /**
50
     * specifies this url is relative to the current request.
51
     *
52
     * @var string
53
     */
54
    const REQUEST = 'REQUEST';
55
56
    /**
57
     * @config
58
     * @var array
59
     */
60
    private static $rules = array();
61
62
    /**
63
     * Set current page
64
     *
65
     * @internal
66
     * @var SiteTree
67
     */
68
    private static $current_page;
69
70
    /**
71
     * @config
72
     * @var string
73
     */
74
    private static $alternate_base_folder;
75
76
    /**
77
     * Force the base_url to a specific value.
78
     * If assigned, default_base_url and the value in the $_SERVER
79
     * global is ignored.
80
     * Supports back-ticked vars; E.g. '`SS_BASE_URL`'
81
     *
82
     * @config
83
     * @var string
84
     */
85
    private static $alternate_base_url;
86
87
    /**
88
     * Base url to populate if cannot be determined otherwise.
89
     * Supports back-ticked vars; E.g. '`SS_BASE_URL`'
90
     *
91
     * @config
92
     * @var string
93
     */
94
    private static $default_base_url = '`SS_BASE_URL`';
95
96
    /**
97
     * Assigned environment type
98
     *
99
     * @internal
100
     * @var string
101
     */
102
    protected static $environment_type;
103
104
    /**
105
     * Test a URL request, returning a response object. This method is a wrapper around
106
     * Director::handleRequest() to assist with functional testing. It will execute the URL given, and
107
     * return the result as an HTTPResponse object.
108
     *
109
     * @param string $url The URL to visit.
110
     * @param array $postVars The $_POST & $_FILES variables.
111
     * @param array|Session $session The {@link Session} object representing the current session.
112
     * By passing the same object to multiple  calls of Director::test(), you can simulate a persisted
113
     * session.
114
     * @param string $httpMethod The HTTP method, such as GET or POST.  It will default to POST if
115
     * postVars is set, GET otherwise. Overwritten by $postVars['_method'] if present.
116
     * @param string $body The HTTP body.
117
     * @param array $headers HTTP headers with key-value pairs.
118
     * @param array|Cookie_Backend $cookies to populate $_COOKIE.
119
     * @param HTTPRequest $request The {@see SS_HTTP_Request} object generated as a part of this request.
120
     *
121
     * @return HTTPResponse
122
     *
123
     * @throws HTTPResponse_Exception
124
     */
125
    public static function test(
126
        $url,
127
        $postVars = [],
128
        $session = array(),
129
        $httpMethod = null,
130
        $body = null,
131
        $headers = array(),
132
        $cookies = array(),
133
        &$request = null
134
    ) {
135
        return static::mockRequest(
136
            function (HTTPRequest $request) {
137
                return Injector::inst()->get(Director::class)->handleRequest($request);
138
            },
139
            $url,
140
            $postVars,
141
            $session,
142
            $httpMethod,
143
            $body,
144
            $headers,
145
            $cookies,
146
            $request
147
        );
148
    }
149
150
    /**
151
     * Mock a request, passing this to the given callback, before resetting.
152
     *
153
     * @param callable $callback Action to pass the HTTPRequst object
154
     * @param string $url The URL to build
155
     * @param array $postVars The $_POST & $_FILES variables.
156
     * @param array|Session $session The {@link Session} object representing the current session.
157
     * By passing the same object to multiple  calls of Director::test(), you can simulate a persisted
158
     * session.
159
     * @param string $httpMethod The HTTP method, such as GET or POST.  It will default to POST if
160
     * postVars is set, GET otherwise. Overwritten by $postVars['_method'] if present.
161
     * @param string $body The HTTP body.
162
     * @param array $headers HTTP headers with key-value pairs.
163
     * @param array|Cookie_Backend $cookies to populate $_COOKIE.
164
     * @param HTTPRequest $request The {@see SS_HTTP_Request} object generated as a part of this request.
165
     * @return mixed Result of callback
166
     */
167
    public static function mockRequest(
0 ignored issues
show
Coding Style introduced by
mockRequest 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...
168
        $callback,
169
        $url,
170
        $postVars = [],
171
        $session = [],
172
        $httpMethod = null,
173
        $body = null,
174
        $headers = [],
175
        $cookies = [],
176
        &$request = null
177
    ) {
178
        // Build list of cleanup promises
179
        $finally = [];
180
181
        /** @var Kernel $kernel */
182
        $kernel = Injector::inst()->get(Kernel::class);
183
        $kernel->nest();
184
        $finally[] = function () use ($kernel) {
185
            $kernel->activate();
186
        };
187
188
        // backup existing vars, and create new vars
189
        $existingVars = Environment::getVariables();
190
        $finally[] = function () use ($existingVars) {
191
            Environment::setVariables($existingVars);
192
        };
193
        $newVars = $existingVars;
194
195
        // These are needed so that calling Director::test() does not muck with whoever is calling it.
196
        // Really, it's some inappropriate coupling and should be resolved by making less use of statics.
197
        if (class_exists(Versioned::class)) {
198
            $oldReadingMode = Versioned::get_reading_mode();
199
            $finally[] = function () use ($oldReadingMode) {
200
                Versioned::set_reading_mode($oldReadingMode);
201
            };
202
        }
203
204
        // Default httpMethod
205
        $newVars['_SERVER']['REQUEST_METHOD'] = $httpMethod ?: ($postVars ? "POST" : "GET");
206
        $newVars['_POST'] = (array)$postVars;
207
208
        // Setup session
209
        if ($session instanceof Session) {
210
            // Note: If passing $session as object, ensure that changes are written back
211
            // This is important for classes such as FunctionalTest which emulate cross-request persistence
212
            $newVars['_SESSION'] = $session->getAll();
213
            $finally[] = function () use ($session) {
214
                if (isset($_SESSION)) {
215
                    foreach ($_SESSION as $key => $value) {
216
                        $session->set($key, $value);
217
                    }
218
                }
219
            };
220
        } else {
221
            $newVars['_SESSION'] = $session ?: [];
222
        }
223
224
        // Setup cookies
225
        $cookieJar = $cookies instanceof Cookie_Backend
226
            ? $cookies
227
            : Injector::inst()->createWithArgs(Cookie_Backend::class, array($cookies ?: []));
228
        $newVars['_COOKIE'] = $cookieJar->getAll(false);
229
        Cookie::config()->update('report_errors', false);
230
        Injector::inst()->registerService($cookieJar, Cookie_Backend::class);
231
232
        // Backup requirements
233
        $existingRequirementsBackend = Requirements::backend();
234
        Requirements::set_backend(Requirements_Backend::create());
235
        $finally[] = function () use ($existingRequirementsBackend) {
236
            Requirements::set_backend($existingRequirementsBackend);
237
        };
238
239
        // Strip any hash
240
        $url = strtok($url, '#');
241
242
        // Handle absolute URLs
243
        if (parse_url($url, PHP_URL_HOST)) {
244
            $bits = parse_url($url);
245
246
            // If a port is mentioned in the absolute URL, be sure to add that into the HTTP host
247
            $newVars['_SERVER']['HTTP_HOST'] = isset($bits['port'])
248
                ? $bits['host'].':'.$bits['port']
249
                : $bits['host'];
250
        }
251
252
        // Ensure URL is properly made relative.
253
        // Example: url passed is "/ss31/my-page" (prefixed with BASE_URL), this should be changed to "my-page"
254
        $url = self::makeRelative($url);
255
        if (strpos($url, '?') !== false) {
256
            list($url, $getVarsEncoded) = explode('?', $url, 2);
257
            parse_str($getVarsEncoded, $newVars['_GET']);
258
        } else {
259
            $newVars['_GET'] = [];
260
        }
261
        $newVars['_SERVER']['REQUEST_URI'] = Director::baseURL() . $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...
262
        $newVars['_REQUEST'] = array_merge($newVars['_GET'], $newVars['_POST']);
263
264
        // Normalise vars
265
        $newVars = HTTPRequestBuilder::cleanEnvironment($newVars);
266
267
        // Create new request
268
        $request = HTTPRequestBuilder::createFromVariables($newVars, $body);
269
        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...
270
            foreach ($headers as $k => $v) {
271
                $request->addHeader($k, $v);
272
            }
273
        }
274
275
        // Apply new vars to environment
276
        Environment::setVariables($newVars);
277
278
        try {
279
            // Normal request handling
280
            return call_user_func($callback, $request);
281
        } finally {
282
            // Restore state in reverse order to assignment
283
            foreach (array_reverse($finally) as $callback) {
284
                call_user_func($callback);
285
            }
286
        }
287
    }
288
289
    /**
290
     * Process the given URL, creating the appropriate controller and executing it.
291
     *
292
     * Request processing is handled as follows:
293
     * - Director::handleRequest($request) checks each of the Director rules and identifies a controller
294
     *   to handle this request.
295
     * - Controller::handleRequest($request) is then called.  This will find a rule to handle the URL,
296
     *   and call the rule handling method.
297
     * - RequestHandler::handleRequest($request) is recursively called whenever a rule handling method
298
     *   returns a RequestHandler object.
299
     *
300
     * In addition to request processing, Director will manage the session, and perform the output of
301
     * the actual response to the browser.
302
     *
303
     * @param HTTPRequest $request
304
     * @return HTTPResponse
305
     * @throws HTTPResponse_Exception
306
     */
307
    public function handleRequest(HTTPRequest $request)
308
    {
309
        Injector::inst()->registerService($request, HTTPRequest::class);
310
311
        $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...
312
313
        // Default handler - mo URL rules matched, so return a 404 error.
314
        $handler = function () {
315
            return new HTTPResponse('No URL rule was matched', 404);
316
        };
317
318
        foreach ($rules as $pattern => $controllerOptions) {
319
            // Normalise route rule
320
            if (is_string($controllerOptions)) {
321
                if (substr($controllerOptions, 0, 2) == '->') {
322
                    $controllerOptions = array('Redirect' => substr($controllerOptions, 2));
323
                } else {
324
                    $controllerOptions = array('Controller' => $controllerOptions);
325
                }
326
            }
327
328
            // Match pattern
329
            $arguments = $request->match($pattern, true);
330
            if ($arguments !== false) {
331
                $request->setRouteParams($controllerOptions);
332
                // controllerOptions provide some default arguments
333
                $arguments = array_merge($controllerOptions, $arguments);
334
335
                // Pop additional tokens from the tokenizer if necessary
336
                if (isset($controllerOptions['_PopTokeniser'])) {
337
                    $request->shift($controllerOptions['_PopTokeniser']);
338
                }
339
340
                // Handler for redirection
341
                if (isset($arguments['Redirect'])) {
342
                    $handler = function () use ($arguments) {
343
                        // Redirection
344
                        $response = new HTTPResponse();
345
                        $response->redirect(static::absoluteURL($arguments['Redirect']));
0 ignored issues
show
Security Bug introduced by
It seems like static::absoluteURL($arguments['Redirect']) targeting SilverStripe\Control\Director::absoluteURL() can also be of type false; however, SilverStripe\Control\HTTPResponse::redirect() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
346
                        return $response;
347
                    };
348
                    break;
349
                }
350
351
                // Find the controller name
352
                $controller = $arguments['Controller'];
353
354
                // String = service name
355
                if (is_string($controller)) {
356
                    $controllerObj = Injector::inst()->get($controller);
357
                // Array = service spec
358
                } elseif (is_array($controller)) {
359
                    $controllerObj = Injector::inst()->createFromSpec($controller);
360
                } else {
361
                    throw new \LogicException("Invalid Controller value '$controller'");
362
                }
363
364
                // Handler for calling a controller
365
                $handler = function (HTTPRequest $request) use ($controllerObj) {
366
                    try {
367
                        // Apply the controller's middleware. We do this outside of handleRequest so that
368
                        // subclasses of handleRequest will be called after the middlware processing
369
                        return $controllerObj->callMiddleware($request, function ($request) use ($controllerObj) {
370
                        return $controllerObj->handleRequest($request);
371
                        });
372
                    } catch (HTTPResponse_Exception $responseException) {
373
                        return $responseException->getResponse();
374
                    }
375
                };
376
                break;
377
            }
378
        }
379
380
        // Call the handler with the configured middlewares
381
        $response = $this->callMiddleware($request, $handler);
382
383
        // Note that if a different request was previously registered, this will now be lost
384
        // In these cases it's better to use Kernel::nest() prior to kicking off a nested request
385
        Injector::inst()->unregisterNamedObject(HTTPRequest::class);
386
387
        return $response;
388
    }
389
390
    /**
391
     * Call the given request handler with the given middlewares
392
     * Middlewares are specified as Injector service names
393
     *
394
     * @param $request The request to pass to the handler
395
     * @param $middlewareNames The services names of the middlewares to apply
396
     * @param $handler The request handler
397
     */
398
    protected static function callWithMiddlewares(HTTPRequest $request, array $middlewareNames, callable $handler)
399
    {
400
        $next = $handler;
401
402
        if ($middlewareNames) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $middlewareNames 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...
403
            $middlewares = array_map(
404
                function ($name) {
405
                    return Injector::inst()->get($name);
406
                },
407
                $middlewareNames
408
            );
409
410
            // Reverse middlewares
411
            /** @var HTTPMiddleware $middleware */
412
            foreach (array_reverse($middlewares) as $middleware) {
413
                $next = function ($request) use ($middleware, $next) {
414
                    return $middleware->process($request, $next);
415
                };
416
            }
417
        }
418
419
        return $next($request);
420
    }
421
422
    /**
423
     * Return the {@link SiteTree} object that is currently being viewed. If there is no SiteTree
424
     * object to return, then this will return the current controller.
425
     *
426
     * @return SiteTree|Controller
427
     */
428
    public static function get_current_page()
429
    {
430
        return self::$current_page ? self::$current_page : Controller::curr();
431
    }
432
433
    /**
434
     * Set the currently active {@link SiteTree} object that is being used to respond to the request.
435
     *
436
     * @param SiteTree $page
437
     */
438
    public static function set_current_page($page)
439
    {
440
        self::$current_page = $page;
441
    }
442
443
    /**
444
     * Turns the given URL into an absolute URL. By default non-site root relative urls will be
445
     * evaluated relative to the current base_url.
446
     *
447
     * @param string $url URL To transform to absolute.
448
     * @param string $relativeParent Method to use for evaluating relative urls.
449
     * Either one of BASE (baseurl), ROOT (site root), or REQUEST (requested page).
450
     * Defaults to BASE, which is the same behaviour as template url resolution.
451
     * Ignored if the url is absolute or site root.
452
     *
453
     * @return string
454
     */
455
    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...
456
    {
457
        if (is_bool($relativeParent)) {
458
            // Deprecate old boolean second parameter
459
            Deprecation::notice('5.0', 'Director::absoluteURL takes an explicit parent for relative url');
460
            $relativeParent = $relativeParent ? self::BASE : self::REQUEST;
461
        }
462
463
        // Check if there is already a protocol given
464
        if (preg_match('/^http(s?):\/\//', $url)) {
465
            return $url;
466
        }
467
468
        // Absolute urls without protocol are added
469
        // E.g. //google.com -> http://google.com
470
        if (strpos($url, '//') === 0) {
471
            return self::protocol() . substr($url, 2);
472
        }
473
474
        // Determine method for mapping the parent to this relative url
475
        if ($relativeParent === self::ROOT || self::is_root_relative_url($url)) {
476
            // Root relative urls always should be evaluated relative to the root
477
            $parent = self::protocolAndHost();
478
        } elseif ($relativeParent === self::REQUEST) {
479
            // Request relative urls rely on the REQUEST_URI param (old default behaviour)
480
            if (!isset($_SERVER['REQUEST_URI'])) {
481
                return false;
482
            }
483
            $parent = dirname($_SERVER['REQUEST_URI'] . 'x');
484
        } else {
485
            // Default to respecting site base_url
486
            $parent = self::absoluteBaseURL();
487
        }
488
489
        // Map empty urls to relative slash and join to base
490
        if (empty($url) || $url === '.' || $url === './') {
491
            $url = '/';
492
        }
493
        return Controller::join_links($parent, $url);
494
    }
495
496
    /**
497
     * A helper to determine the current hostname used to access the site.
498
     * The following are used to determine the host (in order)
499
     *  - Director.alternate_base_url (if it contains a domain name)
500
     *  - Trusted proxy headers
501
     *  - HTTP Host header
502
     *  - SS_BASE_URL env var
503
     *  - SERVER_NAME
504
     *  - gethostname()
505
     *
506
     * @return string
507
     */
508
    public static function host(HTTPRequest $request = null)
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...
509
    {
510
        // Check if overridden by alternate_base_url
511
        if ($baseURL = self::config()->get('alternate_base_url')) {
512
            $baseURL = Injector::inst()->convertServiceProperty($baseURL);
513
            $host = parse_url($baseURL, PHP_URL_HOST);
514
            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...
515
                return $host;
516
            }
517
        }
518
519
        if (Injector::inst()->has(HTTPRequest::class)) {
520
            /** @var HTTPRequest $request */
521
            $request = Injector::inst()->get(HTTPRequest::class);
522
            $host = $request->getHeader('Host');
523
            if ($host) {
524
                return $host;
525
            }
526
        }
527
528
        // Check given header
529
        if (isset($_SERVER['HTTP_HOST'])) {
530
            return $_SERVER['HTTP_HOST'];
531
        }
532
533
        // Check base url
534
        if ($baseURL = self::config()->uninherited('default_base_url')) {
535
            $baseURL = Injector::inst()->convertServiceProperty($baseURL);
536
            $host = parse_url($baseURL, PHP_URL_HOST);
537
            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...
538
                return $host;
539
            }
540
        }
541
542
        // Fail over to server_name (least reliable)
543
        return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : gethostname();
544
    }
545
546
    /**
547
     * Returns the domain part of the URL 'http://www.mysite.com'. Returns FALSE is this environment
548
     * variable isn't set.
549
     *
550
     * @return bool|string
551
     */
552
    public static function protocolAndHost(HTTPRequest $request = null)
553
    {
554
        return static::protocol($request) . static::host($request);
555
    }
556
557
    /**
558
     * Return the current protocol that the site is running under.
559
     *
560
     * @return string
561
     */
562
    public static function protocol(HTTPRequest $request = null)
563
    {
564
        return (self::is_https($request)) ? 'https://' : 'http://';
565
    }
566
567
    /**
568
     * Return whether the site is running as under HTTPS.
569
     *
570
     * @return bool
571
     */
572
    public static function is_https(HTTPRequest $request = null)
573
    {
574
        // Check override from alternate_base_url
575
        if ($baseURL = self::config()->uninherited('alternate_base_url')) {
576
            $baseURL = Injector::inst()->convertServiceProperty($baseURL);
577
            $protocol = parse_url($baseURL, PHP_URL_SCHEME);
578
            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...
579
                return $protocol === 'https';
580
            }
581
        }
582
583
        // Check the current request
584
        if (Injector::inst()->has(HTTPRequest::class)) {
585
            /** @var HTTPRequest $request */
586
            $request = Injector::inst()->get(HTTPRequest::class);
587
            $scheme = $request->getScheme();
588
            if ($scheme) {
589
                return $scheme === 'https';
590
            }
591
        }
592
593
        // Check default_base_url
594
        if ($baseURL = self::config()->uninherited('default_base_url')) {
595
            $baseURL = Injector::inst()->convertServiceProperty($baseURL);
596
            $protocol = parse_url($baseURL, PHP_URL_SCHEME);
597
            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...
598
                return $protocol === 'https';
599
            }
600
        }
601
602
        return false;
603
    }
604
605
    /**
606
     * Return the root-relative url for the baseurl
607
     *
608
     * @return string Root-relative url with trailing slash.
609
     */
610
    public static function baseURL()
611
    {
612
        // Check override base_url
613
        $alternate = self::config()->get('alternate_base_url');
614
        if ($alternate) {
615
            $alternate = Injector::inst()->convertServiceProperty($alternate);
616
            return rtrim(parse_url($alternate, PHP_URL_PATH), '/') . '/';
617
        }
618
619
        // Get env base url
620
        $baseURL = rtrim(BASE_URL, '/') . '/';
621
622
        // Check if BASE_SCRIPT_URL is defined
623
        // e.g. `index.php/`
624
        if (defined('BASE_SCRIPT_URL')) {
625
            return $baseURL . BASE_SCRIPT_URL;
626
        }
627
628
        return $baseURL;
629
    }
630
631
    /**
632
     * Returns the root filesystem folder for the site. It will be automatically calculated unless
633
     * it is overridden with {@link setBaseFolder()}.
634
     *
635
     * @return string
636
     */
637
    public static function baseFolder()
638
    {
639
        $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...
640
        return ($alternate) ? $alternate : BASE_PATH;
641
    }
642
643
    /**
644
     * Turns an absolute URL or folder into one that's relative to the root of the site. This is useful
645
     * when turning a URL into a filesystem reference, or vice versa.
646
     *
647
     * @param string $url Accepts both a URL or a filesystem path.
648
     *
649
     * @return string
650
     */
651
    public static function makeRelative($url)
652
    {
653
        // Allow for the accidental inclusion whitespace and // in the URL
654
        $url = trim(preg_replace('#([^:])//#', '\\1/', $url));
655
656
        $base1 = self::absoluteBaseURL();
657
        $baseDomain = substr($base1, strlen(self::protocol()));
658
659
        // Only bother comparing the URL to the absolute version if $url looks like a URL.
660
        if (preg_match('/^https?[^:]*:\/\//', $url, $matches)) {
661
            $urlProtocol = $matches[0];
662
            $urlWithoutProtocol = substr($url, strlen($urlProtocol));
663
664
            // If we are already looking at baseURL, return '' (substr will return false)
665
            if ($url == $base1) {
666
                return '';
667
            } elseif (substr($url, 0, strlen($base1)) == $base1) {
668
                return substr($url, strlen($base1));
669
            } elseif (substr($base1, -1) == "/" && $url == substr($base1, 0, -1)) {
670
                // Convert http://www.mydomain.com/mysitedir to ''
671
                return "";
672
            }
673
674
            if (substr($urlWithoutProtocol, 0, strlen($baseDomain)) == $baseDomain) {
675
                return substr($urlWithoutProtocol, strlen($baseDomain));
676
            }
677
        }
678
679
        // test for base folder, e.g. /var/www
680
        $base2 = self::baseFolder();
681
        if (substr($url, 0, strlen($base2)) == $base2) {
682
            return substr($url, strlen($base2));
683
        }
684
685
        // Test for relative base url, e.g. mywebsite/ if the full URL is http://localhost/mywebsite/
686
        $base3 = self::baseURL();
687
        if (substr($url, 0, strlen($base3)) == $base3) {
688
            return substr($url, strlen($base3));
689
        }
690
691
        // Test for relative base url, e.g mywebsite/ if the full url is localhost/myswebsite
692
        if (substr($url, 0, strlen($baseDomain)) == $baseDomain) {
693
            return substr($url, strlen($baseDomain));
694
        }
695
696
        // Nothing matched, fall back to returning the original URL
697
        return $url;
698
    }
699
700
    /**
701
     * Returns true if a given path is absolute. Works under both *nix and windows systems.
702
     *
703
     * @param string $path
704
     *
705
     * @return bool
706
     */
707
    public static function is_absolute($path)
708
    {
709
        if (empty($path)) {
710
            return false;
711
        }
712
        if ($path[0] == '/' || $path[0] == '\\') {
713
            return true;
714
        }
715
        return preg_match('/^[a-zA-Z]:[\\\\\/]/', $path) == 1;
716
    }
717
718
    /**
719
     * Determine if the url is root relative (i.e. starts with /, but not with //) SilverStripe
720
     * considers root relative urls as a subset of relative urls.
721
     *
722
     * @param string $url
723
     *
724
     * @return bool
725
     */
726
    public static function is_root_relative_url($url)
727
    {
728
        return strpos($url, '/') === 0 && strpos($url, '//') !== 0;
729
    }
730
731
    /**
732
     * Checks if a given URL is absolute (e.g. starts with 'http://' etc.). URLs beginning with "//"
733
     * are treated as absolute, as browsers take this to mean the same protocol as currently being used.
734
     *
735
     * Useful to check before redirecting based on a URL from user submissions through $_GET or $_POST,
736
     * and avoid phishing attacks by redirecting to an attackers server.
737
     *
738
     * Note: Can't solely rely on PHP's parse_url() , since it is not intended to work with relative URLs
739
     * or for security purposes. filter_var($url, FILTER_VALIDATE_URL) has similar problems.
740
     *
741
     * @param string $url
742
     *
743
     * @return bool
744
     */
745
    public static function is_absolute_url($url)
746
    {
747
        // Strip off the query and fragment parts of the URL before checking
748
        if (($queryPosition = strpos($url, '?')) !== false) {
749
            $url = substr($url, 0, $queryPosition-1);
750
        }
751
        if (($hashPosition = strpos($url, '#')) !== false) {
752
            $url = substr($url, 0, $hashPosition-1);
753
        }
754
        $colonPosition = strpos($url, ':');
755
        $slashPosition = strpos($url, '/');
756
        return (
757
            // Base check for existence of a host on a compliant URL
758
            parse_url($url, PHP_URL_HOST)
759
            // Check for more than one leading slash without a protocol.
760
            // While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers,
761
            // and hence a potential security risk. Single leading slashes are not an issue though.
762
            || preg_match('%^\s*/{2,}%', $url)
763
            || (
764
                // If a colon is found, check if it's part of a valid scheme definition
765
                // (meaning its not preceded by a slash).
766
                $colonPosition !== false
767
                && ($slashPosition === false || $colonPosition < $slashPosition)
768
            )
769
        );
770
    }
771
772
    /**
773
     * Checks if a given URL is relative (or root relative) by checking {@link is_absolute_url()}.
774
     *
775
     * @param string $url
776
     *
777
     * @return bool
778
     */
779
    public static function is_relative_url($url)
780
    {
781
        return !static::is_absolute_url($url);
782
    }
783
784
    /**
785
     * Checks if the given URL is belonging to this "site" (not an external link). That's the case if
786
     * the URL is relative, as defined by {@link is_relative_url()}, or if the host matches
787
     * {@link protocolAndHost()}.
788
     *
789
     * Useful to check before redirecting based on a URL from user submissions through $_GET or $_POST,
790
     * and avoid phishing attacks by redirecting to an attackers server.
791
     *
792
     * @param string $url
793
     *
794
     * @return bool
795
     */
796
    public static function is_site_url($url)
797
    {
798
        $urlHost = parse_url($url, PHP_URL_HOST);
799
        $actualHost = parse_url(self::protocolAndHost(), PHP_URL_HOST);
800
        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...
801
            return true;
802
        } else {
803
            return self::is_relative_url($url);
804
        }
805
    }
806
807
    /**
808
     * Given a filesystem reference relative to the site root, return the full file-system path.
809
     *
810
     * @param string $file
811
     *
812
     * @return string
813
     */
814
    public static function getAbsFile($file)
815
    {
816
        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...
817
    }
818
819
    /**
820
     * Returns true if the given file exists. Filename should be relative to the site root.
821
     *
822
     * @param $file
823
     *
824
     * @return bool
825
     */
826
    public static function fileExists($file)
827
    {
828
        // replace any appended query-strings, e.g. /path/to/foo.php?bar=1 to /path/to/foo.php
829
        $file = preg_replace('/([^\?]*)?.*/', '$1', $file);
830
        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...
831
    }
832
833
    /**
834
     * Returns the Absolute URL of the site root.
835
     *
836
     * @return string
837
     */
838
    public static function absoluteBaseURL()
839
    {
840
        return self::absoluteURL(
841
            self::baseURL(),
842
            self::ROOT
843
        );
844
    }
845
846
    /**
847
     * Returns the Absolute URL of the site root, embedding the current basic-auth credentials into
848
     * the URL.
849
     *
850
     * @return string
851
     */
852
    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...
853
    {
854
        $login = "";
855
856
        if (isset($_SERVER['PHP_AUTH_USER'])) {
857
            $login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@";
858
        }
859
860
        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...
861
    }
862
863
    /**
864
     * Skip any further processing and immediately respond with a redirect to the passed URL.
865
     *
866
     * @param string $destURL
867
     * @throws HTTPResponse_Exception
868
     */
869
    protected static function force_redirect($destURL)
870
    {
871
        // Redirect to installer
872
        $response = new HTTPResponse();
873
        $response->redirect($destURL, 301);
874
        HTTP::add_cache_headers($response);
875
        throw new HTTPResponse_Exception($response);
876
    }
877
878
    /**
879
     * Force the site to run on SSL.
880
     *
881
     * To use, call from _config.php. For example:
882
     * <code>
883
     * if (Director::isLive()) Director::forceSSL();
884
     * </code>
885
     *
886
     * If you don't want your entire site to be on SSL, you can pass an array of PCRE regular expression
887
     * patterns for matching relative URLs. For example:
888
     * <code>
889
     * if (Director::isLive()) Director::forceSSL(array('/^admin/', '/^Security/'));
890
     * </code>
891
     *
892
     * If you want certain parts of your site protected under a different domain, you can specify
893
     * the domain as an argument:
894
     * <code>
895
     * if (Director::isLive()) Director::forceSSL(array('/^admin/', '/^Security/'), 'secure.mysite.com');
896
     * </code>
897
     *
898
     * Note that the session data will be lost when moving from HTTP to HTTPS. It is your responsibility
899
     * to ensure that this won't cause usability problems.
900
     *
901
     * CAUTION: This does not respect the site environment mode. You should check this
902
     * as per the above examples using Director::isLive() or Director::isTest() for example.
903
     *
904
     * @param array $patterns Array of regex patterns to match URLs that should be HTTPS.
905
     * @param string $secureDomain Secure domain to redirect to. Defaults to the current domain.
906
     * @return bool true if already on SSL, false if doesn't match patterns (or cannot redirect)
907
     * @throws HTTPResponse_Exception Throws exception with redirect, if successful
908
     */
909
    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...
910
    {
911
        // Already on SSL
912
        if (static::is_https()) {
913
            return true;
914
        }
915
916
        // Can't redirect without a url
917
        if (!isset($_SERVER['REQUEST_URI'])) {
918
            return false;
919
        }
920
921
        if ($patterns) {
922
            $matched = false;
923
            $relativeURL = self::makeRelative(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...
924
925
            // protect portions of the site based on the pattern
926
            foreach ($patterns as $pattern) {
927
                if (preg_match($pattern, $relativeURL)) {
928
                    $matched = true;
929
                    break;
930
                }
931
            }
932
            if (!$matched) {
933
                return false;
934
            }
935
        }
936
937
        // if an domain is specified, redirect to that instead of the current domain
938
        if (!$secureDomain) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $secureDomain 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...
939
            $secureDomain = static::host();
940
        }
941
        $url = 'https://' . $secureDomain . $_SERVER['REQUEST_URI'];
942
943
        // Force redirect
944
        self::force_redirect($url);
945
        return true;
946
    }
947
948
    /**
949
     * Force a redirect to a domain starting with "www."
950
     */
951
    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...
952
    {
953
        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...
954
            $destURL = str_replace(
955
                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...
956
                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...
957
                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...
958
            );
959
960
            self::force_redirect($destURL);
961
        }
962
    }
963
964
    /**
965
     * Checks if the current HTTP-Request is an "Ajax-Request" by checking for a custom header set by
966
     * jQuery or whether a manually set request-parameter 'ajax' is present.
967
     *
968
     * @return bool
969
     */
970
    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...
971
    {
972
        if (Controller::has_curr()) {
973
            return Controller::curr()->getRequest()->isAjax();
974
        } else {
975
            return (
976
                isset($_REQUEST['ajax']) ||
977
                (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == "XMLHttpRequest")
978
            );
979
        }
980
    }
981
982
    /**
983
     * Returns true if this script is being run from the command line rather than the web server.
984
     *
985
     * @return bool
986
     */
987
    public static function is_cli()
988
    {
989
        return php_sapi_name() === "cli";
990
    }
991
992
    /**
993
     * Can also be checked with {@link Director::isDev()}, {@link Director::isTest()}, and
994
     * {@link Director::isLive()}.
995
     *
996
     * @return bool
997
     */
998
    public static function get_environment_type()
999
    {
1000
        /** @var Kernel $kernel */
1001
        $kernel = Injector::inst()->get(Kernel::class);
1002
        return $kernel->getEnvironment();
1003
    }
1004
1005
    /**
1006
     * This function will return true if the site is in a live environment. For information about
1007
     * environment types, see {@link Director::set_environment_type()}.
1008
     *
1009
     * @return bool
1010
     */
1011
    public static function isLive()
1012
    {
1013
        return self::get_environment_type() === 'live';
1014
    }
1015
1016
    /**
1017
     * This function will return true if the site is in a development environment. For information about
1018
     * environment types, see {@link Director::set_environment_type()}.
1019
     *
1020
     * @return bool
1021
     */
1022
    public static function isDev()
1023
    {
1024
        return self::get_environment_type() === 'dev';
1025
    }
1026
1027
    /**
1028
     * This function will return true if the site is in a test environment. For information about
1029
     * environment types, see {@link Director::set_environment_type()}.
1030
     *
1031
     * @return bool
1032
     */
1033
    public static function isTest()
1034
    {
1035
        return self::get_environment_type() === 'test';
1036
    }
1037
1038
    /**
1039
     * Returns an array of strings of the method names of methods on the call that should be exposed
1040
     * as global variables in the templates.
1041
     *
1042
     * @return array
1043
     */
1044
    public static function get_template_global_variables()
1045
    {
1046
        return array(
1047
            'absoluteBaseURL',
1048
            'baseURL',
1049
            'is_ajax',
1050
            'isAjax' => 'is_ajax',
1051
            'BaseHref' => 'absoluteBaseURL',    //@deprecated 3.0
1052
        );
1053
    }
1054
}
1055