Passed
Push — master ( b4f537...606be1 )
by Sebastian
04:55
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
    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
        return self::createControlCharacters()->getCharsAsHex();
498
    }
499
    
500
   /**
501
    * Retrieves an array of all control characters that
502
    * the {@link stripControlCharacters()} method will 
503
    * remove, as the actual UTF-8 characters.
504
    * 
505
    * @return string[]
506
    */
507
    public static function getControlCharactersAsUTF8()
508
    {
509
        return self::createControlCharacters()->getCharsAsUTF8();
510
    }
511
    
512
   /**
513
    * Retrieves all control characters as JSON encoded
514
    * characters, e.g. "\u200b".
515
    * 
516
    * @return string[]
517
    */
518
    public static function getControlCharactersAsJSON()
519
    {
520
        return self::createControlCharacters()->getCharsAsJSON();
521
    }
522
    
523
   /**
524
    * Removes all control characters from the specified string
525
    * that can cause problems in some cases, like creating
526
    * valid XML documents. This includes invisible non-breaking
527
    * spaces.
528
    *
529
    * @param string $string
530
    * @return string
531
    */
532
    public static function stripControlCharacters(string $string) : string
533
    {
534
        return self::createControlCharacters()->stripControlCharacters($string);
535
    }
536
    
537
   /**
538
    * Creates the control characters class, used to 
539
    * work with control characters in strings.
540
    * 
541
    * @return ConvertHelper_ControlCharacters
542
    */
543
    public static function createControlCharacters() : ConvertHelper_ControlCharacters
544
    {
545
        return new ConvertHelper_ControlCharacters();
546
    }
547
548
   /**
549
    * Converts a unicode character to the PHPO notation.
550
    * 
551
    * Example:
552
    * 
553
    * <pre>unicodeChar2php('"\u0000"')</pre>
554
    * 
555
    * Returns
556
    * 
557
    * <pre>\x0</pre>
558
    * 
559
    * @param string $unicodeChar
560
    * @return string
561
    */
562
    public static function unicodeChar2php(string $unicodeChar) : string 
563
    {
564
        $unicodeChar = json_decode($unicodeChar);
565
        
566
        $output = '';
567
        $split = str_split($unicodeChar);
568
        
569
        foreach($split as $octet) 
570
        {
571
            $ordInt = ord($octet);
572
            // Convert from int (base 10) to hex (base 16), for PHP \x syntax
573
            $ordHex = base_convert((string)$ordInt, 10, 16);
574
            $output .= '\x' . $ordHex;
575
        }
576
        
577
        return $output;
578
    }
579
    
580
    /**
581
     * Removes the extension from the specified filename
582
     * and returns the name without the extension.
583
     *
584
     * Example:
585
     * filename.html > filename
586
     * passed.test.jpg > passed.test
587
     * path/to/file/document.txt > document
588
     *
589
     * @param string $filename
590
     * @return string
591
     */
592
    public static function filenameRemoveExtension($filename)
593
    {
594
        return FileHelper::removeExtension($filename);
595
    }
596
    
597
    public static function areVariablesEqual($a, $b) : bool
598
    {
599
        $a = self::convertScalarForComparison($a);
600
        $b = self::convertScalarForComparison($b);
601
602
        return $a === $b;
603
    }
604
    
605
    protected static function convertScalarForComparison($scalar)
606
    {
607
        if($scalar === '' || is_null($scalar)) {
608
            return null;
609
        }
610
        
611
        if(is_bool($scalar)) {
612
            return self::bool2string($scalar);
613
        }
614
        
615
        if(is_array($scalar)) {
616
            $scalar = md5(serialize($scalar));
617
        }
618
        
619
        if($scalar !== null && !is_scalar($scalar)) {
620
            throw new ConvertHelper_Exception(
621
                'Not a scalar value in comparison',
622
                null,
623
                self::ERROR_CANNOT_NORMALIZE_NON_SCALAR_VALUE
624
            );
625
        }
626
        
627
        return strval($scalar);
628
    }
