Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
13532 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', '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
	);
116
 
117
/**
118
 * Copy of php://input. Since this stream can only be read once in most SAPI's
119
 * keep a copy of it so users don't need to know about that detail.
120
 *
121
 * @var string
122
 */
123
	protected $_input = '';
124
 
125
/**
126
 * Constructor
127
 *
128
 * @param string $url Trimmed URL string to use. Should not contain the application base path.
129
 * @param boolean $parseEnvironment Set to false to not auto parse the environment. ie. GET, POST and FILES.
130
 */
131
	public function __construct($url = null, $parseEnvironment = true) {
132
		$this->_base();
133
		if (empty($url)) {
134
			$url = $this->_url();
135
		}
136
		if ($url[0] === '/') {
137
			$url = substr($url, 1);
138
		}
139
		$this->url = $url;
140
 
141
		if ($parseEnvironment) {
142
			$this->_processPost();
143
			$this->_processGet();
144
			$this->_processFiles();
145
		}
146
		$this->here = $this->base . '/' . $this->url;
147
	}
148
 
149
/**
150
 * process the post data and set what is there into the object.
151
 * processed data is available at `$this->data`
152
 *
153
 * Will merge POST vars prefixed with `data`, and ones without
154
 * into a single array. Variables prefixed with `data` will overwrite those without.
155
 *
156
 * If you have mixed POST values be careful not to make any top level keys numeric
157
 * containing arrays. Hash::merge() is used to merge data, and it has possibly
158
 * unexpected behavior in this situation.
159
 *
160
 * @return void
161
 */
162
	protected function _processPost() {
163
		if ($_POST) {
164
			$this->data = $_POST;
165
		} elseif (
166
			($this->is('put') || $this->is('delete')) &&
167
			strpos(env('CONTENT_TYPE'), 'application/x-www-form-urlencoded') === 0
168
		) {
169
				$data = $this->_readInput();
170
				parse_str($data, $this->data);
171
		}
172
		if (ini_get('magic_quotes_gpc') === '1') {
173
			$this->data = stripslashes_deep($this->data);
174
		}
175
		if (env('HTTP_X_HTTP_METHOD_OVERRIDE')) {
176
			$this->data['_method'] = env('HTTP_X_HTTP_METHOD_OVERRIDE');
177
		}
178
		$isArray = is_array($this->data);
179
		if ($isArray && isset($this->data['_method'])) {
180
			if (!empty($_SERVER)) {
181
				$_SERVER['REQUEST_METHOD'] = $this->data['_method'];
182
			} else {
183
				$_ENV['REQUEST_METHOD'] = $this->data['_method'];
184
			}
185
			unset($this->data['_method']);
186
		}
187
		if ($isArray && isset($this->data['data'])) {
188
			$data = $this->data['data'];
189
			if (count($this->data) <= 1) {
190
				$this->data = $data;
191
			} else {
192
				unset($this->data['data']);
193
				$this->data = Hash::merge($this->data, $data);
194
			}
195
		}
196
	}
197
 
198
/**
199
 * Process the GET parameters and move things into the object.
200
 *
201
 * @return void
202
 */
203
	protected function _processGet() {
204
		if (ini_get('magic_quotes_gpc') === '1') {
205
			$query = stripslashes_deep($_GET);
206
		} else {
207
			$query = $_GET;
208
		}
209
 
210
		unset($query['/' . str_replace('.', '_', urldecode($this->url))]);
211
		if (strpos($this->url, '?') !== false) {
212
			list(, $querystr) = explode('?', $this->url);
213
			parse_str($querystr, $queryArgs);
214
			$query += $queryArgs;
215
		}
216
		if (isset($this->params['url'])) {
217
			$query = array_merge($this->params['url'], $query);
218
		}
219
		$this->query = $query;
220
	}
221
 
222
/**
223
 * Get the request uri. Looks in PATH_INFO first, as this is the exact value we need prepared
224
 * by PHP. Following that, REQUEST_URI, PHP_SELF, HTTP_X_REWRITE_URL and argv are checked in that order.
225
 * Each of these server variables have the base path, and query strings stripped off
226
 *
227
 * @return string URI The CakePHP request path that is being accessed.
228
 */
