Passed
Push — master ( 3c388a...58b85c )
by Sebastian
05:02
created

Request::sendHTML()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 6
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 11
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
     * @see Request::getRefreshParams()
119
     */
120
    public function buildRefreshURL($params = array(), $exclude = array())
121
    {
122
        $params = $this->getRefreshParams($params, $exclude);
123
        
124
        $dispatcher = $this->getDispatcher();
125
        
126
        return $this->buildURL($params, $dispatcher);
127
    }
128
    
129
   /**
130
    * Retrieves the name of the current dispatcher script / page.
131
    * This is made to be extended and implemented in a subclass.
132
    * 
133
    * @return string
134
    */
135
    public function getDispatcher() : string
136
    {
137
        return '';
138
    }
139
    
140
   /**
141
    * Filters and retrieves the current request variables 
142
    * to be used to build an URL to refresh the current page.
143
    * 
144
    * For further customization options, use the 
145
    * {@see Request::createRefreshParams()} method.
146
    * 
147
    * @param array<string,mixed> $params Key => value pairs of parameters to always include in the result.
148
    * @param string[] $exclude Names of parameters to exclude from the result.
149
    * @return array<string,mixed>
150
    * 
151
    * @see Request::createRefreshParams()
152
    */
153
    public function getRefreshParams(array $params = array(), array $exclude = array())
154
    {
155
        return $this->createRefreshParams()
156
        ->overrideParams($params)
157
        ->excludeParamsByName($exclude)
158
        ->getParams();
159
    }
160
    
161
   /**
162
    * Creates an instance of the helper that can be used to
163
    * retrieve the request's parameters collection, with the
164
    * possiblity to exlude and override some by rules.
165
    * 
166
    * @return Request_RefreshParams
167
    */
168
    public function createRefreshParams() : Request_RefreshParams
169
    {
170
        return new Request_RefreshParams();
171
    }
172
    
173
    public function getExcludeParams()
174
    {
175
        return array();
176
    }
177
    
178
    /**
179
     * Builds an application URL using the specified parameters: returns
180
     * an absolute URL to the main dispatcher with the specified parameters.
181
     * Not specifiying any parameters returns the absolute URL to the
182
     * application, without ending slash.
183
     *
184
     * @param array $params
185
     * @param string $dispatcher Relative path to script to use for the URL. Append trailing slash if needed.
186
     * @return string
187
     */
188
    public function buildURL($params = array(), string $dispatcher='')
189
    {
190
        $url = rtrim($this->getBaseURL(), '/') . '/' . $dispatcher;
191
        
192
        // append any leftover parameters to the end of the URL
193
        if (!empty($params)) {
194
            $url .= '?' . http_build_query($params, '', '&amp;');
195
        }
196
        
197
        return $url;
198
    }
199
    
200
   /**
201
    * Retrieves the base URL of the application.
202
    * @return string
203
    */
204
    public function getBaseURL() : string
205
    {
206
        return $this->baseURL;
207
    }
208
    
209
    public function setBaseURL(string $url) : Request
210
    {
211
        $this->baseURL = $url;
212
        return $this;
213
    }
214
    
215
    /**
216
     * Registers a known parameter by name, allowing you to set validation
217
     * rules for the parameter. Returns the parameter object, so you can
218
     * configure it directly by chaining.
219
     *
220
     * @param string $name
221
     * @return Request_Param
222
     */
223
    public function registerParam($name)
224
    {
225
        if(!isset($this->knownParams[$name])) {
226
            $param = new Request_Param($this, $name);
227
            $this->knownParams[$name] = $param;
228
        }
229
        
230
        return $this->knownParams[$name];
231
    }
232
    
233
   /**
234
    * Retrieves a previously registered parameter instance.
235
    * 
236
    * @param string $name
237
    * @throws Request_Exception
238
    * @return Request_Param
239
    */
240
    public function getRegisteredParam(string $name) : Request_Param
241
    {
242
        if(isset($this->knownParams[$name])) {
243
            return $this->knownParams[$name];
244
        }
245
        
246
        throw new Request_Exception(
247
            'Unknown registered request parameter.',
248
            sprintf(
249
                'The request parameter [%s] has not been registered.',
250
                $name
251
            ),
252
            self::ERROR_PARAM_NOT_REGISTERED
253
        );
254
    }
255
    