629
630
    /**
631
     * Compares two strings to check whether they are equal.
632
     * null and empty strings are considered equal.
633
     *
634
     * @param string $a
635
     * @param string $b
636
     * @return boolean
637
     */
638
    public static function areStringsEqual($a, $b) : bool
639
    {
640
        return self::areVariablesEqual($a, $b);
641
    }
642
643
    /**
644
     * Checks whether the two specified numbers are equal.
645
     * null and empty strings are considered as 0 values.
646
     *
647
     * @param number|string $a
648
     * @param number|string $b
649
     * @return boolean
650
     */
651
    public static function areNumbersEqual($a, $b) : bool
652
    {
653
        return self::areVariablesEqual($a, $b);
654
    }
655
656
    /**
657
     * Converts a boolean value to a string. Defaults to returning
658
     * 'true' or 'false', with the additional parameter it can also
659
     * return the 'yes' and 'no' variants.
660
     *
661
     * @param boolean|string $boolean
662
     * @param boolean $yesno
663
     * @return string
664
     */
665
    public static function bool2string($boolean, bool $yesno = false) : string
666
    {
667
        // allow 'yes', 'true', 'no', 'false' string notations as well
668
        if(!is_bool($boolean)) {
669
            $boolean = self::string2bool($boolean);
670
        }
671
        
672
        if ($boolean) {
673
            if ($yesno) {
674
                return 'yes';
675
            }
676
677
            return 'true';
678
        }
679
680
        if ($yesno) {
681
            return 'no';
682
        }
683
684
        return 'false';
685
    }
686
    
687
   /**
688
    * Converts an associative array with attribute name > value pairs
689
    * to an attribute string that can be used in an HTML tag. Empty 
690
    * attribute values are ignored.
691
    * 
692
    * Example:
693
    * 
694
    * array2attributeString(array(
695
    *     'id' => 45,
696
    *     'href' => 'http://www.mistralys.com'
697
    * ));
698
    * 
699
    * Result:
700
    * 
701
    * id="45" href="http://www.mistralys.com"
702
    * 
703
    * @param array $array
704
    * @return string
705
    */
706
    public static function array2attributeString($array)
707
    {
708
        $tokens = array();
709
        foreach($array as $attr => $value) {
710
            if($value == '' || $value == null) {
711
                continue;
712
            }
713
            
714
            $tokens[] = $attr.'="'.$value.'"';
715
        }
716
        
717
        if(empty($tokens)) {
718
            return '';
719
        }
720
        
721
        return ' '.implode(' ', $tokens);
722
    }
723
    
724
   /**
725
    * Converts a string so it can safely be used in a javascript
726
    * statement in an HTML tag: uses single quotes around the string
727
    * and encodes all special characters as needed.
728
    * 
729
    * @param string $string
730
    * @return string
731
    */
732
    public static function string2attributeJS($string, $quoted=true)
733
    {
734
        $converted = addslashes(htmlspecialchars(strip_tags($string), ENT_QUOTES, 'UTF-8'));
735
        if($quoted) {
736
            $converted = "'".$converted."'";
737
        } 
738
        
739
        return $converted;
740
    }
741
    
742
   /**
743
    * Checks if the specified string is a boolean value, which
744
    * includes string representations of boolean values, like 
745
    * <code>yes</code> or <code>no</code>, and <code>true</code>
746
    * or <code>false</code>.
747
    * 
748
    * @param mixed $value
749
    * @return boolean
750
    */
751
    public static function isBoolean($value) : bool
752
    {
753
        if(is_bool($value)) {
754
            return true;
755
        }
756
        
757
        if(!is_scalar($value)) {
758
            return false;
759
        }
760
        
761
        return array_key_exists($value, self::$booleanStrings);
762
    }
763
    
764
   /**
765
    * Converts an associative array to an HTML style attribute value string.
766
    * 
767
    * @param array $subject
768
    * @return string
769
    */
770
    public static function array2styleString(array $subject) : string
