Completed
Push — master ( 84dee3...084275 )
by Michal
36:03
created

Request::_processPost()   C

Complexity

Conditions 7
Paths 16

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 28
rs 6.7272
cc 7
eloc 18
nc 16
nop 1
1
<?php
2
/**
3
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
4
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
5
 *
6
 * Licensed under The MIT License
7
 * For full copyright and license information, please see the LICENSE.txt
8
 * Redistributions of files must retain the above copyright notice.
9
 *
10
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
11
 * @link          http://cakephp.org CakePHP(tm) Project
12
 * @since         2.0.0
13
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
14
 */
15
namespace Cake\Network;
16
17
use ArrayAccess;
18
use BadMethodCallException;
19
use Cake\Core\Configure;
20
use Cake\Network\Exception\MethodNotAllowedException;
21
use Cake\Utility\Hash;
22
23
/**
24
 * A class that helps wrap Request information and particulars about a single request.
25
 * Provides methods commonly used to introspect on the request headers and request body.
26
 *
27
 * Has both an Array and Object interface. You can access framework parameters using indexes:
28
 *
29
 * `$request['controller']` or `$request->controller`.
30
 */
31
class Request implements ArrayAccess
32
{
33
34
    /**
35
     * Array of parameters parsed from the URL.
36
     *
37
     * @var array
38
     */
39
    public $params = [
40
        'plugin' => null,
41
        'controller' => null,
42
        'action' => null,
43
        '_ext' => null,
44
        'pass' => []
45
    ];
46
47
    /**
48
     * Array of POST data. Will contain form data as well as uploaded files.
49
     * In PUT/PATCH/DELETE requests this property will contain the form-urlencoded
50
     * data.
51
     *
52
     * @var array
53
     */
54
    public $data = [];
55
56
    /**
57
     * Array of querystring arguments
58
     *
59
     * @var array
60
     */
61
    public $query = [];
62
63
    /**
64
     * Array of cookie data.
65
     *
66
     * @var array
67
     */
68
    public $cookies = [];
69
70
    /**
71
     * Array of environment data.
72
     *
73
     * @var array
74
     */
75
    protected $_environment = [];
76
77
    /**
78
     * The URL string used for the request.
79
     *
80
     * @var string
81
     */
82
    public $url;
83
84
    /**
85
     * Base URL path.
86
     *
87
     * @var string
88
     */
89
    public $base;
90
91
    /**
92
     * webroot path segment for the request.
93
     *
94
     * @var string
95
     */
96
    public $webroot = '/';
97
98
    /**
99
     * The full address to the current request
100
     *
101
     * @var string
102
     */
103
    public $here;
104
105
    /**
106
     * Whether or not to trust HTTP_X headers set by most load balancers.
107
     * Only set to true if your application runs behind load balancers/proxies
108
     * that you control.
109
     *
110
     * @var bool
111
     */
112
    public $trustProxy = false;
113
114
    /**
115
     * The built in detectors used with `is()` can be modified with `addDetector()`.
116
     *
117
     * There are several ways to specify a detector, see Cake\Network\Request::addDetector() for the
118
     * various formats and ways to define detectors.
119
     *
120
     * @var array
121
     */
122
    protected static $_detectors = [
123
        'get' => ['env' => 'REQUEST_METHOD', 'value' => 'GET'],
124
        'post' => ['env' => 'REQUEST_METHOD', 'value' => 'POST'],
125
        'put' => ['env' => 'REQUEST_METHOD', 'value' => 'PUT'],
126
        'patch' => ['env' => 'REQUEST_METHOD', 'value' => 'PATCH'],
127
        'delete' => ['env' => 'REQUEST_METHOD', 'value' => 'DELETE'],
128
        'head' => ['env' => 'REQUEST_METHOD', 'value' => 'HEAD'],
129
        'options' => ['env' => 'REQUEST_METHOD', 'value' => 'OPTIONS'],
130
        'ssl' => ['env' => 'HTTPS', 'options' => [1, 'on']],
131
        'ajax' => ['env' => 'HTTP_X_REQUESTED_WITH', 'value' => 'XMLHttpRequest'],
132
        'flash' => ['env' => 'HTTP_USER_AGENT', 'pattern' => '/^(Shockwave|Adobe) Flash/'],
133
        'requested' => ['param' => 'requested', 'value' => 1],
134
        'json' => ['accept' => ['application/json'], 'param' => '_ext', 'value' => 'json'],
135
        'xml' => ['accept' => ['application/xml', 'text/xml'], 'param' => '_ext', 'value' => 'xml'],
136
    ];
137
138
    /**
139
     * Instance cache for results of is(something) calls
140
     *
141
     * @var array
142
     */
143
    protected $_detectorCache = [];
144
145
    /**
146
     * Copy of php://input. Since this stream can only be read once in most SAPI's
147
     * keep a copy of it so users don't need to know about that detail.
148
     *
149
     * @var string
150
     */
151
    protected $_input = '';
152
153
    /**
154
     * Instance of a Session object relative to this request
155
     *
156
     * @var \Cake\Network\Session
157
     */
158
    protected $_session;
159
160
    /**
161
     * Wrapper method to create a new request from PHP superglobals.
162
     *
163
     * Uses the $_GET, $_POST, $_FILES, $_COOKIE, $_SERVER, $_ENV and php://input data to construct
164
     * the request.
165
     *
166
     * @return \Cake\Network\Request
167
     */
168
    public static function createFromGlobals()
169
    {
170
        list($base, $webroot) = static::_base();
171
        $sessionConfig = (array)Configure::read('Session') + [
172
            'defaults' => 'php',
173
            'cookiePath' => $webroot
174
        ];
175
176
        $config = [
177
            'query' => $_GET,
178
            'post' => $_POST,
179
            'files' => $_FILES,
180
            'cookies' => $_COOKIE,
181
            'environment' => $_SERVER + $_ENV,
182
            'base' => $base,
183
            'webroot' => $webroot,
184
            'session' => Session::create($sessionConfig)
185
        ];
186
        $config['url'] = static::_url($config);
187
        return new static($config);
188
    }
189
190
    /**
191
     * Create a new request object.
192
     *
193
     * You can supply the data as either an array or as a string.  If you use
194
     * a string you can only supply the URL for the request.  Using an array will
195
     * let you provide the following keys:
196
     *
197
     * - `post` POST data or non query string data
198
     * - `query` Additional data from the query string.
199
     * - `files` Uploaded file data formatted like $_FILES.
200
     * - `cookies` Cookies for this request.
201
     * - `environment` $_SERVER and $_ENV data.
202
     * - `url` The URL without the base path for the request.
203
     * - `base` The base URL for the request.
204
     * - `webroot` The webroot directory for the request.
205
     * - `input` The data that would come from php://input this is useful for simulating
206
     * - `session` An instance of a Session object
207
     *   requests with put, patch or delete data.
208
     *
209
     * @param string|array $config An array of request data to create a request with.
210
     */
211
    public function __construct($config = [])
212
    {
213
        if (is_string($config)) {
214
            $config = ['url' => $config];
215
        }
216
        $config += [
217
            'params' => $this->params,
218
            'query' => [],
219
            'post' => [],
220
            'files' => [],
221
            'cookies' => [],
222
            'environment' => [],
223
            'url' => '',
224
            'base' => '',
225
            'webroot' => '',
226
            'input' => null,
227
        ];
228
229
        $this->_setConfig($config);
230
    }
231
232
    /**
233
     * Process the config/settings data into properties.
234
     *
235
     * @param array $config The config data to use.
236
     * @return void
237
     */
238
    protected function _setConfig($config)
239
    {
240
        if (!empty($config['url']) && $config['url'][0] === '/') {
241
            $config['url'] = substr($config['url'], 1);
242
        }
243
244
        if (empty($config['session'])) {
245
            $config['session'] = new Session([
246
                'cookiePath' => $config['base']
247
            ]);
248
        }
249
250
        $this->url = $config['url'];
251
        $this->base = $config['base'];
252
        $this->cookies = $config['cookies'];
253
        $this->here = $this->base . '/' . $this->url;
254
        $this->webroot = $config['webroot'];
255
256
        $this->_environment = $config['environment'];
257
        if (isset($config['input'])) {
258
            $this->_input = $config['input'];
259
        }
260
        $config['post'] = $this->_processPost($config['post']);
261
        $this->data = $this->_processFiles($config['post'], $config['files']);
262
        $this->query = $this->_processGet($config['query']);
263
        $this->params = $config['params'];
264
        $this->_session = $config['session'];
265
    }
266
267
    /**
268
     * Sets the REQUEST_METHOD environment variable based on the simulated _method
269
     * HTTP override value. The 'ORIGINAL_REQUEST_METHOD' is also preserved, if you
270
     * want the read the non-simulated HTTP method the client used.
271
     *
272
     * @param array $data Array of post data.
273
     * @return array
274
     */
275
    protected function _processPost($data)
276
    {
277
        $method = $this->env('REQUEST_METHOD');
278
        $override = false;
279
280
        if (in_array($method, ['PUT', 'DELETE', 'PATCH']) &&
281
            strpos($this->contentType(), 'application/x-www-form-urlencoded') === 0
282
        ) {
283
            $data = $this->input();
284
            parse_str($data, $data);
285
        }
286
        if ($this->env('HTTP_X_HTTP_METHOD_OVERRIDE')) {
287
            $data['_method'] = $this->env('HTTP_X_HTTP_METHOD_OVERRIDE');
288
            $override = true;
289
        }
290
        $this->_environment['ORIGINAL_REQUEST_METHOD'] = $method;
291
        if (isset($data['_method'])) {
292
            $this->_environment['REQUEST_METHOD'] = $data['_method'];
293
            unset($data['_method']);
294
            $override = true;
295
        }
296
297
        if ($override && !in_array($this->_environment['REQUEST_METHOD'], ['PUT', 'POST', 'DELETE', 'PATCH'])) {
298
            $data = [];
299
        }
300
301
        return $data;
302
    }
303
304
    /**
305
     * Process the GET parameters and move things into the object.
306
     *
307
     * @param array $query The array to which the parsed keys/values are being added.
308
     * @return array An array containing the parsed querystring keys/values.
309
     */
310
    protected function _processGet($query)
311
    {
312
        $unsetUrl = '/' . str_replace(['.', ' '], '_', urldecode($this->url));
313
        unset($query[$unsetUrl]);
314
        unset($query[$this->base . $unsetUrl]);
315
        if (strpos($this->url, '?') !== false) {
316
            list(, $querystr) = explode('?', $this->url);
317
            parse_str($querystr, $queryArgs);
318
            $query += $queryArgs;
319
        }
320
        return $query;
321
    }
322
323
    /**
324
     * Get the request uri. Looks in PATH_INFO first, as this is the exact value we need prepared
325
     * by PHP. Following that, REQUEST_URI, PHP_SELF, HTTP_X_REWRITE_URL and argv are checked in that order.
326
     * Each of these server variables have the base path, and query strings stripped off
327
     *
328
     * @param array $config Configuration to set.
329
     * @return string URI The CakePHP request path that is being accessed.
330
     */
331
    protected static function _url($config)
332
    {
333
        if (!empty($_SERVER['PATH_INFO'])) {
334
            return $_SERVER['PATH_INFO'];
335
        } elseif (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '://') === false) {
336
            $uri = $_SERVER['REQUEST_URI'];
337
        } elseif (isset($_SERVER['REQUEST_URI'])) {
338
            $qPosition = strpos($_SERVER['REQUEST_URI'], '?');
339
            if ($qPosition !== false && strpos($_SERVER['REQUEST_URI'], '://') > $qPosition) {
340
                $uri = $_SERVER['REQUEST_URI'];
341
            } else {
342
                $uri = substr($_SERVER['REQUEST_URI'], strlen(Configure::read('App.fullBaseUrl')));
343
            }
344
        } elseif (isset($_SERVER['PHP_SELF'], $_SERVER['SCRIPT_NAME'])) {
345
            $uri = str_replace($_SERVER['SCRIPT_NAME'], '', $_SERVER['PHP_SELF']);
346
        } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) {
347
            $uri = $_SERVER['HTTP_X_REWRITE_URL'];
348
        } elseif ($var = env('argv')) {
349
            $uri = $var[0];
350
        }
