Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
16591 anikendra 1
<?php
2
/**
3
 * CakeRequest
4
 *
5
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
6
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
7
 *
8
 * Licensed under The MIT License
9
 * For full copyright and license information, please see the LICENSE.txt
10
 * Redistributions of files must retain the above copyright notice.
11
 *
12
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
13
 * @link          http://cakephp.org CakePHP(tm) Project
14
 * @since         CakePHP(tm) v 2.0
15
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
16
 */
17
 
18
App::uses('Hash', 'Utility');
19
 
20
/**
21
 * A class that helps wrap Request information and particulars about a single request.
22
 * Provides methods commonly used to introspect on the request headers and request body.
23
 *
24
 * Has both an Array and Object interface. You can access framework parameters using indexes:
25
 *
26
 * `$request['controller']` or `$request->controller`.
27
 *
28
 * @package       Cake.Network
29
 */
30
class CakeRequest implements ArrayAccess {
31
 
32
/**
33
 * Array of parameters parsed from the URL.
34
 *
35
 * @var array
36
 */
37
	public $params = array(
38
		'plugin' => null,
39
		'controller' => null,
40
		'action' => null,
41
		'named' => array(),
42
		'pass' => array(),
43
	);
44
 
45
/**
46
 * Array of POST data. Will contain form data as well as uploaded files.
47
 * Inputs prefixed with 'data' will have the data prefix removed. If there is
48
 * overlap between an input prefixed with data and one without, the 'data' prefixed
49
 * value will take precedence.
50
 *
51
 * @var array
52
 */
53
	public $data = array();
54
 
55
/**
56
 * Array of querystring arguments
57
 *
58
 * @var array
59
 */
60
	public $query = array();
61
 
62
/**
63
 * The URL string used for the request.
64
 *
65
 * @var string
66
 */
67
	public $url;
68
 
69
/**
70
 * Base URL path.
71
 *
72
 * @var string
73
 */
74
	public $base = false;
75
 
76
/**
77
 * webroot path segment for the request.
78
 *
79
 * @var string
80
 */
81
	public $webroot = '/';
82
 
83
/**
84
 * The full address to the current request
85
 *
86
 * @var string
87
 */
88
	public $here = null;
89
 
90
/**
91
 * The built in detectors used with `is()` can be modified with `addDetector()`.
92
 *
93
 * There are several ways to specify a detector, see CakeRequest::addDetector() for the
94
 * various formats and ways to define detectors.
95
 *
96
 * @var array
97
 */
98
	protected $_detectors = array(
99
		'get' => array('env' => 'REQUEST_METHOD', 'value' => 'GET'),
100
		'post' => array('env' => 'REQUEST_METHOD', 'value' => 'POST'),
101
		'put' => array('env' => 'REQUEST_METHOD', 'value' => 'PUT'),
102
		'delete' => array('env' => 'REQUEST_METHOD', 'value' => 'DELETE'),
103
		'head' => array('env' => 'REQUEST_METHOD', 'value' => 'HEAD'),
104
		'options' => array('env' => 'REQUEST_METHOD', 'value' => 'OPTIONS'),
105
		'ssl' => array('env' => 'HTTPS', 'value' => 1),
106
		'ajax' => array('env' => 'HTTP_X_REQUESTED_WITH', 'value' => 'XMLHttpRequest'),
107
		'flash' => array('env' => 'HTTP_USER_AGENT', 'pattern' => '/^(Shockwave|Adobe) Flash/'),
108
		'mobile' => array('env' => 'HTTP_USER_AGENT', 'options' => array(
109
			'Android', 'AvantGo', 'BB10', 'BlackBerry', 'DoCoMo', 'Fennec', 'iPod', 'iPhone', 'iPad',
110
			'J2ME', 'MIDP', 'NetFront', 'Nokia', 'Opera Mini', 'Opera Mobi', 'PalmOS', 'PalmSource',
111
			'portalmmm', 'Plucker', 'ReqwirelessWeb', 'SonyEricsson', 'Symbian', 'UP\\.Browser',
112
			'webOS', 'Windows CE', 'Windows Phone OS', 'Xiino'
113
		)),
114
		'requested' => array('param' => 'requested', 'value' => 1),
115
		'json' => array('accept' => array('application/json'), 'param' => 'ext', 'value' => 'json'),
116
		'xml' => array('accept' => array('application/xml', 'text/xml'), 'param' => 'ext', 'value' => 'xml'),
117
	);
118
 
119
/**
120
 * Copy of php://input. Since this stream can only be read once in most SAPI's
121
 * keep a copy of it so users don't need to know about that detail.
122
 *
123
 * @var string
124
 */
125
	protected $_input = '';
126
 
127
/**
128
 * Constructor
129
 *
130
 * @param string $url Trimmed URL string to use. Should not contain the application base path.
131
 * @param bool $parseEnvironment Set to false to not auto parse the environment. ie. GET, POST and FILES.
132
 */
133
	public function __construct($url = null, $parseEnvironment = true) {
134
		$this->_base();
135
		if (empty($url)) {
136
			$url = $this->_url();
137
		}
138
		if ($url[0] === '/') {
139
			$url = substr($url, 1);
140
		}
141
		$this->url = $url;
142
 
143
		if ($parseEnvironment) {
144
			$this->_processPost();
145
			$this->_processGet();
146
			$this->_processFiles();
147
		}
148
		$this->here = $this->base . '/' . $this->url;
149
	}
150
 
151
/**
152
 * process the post data and set what is there into the object.
153
 * processed data is available at `$this->data`
154
 *
155
 * Will merge POST vars prefixed with `data`, and ones without
156
 * into a single array. Variables prefixed with `data` will overwrite those without.
157
 *
158
 * If you have mixed POST values be careful not to make any top level keys numeric
159
 * containing arrays. Hash::merge() is used to merge data, and it has possibly
160
 * unexpected behavior in this situation.
161
 *
162
 * @return void
163
 */
164
	protected function _processPost() {
165
		if ($_POST) {
166
			$this->data = $_POST;
167
		} elseif (($this->is('put') || $this->is('delete')) &&
168
			strpos(env('CONTENT_TYPE'), 'application/x-www-form-urlencoded') === 0
169
		) {
170
				$data = $this->_readInput();
171
				parse_str($data, $this->data);
172
		}
173
		if (ini_get('magic_quotes_gpc') === '1') {
174
			$this->data = stripslashes_deep($this->data);
175
		}
176
		if (env('HTTP_X_HTTP_METHOD_OVERRIDE')) {
177
			$this->data['_method'] = env('HTTP_X_HTTP_METHOD_OVERRIDE');
178
		}
179
		$isArray = is_array($this->data);
180
		if ($isArray && isset($this->data['_method'])) {
181
			if (!empty($_SERVER)) {
182
				$_SERVER['REQUEST_METHOD'] = $this->data['_method'];
183
			} else {
184
				$_ENV['REQUEST_METHOD'] = $this->data['_method'];
185
			}
186
			unset($this->data['_method']);
187
		}
188
		if ($isArray && isset($this->data['data'])) {
189
			$data = $this->data['data'];
190
			if (count($this->data) <= 1) {
191
				$this->data = $data;
192
			} else {
193
				unset($this->data['data']);
194
				$this->data = Hash::merge($this->data, $data);
195
			}
196
		}
197
	}
198
 
199
/**
200
 * Process the GET parameters and move things into the object.
201
 *
202
 * @return void
203
 */
204
	protected function _processGet() {
205
		if (ini_get('magic_quotes_gpc') === '1') {
206
			$query = stripslashes_deep($_GET);
207
		} else {
208
			$query = $_GET;
209
		}
210
 
211
		$unsetUrl = '/' . str_replace(array('.', ' '), '_', urldecode($this->url));
212
		unset($query[$unsetUrl]);
213
		unset($query[$this->base . $unsetUrl]);
214
		if (strpos($this->url, '?') !== false) {
215
			list(, $querystr) = explode('?', $this->url);
216
			parse_str($querystr, $queryArgs);
217
			$query += $queryArgs;
218
		}
219
		if (isset($this->params['url'])) {
220
			$query = array_merge($this->params['url'], $query);
221
		}
222
		$this->query = $query;
223
	}
224
 
225
/**
226
 * Get the request uri. Looks in PATH_INFO first, as this is the exact value we need prepared
227
 * by PHP. Following that, REQUEST_URI, PHP_SELF, HTTP_X_REWRITE_URL and argv are checked in that order.
228
 * Each of these server variables have the base path, and query strings stripped off
229
 *
230
 * @return string URI The CakePHP request path that is being accessed.
231
 */
232
	protected function _url() {
233
		if (!empty($_SERVER['PATH_INFO'])) {
234
			return $_SERVER['PATH_INFO'];
235
		} elseif (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '://') === false) {
236
			$uri = $_SERVER['REQUEST_URI'];
237
		} elseif (isset($_SERVER['REQUEST_URI'])) {
238
			$qPosition = strpos($_SERVER['REQUEST_URI'], '?');
239
			if ($qPosition !== false && strpos($_SERVER['REQUEST_URI'], '://') > $qPosition) {
240
				$uri = $_SERVER['REQUEST_URI'];
241
			} else {
242
				$uri = substr($_SERVER['REQUEST_URI'], strlen(Configure::read('App.fullBaseUrl')));
243
			}
244
		} elseif (isset($_SERVER['PHP_SELF']) && isset($_SERVER['SCRIPT_NAME'])) {
245
			$uri = str_replace($_SERVER['SCRIPT_NAME'], '', $_SERVER['PHP_SELF']);
246
		} elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) {
247
			$uri = $_SERVER['HTTP_X_REWRITE_URL'];
248
		} elseif ($var = env('argv')) {
249
			$uri = $var[0];
250
		}
