Passed
Push — master ( f1ca2b...4512a5 )
by Sebastian
04:21
created

ConvertHelper::isBoolean()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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