Passed
Push — 4 ( 3224c9...7533ac )
by Guy
09:03 queued 10s
created

HTTPRequest::hasSession()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace SilverStripe\Control;
4
5
use ArrayAccess;
6
use BadMethodCallException;
7
use InvalidArgumentException;
8
use SilverStripe\Core\ClassInfo;
9
use SilverStripe\ORM\ArrayLib;
10
11
/**
12
 * Represents a HTTP-request, including a URL that is tokenised for parsing, and a request method
13
 * (GET/POST/PUT/DELETE). This is used by {@link RequestHandler} objects to decide what to do.
14
 *
15
 * Caution: objects of this class are immutable, e.g. echo $request['a']; works as expected,
16
 * but $request['a'] = '1'; has no effect.
17
 *
18
 * The intention is that a single HTTPRequest object can be passed from one object to another, each object calling
19
 * match() to get the information that they need out of the URL.  This is generally handled by
20
 * {@link RequestHandler::handleRequest()}.
21
 *
22
 * @todo Accept X_HTTP_METHOD_OVERRIDE http header and $_REQUEST['_method'] to override request types (useful for
23
 *       webclients not supporting PUT and DELETE)
24
 */
25
class HTTPRequest implements ArrayAccess
26
{
27
    /**
28
     * @var string
29
     */
30
    protected $url;
31
32
    /**
33
     * The non-extension parts of the passed URL as an array, originally exploded by the "/" separator.
34
     * All elements of the URL are loaded in here,
35
     * and subsequently popped out of the array by {@link shift()}.
36
     * Only use this structure for internal request handling purposes.
37
     *
38
     * @var array
39
     */
40
    protected $dirParts;
41
42
    /**
43
     * The URL extension (if present)
44
     *
45
     * @var string
46
     */
47
    protected $extension;
48
49
    /**
50
     * The HTTP method in all uppercase: GET/PUT/POST/DELETE/HEAD
51
     *
52
     * @var string
53
     */
54
    protected $httpMethod;
55
56
    /**
57
     * The URL scheme in lowercase: http or https
58
     *
59
     * @var string
60
     */
61
    protected $scheme;
62
63
    /**
64
     * The client IP address
65
     *
66
     * @var string
67
     */
68
    protected $ip;
69
70
    /**
71
     * Contains alls HTTP GET parameters passed into this request.
72
     *
73
     * @var array
74
     */
75
    protected $getVars = array();
76
77
    /**
78
     * Contains alls HTTP POST parameters passed into this request.
79
     *
80
     * @var array
81
     */
82
    protected $postVars = array();
83
84
    /**
85
     * HTTP Headers like "Content-Type: text/xml"
86
     *
87
     * @see http://en.wikipedia.org/wiki/List_of_HTTP_headers
88
     * @var array
89
     */
90
    protected $headers = array();
91
92
    /**
93
     * Raw HTTP body, used by PUT and POST requests.
94
     *
95
     * @var string
96
     */
97
    protected $body;
98
99
    /**
100
     * Contains an associative array of all
101
     * arguments matched in all calls to {@link RequestHandler->handleRequest()}.
102
     * It's a "historical record" that's specific to the current call of
103
     * {@link handleRequest()}, and is only complete once the "last call" to that method is made.
104
     *
105
     * @var array
106
     */
107
    protected $allParams = array();
108
109
    /**
110
     * Contains an associative array of all
111
     * arguments matched in the current call from {@link RequestHandler->handleRequest()},
112
     * as denoted with a "$"-prefix in the $url_handlers definitions.
113
     * Contains different states throughout its lifespan, so just useful
114
     * while processed in {@link RequestHandler} and to get the last
115
     * processes arguments.
116
     *
117
     * @var array
118
     */
119
    protected $latestParams = array();
120
121
    /**
122
     * Contains an associative array of all arguments
123
     * explicitly set in the route table for the current request.
124
     * Useful for passing generic arguments via custom routes.
125
     *
126
     * E.g. The "Locale" parameter would be assigned "en_NZ" below
127
     *
128
     * Director:
129
     *   rules:
130
     *     'en_NZ/$URLSegment!//$Action/$ID/$OtherID':
131
     *       Controller: 'ModelAsController'
132
     *       Locale: 'en_NZ'
133
     *
134
     * @var array
135
     */
136
    protected $routeParams = array();
137
138
    /**
139
     * @var int
140
     */
141
    protected $unshiftedButParsedParts = 0;
142
143
    /**
144
     * @var Session
145
     */
146
    protected $session;
147
148
    /**
149
     * Construct a HTTPRequest from a URL relative to the site root.
150
     *
151
     * @param string $httpMethod
152
     * @param string $url
153
     * @param array $getVars
154
     * @param array $postVars
155
     * @param string $body
156
     */
157
    public function __construct($httpMethod, $url, $getVars = array(), $postVars = array(), $body = null)
158
    {
159
        $this->httpMethod = strtoupper(self::detect_method($httpMethod, $postVars));
160
        $this->setUrl($url);
161
        $this->getVars = (array) $getVars;
162
        $this->postVars = (array) $postVars;
163
        $this->body = $body;
164
        $this->scheme = "http";
165
    }
166
167
    /**
168
     * Allow the setting of a URL
169
     *
170
     * This is here so that RootURLController can change the URL of the request
171
     * without us loosing all the other info attached (like headers)
172
     *
173
     * @param string $url The new URL
174
     * @return HTTPRequest The updated request
175
     */
176
    public function setUrl($url)
177
    {
178
        $this->url = $url;
179
180
        // Normalize URL if its relative (strictly speaking), or has leading slashes
181
        if (Director::is_relative_url($url) || preg_match('/^\//', $url)) {
182
            $this->url = preg_replace(array('/\/+/','/^\//', '/\/$/'), array('/','',''), $this->url);
183
        }
184
        if (preg_match('/^(.*)\.([A-Za-z][A-Za-z0-9]*)$/', $this->url, $matches)) {
185
            $this->url = $matches[1];
186
            $this->extension = $matches[2];
187
        }
188
        if ($this->url) {
189
            $this->dirParts = preg_split('|/+|', $this->url);
0 ignored issues
show
Documentation Bug introduced by
It seems like preg_split('|/+|', $this->url) can also be of type false. However, the property $dirParts is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
190
        } else {
191
            $this->dirParts = array();
192
        }
193
194
        return $this;
195
    }
196
197
    /**
198
     * @return bool
199
     */
200
    public function isGET()
201
    {
202
        return $this->httpMethod == 'GET';
203
    }
204
205
    /**
206
     * @return bool
207
     */
208
    public function isPOST()
209
    {
210
        return $this->httpMethod == 'POST';
211
    }
212
213
    /**
214
     * @return bool
215
     */
216
    public function isPUT()
217
    {
218
        return $this->httpMethod == 'PUT';
219
    }
220
221
    /**
222
     * @return bool
223
     */
224
    public function isDELETE()
225
    {
226
        return $this->httpMethod == 'DELETE';
227
    }
228
229
    /**
230
     * @return bool
231
     */
232
    public function isHEAD()
233
    {
234
        return $this->httpMethod == 'HEAD';
235
    }
236
237
    /**
238
     * @param string $body
239
     * @return HTTPRequest $this
240
     */
241
    public function setBody($body)
242
    {
243
        $this->body = $body;
244
        return $this;
245
    }
246
247
    /**
248
     * @return null|string
249
     */
250
    public function getBody()
251
    {
252
        return $this->body;
253
    }
254
255
    /**
256
     * @return array
257
     */
258
    public function getVars()
259
    {
260
        return $this->getVars;
261
    }
262
263
    /**
264
     * @return array
265
     */
266
    public function postVars()
267
    {
268
        return $this->postVars;
269
    }
270
271
    /**
272
     * Returns all combined HTTP GET and POST parameters
273
     * passed into this request. If a parameter with the same
274
     * name exists in both arrays, the POST value is returned.
275
     *
276
     * @return array
277
     */
278
    public function requestVars()
279
    {
280
        return ArrayLib::array_merge_recursive($this->getVars, $this->postVars);
281
    }
282
283
    /**
284
     * @param string $name
285
     * @return mixed
286
     */
287
    public function getVar($name)
288
    {
289
        if (isset($this->getVars[$name])) {
290
            return $this->getVars[$name];
291
        }
292
        return null;
293
    }
294
295
    /**
296
     * @param string $name
297
     * @return mixed
298
     */
299
    public function postVar($name)
300
    {
301
        if (isset($this->postVars[$name])) {
302
            return $this->postVars[$name];
303
        }
304
        return null;
305
    }
306
307
    /**
308
     * @param string $name
309
     * @return mixed
310
     */
311
    public function requestVar($name)
312
    {
313
        if (isset($this->postVars[$name])) {
314
            return $this->postVars[$name];
315
        }
316
        if (isset($this->getVars[$name])) {
317
            return $this->getVars[$name];
318
        }
319
        return null;
320
    }
321
322
    /**
323
     * Returns a possible file extension found in parsing the URL
324
     * as denoted by a "."-character near the end of the URL.
325
     * Doesn't necessarily have to belong to an existing file,
326
     * as extensions can be also used for content-type-switching.
327
     *
328
     * @return string
329
     */
330
    public function getExtension()
331
    {
332
        return $this->extension;
333
    }
334
335
    /**
336
     * Checks if the {@link HTTPRequest->getExtension()} on this request matches one of the more common media types
337
     * embedded into a webpage - e.g. css, png.
338
     *
339
     * This is useful for things like determining whether to display a fully rendered error page or not. Note that the
340
     * media file types is not at all comprehensive.
341
     *
342
     * @return bool
343
     */
344
    public function isMedia()
345
    {
346
        return in_array($this->getExtension(), array('css', 'js', 'jpg', 'jpeg', 'gif', 'png', 'bmp', 'ico'));
347
    }
348
349
    /**
350
     * Add a HTTP header to the response, replacing any header of the same name.
351
     *
352
     * @param string $header Example: "content-type"
353
     * @param string $value Example: "text/xml"
354
     */
355
    public function addHeader($header, $value)
356
    {
357
        $header = strtolower($header);
358
        $this->headers[$header] = $value;
359
        return $this;
360
    }
361
362
    /**
363
     * @return array
364
     */
365
    public function getHeaders()
366
    {
367
        return $this->headers;
368
    }
369
370
    /**
371
     * Remove an existing HTTP header
372
     *
373
     * @param string $header
374
     * @return mixed
375
     */
376
    public function getHeader($header)
377
    {
378
        $header = strtolower($header);
379
        return (isset($this->headers[$header])) ? $this->headers[$header] : null;
380
    }
381
382
    /**
383
     * Remove an existing HTTP header by its name,
384
     * e.g. "Content-Type".
385
     *
386
     * @param string $header
387
     * @return HTTPRequest $this
388
     */
389
    public function removeHeader($header)
390
    {
391
        $header = strtolower($header);
392
        unset($this->headers[$header]);
393
        return $this;
394
    }
395
396
    /**
397
     * Returns the URL used to generate the page
398
     *
399
     * @param bool $includeGetVars whether or not to include the get parameters\
400
     * @return string
401
     */
402
    public function getURL($includeGetVars = false)
403
    {
404
        $url = ($this->getExtension()) ? $this->url . '.' . $this->getExtension() : $this->url;
405
406
        if ($includeGetVars) {
407
            $vars = $this->getVars();
408
            if (count($vars)) {
409
                $url .= '?' . http_build_query($vars);
410
            }
411
        } elseif (strpos($url, "?") !== false) {
412
            $url = substr($url, 0, strpos($url, "?"));
413
        }
414
415
        return $url;
416
    }
417
418
    /**
419
     * Returns true if this request an ajax request,
420
     * based on custom HTTP ajax added by common JavaScript libraries,
421
     * or based on an explicit "ajax" request parameter.
422
     *
423
     * @return boolean
424
     */
425
    public function isAjax()
426
    {
427
        return (
428
            $this->requestVar('ajax') ||
429
            $this->getHeader('x-requested-with') === "XMLHttpRequest"
430
        );
431
    }
432
433
    /**
434
     * Enables the existence of a key-value pair in the request to be checked using
435
     * array syntax, so isset($request['title']) will check for $_POST['title'] and $_GET['title']
436
     *
437
     * @param string $offset
438
     * @return bool
439
     */
440
    public function offsetExists($offset)
441
    {
442
        return isset($this->postVars[$offset]) || isset($this->getVars[$offset]);
443
    }
444
445
    /**
446
     * Access a request variable using array syntax. eg: $request['title'] instead of $request->postVar('title')
447
     *
448
     * @param string $offset
449
     * @return mixed
450
     */
451
    public function offsetGet($offset)
452
    {
453
        return $this->requestVar($offset);
454
    }
455
456
    public function offsetSet($offset, $value)
457
    {
458
        $this->getVars[$offset] = $value;
459
    }
460
461
    public function offsetUnset($offset)
462
    {
463
        unset($this->getVars[$offset]);
464
        unset($this->postVars[$offset]);
465
    }
466
467
    /**
468
     * Construct an HTTPResponse that will deliver a file to the client.
469
     * Caution: Since it requires $fileData to be passed as binary data (no stream support),
470
     * it's only advisable to send small files through this method.
471
     *
472
     * @static
473
     * @param $fileData
474
     * @param $fileName
475
     * @param null $mimeType
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $mimeType is correct as it would always require null to be passed?
Loading history...
476
     * @return HTTPResponse
477
     */
478
    public static function send_file($fileData, $fileName, $mimeType = null)
479
    {
480
        if (!$mimeType) {
0 ignored issues
show
introduced by
$mimeType is of type null, thus it always evaluated to false.
Loading history...
481
            $mimeType = HTTP::get_mime_type($fileName);
482
        }
483
        $response = new HTTPResponse($fileData);
484
        $response->addHeader("content-type", "$mimeType; name=\"" . addslashes($fileName) . "\"");
485
        // Note a IE-only fix that inspects this header in HTTP::add_cache_headers().
486
        $response->addHeader("content-disposition", "attachment; filename=\"" . addslashes($fileName) . "\"");
487
        $response->addHeader("content-length", strlen($fileData));
488
489
        return $response;
490
    }
491
492
    /**
493
     * Matches a URL pattern
494
     * The pattern can contain a number of segments, separated by / (and an extension indicated by a .)
495
     *
496
     * The parts can be either literals, or, if they start with a $ they are interpreted as variables.
497
     *  - Literals must be provided in order to match
498
     *  - $Variables are optional
499
     *  - However, if you put ! at the end of a variable, then it becomes mandatory.
500
     *
501
     * For example:
502
     *  - admin/crm/list will match admin/crm/$Action/$ID/$OtherID, but it won't match admin/crm/$Action!/$ClassName!
503
     *
504
     * The pattern can optionally start with an HTTP method and a space.  For example, "POST $Controller/$Action".
505
     * This is used to define a rule that only matches on a specific HTTP method.
506
     *
507
     * @param $pattern
508
     * @param bool $shiftOnSuccess
509
     * @return array|bool
510
     */
511
    public function match($pattern, $shiftOnSuccess = false)
512
    {
513
        // Check if a specific method is required
514
        if (preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
515
            $requiredMethod = $matches[1];
516
            if ($requiredMethod != $this->httpMethod) {
517
                return false;
518
            }
519
520
            // If we get this far, we can match the URL pattern as usual.
521
            $pattern = $matches[2];
522
        }
523
524
        // Special case for the root URL controller
525
        if (!$pattern) {
526
            return ($this->dirParts == array()) ? array('Matched' => true) : false;
527
        }
528
529
        // Check for the '//' marker that represents the "shifting point"
530
        $doubleSlashPoint = strpos($pattern, '//');
531
        if ($doubleSlashPoint !== false) {
532
            $shiftCount = substr_count(substr($pattern, 0, $doubleSlashPoint), '/') + 1;
533
            $pattern = str_replace('//', '/', $pattern);
534
            $patternParts = explode('/', $pattern);
535
        } else {
536
            $patternParts = explode('/', $pattern);
537
            $shiftCount = sizeof($patternParts);
538
        }
539
540
        // Filter out any "empty" matching parts - either from an initial / or a trailing /
541
        $patternParts = array_values(array_filter($patternParts));
542
543
        $arguments = array();
544
        foreach ($patternParts as $i => $part) {
545
            $part = trim($part);
546
547
            // Match a variable
548
            if (isset($part[0]) && $part[0] == '$') {
549
                // A variable ending in ! is required
550
                if (substr($part, -1) == '!') {
551
                    $varRequired = true;
552
                    $varName = substr($part, 1, -1);
553
                } else {
554
                    $varRequired = false;
555
                    $varName = substr($part, 1);
556
                }
557
558
                // Fail if a required variable isn't populated
559
                if ($varRequired && !isset($this->dirParts[$i])) {
560
                    return false;
561
                }
562
563
                /** @skipUpgrade */
564
                $key = "Controller";
565
                $arguments[$varName] = isset($this->dirParts[$i]) ? $this->dirParts[$i] : null;
566
                if ($part == '$Controller'
567
                    && (
568
                        !ClassInfo::exists($arguments[$key])
569
                        || !is_subclass_of($arguments[$key], 'SilverStripe\\Control\\Controller')
570
                    )
571
                ) {
572
                    return false;
573
                }
574
575
            // Literal parts with extension
576
            } elseif (isset($this->dirParts[$i]) && $this->dirParts[$i] . '.' . $this->extension == $part) {
577
                continue;
578
579
            // Literal parts must always be there
580
            } elseif (!isset($this->dirParts[$i]) || $this->dirParts[$i] != $part) {
581
                return false;
582
            }
583
        }
584
585
        if ($shiftOnSuccess) {
586
            $this->shift($shiftCount);
587
            // We keep track of pattern parts that we looked at but didn't shift off.
588
            // This lets us say that we have *parsed* the whole URL even when we haven't *shifted* it all
589
            $this->unshiftedButParsedParts = sizeof($patternParts) - $shiftCount;
590
        }
591
592
        $this->latestParams = $arguments;
593
594
        // Load the arguments that actually have a value into $this->allParams
595
        // This ensures that previous values aren't overridden with blanks
596
        foreach ($arguments as $k => $v) {
597
            if ($v || !isset($this->allParams[$k])) {
598
                $this->allParams[$k] = $v;
599
            }
600
        }
601
602
        if ($arguments === array()) {
603
            $arguments['_matched'] = true;
604
        }
605
        return $arguments;
606
    }
607
608
    /**
609
     * @return array
610
     */
611
    public function allParams()
612
    {
613
        return $this->allParams;
614
    }
615
616
    /**
617
     * Shift all the parameter values down a key space, and return the shifted value.
618
     *
619
     * @return string
620
     */
621
    public function shiftAllParams()
622
    {
623
        $keys    = array_keys($this->allParams);
624
        $values  = array_values($this->allParams);
625
        $value   = array_shift($values);
626
627
        // push additional unparsed URL parts onto the parameter stack
628
        if (array_key_exists($this->unshiftedButParsedParts, $this->dirParts)) {
629
            $values[] = $this->dirParts[$this->unshiftedButParsedParts];
630
        }
631
632
        foreach ($keys as $position => $key) {
633
            $this->allParams[$key] = isset($values[$position]) ? $values[$position] : null;
634
        }
635
636
        return $value;
637
    }
638
639
    /**
640
     * @return array
641
     */
642
    public function latestParams()
643
    {
644
        return $this->latestParams;
645
    }
646
647
    /**
648
     * @param string $name
649
     * @return string|null
650
     */
651
    public function latestParam($name)
652
    {
653
        if (isset($this->latestParams[$name])) {
654
            return $this->latestParams[$name];
655
        } else {
656
            return null;
657
        }
658
    }
659
660
    /**
661
     * @return array
662
     */
663
    public function routeParams()
664
    {
665
        return $this->routeParams;
666
    }
667
668
    /**
669
     * @param $params
670
     * @return HTTPRequest $this
671
     */
672
    public function setRouteParams($params)
673
    {
674
        $this->routeParams = $params;
675
        return $this;
676
    }
677
678
    /**
679
     * @return array
680
     */
681
    public function params()
682
    {
683
        return array_merge($this->allParams, $this->routeParams);
684
    }
685
686
    /**
687
     * Finds a named URL parameter (denoted by "$"-prefix in $url_handlers)
688
     * from the full URL, or a parameter specified in the route table
689
     *
690
     * @param string $name
691
     * @return string Value of the URL parameter (if found)
692
     */
693
    public function param($name)
694
    {
695
        $params = $this->params();
696
        if (isset($params[$name])) {
697
            return $params[$name];
698
        } else {
699
            return null;
700
        }
701
    }
702
703
    /**
704
     * Returns the unparsed part of the original URL
705
     * separated by commas. This is used by {@link RequestHandler->handleRequest()}
706
     * to determine if further URL processing is necessary.
707
     *
708
     * @return string Partial URL
709
     */
710
    public function remaining()
711
    {
712
        return implode("/", $this->dirParts);
713
    }
714
715
    /**
716
     * Returns true if this is a URL that will match without shifting off any of the URL.
717
     * This is used by the request handler to prevent infinite parsing loops.
718
     *
719
     * @param string $pattern
720
     * @return bool
721
     */
722
    public function isEmptyPattern($pattern)
723
    {
724
        if (preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
725
            $pattern = $matches[2];
726
        }
727
728
        if (trim($pattern) == "") {
729
            return true;
730
        }
731
        return false;
732
    }
733
734
    /**
735
     * Shift one or more parts off the beginning of the URL.
736
     * If you specify shifting more than 1 item off, then the items will be returned as an array
737
     *
738
     * @param int $count Shift Count
739
     * @return string|array
740
     */
741
    public function shift($count = 1)
742
    {
743
        $return = array();
744
745
        if ($count == 1) {
746
            return array_shift($this->dirParts);
747
        }
748
749
        for ($i=0; $i<$count; $i++) {
750
            $value = array_shift($this->dirParts);
751
752
            if ($value === null) {
753
                break;
754
            }
755
756
            $return[] = $value;
757
        }
758
759
        return $return;
760
    }
761
762
    /**
763
     * Returns true if the URL has been completely parsed.
764
     * This will respect parsed but unshifted directory parts.
765
     *
766
     * @return bool
767
     */
768
    public function allParsed()
769
    {
770
        return sizeof($this->dirParts) <= $this->unshiftedButParsedParts;
771
    }
772
773
    /**
774
     * @return string Return the host from the request
775
     */
776
    public function getHost()
777
    {
778
        return $this->getHeader('host');
779
    }
780
781
    /**
782
     * Returns the client IP address which originated this request.
783
     *
784
     * @return string
785
     */
786
    public function getIP()
787
    {
788
        return $this->ip;
789
    }
790
791
    /**
792
     * Sets the client IP address which originated this request.
793
     * Use setIPFromHeaderValue if assigning from header value.
794
     *
795
     * @param $ip string
796
     * @return $this
797
     */
798
    public function setIP($ip)
799
    {
800
        if (!filter_var($ip, FILTER_VALIDATE_IP)) {
801
            throw new InvalidArgumentException("Invalid ip $ip");
802
        }
803
        $this->ip = $ip;
804
        return $this;
805
    }
806
807
    /**
808
     * Returns all mimetypes from the HTTP "Accept" header
809
     * as an array.
810
     *
811
     * @param boolean $includeQuality Don't strip away optional "quality indicators", e.g. "application/xml;q=0.9"
812
     *                                (Default: false)
813
     * @return array
814
     */
815
    public function getAcceptMimetypes($includeQuality = false)
816
    {
817
        $mimetypes = array();
818
        $mimetypesWithQuality = preg_split('#\s*,\s*#', $this->getHeader('accept'));
819
        foreach ($mimetypesWithQuality as $mimetypeWithQuality) {
820
            $mimetypes[] = ($includeQuality) ? $mimetypeWithQuality : preg_replace('/;.*/', '', $mimetypeWithQuality);
821
        }
822
        return $mimetypes;
823
    }
824
825
    /**
826
     * @return string HTTP method (all uppercase)
827
     */
828
    public function httpMethod()
829
    {
830
        return $this->httpMethod;
831
    }
832
833
    /**
834
     * Return the URL scheme (e.g. "http" or "https").
835
     * Equivalent to PSR-7 getUri()->getScheme()
836
     *
837
     * @return string
838
     */
839
    public function getScheme()
840
    {
841
        return $this->scheme;
842
    }
843
844
    /**
845
     * Set the URL scheme (e.g. "http" or "https").
846
     * Equivalent to PSR-7 getUri()->getScheme(),
847
     *
848
     * @param string $scheme
849
     * @return $this
850
     */
851
    public function setScheme($scheme)
852
    {
853
        $this->scheme = $scheme;
854
        return $this;
855
    }
856
857
    /**
858
     * Gets the "real" HTTP method for a request.
859
     *
860
     * Used to work around browser limitations of form
861
     * submissions to GET and POST, by overriding the HTTP method
862
     * with a POST parameter called "_method" for PUT, DELETE, HEAD.
863
     * Using GET for the "_method" override is not supported,
864
     * as GET should never carry out state changes.
865
     * Alternatively you can use a custom HTTP header 'X-HTTP-Method-Override'
866
     * to override the original method.
867
     * The '_method' POST parameter overrules the custom HTTP header.
868
     *
869
     * @param string $origMethod Original HTTP method from the browser request
870
     * @param array $postVars
871
     * @return string HTTP method (all uppercase)
872
     */
873
    public static function detect_method($origMethod, $postVars)
874
    {
875
        if (isset($postVars['_method'])) {
876
            if (!in_array(strtoupper($postVars['_method']), array('GET','POST','PUT','DELETE','HEAD'))) {
877
                user_error('HTTPRequest::detect_method(): Invalid "_method" parameter', E_USER_ERROR);
878
            }
879
            return strtoupper($postVars['_method']);
880
        } else {
881
            return $origMethod;
882
        }
883
    }
884
885
    /**
886
     * Determines whether the request has a session
887
     *
888
     * @return bool
889
     */
890
    public function hasSession(): bool
891
    {
892
        return !empty($this->session);
893
    }
894
895
    /**
896
     * @return Session
897
     */
898
    public function getSession()
899
    {
900
        if (!$this->hasSession()) {
901
            throw new BadMethodCallException("No session available for this HTTPRequest");
902
        }
903
        return $this->session;
904
    }
905
906
    /**
907
     * @param Session $session
908
     * @return $this
909
     */
910
    public function setSession(Session $session)
911
    {
912
        $this->session = $session;
913
        return $this;
914
    }
915
}
916