Passed
Branch master (43d553)
by Sebastian
02:54
created

ConvertHelper::areNumbersEqual()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 3
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_INVALID_BOOLEAN_STRING = 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
        $normalizer = new ConvertHelper_TabsNormalizer();
44
        $normalizer->convertTabsToSpaces($tabs2spaces);
45
        
46
        return $normalizer->normalize($string);
47
    }
48
49
    /**
50
     * Converts tabs to spaces in the specified string.
51
     * 
52
     * @param string $string
53
     * @param int $tabSize The amount of spaces per tab.
54
     * @return string
55
     */
56
    public static function tabs2spaces(string $string, int $tabSize=4) : string
57
    {
58
        return str_replace("\t", str_repeat(' ', $tabSize), $string);
59
    }
60
    
61
   /**
62
    * Converts spaces to tabs in the specified string.
63
    * 
64
    * @param string $string
65
    * @param int $tabSize The amount of spaces per tab in the source string.
66
    * @return string
67
    */
68
    public static function spaces2tabs(string $string, int $tabSize=4) : string
69
    {
70
        return str_replace(str_repeat(' ', $tabSize), "\t", $string);
71
    }
72
    
73
    public static function hidden2visible(string $string) : string
74
    {
75
        $converter = new ConvertHelper_HiddenConverter();
76
        
77
        return $converter->convert($string);
78
    }
79
    
80
   /**
81
    * Converts the specified amount of seconds into
82
    * a human readable string split in months, weeks,
83
    * days, hours, minutes and seconds.
84
    *
85
    * @param float $seconds
86
    * @return string
87
    */
88
    public static function time2string($seconds)
89
    {
90
        static $units = null;
91
        if (is_null($units)) {
92
            $units = array(
93
                array(
94
                    'value' => 31 * 7 * 24 * 3600,
95
                    'singular' => t('month'),
96
                    'plural' => t('months')
97
                ),
98
                array(
99
                    'value' => 7 * 24 * 3600,
100
                    'singular' => t('week'),
101
                    'plural' => t('weeks')
102
                ),
103
                array(
104
                    'value' => 24 * 3600,
105
                    'singular' => t('day'),
106
                    'plural' => t('days')
107
                ),
108
                array(
109
                    'value' => 3600,
110
                    'singular' => t('hour'),
111
                    'plural' => t('hours')
112
                ),
113
                array(
114
                    'value' => 60,
115
                    'singular' => t('minute'),
116
                    'plural' => t('minutes')
117
                ),
118
                array(
119
                    'value' => 1,
120
                    'singular' => t('second'),
121
                    'plural' => t('seconds')
122
                )
123
            );
124
        }
125
126
        // specifically handle zero
127
        if ($seconds <= 0) {
128
            return '0 ' . t('seconds');
129
        }
130
        
131
        if($seconds < 1) {
132
            return t('less than a second');
133
        }
134
135
        $tokens = array();
136
        foreach ($units as $def) {
137
            $quot = intval($seconds / $def['value']);
138
            if ($quot) {
139
                $item = $quot . ' ';
140
                if (abs($quot) > 1) {
141
                    $item .= $def['plural'];
142
                } else {
143
                    $item .= $def['singular'];
144
                }
145
146
                $tokens[] = $item;
147
                $seconds -= $quot * $def['value'];
148
            }
149
        }
150
151
        $last = array_pop($tokens);
152
        if (empty($tokens)) {
153
            return $last;
154
        }
155
156
        return implode(', ', $tokens) . ' ' . t('and') . ' ' . $last;
157
    }
158
159
   /**
160
    * Converts a timestamp into an easily understandable
161
    * format, e.g. "2 hours", "1 day", "3 months"
162
    *
163
    * If you set the date to parameter, the difference
164
    * will be calculated between the two dates and not
165
    * the current time.
166
    *
167
    * @param integer|\DateTime $datefrom
168
    * @param integer|\DateTime $dateto
169
    * @return string
170
    */
171
    public static function duration2string($datefrom, $dateto = -1) : string
172
    {
173
         $converter = new ConvertHelper_DurationConverter();
174
         
175
         if($datefrom instanceof \DateTime)
176
         {
177
             $converter->setDateFrom($datefrom);
178
         }
179
         else
180
         {
181
             $converter->setDateFrom(self::timestamp2date($datefrom)); 
182
         }
183
184
         if($dateto instanceof \DateTime)
185
         {
186
             $converter->setDateTo($dateto);
187
         }
188
         else if($dateto > 0)
189
         {
190
             $converter->setDateTo(self::timestamp2date($dateto));
191
         }
192
193
         return $converter->convert();
194
    }
195
196
    /**
197
     * Adds syntax highlighting to the specified SQL string in HTML format
198
     * @param string $sql
199
     * @return string
200
     */
201
    public static function highlight_sql($sql)
202
    {
203
        $geshi = new  \GeSHi($sql, 'sql');
204
205
        return $geshi->parse_code();
206
    }
207
    
208
    public static function highlight_xml($xml, $formatSource=false)
209
    {
210
        if($formatSource) 
211
        {
212
            $dom = new \DOMDocument();
213
            $dom->loadXML($xml);
214
            $dom->preserveWhiteSpace = false;
215
            $dom->formatOutput = true;
216
            
217
            $xml = $dom->saveXML();
218
        }
219
        
220
        $geshi = new \GeSHi($xml, 'xml');
221
        
222
        return $geshi->parse_code();
223
    }
224
225
    public static function highlight_php($php)
226
    {
227
        $geshi = new \GeSHi($php, 'php');
228
    
229
        return $geshi->parse_code();
230
    }
231
    
232
   /**
233
    * Converts a number of bytes to a human readable form,
234
    * e.g. xx Kb / xx Mb / xx Gb
235
    *
236
    * @param int $bytes The amount of bytes to convert.
237
    * @param int $precision The amount of decimals
238
    * @param int $base The base to calculate with: Base 10 is default (=1000 Bytes in a KB), Base 2 is mainly used for Windows memory (=1024 Bytes in a KB).
239
    * @return string
240
    * 
241
    * @see https://en.m.wikipedia.org/wiki/Megabyte#Definitions
242
    */