251
 
252
		$base = $this->base;
253
 
254
		if (strlen($base) > 0 && strpos($uri, $base) === 0) {
255
			$uri = substr($uri, strlen($base));
256
		}
257
		if (strpos($uri, '?') !== false) {
258
			list($uri) = explode('?', $uri, 2);
259
		}
260
		if (empty($uri) || $uri === '/' || $uri === '//' || $uri === '/index.php') {
261
			$uri = '/';
262
		}
263
		$endsWithIndex = '/webroot/index.php';
264
		$endsWithLength = strlen($endsWithIndex);
265
		if (strlen($uri) >= $endsWithLength &&
266
			substr($uri, -$endsWithLength) === $endsWithIndex
267
		) {
268
			$uri = '/';
269
		}
270
		return $uri;
271
	}
272
 
273
/**
274
 * Returns a base URL and sets the proper webroot
275
 *
276
 * If CakePHP is called with index.php in the URL even though
277
 * URL Rewriting is activated (and thus not needed) it swallows
278
 * the unnecessary part from $base to prevent issue #3318.
279
 *
280
 * @return string Base URL
281
 * @link https://cakephp.lighthouseapp.com/projects/42648-cakephp/tickets/3318
282
 */
283
	protected function _base() {
284
		$dir = $webroot = null;
285
		$config = Configure::read('App');
286
		extract($config);
287
 
288
		if (!isset($base)) {
289
			$base = $this->base;
290
		}
291
		if ($base !== false) {
292
			$this->webroot = $base . '/';
293
			return $this->base = $base;
294
		}
295
 
296
		if (!$baseUrl) {
297
			$base = dirname(env('PHP_SELF'));
298
			// Clean up additional / which cause following code to fail..
299
			$base = preg_replace('#/+#', '/', $base);
300
 
301
			$indexPos = strpos($base, '/webroot/index.php');
302
			if ($indexPos !== false) {
303
				$base = substr($base, 0, $indexPos) . '/webroot';
304
			}
305
			if ($webroot === 'webroot' && $webroot === basename($base)) {
306
				$base = dirname($base);
307
			}
308
			if ($dir === 'app' && $dir === basename($base)) {
309
				$base = dirname($base);
310
			}
311
 
312
			if ($base === DS || $base === '.') {
313
				$base = '';
314
			}
315
			$base = implode('/', array_map('rawurlencode', explode('/', $base)));
316
			$this->webroot = $base . '/';
317
 
318
			return $this->base = $base;
319
		}
320
 
321
		$file = '/' . basename($baseUrl);
322
		$base = dirname($baseUrl);
323
 
324
		if ($base === DS || $base === '.') {
325
			$base = '';
326
		}
327
		$this->webroot = $base . '/';
328
 
329
		$docRoot = env('DOCUMENT_ROOT');
330
		$docRootContainsWebroot = strpos($docRoot, $dir . DS . $webroot);
331
 
332
		if (!empty($base) || !$docRootContainsWebroot) {
333
			if (strpos($this->webroot, '/' . $dir . '/') === false) {
334
				$this->webroot .= $dir . '/';
335
			}
336
			if (strpos($this->webroot, '/' . $webroot . '/') === false) {
337
				$this->webroot .= $webroot . '/';
338
			}
339
		}
340
		return $this->base = $base . $file;
341
	}