771
    {
772
        $tokens = array();
773
        foreach($subject as $name => $value) {
774
            $tokens[] = $name.':'.$value;
775
        }
776
        
777
        return implode(';', $tokens);
778
    }
779
    
780
   /**
781
    * Converts a DateTime object to a timestamp, which
782
    * is PHP 5.2 compatible.
783
    * 
784
    * @param \DateTime $date
785
    * @return integer
786
    */
787
    public static function date2timestamp(\DateTime $date) : int
788
    {
789
        return (int)$date->format('U');
790
    }
791
    
792
   /**
793
    * Converts a timestamp into a DateTime instance.
794
    * @param int $timestamp
795
    * @return \DateTime
796
    */
797
    public static function timestamp2date(int $timestamp) : \DateTime
798
    {
799
        $date = new \DateTime();
800
        $date->setTimestamp($timestamp);
801
        return $date;
802
    }
803
    
804
   /**
805
    * Strips an absolute path to a file within the application
806
    * to make the path relative to the application root path.
807
    * 
808
    * @param string $path
809
    * @return string
810
    * 
811
    * @see FileHelper::relativizePath()
812
    * @see FileHelper::relativizePathByDepth()
813
    */
814
    public static function fileRelativize(string $path) : string
815
    {
816
        return FileHelper::relativizePathByDepth($path);
817
    }
818
    
819
    /**
820
    * Converts a PHP regex to a javascript RegExp object statement.
821
    * 
822
    * NOTE: This is an alias for the JSHelper's `convertRegex` method. 
823
    * More details are available on its usage there.
824
    *
825
    * @param string $regex A PHP preg regex
826
    * @param string $statementType The type of statement to return: Defaults to a statement to create a RegExp object.
827
    * @return array|string Depending on the specified return type.
828
    * 
829
    * @see JSHelper::buildRegexStatement()
830
    */
831
    public static function regex2js(string $regex, string $statementType=JSHelper::JS_REGEX_OBJECT)
832
    {
833
        return JSHelper::buildRegexStatement($regex, $statementType);
834
    }
835
    
836
   /**
837
    * Converts the specified variable to JSON. Works just
838
    * like the native `json_encode` method, except that it
839
    * will trigger an exception on failure, which has the 
840
    * json error details included in its developer details.
841
    * 
842
    * @param mixed $variable
843
    * @param int $options JSON encode options.
844
    * @param int $depth 
845
    * @throws ConvertHelper_Exception
846
    * @return string
847
    */
848
    public static function var2json($variable, int $options=0, int $depth=512) : string
849
    {
850
        $result = json_encode($variable, $options, $depth);
851
        
852
        if($result !== false) {
853
            return $result;
854
        }
855
        
856
        throw new ConvertHelper_Exception(
857
            'Could not create json array'.json_last_error_msg(),
858
            sprintf(
859
                'The call to json_encode failed for the variable [%s]. JSON error details: #%s, %s',
860
                parseVariable($variable)->toString(),
861
                json_last_error(),
862
                json_last_error_msg()
863
            ),
864
            self::ERROR_JSON_ENCODE_FAILED
865
        );
866
    }
867
    
868
   /**
869
    * Strips all known UTF byte order marks from the specified string.
870
    * 
871
    * @param string $string
872
    * @return string
873
    */
874
    public static function stripUTFBom($string)
875
    {
876
        $boms = FileHelper::getUTFBOMs();
877
        foreach($boms as $bomChars) {
878
            $length = mb_strlen($bomChars);
879
            $text = mb_substr($string, 0, $length);
880
            if($text==$bomChars) {
881
                return mb_substr($string, $length);
882
            }
883
        }
884
        
885
        return $string;
886
    }
887
888
   /**
889
    * Converts a string to valid utf8, regardless
890
    * of the string's encoding(s).
891
    * 
892
    * @param string $string
893
    * @return string
894
    */
895
    public static function string2utf8($string)
896
    {
897
        if(!self::isStringASCII($string)) {
898
            return \ForceUTF8\Encoding::toUTF8($string);
899
        }
900
        
901
        return $string;
902
    }