243
    public static function bytes2readable(int $bytes, int $precision = 1, int $base = ConvertHelper_StorageSizeEnum::BASE_10) : string
244
    {
245
        return self::parseBytes($bytes)->toString($precision, $base);
246
    }
247
    
248
   /**
249
    * Parses a number of bytes, and creates a converter instance which
250
    * allows doing common operations with it.
251
    * 
252
    * @param int $bytes
253
    * @return ConvertHelper_ByteConverter
254
    */
255
    public static function parseBytes(int $bytes) : ConvertHelper_ByteConverter
256
    {
257
        return new ConvertHelper_ByteConverter($bytes);
258
    }
259
260
   /**
261
    * Cuts a text to the specified length if it is longer than the
262
    * target length. Appends a text to signify it has been cut at 
263
    * the end of the string.
264
    * 
265
    * @param string $text
266
    * @param int $targetLength
267
    * @param string $append
268
    * @return string
269
    */
270
    public static function text_cut(string $text, int $targetLength, string $append = '...') : string
271
    {
272
        $length = mb_strlen($text);
273
        if ($length <= $targetLength) {
274
            return $text;
275
        }
276
277
        $text = trim(mb_substr($text, 0, $targetLength)) . $append;
278
279
        return $text;
280
    }
281
282
    public static function var_dump($var, $html=true) : string
283
    {
284
        $info = parseVariable($var);
285
        
286
        if($html) {
287
            return $info->toHTML();
288
        }
289
        
290
        return $info->toString();
291
    }
292
    
293
   /**
294
    * Pretty print_r.
295
    * 
296
    * @param mixed $var The variable to dump.
297
    * @param bool $return Whether to return the dumped code.
298
    * @param bool $html Whether to style the dump as HTML.
299
    * @return string
300
    */
301
    public static function print_r($var, bool $return=false, bool $html=true) : string
302
    {
303
        $result = parseVariable($var)->enableType()->toString();
304
        
305
        if($html) 
306
        {
307
            $result = 
308
            '<pre style="background:#fff;color:#333;padding:16px;border:solid 1px #bbb;border-radius:4px">'.
309
                $result.
310
            '</pre>';
311
        }
312
        
313
        if(!$return) 
314
        {
315
            echo $result;
316
        }
317
        
318
        return $result;
319
    }
320
    
321
    protected static $booleanStrings = array(
322
        1 => true,
323
        0 => false,
324
        'true' => true,
325
        'false' => false,
326
        'yes' => true,
327
        'no' => false
328
    );
329
330
   /**
331
    * Converts a string, number or boolean value to a boolean value.
332
    * 
333
    * @param mixed $string
334
    * @throws ConvertHelper_Exception
335
    * @return bool
336
    * 
337
    * @see ConvertHelper::ERROR_INVALID_BOOLEAN_STRING
338
    */
339
    public static function string2bool($string) : bool
340
    {
341
        if($string === '' || $string === null || !is_scalar($string)) 
342
        {
343
            return false;
344
        }
345
        
346
        if(is_bool($string)) 
347
        {
348
            return $string;
349
        }
350
351
        if(array_key_exists($string, self::$booleanStrings)) 
352
        {
353
            return self::$booleanStrings[$string];
354
        }
355
         
356
        throw new ConvertHelper_Exception(
357
            'Invalid string boolean representation',
358
            sprintf(
359
                'Cannot convert [%s] to a boolean value.',
360
                parseVariable($string)->enableType()->toString()
361
            ),
362
            self::ERROR_INVALID_BOOLEAN_STRING
363
        );
364
    }
365
    
366
   /**
367
    * Whether the specified string is a boolean string or boolean value.
368
    * Alias for {@link ConvertHelper::isBoolean()}.
369
    * 
370
    * @param mixed $string
371
    * @return bool
372
    * @deprecated
373
    * @see ConvertHelper::isBoolean()
374
    */
375
    public static function isBooleanString($string) : bool
376
    {
377
        return self::isBoolean($string);
378
    }
379
380
   /**
381
    * Alias for the {@\AppUtils\XMLHelper::string2xml()} method.
382
    * 
383
    * @param string $text
384
    * @return string
385
    * @deprecated
386
    */
387
    public static function text_makeXMLCompliant($text)
388
    {
389
        return XMLHelper::string2xml($text);
390
    }
391
392
    /**
393
     * Transforms a date into a generic human readable date, optionally with time.
394
     * If the year is the same as the current one, it is omitted.
395
     *
396
     * - 6 Jan 2012
397
     * - 12 Dec 2012 17:45
398
     * - 5 Aug
399
     *
400
     * @param \DateTime $date
401
     * @return string
402
     */
403
    public static function date2listLabel(\DateTime $date, $includeTime = false, $shortMonth = false)
404
    {
405
        $today = new \DateTime();
406
        if($date->format('d.m.Y') == $today->format('d.m.Y')) {
407
            $label = t('Today');
408
        } else {
409
            $label = $date->format('d') . '. ' . self::month2string((int)$date->format('m'), $shortMonth) . ' ';
410
            if ($date->format('Y') != date('Y')) {
411
                $label .= $date->format('Y');
412
            }
413
        }
414
        
415
        if ($includeTime) {
416
            $label .= $date->format(' H:i');
417
        }
418
419
        return trim($label);
420
    }
421
422
    protected static $months;
423
424
    /**
425
     * Returns a human readable month name given the month number. Can optionally
426
     * return the shorthand version of the month. Translated into the current
427
     * application locale.
428
     *
429
     * @param int|string $monthNr
430
     * @param boolean $short
431
     * @throws ConvertHelper_Exception
432
     * @return string
433
     */
434
    public static function month2string($monthNr, $short = false)
