Passed
Push — master ( 2b9668...6875d9 )
by Sebastian
02:20
created

ConvertHelper::bytes2readable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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