Completed
Push — master ( c17796...052b15 )
by Damian
01:29
created

Director::handleRequest()   C

Complexity

Conditions 8
Paths 14

Size

Total Lines 71
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 34
nc 14
nop 1
dl 0
loc 71
rs 6.4391
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\Control;
4
5
use SilverStripe\CMS\Model\SiteTree;
0 ignored issues
show
Bug introduced by
The type SilverStripe\CMS\Model\SiteTree was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use SilverStripe\Control\Middleware\CanonicalURLMiddleware;
7
use SilverStripe\Control\Middleware\HTTPMiddlewareAware;
8
use SilverStripe\Core\Config\Configurable;
9
use SilverStripe\Core\Environment;
10
use SilverStripe\Core\Extensible;
11
use SilverStripe\Core\Injector\Injectable;
12
use SilverStripe\Core\Injector\Injector;
13
use SilverStripe\Core\Kernel;
14
use SilverStripe\Dev\Deprecation;
15
use SilverStripe\Versioned\Versioned;
16
use SilverStripe\View\Requirements;
17
use SilverStripe\View\Requirements_Backend;
18
use SilverStripe\View\TemplateGlobalProvider;
19
20
/**
21
 * Director is responsible for processing URLs, and providing environment information.
22
 *
23
 * The most important part of director is {@link Director::handleRequest()}, which is passed an HTTPRequest and will
24
 * execute the appropriate controller.
25
 *
26
 * @see Director::handleRequest()
27
 * @see Director::$rules
28
 * @skipUpgrade
29
 */
