Passed
Push — master ( ebe470...22e18b )
by Sebastian
02:56
created

Request::setBaseURL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 4
rs 10
1
<?php
2
/**
3
 * File containing the {@link Request} class.
4
 * @package Application Utils
5
 * @subpackage Request
6
 * @see Request
7
 */
8
9
namespace AppUtils;
10
11
/**
12
 * Request management: wrapper around request variables with validation
13
 * capabilities and overall easier and more robust request variable handling.
14
 *
15
 * Usage:
16
 *
17
 * // get a parameter. If it does not exist, returns null.
18
 * $request->getParam('name');
19
 *
20
 * // get a parameter and specifiy the default value to return if it does not exist.
21
 * $request->getParam('name', 'Default value');
22
 *
23
 * // register a parameter to specify its validation: if the existing
24
 * // value does not match the type, it will be considered inexistent.
25
 * $request->registerParam('name')->setInteger();
26
 *
27
 * @package Application Utils
28
 * @subpackage Request
29
 * @author Sebastian Mordziol <[email protected]>
30
 */
31
class Request
32
{
33
    const ERROR_MISSING_OR_INVALID_PARAMETER = 97001;
34
    
35
    const ERROR_PARAM_NOT_REGISTERED = 97002;
36
    
37
    /**
38
     * @var Request
39
     */
40
    protected static $instance;
41
    
42
   /**
43
    * @var string
44
    */
45
    protected $baseURL = '';
46
    
47
    public function __construct()
48
    {
49
        self::$instance = $this;
50
        
51
        $this->init();
52
    }
53
    
54
   /**
55
    * Can be extended in a subclass, to avoid
56
    * redefining the constructor.
57
    */
58
    protected function init()
59
    {
60
        
61
    }
62
    
63
    /**
64
     * @return Request
65
     */
66
    public static function getInstance()
67
    {
68
        return self::$instance;
69
    }
70
    
71
    /**
72
     * Stores registered parameter objects.
73
     * @var Request_Param[]
74
     * @see registerParam()
75
     */
76
    protected $knownParams = array();
77
    
78
    /**
79
     * Retrieves the value of a request parameter. Note that these values
80
     * are NOT validated unless they have been specifically registered
81
     * using the {@link registerParam()} method.
82
     *
83
     * If the request parameter has not been set or its value is empty,
84
     * the specified default value is returned.
85
     *
86
     * @param string $name
87
     * @param mixed $default
88
     * @return mixed
89
     */
90
    public function getParam($name, $default = null)
91
    {
92
        $value = $default;
93
        if(isset($_REQUEST[$name])) {
94
            $value = $_REQUEST[$name];
95
        }
96
        
97
        if(isset($this->knownParams[$name])) {
98
            $value = $this->knownParams[$name]->validate($value);
99
        }
100
        
101
        return $value;
102
    }
103
    
104
    public function getParams()
105
    {
106
        return $_REQUEST;
107
    }
108
    
109
    /**
110
     * Builds an URL to refresh the current page: includes all currently
111
     * specified request variables, with the option to overwrite/specify
112
     * new ones via the params parameter.
113
     *
114
     * @param array $params
115
     * @param string[] $exclude Names of parameters to exclude from the refresh URL.
116
     * @return string
117
     */
118
    public function buildRefreshURL($params = array(), $exclude = array())
119
    {
120
        $params = $this->getRefreshParams($params, $exclude);
121
        
122
        $dispatcher = $this->getDispatcher();
123
        
124
        return $this->buildURL($params, $dispatcher);
125
    }
126
    
127
   /**
128
    * Retrieves the name of the current dispatcher script / page.
129
    * This is made to be extended and implemented in a subclass.
130
    * 
131
    * @return string
132
    */
133
    public function getDispatcher() : string
134
    {
135
        return '';
136
    }
137
    
138
    public function getRefreshParams($params = array(), $exclude = array())
139
    {
140
        if(empty($params)) { $params = array(); }
141
        if(empty($exclude)) { $exclude = array(); }
142
        
143
        $vars = $_REQUEST;
144
145
        $exclude[] = session_name();
146
        $exclude[] = 'ZDEDebuggerPresent';
147
        
148
        $exclude = array_merge($exclude, $this->getExcludeParams());
149
        
150
        foreach ($exclude as $name) {
151
            if (isset($vars[$name])) {
152
                unset($vars[$name]);
153
            }
154
        }
155
        
156
        $names = array_keys($vars);
157
        
158
        // remove the quickform form variable if present, to 
159
        // avoid redirect loops when using the refresh URL in
160
        // a page in which a form has been submitted.
161
        foreach($names as $name) {
162
            if(strstr($name, '_qf__')) {
163
                unset($vars[$name]);
164
                break;
165
            }
166
        }
167
        
168
        // to allow specifiying even exluded parameters
169
        $params = array_merge($vars, $params);
170
        
171
        return $params;
172
    }
173
    
174
    public function getExcludeParams()
175
    {
176
        return array();
177
    }
178
    
179
    /**
180
     * Builds an application URL using the specified parameters: returns
181
     * an absolute URL to the main dispatcher with the specified parameters.
182
     * Not specifiying any parameters returns the absolute URL to the
183
     * application, without ending slash.
184
     *
185
     * @param array $params
186
     * @param string $dispatcher Relative path to script to use for the URL. Append trailing slash if needed.
187
     * @return string
188
     */
189
    public function buildURL($params = array(), string $dispatcher='')
190
    {
191
        $url = rtrim($this->getBaseURL(), '/') . '/' . $dispatcher;
192
        
193
        // append any leftover parameters to the end of the URL
194
        if (!empty($params)) {
195
            $url .= '?' . http_build_query($params, null, '&amp;');
196
        }
197
        
198
        return $url;
199
    }
200
    
201
   /**
202
    * Retrieves the base URL of the application.
203
    * @return string
204
    */
205
    public function getBaseURL() : string
206
    {
207
        return $this->baseURL;
208
    }
209
    
210
    public function setBaseURL(string $url) : Request
211
    {
212
        $this->baseURL = $url;
213
        return $this;
214
    }
215
    
216
    /**
217
     * Registers a known parameter by name, allowing you to set validation
218
     * rules for the parameter. Returns the parameter object, so you can
219
     * configure it directly by chaining.
220
     *
221
     * @param string $name
222
     * @return Request_Param
223
     */
224
    public function registerParam($name)
225
    {
226
        if(!isset($this->knownParams[$name])) {
227
            $param = new Request_Param($this, $name);
228
            $this->knownParams[$name] = $param;
229
        }
230
        
231
        return $this->knownParams[$name];
232
    }
233
    
234
   /**
235
    * Retrieves a previously registered parameter instance.
236
    * 
237
    * @param string $name
238
    * @throws Request_Exception
239
    * @return Request_Param
240
    */
241
    public function getRegisteredParam(string $name) : Request_Param
242
    {
243
        if(isset($this->knownParams[$name])) {
244
            return $this->knownParams[$name];
245
        }
246
        
247
        throw new Request_Exception(
248
            'Unknown registered request parameter.',
249
            sprintf(
250
                'The request parameter [%s] has not been registered.',
251
                $name
252
            ),
253
            self::ERROR_PARAM_NOT_REGISTERED
254
        );
255
    }
256
    
257
   /**
258
    * Checks whether a parameter with the specified name 
259
    * has been registered.
260
    * 
261
    * @param string $name
262
    * @return bool
263
    */
264
    public function hasRegisteredParam(string $name) : bool
265
    {
266
        return isset($this->knownParams[$name]);
267
    }
268
    
269
    protected static $acceptHeaders;
270
    
271
    /**
272
     * Retrieves an indexed array with accept mime types
273
     * that the client sent, in the order of preference
274
     * the client specified.
275
     *
276
     * Example:
277
     *
278
     * array(
279
     *     'text/html',
280
     *     'application/xhtml+xml',
281
     *     'image/webp'
282
     *     ...
283
     * )
284
     */
285
    public static function getAcceptHeaders()
286
    {
287
        if (isset(self::$acceptHeaders)) {
288
            return self::$acceptHeaders;
289
        }
290
        
291
        self::$acceptHeaders = array();
292
        
293
        $acceptHeader = $_SERVER['HTTP_ACCEPT'];
294
        
295
        $accept = array();
296
        foreach (preg_split('/\s*,\s*/', $acceptHeader) as $i => $term) 
297
        {
298
            $entry = array(
299
                'pos' => $i,
300
                'params' => array(),
301
                'quality' => 0,
302
                'type' => null
303
            );
304
            
305
            $matches = null;
306
            if (preg_match('/^(\S+)\s*;(.*)/six', $term, $matches)) 
307
            {
308
                $entry['type'] = $matches[1];
309
                
310
                if(isset($matches[2]) && !empty($matches[2])) 
311
                {
312
                    $params = ConvertHelper::parseQueryString($matches[2]);
313
                    $entry['params'] = $params;
314
                     
315
                    if(isset($params['q'])) {
316
                        $entry['quality'] = (double)$params['q'];
317
                    }
318
                }
319
            }
320
            else
321
            {
322
                $entry['type'] = $term;
323
            }
324
            
325
            $accept[] = $entry;
326
        }
327
        
328
        usort($accept, array(Request::class, 'sortAcceptHeaders'));
329
        
330
        foreach ($accept as $a) {
331
            self::$acceptHeaders[] = $a['type'];
332
        }
333
        
334
        return self::$acceptHeaders;
335
    }
336
    
337
    public static function sortAcceptHeaders($a, $b)
338
    {
339
        /* first tier: highest q factor wins */
340
        $diff = $b['quality'] - $a['quality'];
341
        if ($diff > 0) {
342
            $diff = 1;
343
        } else {
344
            if ($diff < 0) {
345
                $diff = -1;
346
            } else {
347
                /* tie-breaker: first listed item wins */
348
                $diff = $a['pos'] - $b['pos'];
349
            }
350
        }
351
        
352
        return $diff;
353
    }
354
    
355
    /**
356
     * Sets a request parameter. Does nothing more than setting/overwriting
357
     * a parameter value within the same request.
358
     *
359
     * @since 3.3.7
360
     * @param string $name
361
     * @param string $value
362
     * @return Request
363
     */
364
    public function setParam(string $name, $value) : Request
365
    {
366
        $_REQUEST[$name] = $value;
367
        
368
        if(isset($this->knownParams[$name])) {
369
            unset($this->knownParams[$name]);
370
        }
371
        
372
        return $this;
373
    }
374
    
375
    /**
376
     * Checks whether the specified param exists in the current request.
377
     * Note: if the parameter exists, but is not valid according to the
378
     * parameter definition, it is assumed it does not exist.
379
     *
380
     * @return boolean
381
     */
382
    public function hasParam(string $name) : bool
383
    {
384
        $value = $this->getParam($name);
385
        if ($value !== null) {
386
            return true;
387
        }
388
        
389
        return false;
390
    }
391
    
392
   /**
393
    * Removes a single parameter from the request.
394
    * If the parameter has been registered, also
395
    * removes the registration info.
396
    * 
397
    * @param string $name
398
    * @return Request
399
    */
400
    public function removeParam(string $name) : Request
401
    {
402
        if(isset($_REQUEST[$name])) {
403
            unset($_REQUEST[$name]);
404
        }
405
        
406
        if(isset($this->knownParams[$name])) {
407
            unset($this->knownParams[$name]);
408
        }
409
        
410
        return $this;
411
    }
412
    
413
   /**
414
    * Removes several parameters from the request.
415
    * 
416
    * @param string[] $names
417
    * @return Request
418
    */
419
    public function removeParams(array $names) : Request
420
    {
421
        foreach($names as $name) {
422
            $this->removeParam($name);
423
        }
424
        
425
        return $this;
426
    }
427
    
428
    /**
429
     * Treats the request parameter as a boolean parameter
430
     * and returns its value as a boolean. If it does not exist
431
     * or does not have a value convertable to a boolean,
432
     * returns false.
433
     *
434
     * @param string $name
435
     * @return bool
436
     */
437
    public function getBool($name, $default=false)
438
    {
439
        $value = $this->getParam($name, $default);
440
        if(ConvertHelper::isBoolean($value)) {
441
            return ConvertHelper::string2bool($value);
442
        }
443
        
444
        return false;
445
    }
446
    
447
    public function validate()
448
    {
449
        foreach($this->knownParams as $param) {
450
            $name = $param->getName();
451
            if($param->isRequired() && !$this->hasParam($name)) {
452
                throw new Request_Exception(
453
                    'Missing request parameter '.$name,
454
                    sprintf(
455
                        'The request parameter [%s] is required, and is either empty or invalid according to the specified format [%s].',
456
                        $name,
457
                        $param->getValidationType()
458
                    ),
459
                    self::ERROR_MISSING_OR_INVALID_PARAMETER
460
                );
461
            }
462
        }
463
    }
464
    
465
    /**
466
     * Retrieves a param, filtered to remove HTML tags and with
467
     * html special characters encoded to avoid XSS injections.
468
     *
469
     * @param string $name
470
     * @param mixed $default
471
     * @return string
472
     */
473
    public function getFilteredParam($name, $default=null)
474
    {
475
        $val = $this->getParam($name, $default);
476
        if(is_string($val)) {
477
            $val = htmlspecialchars(trim(strip_tags($val)), ENT_QUOTES, 'UTF-8');
478
        }
479
        
480
        return $val;
481
    }
482
    
483
   /**
484
    * Treats the request parameter as a JSON string, and
485
    * if it exists and contains valid JSON, returns the
486
    * decoded JSON value as an array (default).
487
    *
488
    * @param string $name
489
    * @param bool $assoc
490
    * @return array|object
491
    * 
492
    * @see Request::getJSONAssoc()
493
    * @see Request::getJSONObject()
494
    */
495
    public function getJSON(string $name, bool $assoc=true)
496
    {
497
        $value = $this->getParam($name);
498
        
499
        if(!empty($value) && is_string($value)) 
500
        {
501
            $data = json_decode($value, $assoc);
502
            
503
            if($assoc && is_array($data)) {
504
                return $data;
505
            }
506
            
507
            if(is_object($data)) {
508
                return $data;
509
            }
510
        }
511
        
512
        if($assoc) {
513
            return array();
514
        }
515
        
516
        return new \stdClass();
517
    }
518
    
519
   /**
520
    * Like {@link Request::getJSON()}, but omitting the second
521
    * parameter. Use this for more readable code.
522
    * 
523
    * @param string $name
524
    * @return array
525
    */
526
    public function getJSONAssoc(string $name) : array
527
    {
528
        $result = $this->getJSON($name);
529
        if(is_array($result)) {
530
            return $result;
531
        }
532
        
533
        return array();
534
    }
535
    
536
   /**
537
    * Like {@link Request::getJSON()}, but omitting the second
538
    * parameter. Use this for more readable code.
539
    *
540
    * @param string $name
541
    * @return object
542
    */
543
    public function getJSONObject(string $name) : object
544
    {
545
        $result = $this->getJSON($name, false);
546
        if(is_object($result)) {
547
            return $result;
548
        }
549
        
550
        return new \stdClass();
551
    }
552
    
553
   /**
554
    * Sends a JSON response with the correct headers.
555
    *
556
    * @param array|string $data
557
    * @param bool $exit Whether to exit the script afterwards.
558
    */
559
    public static function sendJSON($data, bool $exit=true)
560
    {
561
        $payload = $data;
562
        if(!is_string($payload)) {
563
            $payload = json_encode($payload);
564
        }
565
        
566
        header('Cache-Control: no-cache, must-revalidate');
567
        header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
568
        header('Content-type: application/json');
569
        
570
        echo $payload;
571
        
572
        if($exit) 
573
        {
574
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
575
        }
576
    }
577
    
578
   /**
579
    * Sends HTML to the browser with the correct headers.
580
    * 
581
    * @param string $html
582
    * @param bool $exit Whether to exit the script afterwards.
583
    */
584
    public static function sendHTML(string $html, bool $exit=true)
585
    {
586
        header('Cache-Control: no-cache, must-revalidate');
587
        header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
588
        header('Content-type: text/html; charset=utf-8');
589
        
590
        echo $html;
591
        
592
        if($exit)
593
        {
594
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
595
        }
596
    }
597
    
598
   /**
599
    * Creates a new instance of the URL comparer, which can check 
600
    * whether the specified URLs match, regardless of the order in 
601
    * which the query parameters are, if any.
602
    * 
603
    * @param string $sourceURL
604
    * @param string $targetURL
605
    * @param array $limitParams Whether to limit the comparison to these specific parameter names (if present)
606
    * @return Request_URLComparer
607
    */
608
    public function createURLComparer(string $sourceURL, string $targetURL, array $limitParams=array()) : Request_URLComparer
609
    {
610
        $comparer = new Request_URLComparer($this, $sourceURL, $targetURL);
611
        $comparer->addLimitParams($limitParams);
612
        
613
        return $comparer;
614
    }
615
    
616
   /**
617
    * Retrieves the full URL that was used to access the current page.
618
    * @return string
619
    */
620
    public function getCurrentURL() : string
621
    {
622
        return $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
623
    }
624
}