Passed
Push — master ( 7ae6f1...8efc25 )
by Sebastian
02:30
created

URLInfo::excludeParam()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 9
rs 10
1
<?php
2
/**
3
 * File containing the {@see AppUtils\URLInfo} class.
4
 * 
5
 * @package Application Utils
6
 * @subpackage URLInfo
7
 * @see AppUtils\URLInfo
8
 */
9
10
declare(strict_types=1);
11
12
namespace AppUtils;
13
14
/**
15
 * Replacement for PHP's native `parse_url` function, which
16
 * handles some common pitfalls and issues that are hard to 
17
 * follow, as well as adding a number of utility methods.
18
 * 
19
 * @package Application Utils
20
 * @subpackage URLInfo
21
 * @author Sebastian Mordziol <[email protected]>
22
 */
23
class URLInfo implements \ArrayAccess
24
{
25
    const ERROR_MISSING_SCHEME = 42101;
26
    
27
    const ERROR_INVALID_SCHEME = 42102;
28
29
    const ERROR_MISSING_HOST = 42103;
30
    
31
    const ERROR_CANNOT_FIND_CSS_FOLDER = 42104;
32
    
33
    const ERROR_UNKNOWN_TYPE_FOR_LABEL = 42105;
34
    
35
    const TYPE_EMAIL = 'email';
36
    const TYPE_FRAGMENT = 'fragment';
37
    const TYPE_PHONE = 'phone';
38
    const TYPE_URL = 'url';
39
    
40
   /**
41
    * The original URL that was passed to the constructor.
42
    * @var string
43
    */
44
    protected $rawURL;
45
46
   /**
47
    * @var array
48
    */
49
    protected $info;
50
    
51
   /**
52
    * @var string[]
53
    */
54
    protected $excludedParams = array();
55
    
56
   /**
57
    * @var bool
58
    * @see URLInfo::setParamExclusion()
59
    */
60
    protected $paramExclusion = false;
61
    
62
   /**
63
    * @var array
64
    * @see URLInfo::getTypeLabel()
65
    */
66
    protected static $typeLabels;
67
    
68
   /**
69
    * @var bool
70
    */
71
    protected $highlightExcluded = false;
72
    
73
   /**
74
    * @var array
75
    */
76
    protected $infoKeys = array(
77
        'scheme',
78
        'host',
79
        'port',
80
        'user',
81
        'pass',
82
        'path',
83
        'query',
84
        'fragment'
85
    );
86
    
87
   /**
88
    * @var string
89
    */
90
    protected $url;
91
    
92
   /**
93
    * @var URLInfo_Parser
94
    */
95
    protected $parser;
96
    
97
   /**
98
    * @var URLInfo_Normalizer
99
    */
100
    protected $normalizer;
101
    
102
    public function __construct(string $url)
103
    {
104
        $this->rawURL = $url;
105
        $this->url = self::filterURL($url);
106
        
107
        $this->parser = new URLInfo_Parser($this->url);
108
        $this->info = $this->parser->getInfo();
109
    }
110
    
111
   /**
112
    * Filters an URL: removes control characters and the
113
    * like to have a clean URL to work with.
114
    * 
115
    * @param string $url
116
    * @return string
117
    */
118
    public static function filterURL(string $url)
119
    {
120
        return URLInfo_Filter::filter($url);
121
    }
122
    
123
    /**
124
     * Checks if it is an https link.
125
     * @return boolean
126
     */
127
    public function isSecure() : bool
128
    {
129
        return isset($this->info['scheme']) && $this->info['scheme'] === 'https';
130
    }
131
    
132
    public function isAnchor() : bool
133
    {
134
        return $this->info['type'] === self::TYPE_FRAGMENT;
135
    }
136
    
137
    public function isEmail() : bool
138
    {
139
        return $this->info['type'] === self::TYPE_EMAIL;
140
    }
141
    
142
    public function isPhoneNumber() : bool
143
    {
144
        return $this->info['type'] === self::TYPE_PHONE;
145
    }
146
    
147
   /**
148
    * Whether the URL is a regular URL, not one of the 
149
    * other types like a phone number or email address.
150
    * 
151
    * @return bool
152
    */
153
    public function isURL() : bool
154
    {
155
        $host = $this->getHost();
156
        return !empty($host);
157
    }
158
    
159
    public function isValid() : bool
160
    {
161
        return $this->parser->isValid();
162
    }
163
    
164
   /**
165
    * Retrieves the host name, or an empty string if none is present.
166
    * 
167
    * @return string
168
    */
169
    public function getHost() : string
170
    {
171
        return $this->getInfoKey('host');
172
    }
173
    
174
   /**
175
    * Retrieves the path, or an empty string if none is present.
176
    * @return string
177
    */
178
    public function getPath() : string
179
    {
180
        return $this->getInfoKey('path');
181
    }
182
    
183
    public function getFragment() : string
184
    {
185
        return $this->getInfoKey('fragment');
186
    }
187
    
188
    public function getScheme() : string
189
    {
190
        return $this->getInfoKey('scheme');
191
    }
192
    
193
   /**
194
    * Retrieves the port specified in the URL, or -1 if none is preseent.
195
    * @return int
196
    */
197
    public function getPort() : int
198
    {
199
        $port = $this->getInfoKey('port');
200
        
201
        if(!empty($port)) {
202
            return (int)$port;
203
        }
204
        
205
        return -1;
206
    }
207
    
208
   /**
209
    * Retrieves the raw query string, or an empty string if none is present.
210
    * 
211
    * @return string
212
    * 
213
    * @see URLInfo::getParams()
214
    */
215
    public function getQuery() : string
216
    {
217
        return $this->getInfoKey('query');
218
    }
219
    
220
    public function getUsername() : string
221
    {
222
        return $this->getInfoKey('user');
223
    }
224
    
225
    public function getPassword() : string
226
    {
227
        return $this->getInfoKey('pass');
228
    }
229
    
230
   /**
231
    * Whether the URL contains a port number.
232
    * @return bool
233
    */
234
    public function hasPort() : bool
235
    {
236
        return $this->getPort() !== -1;
237
    }
238
    
239
   /**
240
    * Alias for the hasParams() method.
241
    * @return bool
242
    * @see URLInfo::hasParams()
243
    */
244
    public function hasQuery() : bool
245
    {
246
        return $this->hasParams();
247
    }
248
    
249
    public function hasHost() : bool
250
    {
251
        return $this->getHost() !== ''; 
252
    }
253
    
254
    public function hasPath() : bool
255
    {
256
        return $this->getPath() !== '';
257
    }
258
    
259
    public function hasFragment() : bool
260
    {
261
        return $this->getFragment() !== '';
262
    }
263
    
264
    public function hasUsername() : bool
265
    {
266
        return $this->getUsername() !== '';
267
    }
268
    
269
    public function hasPassword() : bool
270
    {
271
        return $this->getPassword() !== '';
272
    }
273
    
274
    public function hasScheme() : bool
275
    {
276
        return $this->getScheme() !== '';
277
    }
278
    
279
    protected function getInfoKey(string $name) : string
280
    {
281
        if(isset($this->info[$name])) {
282
            return (string)$this->info[$name];
283
        }
284
        
285
        return '';
286
    }
287
288
    public function getNormalized() : string
289
    {
290
        if(!$this->isValid()) {
291
            return '';
292
        }
293
        
294
        if(!isset($this->normalizer)) {
295
            $this->normalizer = new URLInfo_Normalizer($this);
296
        }
297
        
298
        return $this->normalizer->normalize();
299
    }
300
    
301
   /**
302
    * Creates a hash of the URL, which can be used for comparisons.
303
    * Since any parameters in the URL's query are sorted alphabetically,
304
    * the same links with a different parameter order will have the 
305
    * same hash.
306
    * 
307
    * @return string
308
    */
309
    public function getHash()
310
    {
311
        return \AppUtils\ConvertHelper::string2shortHash($this->getNormalized());
312
    }
313
314
   /**
315
    * Highlights the URL using HTML tags with specific highlighting
316
    * class names.
317
    * 
318
    * @return string Will return an empty string if the URL is not valid.
319
    */
320
    public function getHighlighted() : string
321
    {
322
        if(!$this->isValid()) {
323
            return '';
324
        }
325
        
326
        $highlighter = new URLInfo_Highlighter($this);
327
        
328
        return $highlighter->highlight();
329
    }
330
    
331
    public function getErrorMessage() : string
332
    {
333
        return $this->parser->getErrorMessage();
334
    }
335
    
336
    public function getErrorCode() : int
337
    {
338
        return $this->parser->getErrorCode();
339
    }
340
    
341
    public function hasParams() : bool
342
    {
343
        $params = $this->getParams();
344
        return !empty($params);
345
    }
346
    
347
    public function countParams() : int
348
    {
349
        $params = $this->getParams();
350
        return count($params);
351
    }
352
    
353
   /**
354
    * Retrieves all parameters specified in the url,
355
    * if any, as an associative array. 
356
    * 
357
    * NOTE: Ignores parameters that have been added
358
    * to the excluded parameters list.
359
    *
360
    * @return array
361
    */
362
    public function getParams() : array
363
    {
364
        if(!$this->paramExclusion || empty($this->excludedParams)) {
365
            return $this->info['params'];
366
        }
367
        
368
        $keep = array();
369
        foreach($this->info['params'] as $name => $value) 
370
        {
371
            if(!isset($this->excludedParams[$name])) {
372
                $keep[$name] = $value;
373
            }
374
        }
375
        
376
        return $keep;
377
    }
378
    
379
   /**
380
    * Retrieves the names of all parameters present in the URL, if any.
381
    * @return string[]
382
    */
383
    public function getParamNames() : array
384
    {
385
        $params = $this->getParams();
386
        return array_keys($params);
387
    }
388
    
389
   /**
390
    * Retrieves a specific parameter value from the URL.
391
    * 
392
    * @param string $name
393
    * @return string The parameter value, or an empty string if it does not exist.
394
    */
395
    public function getParam(string $name) : string
396
    {
397
        if(isset($this->info['params'][$name])) {
398
            return $this->info['params'][$name];
399
        }
400
        
401
        return '';
402
    }
403
    
404
   /**
405
    * Excludes an URL parameter entirely if present:
406
    * the parser will act as if the parameter was not
407
    * even present in the source URL, effectively
408
    * stripping it.
409
    *
410
    * @param string $name
411
    * @param string $reason A human readable explanation why this is excluded - used when highlighting links.
412
    * @return URLInfo
413
    */
414
    public function excludeParam(string $name, string $reason) : URLInfo
415
    {
416
        if(!isset($this->excludedParams[$name]))
417
        {
418
            $this->excludedParams[$name] = $reason;
419
            $this->setParamExclusion();
420
        }
421
        
422
        return $this;
423
    }
424
425
    /**
426
     * Retrieves a string identifier of the type of URL that was detected.
427
     *
428
     * @return string
429
     *
430
     * @see URLInfo::TYPE_EMAIL
431
     * @see URLInfo::TYPE_FRAGMENT
432
     * @see URLInfo::TYPE_PHONE
433
     * @see URLInfo::TYPE_URL
434
     */
435
    public function getType() : string
436
    {
437
        return $this->info['type'];
438
    }
439
    
440
    public function getTypeLabel() : string
441
    {
442
        if(!isset(self::$typeLabels))
443
        {
444
            self::$typeLabels = array(
445
                self::TYPE_EMAIL => t('Email'),
446
                self::TYPE_FRAGMENT => t('Jump mark'),
447
                self::TYPE_PHONE => t('Phone number'),
448
                self::TYPE_URL => t('URL'),
449
            );
450
        }
451
        
452
        $type = $this->getType();
453
        
454
        if(!isset(self::$typeLabels[$type]))
455
        {
456
            throw new BaseException(
457
                sprintf('Unknown URL type label for type [%s].', $type),
458
                null,
459
                self::ERROR_UNKNOWN_TYPE_FOR_LABEL
460
            );
461
        }
462
        
463
        return self::$typeLabels[$this->getType()];
464
    }
465
466
   /**
467
    * Whether excluded parameters should be highlighted in
468
    * a different color in the URL when using the
469
    * {@link URLInfo::getHighlighted()} method.
470
    *
471
    * @param bool $highlight
472
    * @return URLInfo
473
    */
474
    public function setHighlightExcluded(bool $highlight=true) : URLInfo
475
    {
476
        $this->highlightExcluded = $highlight;
477
        return $this;
478
    }
479
    
480
   /**
481
    * Returns an array with all relevant URL information.
482
    * 
483
    * @return array
484
    */
485
    public function toArray() : array
486
    {
487
        return array(
488
            'hasParams' => $this->hasParams(),
489
            'params' => $this->getParams(),
490
            'type' => $this->getType(),
491
            'typeLabel' => $this->getTypeLabel(),
492
            'normalized' => $this->getNormalized(),
493
            'highlighted' => $this->getHighlighted(),
494
            'hash' => $this->getHash(),
495
            'host' => $this->getHost(),
496
            'isValid' => $this->isValid(),
497
            'isURL' => $this->isURL(),
498
            'isEmail' => $this->isEmail(),
499
            'isAnchor' => $this->isAnchor(),
500
            'isPhoneNumber' => $this->isPhoneNumber(),
501
            'errorMessage' => $this->getErrorMessage(),
502
            'errorCode' => $this->getErrorCode(),
503
            'excludedParams' => array_keys($this->excludedParams)
504
        );
505
    }
506
    
507
    /**
508
     * Enable or disable parameter exclusion: if any parameters
509
     * to exclude have been added, this allows switching between
510
     * both modes. When enabled, methods like getNormalized or
511
     * getHighlighted will exclude any parameters to exclude. When
512
     * disabled, it will act as usual.
513
     *
514
     * This allows adding parameters to exclude, but still have
515
     * access to the original URLs.
516
     *
517
     * @param bool $enabled
518
     * @return URLInfo
519
     * @see URLInfo::isParamExclusionEnabled()
520
     * @see URLInfo::setHighlightExcluded()
521
     */
522
    public function setParamExclusion(bool $enabled=true) : URLInfo
523
    {
524
        $this->paramExclusion = $enabled;
525
        return $this;
526
    }
527
    
528
   /**
529
    * Whether the parameter exclusion mode is enabled:
530
    * In this case, if any parameters have been added to the
531
    * exclusion list, all relevant methods will exclude these.
532
    *
533
    * @return bool
534
    */
535
    public function isParamExclusionEnabled() : bool
536
    {
537
        return $this->paramExclusion;
538
    }
539
    
540
   /**
541
    * Checks whether the link contains any parameters that
542
    * are on the list of excluded parameters.
543
    *
544
    * @return bool
545
    */
546
    public function containsExcludedParams() : bool
547
    {
548
        if(empty($this->excludedParams)) {
549
            return false;
550
        }
551
        
552
        $names = array_keys($this->info['params']);
553
        foreach($names as $name) {
554
            if(isset($this->excludedParams[$name])) {
555
                return true;
556
            }
557
        }
558
        
559
        return false;
560
    }
561
    
562
    public function hasParam(string $name) : bool
563
    {
564
        $names = $this->getParamNames();
565
        return in_array($name, $names);
566
    }
567
568
    public function offsetSet($offset, $value) 
569
    {
570
        if(in_array($offset, $this->infoKeys)) {
571
            $this->info[$offset] = $value;
572
        }
573
    }
574
    
575
    public function offsetExists($offset) 
576
    {
577
        return isset($this->info[$offset]);
578
    }
579
    
580
    public function offsetUnset($offset) 
581
    {
582
        unset($this->info[$offset]);
583
    }
584
    
585
    public function offsetGet($offset) 
586
    {
587
        if($offset === 'port') {
588
            return $this->getPort();
589
        }
590
        
591
        if(in_array($offset, $this->infoKeys)) {
592
            return $this->getInfoKey($offset);
593
        }
594
        
595
        return '';
596
    }
597
    
598
    public static function getHighlightCSS() : string
599
    {
600
        return URLInfo_Highlighter::getHighlightCSS();
601
    }
602
    
603
    public function getExcludedParams() : array
604
    {
605
        return $this->excludedParams;
606
    }
607
    
608
    public function isHighlightExcludeEnabled() : bool
609
    {
610
        return $this->highlightExcluded;
611
    }
612
}
613