229
	protected function _url() {
230
		if (!empty($_SERVER['PATH_INFO'])) {
231
			return $_SERVER['PATH_INFO'];
232
		} elseif (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '://') === false) {
233
			$uri = $_SERVER['REQUEST_URI'];
234
		} elseif (isset($_SERVER['REQUEST_URI'])) {
235
			$qPosition = strpos($_SERVER['REQUEST_URI'], '?');
236
			if ($qPosition !== false && strpos($_SERVER['REQUEST_URI'], '://') > $qPosition) {
237
				$uri = $_SERVER['REQUEST_URI'];
238
			} else {
239
				$uri = substr($_SERVER['REQUEST_URI'], strlen(Configure::read('App.fullBaseUrl')));
240
			}
241
		} elseif (isset($_SERVER['PHP_SELF']) && isset($_SERVER['SCRIPT_NAME'])) {
242
			$uri = str_replace($_SERVER['SCRIPT_NAME'], '', $_SERVER['PHP_SELF']);
243
		} elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) {
244
			$uri = $_SERVER['HTTP_X_REWRITE_URL'];
245
		} elseif ($var = env('argv')) {
246
			$uri = $var[0];
247
		}
248
 
249
		$base = $this->base;
250
 
251
		if (strlen($base) > 0 && strpos($uri, $base) === 0) {
252
			$uri = substr($uri, strlen($base));
253
		}
254
		if (strpos($uri, '?') !== false) {
255
			list($uri) = explode('?', $uri, 2);
256
		}
257
		if (empty($uri) || $uri === '/' || $uri === '//' || $uri === '/index.php') {
258
			$uri = '/';
259
		}
260
		$endsWithIndex = '/webroot/index.php';
261
		$endsWithLength = strlen($endsWithIndex);
262
		if (
263
			strlen($uri) >= $endsWithLength &&
264
			substr($uri, -$endsWithLength) === $endsWithIndex
265
		) {
266
			$uri = '/';
267
		}
268
		return $uri;
269
	}
270
 
271
/**
272
 * Returns a base URL and sets the proper webroot
273
 *
274
 * If CakePHP is called with index.php in the URL even though
275
 * URL Rewriting is activated (and thus not needed) it swallows
276
 * the unnecessary part from $base to prevent issue #3318.
277
 *
278
 * @return string Base URL
279
 * @link https://cakephp.lighthouseapp.com/projects/42648-cakephp/tickets/3318
280
 */
281
	protected function _base() {
282
		$dir = $webroot = null;
283
		$config = Configure::read('App');
284
		extract($config);
285
 
286
		if (!isset($base)) {
287
			$base = $this->base;
288
		}
289
		if ($base !== false) {
290
			$this->webroot = $base . '/';
291
			return $this->base = $base;
292
		}
293
 
294
		if (!$baseUrl) {
295
			$base = dirname(env('PHP_SELF'));
296
 
297
			$indexPos = strpos($base, '/webroot/index.php');
298
			if ($indexPos !== false) {
299
				$base = substr($base, 0, $indexPos) . '/webroot';
300
			}
301
			if ($webroot === 'webroot' && $webroot === basename($base)) {
302
				$base = dirname($base);
303
			}
304
			if ($dir === 'app' && $dir === basename($base)) {
305
				$base = dirname($base);
306
			}
307
 
308
			if ($base === DS || $base === '.') {
309
				$base = '';
310
			}
311
			$base = implode('/', array_map('rawurlencode', explode('/', $base)));
312
			$this->webroot = $base . '/';
313
 
314
			return $this->base = $base;
315
		}
316
 
317
		$file = '/' . basename($baseUrl);
318
		$base = dirname($baseUrl);
319
 
320
		if ($base === DS || $base === '.') {
321
			$base = '';
322
		}
323
		$this->webroot = $base . '/';
324
 
325
		$docRoot = env('DOCUMENT_ROOT');
326
		$docRootContainsWebroot = strpos($docRoot, $dir . DS . $webroot);
327
 
328
		if (!empty($base) || !$docRootContainsWebroot) {
329
			if (strpos($this->webroot, '/' . $dir . '/') === false) {
330
				$this->webroot .= $dir . '/';
331
			}
332
			if (strpos($this->webroot, '/' . $webroot . '/') === false) {
333
				$this->webroot .= $webroot . '/';
334
			}
335
		}
336
		return $this->base = $base . $file;
337
	}
