Completed
Push — namespace-model ( 32fe71...476d0e )
by Sam
16:05
created

HTTPRequest::getExtension()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace SilverStripe\Control;
4
use ArrayLib;
5
6
use ClassInfo;
7
use ArrayAccess;
8
use SilverStripe\Control\HTTPResponse;
9
10
11
12
/**
13
 * Represents a HTTP-request, including a URL that is tokenised for parsing, and a request method
14
 * (GET/POST/PUT/DELETE). This is used by {@link RequestHandler} objects to decide what to do.
15
 *
16
 * Caution: objects of this class are immutable, e.g. echo $request['a']; works as expected,
17
 * but $request['a'] = '1'; has no effect.
18
 *
19
 * The intention is that a single SS_HTTPRequest object can be passed from one object to another, each object calling
20
 * match() to get the information that they need out of the URL.  This is generally handled by
21
 * {@link RequestHandler::handleRequest()}.
22
 *
23
 * @todo Accept X_HTTP_METHOD_OVERRIDE http header and $_REQUEST['_method'] to override request types (useful for
24
 *       webclients not supporting PUT and DELETE)
25
 *
26
 * @package framework
27
 * @subpackage control
28
 */
29
class HTTPRequest implements ArrayAccess {
30
31
	/**
32
	 * @var string $url
33
	 */
34
	protected $url;
35
36
	/**
37
	 * The non-extension parts of the passed URL as an array, originally exploded by the "/" separator.
38
	 * All elements of the URL are loaded in here,
39
	 * and subsequently popped out of the array by {@link shift()}.
40
	 * Only use this structure for internal request handling purposes.
41
	 */
42
	protected $dirParts;
43
44
	/**
45
	 * @var string $extension The URL extension (if present)
46
	 */
47
	protected $extension;
48
49
	/**
50
	 * @var string $httpMethod The HTTP method in all uppercase: GET/PUT/POST/DELETE/HEAD
51
	 */
52
	protected $httpMethod;
53
54
	/**
55
	 * @var array $getVars Contains alls HTTP GET parameters passed into this request.
56
	 */
57
	protected $getVars = array();
58
59
	/**
60
	 * @var array $postVars Contains alls HTTP POST parameters passed into this request.
61
	 */
62
	protected $postVars = array();
63
64
	/**
65
	 * HTTP Headers like "Content-Type: text/xml"
66
	 *
67
	 * @see http://en.wikipedia.org/wiki/List_of_HTTP_headers
68
	 * @var array
69
	 */
70
	protected $headers = array();
71
72
	/**
73
	 * Raw HTTP body, used by PUT and POST requests.
74
	 *
75
	 * @var string
76
	 */
77
	protected $body;
78
79
	/**
80
	 * @var array $allParams Contains an associative array of all
81
	 * arguments matched in all calls to {@link RequestHandler->handleRequest()}.
82
	 * It's a "historical record" that's specific to the current call of
83
	 * {@link handleRequest()}, and is only complete once the "last call" to that method is made.
84
	 */
85
	protected $allParams = array();
86
87
	/**
88
	 * @var array $latestParams Contains an associative array of all
89
	 * arguments matched in the current call from {@link RequestHandler->handleRequest()},
90
	 * as denoted with a "$"-prefix in the $url_handlers definitions.
91
	 * Contains different states throughout its lifespan, so just useful
92
	 * while processed in {@link RequestHandler} and to get the last
93
	 * processes arguments.
94
	 */
95
	protected $latestParams = array();
96
97
	/**
98
	 * @var array $routeParams Contains an associative array of all arguments
99
	 * explicitly set in the route table for the current request.
100
	 * Useful for passing generic arguments via custom routes.
101
	 *
102
	 * E.g. The "Locale" parameter would be assigned "en_NZ" below
103
	 *
104
	 * Director:
105
	 *   rules:
106
	 *     'en_NZ/$URLSegment!//$Action/$ID/$OtherID':
107
	 *       Controller: 'ModelAsController'
108
	 *       Locale: 'en_NZ'
109
	 */
110
	protected $routeParams = array();
111
112
	protected $unshiftedButParsedParts = 0;
113
114
	/**
115
	 * Construct a SS_HTTPRequest from a URL relative to the site root.
116
	 */
117
	public function __construct($httpMethod, $url, $getVars = array(), $postVars = array(), $body = null) {
118
		$this->httpMethod = strtoupper(self::detect_method($httpMethod, $postVars));
119
		$this->setUrl($url);
120
121
		$this->getVars = (array) $getVars;
122
		$this->postVars = (array) $postVars;
123
		$this->body = $body;
124
	}
125
126
	/**
127
	 * Allow the setting of a URL
128
	 *
129
	 * This is here so that RootURLController can change the URL of the request
130
	 * without us loosing all the other info attached (like headers)
131
	 *
132
	 * @param string The new URL
133
	 *
134
	 * @return SS_HTTPRequest The updated request
135
	 */
136
	public function setUrl($url) {
137
		$this->url = $url;
138
139
		// Normalize URL if its relative (strictly speaking), or has leading slashes
140
		if(Director::is_relative_url($url) || preg_match('/^\//', $url)) {
141
			$this->url = preg_replace(array('/\/+/','/^\//', '/\/$/'),array('/','',''), $this->url);
142
		}
143
		if(preg_match('/^(.*)\.([A-Za-z][A-Za-z0-9]*)$/', $this->url, $matches)) {
144
			$this->url = $matches[1];
145
			$this->extension = $matches[2];
146
		}
147
		if($this->url) $this->dirParts = preg_split('|/+|', $this->url);
148
		else $this->dirParts = array();
149
150
		return $this;
151
	}
152
153
	/**
154
	 * @return bool
155
	 */
156
	public function isGET() {
157
		return $this->httpMethod == 'GET';
158
	}
159
160
	/**
161
	 * @return bool
162
	 */
163
	public function isPOST() {
164
		return $this->httpMethod == 'POST';
165
	}
166
167
	/**
168
	 * @return bool
169
	 */
170
	public function isPUT() {
171
		return $this->httpMethod == 'PUT';
172
	}
173
174
	/**
175
	 * @return bool
176
	 */
177
	public function isDELETE() {
178
		return $this->httpMethod == 'DELETE';
179
	}
180
181
	/**
182
	 * @return bool
183
	 */
184
	public function isHEAD() {
185
		return $this->httpMethod == 'HEAD';
186
	}
187
188
	/**
189
	 * @param string $body
190
	 * @return SS_HTTPRequest $this
191
	 */
192
	public function setBody($body) {
193
		$this->body = $body;
194
		return $this;
195
	}
196
197
	/**
198
	 * @return null|string
199
	 */
200
	public function getBody() {
201
		return $this->body;
202
	}
203
204
	/**
205
	 * @return array
206
	 */
207
	public function getVars() {
208
		return $this->getVars;
209
	}
210
211
	/**
212
	 * @return array
213
	 */
214
	public function postVars() {
215
		return $this->postVars;
216
	}
217
218
	/**
219
	 * Returns all combined HTTP GET and POST parameters
220
	 * passed into this request. If a parameter with the same
221
	 * name exists in both arrays, the POST value is returned.
222
	 *
223
	 * @return array
224
	 */
225
	public function requestVars() {
226
		return ArrayLib::array_merge_recursive($this->getVars, $this->postVars);
227
	}
228
229
	/**
230
	 * @param string $name
231
	 * @return mixed
232
	 */
233
	public function getVar($name) {
234
		if(isset($this->getVars[$name])) return $this->getVars[$name];
235
	}
236
237
	/**
238
	 * @param string $name
239
	 * @return mixed
240
	 */
241
	public function postVar($name) {
242
		if(isset($this->postVars[$name])) return $this->postVars[$name];
243
	}
244
245
	/**
246
	 * @param string $name
247
	 * @return mixed
248
	 */
249
	public function requestVar($name) {
250
		if(isset($this->postVars[$name])) return $this->postVars[$name];
251
		if(isset($this->getVars[$name])) return $this->getVars[$name];
252
	}
253
254
	/**
255
	 * Returns a possible file extension found in parsing the URL
256
	 * as denoted by a "."-character near the end of the URL.
257
	 * Doesn't necessarily have to belong to an existing file,
258
	 * as extensions can be also used for content-type-switching.
259
	 *
260
	 * @return string
261
	 */
262
	public function getExtension() {
263
		return $this->extension;
264
	}
265
266
	/**
267
	 * Checks if the {@link SS_HTTPRequest->getExtension()} on this request matches one of the more common media types
268
	 * embedded into a webpage - e.g. css, png.
269
	 *
270
	 * This is useful for things like determining wether to display a fully rendered error page or not. Note that the
271
	 * media file types is not at all comprehensive.
272
	 *
273
	 * @return bool
274
	 */
275
	public function isMedia() {
276
		return in_array($this->getExtension(), array('css', 'js', 'jpg', 'jpeg', 'gif', 'png', 'bmp', 'ico'));
277
	}
278
279
	/**
280
	 * Add a HTTP header to the response, replacing any header of the same name.
281
	 *
282
	 * @param string $header Example: "Content-Type"
283
	 * @param string $value Example: "text/xml"
284
	 */
285
	public function addHeader($header, $value) {
286
		$this->headers[$header] = $value;
287
	}
288
289
	/**
290
	 * @return array
291
	 */
292
	public function getHeaders() {
293
		return $this->headers;
294
	}
295
296
	/**
297
	 * Remove an existing HTTP header
298
	 *
299
	 * @param string $header
300
	 * @return mixed
301
	 */
302
	public function getHeader($header) {
303
		return (isset($this->headers[$header])) ? $this->headers[$header] : null;
304
	}
305
306
	/**
307
	 * Remove an existing HTTP header by its name,
308
	 * e.g. "Content-Type".
309
	 *
310
	 * @param string $header
311
	 * @return SS_HTTPRequest $this
312
	 */
313
	public function removeHeader($header) {
314
		if(isset($this->headers[$header])) unset($this->headers[$header]);
315
		return $this;
316
	}
317
318
	/**
319
	 * Returns the URL used to generate the page
320
	 *
321
	 * @param bool $includeGetVars whether or not to include the get parameters\
322
	 * @return string
323
	 */
324
	public function getURL($includeGetVars = false) {
325
		$url = ($this->getExtension()) ? $this->url . '.' . $this->getExtension() : $this->url;
326
327
		if ($includeGetVars) {
328
			// if we don't unset $vars['url'] we end up with /my/url?url=my/url&foo=bar etc
329
330
			$vars = $this->getVars();
331
			unset($vars['url']);
332
333
			if (count($vars)) {
334
				$url .= '?' . http_build_query($vars);
335
			}
336
		}
337 View Code Duplication
		else if(strpos($url, "?") !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
338
			$url = substr($url, 0, strpos($url, "?"));
339
		}
340
341
		return $url;
342
	}
343
344
	/**
345
	 * Returns true if this request an ajax request,
346
	 * based on custom HTTP ajax added by common JavaScript libraries,
347
	 * or based on an explicit "ajax" request parameter.
348
	 *
349
	 * @return boolean
350
	 */
351
	public function isAjax() {
352
		return (
353
			$this->requestVar('ajax') ||
354
			$this->getHeader('X-Requested-With') && $this->getHeader('X-Requested-With') == "XMLHttpRequest"
355
		);
356
	}
357
358
	/**
359
	 * Enables the existence of a key-value pair in the request to be checked using
360
	 * array syntax, so isset($request['title']) will check for $_POST['title'] and $_GET['title']
361
	 *
362
	 * @param unknown_type $offset
363
	 * @return boolean
364
	 */
365
	public function offsetExists($offset) {
366
		if(isset($this->postVars[$offset])) return true;
367
		if(isset($this->getVars[$offset])) return true;
368
		return false;
369
	}
370
371
	/**
372
	 * Access a request variable using array syntax. eg: $request['title'] instead of $request->postVar('title')
373
	 *
374
	 * @param unknown_type $offset
375
	 * @return unknown
376
	 */
377
	public function offsetGet($offset) {
378
		return $this->requestVar($offset);
379
	}
380
381
	/**
382
	 * @ignore
383
	 */
384
	public function offsetSet($offset, $value) {}
385
386
	/**
387
	 * @ignore
388
	 */
389
	public function offsetUnset($offset) {}
390
391
	/**
392
	 * Construct an SS_HTTPResponse that will deliver a file to the client.
393
	 * Caution: Since it requires $fileData to be passed as binary data (no stream support),
394
	 * it's only advisable to send small files through this method.
395
	 *
396
	 * @static
397
	 * @param $fileData
398
	 * @param $fileName
399
	 * @param null $mimeType
400
	 * @return SS_HTTPResponse
401
	 */
402
	public static function send_file($fileData, $fileName, $mimeType = null) {
403
		if(!$mimeType) {
404
			$mimeType = HTTP::get_mime_type($fileName);
405
		}
406
		$response = new HTTPResponse($fileData);
407
		$response->addHeader("Content-Type", "$mimeType; name=\"" . addslashes($fileName) . "\"");
408
		// Note a IE-only fix that inspects this header in HTTP::add_cache_headers().
409
		$response->addHeader("Content-Disposition", "attachment; filename=" . addslashes($fileName));
410
		$response->addHeader("Content-Length", strlen($fileData));
411
412
		return $response;
413
	}
414
415
	/**
416
	 * Matches a URL pattern
417
	 * The pattern can contain a number of segments, separated by / (and an extension indicated by a .)
418
	 *
419
	 * The parts can be either literals, or, if they start with a $ they are interpreted as variables.
420
	 *  - Literals must be provided in order to match
421
	 *  - $Variables are optional
422
	 *  - However, if you put ! at the end of a variable, then it becomes mandatory.
423
	 *
424
	 * For example:
425
	 *  - admin/crm/list will match admin/crm/$Action/$ID/$OtherID, but it won't match admin/crm/$Action!/$ClassName!
426
	 *
427
	 * The pattern can optionally start with an HTTP method and a space.  For example, "POST $Controller/$Action".
428
	 * This is used to define a rule that only matches on a specific HTTP method.
429
	 *
430
	 * @param $pattern
431
	 * @param bool $shiftOnSuccess
432
	 * @return array|bool
433
	 */
434
	public function match($pattern, $shiftOnSuccess = false) {
435
		// Check if a specific method is required
436
		if(preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
437
			$requiredMethod = $matches[1];
438
			if($requiredMethod != $this->httpMethod) return false;
439
440
			// If we get this far, we can match the URL pattern as usual.
441
			$pattern = $matches[2];
442
		}
443
444
		// Special case for the root URL controller
445
		if(!$pattern) {
446
			return ($this->dirParts == array()) ? array('Matched' => true) : false;
447
		}
448
449
		// Check for the '//' marker that represents the "shifting point"
450
		$doubleSlashPoint = strpos($pattern, '//');
451
		if($doubleSlashPoint !== false) {
452
			$shiftCount = substr_count(substr($pattern,0,$doubleSlashPoint), '/') + 1;
453
			$pattern = str_replace('//', '/', $pattern);
454
			$patternParts = explode('/', $pattern);
455
456
457
		} else {
458
			$patternParts = explode('/', $pattern);
459
			$shiftCount = sizeof($patternParts);
460
		}
461
462
		// Filter out any "empty" matching parts - either from an initial / or a trailing /
463
		$patternParts = array_values(array_filter($patternParts));
464
465
		$arguments = array();
466
		foreach($patternParts as $i => $part) {
467
			$part = trim($part);
468
469
			// Match a variable
470
			if(isset($part[0]) && $part[0] == '$') {
471
				// A variable ending in ! is required
472
				if(substr($part,-1) == '!') {
473
					$varRequired = true;
474
					$varName = substr($part,1,-1);
475
				} else {
476
					$varRequired = false;
477
					$varName = substr($part,1);
478
				}
479
480
				// Fail if a required variable isn't populated
481
				if($varRequired && !isset($this->dirParts[$i])) return false;
482
483
				$arguments[$varName] = isset($this->dirParts[$i]) ? $this->dirParts[$i] : null;
484
				if($part == '$Controller' && (!ClassInfo::exists($arguments['Controller'])
485
						|| !is_subclass_of($arguments['Controller'], 'SilverStripe\Control\Controller'))) {
486
487
					return false;
488
				}
489
490
			// Literal parts with extension
491
			} else if(isset($this->dirParts[$i]) && $this->dirParts[$i] . '.' . $this->extension == $part) {
492
				continue;
493
494
			// Literal parts must always be there
495
			} else if(!isset($this->dirParts[$i]) || $this->dirParts[$i] != $part) {
496
				return false;
497
			}
498
499
		}
500
501
		if($shiftOnSuccess) {
502
			$this->shift($shiftCount);
503
			// We keep track of pattern parts that we looked at but didn't shift off.
504
			// This lets us say that we have *parsed* the whole URL even when we haven't *shifted* it all
505
			$this->unshiftedButParsedParts = sizeof($patternParts) - $shiftCount;
0 ignored issues
show
Documentation Bug introduced by
It seems like sizeof($patternParts) - $shiftCount can also be of type double. However, the property $unshiftedButParsedParts is declared as type integer. 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...
506
		}
507
508
		$this->latestParams = $arguments;
509
510
		// Load the arguments that actually have a value into $this->allParams
511
		// This ensures that previous values aren't overridden with blanks
512
		foreach($arguments as $k => $v) {
513
			if($v || !isset($this->allParams[$k])) $this->allParams[$k] = $v;
514
		}
515
516
		if($arguments === array()) $arguments['_matched'] = true;
517
		return $arguments;
518
	}
519
520
	/**
521
	 * @return array
522
	 */
523
	public function allParams() {
524
		return $this->allParams;
525
	}
526
527
	/**
528
	 * Shift all the parameter values down a key space, and return the shifted value.
529
	 *
530
	 * @return string
531
	 */
532
	public function shiftAllParams() {
533
		$keys    = array_keys($this->allParams);
534
		$values  = array_values($this->allParams);
535
		$value   = array_shift($values);
536
537
		// push additional unparsed URL parts onto the parameter stack
538
		if(array_key_exists($this->unshiftedButParsedParts, $this->dirParts)) {
539
			$values[] = $this->dirParts[$this->unshiftedButParsedParts];
540
		}
541
542
		foreach($keys as $position => $key) {
543
			$this->allParams[$key] = isset($values[$position]) ? $values[$position] : null;
544
		}
545
546
		return $value;
547
	}
548
549
	/**
550
	 * @return array
551
	 */
552
	public function latestParams() {
553
		return $this->latestParams;
554
	}
555
556
	/**
557
	 * @param string $name
558
	 * @return string|null
559
	 */
560
	public function latestParam($name) {
561
		if(isset($this->latestParams[$name])) return $this->latestParams[$name];
562
		else return null;
563
	}
564
565
	/**
566
	 * @return array
567
	 */
568
	public function routeParams() {
569
		return $this->routeParams;
570
	}
571
572
	/**
573
	 * @param $params
574
	 * @return SS_HTTPRequest $this
575
	 */
576
	public function setRouteParams($params) {
577
		$this->routeParams = $params;
578
		return $this;
579
	}
580
581
	/**
582
	 * @return array
583
	 */
584
	public function params() {
585
		return array_merge($this->allParams, $this->routeParams);
586
	}
587
588
	/**
589
	 * Finds a named URL parameter (denoted by "$"-prefix in $url_handlers)
590
	 * from the full URL, or a parameter specified in the route table
591
	 *
592
	 * @param string $name
593
	 * @return string Value of the URL parameter (if found)
594
	 */
595
	public function param($name) {
596
		$params = $this->params();
597
		if(isset($params[$name])) return $params[$name];
598
		else return null;
599
	}
600
601
	/**
602
	 * Returns the unparsed part of the original URL
603
	 * separated by commas. This is used by {@link RequestHandler->handleRequest()}
604
	 * to determine if further URL processing is necessary.
605
	 *
606
	 * @return string Partial URL
607
	 */
608
	public function remaining() {
609
		return implode("/", $this->dirParts);
610
	}
611
612
	/**
613
	 * Returns true if this is a URL that will match without shifting off any of the URL.
614
	 * This is used by the request handler to prevent infinite parsing loops.
615
	 *
616
	 * @param $pattern
617
	 * @return bool
618
	 */
619
	public function isEmptyPattern($pattern) {
620
		if(preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
621
			$pattern = $matches[2];
622
		}
623
624
		if(trim($pattern) == "") return true;
625
	}
626
627
	/**
628
	 * Shift one or more parts off the beginning of the URL.
629
	 * If you specify shifting more than 1 item off, then the items will be returned as an array
630
	 *
631
	 * @param int $count Shift Count
632
	 * @return string|Array
633
	 */
634
	public function shift($count = 1) {
635
		$return = array();
636
637
		if($count == 1) return array_shift($this->dirParts);
638
639
		for($i=0;$i<$count;$i++) {
640
			$value = array_shift($this->dirParts);
641
642
			if($value === null) break;
643
644
			$return[] = $value;
645
		}
646
647
		return $return;
648
	}
649
650
	/**
651
	 * Returns true if the URL has been completely parsed.
652
	 * This will respect parsed but unshifted directory parts.
653
	 *
654
	 * @return bool
655
	 */
656
	public function allParsed() {
657
		return sizeof($this->dirParts) <= $this->unshiftedButParsedParts;
658
	}
659
660
	/**
661
	 * Returns the client IP address which
662
	 * originated this request.
663
	 *
664
	 * @return string
665
	 */
666
	public function getIP() {
667
		$headerOverrideIP = null;
668
		if(TRUSTED_PROXY) {
669
			$headers = (defined('SS_TRUSTED_PROXY_IP_HEADER')) ? array(SS_TRUSTED_PROXY_IP_HEADER) : null;
670
			if(!$headers) {
671
				// Backwards compatible defaults
672
				$headers = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR');
673
			}
674
			foreach($headers as $header) {
675
				if(!empty($_SERVER[$header])) {
676
					$headerOverrideIP = $_SERVER[$header];
677
					break;
678
				}
679
			}
680
		}
681
682
		if ($headerOverrideIP && filter_var($headerOverrideIP, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
683
			return $this->getIPFromHeaderValue($headerOverrideIP);
684
		} elseif(isset($_SERVER['REMOTE_ADDR'])) {
685
			return $_SERVER['REMOTE_ADDR'];
686
		} else {
687
			return null;
688
		}
689
	}
690
691
	/**
692
	 * Extract an IP address from a header value that has been obtained. Accepts single IP or comma separated string of
693
	 * IPs
694
	 *
695
	 * @param string $headerValue The value from a trusted header
696
	 * @return string The IP address
697
	 */
698
	protected function getIPFromHeaderValue($headerValue) {
699
		if (strpos($headerValue, ',') !== false) {
700
			//sometimes the IP from a load balancer could be "x.x.x.x, y.y.y.y, z.z.z.z" so we need to find the most
701
			// likely candidate
702
			$ips = explode(',', $headerValue);
703
			foreach ($ips as $ip) {
704
				$ip = trim($ip);
705
				if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
706
					return $ip;
707
				}
708
			}
709
		}
710
		return $headerValue;
711
	}
712
713
	/**
714
	 * Returns all mimetypes from the HTTP "Accept" header
715
	 * as an array.
716
	 *
717
	 * @param boolean $includeQuality Don't strip away optional "quality indicators", e.g. "application/xml;q=0.9"
718
	 *                                (Default: false)
719
	 * @return array
720
	 */
721
	public function getAcceptMimetypes($includeQuality = false) {
722
		$mimetypes = array();
723
		$mimetypesWithQuality = explode(',',$this->getHeader('Accept'));
724
		foreach($mimetypesWithQuality as $mimetypeWithQuality) {
725
			$mimetypes[] = ($includeQuality) ? $mimetypeWithQuality : preg_replace('/;.*/', '', $mimetypeWithQuality);
726
		}
727
		return $mimetypes;
728
	}
729
730
	/**
731
	 * @return string HTTP method (all uppercase)
732
	 */
733
	public function httpMethod() {
734
		return $this->httpMethod;
735
	}
736
737
	/**
738
	 * Gets the "real" HTTP method for a request.
739
	 *
740
	 * Used to work around browser limitations of form
741
	 * submissions to GET and POST, by overriding the HTTP method
742
	 * with a POST parameter called "_method" for PUT, DELETE, HEAD.
743
	 * Using GET for the "_method" override is not supported,
744
	 * as GET should never carry out state changes.
745
	 * Alternatively you can use a custom HTTP header 'X-HTTP-Method-Override'
746
	 * to override the original method in {@link Director::direct()}.
747
	 * The '_method' POST parameter overrules the custom HTTP header.
748
	 *
749
	 * @param string $origMethod Original HTTP method from the browser request
750
	 * @param array $postVars
751
	 * @return string HTTP method (all uppercase)
752
	 */
753
	public static function detect_method($origMethod, $postVars) {
754
		if(isset($postVars['_method'])) {
755
			if(!in_array(strtoupper($postVars['_method']), array('GET','POST','PUT','DELETE','HEAD'))) {
756
				user_error('Director::direct(): Invalid "_method" parameter', E_USER_ERROR);
757
			}
758
			return strtoupper($postVars['_method']);
759
		} else {
760
			return $origMethod;
761
		}
762
	}
763
}
764