342
 
343
/**
344
 * Process $_FILES and move things into the object.
345
 *
346
 * @return void
347
 */
348
	protected function _processFiles() {
349
		if (isset($_FILES) && is_array($_FILES)) {
350
			foreach ($_FILES as $name => $data) {
351
				if ($name !== 'data') {
352
					$this->params['form'][$name] = $data;
353
				}
354
			}
355
		}
356
 
357
		if (isset($_FILES['data'])) {
358
			foreach ($_FILES['data'] as $key => $data) {
359
				$this->_processFileData('', $data, $key);
360
			}
361
		}
362
	}
363
 
364
/**
365
 * Recursively walks the FILES array restructuring the data
366
 * into something sane and useable.
367
 *
368
 * @param string $path The dot separated path to insert $data into.
369
 * @param array $data The data to traverse/insert.
370
 * @param string $field The terminal field name, which is the top level key in $_FILES.
371
 * @return void
372
 */
373
	protected function _processFileData($path, $data, $field) {
374
		foreach ($data as $key => $fields) {
375
			$newPath = $key;
376
			if (strlen($path) > 0) {
377
				$newPath = $path . '.' . $key;
378
			}
379
			if (is_array($fields)) {
380
				$this->_processFileData($newPath, $fields, $field);
381
			} else {
382
				$newPath .= '.' . $field;
383
				$this->data = Hash::insert($this->data, $newPath, $fields);
384
			}
385
		}
386
	}
387
 
388
/**
389
 * Get the IP the client is using, or says they are using.
390
 *
391
 * @param bool $safe Use safe = false when you think the user might manipulate their HTTP_CLIENT_IP
392
 *   header. Setting $safe = false will also look at HTTP_X_FORWARDED_FOR
393
 * @return string The client IP.
394
 */