351
352
        $base = $config['base'];
353
354 View Code Duplication
        if (strlen($base) > 0 && strpos($uri, $base) === 0) {
355
            $uri = substr($uri, strlen($base));
0 ignored issues
show
Bug introduced by
The variable $uri does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
356
        }
357
        if (strpos($uri, '?') !== false) {
358
            list($uri) = explode('?', $uri, 2);
359
        }
360
        if (empty($uri) || $uri === '/' || $uri === '//' || $uri === '/index.php') {
361
            $uri = '/';
362
        }
363
        $endsWithIndex = '/webroot/index.php';
364
        $endsWithLength = strlen($endsWithIndex);
365
        if (strlen($uri) >= $endsWithLength &&
366
            substr($uri, -$endsWithLength) === $endsWithIndex
367
        ) {
368
            $uri = '/';
369
        }
370
        return $uri;
371
    }
372
373
    /**
374
     * Returns a base URL and sets the proper webroot
375
     *
376
     * If CakePHP is called with index.php in the URL even though
377
     * URL Rewriting is activated (and thus not needed) it swallows
378
     * the unnecessary part from $base to prevent issue #3318.
379
     *
380
     * @return array Base URL, webroot dir ending in /
381
     * @link https://cakephp.lighthouseapp.com/projects/42648-cakephp/tickets/3318
382
     */