435
    {
436
        if (!isset(self::$months)) {
437
            self::$months = array(
438
                1 => array(t('January'), t('Jan')),
439
                2 => array(t('February'), t('Feb')),
440
                3 => array(t('March'), t('Mar')),
441
                4 => array(t('April'), t('Apr')),
442
                5 => array(t('May'), t('May')),
443
                6 => array(t('June'), t('Jun')),
444
                7 => array(t('July'), t('Jul')),
445
                8 => array(t('August'), t('Aug')),
446
                9 => array(t('September'), t('Sep')),
447
                10 => array(t('October'), t('Oct')),
448
                11 => array(t('November'), t('Nov')),
449
                12 => array(t('December'), t('Dec'))
450
            );
451
        }
452
453
        $monthNr = intval($monthNr);
454
        if (!isset(self::$months[$monthNr])) {
455
            throw new ConvertHelper_Exception(
456
                'Invalid month number',
457
                sprintf('%1$s is not a valid month number.', $monthNr),
458
                self::ERROR_MONTHTOSTRING_NOT_VALID_MONTH_NUMBER
459
            );
460
        }
461
462
        if ($short) {
463
            return self::$months[$monthNr][1];
464
        }
465
466
        return self::$months[$monthNr][0];
467
    }
468
469
    /**
470
     * Transliterates a string.
471
     *
472
     * @param string $string
473
     * @param string $spaceChar
474
     * @param bool $lowercase
475
     * @return string
476
     */
477
    public static function transliterate(string $string, string $spaceChar = '-', bool $lowercase = true) : string
478
    {
479
        $translit = new Transliteration();
480
        $translit->setSpaceReplacement($spaceChar);
481
        if ($lowercase) {
482
            $translit->setLowercase();
483
        }
484
485
        return $translit->convert($string);
486
    }
487
    
488
   /**
489
    * Retrieves the HEX character codes for all control
490
    * characters that the {@link stripControlCharacters()} 
491
    * method will remove.
492
    * 
493
    * @return string[]
494
    */
495
    public static function getControlCharactersAsHex()
496
    {
497
        $hexAlphabet = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
498
        
499
        $stack = array();
500
        foreach(self::$controlChars as $char)
501
        {
502
            $tokens = explode('-', $char);
503
            $start = $tokens[0];
504
            $end = $tokens[1];
505
            $prefix = substr($start, 0, 3);
506
            $range = array();
507
            foreach($hexAlphabet as $number) {
508
                $range[] = $prefix.$number;
509
            }
510
            
511
            $use = false;
512
            foreach($range as $number) {
513
                if($number == $start) {
514
                    $use = true;
515
                }
516
                
517
                if($use) {
518
                    $stack[] = $number;
519
                }
520
                
521
                if($number == $end) {
522
                    break;
523
                }
524
            }
525
        }
526
        
527
        return $stack;
528
    }
529
    
530
   /**
531
    * Retrieves an array of all control characters that
532
    * the {@link stripControlCharacters()} method will 
533
    * remove, as the actual UTF-8 characters.
534
    * 
535
    * @return string[]
536
    */
537
    public static function getControlCharactersAsUTF8()
538
    {
539
        $chars = self::getControlCharactersAsHex();
540
        
541
        $result = array();
542
        foreach($chars as $char) {
543
            $result[] = hex2bin($char);
544
        }
545
        
546
        return $result;
547
    }
548
    
549
   /**
550
    * Retrieves all control characters as JSON encoded
551
    * characters, e.g. "\u200b".
552
    * 
553
    * @return string[]
554
    */
555
    public static function getControlCharactersAsJSON()
556
    {
557
        $chars = self::getControlCharactersAsHex();
558
        
559
        $result = array();
560
        foreach($chars as $char) {
561
            $result[] = '\u'.strtolower($char);
562
        }
563
        
564
        return $result;
565
    }
566
    
567
    protected static $controlChars =  array(
568
        '0000-0008', // control chars
569
        '000E-000F', // control chars
570
        '0010-001F', // control chars
571
        '2000-200F', // non-breaking space and co
572
    );
573
    
574
    protected static $controlCharsRegex;
575
576
    /**
577
     * Removes all control characters from the specified string
578
     * that can cause problems in some cases, like creating
579
     * valid XML documents. This includes invisible non-breaking
580
     * spaces.
581
     *
582
     * @param string $string
583
     * @return string
584
     * @see https://stackoverflow.com/a/8171868/2298192
585
     * @see https://unicode-table.com/en
586
     */
587
    public static function stripControlCharacters(string $string) : string
588
    {
589
        if(empty($string)) {
590
            return $string;
591
        }
592
        
593
        // create the regex from the unicode characters list
594
        if(!isset(self::$controlCharsRegex)) 
595
        {
596
            $chars = self::getControlCharactersAsHex();
597
598
            // we use the notation \x{0000} to specify the unicode character key
599
            // in the regular expression.
600
            $stack = array();
601
            foreach($chars as $char) {
602
                $stack[] = '\x{'.$char.'}';
603
            }
604
            
605
            self::$controlCharsRegex = '/['.implode('', $stack).']/u';
606
        }
607
        
608
        return preg_replace(self::$controlCharsRegex, '', $string);
609
    }
610
611
   /**
612
    * Converts a unicode character to the PHPO notation.
613
    * 
614
    * Example:
615
    * 
616
    * <pre>unicodeChar2php('"\u0000"')</pre>
617
    * 
618
    * Returns
619
    * 
620
    * <pre>\x0</pre>
621
    * 
622
    * @param string $unicodeChar
623
    * @return string
624
    */
625
    public static function unicodeChar2php(string $unicodeChar) : string 
626
    {
627
        $unicodeChar = json_decode($unicodeChar);
628
        
629
        $output = '';
630
        $split = str_split($unicodeChar);
631
        
632
        foreach($split as $octet) 
633
        {
634
            $ordInt = ord($octet);
635
            // Convert from int (base 10) to hex (base 16), for PHP \x syntax
636
            $ordHex = base_convert((string)$ordInt, 10, 16);
637
            $output .= '\x' . $ordHex;
638
        }
639
        
640
        return $output;
641
    }