338
 
339
/**
340
 * Process $_FILES and move things into the object.
341
 *
342
 * @return void
343
 */
344
	protected function _processFiles() {
345
		if (isset($_FILES) && is_array($_FILES)) {
346
			foreach ($_FILES as $name => $data) {
347
				if ($name !== 'data') {
348
					$this->params['form'][$name] = $data;
349
				}
350
			}
351
		}
352
 
353
		if (isset($_FILES['data'])) {
354
			foreach ($_FILES['data'] as $key => $data) {
355
				$this->_processFileData('', $data, $key);
356
			}
357
		}
358
	}
359
 
360
/**
361
 * Recursively walks the FILES array restructuring the data
362
 * into something sane and useable.
363
 *
364
 * @param string $path The dot separated path to insert $data into.
365
 * @param array $data The data to traverse/insert.
366
 * @param string $field The terminal field name, which is the top level key in $_FILES.
367
 * @return void
368
 */
369
	protected function _processFileData($path, $data, $field) {
370
		foreach ($data as $key => $fields) {
371
			$newPath = $key;
372
			if (!empty($path)) {
373
				$newPath = $path . '.' . $key;
374
			}
375
			if (is_array($fields)) {
376
				$this->_processFileData($newPath, $fields, $field);
377
			} else {
378
				$newPath .= '.' . $field;
379
				$this->data = Hash::insert($this->data, $newPath, $fields);
380
			}
381
		}
382
	}
383
 
384
/**
385
 * Get the IP the client is using, or says they are using.
386
 *
387
 * @param boolean $safe Use safe = false when you think the user might manipulate their HTTP_CLIENT_IP
388
 *   header. Setting $safe = false will will also look at HTTP_X_FORWARDED_FOR
389
 * @return string The client IP.
390
 */
391
	public function clientIp($safe = true) {
392
		if (!$safe && env('HTTP_X_FORWARDED_FOR')) {
393
			$ipaddr = preg_replace('/(?:,.*)/', '', env('HTTP_X_FORWARDED_FOR'));
394
		} else {
395
			if (env('HTTP_CLIENT_IP')) {
396
				$ipaddr = env('HTTP_CLIENT_IP');
397
			} else {
398
				$ipaddr = env('REMOTE_ADDR');
399
			}
400
		}
401
 
402
		if (env('HTTP_CLIENTADDRESS')) {
403
			$tmpipaddr = env('HTTP_CLIENTADDRESS');
404
 
405
			if (!empty($tmpipaddr)) {
406
				$ipaddr = preg_replace('/(?:,.*)/', '', $tmpipaddr);
407
			}
408
		}
409
		return trim($ipaddr);
410
	}
411
 
412
/**
413
 * Returns the referer that referred this request.
414
 *
415
 * @param boolean $local Attempt to return a local address. Local addresses do not contain hostnames.
416
 * @return string The referring address for this request.
417
 */
418
	public function referer($local = false) {
419
		$ref = env('HTTP_REFERER');
420
		$forwarded = env('HTTP_X_FORWARDED_HOST');
421
		if ($forwarded) {
422
			$ref = $forwarded;
423
		}
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 boolean 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 boolean 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'])) {
504
			if (isset($detect['value'])) {
505
				return env($detect['env']) == $detect['value'];
506
			}
507
			if (isset($detect['pattern'])) {
508
				return (bool)preg_match($detect['pattern'], env($detect['env']));
509
			}
510
			if (isset($detect['options'])) {
511
				$pattern = '/' . implode('|', $detect['options']) . '/i';
512
				return (bool)preg_match($pattern, env($detect['env']));
513
			}
514
		}