903
    
904
   /**
905
    * Checks whether the specified string is an ASCII
906
    * string, without any special or UTF8 characters.
907
    * Note: empty strings and NULL are considered ASCII.
908
    * Any variable types other than strings are not.
909
    * 
910
    * @param mixed $string
911
    * @return boolean
912
    */
913
    public static function isStringASCII($string) : bool
914
    {
915
        if($string === '' || $string === NULL) {
916
            return true;
917
        }
918
        
919
        if(!is_string($string)) {
920
            return false;
921
        }
922
        
923
        return !preg_match('/[^\x00-\x7F]/', $string);
924
    }
925
    
926
    public static function highlight_url($url)
927
    {
928
        $url = htmlspecialchars($url);
929
        $url = str_replace(
930
            array('/', '='), 
931
            array('/<wbr>', '=<wbr>'), 
932
            $url
933
        );
934
        return $url;
935
    }
936
937
   /**
938
    * Calculates a percentage match of the source string with the target string.
939
    * 
940
    * Options are:
941
    * 
942
    * - maxLevenshtein, default: 10
943
    *   Any levenshtein results above this value are ignored.
944
    *   
945
    * - precision, default: 1
946
    *   The precision of the percentage float value
947
    * 
948
    * @param string $source
949
    * @param string $target
950
    * @param array $options
951
    * @return float
952
    */
953
    public static function matchString($source, $target, $options=array())
954
    {
955
        $defaults = array(
956
            'maxLevenshtein' => 10,
957
            'precision' => 1
958
        );
959
        
960
        $options = array_merge($defaults, $options);
961
        
962
        // avoid doing this via levenshtein
963
        if($source == $target) {
964
            return 100;
965
        }
966
        
967
        $diff = levenshtein($source, $target);
968
        if($diff > $options['maxLevenshtein']) {
969
            return 0;
970
        }
971
        
972
        $percent = $diff * 100 / ($options['maxLevenshtein'] + 1);
973
        return round(100 - $percent, $options['precision']);
974
    }
975
    
976
   /**
977
    * Converts a date interval to a human readable string with
978
    * all necessary time components, e.g. "1 year, 2 months and 4 days".
979
    * 
980
    * @param \DateInterval $interval
981
    * @return string
982
    * @see ConvertHelper_IntervalConverter
983
    */
984
    public static function interval2string(\DateInterval $interval) : string
985
    {
986
        $converter = new ConvertHelper_IntervalConverter();
987
        return $converter->toString($interval);
988
    }
989
    
990
    const INTERVAL_DAYS = 'days';
991
    
992
    const INTERVAL_HOURS = 'hours';
993
    
994
    const INTERVAL_MINUTES = 'minutes';
995
    
996
    const INTERVAL_SECONDS = 'seconds';
997
    
998
   /**
999
    * Converts an interval to its total amount of days.
1000
    * @param \DateInterval $interval
1001
    * @return int
1002
    */
1003
    public static function interval2days(\DateInterval $interval) : int
1004
    {
1005
        return self::interval2total($interval, self::INTERVAL_DAYS);
1006
    }
1007
1008
   /**
1009
    * Converts an interval to its total amount of hours.
1010
    * @param \DateInterval $interval
1011
    * @return int
1012
    */
1013
    public static function interval2hours(\DateInterval $interval) : int
1014
    {
1015
        return self::interval2total($interval, self::INTERVAL_HOURS);
1016
    }
1017
    
1018
   /**
1019
    * Converts an interval to its total amount of minutes. 
1020
    * @param \DateInterval $interval
1021
    * @return int
1022
    */
1023
    public static function interval2minutes(\DateInterval $interval) : int
1024
    {
1025
        return self::interval2total($interval, self::INTERVAL_MINUTES);
1026
    }
1027
    
1028
   /**
1029
    * Converts an interval to its total amount of seconds.
1030
    * @param \DateInterval $interval
1031
    * @return int
1032
    */    