256
   /**
257
    * Checks whether a parameter with the specified name 
258
    * has been registered.
259
    * 
260
    * @param string $name
261
    * @return bool
262
    */
263
    public function hasRegisteredParam(string $name) : bool
264
    {
265
        return isset($this->knownParams[$name]);
266
    }
267
    
268
   /**
269
    * Retrieves an indexed array with accept mime types
270
    * that the client sent, in the order of preference
271
    * the client specified.
272
    *
273
    * Example:
274
    *
275
    * array(
276
    *     'text/html',
277
    *     'application/xhtml+xml',
278
    *     'image/webp'
279
    *     ...
280
    * )
281
    * 
282
    * @return array
283
    * @see Request::parseAcceptHeaders()
284
    */
285
    public static function getAcceptHeaders() : array
286
    {
287
        return self::parseAcceptHeaders()->getMimeStrings();
288
    }
289
    
290
   /**
291
    * Returns an instance of the accept headers parser,
292
    * to access information on the browser's accepted
293
    * mime types.
294
    *  
295
    * @return Request_AcceptHeaders
296
    * @see Request::getAcceptHeaders()
297
    */
298
    public static function parseAcceptHeaders() : Request_AcceptHeaders
299
    {
300
        static $accept;
301
        
302
        if(!isset($accept)) {
303
            $accept = new Request_AcceptHeaders();
304
        }
305
        
306
        return $accept;
307
    }
308
    
309
    /**
310
     * Sets a request parameter. Does nothing more than setting/overwriting
311
     * a parameter value within the same request.
312
     *
313
     * @param string $name
314
     * @param string $value
315
     * @return Request
316
     */
317
    public function setParam(string $name, $value) : Request
318
    {
319
        $_REQUEST[$name] = $value;
320
        
321
        if(isset($this->knownParams[$name])) {
322
            unset($this->knownParams[$name]);
323
        }
324
        
325
        return $this;
326
    }
327
    
328
    /**
329
     * Checks whether the specified param exists in the current request.
330
     * Note: if the parameter exists, but is not valid according to the
331
     * parameter definition, it is assumed it does not exist.
332
     *
333
     * @return boolean
334
     */
335
    public function hasParam(string $name) : bool
336
    {
337
        $value = $this->getParam($name);
338
        if ($value !== null) {
339
            return true;
340
        }
341
        
342
        return false;
343
    }
344
    
345
   /**
346
    * Removes a single parameter from the request.
347
    * If the parameter has been registered, also
348
    * removes the registration info.
349
    * 
350
    * @param string $name
351
    * @return Request
352
    */
353
    public function removeParam(string $name) : Request
354
    {
355
        if(isset($_REQUEST[$name])) {
356
            unset($_REQUEST[$name]);
357
        }
358
        
359
        if(isset($this->knownParams[$name])) {
360
            unset($this->knownParams[$name]);
361
        }
362
        
363
        return $this;
364
    }
365
    
366
   /**
367
    * Removes several parameters from the request.
368
    * 
369
    * @param string[] $names
370
    * @return Request
371
    */
372
    public function removeParams(array $names) : Request
373
    {
374
        foreach($names as $name) {
375
            $this->removeParam($name);
376
        }
377
        
378
        return $this;
379
    }
380
    
381
    /**
382
     * Treats the request parameter as a boolean parameter
383
     * and returns its value as a boolean. If it does not exist
384
     * or does not have a value convertable to a boolean,
385
     * returns false.
386
     *
387
     * @param string $name
388
     * @return bool
389
     */
390
    public function getBool($name, $default=false)
391
    {
392
        $value = $this->getParam($name, $default);
393
        if(ConvertHelper::isBoolean($value)) {
394
            return ConvertHelper::string2bool($value);
395
        }
396
        
397
        return false;
398
    }
399
    
400
    public function validate()
401
    {
402
        foreach($this->knownParams as $param) 
403
        {
404
            $name = $param->getName();
405
            
406
            if($param->isRequired() && !$this->hasParam($name)) 
407
            {
408
                throw new Request_Exception(
409
                    'Missing request parameter '.$name,
410
                    sprintf(
411
                        'The request parameter [%s] is required, and is either empty or invalid.',
412
                        $name
413
                    ),
414
                    self::ERROR_MISSING_OR_INVALID_PARAMETER
415
                );
416
            }
417
        }
418
    }
419
    