642
    
643
    /**
644
     * Removes the extension from the specified filename
645
     * and returns the name without the extension.
646
     *
647
     * Example:
648
     * filename.html > filename
649
     * passed.test.jpg > passed.test
650
     * path/to/file/document.txt > document
651
     *
652
     * @param string $filename
653
     * @return string
654
     */
655
    public static function filenameRemoveExtension($filename)
656
    {
657
        return FileHelper::removeExtension($filename);
658
    }
659
    
660
    public static function areVariablesEqual($a, $b) : bool
661
    {
662
        $a = self::convertScalarForComparison($a);
663
        $b = self::convertScalarForComparison($b);
664
665
        return $a === $b;
666
    }
667
    
668
    protected static function convertScalarForComparison($scalar)
669
    {
670
        if($scalar === '' || is_null($scalar)) {
671
            return null;
672
        }
673
        
674
        if(is_bool($scalar)) {
675
            return self::bool2string($scalar);
676
        }
677
        
678
        if(is_array($scalar)) {
679
            $scalar = md5(serialize($scalar));
680
        }
681
        
682
        if($scalar !== null && !is_scalar($scalar)) {
683
            throw new ConvertHelper_Exception(
684
                'Not a scalar value in comparison',
685
                null,
686
                self::ERROR_CANNOT_NORMALIZE_NON_SCALAR_VALUE
687
            );
688
        }
689
        
690
        return strval($scalar);
691
    }
692
693
    /**
694
     * Compares two strings to check whether they are equal.
695
     * null and empty strings are considered equal.
696
     *
697
     * @param string $a
698
     * @param string $b
699
     * @return boolean
700
     */
701
    public static function areStringsEqual($a, $b) : bool
702
    {
703
        return self::areVariablesEqual($a, $b);
704
    }
705
706
    /**
707
     * Checks whether the two specified numbers are equal.
708
     * null and empty strings are considered as 0 values.
709
     *
710
     * @param number|string $a
711
     * @param number|string $b
712
     * @return boolean
713
     */
714
    public static function areNumbersEqual($a, $b) : bool
715
    {
716
        return self::areVariablesEqual($a, $b);
717
    }
718
719
    /**
720
     * Converts a boolean value to a string. Defaults to returning
721
     * 'true' or 'false', with the additional parameter it can also
722
     * return the 'yes' and 'no' variants.
723
     *
724
     * @param boolean|string $boolean
725
     * @param boolean $yesno
726
     * @return string
727
     */
728
    public static function bool2string($boolean, bool $yesno = false) : string
729
    {
730
        // allow 'yes', 'true', 'no', 'false' string notations as well
731
        if(!is_bool($boolean)) {
732
            $boolean = self::string2bool($boolean);
733
        }
734
        
735
        if ($boolean) {
736
            if ($yesno) {
737
                return 'yes';
738
            }
739
740
            return 'true';
741
        }
742
743
        if ($yesno) {
744
            return 'no';
745
        }
746
747
        return 'false';
748
    }
749
    
750
   /**
751
    * Converts an associative array with attribute name > value pairs
752
    * to an attribute string that can be used in an HTML tag. Empty 
753
    * attribute values are ignored.
754
    * 
755
    * Example:
756
    * 
757
    * array2attributeString(array(
758
    *     'id' => 45,
759
    *     'href' => 'http://www.mistralys.com'
760
    * ));
761
    * 
762
    * Result:
763
    * 
764
    * id="45" href="http://www.mistralys.com"
765
    * 
766
    * @param array $array
767
    * @return string
768
    */
769
    public static function array2attributeString($array)
770
    {
771
        $tokens = array();
772
        foreach($array as $attr => $value) {
773
            if($value == '' || $value == null) {
774
                continue;
775
            }
776
            
777
            $tokens[] = $attr.'="'.$value.'"';
778
        }
779
        
780
        if(empty($tokens)) {
781
            return '';
782
        }
783
        
784
        return ' '.implode(' ', $tokens);
785
    }
786
    
787
   /**
788
    * Converts a string so it can safely be used in a javascript
789
    * statement in an HTML tag: uses single quotes around the string
790
    * and encodes all special characters as needed.
791
    * 
792
    * @param string $string
793
    * @return string
794
    */
795
    public static function string2attributeJS($string, $quoted=true)
796
    {
797
        $converted = addslashes(htmlspecialchars(strip_tags($string), ENT_QUOTES, 'UTF-8'));
798
        if($quoted) {
799
            $converted = "'".$converted."'";
800
        } 
801
        
802
        return $converted;
803
    }
804
    
805
   /**
806
    * Checks if the specified string is a boolean value, which
807
    * includes string representations of boolean values, like 
808
    * <code>yes</code> or <code>no</code>, and <code>true</code>
809
    * or <code>false</code>.
810
    * 
811
    * @param mixed $value
812
    * @return boolean
813
    */
814
    public static function isBoolean($value) : bool
815
    {
816
        if(is_bool($value)) {
817
            return true;
818
        }
819
        
820
        if(!is_scalar($value)) {
821
            return false;
822
        }
823
        
824
        return array_key_exists($value, self::$booleanStrings);
825
    }
826
    
827
   /**
828
    * Converts an associative array to an HTML style attribute value string.
829
    * 
830
    * @param array $subject
831
    * @return string
832
    */
833
    public static function array2styleString(array $subject) : string
834
    {
835
        $tokens = array();
836
        foreach($subject as $name => $value) {
837
            $tokens[] = $name.':'.$value;
838
        }
839
        
840
        return implode(';', $tokens);
841
    }
842
    
843
   /**
844
    * Converts a DateTime object to a timestamp, which
845
    * is PHP 5.2 compatible.
846
    * 
847
    * @param \DateTime $date
848
    * @return integer
849
    */