30
class Director implements TemplateGlobalProvider
31
{
32
    use Configurable;
33
    use Extensible;
34
    use Injectable;
35
    use HTTPMiddlewareAware;
36
37
    /**
38
     * Specifies this url is relative to the base.
39
     *
40
     * @var string
41
     */
42
    const BASE = 'BASE';
43
44
    /**
45
     * Specifies this url is relative to the site root.
46
     *
47
     * @var string
48
     */
49
    const ROOT = 'ROOT';
50
51
    /**
52
     * specifies this url is relative to the current request.
53
     *
54
     * @var string
55
     */
56
    const REQUEST = 'REQUEST';
57
58
    /**
59
     * @config
60
     * @var array
61
     */
62
    private static $rules = array();
0 ignored issues
show
introduced by
The private property $rules is not used, and could be removed.
Loading history...
63
64
    /**
65
     * Set current page
66
     *
67
     * @internal
68
     * @var SiteTree
69
     */
70
    private static $current_page;
71
72
    /**
73
     * @config
74
     * @var string
75
     */
76
    private static $alternate_base_folder;
0 ignored issues
show
introduced by
The private property $alternate_base_folder is not used, and could be removed.
Loading history...
77
78
    /**
79
     * Force the base_url to a specific value.
80
     * If assigned, default_base_url and the value in the $_SERVER
81
     * global is ignored.
82
     * Supports back-ticked vars; E.g. '`SS_BASE_URL`'
83
     *
84
     * @config
85
     * @var string
86
     */
87
    private static $alternate_base_url;
0 ignored issues
show
introduced by
The private property $alternate_base_url is not used, and could be removed.
Loading history...
88
89
    /**
90
     * Base url to populate if cannot be determined otherwise.
91
     * Supports back-ticked vars; E.g. '`SS_BASE_URL`'
92
     *
93
     * @config
94
     * @var string
95
     */
96
    private static $default_base_url = '`SS_BASE_URL`';
0 ignored issues
show
introduced by
The private property $default_base_url is not used, and could be removed.
Loading history...
97
98
    public function __construct()
99
    {
100
    }
101
102
    /**
103
     * Test a URL request, returning a response object. This method is a wrapper around
104
     * Director::handleRequest() to assist with functional testing. It will execute the URL given, and
105
     * return the result as an HTTPResponse object.
106
     *
107
     * @param string $url The URL to visit.
108
     * @param array $postVars The $_POST & $_FILES variables.
109
     * @param array|Session $session The {@link Session} object representing the current session.
110
     * By passing the same object to multiple  calls of Director::test(), you can simulate a persisted
111
     * session.
112
     * @param string $httpMethod The HTTP method, such as GET or POST.  It will default to POST if
113
     * postVars is set, GET otherwise. Overwritten by $postVars['_method'] if present.
114
     * @param string $body The HTTP body.
115
     * @param array $headers HTTP headers with key-value pairs.
116
     * @param array|Cookie_Backend $cookies to populate $_COOKIE.
117
     * @param HTTPRequest $request The {@see SS_HTTP_Request} object generated as a part of this request.
118
     *
119
     * @return HTTPResponse
120
     *
121
     * @throws HTTPResponse_Exception
122
     */
123
    public static function test(
124
        $url,
125
        $postVars = [],
126
        $session = array(),
127
        $httpMethod = null,
128
        $body = null,
129
        $headers = array(),
130
        $cookies = array(),
131
        &$request = null
132
    ) {
133
        return static::mockRequest(
134
            function (HTTPRequest $request) {
135
                return Director::singleton()->handleRequest($request);
136
            },
137
            $url,
138
            $postVars,
139
            $session,
140
            $httpMethod,
141
            $body,
142
            $headers,
143
            $cookies,
144
            $request
145
        );
146
    }
147
148
    /**
149
     * Mock a request, passing this to the given callback, before resetting.
150
     *
151
     * @param callable $callback Action to pass the HTTPRequst object
152
     * @param string $url The URL to build
153
     * @param array $postVars The $_POST & $_FILES variables.
154
     * @param array|Session $session The {@link Session} object representing the current session.
155
     * By passing the same object to multiple  calls of Director::test(), you can simulate a persisted
156
     * session.
157
     * @param string $httpMethod The HTTP method, such as GET or POST.  It will default to POST if
158
     * postVars is set, GET otherwise. Overwritten by $postVars['_method'] if present.
159
     * @param string $body The HTTP body.
160
     * @param array $headers HTTP headers with key-value pairs.
161
     * @param array|Cookie_Backend $cookies to populate $_COOKIE.
162
     * @param HTTPRequest $request The {@see SS_HTTP_Request} object generated as a part of this request.
163
     * @return mixed Result of callback
164
     */
165
    public static function mockRequest(
166
        $callback,
167
        $url,
168
        $postVars = [],
169
        $session = [],
170
        $httpMethod = null,
171
        $body = null,
172
        $headers = [],
173
        $cookies = [],
174
        &$request = null
175
    ) {
176
        // Build list of cleanup promises
177
        $finally = [];
178
179
        /** @var Kernel $kernel */
180
        $kernel = Injector::inst()->get(Kernel::class);
181
        $kernel->nest();
182
        $finally[] = function () use ($kernel) {
183
            $kernel->activate();
184
        };
185
186
        // backup existing vars, and create new vars
187
        $existingVars = Environment::getVariables();
188
        $finally[] = function () use ($existingVars) {
189
            Environment::setVariables($existingVars);
190
        };
191
        $newVars = $existingVars;
192
193
        // These are needed so that calling Director::test() does not muck with whoever is calling it.
194
        // Really, it's some inappropriate coupling and should be resolved by making less use of statics.
195
        if (class_exists(Versioned::class)) {
196
            $oldReadingMode = Versioned::get_reading_mode();
197
            $finally[] = function () use ($oldReadingMode) {
198
                Versioned::set_reading_mode($oldReadingMode);
199
            };
200
        }
201
202
        // Default httpMethod
203
        $newVars['_SERVER']['REQUEST_METHOD'] = $httpMethod ?: ($postVars ? "POST" : "GET");
204
        $newVars['_POST'] = (array)$postVars;
205
206
        // Setup session
207
        if ($session instanceof Session) {
208
            // Note: If passing $session as object, ensure that changes are written back
209
            // This is important for classes such as FunctionalTest which emulate cross-request persistence
210
            $newVars['_SESSION'] = $session->getAll();
211
            $finally[] = function () use ($session) {
212
                if (isset($_SESSION)) {
213
                    foreach ($_SESSION as $key => $value) {
214
                        $session->set($key, $value);
215
                    }
216
                }
217
            };
218
        } else {
219
            $newVars['_SESSION'] = $session ?: [];
220
        }
221
222
        // Setup cookies
223
        $cookieJar = $cookies instanceof Cookie_Backend
224
            ? $cookies
225
            : Injector::inst()->createWithArgs(Cookie_Backend::class, array($cookies ?: []));
226
        $newVars['_COOKIE'] = $cookieJar->getAll(false);
227
        Cookie::config()->update('report_errors', false);
228
        Injector::inst()->registerService($cookieJar, Cookie_Backend::class);
229
230
        // Backup requirements
231
        $existingRequirementsBackend = Requirements::backend();
232
        Requirements::set_backend(Requirements_Backend::create());
233
        $finally[] = function () use ($existingRequirementsBackend) {
234
            Requirements::set_backend($existingRequirementsBackend);
235
        };
236
237
        // Strip any hash
238
        $url = strtok($url, '#');
239
240
        // Handle absolute URLs
241
        if (parse_url($url, PHP_URL_HOST)) {
242
            $bits = parse_url($url);
243
244
            // If a port is mentioned in the absolute URL, be sure to add that into the HTTP host
245
            $newVars['_SERVER']['HTTP_HOST'] = isset($bits['port'])
246
                ? $bits['host'] . ':' . $bits['port']
247
                : $bits['host'];
248
        }
249
250
        // Ensure URL is properly made relative.
251
        // Example: url passed is "/ss31/my-page" (prefixed with BASE_URL), this should be changed to "my-page"
252
        $url = self::makeRelative($url);
253
        if (strpos($url, '?') !== false) {
254
            list($url, $getVarsEncoded) = explode('?', $url, 2);
255
            parse_str($getVarsEncoded, $newVars['_GET']);
256
        } else {
257
            $newVars['_GET'] = [];
258
        }
259
        $newVars['_SERVER']['REQUEST_URI'] = Director::baseURL() . ltrim($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...
260
        $newVars['_REQUEST'] = array_merge($newVars['_GET'], $newVars['_POST']);
261
262
        // Normalise vars
263
        $newVars = HTTPRequestBuilder::cleanEnvironment($newVars);
264
265
        // Create new request
266
        $request = HTTPRequestBuilder::createFromVariables($newVars, $body, ltrim($url, '/'));
267
        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...
268
            foreach ($headers as $k => $v) {
269
                $request->addHeader($k, $v);
270
            }
271
        }
272
273
        // Apply new vars to environment
274
        Environment::setVariables($newVars);
275
276
        try {
277
            // Normal request handling
278
            return call_user_func($callback, $request);
279
        } finally {
280
            // Restore state in reverse order to assignment
281
            foreach (array_reverse($finally) as $callback) {
282
                call_user_func($callback);
283
            }
284
        }
285
    }
286
287
    /**
288
     * Process the given URL, creating the appropriate controller and executing it.
289
     *
290
     * Request processing is handled as follows:
291
     * - Director::handleRequest($request) checks each of the Director rules and identifies a controller
292
     *   to handle this request.
293
     * - Controller::handleRequest($request) is then called.  This will find a rule to handle the URL,
294
     *   and call the rule handling method.
295
     * - RequestHandler::handleRequest($request) is recursively called whenever a rule handling method
296
     *   returns a RequestHandler object.
297
     *
298
     * In addition to request processing, Director will manage the session, and perform the output of
299
     * the actual response to the browser.
300
     *
301
     * @param HTTPRequest $request
302
     * @return HTTPResponse
303
     * @throws HTTPResponse_Exception
304
     */
305
    public function handleRequest(HTTPRequest $request)
306
    {
307
        Injector::inst()->registerService($request, HTTPRequest::class);
308
309
        $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...
310
311
        $this->extend('updateRules', $rules);
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
            // Match pattern
320
            $arguments = $request->match($pattern, true);
321
            if ($arguments == false) {
322
                continue;
323
            }
324
325
            // Normalise route rule
326
            if (is_string($controllerOptions)) {
327
                if (substr($controllerOptions, 0, 2) == '->') {
328
                    $controllerOptions = array('Redirect' => substr($controllerOptions, 2));
329
                } else {
330
                    $controllerOptions = array('Controller' => $controllerOptions);
331
                }
332
            }
333
            $request->setRouteParams($controllerOptions);
334
335
            // controllerOptions provide some default arguments
336
            $arguments = array_merge($controllerOptions, $arguments);
0 ignored issues
show
Bug introduced by
It seems like $arguments can also be of type true; however, parameter $array2 of array_merge() does only seem to accept null|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

336
            $arguments = array_merge($controllerOptions, /** @scrutinizer ignore-type */ $arguments);
Loading history...
337
338
            // Pop additional tokens from the tokenizer if necessary
339
            if (isset($controllerOptions['_PopTokeniser'])) {
340
                $request->shift($controllerOptions['_PopTokeniser']);
341
            }
342
343
            // Handler for redirection
344
            if (isset($arguments['Redirect'])) {
345
                $handler = function () use ($arguments) {
346
                    // Redirection
347
                    $response = new HTTPResponse();
348
                    $response->redirect(static::absoluteURL($arguments['Redirect']));
349
                    return $response;
350
                };
351
                break;
352
            }
353
354
            /** @var RequestHandler $controllerObj */
355
            $controllerObj = Injector::inst()->create($arguments['Controller']);
356
357
            // Handler for calling a controller
358
            $handler = function (HTTPRequest $request) use ($controllerObj) {
359
                try {
360
                    return $controllerObj->handleRequest($request);
361
                } catch (HTTPResponse_Exception $responseException) {
362
                    return $responseException->getResponse();
363
                }
364
            };
365
            break;
366
        }
367
368
        // Call the handler with the configured middlewares
369
        $response = $this->callMiddleware($request, $handler);
370
371
        // Note that if a different request was previously registered, this will now be lost
372
        // In these cases it's better to use Kernel::nest() prior to kicking off a nested request
373
        Injector::inst()->unregisterNamedObject(HTTPRequest::class);
374
375
        return $response;
376
    }