420
    /**
421
     * Retrieves a param, filtered to remove HTML tags and with
422
     * html special characters encoded to avoid XSS injections.
423
     *
424
     * @param string $name
425
     * @param mixed $default
426
     * @return string
427
     */
428
    public function getFilteredParam($name, $default=null)
429
    {
430
        $val = $this->getParam($name, $default);
431
        if(is_string($val)) {
432
            $val = htmlspecialchars(trim(strip_tags($val)), ENT_QUOTES, 'UTF-8');
433
        }
434
        
435
        return $val;
436
    }
437
    
438
   /**
439
    * Treats the request parameter as a JSON string, and
440
    * if it exists and contains valid JSON, returns the
441
    * decoded JSON value as an array (default).
442
    *
443
    * @param string $name
444
    * @param bool $assoc
445
    * @return array|object
446
    * 
447
    * @see Request::getJSONAssoc()
448
    * @see Request::getJSONObject()
449
    */
450
    public function getJSON(string $name, bool $assoc=true)
451
    {
452
        $value = $this->getParam($name);
453
        
454
        if(!empty($value) && is_string($value)) 
455
        {
456
            $data = json_decode($value, $assoc);
457
            
458
            if($assoc && is_array($data)) {
459
                return $data;
460
            }
461
            
462
            if(is_object($data)) {
463
                return $data;
464
            }
465
        }
466
        
467
        if($assoc) {
468
            return array();
469
        }
470
        
471
        return new \stdClass();
472
    }
473
    
474
   /**
475
    * Like {@link Request::getJSON()}, but omitting the second
476
    * parameter. Use this for more readable code.
477
    * 
478
    * @param string $name
479
    * @return array
480
    */
481
    public function getJSONAssoc(string $name) : array
482
    {
483
        $result = $this->getJSON($name);
484
        if(is_array($result)) {
485
            return $result;
486
        }
487
        
488
        return array();
489
    }
490
    
491
   /**
492
    * Like {@link Request::getJSON()}, but omitting the second
493
    * parameter. Use this for more readable code.
494
    *
495
    * @param string $name
496
    * @return object
497
    */
498
    public function getJSONObject(string $name) : object
499
    {
500
        $result = $this->getJSON($name, false);
501
        if(is_object($result)) {
502
            return $result;
503
        }
504
        
505
        return new \stdClass();
506
    }
507
    
508
   /**
509
    * Sends a JSON response with the correct headers.
510
    *
511
    * @param array|string $data
512
    * @param bool $exit Whether to exit the script afterwards.
513
    */
514
    public static function sendJSON($data, bool $exit=true)
515
    {
516
        $payload = $data;
517
        if(!is_string($payload)) {
518
            $payload = json_encode($payload);
519
        }
520
        
521
        header('Cache-Control: no-cache, must-revalidate');
522
        header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
523
        header('Content-type: application/json');
524
        
525
        echo $payload;
526
        
527
        if($exit) 
528
        {
529
            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...
530
        }
531
    }
532
    
533
   /**
534
    * Sends HTML to the browser with the correct headers.
535
    * 
536
    * @param string $html
537
    * @param bool $exit Whether to exit the script afterwards.
538
    */
539
    public static function sendHTML(string $html, bool $exit=true)
540
    {
541
        header('Cache-Control: no-cache, must-revalidate');
542
        header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
543
        header('Content-type: text/html; charset=utf-8');
544
        
545
        echo $html;
546
        
547
        if($exit)
548
        {
549
            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...
550
        }
551
    }
552
    
553
   /**
554
    * Creates a new instance of the URL comparer, which can check 
555
    * whether the specified URLs match, regardless of the order in 
556
    * which the query parameters are, if any.
557
    * 
558
    * @param string $sourceURL
559
    * @param string $targetURL
560
    * @param array $limitParams Whether to limit the comparison to these specific parameter names (if present)
561
    * @return Request_URLComparer
562
    */
563
    public function createURLComparer(string $sourceURL, string $targetURL, array $limitParams=array()) : Request_URLComparer
564
    {
565
        $comparer = new Request_URLComparer($this, $sourceURL, $targetURL);
566
        $comparer->addLimitParams($limitParams);
567
        
568
        return $comparer;
569
    }
570
    
571
   /**
572
    * Retrieves the full URL that was used to access the current page.
573
    * @return string
574
    */
575
    public function getCurrentURL() : string
576
    {
577
        return $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
578
    }
579
}