850
    public static function date2timestamp(\DateTime $date) : int
851
    {
852
        return (int)$date->format('U');
853
    }
854
    
855
   /**
856
    * Converts a timestamp into a DateTime instance.
857
    * @param int $timestamp
858
    * @return \DateTime
859
    */
860
    public static function timestamp2date(int $timestamp) : \DateTime
861
    {
862
        $date = new \DateTime();
863
        $date->setTimestamp($timestamp);
864
        return $date;
865
    }
866
    
867
   /**
868
    * Strips an absolute path to a file within the application
869
    * to make the path relative to the application root path.
870
    * 
871
    * @param string $path
872
    * @return string
873
    * 
874
    * @see FileHelper::relativizePath()
875
    * @see FileHelper::relativizePathByDepth()
876
    */
877
    public static function fileRelativize(string $path) : string
878
    {
879
        return FileHelper::relativizePathByDepth($path);
880
    }
881
    
882
    /**
883
    * Converts a PHP regex to a javascript RegExp object statement.
884
    * 
885
    * NOTE: This is an alias for the JSHelper's `convertRegex` method. 
886
    * More details are available on its usage there.
887
    *
888
    * @param string $regex A PHP preg regex
889
    * @param string $statementType The type of statement to return: Defaults to a statement to create a RegExp object.
890
    * @return array|string Depending on the specified return type.
891
    * 
892
    * @see JSHelper::buildRegexStatement()
893
    */
894
    public static function regex2js(string $regex, string $statementType=JSHelper::JS_REGEX_OBJECT)
895
    {
896
        return JSHelper::buildRegexStatement($regex, $statementType);
897
    }
898
    
899
   /**
900
    * Converts the specified variable to JSON. Works just
901
    * like the native `json_encode` method, except that it
902
    * will trigger an exception on failure, which has the 
903
    * json error details included in its developer details.
904
    * 
905
    * @param mixed $variable
906
    * @param int $options JSON encode options.
907
    * @param int $depth 
908
    * @throws ConvertHelper_Exception
909
    * @return string
910
    */
911
    public static function var2json($variable, int $options=0, int $depth=512) : string
912
    {
913
        $result = json_encode($variable, $options, $depth);
914
        
915
        if($result !== false) {
916
            return $result;
917
        }
918
        
919
        throw new ConvertHelper_Exception(
920
            'Could not create json array'.json_last_error_msg(),
921
            sprintf(
922
                'The call to json_encode failed for the variable [%s]. JSON error details: #%s, %s',
923
                parseVariable($variable)->toString(),
924
                json_last_error(),
925
                json_last_error_msg()
926
            ),
927
            self::ERROR_JSON_ENCODE_FAILED
928
        );
929
    }
930
    
931
   /**
932
    * Strips all known UTF byte order marks from the specified string.
933
    * 
934
    * @param string $string
935
    * @return string
936
    */
937
    public static function stripUTFBom($string)
938
    {
939
        $boms = FileHelper::getUTFBOMs();
940
        foreach($boms as $bomChars) {
941
            $length = mb_strlen($bomChars);
942
            $text = mb_substr($string, 0, $length);
943
            if($text==$bomChars) {
944
                return mb_substr($string, $length);
945
            }
946
        }
947
        
948
        return $string;
949
    }
950
951
   /**
952
    * Converts a string to valid utf8, regardless
953
    * of the string's encoding(s).
954
    * 
955
    * @param string $string
956
    * @return string
957
    */
958
    public static function string2utf8($string)
959
    {
960
        if(!self::isStringASCII($string)) {
961
            return \ForceUTF8\Encoding::toUTF8($string);
962
        }
963
        
964
        return $string;
965
    }
966
    
967
   /**
968
    * Checks whether the specified string is an ASCII
969
    * string, without any special or UTF8 characters.
970
    * Note: empty strings and NULL are considered ASCII.
971
    * Any variable types other than strings are not.
972
    * 
973
    * @param mixed $string
974
    * @return boolean
975
    */
976
    public static function isStringASCII($string) : bool
977
    {
978
        if($string === '' || $string === NULL) {
979
            return true;
980
        }
981
        
982
        if(!is_string($string)) {
983
            return false;
984
        }
985
        
986
        return !preg_match('/[^\x00-\x7F]/', $string);
987
    }
988
    
989
    public static function highlight_url($url)
990
    {
991
        $url = htmlspecialchars($url);
992
        $url = str_replace(
993
            array('/', '='), 
994
            array('/<wbr>', '=<wbr>'), 
995
            $url
996
        );
997
        return $url;
998
    }
999
1000
   /**
1001
    * Calculates a percentage match of the source string with the target string.
1002
    * 
1003
    * Options are:
1004
    * 
1005
    * - maxLevenshtein, default: 10
1006
    *   Any levenshtein results above this value are ignored.
1007
    *   
1008
    * - precision, default: 1
1009
    *   The precision of the percentage float value
1010
    * 
1011
    * @param string $source
1012
    * @param string $target
1013
    * @param array $options
1014
    * @return float
1015
    */
1016
    public static function matchString($source, $target, $options=array())
1017
    {
1018
        $defaults = array(
1019
            'maxLevenshtein' => 10,
1020
            'precision' => 1
1021
        );
1022
        
1023
        $options = array_merge($defaults, $options);
1024
        
1025
        // avoid doing this via levenshtein
1026
        if($source == $target) {
1027
            return 100;
1028
        }
1029
        
1030
        $diff = levenshtein($source, $target);
1031
        if($diff > $options['maxLevenshtein']) {
1032
            return 0;
1033
        }
1034
        
1035
        $percent = $diff * 100 / ($options['maxLevenshtein'] + 1);
1036
        return round(100 - $percent, $options['precision']);
1037
    }
1038
    