515
		if (isset($detect['param'])) {
516
			$key = $detect['param'];
517
			$value = $detect['value'];
518
			return isset($this->params[$key]) ? $this->params[$key] == $value : false;
519
		}
520
		if (isset($detect['callback']) && is_callable($detect['callback'])) {
521
			return call_user_func($detect['callback'], $this);
522
		}
523
		return false;
524
	}
525
 
526
/**
527
 * Check that a request matches all the given types.
528
 *
529
 * Allows you to test multiple types and union the results.
530
 * See CakeRequest::is() for how to add additional types and the
531
 * built-in types.
532
 *
533
 * @param array $types The types to check.
534
 * @return boolean Success.
535
 * @see CakeRequest::is()
536
 */
537
	public function isAll(array $types) {
538
		$result = array_filter(array_map(array($this, 'is'), $types));
539
		return count($result) === count($types);
540
	}
541
 
542
/**
543
 * Add a new detector to the list of detectors that a request can use.
544
 * There are several different formats and types of detectors that can be set.
545
 *
546
 * ### Environment value comparison
547
 *
548
 * An environment value comparison, compares a value fetched from `env()` to a known value
549
 * the environment value is equality checked against the provided value.
550
 *
551
 * e.g `addDetector('post', array('env' => 'REQUEST_METHOD', 'value' => 'POST'))`
552
 *
553
 * ### Pattern value comparison
554
 *
555
 * Pattern value comparison allows you to compare a value fetched from `env()` to a regular expression.
556
 *
557
 * e.g `addDetector('iphone', array('env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i'));`
558
 *
559
 * ### Option based comparison
560
 *
561
 * Option based comparisons use a list of options to create a regular expression. Subsequent calls
562
 * to add an already defined options detector will merge the options.
563
 *
564
 * e.g `addDetector('mobile', array('env' => 'HTTP_USER_AGENT', 'options' => array('Fennec')));`
565
 *
566
 * ### Callback detectors
567
 *
568
 * Callback detectors allow you to provide a 'callback' type to handle the check. The callback will
569
 * receive the request object as its only parameter.
570
 *
571
 * e.g `addDetector('custom', array('callback' => array('SomeClass', 'somemethod')));`
572
 *
573
 * ### Request parameter detectors
574
 *
575
 * Allows for custom detectors on the request parameters.
576
 *
577
 * e.g `addDetector('post', array('param' => 'requested', 'value' => 1)`
578
 *
579
 * @param string $name The name of the detector.
580
 * @param array $options The options for the detector definition. See above.
581
 * @return void
582
 */
583
	public function addDetector($name, $options) {
584
		$name = strtolower($name);
585
		if (isset($this->_detectors[$name]) && isset($options['options'])) {
586
			$options = Hash::merge($this->_detectors[$name], $options);
587
		}
588
		$this->_detectors[$name] = $options;
589
	}
590
 
591
/**
592
 * Add parameters to the request's parsed parameter set. This will overwrite any existing parameters.
593
 * This modifies the parameters available through `$request->params`.
594
 *
595
 * @param array $params Array of parameters to merge in
596
 * @return The current object, you can chain this method.
597
 */
598
	public function addParams($params) {
599
		$this->params = array_merge($this->params, (array)$params);
600
		return $this;
601
	}
602
 
603
/**
604
 * Add paths to the requests' paths vars. This will overwrite any existing paths.
605
 * Provides an easy way to modify, here, webroot and base.
606
 *
607
 * @param array $paths Array of paths to merge in
608
 * @return CakeRequest the current object, you can chain this method.
609
 */
610
	public function addPaths($paths) {
611
		foreach (array('webroot', 'here', 'base') as $element) {
612
			if (isset($paths[$element])) {
613
				$this->{$element} = $paths[$element];
614
			}
615
		}
616
		return $this;
617
	}
618
 