377
378
    /**
379
     * Return the {@link SiteTree} object that is currently being viewed. If there is no SiteTree
380
     * object to return, then this will return the current controller.
381
     *
382
     * @return SiteTree|Controller
383
     */
384
    public static function get_current_page()
385
    {
386
        return self::$current_page ? self::$current_page : Controller::curr();
387
    }
388
389
    /**
390
     * Set the currently active {@link SiteTree} object that is being used to respond to the request.
391
     *
392
     * @param SiteTree $page
393
     */
394
    public static function set_current_page($page)
395
    {
396
        self::$current_page = $page;
397
    }
398
399
    /**
400
     * Turns the given URL into an absolute URL. By default non-site root relative urls will be
401
     * evaluated relative to the current base_url.
402
     *
403
     * @param string $url URL To transform to absolute.
404
     * @param string $relativeParent Method to use for evaluating relative urls.
405
     * Either one of BASE (baseurl), ROOT (site root), or REQUEST (requested page).
406
     * Defaults to BASE, which is the same behaviour as template url resolution.
407
     * Ignored if the url is absolute or site root.
408
     *
409
     * @return string
410
     */
411
    public static function absoluteURL($url, $relativeParent = self::BASE)
412
    {
413
        if (is_bool($relativeParent)) {
414
            // Deprecate old boolean second parameter
415
            Deprecation::notice('5.0', 'Director::absoluteURL takes an explicit parent for relative url');
416
            $relativeParent = $relativeParent ? self::BASE : self::REQUEST;
417
        }
418
419
        // Check if there is already a protocol given
420
        if (preg_match('/^http(s?):\/\//', $url)) {
421
            return $url;
422
        }
423
424
        // Absolute urls without protocol are added
425
        // E.g. //google.com -> http://google.com
426
        if (strpos($url, '//') === 0) {
427
            return self::protocol() . substr($url, 2);
428
        }
429
430
        // Determine method for mapping the parent to this relative url
431
        if ($relativeParent === self::ROOT || self::is_root_relative_url($url)) {
432
            // Root relative urls always should be evaluated relative to the root
433
            $parent = self::protocolAndHost();
434
        } elseif ($relativeParent === self::REQUEST) {
435
            // Request relative urls rely on the REQUEST_URI param (old default behaviour)
436
            if (!isset($_SERVER['REQUEST_URI'])) {
437
                return false;
438
            }
439
            $parent = dirname($_SERVER['REQUEST_URI'] . 'x');
440
        } else {
441
            // Default to respecting site base_url
442
            $parent = self::absoluteBaseURL();
443
        }
444
445
        // Map empty urls to relative slash and join to base
446
        if (empty($url) || $url === '.' || $url === './') {
447
            $url = '/';
448
        }
449
        return Controller::join_links($parent, $url);
450
    }