1033
    public static function interval2seconds(\DateInterval $interval) : int
1034
    {
1035
        return self::interval2total($interval, self::INTERVAL_SECONDS);
1036
    }
1037
    
1038
   /**
1039
    * Calculates the total amount of days / hours / minutes or seconds
1040
    * of a date interval object (depending in the specified units), and 
1041
    * returns the total amount.
1042
    * 
1043
    * @param \DateInterval $interval
1044
    * @param string $unit What total value to calculate.
1045
    * @return integer
1046
    * 
1047
    * @see ConvertHelper::INTERVAL_SECONDS
1048
    * @see ConvertHelper::INTERVAL_MINUTES
1049
    * @see ConvertHelper::INTERVAL_HOURS
1050
    * @see ConvertHelper::INTERVAL_DAYS
1051
    */
1052
    public static function interval2total(\DateInterval $interval, $unit=self::INTERVAL_SECONDS) : int
1053
    {
1054
        $total = (int)$interval->format('%a');
1055
        if ($unit == self::INTERVAL_DAYS) {
1056
            return $total;
1057
        }
1058
        
1059
        $total = ($total * 24) + ((int)$interval->h );
1060
        if ($unit == self::INTERVAL_HOURS) {
1061
            return $total;
1062
        }
1063
    
1064
        $total = ($total * 60) + ((int)$interval->i );
1065
        if ($unit == self::INTERVAL_MINUTES) {
1066
            return $total;
1067
        }
1068
1069
        $total = ($total * 60) + ((int)$interval->s );
1070
        if ($unit == self::INTERVAL_SECONDS) {
1071
            return $total;
1072
        }
1073
        
1074
        return 0;
1075
    }
1076
1077
    protected static $days;
1078
    
1079
    protected static $daysShort;
1080
1081
    protected static $daysInvariant = array(
1082
        'Monday',
1083
        'Tuesday',
1084
        'Wednesday',
1085
        'Thursday',
1086
        'Friday',
1087
        'Saturday',
1088
        'Sunday'
1089
    );
1090
    
1091
   /**
1092
    * Converts a date to the corresponding day name.
1093
    * 
1094
    * @param \DateTime $date
1095
    * @param bool $short
1096
    * @return string|NULL
1097
    */
1098
    public static function date2dayName(\DateTime $date, bool $short=false)
1099
    {
1100
        $day = $date->format('l');
1101
        $invariant = self::getDayNamesInvariant();
1102
        
1103
        $idx = array_search($day, $invariant);
1104
        if($idx !== false) {
1105
            $localized = self::getDayNames($short);
1106
            return $localized[$idx];
1107
        }
1108
        
1109
        return null;
1110
    }
1111
    
1112
   /**
1113
    * Retrieves a list of english day names.
1114
    * @return string[]
1115
    */
1116
    public static function getDayNamesInvariant()
1117
    {
1118
        return self::$daysInvariant;
1119
    }
1120
    
1121
   /**
1122
    * Retrieves the day names list for the current locale.
1123
    * 
1124
    * @param bool $short
1125
    * @return array
1126
    */
1127
    public static function getDayNames(bool $short=false) : array
1128
    {
1129
        if($short) {
1130
            if(!isset(self::$daysShort)) {
1131
                self::$daysShort = array(
1132
                    t('Mon'),
1133
                    t('Tue'),
1134
                    t('Wed'),
1135
                    t('Thu'),
1136
                    t('Fri'),
1137
                    t('Sat'),
1138
                    t('Sun')
1139
                );
1140
            }
1141
            
1142
            return self::$daysShort;
1143
        }
1144
        
1145
        if(!isset(self::$days)) {
1146
            self::$days = array(
1147
                t('Monday'),
1148
                t('Tuesday'),
1149
                t('Wednesday'),
1150
                t('Thursday'),
1151
                t('Friday'),
1152
                t('Saturday'),
1153
                t('Sunday')
1154
            );
1155
        }
1156
        
1157
        return self::$days;
1158
    }