395
	public function clientIp($safe = true) {
396
		if (!$safe && env('HTTP_X_FORWARDED_FOR')) {
397
			$ipaddr = preg_replace('/(?:,.*)/', '', env('HTTP_X_FORWARDED_FOR'));
398
		} else {
399
			if (env('HTTP_CLIENT_IP')) {
400
				$ipaddr = env('HTTP_CLIENT_IP');
401
			} else {
402
				$ipaddr = env('REMOTE_ADDR');
403
			}
404
		}
405
 
406
		if (env('HTTP_CLIENTADDRESS')) {
407
			$tmpipaddr = env('HTTP_CLIENTADDRESS');
408
 
409
			if (!empty($tmpipaddr)) {
410
				$ipaddr = preg_replace('/(?:,.*)/', '', $tmpipaddr);
411
			}
412
		}
413
		return trim($ipaddr);
414
	}
415
 
416
/**
417
 * Returns the referer that referred this request.
418
 *
419
 * @param bool $local Attempt to return a local address. Local addresses do not contain hostnames.
420
 * @return string The referring address for this request.
421
 */
422
	public function referer($local = false) {
423
		$ref = env('HTTP_REFERER');
424
 
425
		$base = Configure::read('App.fullBaseUrl') . $this->webroot;
426
		if (!empty($ref) && !empty($base)) {
427
			if ($local && strpos($ref, $base) === 0) {
428
				$ref = substr($ref, strlen($base));
429
				if ($ref[0] !== '/') {
430
					$ref = '/' . $ref;
431
				}
432
				return $ref;
433
			} elseif (!$local) {
434
				return $ref;
435
			}
436
		}
437
		return '/';
438
	}
439
 
440
/**
441
 * Missing method handler, handles wrapping older style isAjax() type methods
442
 *
443
 * @param string $name The method called
444
 * @param array $params Array of parameters for the method call
445
 * @return mixed
446
 * @throws CakeException when an invalid method is called.
447
 */
448
	public function __call($name, $params) {
449
		if (strpos($name, 'is') === 0) {
450
			$type = strtolower(substr($name, 2));
451
			return $this->is($type);
452
		}
453
		throw new CakeException(__d('cake_dev', 'Method %s does not exist', $name));
454
	}
455
 
456
/**
457
 * Magic get method allows access to parsed routing parameters directly on the object.
458
 *
459
 * Allows access to `$this->params['controller']` via `$this->controller`
460
 *
461
 * @param string $name The property being accessed.
462
 * @return mixed Either the value of the parameter or null.
463
 */
464
	public function __get($name) {
465
		if (isset($this->params[$name])) {
466
			return $this->params[$name];
467
		}
468
		return null;
469
	}
470
 
471
/**
472
 * Magic isset method allows isset/empty checks
473
 * on routing parameters.
474
 *
475
 * @param string $name The property being accessed.
476
 * @return bool Existence
477
 */
478
	public function __isset($name) {
479
		return isset($this->params[$name]);
480
	}
481
 
482
/**
483
 * Check whether or not a Request is a certain type.
484
 *
485
 * Uses the built in detection rules as well as additional rules
486
 * defined with CakeRequest::addDetector(). Any detector can be called
487
 * as `is($type)` or `is$Type()`.
488
 *
489
 * @param string|array $type The type of request you want to check. If an array
490
 *   this method will return true if the request matches any type.
491
 * @return bool Whether or not the request is the type you are checking.
492
 */
493
	public function is($type) {
494
		if (is_array($type)) {
495
			$result = array_map(array($this, 'is'), $type);
496
			return count(array_filter($result)) > 0;
497
		}
498
		$type = strtolower($type);
499
		if (!isset($this->_detectors[$type])) {
500
			return false;
501
		}
502
		$detect = $this->_detectors[$type];
503
		if (isset($detect['env']) && $this->_environmentDetector($detect)) {
504
			return true;
505
		}
506
		if (isset($detect['header']) && $this->_headerDetector($detect)) {
507
			return true;
508
		}
509
		if (isset($detect['accept']) && $this->_acceptHeaderDetector($detect)) {
510
			return true;
511
		}
512
		if (isset($detect['param']) && $this->_paramDetector($detect)) {
513
			return true;
514
		}
515
		if (isset($detect['callback']) && is_callable($detect['callback'])) {
516
			return call_user_func($detect['callback'], $this);
517
		}
518
		return false;
519
	}
520
 
521
/**
522
 * Detects if a URL extension is present.
523
 *
524
 * @param array $detect Detector options array.
525
 * @return bool Whether or not the request is the type you are checking.
526
 */
527
	protected function _extensionDetector($detect) {
528
		if (is_string($detect['extension'])) {
529
			$detect['extension'] = array($detect['extension']);
530
		}
531
		if (in_array($this->params['ext'], $detect['extension'])) {
532
			return true;
533
		}
534
		return false;
535
	}
536
 