619
/**
620
 * Get the value of the current requests URL. Will include named parameters and querystring arguments.
621
 *
622
 * @param boolean $base Include the base path, set to false to trim the base path off.
623
 * @return string the current request URL including query string args.
624
 */
625
	public function here($base = true) {
626
		$url = $this->here;
627
		if (!empty($this->query)) {
628
			$url .= '?' . http_build_query($this->query, null, '&');
629
		}
630
		if (!$base) {
631
			$url = preg_replace('/^' . preg_quote($this->base, '/') . '/', '', $url, 1);
632
		}
633
		return $url;
634
	}
635
 
636
/**
637
 * Read an HTTP header from the Request information.
638
 *
639
 * @param string $name Name of the header you want.
640
 * @return mixed Either false on no header being set or the value of the header.
641
 */
642
	public static function header($name) {
643
		$name = 'HTTP_' . strtoupper(str_replace('-', '_', $name));
644
		if (!empty($_SERVER[$name])) {
645
			return $_SERVER[$name];
646
		}
647
		return false;
648
	}
649
 
650
/**
651
 * Get the HTTP method used for this request.
652
 * There are a few ways to specify a method.
653
 *
654
 * - If your client supports it you can use native HTTP methods.
655
 * - You can set the HTTP-X-Method-Override header.
656
 * - You can submit an input with the name `_method`
657
 *
658
 * Any of these 3 approaches can be used to set the HTTP method used
659
 * by CakePHP internally, and will effect the result of this method.
660
 *
661
 * @return string The name of the HTTP method used.
662
 */
663
	public function method() {
664
		return env('REQUEST_METHOD');
665
	}
666
 
667
/**
668
 * Get the host that the request was handled on.
669
 *
670
 * @return string
671
 */
672
	public function host() {
673
		return env('HTTP_HOST');
674
	}
675
 
676
/**
677
 * Get the domain name and include $tldLength segments of the tld.
678
 *
679
 * @param integer $tldLength Number of segments your tld contains. For example: `example.com` contains 1 tld.
680
 *   While `example.co.uk` contains 2.
681
 * @return string Domain name without subdomains.
682
 */
683
	public function domain($tldLength = 1) {
684
		$segments = explode('.', $this->host());
685
		$domain = array_slice($segments, -1 * ($tldLength + 1));
686
		return implode('.', $domain);
687
	}
688
 
689
/**
690
 * Get the subdomains for a host.
691
 *
692
 * @param integer $tldLength Number of segments your tld contains. For example: `example.com` contains 1 tld.
693
 *   While `example.co.uk` contains 2.
694
 * @return array An array of subdomains.
695
 */
696
	public function subdomains($tldLength = 1) {
697
		$segments = explode('.', $this->host());
698
		return array_slice($segments, 0, -1 * ($tldLength + 1));
699
	}
700
 
701
/**
702
 * Find out which content types the client accepts or check if they accept a
703
 * particular type of content.
704
 *
705
 * #### Get all types:
706
 *
707
 * `$this->request->accepts();`
708
 *
709
 * #### Check for a single type:
710
 *
711
 * `$this->request->accepts('application/json');`
712
 *
713
 * This method will order the returned content types by the preference values indicated
714
 * by the client.
715
 *
716
 * @param string $type The content type to check for. Leave null to get all types a client accepts.
717
 * @return mixed Either an array of all the types the client accepts or a boolean if they accept the
718
 *   provided type.
719
 */
720
	public function accepts($type = null) {
721
		$raw = $this->parseAccept();
722
		$accept = array();
723
		foreach ($raw as $types) {
724
			$accept = array_merge($accept, $types);
725
		}
726
		if ($type === null) {
727
			return $accept;
728
		}
729
		return in_array($type, $accept);
730
	}
731
 
732
/**
733
 * Parse the HTTP_ACCEPT header and return a sorted array with content types
734
 * as the keys, and pref values as the values.
735
 *
736
 * Generally you want to use CakeRequest::accept() to get a simple list
737
 * of the accepted content types.
738
 *
739
 * @return array An array of prefValue => array(content/types)
740
 */
