Passed
Push — master ( bc9af2...420731 )
by Sebastian
02:22
created

ConvertHelper::string2attributeJS()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 8
rs 10
1
<?php
2
/**
3
 * File containing the {@see AppUtils\ConvertHelper} class.
4
 * 
5
 * @package Application Utils
6
 * @subpackage ConvertHelper
7
 * @see AppUtils\ConvertHelper
8
 */
9
10
namespace AppUtils;
11
12
/**
13
 * Static conversion helper class: offers a number of utility methods
14
 * to convert variable types, as well as specialized methods for working
15
 * with specific types like dates.
16
 * 
17
 * @package Application Utils
18
 * @subpackage ConvertHelper
19
 * @author Sebastian Mordziol <[email protected]>
20
 */
21
class ConvertHelper
22
{
23
    const ERROR_MONTHTOSTRING_NOT_VALID_MONTH_NUMBER = 23303;
24
    
25
    const ERROR_CANNOT_NORMALIZE_NON_SCALAR_VALUE = 23304;
26
    
27
    const ERROR_JSON_ENCODE_FAILED = 23305;
28
    
29
    const ERROR_CANNOT_GET_DATE_DIFF = 23306;
30
    
31
    /**
32
     * Normalizes tabs in the specified string by indenting everything
33
     * back to the minimum tab distance. With the second parameter,
34
     * tabs can optionally be converted to spaces as well (recommended
35
     * for HTML output).
36
     *
37
     * @param string $string
38
     * @param boolean $tabs2spaces
39
     * @return string
40
     */
41
    public static function normalizeTabs(string $string, bool $tabs2spaces = false)
42
    {
43
        $lines = explode("\n", $string);
44
        $max = 0;
45
        $min = 99999;
46
        foreach ($lines as $line) {
47
            $amount = substr_count($line, "\t");
48
            if ($amount > $max) {
49
                $max = $amount;
50
            }
51
52
            if ($amount > 0 && $amount < $min) {
53
                $min = $amount;
54
            }
55
        }
56
57
        $converted = array();
58
        foreach ($lines as $line) {
59
            $amount = substr_count($line, "\t") - $min;
60
            $line = trim($line);
61
            if ($amount >= 1) {
62
                $line = str_repeat("\t", $amount) . $line;
63
            }
64
65
            $converted[] = $line;
66
        }
67
68
        $string = implode("\n", $converted);
69
        if ($tabs2spaces) {
70
            $string = self::tabs2spaces($string);
71
        }
72
73
        return $string;
74
    }
75
76
    /**
77
     * Converts tabs to spaces in the specified string.
78
     * @param string $string
79
     * @return string
80
     */
81
    public static function tabs2spaces($string)
82
    {
83
        return str_replace("\t", '    ', $string);
84
    }
85
    
86
    /**
87
     * Converts the specified amount of seconds into
88
     * a human readable string split in months, weeks,
89
     * days, hours, minutes and seconds.
90
     *
91
     * @param float $seconds
92
     * @return string
93
     */
94
    public static function time2string($seconds)
95
    {
96
        static $units = null;
97
        if (is_null($units)) {
98
            $units = array(
99
                array(
100
                    'value' => 31 * 7 * 24 * 3600,
101
                    'singular' => t('month'),
102
                    'plural' => t('months')
103
                ),
104
                array(
105
                    'value' => 7 * 24 * 3600,
106
                    'singular' => t('week'),
107
                    'plural' => t('weeks')
108
                ),
109
                array(
110
                    'value' => 24 * 3600,
111
                    'singular' => t('day'),
112
                    'plural' => t('days')
113
                ),
114
                array(
115
                    'value' => 3600,
116
                    'singular' => t('hour'),
117
                    'plural' => t('hours')
118
                ),
119
                array(
120
                    'value' => 60,
121
                    'singular' => t('minute'),
122
                    'plural' => t('minutes')
123
                ),
124
                array(
125
                    'value' => 1,
126
                    'singular' => t('second'),
127
                    'plural' => t('seconds')
128
                )
129
            );
130
        }
131
132
        // specifically handle zero
133
        if ($seconds <= 0) {
134
            return '0 ' . t('seconds');
135
        }
136
        
137
        if($seconds < 1) {
138
            return t('less than a second');
139
        }
140
141
        $tokens = array();
142
        foreach ($units as $def) {
143
            $quot = intval($seconds / $def['value']);
144
            if ($quot) {
145
                $item = $quot . ' ';
146
                if (abs($quot) > 1) {
147
                    $item .= $def['plural'];
148
                } else {
149
                    $item .= $def['singular'];
150
                }
151
152
                $tokens[] = $item;
153
                $seconds -= $quot * $def['value'];
154
            }
155
        }
156
157
        $last = array_pop($tokens);
158
        if (empty($tokens)) {
159
            return $last;
160
        }
161
162
        return implode(', ', $tokens) . ' ' . t('and') . ' ' . $last;
163
    }
164
165
   /**
166
    * Converts a timestamp into an easily understandable
167
    * format, e.g. "2 hours", "1 day", "3 months"
168
    *
169
    * If you set the date to parameter, the difference
170
    * will be calculated between the two dates and not
171
    * the current time.
172
    *
173
    * @param integer|\DateTime $datefrom
174
    * @param integer|\DateTime $dateto
175
    * @return string
176
    */
177
    public static function duration2string($datefrom, $dateto = -1) : string
178
    {
179
         $converter = new ConvertHelper_DurationConverter();
180
         
181
         if($datefrom instanceof \DateTime)
182
         {
183
             $converter->setDateFrom($datefrom);
184
         }
185
         else
186
         {
187
             $converter->setDateFrom(self::timestamp2date($datefrom)); 
188
         }
189
190
         if($dateto instanceof \DateTime)
191
         {
192
             $converter->setDateTo($dateto);
193
         }
194
         else if($dateto > 0)
195
         {
196
             $converter->setDateTo(self::timestamp2date($dateto));
197
         }
198
199
         return $converter->convert();
200
    }
201
202
    /**
203
     * Adds syntax highlighting to the specified SQL string in HTML format
204
     * @param string $sql
205
     * @return string
206
     */
207
    public static function highlight_sql($sql)
208
    {
209
        $geshi = new  \GeSHi($sql, 'sql');
210
211
        return $geshi->parse_code();
212
    }
213
    
214
    public static function highlight_xml($xml, $formatSource=false)
215
    {
216
        if($formatSource) 
217
        {
218
            $dom = new \DOMDocument();
219
            $dom->loadXML($xml);
220
            $dom->preserveWhiteSpace = false;
221
            $dom->formatOutput = true;
222
            
223
            $xml = $dom->saveXML();
224
        }
225
        
226
        $geshi = new \GeSHi($xml, 'xml');
227
        
228
        return $geshi->parse_code();
229
    }
230
231
    public static function highlight_php($php)
232
    {
233
        $geshi = new \GeSHi($php, 'php');
234
    
235
        return $geshi->parse_code();
236
    }
237
    
238
    /**
239
     * Converts a number of bytes to a human readable form,
240
     * e.g. xx Kb / xx Mb / xx Gb
241
     *
242
     * @param $bytes
243
     * @param $precision
244
     * @return string
245
     */
246
    public static function bytes2readable($bytes, $precision = 1)
247
    {
248
        $kilobyte = 1024;
249
        $megabyte = $kilobyte * 1024;
250
        $gigabyte = $megabyte * 1024;
251
        $terabyte = $gigabyte * 1024;
252
253
        if (($bytes >= 0) && ($bytes < $kilobyte)) {
254
            return $bytes . ' ' . t('B');
255
256
        } elseif (($bytes >= $kilobyte) && ($bytes < $megabyte)) {
257
            return round($bytes / $kilobyte, $precision) . ' ' . t('Kb');
258
259
        } elseif (($bytes >= $megabyte) && ($bytes < $gigabyte)) {
260
            return round($bytes / $megabyte, $precision) . ' ' . t('Mb');
261
262
        } elseif (($bytes >= $gigabyte) && ($bytes < $terabyte)) {
263
            return round($bytes / $gigabyte, $precision) . ' ' . t('Gb');
264
265
        } elseif ($bytes >= $terabyte) {
266
            return round($bytes / $gigabyte, $precision) . ' ' . t('Tb');
267
        }
268
269
        return $bytes . ' ' . t('B');
270
    }
271
272
   /**
273
    * Cuts a text to the specified length if it is longer than the
274
    * target length. Appends a text to signify it has been cut at 
275
    * the end of the string.
276
    * 
277
    * @param string $text
278
    * @param int $targetLength
279
    * @param string $append
280
    * @return string
281
    */
282
    public static function text_cut(string $text, int $targetLength, string $append = '...') : string
283
    {
284
        $length = mb_strlen($text);
285
        if ($length <= $targetLength) {
286
            return $text;
287
        }
288
289
        $text = trim(mb_substr($text, 0, $targetLength)) . $append;
290
291
        return $text;
292
    }
293
294
    public static function var_dump($var, $html=true)
295
    {
296
        $info = parseVariable($var);
297
        
298
        if($html) {
299
            return $info->toHTML();
300
        }
301
        
302
        return $info->toString();
303
    }
304
    
305
    public static function print_r($var, $return=false, $html=true)
306
    {
307
        $result = self::var_dump($var, $html);
308
        
309
        if($html) {
310
            $result = 
311
            '<pre style="background:#fff;color:#333;padding:16px;border:solid 1px #bbb;border-radius:4px">'.
312
                $result.
313
            '</pre>';
314
        }
315
        
316
        if($return) {
317
            return $result;
318
        }
319
        
320
        echo $result;
321
    }
322
    
323
    protected static $booleanStrings = array(
324
        1 => true,
325
        0 => false,
326
        '1' => true,
327
        '0' => false,
328
        'true' => true,
329
        'false' => false,
330
        'yes' => true,
331
        'no' => false
332
    );
333
334
    public static function string2bool($string)
335
    {
336
        if($string === '' || $string === null) {
337
            return false;
338
        }
339
        
340
        if (is_bool($string)) {
341
            return $string;
342
        }
343
344
        if (!array_key_exists($string, self::$booleanStrings)) {
345
            throw new \InvalidArgumentException('Invalid string boolean representation');
346
        }
347
348
        return self::$booleanStrings[$string];
349
    }
350
    
351
   /**
352
    * Whether the specified string is a boolean string or boolean value.
353
    * Alias for {@link ConvertHelper::isBoolean()}.
354
    * 
355
    * @param mixed $string
356
    * @return bool
357
    * @deprecated
358
    * @see ConvertHelper::isBoolean()
359
    */
360
    public static function isBooleanString($string) : bool
361
    {
362
        return self::isBoolean($string);
363
    }
364
365
   /**
366
    * Alias for the {@\AppUtils\XMLHelper::string2xml()} method.
367
    * 
368
    * @param string $text
369
    * @return string
370
    * @deprecated
371
    */
372
    public static function text_makeXMLCompliant($text)
373
    {
374
        return XMLHelper::string2xml($text);
375
    }
376
377
    /**
378
     * Transforms a date into a generic human readable date, optionally with time.
379
     * If the year is the same as the current one, it is omitted.
380
     *
381
     * - 6 Jan 2012
382
     * - 12 Dec 2012 17:45
383
     * - 5 Aug
384
     *
385
     * @param \DateTime $date
386
     * @return string
387
     */
388
    public static function date2listLabel(\DateTime $date, $includeTime = false, $shortMonth = false)
389
    {
390
        $today = new \DateTime();
391
        if($date->format('d.m.Y') == $today->format('d.m.Y')) {
392
            $label = t('Today');
393
        } else {
394
            $label = $date->format('d') . '. ' . self::month2string((int)$date->format('m'), $shortMonth) . ' ';
395
            if ($date->format('Y') != date('Y')) {
396
                $label .= $date->format('Y');
397
            }
398
        }
399
        
400
        if ($includeTime) {
401
            $label .= $date->format(' H:i');
402
        }
403
404
        return trim($label);
405
    }
406
407
    protected static $months;
408
409
    /**
410
     * Returns a human readable month name given the month number. Can optionally
411
     * return the shorthand version of the month. Translated into the current
412
     * application locale.
413
     *
414
     * @param int|string $monthNr
415
     * @param boolean $short
416
     * @throws ConvertHelper_Exception
417
     * @return string
418
     */
419
    public static function month2string($monthNr, $short = false)
420
    {
421
        if (!isset(self::$months)) {
422
            self::$months = array(
423
                1 => array(t('January'), t('Jan')),
424
                2 => array(t('February'), t('Feb')),
425
                3 => array(t('March'), t('Mar')),
426
                4 => array(t('April'), t('Apr')),
427
                5 => array(t('May'), t('May')),
428
                6 => array(t('June'), t('Jun')),
429
                7 => array(t('July'), t('Jul')),
430
                8 => array(t('August'), t('Aug')),
431
                9 => array(t('September'), t('Sep')),
432
                10 => array(t('October'), t('Oct')),
433
                11 => array(t('November'), t('Nov')),
434
                12 => array(t('December'), t('Dec'))
435
            );
436
        }
437
438
        $monthNr = intval($monthNr);
439
        if (!isset(self::$months[$monthNr])) {
440
            throw new ConvertHelper_Exception(
441
                'Invalid month number',
442
                sprintf('%1$s is not a valid month number.', $monthNr),
443
                self::ERROR_MONTHTOSTRING_NOT_VALID_MONTH_NUMBER
444
            );
445
        }
446
447
        if ($short) {
448
            return self::$months[$monthNr][1];
449
        }
450
451
        return self::$months[$monthNr][0];
452
    }
453
454
    /**
455
     * Transliterates a string.
456
     *
457
     * @param string $string
458
     * @param string $spaceChar
459
     * @param string $lowercase
460
     * @return string
461
     */
462
    public static function transliterate($string, $spaceChar = '-', $lowercase = true)
463
    {
464
        $translit = new Transliteration();
465
        $translit->setSpaceReplacement($spaceChar);
466
        if ($lowercase) {
467
            $translit->setLowercase();
468
        }
469
470
        return $translit->convert($string);
471
    }
472
    
473
   /**
474
    * Retrieves the HEX character codes for all control
475
    * characters that the {@link stripControlCharacters()} 
476
    * method will remove.
477
    * 
478
    * @return string[]
479
    */
480
    public static function getControlCharactersAsHex()
481
    {
482
        $hexAlphabet = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
483
        
484
        $stack = array();
485
        foreach(self::$controlChars as $char)
486
        {
487
            $tokens = explode('-', $char);
488
            $start = $tokens[0];
489
            $end = $tokens[1];
490
            $prefix = substr($start, 0, 3);
491
            $range = array();
492
            foreach($hexAlphabet as $number) {
493
                $range[] = $prefix.$number;
494
            }
495
            
496
            $use = false;
497
            foreach($range as $number) {
498
                if($number == $start) {
499
                    $use = true;
500
                }
501
                
502
                if($use) {
503
                    $stack[] = $number;
504
                }
505
                
506
                if($number == $end) {
507
                    break;
508
                }
509
            }
510
        }
511
        
512
        return $stack;
513
    }
514
    
515
   /**
516
    * Retrieves an array of all control characters that
517
    * the {@link stripControlCharacters()} method will 
518
    * remove, as the actual UTF-8 characters.
519
    * 
520
    * @return string[]
521
    */
522
    public static function getControlCharactersAsUTF8()
523
    {
524
        $chars = self::getControlCharactersAsHex();
525
        
526
        $result = array();
527
        foreach($chars as $char) {
528
            $result[] = hex2bin($char);
529
        }
530
        
531
        return $result;
532
    }
533
    
534
   /**
535
    * Retrieves all control characters as JSON encoded
536
    * characters, e.g. "\u200b".
537
    * 
538
    * @return string[]
539
    */
540
    public static function getControlCharactersAsJSON()
541
    {
542
        $chars = self::getControlCharactersAsHex();
543
        
544
        $result = array();
545
        foreach($chars as $char) {
546
            $result[] = '\u'.strtolower($char);
547
        }
548
        
549
        return $result;
550
    }
551
    
552
    protected static $controlChars =  array(
553
        '0000-0008', // control chars
554
        '000E-000F', // control chars
555
        '0010-001F', // control chars
556
        '2000-200F', // non-breaking space and co
557
    );
558
    
559
    protected static $controlCharsRegex;
560
561
    /**
562
     * Removes all control characters from the specified string
563
     * that can cause problems in some cases, like creating
564
     * valid XML documents. This includes invisible non-breaking
565
     * spaces.
566
     *
567
     * @param string $string
568
     * @return string
569
     * @see https://stackoverflow.com/a/8171868/2298192
570
     * @see https://unicode-table.com/en
571
     */
572
    public static function stripControlCharacters(string $string) : string
573
    {
574
        if(empty($string)) {
575
            return $string;
576
        }
577
        
578
        // create the regex from the unicode characters list
579
        if(!isset(self::$controlCharsRegex)) 
580
        {
581
            $chars = self::getControlCharactersAsHex();
582
583
            // we use the notation \x{0000} to specify the unicode character key
584
            // in the regular expression.
585
            $stack = array();
586
            foreach($chars as $char) {
587
                $stack[] = '\x{'.$char.'}';
588
            }
589
            
590
            self::$controlCharsRegex = '/['.implode('', $stack).']/u';
591
        }
592
        
593
        return preg_replace(self::$controlCharsRegex, '', $string);
594
    }
595
596
   /**
597
    * Converts a unicode character to the PHPO notation.
598
    * 
599
    * Example:
600
    * 
601
    * <pre>unicodeChar2php('"\u0000"')</pre>
602
    * 
603
    * Returns
604
    * 
605
    * <pre>\x0</pre>
606
    * 
607
    * @param string $unicodeChar
608
    * @return string
609
    */
610
    public static function unicodeChar2php($unicodeChar) 
611
    {
612
        $unicodeChar = json_decode($unicodeChar);
613
        
614
        /** @author Krinkle 2018 */
615
        $output = '';
616
        foreach (str_split($unicodeChar) as $octet) {
617
            $ordInt = ord($octet);
618
            // Convert from int (base 10) to hex (base 16), for PHP \x syntax
619
            $ordHex = base_convert($ordInt, 10, 16);
620
            $output .= '\x' . $ordHex;
621
        }
622
        return $output;
623
    }
624
    
625
    /**
626
     * Removes the extension from the specified filename
627
     * and returns the name without the extension.
628
     *
629
     * Example:
630
     * filename.html > filename
631
     * passed.test.jpg > passed.test
632
     * path/to/file/document.txt > document
633
     *
634
     * @param string $filename
635
     * @return string
636
     */
637
    public static function filenameRemoveExtension($filename)
638
    {
639
        return FileHelper::removeExtension($filename);
640
    }
641
    
642
    public static function areVariablesEqual($a, $b) : bool
643
    {
644
        $a = self::convertScalarForComparison($a);
645
        $b = self::convertScalarForComparison($b);
646
647
        return $a === $b;
648
    }
649
    
650
    protected static function convertScalarForComparison($scalar)
651
    {
652
        if($scalar === '' || is_null($scalar)) {
653
            return null;
654
        }
655
        
656
        if(is_bool($scalar)) {
657
            return self::bool2string($scalar);
658
        }
659
        
660
        if(is_array($scalar)) {
661
            $scalar = md5(serialize($scalar));
662
        }
663
        
664
        if($scalar !== null && !is_scalar($scalar)) {
665
            throw new ConvertHelper_Exception(
666
                'Not a scalar value in comparison',
667
                null,
668
                self::ERROR_CANNOT_NORMALIZE_NON_SCALAR_VALUE
669
            );
670
        }
671
        
672
        return strval($scalar);
673
    }
674
675
    /**
676
     * Compares two strings to check whether they are equal.
677
     * null and empty strings are considered equal.
678
     *
679
     * @param string $a
680
     * @param string $b
681
     * @return boolean
682
     */
683
    public static function areStringsEqual($a, $b) : bool
684
    {
685
        return self::areVariablesEqual($a, $b);
686
    }
687
688
    /**
689
     * Checks whether the two specified numbers are equal.
690
     * null and empty strings are considered as 0 values.
691
     *
692
     * @param number|string $a
693
     * @param number|string $b
694
     * @return boolean
695
     */
696
    public static function areNumbersEqual($a, $b) : bool
697
    {
698
        return self::areVariablesEqual($a, $b);
699
    }
700
701
    /**
702
     * Converts a boolean value to a string. Defaults to returning
703
     * 'true' or 'false', with the additional parameter it can also
704
     * return the 'yes' and 'no' variants.
705
     *
706
     * @param boolean|string $boolean
707
     * @param boolean $yesno
708
     * @return string
709
     */
710
    public static function bool2string($boolean, bool $yesno = false) : string
711
    {
712
        // allow 'yes', 'true', 'no', 'false' string notations as well
713
        if(!is_bool($boolean)) {
714
            $boolean = self::string2bool($boolean);
715
        }
716
        
717
        if ($boolean) {
718
            if ($yesno) {
719
                return 'yes';
720
            }
721
722
            return 'true';
723
        }
724
725
        if ($yesno) {
726
            return 'no';
727
        }
728
729
        return 'false';
730
    }
731
    
732
   /**
733
    * Converts an associative array with attribute name > value pairs
734
    * to an attribute string that can be used in an HTML tag. Empty 
735
    * attribute values are ignored.
736
    * 
737
    * Example:
738
    * 
739
    * array2attributeString(array(
740
    *     'id' => 45,
741
    *     'href' => 'http://www.mistralys.com'
742
    * ));
743
    * 
744
    * Result:
745
    * 
746
    * id="45" href="http://www.mistralys.com"
747
    * 
748
    * @param array $array
749
    * @return string
750
    */
751
    public static function array2attributeString($array)
752
    {
753
        $tokens = array();
754
        foreach($array as $attr => $value) {
755
            if($value == '' || $value == null) {
756
                continue;
757
            }
758
            
759
            $tokens[] = $attr.'="'.$value.'"';
760
        }
761
        
762
        if(empty($tokens)) {
763
            return '';
764
        }
765
        
766
        return ' '.implode(' ', $tokens);
767
    }
768
    
769
   /**
770
    * Converts a string so it can safely be used in a javascript
771
    * statement in an HTML tag: uses single quotes around the string
772
    * and encodes all special characters as needed.
773
    * 
774
    * @param string $string
775
    * @return string
776
    */
777
    public static function string2attributeJS($string, $quoted=true)
778
    {
779
        $converted = addslashes(htmlspecialchars(strip_tags($string), ENT_QUOTES, 'UTF-8'));
780
        if($quoted) {
781
            $converted = "'".$converted."'";
782
        } 
783
        
784
        return $converted;
785
    }
786
    
787
   /**
788
    * Checks if the specified string is a boolean value, which
789
    * includes string representations of boolean values, like 
790
    * <code>yes</code> or <code>no</code>, and <code>true</code>
791
    * or <code>false</code>.
792
    * 
793
    * @param mixed $value
794
    * @return boolean
795
    */
796
    public static function isBoolean($value) : bool
797
    {
798
        if(is_bool($value)) {
799
            return true;
800
        }
801
        
802
        if(!is_scalar($value)) {
803
            return false;
804
        }
805
        
806
        return array_key_exists($value, self::$booleanStrings);
807
    }
808
    
809
   /**
810
    * Converts an associative array to an HTML style attribute value string.
811
    * 
812
    * @param array $subject
813
    * @return string
814
    */
815
    public static function array2styleString(array $subject) : string
816
    {
817
        $tokens = array();
818
        foreach($subject as $name => $value) {
819
            $tokens[] = $name.':'.$value;
820
        }
821
        
822
        return implode(';', $tokens);
823
    }
824
    
825
   /**
826
    * Converts a DateTime object to a timestamp, which
827
    * is PHP 5.2 compatible.
828
    * 
829
    * @param \DateTime $date
830
    * @return integer
831
    */
832
    public static function date2timestamp(\DateTime $date) : int
833
    {
834
        return (int)$date->format('U');
835
    }
836
    
837
   /**
838
    * Converts a timestamp into a DateTime instance.
839
    * @param int $timestamp
840
    * @return \DateTime
841
    */
842
    public static function timestamp2date(int $timestamp) : \DateTime
843
    {
844
        $date = new \DateTime();
845
        $date->setTimestamp($timestamp);
846
        return $date;
847
    }
848
    
849
   /**
850
    * Strips an absolute path to a file within the application
851
    * to make the path relative to the application root path.
852
    * 
853
    * @param string $path
854
    * @return string
855
    * 
856
    * @see FileHelper::relativizePath()
857
    * @see FileHelper::relativizePathByDepth()
858
    */
859
    public static function fileRelativize(string $path) : string
860
    {
861
        return FileHelper::relativizePathByDepth($path);
862
    }
863
    
864
    /**
865
    * Converts a PHP regex to a javascript RegExp object statement.
866
    * 
867
    * NOTE: This is an alias for the JSHelper's `convertRegex` method. 
868
    * More details are available on its usage there.
869
    *
870
    * @param string $regex A PHP preg regex
871
    * @param string $statementType The type of statement to return: Defaults to a statement to create a RegExp object.
872
    * @return array|string Depending on the specified return type.
873
    * 
874
    * @see JSHelper::buildRegexStatement()
875
    */
876
    public static function regex2js(string $regex, string $statementType=JSHelper::JS_REGEX_OBJECT)
877
    {
878
        return JSHelper::buildRegexStatement($regex, $statementType);
879
    }
880
    
881
   /**
882
    * Converts the specified variable to JSON. Works just
883
    * like the native `json_encode` method, except that it
884
    * will trigger an exception on failure, which has the 
885
    * json error details included in its developer details.
886
    * 
887
    * @param mixed $variable
888
    * @param int|NULL $options JSON encode options.
889
    * @param int|NULL $depth 
890
    * @throws ConvertHelper_Exception
891
    * @return string
892
    */
893
    public static function var2json($variable, int $options=0, int $depth=512) : string
894
    {
895
        $result = json_encode($variable, $options, $depth);
896
        
897
        if($result !== false) {
898
            return $result;
899
        }
900
        
901
        throw new ConvertHelper_Exception(
902
            'Could not create json array'.json_last_error_msg(),
903
            sprintf(
904
                'The call to json_encode failed for the variable [%s]. JSON error details: #%s, %s',
905
                parseVariable($variable)->toString(),
906
                json_last_error(),
907
                json_last_error_msg()
908
            ),
909
            self::ERROR_JSON_ENCODE_FAILED
910
        );
911
    }
912
    
913
   /**
914
    * Strips all known UTF byte order marks from the specified string.
915
    * 
916
    * @param string $string
917
    * @return string
918
    */
919
    public static function stripUTFBom($string)
920
    {
921
        $boms = FileHelper::getUTFBOMs();
922
        foreach($boms as $bomChars) {
923
            $length = mb_strlen($bomChars);
924
            $text = mb_substr($string, 0, $length);
925
            if($text==$bomChars) {
926
                return mb_substr($string, $length);
927
            }
928
        }
929
        
930
        return $string;
931
    }
932
933
   /**
934
    * Converts a string to valid utf8, regardless
935
    * of the string's encoding(s).
936
    * 
937
    * @param string $string
938
    * @return string
939
    */
940
    public static function string2utf8($string)
941
    {
942
        if(!self::isStringASCII($string)) {
943
            return \ForceUTF8\Encoding::toUTF8($string);
944
        }
945
        
946
        return $string;
947
    }
948
    
949
   /**
950
    * Checks whether the specified string is an ASCII
951
    * string, without any special or UTF8 characters.
952
    * Note: empty strings and NULL are considered ASCII.
953
    * Any variable types other than strings are not.
954
    * 
955
    * @param mixed $string
956
    * @return boolean
957
    */
958
    public static function isStringASCII($string) : bool
959
    {
960
        if($string === '' || $string === NULL) {
961
            return true;
962
        }
963
        
964
        if(!is_string($string)) {
965
            return false;
966
        }
967
        
968
        return !preg_match('/[^\x00-\x7F]/', $string);
969
    }
970
    
971
    public static function highlight_url($url)
972
    {
973
        $url = htmlspecialchars($url);
974
        $url = str_replace(
975
            array('/', '='), 
976
            array('/<wbr>', '=<wbr>'), 
977
            $url
978
        );
979
        return $url;
980
    }
981
982
   /**
983
    * Calculates a percentage match of the source string with the target string.
984
    * 
985
    * Options are:
986
    * 
987
    * - maxLevenshtein, default: 10
988
    *   Any levenshtein results above this value are ignored.
989
    *   
990
    * - precision, default: 1
991
    *   The precision of the percentage float value
992
    * 
993
    * @param string $source
994
    * @param string $target
995
    * @param array $options
996
    * @return float
997
    */
998
    public static function matchString($source, $target, $options=array())
999
    {
1000
        $defaults = array(
1001
            'maxLevenshtein' => 10,
1002
            'precision' => 1
1003
        );
1004
        
1005
        $options = array_merge($defaults, $options);
1006
        
1007
        // avoid doing this via levenshtein
1008
        if($source == $target) {
1009
            return 100;
1010
        }
1011
        
1012
        $diff = levenshtein($source, $target);
1013
        if($diff > $options['maxLevenshtein']) {
1014
            return 0;
1015
        }
1016
        
1017
        $percent = $diff * 100 / ($options['maxLevenshtein'] + 1);
1018
        return round(100 - $percent, $options['precision']);
1019
    }
1020
    
1021
    public static function interval2string(\DateInterval $interval)
1022
    {
1023
        $tokens = array('y', 'm', 'd', 'h', 'i', 's');
1024
        
1025
        $offset = 0;
1026
        $keep = array();
1027
        foreach($tokens as $token) {
1028
            if($interval->$token > 0) {
1029
                $keep = array_slice($tokens, $offset);
1030
                break;
1031
            }
1032
            
1033
            $offset++;
1034
        }
1035
        
1036
        $parts = array();
1037
        foreach($keep as $token) 
1038
        {
1039
            $value = $interval->$token;
1040
            $label = '';
1041
            
1042
            $suffix = 'p';
1043
            if($value == 1) { $suffix = 's'; }
1044
            $token .= $suffix;
1045
            
1046
            switch($token) {
1047
                case 'ys': $label = t('1 year'); break;
1048
                case 'yp': $label = t('%1$s years', $value); break;
1049
                case 'ms': $label = t('1 month'); break;
1050
                case 'mp': $label = t('%1$s months', $value); break;
1051
                case 'ds': $label = t('1 day'); break;
1052
                case 'dp': $label = t('%1$s days', $value); break;
1053
                case 'hs': $label = t('1 hour'); break;
1054
                case 'hp': $label = t('%1$s hours', $value); break;
1055
                case 'is': $label = t('1 minute'); break;
1056
                case 'ip': $label = t('%1$s minutes', $value); break;
1057
                case 'ss': $label = t('1 second'); break;
1058
                case 'sp': $label = t('%1$s seconds', $value); break;
1059
            }
1060
            
1061
            $parts[] = $label;
1062
        }
1063
        
1064
        if(count($parts) == 1) {
1065
            return $parts[0];
1066
        } 
1067
        
1068
        $last = array_pop($parts);
1069
        
1070
        return t('%1$s and %2$s', implode(', ', $parts), $last);
1071
    }
1072
    
1073
    const INTERVAL_DAYS = 'days';
1074
    
1075
    const INTERVAL_HOURS = 'hours';
1076
    
1077
    const INTERVAL_MINUTES = 'minutes';
1078
    
1079
    const INTERVAL_SECONDS = 'seconds';
1080
    
1081
   /**
1082
    * Converts an interval to its total amount of days.
1083
    * @param \DateInterval $interval
1084
    * @return int
1085
    */
1086
    public static function interval2days(\DateInterval $interval) : int
1087
    {
1088
        return self::interval2total($interval, self::INTERVAL_DAYS);
1089
    }
1090
1091
   /**
1092
    * Converts an interval to its total amount of hours.
1093
    * @param \DateInterval $interval
1094
    * @return int
1095
    */
1096
    public static function interval2hours(\DateInterval $interval) : int
1097
    {
1098
        return self::interval2total($interval, self::INTERVAL_HOURS);
1099
    }
1100
    
1101
   /**
1102
    * Converts an interval to its total amount of minutes. 
1103
    * @param \DateInterval $interval
1104
    * @return int
1105
    */
1106
    public static function interval2minutes(\DateInterval $interval) : int
1107
    {
1108
        return self::interval2total($interval, self::INTERVAL_MINUTES);
1109
    }
1110
    
1111
   /**
1112
    * Converts an interval to its total amount of seconds.
1113
    * @param \DateInterval $interval
1114
    * @return int
1115
    */    
1116
    public static function interval2seconds(\DateInterval $interval) : int
1117
    {
1118
        return self::interval2total($interval, self::INTERVAL_SECONDS);
1119
    }
1120
    
1121
   /**
1122
    * Calculates the total amount of days / hours / minutes or seconds
1123
    * of a date interval object (depending in the specified units), and 
1124
    * returns the total amount.
1125
    * 
1126
    * @param \DateInterval $interval
1127
    * @param string $unit What total value to calculate.
1128
    * @return integer
1129
    * 
1130
    * @see ConvertHelper::INTERVAL_SECONDS
1131
    * @see ConvertHelper::INTERVAL_MINUTES
1132
    * @see ConvertHelper::INTERVAL_HOURS
1133
    * @see ConvertHelper::INTERVAL_DAYS
1134
    */
1135
    public static function interval2total(\DateInterval $interval, $unit=self::INTERVAL_SECONDS) : int
1136
    {
1137
        $total = $interval->format('%a');
1138
        if ($unit == self::INTERVAL_DAYS) {
1139
            return (int)$total;
1140
        }
1141
        
1142
        $total = ($total * 24) + ($interval->h );
1143
        if ($unit == self::INTERVAL_HOURS) {
1144
            return (int)$total;
1145
        }
1146
    
1147
        $total = ($total * 60) + ($interval->i );
1148
        if ($unit == self::INTERVAL_MINUTES) {
1149
            return (int)$total;
1150
        }
1151
1152
        $total = ($total * 60) + ($interval->s );
1153
        if ($unit == self::INTERVAL_SECONDS) {
1154
            return (int)$total;
1155
        }
1156
        
1157
        return 0;
1158
    }
1159
1160
    protected static $days;
1161
    
1162
    protected static $daysShort;
1163
1164
    protected static $daysInvariant = array(
1165
        'Monday',
1166
        'Tuesday',
1167
        'Wednesday',
1168
        'Thursday',
1169
        'Friday',
1170
        'Saturday',
1171
        'Sunday'
1172
    );
1173
    
1174
   /**
1175
    * Converts a date to the corresponding day name.
1176
    * 
1177
    * @param \DateTime $date
1178
    * @param string $short
1179
    * @return string|NULL
1180
    */
1181
    public static function date2dayName(\DateTime $date, $short=false)
1182
    {
1183
        $day = $date->format('l');
1184
        $invariant = self::getDayNamesInvariant();
1185
        
1186
        $idx = array_search($day, $invariant);
1187
        if($idx !== false) {
1188
            $localized = self::getDayNames($short);
0 ignored issues
show
Bug introduced by
It seems like $short can also be of type false; however, parameter $short of AppUtils\ConvertHelper::getDayNames() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1188
            $localized = self::getDayNames(/** @scrutinizer ignore-type */ $short);
Loading history...
1189
            return $localized[$idx];
1190
        }
1191
        
1192
        return null;
1193
    }
1194
    
1195
   /**
1196
    * Retrieves a list of english day names.
1197
    * @return string[]
1198
    */
1199
    public static function getDayNamesInvariant()
1200
    {
1201
        return self::$daysInvariant;
1202
    }
1203
    
1204
   /**
1205
    * Retrieves the day names list for the current locale.
1206
    * 
1207
    * @param string $short
1208
    * @return string[]
1209
    */
1210
    public static function getDayNames($short=false)
1211
    {
1212
        if($short) {
1213
            if(!isset(self::$daysShort)) {
1214
                self::$daysShort = array(
1215
                    t('Mon'),
1216
                    t('Tue'),
1217
                    t('Wed'),
1218
                    t('Thu'),
1219
                    t('Fri'),
1220
                    t('Sat'),
1221
                    t('Sun')
1222
                );
1223
            }
1224
            
1225
            return self::$daysShort;
1226
        }
1227
        
1228
        if(!isset(self::$days)) {
1229
            self::$days = array(
1230
                t('Monday'),
1231
                t('Tuesday'),
1232
                t('Wednesday'),
1233
                t('Thursday'),
1234
                t('Friday'),
1235
                t('Saturday'),
1236
                t('Sunday')
1237
            );
1238
        }
1239
        
1240
        return self::$days;
1241
    }
1242
1243
    /**
1244
     * Implodes an array with a separator character, and the last item with "add".
1245
     * 
1246
     * @param array $list The indexed array with items to implode.
1247
     * @param string $sep The separator character to use.
1248
     * @param string $conjunction The word to use as conjunction with the last item in the list. NOTE: include spaces as needed.
1249
     * @return string
1250
     */
1251
    public static function implodeWithAnd(array $list, $sep = ', ', $conjunction = null)
1252
    {
1253
        if(empty($list)) {
1254
            return '';
1255
        }
1256
        
1257
        if(empty($conjunction)) {
1258
            $conjunction = t('and');
1259
        }
1260
        
1261
        $last = array_pop($list);
1262
        if($list) {
1263
            return implode($sep, $list) . $conjunction . ' ' . $last;
1264
        }
1265
        
1266
        return $last;
1267
    }
1268
    
1269
   /**
1270
    * Splits a string into an array of all characters it is composed of.
1271
    * Unicode character safe.
1272
    * 
1273
    * NOTE: Spaces and newlines (both \r and \n) are also considered single
1274
    * characters.
1275
    * 
1276
    * @param string $string
1277
    * @return array
1278
    */
1279
    public static function string2array(string $string) : array
1280
    {
1281
        $result = preg_split('//u', $string, null, PREG_SPLIT_NO_EMPTY);
1282
        if($result !== false) {
1283
            return $result;
1284
        }
1285
        
1286
        return array();
1287
    }
1288
    
1289
   /**
1290
    * Checks whether the specified string contains HTML code.
1291
    * 
1292
    * @param string $string
1293
    * @return boolean
1294
    */
1295
    public static function isStringHTML(string $string) : bool
1296
    {
1297
        if(preg_match('%<[a-z/][\s\S]*>%siU', $string)) {
1298
            return true;
1299
        }
1300
        
1301
        $decoded = html_entity_decode($string);
1302
        if($decoded !== $string) {
1303
            return true;
1304
        }
1305
        
1306
        return false;
1307
    }
1308
    
1309
   /**
1310
    * UTF8-safe wordwrap method: works like the regular wordwrap
1311
    * PHP function but compatible with UTF8. Otherwise the lengths
1312
    * are no calculated correctly.
1313
    * 
1314
    * @param string $str
1315
    * @param int $width
1316
    * @param string $break
1317
    * @param bool $cut
1318
    * @return string
1319
    * @see https://stackoverflow.com/a/4988494/2298192
1320
    */
1321
    public static function wordwrap($str, $width = 75, $break = "\n", $cut = false) 
1322
    {
1323
        $lines = explode($break, $str);
1324
        
1325
        foreach ($lines as &$line) 
1326
        {
1327
            $line = rtrim($line);
1328
            if (mb_strlen($line) <= $width) {
1329
                continue;
1330
            }
1331
        
1332
            $words = explode(' ', $line);
1333
            $line = '';
1334
            $actual = '';
1335
            foreach ($words as $word) 
1336
            {
1337
                if (mb_strlen($actual.$word) <= $width) 
1338
                {
1339
                    $actual .= $word.' ';
1340
                } 
1341
                else 
1342
                {
1343
                    if ($actual != '') {
1344
                        $line .= rtrim($actual).$break;
1345
                    }
1346
                    
1347
                    $actual = $word;
1348
                    if ($cut) 
1349
                    {
1350
                        while (mb_strlen($actual) > $width) {
1351
                            $line .= mb_substr($actual, 0, $width).$break;
1352
                            $actual = mb_substr($actual, $width);
1353
                        }
1354
                    }
1355
                    
1356
                    $actual .= ' ';
1357
                }
1358
            }
1359
            
1360
            $line .= trim($actual);
1361
        }
1362
        
1363
        return implode($break, $lines);
1364
    }
1365
    
1366
   /**
1367
    * Calculates the byte length of a string, taking into 
1368
    * account any unicode characters.
1369
    * 
1370
    * @param string $string
1371
    * @return int
1372
    * @see https://stackoverflow.com/a/9718273/2298192
1373
    */
1374
    public static function string2bytes($string)
1375
    {
1376
        return mb_strlen($string, '8bit');
1377
    }
1378
    
1379
   /**
1380
    * Creates a short, 8-character long hash for the specified string.
1381
    * 
1382
    * WARNING: Not cryptographically safe.
1383
    * 
1384
    * @param string $string
1385
    * @return string
1386
    */
1387
    public static function string2shortHash($string)
1388
    {
1389
        return hash('crc32', $string, false);
1390
    }
1391
    
1392
    public static function string2hash($string)
1393
    {
1394
        return md5($string);
1395
    }
1396
    
1397
    public static function callback2string($callback) : string
1398
    {
1399
        return parseVariable($callback)->toString();
1400
    }
1401
1402
    public static function exception2info(\Throwable $e) : ConvertHelper_ThrowableInfo
1403
    {
1404
        return self::throwable2info($e);
1405
    }
1406
    
1407
    public static function throwable2info(\Throwable $e) : ConvertHelper_ThrowableInfo
1408
    {
1409
        return ConvertHelper_ThrowableInfo::fromThrowable($e);
1410
    }
1411
    
1412
   /**
1413
    * Parses the specified query string like the native 
1414
    * function <code>parse_str</code>, without the key
1415
    * naming limitations.
1416
    * 
1417
    * Using parse_str, dots or spaces in key names are 
1418
    * replaced by underscores. This method keeps all names
1419
    * intact.
1420
    * 
1421
    * It still uses the parse_str implementation as it 
1422
    * is tested and tried, but fixes the parameter names
1423
    * after parsing, as needed.
1424
    * 
1425
    * @param string $queryString
1426
    * @return array
1427
    * @see https://www.php.net/manual/en/function.parse-str.php
1428
    */
1429
    public static function parseQueryString(string $queryString) : array
1430
    {
1431
        // allow HTML entities notation
1432
        $queryString = str_replace('&amp;', '&', $queryString);
1433
        
1434
        $paramNames = array();
1435
        
1436
        // extract parameter names from the query string
1437
        $result = array();
1438
        preg_match_all('/&?([^&]+)=.*/sixU', $queryString, $result, PREG_PATTERN_ORDER);
1439
        if(isset($result[1])) {
1440
            $paramNames = $result[1];
1441
        }
1442
        
1443
        // to avoid iterating over the param names, we simply concatenate it
1444
        $search = implode('', $paramNames);
1445
        
1446
        // store whether we need to adjust any of the names: 
1447
        // this is true if we find dots or spaces in any of them.
1448
        $fixRequired = stristr($search, '.') || stristr($search, ' ');
1449
1450
        unset($search);
1451
        
1452
        $table = array();
1453
        
1454
        // A fix is required: replace all parameter names with placeholders,
1455
        // which do not conflict with parse_str and which will be restored
1456
        // with the actual parameter names after the parsing.
1457
        //
1458
        // It is necessary to do this even before the parsing, to resolve
1459
        // possible naming conflicts like having both parameters "foo.bar" 
1460
        // and "foo_bar" in the query string: since "foo.bar" would be converted
1461
        // to "foo_bar", one of the two would be replaced.
1462
        if($fixRequired) 
1463
        {
1464
            $counter = 1;
1465
            $placeholders = array();
1466
            foreach($paramNames as $paramName)
1467
            {
1468
                 // create a unique placeholder name
1469
                 $placeholder = '__PLACEHOLDER'.$counter.'__';
1470
                 
1471
                 // store the placeholder name to replace later
1472
                 $table[$placeholder] = $paramName;
1473
                 
1474
                 // add the placeholder to replace in the query string before parsing
1475
                 $placeholders[$paramName.'='] = $placeholder.'=';
1476
                 
1477
                 $counter++;
1478
            }
1479
            
1480
            // next challenge: replacing the parameter names by placeholders
1481
            // safely. We sort the list by longest name first, to avoid shorter 
1482
            // parameter names being replaced first that can be part of longer ones.
1483
            uksort($placeholders, function($a, $b) {
1484
                return strlen($b) - strlen($a);
1485
            });
1486
            
1487
            // replace all instances with the placeholder
1488
            $queryString = str_replace(array_keys($placeholders), array_values($placeholders), $queryString);
1489
        }
1490
        
1491
        // parse the query string natively
1492
        $parsed = array();
1493
        parse_str($queryString, $parsed);
1494
        
1495
        // do any of the parameter names need to be fixed?
1496
        if(!$fixRequired) {
1497
            return $parsed;
1498
        }
1499
        
1500
        $keep = array();
1501
        
1502
        foreach($parsed as $name => $value)
1503
        {
1504
             $keep[$table[$name]] = $value;
1505
        }
1506
        
1507
        return $keep;
1508
    }
1509
1510
   /**
1511
    * Searches for needle in the specified string, and returns a list
1512
    * of all occurrences, including the matched string. The matched 
1513
    * string is useful when doing a case insensitive search, as it 
1514
    * shows the exact matched case of needle.
1515
    *   
1516
    * @param string $needle
1517
    * @param string $haystack
1518
    * @param bool $caseInsensitive
1519
    * @return ConvertHelper_StringMatch[]
1520
    */
1521
    public static function findString(string $needle, string $haystack, bool $caseInsensitive=false)
1522
    {
1523
        if($needle === '') {
1524
            return array();
1525
        }
1526
        
1527
        $function = 'mb_strpos';
1528
        if($caseInsensitive) {
1529
            $function = 'mb_stripos';
1530
        }
1531
        
1532
        $pos = 0;
1533
        $positions = array();
1534
        $length = mb_strlen($needle);
1535
        
1536
        while( ($pos = $function($haystack, $needle, $pos)) !== false) 
1537
        {
1538
            $match = mb_substr($haystack, $pos, $length);
1539
            $positions[] = new ConvertHelper_StringMatch($pos, $match);
1540
            $pos += $length;
1541
        }
1542
        
1543
        return $positions;
1544
    }
1545
    
1546
   /**
1547
    * Like explode, but trims all entries, and removes 
1548
    * empty entries from the resulting array.
1549
    * 
1550
    * @param string $delimiter
1551
    * @param string $string
1552
    * @return string[]
1553
    */
1554
    public static function explodeTrim(string $delimiter, string $string) : array
1555
    {
1556
        if(empty($string) || empty($delimiter)) {
1557
            return array();
1558
        }
1559
        
1560
        $tokens = explode($delimiter, $string);
1561
        $tokens = array_map('trim', $tokens);
1562
        
1563
        $keep = array();
1564
        foreach($tokens as $token) {
1565
            if($token !== '') {
1566
                $keep[] = $token;
1567
            }
1568
        }
1569
        
1570
        return $keep;
1571
    }
1572
    
1573
    protected static $eolChars;
1574
1575
   /**
1576
    * Detects the most used end-of-line character in the subject string.
1577
    * 
1578
    * @param string $str The string to check.
1579
    * @return NULL|ConvertHelper_EOL The detected EOL instance, or NULL if none has been detected.
1580
    */
1581
    public static function detectEOLCharacter(string $subjectString) : ?ConvertHelper_EOL
1582
    {
1583
        if(empty($subjectString)) {
1584
            return null;
1585
        }
1586
        
1587
        if(!isset(self::$eolChars))
1588
        {
1589
            $cr = chr((int)hexdec('0d'));
1590
            $lf = chr((int)hexdec('0a'));
1591
            
1592
           self::$eolChars = array(
1593
               array(
1594
                   'char' => $cr.$lf,
1595
                   'type' => ConvertHelper_EOL::TYPE_CRLF,
1596
                   'description' => t('Carriage return followed by a line feed'),
1597
               ),
1598
               array(
1599
                   'char' => $lf.$cr,
1600
                   'type' => ConvertHelper_EOL::TYPE_LFCR,
1601
                   'description' => t('Line feed followed by a carriage return'),
1602
               ),
1603
               array(
1604
                  'char' => $lf,
1605
                  'type' => ConvertHelper_EOL::TYPE_LF,
1606
                  'description' => t('Line feed'),
1607
               ),
1608
               array(
1609
                  'char' => $cr,
1610
                  'type' => ConvertHelper_EOL::TYPE_CR,
1611
                  'description' => t('Carriage Return'),
1612
               ),
1613
            );
1614
        }
1615
        
1616
        $max = 0;
1617
        $results = array();
1618
        foreach(self::$eolChars as $def) 
1619
        {
1620
            $amount = substr_count($subjectString, $def['char']);
1621
            
1622
            if($amount > $max)
1623
            {
1624
                $max = $amount;
1625
                $results[] = $def;
1626
            }
1627
        }
1628
        
1629
        if(empty($results)) {
1630
            return null;
1631
        }
1632
        
1633
        return new ConvertHelper_EOL(
1634
            $results[0]['char'], 
1635
            $results[0]['type'],
1636
            $results[0]['description']
1637
        );
1638
    }
1639
1640
   /**
1641
    * Removes the specified keys from the target array,
1642
    * if they exist.
1643
    * 
1644
    * @param array $array
1645
    * @param array $keys
1646
    */
1647
    public static function arrayRemoveKeys(array &$array, array $keys) : void
1648
    {
1649
        foreach($keys as $key) 
1650
        {
1651
            if(array_key_exists($key, $array)) {
1652
                unset($array[$key]); 
1653
            }
1654
        }
1655
    }
1656
    
1657
   /**
1658
    * Checks if the specified variable is an integer or a string containing an integer.
1659
    * Accepts both positive and negative integers.
1660
    * 
1661
    * @param mixed $value
1662
    * @return bool
1663
    */
1664
    public static function isInteger($value) : bool
1665
    {
1666
        if(is_int($value)) {
1667
            return true;
1668
        }
1669
        
1670
        // booleans get converted to numbers, so they would
1671
        // actually match the regex.
1672
        if(is_bool($value)) {
1673
            return false;
1674
        }
1675
        
1676
        if(is_string($value) && $value !== '') {
1677
            return preg_match('/\A-?\d+\z/', $value) === 1;
1678
        }
1679
        
1680
        return false;    
1681
    }
1682
    
1683
   /**
1684
    * Converts an amount of seconds to a DateInterval object.
1685
    * 
1686
    * @param int $seconds
1687
    * @return \DateInterval
1688
    * @throws ConvertHelper_Exception If the date interval cannot be created.
1689
    * 
1690
    * @see ConvertHelper::ERROR_CANNOT_GET_DATE_DIFF
1691
    */
1692
    public static function seconds2interval(int $seconds) : \DateInterval
1693
    {
1694
        // The DateInterval::format() method does not recalculate carry 
1695
        // over points in days / seconds / months etc, so we calculate the
1696
        // actual interval using dates to ensure we get a fully populated
1697
        // interval object.
1698
        $d1 = new \DateTime();
1699
        $d2 = new \DateTime();
1700
        $d2->add(new \DateInterval('PT'.$seconds.'S'));
1701
        
1702
        $result = $d2->diff($d1);
1703
        if($result !== false) {
1704
            return $result;
1705
        }
1706
        
1707
        throw new ConvertHelper_Exception(
1708
            'Cannot create interval',
1709
            sprintf('Getting the date diff failed to retrieve the interval for [%s] seconds.', $seconds),
1710
            self::ERROR_CANNOT_GET_DATE_DIFF
1711
        );
1712
    }
1713
}
1714