537
/**
538
 * Detects if a specific accept header is present.
539
 *
540
 * @param array $detect Detector options array.
541
 * @return bool Whether or not the request is the type you are checking.
542
 */
543
	protected function _acceptHeaderDetector($detect) {
544
		$acceptHeaders = explode(',', (string)env('HTTP_ACCEPT'));
545
		foreach ($detect['accept'] as $header) {
546
			if (in_array($header, $acceptHeaders)) {
547
				return true;
548
			}
549
		}
550
		return false;
551
	}
552
 
553
/**
554
 * Detects if a specific header is present.
555
 *
556
 * @param array $detect Detector options array.
557
 * @return bool Whether or not the request is the type you are checking.
558
 */
559
	protected function _headerDetector($detect) {
560
		foreach ($detect['header'] as $header => $value) {
561
			$header = env('HTTP_' . strtoupper($header));
562
			if (!is_null($header)) {
563
				if (!is_string($value) && !is_bool($value) && is_callable($value)) {
564
					return call_user_func($value, $header);
565
				}
566
				return ($header === $value);
567
			}
568
		}
569
		return false;
570
	}
571
 
572
/**
573
 * Detects if a specific request parameter is present.
574
 *
575
 * @param array $detect Detector options array.
576
 * @return bool Whether or not the request is the type you are checking.
577
 */
578
	protected function _paramDetector($detect) {
579
		$key = $detect['param'];
580
		if (isset($detect['value'])) {
581
			$value = $detect['value'];
582
			return isset($this->params[$key]) ? $this->params[$key] == $value : false;
583
		}
584
		if (isset($detect['options'])) {
585
			return isset($this->params[$key]) ? in_array($this->params[$key], $detect['options']) : false;
586
		}
587
		return false;
588
	}
589
 
590
/**
591
 * Detects if a specific environment variable is present.
592
 *
593
 * @param array $detect Detector options array.
594
 * @return bool Whether or not the request is the type you are checking.
595
 */
596
	protected function _environmentDetector($detect) {
597
		if (isset($detect['env'])) {
598
			if (isset($detect['value'])) {
599
				return env($detect['env']) == $detect['value'];
600
			}
601
			if (isset($detect['pattern'])) {
602
				return (bool)preg_match($detect['pattern'], env($detect['env']));
603
			}
604
			if (isset($detect['options'])) {
605
				$pattern = '/' . implode('|', $detect['options']) . '/i';
606
				return (bool)preg_match($pattern, env($detect['env']));
607
			}
608
		}
609
		return false;
610
	}
611
 
612
/**
613
 * Check that a request matches all the given types.
614
 *
615
 * Allows you to test multiple types and union the results.
616
 * See CakeRequest::is() for how to add additional types and the
617
 * built-in types.
618
 *
619
 * @param array $types The types to check.
620
 * @return bool Success.
621
 * @see CakeRequest::is()
622
 */
623
	public function isAll(array $types) {
624
		$result = array_filter(array_map(array($this, 'is'), $types));
625
		return count($result) === count($types);
626
	}
627
 
628
/**
629
 * Add a new detector to the list of detectors that a request can use.
630
 * There are several different formats and types of detectors that can be set.
631
 *
632
 * ### Environment value comparison
633
 *
634
 * An environment value comparison, compares a value fetched from `env()` to a known value
635
 * the environment value is equality checked against the provided value.
636
 *
637
 * e.g `addDetector('post', array('env' => 'REQUEST_METHOD', 'value' => 'POST'))`
638
 *
639
 * ### Pattern value comparison
640
 *
641
 * Pattern value comparison allows you to compare a value fetched from `env()` to a regular expression.
642
 *
643
 * e.g `addDetector('iphone', array('env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i'));`
644
 *
645
 * ### Option based comparison
646
 *
647
 * Option based comparisons use a list of options to create a regular expression. Subsequent calls
648
 * to add an already defined options detector will merge the options.
649
 *
650
 * e.g `addDetector('mobile', array('env' => 'HTTP_USER_AGENT', 'options' => array('Fennec')));`
651
 *
652
 * ### Callback detectors
653
 *
654
 * Callback detectors allow you to provide a 'callback' type to handle the check. The callback will
655
 * receive the request object as its only parameter.
656
 *
657
 * e.g `addDetector('custom', array('callback' => array('SomeClass', 'somemethod')));`
658
 *
659
 * ### Request parameter detectors
660
 *
661
 * Allows for custom detectors on the request parameters.
662
 *
663
 * e.g `addDetector('requested', array('param' => 'requested', 'value' => 1)`
664
 *
665
 * You can also make parameter detectors that accept multiple values
666
 * using the `options` key. This is useful when you want to check
667
 * if a request parameter is in a list of options.
668
 *
669
 * `addDetector('extension', array('param' => 'ext', 'options' => array('pdf', 'csv'))`
670
 *
671
 * @param string $name The name of the detector.
672
 * @param array $options The options for the detector definition. See above.
673
 * @return void
674
 */