741
	public function parseAccept() {
742
		return $this->_parseAcceptWithQualifier($this->header('accept'));
743
	}
744
 
745
/**
746
 * Get the languages accepted by the client, or check if a specific language is accepted.
747
 *
748
 * Get the list of accepted languages:
749
 *
750
 * {{{ CakeRequest::acceptLanguage(); }}}
751
 *
752
 * Check if a specific language is accepted:
753
 *
754
 * {{{ CakeRequest::acceptLanguage('es-es'); }}}
755
 *
756
 * @param string $language The language to test.
757
 * @return mixed If a $language is provided, a boolean. Otherwise the array of accepted languages.
758
 */
759
	public static function acceptLanguage($language = null) {
760
		$raw = self::_parseAcceptWithQualifier(self::header('Accept-Language'));
761
		$accept = array();
762
		foreach ($raw as $languages) {
763
			foreach ($languages as &$lang) {
764
				if (strpos($lang, '_')) {
765
					$lang = str_replace('_', '-', $lang);
766
				}
767
				$lang = strtolower($lang);
768
			}
769
			$accept = array_merge($accept, $languages);
770
		}
771
		if ($language === null) {
772
			return $accept;
773
		}
774
		return in_array(strtolower($language), $accept);
775
	}
776
 
777
/**
778
 * Parse Accept* headers with qualifier options.
779
 *
780
 * Only qualifiers will be extracted, any other accept extensions will be
781
 * discarded as they are not frequently used.
782
 *
783
 * @param string $header
784
 * @return array
785
 */
786
	protected static function _parseAcceptWithQualifier($header) {
787
		$accept = array();
788
		$header = explode(',', $header);
789
		foreach (array_filter($header) as $value) {
790
			$prefValue = '1.0';
791
			$value = trim($value);
792
 
793
			$semiPos = strpos($value, ';');
794
			if ($semiPos !== false) {
795
				$params = explode(';', $value);
796
				$value = trim($params[0]);
797
				foreach ($params as $param) {
798
					$qPos = strpos($param, 'q=');
799
					if ($qPos !== false) {
800
						$prefValue = substr($param, $qPos + 2);
801
					}
802
				}
803
			}
804
 
805
			if (!isset($accept[$prefValue])) {
806
				$accept[$prefValue] = array();
807
			}
808
			if ($prefValue) {
809
				$accept[$prefValue][] = $value;
810
			}
811
		}
812
		krsort($accept);
813
		return $accept;
814
	}
815
 
816
/**
817
 * Provides a read accessor for `$this->query`. Allows you
818
 * to use a syntax similar to `CakeSession` for reading URL query data.
819
 *
820
 * @param string $name Query string variable name
821
 * @return mixed The value being read
822
 */
823
	public function query($name) {
824
		return Hash::get($this->query, $name);
825
	}
826
 
827
/**
828
 * Provides a read/write accessor for `$this->data`. Allows you
829
 * to use a syntax similar to `CakeSession` for reading post data.
830
 *
831
 * ## Reading values.
832
 *
833
 * `$request->data('Post.title');`
834
 *
835
 * When reading values you will get `null` for keys/values that do not exist.
836
 *
837
 * ## Writing values
838
 *
839
 * `$request->data('Post.title', 'New post!');`
840
 *
841
 * You can write to any value, even paths/keys that do not exist, and the arrays
842
 * will be created for you.
843
 *
844
 * @param string $name,... Dot separated name of the value to read/write
845
 * @return mixed Either the value being read, or this so you can chain consecutive writes.
846
 */
847
	public function data($name) {
848
		$args = func_get_args();
849
		if (count($args) == 2) {
850
			$this->data = Hash::insert($this->data, $name, $args[1]);
851
			return $this;
852
		}
853
		return Hash::get($this->data, $name);
854
	}
855
 
856
/**
857
 * Safely access the values in $this->params.
858
 *
859
 * @param string $name The name of the parameter to get.
860
 * @return mixed The value of the provided parameter. Will
861
 *   return false if the parameter doesn't exist or is falsey.
862
 */
