統計
| ブランチ: | リビジョン:

pictcode / lib / Cake / Network / CakeRequest.php @ 0b1b8047

履歴 | 表示 | アノテート | ダウンロード (32.009 KB)

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