383
    protected static function _base()
384
    {
385
        $base = $webroot = $baseUrl = null;
386
        $config = Configure::read('App');
387
        extract($config);
388
389
        if ($base !== false && $base !== null) {
390
            return [$base, $base . '/'];
391
        }
392
393
        if (!$baseUrl) {
394
            $base = dirname(env('PHP_SELF'));
395
            // Clean up additional / which cause following code to fail..
396
            $base = preg_replace('#/+#', '/', $base);
397
398
            $indexPos = strpos($base, '/' . $webroot . '/index.php');
399
            if ($indexPos !== false) {
400
                $base = substr($base, 0, $indexPos) . '/' . $webroot;
401
            }
402
            if ($webroot === basename($base)) {
403
                $base = dirname($base);
404
            }
405
406
            if ($base === DS || $base === '.') {
407
                $base = '';
408
            }
409
            $base = implode('/', array_map('rawurlencode', explode('/', $base)));
410
            return [$base, $base . '/'];
411
        }
412
413
        $file = '/' . basename($baseUrl);
414
        $base = dirname($baseUrl);
415
416
        if ($base === DS || $base === '.') {
417
            $base = '';
418
        }
419
        $webrootDir = $base . '/';
420
421
        $docRoot = env('DOCUMENT_ROOT');
422
        $docRootContainsWebroot = strpos($docRoot, $webroot);
423
424
        if (!empty($base) || !$docRootContainsWebroot) {
425
            if (strpos($webrootDir, '/' . $webroot . '/') === false) {
426
                $webrootDir .= $webroot . '/';
427
            }
428
        }
429
        return [$base . $file, $webrootDir];
430
    }
431
432
    /**
433
     * Process uploaded files and move things onto the post data.
434
     *
435
     * @param array $post Post data to merge files onto.
436
     * @param array $files Uploaded files to merge in.
437
     * @return array merged post + file data.
438
     */
439
    protected function _processFiles($post, $files)
440
    {
441
        if (is_array($files)) {
442
            foreach ($files as $key => $data) {
443
                if (isset($data['tmp_name']) && is_string($data['tmp_name'])) {
444
                    $post[$key] = $data;
445
                } else {
446
                    $keyData = isset($post[$key]) ? $post[$key] : [];
447
                    $post[$key] = $this->_processFileData($keyData, $data);
448
                }
449
            }
450
        }
451
        return $post;
452
    }
453
454
    /**
455
     * Recursively walks the FILES array restructuring the data
456
     * into something sane and usable.
457
     *
458
     * @param array $data The data being built
459
     * @param array $post The post data being traversed
460
     * @param string $path The dot separated path to insert $data into.
461
     * @param string $field The terminal field in the path. This is one of the
462
     *   $_FILES properties e.g. name, tmp_name, size, error
463
     * @return array The restructured FILES data
464
     */
465
    protected function _processFileData($data, $post, $path = '', $field = '')
466
    {
467
        foreach ($post as $key => $fields) {
468
            $newField = $field;
469
            $newPath = $path;
470
            if ($path === '' && $newField === '') {
471
                $newField = $key;
472
            }
473
            if ($field === $newField) {
474
                $newPath .= '.' . $key;
475
            }
476
            if (is_array($fields)) {
477
                $data = $this->_processFileData($data, $fields, $newPath, $newField);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->_processFileData(...s, $newPath, $newField) on line 477 can also be of type null; however, Cake\Network\Request::_processFileData() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
478
            } else {
479
                $newPath = trim($newPath . '.' . $field, '.');
480
                $data = Hash::insert($data, $newPath, $fields);
0 ignored issues
show
Bug introduced by
It seems like $data defined by \Cake\Utility\Hash::inse...ata, $newPath, $fields) on line 480 can also be of type null; however, Cake\Utility\Hash::insert() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
481
            }
482
        }
483
        return $data;
484
    }
485
486
    /**
487
     * Get the content type used in this request.
488
     *
489
     * @return string
490
     */
491
    public function contentType()