1159
1160
    /**
1161
     * Implodes an array with a separator character, and the last item with "add".
1162
     * 
1163
     * @param array $list The indexed array with items to implode.
1164
     * @param string $sep The separator character to use.
1165
     * @param string $conjunction The word to use as conjunction with the last item in the list. NOTE: include spaces as needed.
1166
     * @return string
1167
     */
1168
    public static function implodeWithAnd(array $list, $sep = ', ', $conjunction = null)
1169
    {
1170
        if(empty($list)) {
1171
            return '';
1172
        }
1173
        
1174
        if(empty($conjunction)) {
1175
            $conjunction = t('and');
1176
        }
1177
        
1178
        $last = array_pop($list);
1179
        if($list) {
1180
            return implode($sep, $list) . $conjunction . ' ' . $last;
1181
        }
1182
        
1183
        return $last;
1184
    }
1185
    
1186
   /**
1187
    * Splits a string into an array of all characters it is composed of.
1188
    * Unicode character safe.
1189
    * 
1190
    * NOTE: Spaces and newlines (both \r and \n) are also considered single
1191
    * characters.
1192
    * 
1193
    * @param string $string
1194
    * @return array
1195
    */
1196
    public static function string2array(string $string) : array
1197
    {
1198
        $result = preg_split('//u', $string, null, PREG_SPLIT_NO_EMPTY);
1199
        if($result !== false) {
1200
            return $result;
1201
        }
1202
        
1203
        return array();
1204
    }
1205
    
1206
   /**
1207
    * Checks whether the specified string contains HTML code.
1208
    * 
1209
    * @param string $string
1210
    * @return boolean
1211
    */
1212
    public static function isStringHTML(string $string) : bool
1213
    {
1214
        if(preg_match('%<[a-z/][\s\S]*>%siU', $string)) {
1215
            return true;
1216
        }
1217
        
1218
        $decoded = html_entity_decode($string);
1219
        if($decoded !== $string) {
1220
            return true;
1221
        }
1222
        
1223
        return false;
1224
    }
1225
    
1226
   /**
1227
    * UTF8-safe wordwrap method: works like the regular wordwrap
1228
    * PHP function but compatible with UTF8. Otherwise the lengths
1229
    * are not calculated correctly.
1230
    * 
1231
    * @param string $str
1232
    * @param int $width
1233
    * @param string $break
1234
    * @param bool $cut
1235
    * @return string
1236
    */
1237
    public static function wordwrap(string $str, int $width = 75, string $break = "\n", bool $cut = false) : string 
1238
    {
1239
        $wrapper = new ConvertHelper_WordWrapper();
1240
        
1241
        return $wrapper
1242
        ->setLineWidth($width)
1243
        ->setBreakCharacter($break)
1244
        ->setCuttingEnabled($cut)
1245
        ->wrapText($str);
1246
    }
1247
    
1248
   /**
1249
    * Calculates the byte length of a string, taking into 
1250
    * account any unicode characters.
1251
    * 
1252
    * @param string $string
1253
    * @return int
1254
    * @see https://stackoverflow.com/a/9718273/2298192
1255
    */
1256
    public static function string2bytes($string)
1257
    {
1258
        return mb_strlen($string, '8bit');
1259
    }
1260
    
1261
   /**
1262
    * Creates a short, 8-character long hash for the specified string.
1263
    * 
1264
    * WARNING: Not cryptographically safe.
1265
    * 
1266
    * @param string $string
1267
    * @return string
1268
    */
1269
    public static function string2shortHash($string)
1270
    {
1271
        return hash('crc32', $string, false);
1272
    }
1273
    
1274
    public static function string2hash($string)
1275
    {
1276
        return md5($string);
1277
    }
1278
    
1279
    public static function callback2string($callback) : string
1280
    {
1281
        return parseVariable($callback)->toString();
1282
    }
1283
1284
    public static function exception2info(\Throwable $e) : ConvertHelper_ThrowableInfo
1285
    {
1286
        return self::throwable2info($e);
1287
    }
1288
    