675
	public function addDetector($name, $options) {
676
		$name = strtolower($name);
677
		if (isset($this->_detectors[$name]) && isset($options['options'])) {
678
			$options = Hash::merge($this->_detectors[$name], $options);
679
		}
680
		$this->_detectors[$name] = $options;
681
	}
682
 
683
/**
684
 * Add parameters to the request's parsed parameter set. This will overwrite any existing parameters.
685
 * This modifies the parameters available through `$request->params`.
686
 *
687
 * @param array $params Array of parameters to merge in
688
 * @return $this
689
 */
690
	public function addParams($params) {
691
		$this->params = array_merge($this->params, (array)$params);
692
		return $this;
693
	}
694
 
695
/**
696
 * Add paths to the requests' paths vars. This will overwrite any existing paths.
697
 * Provides an easy way to modify, here, webroot and base.
698
 *
699
 * @param array $paths Array of paths to merge in
700
 * @return $this
701
 */
702
	public function addPaths($paths) {
703
		foreach (array('webroot', 'here', 'base') as $element) {
704
			if (isset($paths[$element])) {
705
				$this->{$element} = $paths[$element];
706
			}
707
		}
708
		return $this;
709
	}
710
 
711
/**
712
 * Get the value of the current requests URL. Will include named parameters and querystring arguments.
713
 *
714
 * @param bool $base Include the base path, set to false to trim the base path off.
715
 * @return string the current request URL including query string args.
716
 */
717
	public function here($base = true) {
718
		$url = $this->here;
719
		if (!empty($this->query)) {
720
			$url .= '?' . http_build_query($this->query, null, '&');
721
		}
722
		if (!$base) {
723
			$url = preg_replace('/^' . preg_quote($this->base, '/') . '/', '', $url, 1);
724
		}
725
		return $url;
726
	}
727
 
728
/**
729
 * Read an HTTP header from the Request information.
730
 *
731
 * @param string $name Name of the header you want.
732
 * @return mixed Either false on no header being set or the value of the header.
733
 */
734
	public static function header($name) {
735
		$name = 'HTTP_' . strtoupper(str_replace('-', '_', $name));
736
		if (isset($_SERVER[$name])) {
737
			return $_SERVER[$name];
738
		}
739
		return false;
740
	}
741
 
742
/**
743
 * Get the HTTP method used for this request.
744
 * There are a few ways to specify a method.
745
 *
746
 * - If your client supports it you can use native HTTP methods.
747
 * - You can set the HTTP-X-Method-Override header.
748
 * - You can submit an input with the name `_method`
749
 *
750
 * Any of these 3 approaches can be used to set the HTTP method used
751
 * by CakePHP internally, and will effect the result of this method.
752
 *
753
 * @return string The name of the HTTP method used.
754
 */
755
	public function method() {
756
		return env('REQUEST_METHOD');
757
	}
758
 
759
/**
760
 * Get the host that the request was handled on.
761
 *
762
 * @param bool $trustProxy Whether or not to trust the proxy host.
763
 * @return string
764
 */
765
	public function host($trustProxy = false) {
766
		if ($trustProxy) {
767
			return env('HTTP_X_FORWARDED_HOST');
768
		}
769
		return env('HTTP_HOST');
770
	}
771
 
772
/**
773
 * Get the domain name and include $tldLength segments of the tld.
774
 *
775
 * @param int $tldLength Number of segments your tld contains. For example: `example.com` contains 1 tld.
776
 *   While `example.co.uk` contains 2.
777
 * @return string Domain name without subdomains.
778
 */
779
	public function domain($tldLength = 1) {
780
		$segments = explode('.', $this->host());
781
		$domain = array_slice($segments, -1 * ($tldLength + 1));
782
		return implode('.', $domain);
783
	}
784
 
785
/**
786
 * Get the subdomains for a host.
787
 *
788
 * @param int $tldLength Number of segments your tld contains. For example: `example.com` contains 1 tld.
789
 *   While `example.co.uk` contains 2.
790
 * @return array An array of subdomains.
791
 */
792
	public function subdomains($tldLength = 1) {
793
		$segments = explode('.', $this->host());
794
		return array_slice($segments, 0, -1 * ($tldLength + 1));
795
	}
796
 
797
/**
798
 * Find out which content types the client accepts or check if they accept a
799
 * particular type of content.
800
 *
801
 * #### Get all types:
802
 *
803
 * `$this->request->accepts();`
804
 *
805
 * #### Check for a single type:
806
 *
807
 * `$this->request->accepts('application/json');`
808
 *
809
 * This method will order the returned content types by the preference values indicated
810
 * by the client.
811
 *
812
 * @param string $type The content type to check for. Leave null to get all types a client accepts.
813
 * @return mixed Either an array of all the types the client accepts or a boolean if they accept the
814
 *   provided type.
815
 */
816
	public function accepts($type = null) {
817
		$raw = $this->parseAccept();
818
		$accept = array();
819
		foreach ($raw as $types) {
820
			$accept = array_merge($accept, $types);
821
		}
822
		if ($type === null) {
823
			return $accept;
824
		}
825
		return in_array($type, $accept);
826
	}