451
452
    /**
453
     * A helper to determine the current hostname used to access the site.
454
     * The following are used to determine the host (in order)
455
     *  - Director.alternate_base_url (if it contains a domain name)
456
     *  - Trusted proxy headers
457
     *  - HTTP Host header
458
     *  - SS_BASE_URL env var
459
     *  - SERVER_NAME
460
     *  - gethostname()
461
     *
462
     * @param HTTPRequest $request
463
     * @return string
464
     */
465
    public static function host(HTTPRequest $request = null)
466
    {
467
        // Check if overridden by alternate_base_url
468
        if ($baseURL = self::config()->get('alternate_base_url')) {
469
            $baseURL = Injector::inst()->convertServiceProperty($baseURL);
470
            $host = parse_url($baseURL, PHP_URL_HOST);
471
            if ($host) {
472
                return $host;
473
            }
474
        }
475
476
        $request = static::currentRequest($request);
477
        if ($request && ($host = $request->getHeader('Host'))) {
478
            return $host;
479
        }
480
481
        // Check given header
482
        if (isset($_SERVER['HTTP_HOST'])) {
483
            return $_SERVER['HTTP_HOST'];
484
        }
485
486
        // Check base url
487
        if ($baseURL = self::config()->uninherited('default_base_url')) {
488
            $baseURL = Injector::inst()->convertServiceProperty($baseURL);
489
            $host = parse_url($baseURL, PHP_URL_HOST);
490
            if ($host) {
491
                return $host;
492
            }
493
        }
494
495
        // Fail over to server_name (least reliable)
496
        return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : gethostname();
497
    }