492
    {
493
        $type = $this->env('CONTENT_TYPE');
494
        if ($type) {
495
            return $type;
496
        }
497
        return $this->env('HTTP_CONTENT_TYPE');
498
    }
499
500
    /**
501
     * Returns the instance of the Session object for this request
502
     *
503
     * If a session object is passed as first argument it will be set as
504
     * the session to use for this request
505
     *
506
     * @param \Cake\Network\Session|null $session the session object to use
507
     * @return \Cake\Network\Session
508
     */
509
    public function session(Session $session = null)
510
    {
511
        if ($session === null) {
512
            return $this->_session;
513
        }
514
        return $this->_session = $session;
515
    }
516
517
    /**
518
     * Get the IP the client is using, or says they are using.
519
     *
520
     * @return string The client IP.
521
     */
522
    public function clientIp()
523
    {
524
        if ($this->trustProxy && $this->env('HTTP_X_FORWARDED_FOR')) {
525
            $ipaddr = preg_replace('/(?:,.*)/', '', $this->env('HTTP_X_FORWARDED_FOR'));
526
        } else {
527
            if ($this->env('HTTP_CLIENT_IP')) {
528
                $ipaddr = $this->env('HTTP_CLIENT_IP');
529
            } else {
530
                $ipaddr = $this->env('REMOTE_ADDR');
531
            }
532
        }
533
534
        if ($this->env('HTTP_CLIENTADDRESS')) {
535
            $tmpipaddr = $this->env('HTTP_CLIENTADDRESS');
536
537
            if (!empty($tmpipaddr)) {
538
                $ipaddr = preg_replace('/(?:,.*)/', '', $tmpipaddr);
539
            }
540
        }
541
        return trim($ipaddr);
542
    }
543
544
    /**
545
     * Returns the referer that referred this request.
546
     *
547
     * @param bool $local Attempt to return a local address.
548
     *   Local addresses do not contain hostnames.
549
     * @return string The referring address for this request.
550
     */
551
    public function referer($local = false)
552
    {
553
        $ref = $this->env('HTTP_REFERER');
554
555
        $base = Configure::read('App.fullBaseUrl') . $this->webroot;
556
        if (!empty($ref) && !empty($base)) {
557
            if ($local && strpos($ref, $base) === 0) {
558
                $ref = substr($ref, strlen($base));
559
                if ($ref[0] !== '/') {
560
                    $ref = '/' . $ref;
561
                }
562
                return $ref;
563
            } elseif (!$local) {
564
                return $ref;
565
            }
566
        }
567
        return '/';
568
    }
569
570
    /**
571
     * Missing method handler, handles wrapping older style isAjax() type methods
572
     *
573
     * @param string $name The method called
574
     * @param array $params Array of parameters for the method call
575
     * @return mixed
576
     * @throws \BadMethodCallException when an invalid method is called.
577
     */
578
    public function __call($name, $params)
579
    {
580
        if (strpos($name, 'is') === 0) {
581
            $type = strtolower(substr($name, 2));
582
            return $this->is($type);
583
        }
584
        throw new BadMethodCallException(sprintf('Method %s does not exist', $name));
585
    }
586
587
    /**
588
     * Magic get method allows access to parsed routing parameters directly on the object.
589
     *
590
     * Allows access to `$this->params['controller']` via `$this->controller`
591
     *
592
     * @param string $name The property being accessed.
593
     * @return mixed Either the value of the parameter or null.
594
     */
595
    public function __get($name)
596
    {
597
        if (isset($this->params[$name])) {
598
            return $this->params[$name];
599
        }
600
        return null;
601
    }
602
603
    /**
604
     * Magic isset method allows isset/empty checks
605
     * on routing parameters.
606
     *
607
     * @param string $name The property being accessed.
608
     * @return bool Existence
609
     */
610
    public function __isset($name)
611
    {
612
        return isset($this->params[$name]);
613
    }
614
615
    /**
616
     * Check whether or not a Request is a certain type.
617
     *
618
     * Uses the built in detection rules as well as additional rules
619
     * defined with Cake\Network\CakeRequest::addDetector(). Any detector can be called
620
     * as `is($type)` or `is$Type()`.
621
     *
622
     * @param string|array $type The type of request you want to check. If an array
623
     *   this method will return true if the request matches any type.
624
     * @return bool Whether or not the request is the type you are checking.
625
     */
626
    public function is($type)
627
    {
628
        if (is_array($type)) {
629
            $result = array_map([$this, 'is'], $type);
630
            return count(array_filter($result)) > 0;
631
        }
632
633
        $type = strtolower($type);
634
        if (!isset(static::$_detectors[$type])) {
635
            return false;
636
        }
637
638
        if (!isset($this->_detectorCache[$type])) {
639
            $this->_detectorCache[$type] = $this->_is($type);
640
        }
641
642
        return $this->_detectorCache[$type];
643
    }
644
645
    /**
646
     * Clears the instance detector cache, used by the is() function
647
     *
648
     * @return void
649
     */
650
    public function clearDetectorCache()
651
    {
652
        $this->_detectorCache = [];
653
    }
654
655
    /**
656
     * Worker for the public is() function
657
     *
658
     * @param string|array $type The type of request you want to check. If an array
659
     *   this method will return true if the request matches any type.
660
     * @return bool Whether or not the request is the type you are checking.
661
     */
662
    protected function _is($type)
663
    {
664
        $detect = static::$_detectors[$type];
665
        if (is_callable($detect)) {
666
            return call_user_func($detect, $this);
667
        }
668
        if (isset($detect['env']) && $this->_environmentDetector($detect)) {
669
            return true;
670
        }
671
        if (isset($detect['header']) && $this->_headerDetector($detect)) {
672
            return true;
673
        }
674
        if (isset($detect['accept']) && $this->_acceptHeaderDetector($detect)) {
675
            return true;
676
        }
677
        if (isset($detect['param']) && $this->_paramDetector($detect)) {
678
            return true;
679
        }
680
        return false;
681
    }