1289
    public static function throwable2info(\Throwable $e) : ConvertHelper_ThrowableInfo
1290
    {
1291
        return ConvertHelper_ThrowableInfo::fromThrowable($e);
1292
    }
1293
    
1294
   /**
1295
    * Parses the specified query string like the native 
1296
    * function <code>parse_str</code>, without the key
1297
    * naming limitations.
1298
    * 
1299
    * Using parse_str, dots or spaces in key names are 
1300
    * replaced by underscores. This method keeps all names
1301
    * intact.
1302
    * 
1303
    * It still uses the parse_str implementation as it 
1304
    * is tested and tried, but fixes the parameter names
1305
    * after parsing, as needed.
1306
    * 
1307
    * @param string $queryString
1308
    * @return array
1309
    * @see ConvertHelper_QueryParser
1310
    */
1311
    public static function parseQueryString(string $queryString) : array
1312
    {
1313
        $parser = new ConvertHelper_QueryParser();
1314
        return $parser->parse($queryString);
1315
    }
1316
1317
   /**
1318
    * Searches for needle in the specified string, and returns a list
1319
    * of all occurrences, including the matched string. The matched 
1320
    * string is useful when doing a case insensitive search, as it 
1321
    * shows the exact matched case of needle.
1322
    *   
1323
    * @param string $needle
1324
    * @param string $haystack
1325
    * @param bool $caseInsensitive
1326
    * @return ConvertHelper_StringMatch[]
1327
    */
1328
    public static function findString(string $needle, string $haystack, bool $caseInsensitive=false)
1329
    {
1330
        if($needle === '') {
1331
            return array();
1332
        }
1333
        
1334
        $function = 'mb_strpos';
1335
        if($caseInsensitive) {
1336
            $function = 'mb_stripos';
1337
        }
1338
        
1339
        $pos = 0;
1340
        $positions = array();
1341
        $length = mb_strlen($needle);
1342
        
1343
        while( ($pos = $function($haystack, $needle, $pos)) !== false) 
1344
        {
1345
            $match = mb_substr($haystack, $pos, $length);
1346
            $positions[] = new ConvertHelper_StringMatch($pos, $match);
1347
            $pos += $length;
1348
        }
1349
        
1350
        return $positions;
1351
    }
1352
    
1353
   /**
1354
    * Like explode, but trims all entries, and removes 
1355
    * empty entries from the resulting array.
1356
    * 
1357
    * @param string $delimiter
1358
    * @param string $string
1359
    * @return string[]
1360
    */
1361
    public static function explodeTrim(string $delimiter, string $string) : array
1362
    {
1363
        if(empty($string) || empty($delimiter)) {
1364
            return array();
1365
        }
1366
        
1367
        $tokens = explode($delimiter, $string);
1368
        $tokens = array_map('trim', $tokens);
1369
        
1370
        $keep = array();
1371
        foreach($tokens as $token) {
1372
            if($token !== '') {
1373
                $keep[] = $token;
1374
            }
1375
        }
1376
        
1377
        return $keep;
1378
    }
1379
    
1380
    protected static $eolChars;
1381
1382
   /**
1383
    * Detects the most used end-of-line character in the subject string.
1384
    * 
1385
    * @param string $subjectString The string to check.
1386
    * @return NULL|ConvertHelper_EOL The detected EOL instance, or NULL if none has been detected.
1387
    */
1388
    public static function detectEOLCharacter(string $subjectString) : ?ConvertHelper_EOL