498
499
    /**
500
     * Returns the domain part of the URL 'http://www.mysite.com'. Returns FALSE is this environment
501
     * variable isn't set.
502
     *
503
     * @param HTTPRequest $request
504
     * @return bool|string
505
     */
506
    public static function protocolAndHost(HTTPRequest $request = null)
507
    {
508
        return static::protocol($request) . static::host($request);
509
    }
510
511
    /**
512
     * Return the current protocol that the site is running under.
513
     *
514
     * @param HTTPRequest $request
515
     * @return string
516
     */
517
    public static function protocol(HTTPRequest $request = null)
518
    {
519
        return (self::is_https($request)) ? 'https://' : 'http://';
520
    }
521
522
    /**
523
     * Return whether the site is running as under HTTPS.
524
     *
525
     * @param HTTPRequest $request
526
     * @return bool
527
     */
528
    public static function is_https(HTTPRequest $request = null)
529
    {
530
        // Check override from alternate_base_url
531
        if ($baseURL = self::config()->uninherited('alternate_base_url')) {
532
            $baseURL = Injector::inst()->convertServiceProperty($baseURL);
533
            $protocol = parse_url($baseURL, PHP_URL_SCHEME);
534
            if ($protocol) {
535
                return $protocol === 'https';
536
            }
537
        }
538
539
        // Check the current request
540
        $request = static::currentRequest($request);
541
        if ($request && ($scheme = $request->getScheme())) {
542
            return $scheme === 'https';
543
        }
544
545
        // Check default_base_url
546
        if ($baseURL = self::config()->uninherited('default_base_url')) {
547
            $baseURL = Injector::inst()->convertServiceProperty($baseURL);
548
            $protocol = parse_url($baseURL, PHP_URL_SCHEME);
549
            if ($protocol) {
550
                return $protocol === 'https';
551
            }
552
        }
553
554
        return false;
555
    }
556
557
    /**
558
     * Return the root-relative url for the baseurl
559
     *
560
     * @return string Root-relative url with trailing slash.
561
     */
562
    public static function baseURL()
563
    {
564
        // Check override base_url
565
        $alternate = self::config()->get('alternate_base_url');
566
        if ($alternate) {
567
            $alternate = Injector::inst()->convertServiceProperty($alternate);
568
            return rtrim(parse_url($alternate, PHP_URL_PATH), '/') . '/';
569
        }
570
571
        // Get env base url
572
        $baseURL = rtrim(BASE_URL, '/') . '/';
573
574
        // Check if BASE_SCRIPT_URL is defined
575
        // e.g. `index.php/`
576
        if (defined('BASE_SCRIPT_URL')) {
577
            return $baseURL . BASE_SCRIPT_URL;
0 ignored issues
show
Bug introduced by
The constant SilverStripe\Control\BASE_SCRIPT_URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
578
        }
579
580
        return $baseURL;
581
    }
582
583
    /**
584
     * Returns the root filesystem folder for the site. It will be automatically calculated unless
585
     * it is overridden with {@link setBaseFolder()}.
586
     *
587
     * @return string
588
     */