682
683
    /**
684
     * Detects if a specific accept header is present.
685
     *
686
     * @param array $detect Detector options array.
687
     * @return bool Whether or not the request is the type you are checking.
688
     */
689
    protected function _acceptHeaderDetector($detect)
690
    {
691
        $acceptHeaders = explode(',', $this->env('HTTP_ACCEPT'));
692
        foreach ($detect['accept'] as $header) {
693
            if (in_array($header, $acceptHeaders)) {
694
                return true;
695
            }
696
        }
697
        return false;
698
    }
699
700
    /**
701
     * Detects if a specific header is present.
702
     *
703
     * @param array $detect Detector options array.
704
     * @return bool Whether or not the request is the type you are checking.
705
     */
706
    protected function _headerDetector($detect)
707
    {
708
        foreach ($detect['header'] as $header => $value) {
709
            $header = $this->env('http_' . $header);
710
            if ($header !== null) {
711
                if (!is_string($value) && !is_bool($value) && is_callable($value)) {
712
                    return call_user_func($value, $header);
713
                }
714
                return ($header === $value);
715
            }
716
        }
717
        return false;
718
    }
719
720
    /**
721
     * Detects if a specific request parameter is present.
722
     *
723
     * @param array $detect Detector options array.
724
     * @return bool Whether or not the request is the type you are checking.
725
     */
726
    protected function _paramDetector($detect)
727
    {
728
        $key = $detect['param'];
729 View Code Duplication
        if (isset($detect['value'])) {
730
            $value = $detect['value'];
731
            return isset($this->params[$key]) ? $this->params[$key] == $value : false;
732
        }
733 View Code Duplication
        if (isset($detect['options'])) {
734
            return isset($this->params[$key]) ? in_array($this->params[$key], $detect['options']) : false;
735
        }
736
        return false;
737
    }
738
739
    /**
740
     * Detects if a specific environment variable is present.
741
     *
742
     * @param array $detect Detector options array.
743
     * @return bool Whether or not the request is the type you are checking.
744
     */
745
    protected function _environmentDetector($detect)
746
    {
747
        if (isset($detect['env'])) {
748
            if (isset($detect['value'])) {
749
                return $this->env($detect['env']) == $detect['value'];
750
            }
751
            if (isset($detect['pattern'])) {
752
                return (bool)preg_match($detect['pattern'], $this->env($detect['env']));
753
            }
754
            if (isset($detect['options'])) {
755
                $pattern = '/' . implode('|', $detect['options']) . '/i';
756
                return (bool)preg_match($pattern, $this->env($detect['env']));
757
            }
758
        }
759
        return false;
760
    }
761
762
    /**
763
     * Check that a request matches all the given types.
764
     *
765
     * Allows you to test multiple types and union the results.
766
     * See Request::is() for how to add additional types and the
767
     * built-in types.
768
     *
769
     * @param array $types The types to check.
770
     * @return bool Success.
771
     * @see \Cake\Network\Request::is()
772
     */
773
    public function isAll(array $types)
774
    {
775
        $result = array_filter(array_map([$this, 'is'], $types));
776
        return count($result) === count($types);
777
    }
778
779
    /**
780
     * Add a new detector to the list of detectors that a request can use.
781
     * There are several different formats and types of detectors that can be set.
782
     *
783
     * ### Callback detectors
784
     *
785
     * Callback detectors allow you to provide a callable to handle the check.
786
     * The callback will receive the request object as its only parameter.
787
     *
788
     * ```
789
     * addDetector('custom', function ($request) { //Return a boolean });
790
     * addDetector('custom', ['SomeClass', 'somemethod']);
791
     * ```
792
     *
793
     * ### Environment value comparison
794
     *
795
     * An environment value comparison, compares a value fetched from `env()` to a known value
796
     * the environment value is equality checked against the provided value.
797
     *
798
     * e.g `addDetector('post', ['env' => 'REQUEST_METHOD', 'value' => 'POST'])`
799
     *
800
     * ### Pattern value comparison
801
     *
802
     * Pattern value comparison allows you to compare a value fetched from `env()` to a regular expression.
803
     *
804
     * ```
805
     * addDetector('iphone', ['env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i']);
806
     * ```
807
     *
808
     * ### Option based comparison
809
     *
810
     * Option based comparisons use a list of options to create a regular expression. Subsequent calls
811
     * to add an already defined options detector will merge the options.
812
     *
813
     * ```
814
     * addDetector('mobile', ['env' => 'HTTP_USER_AGENT', 'options' => ['Fennec']]);
815
     * ```
816
     *
817
     * ### Request parameter detectors
818
     *
819
     * Allows for custom detectors on the request parameters.
820
     *
821
     * e.g `addDetector('requested', ['param' => 'requested', 'value' => 1]`
822
     *
823
     * You can also make parameter detectors that accept multiple values
824
     * using the `options` key. This is useful when you want to check
825
     * if a request parameter is in a list of options.
826
     *
827
     * `addDetector('extension', ['param' => 'ext', 'options' => ['pdf', 'csv']]`
828
     *
829
     * @param string $name The name of the detector.
830
     * @param callable|array $callable A callable or options array for the detector definition.
831
     * @return void
832
     */
833
    public static function addDetector($name, $callable)
834
    {
835
        $name = strtolower($name);
836
        if (is_callable($callable)) {
837
            static::$_detectors[$name] = $callable;
838
            return;
839
        }
840
        if (isset(static::$_detectors[$name], $callable['options'])) {
841
            $callable = Hash::merge(static::$_detectors[$name], $callable);
842
        }
843
        static::$_detectors[$name] = $callable;
844
    }
845
846
    /**
847
     * Add parameters to the request's parsed parameter set. This will overwrite any existing parameters.
848
     * This modifies the parameters available through `$request->params`.
849
     *
850
     * @param array $params Array of parameters to merge in
851
     * @return $this The current object, you can chain this method.
852
     */