827
 
828
/**
829
 * Parse the HTTP_ACCEPT header and return a sorted array with content types
830
 * as the keys, and pref values as the values.
831
 *
832
 * Generally you want to use CakeRequest::accept() to get a simple list
833
 * of the accepted content types.
834
 *
835
 * @return array An array of prefValue => array(content/types)
836
 */
837
	public function parseAccept() {
838
		return $this->_parseAcceptWithQualifier($this->header('accept'));
839
	}
840
 
841
/**
842
 * Get the languages accepted by the client, or check if a specific language is accepted.
843
 *
844
 * Get the list of accepted languages:
845
 *
846
 * ``` CakeRequest::acceptLanguage(); ```
847
 *
848
 * Check if a specific language is accepted:
849
 *
850
 * ``` CakeRequest::acceptLanguage('es-es'); ```
851
 *
852
 * @param string $language The language to test.
853
 * @return mixed If a $language is provided, a boolean. Otherwise the array of accepted languages.
854
 */
855
	public static function acceptLanguage($language = null) {
856
		$raw = static::_parseAcceptWithQualifier(static::header('Accept-Language'));
857
		$accept = array();
858
		foreach ($raw as $languages) {
859
			foreach ($languages as &$lang) {
860
				if (strpos($lang, '_')) {
861
					$lang = str_replace('_', '-', $lang);
862
				}
863
				$lang = strtolower($lang);
864
			}
865
			$accept = array_merge($accept, $languages);
866
		}
867
		if ($language === null) {
868
			return $accept;
869
		}
870
		return in_array(strtolower($language), $accept);
871
	}
872
 
873
/**
874
 * Parse Accept* headers with qualifier options.
875
 *
876
 * Only qualifiers will be extracted, any other accept extensions will be
877
 * discarded as they are not frequently used.
878
 *
879
 * @param string $header Header to parse.
880
 * @return array
881
 */
882
	protected static function _parseAcceptWithQualifier($header) {
883
		$accept = array();
884
		$header = explode(',', $header);
885
		foreach (array_filter($header) as $value) {
886
			$prefValue = '1.0';
887
			$value = trim($value);
888
 
889
			$semiPos = strpos($value, ';');
890
			if ($semiPos !== false) {
891
				$params = explode(';', $value);
892
				$value = trim($params[0]);
893
				foreach ($params as $param) {
894
					$qPos = strpos($param, 'q=');
895
					if ($qPos !== false) {
896
						$prefValue = substr($param, $qPos + 2);
897
					}
898
				}
899
			}
900
 
901
			if (!isset($accept[$prefValue])) {
902
				$accept[$prefValue] = array();
903
			}
904
			if ($prefValue) {
905
				$accept[$prefValue][] = $value;
906
			}
907
		}
908
		krsort($accept);
909
		return $accept;
910
	}
911
 
912
/**
913
 * Provides a read accessor for `$this->query`. Allows you
914
 * to use a syntax similar to `CakeSession` for reading URL query data.
915
 *
916
 * @param string $name Query string variable name
917
 * @return mixed The value being read
918
 */
919
	public function query($name) {
920
		return Hash::get($this->query, $name);
921
	}
922
 
923
/**
924
 * Provides a read/write accessor for `$this->data`. Allows you
925
 * to use a syntax similar to `CakeSession` for reading post data.
926
 *
927
 * ## Reading values.
928
 *
929
 * `$request->data('Post.title');`
930
 *
931
 * When reading values you will get `null` for keys/values that do not exist.
932
 *
933
 * ## Writing values
934
 *
935
 * `$request->data('Post.title', 'New post!');`
936
 *
937
 * You can write to any value, even paths/keys that do not exist, and the arrays
938
 * will be created for you.
939
 *
940
 * @param string $name Dot separated name of the value to read/write, one or more args.
941
 * @return mixed|$this Either the value being read, or $this so you can chain consecutive writes.
942
 */
943
	public function data($name) {
944
		$args = func_get_args();
945
		if (count($args) === 2) {
946
			$this->data = Hash::insert($this->data, $name, $args[1]);
947
			return $this;
948
		}
949
		return Hash::get($this->data, $name);
950
	}
951
 
952
/**
953
 * Safely access the values in $this->params.
954
 *
955
 * @param string $name The name of the parameter to get.
956
 * @return mixed The value of the provided parameter. Will
957
 *   return false if the parameter doesn't exist or is falsey.
958
 */
959
	public function param($name) {
960
		$args = func_get_args();
961
		if (count($args) === 2) {
962
			$this->params = Hash::insert($this->params, $name, $args[1]);
963
			return $this;
964
		}
965
		if (!isset($this->params[$name])) {
966
			return Hash::get($this->params, $name, false);
967
		}
968
		return $this->params[$name];
969
	}
970
 