589
    public static function baseFolder()
590
    {
591
        $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...
592
        return ($alternate) ? $alternate : BASE_PATH;
593
    }
594
595
    /**
596
     * Turns an absolute URL or folder into one that's relative to the root of the site. This is useful
597
     * when turning a URL into a filesystem reference, or vice versa.
598
     *
599
     * Note: You should check {@link Director::is_site_url()} if making an untrusted url relative prior
600
     * to calling this function.
601
     *
602
     * @param string $url Accepts both a URL or a filesystem path.
603
     * @return string
604
     */
605
    public static function makeRelative($url)
606
    {
607
        // Allow for the accidental inclusion whitespace and // in the URL
608
        $url = preg_replace('#([^:])//#', '\\1/', trim($url));
609
610
        // If using a real url, remove protocol / hostname / auth / port
611
        if (preg_match('#^(?<protocol>https?:)?//(?<hostpart>[^/]*)(?<url>(/.*)?)$#i', $url, $matches)) {
612
            $url = $matches['url'];
613
        }
614
615
        // Empty case
616
        if (trim($url, '\\/') === '') {
617
            return '';
618
        }
619
620
        // Remove base folder or url
621
        foreach ([self::baseFolder(), self::baseURL()] as $base) {
622
            // Ensure single / doesn't break comparison (unless it would make base empty)
623
            $base = rtrim($base, '\\/') ?: $base;
624
            if (stripos($url, $base) === 0) {
625
                return ltrim(substr($url, strlen($base)), '\\/');
626
            }
627
        }
628
629
        // Nothing matched, fall back to returning the original URL
630
        return $url;
631
    }
632
633
    /**
634
     * Returns true if a given path is absolute. Works under both *nix and windows systems.
635
     *
636
     * @param string $path
637
     *
638
     * @return bool
639
     */
640
    public static function is_absolute($path)
641
    {
642
        if (empty($path)) {
643
            return false;
644
        }
645
        if ($path[0] == '/' || $path[0] == '\\') {
646
            return true;
647
        }
648
        return preg_match('/^[a-zA-Z]:[\\\\\/]/', $path) == 1;
649
    }
650
651
    /**
652
     * Determine if the url is root relative (i.e. starts with /, but not with //) SilverStripe
653
     * considers root relative urls as a subset of relative urls.
654
     *
655
     * @param string $url
656
     *
657
     * @return bool
658
     */
659
    public static function is_root_relative_url($url)
660
    {
661
        return strpos($url, '/') === 0 && strpos($url, '//') !== 0;
662
    }
663
664
    /**
665
     * Checks if a given URL is absolute (e.g. starts with 'http://' etc.). URLs beginning with "//"
666
     * are treated as absolute, as browsers take this to mean the same protocol as currently being used.
667
     *
668
     * Useful to check before redirecting based on a URL from user submissions through $_GET or $_POST,
669
     * and avoid phishing attacks by redirecting to an attackers server.
670
     *
671
     * Note: Can't solely rely on PHP's parse_url() , since it is not intended to work with relative URLs
672
     * or for security purposes. filter_var($url, FILTER_VALIDATE_URL) has similar problems.
673
     *
674
     * @param string $url
675
     *
676
     * @return bool
677
     */
678
    public static function is_absolute_url($url)
679
    {
680
        // Strip off the query and fragment parts of the URL before checking
681
        if (($queryPosition = strpos($url, '?')) !== false) {
682
            $url = substr($url, 0, $queryPosition - 1);
683
        }
684
        if (($hashPosition = strpos($url, '#')) !== false) {
685
            $url = substr($url, 0, $hashPosition - 1);
686
        }
687
        $colonPosition = strpos($url, ':');
688
        $slashPosition = strpos($url, '/');
689
        return (
690
            // Base check for existence of a host on a compliant URL
691
            parse_url($url, PHP_URL_HOST)
692
            // Check for more than one leading slash without a protocol.
693
            // While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers,
694
            // and hence a potential security risk. Single leading slashes are not an issue though.
695
            || preg_match('%^\s*/{2,}%', $url)
696
            || (
697
                // If a colon is found, check if it's part of a valid scheme definition
698
                // (meaning its not preceded by a slash).
699
                $colonPosition !== false
700
                && ($slashPosition === false || $colonPosition < $slashPosition)
701
            )
702
        );
703
    }