853
    public function addParams(array $params)
854
    {
855
        $this->params = array_merge($this->params, $params);
856
        return $this;
857
    }
858
859
    /**
860
     * Add paths to the requests' paths vars. This will overwrite any existing paths.
861
     * Provides an easy way to modify, here, webroot and base.
862
     *
863
     * @param array $paths Array of paths to merge in
864
     * @return $this The current object, you can chain this method.
865
     */
866
    public function addPaths(array $paths)
867
    {
868
        foreach (['webroot', 'here', 'base'] as $element) {
869
            if (isset($paths[$element])) {
870
                $this->{$element} = $paths[$element];
871
            }
872
        }
873
        return $this;
874
    }
875
876
    /**
877
     * Get the value of the current requests URL. Will include querystring arguments.
878
     *
879
     * @param bool $base Include the base path, set to false to trim the base path off.
880
     * @return string The current request URL including query string args.
881
     */
882
    public function here($base = true)
883
    {
884
        $url = $this->here;
885
        if (!empty($this->query)) {
886
            $url .= '?' . http_build_query($this->query, null, '&');
887
        }
888
        if (!$base) {
889
            $url = preg_replace('/^' . preg_quote($this->base, '/') . '/', '', $url, 1);
890
        }
891
        return $url;
892
    }
893
894
    /**
895
     * Read an HTTP header from the Request information.
896
     *
897
     * @param string $name Name of the header you want.
898
     * @return string|null Either null on no header being set or the value of the header.
899
     */
900
    public function header($name)
901
    {
902
        $name = 'HTTP_' . str_replace('-', '_', $name);
903
        return $this->env($name);
904
    }
905
906
    /**
907
     * Get the HTTP method used for this request.
908
     * There are a few ways to specify a method.
909
     *
910
     * - If your client supports it you can use native HTTP methods.
911
     * - You can set the HTTP-X-Method-Override header.
912
     * - You can submit an input with the name `_method`
913
     *
914
     * Any of these 3 approaches can be used to set the HTTP method used
915
     * by CakePHP internally, and will effect the result of this method.
916
     *
917
     * @return string The name of the HTTP method used.
918
     */
919
    public function method()
920
    {
921
        return $this->env('REQUEST_METHOD');
922
    }
923
924
    /**
925
     * Get the host that the request was handled on.
926
     *
927
     * @return string
928
     */
929
    public function host()
930
    {
931
        if ($this->trustProxy && $this->env('HTTP_X_FORWARDED_HOST')) {
932
            return $this->env('HTTP_X_FORWARDED_HOST');
933
        }
934
        return $this->env('HTTP_HOST');
935
    }
936
937
    /**
938
     * Get the port the request was handled on.
939
     *
940
     * @return string
941
     */
942
    public function port()
943
    {
944
        if ($this->trustProxy && $this->env('HTTP_X_FORWARDED_PORT')) {
945
            return $this->env('HTTP_X_FORWARDED_PORT');
946
        }
947
        return $this->env('SERVER_PORT');
948
    }
949
950
    /**
951
     * Get the current url scheme used for the request.
952
     *
953
     * e.g. 'http', or 'https'
954
     *
955
     * @return string The scheme used for the request.
956
     */
957
    public function scheme()
958
    {
959
        if ($this->trustProxy && $this->env('HTTP_X_FORWARDED_PROTO')) {
960
            return $this->env('HTTP_X_FORWARDED_PROTO');
961
        }
962
        return $this->env('HTTPS') ? 'https' : 'http';
963
    }
964
965
    /**
966
     * Get the domain name and include $tldLength segments of the tld.
967
     *
968
     * @param int $tldLength Number of segments your tld contains. For example: `example.com` contains 1 tld.
969
     *   While `example.co.uk` contains 2.
970
     * @return string Domain name without subdomains.
971
     */
972
    public function domain($tldLength = 1)
973
    {
974
        $segments = explode('.', $this->host());
975
        $domain = array_slice($segments, -1 * ($tldLength + 1));
976
        return implode('.', $domain);
977
    }
978
979
    /**
980
     * Get the subdomains for a host.
981
     *
982
     * @param int $tldLength Number of segments your tld contains. For example: `example.com` contains 1 tld.
983
     *   While `example.co.uk` contains 2.
984
     * @return array An array of subdomains.
985
     */
986
    public function subdomains($tldLength = 1)
987
    {
988
        $segments = explode('.', $this->host());
989
        return array_slice($segments, 0, -1 * ($tldLength + 1));
990
    }
991
992
    /**
993
     * Find out which content types the client accepts or check if they accept a
994
     * particular type of content.
995
     *
996
     * #### Get all types:
997
     *
998
     * ```
999
     * $this->request->accepts();
1000
     * ```
1001
     *
1002
     * #### Check for a single type:
1003
     *
1004
     * ```
1005
     * $this->request->accepts('application/json');
1006
     * ```
1007
     *
1008
     * This method will order the returned content types by the preference values indicated
1009
     * by the client.
1010
     *
1011
     * @param string|null $type The content type to check for. Leave null to get all types a client accepts.
1012
     * @return array|bool Either an array of all the types the client accepts or a boolean if they accept the
1013
     *   provided type.
1014
     */
1015
    public function accepts($type = null)
1016
    {
1017
        $raw = $this->parseAccept();
1018
        $accept = [];
1019
        foreach ($raw as $types) {
1020
            $accept = array_merge($accept, $types);
1021
        }
1022
        if ($type === null) {
1023
            return $accept;
1024
        }
1025
        return in_array($type, $accept);
1026
    }
1027
1028
    /**
1029
     * Parse the HTTP_ACCEPT header and return a sorted array with content types
1030
     * as the keys, and pref values as the values.
1031
     *
1032
     * Generally you want to use Cake\Network\Request::accept() to get a simple list
1033
     * of the accepted content types.
1034
     *
1035
     * @return array An array of prefValue => [content/types]
1036
     */