1039
   /**
1040
    * Converts a date interval to a human readable string with
1041
    * all necessary time components, e.g. "1 year, 2 months and 4 days".
1042
    * 
1043
    * @param \DateInterval $interval
1044
    * @return string
1045
    * @see ConvertHelper_IntervalConverter
1046
    */
1047
    public static function interval2string(\DateInterval $interval) : string
1048
    {
1049
        $converter = new ConvertHelper_IntervalConverter();
1050
        return $converter->toString($interval);
1051
    }
1052
    
1053
    const INTERVAL_DAYS = 'days';
1054
    
1055
    const INTERVAL_HOURS = 'hours';
1056
    
1057
    const INTERVAL_MINUTES = 'minutes';
1058
    
1059
    const INTERVAL_SECONDS = 'seconds';
1060
    
1061
   /**
1062
    * Converts an interval to its total amount of days.
1063
    * @param \DateInterval $interval
1064
    * @return int
1065
    */
1066
    public static function interval2days(\DateInterval $interval) : int
1067
    {
1068
        return self::interval2total($interval, self::INTERVAL_DAYS);
1069
    }
1070
1071
   /**
1072
    * Converts an interval to its total amount of hours.
1073
    * @param \DateInterval $interval
1074
    * @return int
1075
    */
1076
    public static function interval2hours(\DateInterval $interval) : int
1077
    {
1078
        return self::interval2total($interval, self::INTERVAL_HOURS);
1079
    }
1080
    
1081
   /**
1082
    * Converts an interval to its total amount of minutes. 
1083
    * @param \DateInterval $interval
1084
    * @return int
1085
    */
1086
    public static function interval2minutes(\DateInterval $interval) : int
1087
    {
1088
        return self::interval2total($interval, self::INTERVAL_MINUTES);
1089
    }
1090
    
1091
   /**
1092
    * Converts an interval to its total amount of seconds.
1093
    * @param \DateInterval $interval
1094
    * @return int
1095
    */    
1096
    public static function interval2seconds(\DateInterval $interval) : int
1097
    {
1098
        return self::interval2total($interval, self::INTERVAL_SECONDS);
1099
    }
1100
    
1101
   /**
1102
    * Calculates the total amount of days / hours / minutes or seconds
1103
    * of a date interval object (depending in the specified units), and 
1104
    * returns the total amount.
1105
    * 
1106
    * @param \DateInterval $interval
1107
    * @param string $unit What total value to calculate.
1108
    * @return integer
1109
    * 
1110
    * @see ConvertHelper::INTERVAL_SECONDS
1111
    * @see ConvertHelper::INTERVAL_MINUTES
1112
    * @see ConvertHelper::INTERVAL_HOURS
1113
    * @see ConvertHelper::INTERVAL_DAYS
1114
    */
1115
    public static function interval2total(\DateInterval $interval, $unit=self::INTERVAL_SECONDS) : int
1116
    {
1117
        $total = (int)$interval->format('%a');
1118
        if ($unit == self::INTERVAL_DAYS) {
1119
            return $total;
1120
        }
1121
        
1122
        $total = ($total * 24) + ((int)$interval->h );
1123
        if ($unit == self::INTERVAL_HOURS) {
1124
            return $total;
1125
        }
1126
    
1127
        $total = ($total * 60) + ((int)$interval->i );
1128
        if ($unit == self::INTERVAL_MINUTES) {
1129
            return $total;
1130
        }
1131
1132
        $total = ($total * 60) + ((int)$interval->s );
1133
        if ($unit == self::INTERVAL_SECONDS) {
1134
            return $total;
1135
        }
1136
        
1137
        return 0;
1138
    }
1139
1140
    protected static $days;
1141
    
1142
    protected static $daysShort;
1143
1144
    protected static $daysInvariant = array(
1145
        'Monday',
1146
        'Tuesday',
1147
        'Wednesday',
1148
        'Thursday',
1149
        'Friday',
1150
        'Saturday',
1151
        'Sunday'
1152
    );
1153
    
1154
   /**
1155
    * Converts a date to the corresponding day name.
1156
    * 
1157
    * @param \DateTime $date
1158
    * @param bool $short
1159
    * @return string|NULL
1160
    */
1161
    public static function date2dayName(\DateTime $date, bool $short=false)
1162
    {
1163
        $day = $date->format('l');
1164
        $invariant = self::getDayNamesInvariant();
1165
        
1166
        $idx = array_search($day, $invariant);
1167
        if($idx !== false) {
1168
            $localized = self::getDayNames($short);
1169
            return $localized[$idx];
1170
        }
1171
        
1172
        return null;
1173
    }
1174
    
1175
   /**
1176
    * Retrieves a list of english day names.
1177
    * @return string[]
1178
    */
1179
    public static function getDayNamesInvariant()
1180
    {
1181
        return self::$daysInvariant;
1182
    }
1183
    
1184
   /**
1185
    * Retrieves the day names list for the current locale.
1186
    * 
1187
    * @param bool $short
1188
    * @return array
1189
    */
1190
    public static function getDayNames(bool $short=false) : array
1191
    {
1192
        if($short) {
1193
            if(!isset(self::$daysShort)) {
1194
                self::$daysShort = array(
1195
                    t('Mon'),
1196
                    t('Tue'),
1197
                    t('Wed'),
1198
                    t('Thu'),
1199
                    t('Fri'),
1200
                    t('Sat'),
1201
                    t('Sun')
1202
                );
1203
            }
1204
            
1205
            return self::$daysShort;
1206
        }
1207
        
1208
        if(!isset(self::$days)) {
1209
            self::$days = array(
1210
                t('Monday'),
1211
                t('Tuesday'),
1212
                t('Wednesday'),
1213
                t('Thursday'),
1214
                t('Friday'),
1215
                t('Saturday'),
1216
                t('Sunday')
1217
            );
1218
        }
1219
        
1220
        return self::$days;
1221
    }