863
	public function param($name) {
864
		if (!isset($this->params[$name])) {
865
			return false;
866
		}
867
		return $this->params[$name];
868
	}
869
 
870
/**
871
 * Read data from `php://input`. Useful when interacting with XML or JSON
872
 * request body content.
873
 *
874
 * Getting input with a decoding function:
875
 *
876
 * `$this->request->input('json_decode');`
877
 *
878
 * Getting input using a decoding function, and additional params:
879
 *
880
 * `$this->request->input('Xml::build', array('return' => 'DOMDocument'));`
881
 *
882
 * Any additional parameters are applied to the callback in the order they are given.
883
 *
884
 * @param string $callback A decoding callback that will convert the string data to another
885
 *     representation. Leave empty to access the raw input data. You can also
886
 *     supply additional parameters for the decoding callback using var args, see above.
887
 * @return The decoded/processed request data.
888
 */
889
	public function input($callback = null) {
890
		$input = $this->_readInput();
891
		$args = func_get_args();
892
		if (!empty($args)) {
893
			$callback = array_shift($args);
894
			array_unshift($args, $input);
895
			return call_user_func_array($callback, $args);
896
		}
897
		return $input;
898
	}
899
 
900
/**
901
 * Only allow certain HTTP request methods, if the request method does not match
902
 * a 405 error will be shown and the required "Allow" response header will be set.
903
 *
904
 * Example:
905
 *
906
 * $this->request->onlyAllow('post', 'delete');
907
 * or
908
 * $this->request->onlyAllow(array('post', 'delete'));
909
 *
910
 * If the request would be GET, response header "Allow: POST, DELETE" will be set
911
 * and a 405 error will be returned
912
 *
913
 * @param string|array $methods Allowed HTTP request methods
914
 * @return boolean true
915
 * @throws MethodNotAllowedException
916
 */
917
	public function onlyAllow($methods) {
918
		if (!is_array($methods)) {
919
			$methods = func_get_args();
920
		}
921
		foreach ($methods as $method) {
922
			if ($this->is($method)) {
923
				return true;
924
			}
925
		}
926
		$allowed = strtoupper(implode(', ', $methods));
927
		$e = new MethodNotAllowedException();
928
		$e->responseHeader('Allow', $allowed);
929
		throw $e;
930
	}
931
 
932
/**
933
 * Read data from php://input, mocked in tests.
934
 *
935
 * @return string contents of php://input
936
 */
937
	protected function _readInput() {
938
		if (empty($this->_input)) {
939
			$fh = fopen('php://input', 'r');
940
			$content = stream_get_contents($fh);
941
			fclose($fh);
942
			$this->_input = $content;
943
		}
944
		return $this->_input;
945
	}
946
 
947
/**
948
 * Array access read implementation
949
 *
950
 * @param string $name Name of the key being accessed.
951
 * @return mixed
952
 */
953
	public function offsetGet($name) {
954
		if (isset($this->params[$name])) {
955
			return $this->params[$name];
956
		}
957
		if ($name === 'url') {
958
			return $this->query;
959
		}
960
		if ($name === 'data') {
961
			return $this->data;
962
		}
963
		return null;
964
	}
965
 
966
/**
967
 * Array access write implementation
968
 *
969
 * @param string $name Name of the key being written
970
 * @param mixed $value The value being written.
971
 * @return void
972
 */
973
	public function offsetSet($name, $value) {
974
		$this->params[$name] = $value;
975
	}
976
 
977
/**
978
 * Array access isset() implementation
979
 *
980
 * @param string $name thing to check.
981
 * @return boolean
982
 */
983
	public function offsetExists($name) {
984
		return isset($this->params[$name]);
985
	}
986
 
987
/**
988
 * Array access unset() implementation
989
 *
990
 * @param string $name Name to unset.
991
 * @return void
992
 */
993
	public function offsetUnset($name) {
994
		unset($this->params[$name]);
995
	}
996
 
997
}