1037
    public function parseAccept()
1038
    {
1039
        return $this->_parseAcceptWithQualifier($this->header('accept'));
0 ignored issues
show
Bug introduced by
It seems like $this->header('accept') targeting Cake\Network\Request::header() can also be of type null or object<Cake\Network\Request>; however, Cake\Network\Request::_parseAcceptWithQualifier() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1040
    }
1041
1042
    /**
1043
     * Get the languages accepted by the client, or check if a specific language is accepted.
1044
     *
1045
     * Get the list of accepted languages:
1046
     *
1047
     * ``` \Cake\Network\Request::acceptLanguage(); ```
1048
     *
1049
     * Check if a specific language is accepted:
1050
     *
1051
     * ``` \Cake\Network\Request::acceptLanguage('es-es'); ```
1052
     *
1053
     * @param string|null $language The language to test.
1054
     * @return array|bool If a $language is provided, a boolean. Otherwise the array of accepted languages.
1055
     */
1056
    public function acceptLanguage($language = null)
1057
    {
1058
        $raw = $this->_parseAcceptWithQualifier($this->header('Accept-Language'));
0 ignored issues
show
Bug introduced by
It seems like $this->header('Accept-Language') targeting Cake\Network\Request::header() can also be of type null or object<Cake\Network\Request>; however, Cake\Network\Request::_parseAcceptWithQualifier() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1059
        $accept = [];
1060
        foreach ($raw as $languages) {
1061
            foreach ($languages as &$lang) {
1062
                if (strpos($lang, '_')) {
1063
                    $lang = str_replace('_', '-', $lang);
1064
                }
1065
                $lang = strtolower($lang);
1066
            }
1067
            $accept = array_merge($accept, $languages);
1068
        }
1069
        if ($language === null) {
1070
            return $accept;
1071
        }
1072
        return in_array(strtolower($language), $accept);
1073
    }
1074
1075
    /**
1076
     * Parse Accept* headers with qualifier options.
1077
     *
1078
     * Only qualifiers will be extracted, any other accept extensions will be
1079
     * discarded as they are not frequently used.
1080
     *
1081
     * @param string $header Header to parse.
1082
     * @return array
1083
     */
1084
    protected function _parseAcceptWithQualifier($header)
1085
    {
1086
        $accept = [];
1087
        $header = explode(',', $header);
1088
        foreach (array_filter($header) as $value) {
1089
            $prefValue = '1.0';
1090
            $value = trim($value);
1091
1092
            $semiPos = strpos($value, ';');
1093 View Code Duplication
            if ($semiPos !== false) {
1094
                $params = explode(';', $value);
1095
                $value = trim($params[0]);
1096
                foreach ($params as $param) {
1097
                    $qPos = strpos($param, 'q=');
1098
                    if ($qPos !== false) {
1099
                        $prefValue = substr($param, $qPos + 2);
1100
                    }
1101
                }
1102
            }
1103
1104
            if (!isset($accept[$prefValue])) {
1105
                $accept[$prefValue] = [];
1106
            }
1107
            if ($prefValue) {
1108
                $accept[$prefValue][] = $value;
1109
            }
1110
        }
1111
        krsort($accept);
1112
        return $accept;
1113
    }
1114
1115
    /**
1116
     * Provides a read accessor for `$this->query`. Allows you
1117
     * to use a syntax similar to `CakeSession` for reading URL query data.
1118
     *
1119
     * @param string $name Query string variable name
1120
     * @return mixed The value being read
1121
     */
1122
    public function query($name)
1123
    {
1124
        return Hash::get($this->query, $name);
1125
    }
1126
1127
    /**
1128
     * Provides a read/write accessor for `$this->data`. Allows you
1129
     * to use a syntax similar to `Cake\Model\Datasource\Session` for reading post data.
1130
     *
1131
     * ### Reading values.
1132
     *
1133
     * ```
1134
     * $request->data('Post.title');
1135
     * ```
1136
     *
1137
     * When reading values you will get `null` for keys/values that do not exist.
1138
     *
1139
     * ### Writing values
1140
     *
1141
     * ```
1142
     * $request->data('Post.title', 'New post!');
1143
     * ```
1144
     *
1145
     * You can write to any value, even paths/keys that do not exist, and the arrays
1146
     * will be created for you.
1147
     *
1148
     * @param string|null $name Dot separated name of the value to read/write
1149
     * @return mixed|$this Either the value being read, or this so you can chain consecutive writes.
1150
     */
1151
    public function data($name = null)
1152
    {
1153
        $args = func_get_args();
1154 View Code Duplication
        if (count($args) === 2) {
1155
            $this->data = Hash::insert($this->data, $name, $args[1]);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Cake\Utility\Hash::inse...>data, $name, $args[1]) can be null. However, the property $data is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
1156
            return $this;
1157
        }
1158
        if ($name !== null) {
1159
            return Hash::get($this->data, $name);
1160
        }
1161
        return $this->data;
1162
    }
1163
1164
    /**
1165
     * Safely access the values in $this->params.
1166
     *
1167
     * @param string $name The name of the parameter to get.
1168
     * @return mixed|$this The value of the provided parameter. Will
1169
     *   return false if the parameter doesn't exist or is falsey.
1170
     */
1171
    public function param($name)
1172
    {
1173
        $args = func_get_args();
1174 View Code Duplication
        if (count($args) === 2) {
1175
            $this->params = Hash::insert($this->params, $name, $args[1]);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Cake\Utility\Hash::inse...arams, $name, $args[1]) can be null. However, the property $params is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
1176
            return $this;
1177
        }
1178
        if (!isset($this->params[$name])) {
1179
            return Hash::get($this->params, $name, false);
1180
        }
1181
        return $this->params[$name];
1182
    }