704
705
    /**
706
     * Checks if a given URL is relative (or root relative) by checking {@link is_absolute_url()}.
707
     *
708
     * @param string $url
709
     *
710
     * @return bool
711
     */
712
    public static function is_relative_url($url)
713
    {
714
        return !static::is_absolute_url($url);
715
    }
716
717
    /**
718
     * Checks if the given URL is belonging to this "site" (not an external link). That's the case if
719
     * the URL is relative, as defined by {@link is_relative_url()}, or if the host matches
720
     * {@link protocolAndHost()}.
721
     *
722
     * Useful to check before redirecting based on a URL from user submissions through $_GET or $_POST,
723
     * and avoid phishing attacks by redirecting to an attackers server.
724
     *
725
     * @param string $url
726
     *
727
     * @return bool
728
     */
729
    public static function is_site_url($url)
730
    {
731
        $urlHost = parse_url($url, PHP_URL_HOST);
732
        $actualHost = parse_url(self::protocolAndHost(), PHP_URL_HOST);
733
        if ($urlHost && $actualHost && $urlHost == $actualHost) {
734
            return true;
735
        } else {
736
            return self::is_relative_url($url);
737
        }
738
    }
739
740
    /**
741
     * Given a filesystem reference relative to the site root, return the full file-system path.
742
     *
743
     * @param string $file
744
     *
745
     * @return string
746
     */
747
    public static function getAbsFile($file)
748
    {
749
        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...
750
    }
751
752
    /**
753
     * Returns true if the given file exists. Filename should be relative to the site root.
754
     *
755
     * @param $file
756
     *
757
     * @return bool
758
     */
759
    public static function fileExists($file)
760
    {
761
        // replace any appended query-strings, e.g. /path/to/foo.php?bar=1 to /path/to/foo.php
762
        $file = preg_replace('/([^\?]*)?.*/', '$1', $file);
763
        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...
764
    }
765
766
    /**
767
     * Returns the Absolute URL of the site root.
768
     *
769
     * @return string
770
     */
771
    public static function absoluteBaseURL()
772
    {
773
        return self::absoluteURL(
774
            self::baseURL(),
775
            self::ROOT
776
        );
777
    }
778
779
    /**
780
     * Returns the Absolute URL of the site root, embedding the current basic-auth credentials into
781
     * the URL.
782
     *
783
     * @param HTTPRequest|null $request
784
     * @return string
785
     */
786
    public static function absoluteBaseURLWithAuth(HTTPRequest $request = null)
787
    {
788
        $login = "";
789
790
        if (isset($_SERVER['PHP_AUTH_USER'])) {
791
            $login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@";
792
        }
793
794
        return Director::protocol($request) . $login . static::host($request) . 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...
795
    }
796
797
    /**
798
     * Skip any further processing and immediately respond with a redirect to the passed URL.
799
     *
800
     * @param string $destURL
801
     * @throws HTTPResponse_Exception
802
     */
803
    protected static function force_redirect($destURL)
804
    {
805
        // Redirect to installer
806
        $response = new HTTPResponse();
807
        $response->redirect($destURL, 301);
808
        HTTP::add_cache_headers($response);
809
        throw new HTTPResponse_Exception($response);
810
    }
811
812
    /**
813
     * Force the site to run on SSL.
814
     *
815
     * To use, call from the init() method of your PageController. For example:
816
     * <code>
817
     * if (Director::isLive()) Director::forceSSL();
818
     * </code>
819
     *
820
     * If you don't want your entire site to be on SSL, you can pass an array of PCRE regular expression
821
     * patterns for matching relative URLs. For example:
822
     * <code>
823
     * if (Director::isLive()) Director::forceSSL(array('/^admin/', '/^Security/'));
824
     * </code>
825
     *
826
     * If you want certain parts of your site protected under a different domain, you can specify
827
     * the domain as an argument:
828
     * <code>
829
     * if (Director::isLive()) Director::forceSSL(array('/^admin/', '/^Security/'), 'secure.mysite.com');
830
     * </code>
831
     *
832
     * Note that the session data will be lost when moving from HTTP to HTTPS. It is your responsibility
833
     * to ensure that this won't cause usability problems.
834
     *
835
     * CAUTION: This does not respect the site environment mode. You should check this
836
     * as per the above examples using Director::isLive() or Director::isTest() for example.
837
     *
838
     * @param array $patterns Array of regex patterns to match URLs that should be HTTPS.
839
     * @param string $secureDomain Secure domain to redirect to. Defaults to the current domain.
840
     * @param HTTPRequest|null $request Request object to check
841
     */