1222
1223
    /**
1224
     * Implodes an array with a separator character, and the last item with "add".
1225
     * 
1226
     * @param array $list The indexed array with items to implode.
1227
     * @param string $sep The separator character to use.
1228
     * @param string $conjunction The word to use as conjunction with the last item in the list. NOTE: include spaces as needed.
1229
     * @return string
1230
     */
1231
    public static function implodeWithAnd(array $list, $sep = ', ', $conjunction = null)
1232
    {
1233
        if(empty($list)) {
1234
            return '';
1235
        }
1236
        
1237
        if(empty($conjunction)) {
1238
            $conjunction = t('and');
1239
        }
1240
        
1241
        $last = array_pop($list);
1242
        if($list) {
1243
            return implode($sep, $list) . $conjunction . ' ' . $last;
1244
        }
1245
        
1246
        return $last;
1247
    }
1248
    
1249
   /**
1250
    * Splits a string into an array of all characters it is composed of.
1251
    * Unicode character safe.
1252
    * 
1253
    * NOTE: Spaces and newlines (both \r and \n) are also considered single
1254
    * characters.
1255
    * 
1256
    * @param string $string
1257
    * @return array
1258
    */
1259
    public static function string2array(string $string) : array
1260
    {
1261
        $result = preg_split('//u', $string, null, PREG_SPLIT_NO_EMPTY);
1262
        if($result !== false) {
1263
            return $result;
1264
        }
1265
        
1266
        return array();
1267
    }
1268
    
1269
   /**
1270
    * Checks whether the specified string contains HTML code.
1271
    * 
1272
    * @param string $string
1273
    * @return boolean
1274
    */
1275
    public static function isStringHTML(string $string) : bool
1276
    {
1277
        if(preg_match('%<[a-z/][\s\S]*>%siU', $string)) {
1278
            return true;
1279
        }
1280
        
1281
        $decoded = html_entity_decode($string);
1282
        if($decoded !== $string) {
1283
            return true;
1284
        }
1285
        
1286
        return false;
1287
    }
1288
    
1289
   /**
1290
    * UTF8-safe wordwrap method: works like the regular wordwrap
1291
    * PHP function but compatible with UTF8. Otherwise the lengths
1292
    * are not calculated correctly.
1293
    * 
1294
    * @param string $str
1295
    * @param int $width
1296
    * @param string $break
1297
    * @param bool $cut
1298
    * @return string
1299
    */
1300
    public static function wordwrap(string $str, int $width = 75, string $break = "\n", bool $cut = false) : string 
1301
    {
1302
        $wrapper = new ConvertHelper_WordWrapper();
1303
        
1304
        return $wrapper
1305
        ->setLineWidth($width)
1306
        ->setBreakCharacter($break)
1307
        ->setCuttingEnabled($cut)
1308
        ->wrapText($str);
1309
    }
1310
    
1311
   /**
1312
    * Calculates the byte length of a string, taking into 
1313
    * account any unicode characters.
1314
    * 
1315
    * @param string $string
1316
    * @return int
1317
    * @see https://stackoverflow.com/a/9718273/2298192
1318
    */
1319
    public static function string2bytes($string)
1320
    {
1321
        return mb_strlen($string, '8bit');
1322
    }
1323
    
1324
   /**
1325
    * Creates a short, 8-character long hash for the specified string.
1326
    * 
1327
    * WARNING: Not cryptographically safe.
1328
    * 
1329
    * @param string $string
1330
    * @return string
1331
    */
1332
    public static function string2shortHash($string)
1333
    {
1334
        return hash('crc32', $string, false);
1335
    }
1336
    
1337
    public static function string2hash($string)
1338
    {
1339
        return md5($string);
1340
    }
1341
    
1342
    public static function callback2string($callback) : string
1343
    {
1344
        return parseVariable($callback)->toString();
1345
    }
1346
1347
    public static function exception2info(\Throwable $e) : ConvertHelper_ThrowableInfo
1348
    {
1349
        return self::throwable2info($e);
1350
    }
1351
    
1352
    public static function throwable2info(\Throwable $e) : ConvertHelper_ThrowableInfo
1353
    {
1354
        return ConvertHelper_ThrowableInfo::fromThrowable($e);
1355
    }
1356
    
1357
   /**
1358
    * Parses the specified query string like the native 
1359
    * function <code>parse_str</code>, without the key
1360
    * naming limitations.
1361
    * 
1362
    * Using parse_str, dots or spaces in key names are 
1363
    * replaced by underscores. This method keeps all names
1364
    * intact.
1365
    * 
1366
    * It still uses the parse_str implementation as it 
1367
    * is tested and tried, but fixes the parameter names
1368
    * after parsing, as needed.
1369
    * 
1370
    * @param string $queryString
1371
    * @return array
1372
    * @see ConvertHelper_QueryParser
1373
    */
1374
    public static function parseQueryString(string $queryString) : array
1375
    {
1376
        $parser = new ConvertHelper_QueryParser();
1377
        return $parser->parse($queryString);
1378
    }
1379
1380
   /**
1381
    * Searches for needle in the specified string, and returns a list
1382
    * of all occurrences, including the matched string. The matched 
1383
    * string is useful when doing a case insensitive search, as it 
1384
    * shows the exact matched case of needle.
1385
    *   
1386
    * @param string $needle
1387
    * @param string $haystack
1388
    * @param bool $caseInsensitive
1389
    * @return ConvertHelper_StringMatch[]
1390
    */
1391
    public static function findString(string $needle, string $haystack, bool $caseInsensitive=false)
1392
    {
1393
        if($needle === '') {
1394
            return array();
1395
        }
1396
        
1397
        $function = 'mb_strpos';
1398
        if($caseInsensitive) {
1399
            $function = 'mb_stripos';
1400
        }
1401
        
1402
        $pos = 0;
1403
        $positions = array();
1404
        $length = mb_strlen($needle);
1405
        
1406
        while( ($pos = $function($haystack, $needle, $pos)) !== false) 
1407
        {
1408
            $match = mb_substr($haystack, $pos, $length);
1409
            $positions[] = new ConvertHelper_StringMatch($pos, $match);
1410
            $pos += $length;
1411
        }
1412
        
1413
        return $positions;
1414
    }