1183
1184
    /**
1185
     * Read data from `php://input`. Useful when interacting with XML or JSON
1186
     * request body content.
1187
     *
1188
     * Getting input with a decoding function:
1189
     *
1190
     * ```
1191
     * $this->request->input('json_decode');
1192
     * ```
1193
     *
1194
     * Getting input using a decoding function, and additional params:
1195
     *
1196
     * ```
1197
     * $this->request->input('Xml::build', ['return' => 'DOMDocument']);
1198
     * ```
1199
     *
1200
     * Any additional parameters are applied to the callback in the order they are given.
1201
     *
1202
     * @param string|null $callback A decoding callback that will convert the string data to another
1203
     *     representation. Leave empty to access the raw input data. You can also
1204
     *     supply additional parameters for the decoding callback using var args, see above.
1205
     * @return string The decoded/processed request data.
1206
     */
1207
    public function input($callback = null)
1208
    {
1209
        $input = $this->_readInput();
1210
        $args = func_get_args();
1211
        if (!empty($args)) {
1212
            $callback = array_shift($args);
1213
            array_unshift($args, $input);
1214
            return call_user_func_array($callback, $args);
1215
        }
1216
        return $input;
1217
    }
1218
1219
    /**
1220
     * Read cookie data from the request's cookie data.
1221
     *
1222
     * @param string $key The key you want to read.
1223
     * @return null|string Either the cookie value, or null if the value doesn't exist.
1224
     */
1225
    public function cookie($key)
1226
    {
1227
        if (isset($this->cookies[$key])) {
1228
            return $this->cookies[$key];
1229
        }
1230
        return null;
1231
    }
1232
1233
    /**
1234
     * Get/Set value from the request's environment data.
1235
     * Fallback to using env() if key not set in $environment property.
1236
     *
1237
     * @param string $key The key you want to read/write from/to.
1238
     * @param string|null $value Value to set. Default null.
1239
     * @param string|null $default Default value when trying to retrieve an environment
1240
     *   variable's value that does not exist. The value parameter must be null.
1241
     * @return $this|string|null This instance if used as setter,
1242
     *   if used as getter either the environment value, or null if the value doesn't exist.
1243
     */
1244
    public function env($key, $value = null, $default = null)
1245
    {
1246
        if ($value !== null) {
1247
            $this->_environment[$key] = $value;
1248
            $this->clearDetectorCache();
1249
            return $this;
1250
        }
1251
1252
        $key = strtoupper($key);
1253
        if (!array_key_exists($key, $this->_environment)) {
1254
            $this->_environment[$key] = env($key);
1255
        }
1256
        return $this->_environment[$key] !== null ? $this->_environment[$key] : $default;
1257
    }
1258
1259
    /**
1260
     * Allow only certain HTTP request methods, if the request method does not match
1261
     * a 405 error will be shown and the required "Allow" response header will be set.
1262
     *
1263
     * Example:
1264
     *
1265
     * $this->request->allowMethod('post');
1266
     * or
1267
     * $this->request->allowMethod(['post', 'delete']);
1268
     *
1269
     * If the request would be GET, response header "Allow: POST, DELETE" will be set
1270
     * and a 405 error will be returned.
1271
     *
1272
     * @param string|array $methods Allowed HTTP request methods.
1273
     * @return bool true
1274
     * @throws \Cake\Network\Exception\MethodNotAllowedException
1275
     */
1276
    public function allowMethod($methods)
1277
    {
1278
        $methods = (array)$methods;
1279
        foreach ($methods as $method) {
1280
            if ($this->is($method)) {
1281
                return true;
1282
            }
1283
        }
1284
        $allowed = strtoupper(implode(', ', $methods));
1285
        $e = new MethodNotAllowedException();
1286
        $e->responseHeader('Allow', $allowed);
1287
        throw $e;
1288
    }
1289
1290
    /**
1291
     * Read data from php://input, mocked in tests.
1292
     *
1293
     * @return string contents of php://input
1294
     */
1295
    protected function _readInput()
1296
    {
1297
        if (empty($this->_input)) {
1298
            $fh = fopen('php://input', 'r');
1299
            $content = stream_get_contents($fh);
1300
            fclose($fh);
1301
            $this->_input = $content;
1302
        }
1303
        return $this->_input;
1304
    }
1305
1306
    /**
1307
     * Modify data originally from `php://input`. Useful for altering json/xml data
1308
     * in middleware or DispatcherFilters before it gets to RequestHandlerComponent
1309
     *
1310
     * @param string $input A string to replace original parsed data from input()
1311
     * @return void
1312
     */
1313
    public function setInput($input)
1314
    {
1315
        $this->_input = $input;
1316
    }
1317
1318
    /**
1319
     * Array access read implementation
1320
     *
1321
     * @param string $name Name of the key being accessed.
1322
     * @return mixed
1323
     */
1324
    public function offsetGet($name)
1325
    {
1326
        if (isset($this->params[$name])) {
1327
            return $this->params[$name];
1328
        }
1329
        if ($name === 'url') {
1330
            return $this->query;
1331
        }
1332
        if ($name === 'data') {
1333
            return $this->data;
1334
        }
1335
        return null;
1336
    }
1337
1338
    /**
1339
     * Array access write implementation
1340
     *
1341
     * @param string $name Name of the key being written
1342
     * @param mixed $value The value being written.
1343
     * @return void
1344
     */
1345
    public function offsetSet($name, $value)
1346
    {
1347
        $this->params[$name] = $value;
1348
    }
1349
1350
    /**
1351
     * Array access isset() implementation
1352
     *
1353
     * @param string $name thing to check.
1354
     * @return bool
1355
     */
1356
    public function offsetExists($name)
1357
    {
1358
        return isset($this->params[$name]);
1359
    }
1360
1361
    /**
1362
     * Array access unset() implementation
1363
     *
1364
     * @param string $name Name to unset.
1365
     * @return void
1366
     */
1367
    public function offsetUnset($name)
1368
    {
1369
        unset($this->params[$name]);
1370
    }
1371
}
1372