842
    public static function forceSSL($patterns = null, $secureDomain = null, HTTPRequest $request = null)
843
    {
844
        $handler = CanonicalURLMiddleware::singleton()->setForceSSL(true);
845
        if ($patterns) {
846
            $handler->setForceSSLPatterns($patterns);
847
        }
848
        if ($secureDomain) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $secureDomain of type null|string 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...
849
            $handler->setForceSSLDomain($secureDomain);
850
        }
851
        $handler->throwRedirectIfNeeded($request);
852
    }
853
854
    /**
855
     * Force a redirect to a domain starting with "www."
856
     *
857
     * @param HTTPRequest $request
858
     */
859
    public static function forceWWW(HTTPRequest $request = null)
860
    {
861
        $handler = CanonicalURLMiddleware::singleton()->setForceWWW(true);
862
        $handler->throwRedirectIfNeeded($request);
863
    }
864
865
    /**
866
     * Checks if the current HTTP-Request is an "Ajax-Request" by checking for a custom header set by
867
     * jQuery or whether a manually set request-parameter 'ajax' is present.
868
     *
869
     * @param HTTPRequest $request
870
     * @return bool
871
     */
872
    public static function is_ajax(HTTPRequest $request = null)
873
    {
874
        $request = self::currentRequest($request);
875
        if ($request) {
876
            return $request->isAjax();
877
        } else {
878
            return (
879
                isset($_REQUEST['ajax']) ||
880
                (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == "XMLHttpRequest")
881
            );
882
        }
883
    }
884
885
    /**
886
     * Returns true if this script is being run from the command line rather than the web server.
887
     *
888
     * @return bool
889
     */
890
    public static function is_cli()
891
    {
892
        return in_array(php_sapi_name(), ['cli', 'phpdbg']);
893
    }
894
895
    /**
896
     * Can also be checked with {@link Director::isDev()}, {@link Director::isTest()}, and
897
     * {@link Director::isLive()}.
898
     *
899
     * @return string
900
     */
901
    public static function get_environment_type()
902
    {
903
        /** @var Kernel $kernel */
904
        $kernel = Injector::inst()->get(Kernel::class);
905
        return $kernel->getEnvironment();
906
    }
907
908
    /**
909
     * This function will return true if the site is in a live environment. For information about
910
     * environment types, see {@link Director::set_environment_type()}.
911
     *
912
     * @return bool
913
     */
914
    public static function isLive()
915
    {
916
        return self::get_environment_type() === 'live';
917
    }
918
919
    /**
920
     * This function will return true if the site is in a development environment. For information about
921
     * environment types, see {@link Director::set_environment_type()}.
922
     *
923
     * @return bool
924
     */
925
    public static function isDev()
926
    {
927
        return self::get_environment_type() === 'dev';
928
    }
929
930
    /**
931
     * This function will return true if the site is in a test environment. For information about
932
     * environment types, see {@link Director::set_environment_type()}.
933
     *
934
     * @return bool
935
     */
936
    public static function isTest()
937
    {
938
        return self::get_environment_type() === 'test';
939
    }
940
941
    /**
942
     * Returns an array of strings of the method names of methods on the call that should be exposed
943
     * as global variables in the templates.
944
     *
945
     * @return array
946
     */
947
    public static function get_template_global_variables()
948
    {
949
        return array(
950
            'absoluteBaseURL',
951
            'baseURL',
952
            'is_ajax',
953
            'isAjax' => 'is_ajax',
954
            'BaseHref' => 'absoluteBaseURL',    //@deprecated 3.0
955
        );
956
    }
957
958
    /**
959
     * Helper to validate or check the current request object
960
     *
961
     * @param HTTPRequest $request
962
     * @return HTTPRequest Request object if one is both current and valid
963
     */
964
    protected static function currentRequest(HTTPRequest $request = null)
965
    {
966
        // Ensure we only use a registered HTTPRequest and don't
967
        // incidentally construct a singleton
968
        if (!$request && Injector::inst()->has(HTTPRequest::class)) {
969
            $request = Injector::inst()->get(HTTPRequest::class);
970
        }
971
        return $request;
972
    }
973
}
974