1389
    {
1390
        if(empty($subjectString)) {
1391
            return null;
1392
        }
1393
        
1394
        if(!isset(self::$eolChars))
1395
        {
1396
            $cr = chr((int)hexdec('0d'));
1397
            $lf = chr((int)hexdec('0a'));
1398
            
1399
           self::$eolChars = array(
1400
               array(
1401
                   'char' => $cr.$lf,
1402
                   'type' => ConvertHelper_EOL::TYPE_CRLF,
1403
                   'description' => t('Carriage return followed by a line feed'),
1404
               ),
1405
               array(
1406
                   'char' => $lf.$cr,
1407
                   'type' => ConvertHelper_EOL::TYPE_LFCR,
1408
                   'description' => t('Line feed followed by a carriage return'),
1409
               ),
1410
               array(
1411
                  'char' => $lf,
1412
                  'type' => ConvertHelper_EOL::TYPE_LF,
1413
                  'description' => t('Line feed'),
1414
               ),
1415
               array(
1416
                  'char' => $cr,
1417
                  'type' => ConvertHelper_EOL::TYPE_CR,
1418
                  'description' => t('Carriage Return'),
1419
               ),
1420
            );
1421
        }
1422
        
1423
        $max = 0;
1424
        $results = array();
1425
        foreach(self::$eolChars as $def) 
1426
        {
1427
            $amount = substr_count($subjectString, $def['char']);
1428
            
1429
            if($amount > $max)
1430
            {
1431
                $max = $amount;
1432
                $results[] = $def;
1433
            }
1434
        }
1435
        
1436
        if(empty($results)) {
1437
            return null;
1438
        }
1439
        
1440
        return new ConvertHelper_EOL(
1441
            $results[0]['char'], 
1442
            $results[0]['type'],
1443
            $results[0]['description']
1444
        );
1445
    }
1446
1447
   /**
1448
    * Removes the specified keys from the target array,
1449
    * if they exist.
1450
    * 
1451
    * @param array $array
1452
    * @param array $keys
1453
    */
1454
    public static function arrayRemoveKeys(array &$array, array $keys) : void
1455
    {
1456
        foreach($keys as $key) 
1457
        {
1458
            if(array_key_exists($key, $array)) {
1459
                unset($array[$key]); 
1460
            }
1461
        }
1462
    }
1463
    
1464
   /**
1465
    * Checks if the specified variable is an integer or a string containing an integer.
1466
    * Accepts both positive and negative integers.
1467
    * 
1468
    * @param mixed $value
1469
    * @return bool
1470
    */
1471
    public static function isInteger($value) : bool
1472
    {
1473
        if(is_int($value)) {
1474
            return true;
1475
        }
1476
        
1477
        // booleans get converted to numbers, so they would
1478
        // actually match the regex.
1479
        if(is_bool($value)) {
1480
            return false;
1481
        }
1482
        
1483
        if(is_string($value) && $value !== '') {
1484
            return preg_match('/\A-?\d+\z/', $value) === 1;
1485
        }
1486
        
1487
        return false;    
1488
    }
1489
    
1490
   /**
1491
    * Converts an amount of seconds to a DateInterval object.
1492
    * 
1493
    * @param int $seconds
1494
    * @return \DateInterval
1495
    * @throws ConvertHelper_Exception If the date interval cannot be created.
1496
    * 
1497
    * @see ConvertHelper::ERROR_CANNOT_GET_DATE_DIFF
1498
    */
1499
    public static function seconds2interval(int $seconds) : \DateInterval
1500
    {
1501
        return ConvertHelper_DateInterval::fromSeconds($seconds)->getInterval();
1502
    }
1503
    
1504
   /**
1505
    * Converts a size string like "50 MB" to the corresponding byte size.
1506
    * It is case insensitive, ignores spaces, and supports both traditional
1507
    * "MB" and "MiB" notations.
1508
    * 
1509
    * @param string $size
1510
    * @return int
1511
    */
1512
    public static function size2bytes(string $size) : int
1513
    {
1514
        return self::parseSize($size)->toBytes();
1515
    }
1516
    
1517
   /**
1518
    * Parses a size string like "50 MB" and returns a size notation instance
1519
    * that has utility methods to access information on it, and convert it.
1520
    * 
1521
    * @param string $size
1522
    * @return ConvertHelper_SizeNotation
1523
    */
1524
    public static function parseSize(string $size) : ConvertHelper_SizeNotation
1525
    {
1526
        return new ConvertHelper_SizeNotation($size);
1527
    }
1528
}
1529