1415
    
1416
   /**
1417
    * Like explode, but trims all entries, and removes 
1418
    * empty entries from the resulting array.
1419
    * 
1420
    * @param string $delimiter
1421
    * @param string $string
1422
    * @return string[]
1423
    */
1424
    public static function explodeTrim(string $delimiter, string $string) : array
1425
    {
1426
        if(empty($string) || empty($delimiter)) {
1427
            return array();
1428
        }
1429
        
1430
        $tokens = explode($delimiter, $string);
1431
        $tokens = array_map('trim', $tokens);
1432
        
1433
        $keep = array();
1434
        foreach($tokens as $token) {
1435
            if($token !== '') {
1436
                $keep[] = $token;
1437
            }
1438
        }
1439
        
1440
        return $keep;
1441
    }
1442
    
1443
    protected static $eolChars;
1444
1445
   /**
1446
    * Detects the most used end-of-line character in the subject string.
1447
    * 
1448
    * @param string $subjectString The string to check.
1449
    * @return NULL|ConvertHelper_EOL The detected EOL instance, or NULL if none has been detected.
1450
    */
1451
    public static function detectEOLCharacter(string $subjectString) : ?ConvertHelper_EOL
1452
    {
1453
        if(empty($subjectString)) {
1454
            return null;
1455
        }
1456
        
1457
        if(!isset(self::$eolChars))
1458
        {
1459
            $cr = chr((int)hexdec('0d'));
1460
            $lf = chr((int)hexdec('0a'));
1461
            
1462
           self::$eolChars = array(
1463
               array(
1464
                   'char' => $cr.$lf,
1465
                   'type' => ConvertHelper_EOL::TYPE_CRLF,
1466
                   'description' => t('Carriage return followed by a line feed'),
1467
               ),
1468
               array(
1469
                   'char' => $lf.$cr,
1470
                   'type' => ConvertHelper_EOL::TYPE_LFCR,
1471
                   'description' => t('Line feed followed by a carriage return'),
1472
               ),
1473
               array(
1474
                  'char' => $lf,
1475
                  'type' => ConvertHelper_EOL::TYPE_LF,
1476
                  'description' => t('Line feed'),
1477
               ),
1478
               array(
1479
                  'char' => $cr,
1480
                  'type' => ConvertHelper_EOL::TYPE_CR,
1481
                  'description' => t('Carriage Return'),
1482
               ),
1483
            );
1484
        }
1485
        
1486
        $max = 0;
1487
        $results = array();
1488
        foreach(self::$eolChars as $def) 
1489
        {
1490
            $amount = substr_count($subjectString, $def['char']);
1491
            
1492
            if($amount > $max)
1493
            {
1494
                $max = $amount;
1495
                $results[] = $def;
1496
            }
1497
        }
1498
        
1499
        if(empty($results)) {
1500
            return null;
1501
        }
1502
        
1503
        return new ConvertHelper_EOL(
1504
            $results[0]['char'], 
1505
            $results[0]['type'],
1506
            $results[0]['description']
1507
        );
1508
    }
1509
1510
   /**
1511
    * Removes the specified keys from the target array,
1512
    * if they exist.
1513
    * 
1514
    * @param array $array
1515
    * @param array $keys
1516
    */
1517
    public static function arrayRemoveKeys(array &$array, array $keys) : void
1518
    {
1519
        foreach($keys as $key) 
1520
        {
1521
            if(array_key_exists($key, $array)) {
1522
                unset($array[$key]); 
1523
            }
1524
        }
1525
    }
1526
    
1527
   /**
1528
    * Checks if the specified variable is an integer or a string containing an integer.
1529
    * Accepts both positive and negative integers.
1530
    * 
1531
    * @param mixed $value
1532
    * @return bool
1533
    */
1534
    public static function isInteger($value) : bool
1535
    {
1536
        if(is_int($value)) {
1537
            return true;
1538
        }
1539
        
1540
        // booleans get converted to numbers, so they would
1541
        // actually match the regex.
1542
        if(is_bool($value)) {
1543
            return false;
1544
        }
1545
        
1546
        if(is_string($value) && $value !== '') {
1547
            return preg_match('/\A-?\d+\z/', $value) === 1;
1548
        }
1549
        
1550
        return false;    
1551
    }
1552
    
1553
   /**
1554
    * Converts an amount of seconds to a DateInterval object.
1555
    * 
1556
    * @param int $seconds
1557
    * @return \DateInterval
1558
    * @throws ConvertHelper_Exception If the date interval cannot be created.
1559
    * 
1560
    * @see ConvertHelper::ERROR_CANNOT_GET_DATE_DIFF
1561
    */
1562
    public static function seconds2interval(int $seconds) : \DateInterval
1563
    {
1564
        return ConvertHelper_DateInterval::fromSeconds($seconds)->getInterval();
1565
    }
1566
    
1567
   /**
1568
    * Converts a size string like "50 MB" to the corresponding byte size.
1569
    * It is case insensitive, ignores spaces, and supports both traditional
1570
    * "MB" and "MiB" notations.
1571
    * 
1572
    * @param string $size
1573
    * @return int
1574
    */
1575
    public static function size2bytes(string $size) : int
1576
    {
1577
        return self::parseSize($size)->toBytes();
1578
    }
1579
    
1580
   /**
1581
    * Parses a size string like "50 MB" and returns a size notation instance
1582
    * that has utility methods to access information on it, and convert it.
1583
    * 
1584
    * @param string $size
1585
    * @return ConvertHelper_SizeNotation
1586
    */
1587
    public static function parseSize(string $size) : ConvertHelper_SizeNotation
1588
    {
1589
        return new ConvertHelper_SizeNotation($size);
1590
    }
1591
}
1592