971
/**
972
 * Read data from `php://input`. Useful when interacting with XML or JSON
973
 * request body content.
974
 *
975
 * Getting input with a decoding function:
976
 *
977
 * `$this->request->input('json_decode');`
978
 *
979
 * Getting input using a decoding function, and additional params:
980
 *
981
 * `$this->request->input('Xml::build', array('return' => 'DOMDocument'));`
982
 *
983
 * Any additional parameters are applied to the callback in the order they are given.
984
 *
985
 * @param string $callback A decoding callback that will convert the string data to another
986
 *     representation. Leave empty to access the raw input data. You can also
987
 *     supply additional parameters for the decoding callback using var args, see above.
988
 * @return The decoded/processed request data.
989
 */
990
	public function input($callback = null) {
991
		$input = $this->_readInput();
992
		$args = func_get_args();
993
		if (!empty($args)) {
994
			$callback = array_shift($args);
995
			array_unshift($args, $input);
996
			return call_user_func_array($callback, $args);
997
		}
998
		return $input;
999
	}
1000
 
1001
/**
1002
 * Modify data originally from `php://input`. Useful for altering json/xml data
1003
 * in middleware or DispatcherFilters before it gets to RequestHandlerComponent
1004
 *
1005
 * @param string $input A string to replace original parsed data from input()
1006
 * @return void
1007
 */
1008
	public function setInput($input) {
1009
		$this->_input = $input;
1010
	}
1011
 
1012
/**
1013
 * Allow only certain HTTP request methods. If the request method does not match
1014
 * a 405 error will be shown and the required "Allow" response header will be set.
1015
 *
1016
 * Example:
1017
 *
1018
 * $this->request->allowMethod('post', 'delete');
1019
 * or
1020
 * $this->request->allowMethod(array('post', 'delete'));
1021
 *
1022
 * If the request would be GET, response header "Allow: POST, DELETE" will be set
1023
 * and a 405 error will be returned.
1024
 *
1025
 * @param string|array $methods Allowed HTTP request methods.
1026
 * @return bool true
1027
 * @throws MethodNotAllowedException
1028
 */
1029
	public function allowMethod($methods) {
1030
		if (!is_array($methods)) {
1031
			$methods = func_get_args();
1032
		}
1033
		foreach ($methods as $method) {
1034
			if ($this->is($method)) {
1035
				return true;
1036
			}
1037
		}
1038
		$allowed = strtoupper(implode(', ', $methods));
1039
		$e = new MethodNotAllowedException();
1040
		$e->responseHeader('Allow', $allowed);
1041
		throw $e;
1042
	}
1043
 
1044
/**
1045
 * Alias of CakeRequest::allowMethod() for backwards compatibility.
1046
 *
1047
 * @param string|array $methods Allowed HTTP request methods.
1048
 * @return bool true
1049
 * @throws MethodNotAllowedException
1050
 * @see CakeRequest::allowMethod()
1051
 * @deprecated 3.0.0 Since 2.5, use CakeRequest::allowMethod() instead.
1052
 */
1053
	public function onlyAllow($methods) {
1054
		if (!is_array($methods)) {
1055
			$methods = func_get_args();
1056
		}
1057
		return $this->allowMethod($methods);
1058
	}
1059
 
1060
/**
1061
 * Read data from php://input, mocked in tests.
1062
 *
1063
 * @return string contents of php://input
1064
 */
1065
	protected function _readInput() {
1066
		if (empty($this->_input)) {
1067
			$fh = fopen('php://input', 'r');
1068
			$content = stream_get_contents($fh);
1069
			fclose($fh);
1070
			$this->_input = $content;
1071
		}
1072
		return $this->_input;
1073
	}
1074
 
1075
/**
1076
 * Array access read implementation
1077
 *
1078
 * @param string $name Name of the key being accessed.
1079
 * @return mixed
1080
 */
1081
	public function offsetGet($name) {
1082
		if (isset($this->params[$name])) {
1083
			return $this->params[$name];
1084
		}
1085
		if ($name === 'url') {
1086
			return $this->query;
1087
		}
1088
		if ($name === 'data') {
1089
			return $this->data;
1090
		}
1091
		return null;
1092
	}
1093
 
1094
/**
1095
 * Array access write implementation
1096
 *
1097
 * @param string $name Name of the key being written
1098
 * @param mixed $value The value being written.
1099
 * @return void
1100
 */
1101
	public function offsetSet($name, $value) {
1102
		$this->params[$name] = $value;
1103
	}
1104
 
1105
/**
1106
 * Array access isset() implementation
1107
 *
1108
 * @param string $name thing to check.
1109
 * @return bool
1110
 */
1111
	public function offsetExists($name) {
1112
		return isset($this->params[$name]);
1113
	}
1114
 
1115
/**
1116
 * Array access unset() implementation
1117
 *
1118
 * @param string $name Name to unset.
1119
 * @return void
1120
 */
1121
	public function offsetUnset($name) {
1122
		unset($this->params[$name]);
1123
	}
1124
 
1125
}