Passed
Push — master ( 2dea8c...1a0534 )
by Sebastian
02:34
created

ConvertHelper::var2json()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 11
c 0
b 0
f 0
nc 2
nop 3
dl 0
loc 17
rs 9.9
1
<?php
2
/**
3
 * File containing the {@see AppUtils\ConvertHelper} class.
4
 * 
5
 * @package Application Utils
6
 * @subpackage ConvertHelper
7
 * @see AppUtils\ConvertHelper
8
 */
9
10
namespace AppUtils;
11
12
/**
13
 * Static conversion helper class: offers a number of utility methods
14
 * to convert variable types, as well as specialized methods for working
15
 * with specific types like dates.
16
 * 
17
 * @package Application Utils
18
 * @subpackage ConvertHelper
19
 * @author Sebastian Mordziol <[email protected]>
20
 */
21
class ConvertHelper
22
{
23
    const ERROR_MONTHTOSTRING_NOT_VALID_MONTH_NUMBER = 23303;
24
    
25
    const ERROR_CANNOT_NORMALIZE_NON_SCALAR_VALUE = 23304;
26
    
27
    const ERROR_JSON_ENCODE_FAILED = 23305;
28
    
29
    /**
30
     * Normalizes tabs in the specified string by indenting everything
31
     * back to the minimum tab distance. With the second parameter,
32
     * tabs can optionally be converted to spaces as well (recommended
33
     * for HTML output).
34
     *
35
     * @param string $string
36
     * @param boolean $tabs2spaces
37
     * @return string
38
     */
39
    public static function normalizeTabs(string $string, bool $tabs2spaces = false)
40
    {
41
        $lines = explode("\n", $string);
42
        $max = 0;
43
        $min = 99999;
44
        foreach ($lines as $line) {
45
            $amount = substr_count($line, "\t");
46
            if ($amount > $max) {
47
                $max = $amount;
48
            }
49
50
            if ($amount > 0 && $amount < $min) {
51
                $min = $amount;
52
            }
53
        }
54
55
        $converted = array();
56
        foreach ($lines as $line) {
57
            $amount = substr_count($line, "\t") - $min;
58
            $line = trim($line);
59
            if ($amount >= 1) {
60
                $line = str_repeat("\t", $amount) . $line;
61
            }
62
63
            $converted[] = $line;
64
        }
65
66
        $string = implode("\n", $converted);
67
        if ($tabs2spaces) {
68
            $string = self::tabs2spaces($string);
69
        }
70
71
        return $string;
72
    }
73
74
    /**
75
     * Converts tabs to spaces in the specified string.
76
     * @param string $string
77
     * @return string
78
     */
79
    public static function tabs2spaces($string)
80
    {
81
        return str_replace("\t", '    ', $string);
82
    }
83
    
84
    /**
85
     * Converts the specified amount of seconds into
86
     * a human readable string split in months, weeks,
87
     * days, hours, minutes and seconds.
88
     *
89
     * @param float $seconds
90
     * @return string
91
     */
92
    public static function time2string($seconds)
93
    {
94
        static $units = null;
95
        if (is_null($units)) {
96
            $units = array(
97
                array(
98
                    'value' => 31 * 7 * 24 * 3600,
99
                    'singular' => t('month'),
100
                    'plural' => t('months')
101
                ),
102
                array(
103
                    'value' => 7 * 24 * 3600,
104
                    'singular' => t('week'),
105
                    'plural' => t('weeks')
106
                ),
107
                array(
108
                    'value' => 24 * 3600,
109
                    'singular' => t('day'),
110
                    'plural' => t('days')
111
                ),
112
                array(
113
                    'value' => 3600,
114
                    'singular' => t('hour'),
115
                    'plural' => t('hours')
116
                ),
117
                array(
118
                    'value' => 60,
119
                    'singular' => t('minute'),
120
                    'plural' => t('minutes')
121
                ),
122
                array(
123
                    'value' => 1,
124
                    'singular' => t('second'),
125
                    'plural' => t('seconds')
126
                )
127
            );
128
        }
129
130
        // specifically handle zero
131
        if ($seconds <= 0) {
132
            return '0 ' . t('seconds');
133
        }
134
        
135
        if($seconds < 1) {
136
            return t('less than a second');
137
        }
138
139
        $tokens = array();
140
        foreach ($units as $def) {
141
            $quot = intval($seconds / $def['value']);
142
            if ($quot) {
143
                $item = $quot . ' ';
144
                if (abs($quot) > 1) {
145
                    $item .= $def['plural'];
146
                } else {
147
                    $item .= $def['singular'];
148
                }
149
150
                $tokens[] = $item;
151
                $seconds -= $quot * $def['value'];
152
            }
153
        }
154
155
        $last = array_pop($tokens);
156
        if (empty($tokens)) {
157
            return $last;
158
        }
159
160
        return implode(', ', $tokens) . ' ' . t('and') . ' ' . $last;
161
    }
162
163
    /**
164
     * Converts a timestamp into an easily understandable
165
     * format, e.g. "2 hours", "1 day", "3 months"
166
     *
167
     * If you set the date to parameter, the difference
168
     * will be calculated between the two dates and not
169
     * the current time.
170
     *
171
     * @param float|\DateTime $datefrom
172
     * @param float|\DateTime $dateto
173
     * @link http://www.sajithmr.com/php-time-ago-calculation/
174
     */
175
    public static function duration2string($datefrom, $dateto = -1)
176
    {
177
        if($datefrom instanceof \DateTime) {
178
            $datefrom = ConvertHelper::date2timestamp($datefrom);
179
        }
180
        
181
        if($dateto instanceof \DateTime) {
182
            $dateto = ConvertHelper::date2timestamp($dateto);
183
        }
184
        
185
        // Defaults and assume if 0 is passed in that
186
        // its an error rather than the epoch
187
188
        if ($datefrom <= 0) {
189
            return t('A long time ago');
190
        }
191
        if ($dateto == -1) {
192
            $dateto = time();
193
        }
194
195
        // Calculate the difference in seconds betweeen
196
        // the two timestamps
197
198
        $difference = $dateto - $datefrom;
199
        $interval = "";
200
        
201
        $future = false;
202
        if($difference < 0) {
203
            $difference = $difference * -1;
204
            $future = true;
205
        }
206
207
        // If difference is less than 60 seconds,
208
        // seconds is a good interval of choice
209
210
        if ($difference < 60) {
211
            $interval = "s";
212
        }
213
214
        // If difference is between 60 seconds and
215
        // 60 minutes, minutes is a good interval
216
        elseif ($difference >= 60 && $difference < 60 * 60) {
217
            $interval = "n";
218
        }
219
220
        // If difference is between 1 hour and 24 hours
221
        // hours is a good interval
222
        elseif ($difference >= 60 * 60 && $difference < 60 * 60 * 24) {
223
            $interval = "h";
224
        }
225
226
        // If difference is between 1 day and 7 days
227
        // days is a good interval
228
        elseif ($difference >= 60 * 60 * 24 && $difference < 60 * 60 * 24 * 7) {
229
            $interval = "d";
230
        }
231
232
        // If difference is between 1 week and 30 days
233
        // weeks is a good interval
234
        elseif ($difference >= 60 * 60 * 24 * 7 && $difference < 60 * 60 * 24 * 30) {
235
            $interval = "ww";
236
        }
237
238
        // If difference is between 30 days and 365 days
239
        // months is a good interval, again, the same thing
240
        // applies, if the 29th February happens to exist
241
        // between your 2 dates, the function will return
242
        // the 'incorrect' value for a day
243
        elseif ($difference >= 60 * 60 * 24 * 30 && $difference < 60 * 60 * 24 * 365) {
244
            $interval = "m";
245
        }
246
247
        // If difference is greater than or equal to 365
248
        // days, return year. This will be incorrect if
249
        // for example, you call the function on the 28th April
250
        // 2008 passing in 29th April 2007. It will return
251
        // 1 year ago when in actual fact (yawn!) not quite
252
        // a year has gone by
253
        elseif ($difference >= 60 * 60 * 24 * 365) {
254
            $interval = "y";
255
        }
256
        
257
        $result = '';
258
259
        // Based on the interval, determine the
260
        // number of units between the two dates
261
        // From this point on, you would be hard
262
        // pushed telling the difference between
263
        // this function and DateDiff. If the $datediff
264
        // returned is 1, be sure to return the singular
265
        // of the unit, e.g. 'day' rather 'days'
266
        switch ($interval) 
267
        {
268
            case "m":
269
                $months_difference = (int)floor($difference / 60 / 60 / 24 / 29);
270
                $hour = (int)date("H", $datefrom);
0 ignored issues
show
Bug introduced by
It seems like $datefrom can also be of type double; however, parameter $timestamp of date() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

270
                $hour = (int)date("H", /** @scrutinizer ignore-type */ $datefrom);
Loading history...
271
                $min = (int)date("i", $datefrom);
272
                $sec = (int)date("s", $datefrom);
273
                $month = (int)date("n", $datefrom);
274
                $day = (int)date("j", $dateto);
275
                $year = (int)date("Y", $datefrom);
276
                
277
                while(mktime($hour, $min, $sec, $month + ($months_difference), $day, $year) < $dateto) 
278
                {
279
                    $months_difference++;
280
                }
281
                
282
                $datediff = $months_difference;
283
284
                // We need this in here because it is possible
285
                // to have an 'm' interval and a months
286
                // difference of 12 because we are using 29 days
287
                // in a month
288
                if ($datediff == 12) {
289
                    $datediff--;
290
                }
291
292
                if($future) {
293
                    $result = ($datediff == 1) ? t('In one month', $datediff) : t('In %1s months', $datediff);
294
                } else {
295
                    $result = ($datediff == 1) ? t('One month ago', $datediff) : t('%1s months ago', $datediff);
296
                }
297
                break;
298
299
            case "y":
300
                $datediff = floor($difference / 60 / 60 / 24 / 365);
301
                if($future) {
302
                    $result = ($datediff == 1) ? t('In one year', $datediff) : t('In %1s years', $datediff);
303
                } else {
304
                    $result = ($datediff == 1) ? t('One year ago', $datediff) : t('%1s years ago', $datediff);
305
                }
306
                break;
307
308
            case "d":
309
                $datediff = floor($difference / 60 / 60 / 24);
310
                if($future) {
311
                    $result = ($datediff == 1) ? t('In one day', $datediff) : t('In %1s days', $datediff);
312
                } else {
313
                    $result = ($datediff == 1) ? t('One day ago', $datediff) : t('%1s days ago', $datediff);
314
                }
315
                break;
316
317
            case "ww":
318
                $datediff = floor($difference / 60 / 60 / 24 / 7);
319
                if($future) {
320
                    $result = ($datediff == 1) ? t('In one week', $datediff) : t('In %1s weeks', $datediff);
321
                } else {
322
                    $result = ($datediff == 1) ? t('One week ago', $datediff) : t('%1s weeks ago', $datediff);
323
                }
324
                break;
325
326
            case "h":
327
                $datediff = floor($difference / 60 / 60);
328
                if($future) {
329
                    $result = ($datediff == 1) ? t('In one hour', $datediff) : t('In %1s hours', $datediff);
330
                } else {
331
                    $result = ($datediff == 1) ? t('One hour ago', $datediff) : t('%1s hours ago', $datediff);
332
                }
333
                break;
334
335
            case "n":
336
                $datediff = floor($difference / 60);
337
                if($future) {
338
                    $result = ($datediff == 1) ? t('In one minute', $datediff) : t('In %1s minutes', $datediff);
339
                } else {
340
                    $result = ($datediff == 1) ? t('One minute ago', $datediff) : t('%1s minutes ago', $datediff);
341
                }
342
                break;
343
344
            case "s":
345
                $datediff = $difference;
346
                if($future) {
347
                    $result = ($datediff == 1) ? t('In one second', $datediff) : t('In %1s seconds', $datediff);
348
                } else {
349
                    $result = ($datediff == 1) ? t('One second ago', $datediff) : t('%1s seconds ago', $datediff);
350
                }
351
                break;
352
        }
353
354
        return $result;
355
    }
356
357
    /**
358
     * Adds syntax highlighting to the specified SQL string in HTML format
359
     * @param string $sql
360
     * @return string
361
     */
362
    public static function highlight_sql($sql)
363
    {
364
        $geshi = new  \GeSHi($sql, 'sql');
365
366
        return $geshi->parse_code();
367
    }
368
    
369
    public static function highlight_xml($xml, $formatSource=false)
370
    {
371
        if($formatSource) 
372
        {
373
            $dom = new \DOMDocument();
374
            $dom->loadXML($xml);
375
            $dom->preserveWhiteSpace = false;
376
            $dom->formatOutput = true;
377
            
378
            $xml = $dom->saveXML();
379
        }
380
        
381
        $geshi = new \GeSHi($xml, 'xml');
382
        
383
        return $geshi->parse_code();
384
    }
385
386
    public static function highlight_php($php)
387
    {
388
        $geshi = new \GeSHi($php, 'php');
389
    
390
        return $geshi->parse_code();
391
    }
392
    
393
    /**
394
     * Converts a number of bytes to a human readable form,
395
     * e.g. xx Kb / xx Mb / xx Gb
396
     *
397
     * @param $bytes
398
     * @param $precision
399
     * @return string
400
     */
401
    public static function bytes2readable($bytes, $precision = 1)
402
    {
403
        $kilobyte = 1024;
404
        $megabyte = $kilobyte * 1024;
405
        $gigabyte = $megabyte * 1024;
406
        $terabyte = $gigabyte * 1024;
407
408
        if (($bytes >= 0) && ($bytes < $kilobyte)) {
409
            return $bytes . ' ' . t('B');
410
411
        } elseif (($bytes >= $kilobyte) && ($bytes < $megabyte)) {
412
            return round($bytes / $kilobyte, $precision) . ' ' . t('Kb');
413
414
        } elseif (($bytes >= $megabyte) && ($bytes < $gigabyte)) {
415
            return round($bytes / $megabyte, $precision) . ' ' . t('Mb');
416
417
        } elseif (($bytes >= $gigabyte) && ($bytes < $terabyte)) {
418
            return round($bytes / $gigabyte, $precision) . ' ' . t('Gb');
419
420
        } elseif ($bytes >= $terabyte) {
421
            return round($bytes / $gigabyte, $precision) . ' ' . t('Tb');
422
        }
423
424
        return $bytes . ' ' . t('B');
425
    }
426
427
   /**
428
    * Cuts a text to the specified length if it is longer than the
429
    * target length. Appends a text to signify it has been cut at 
430
    * the end of the string.
431
    * 
432
    * @param string $text
433
    * @param int $targetLength
434
    * @param string $append
435
    * @return string
436
    */
437
    public static function text_cut(string $text, int $targetLength, string $append = '...') : string
438
    {
439
        $length = mb_strlen($text);
440
        if ($length <= $targetLength) {
441
            return $text;
442
        }
443
444
        $text = trim(mb_substr($text, 0, $targetLength)) . $append;
445
446
        return $text;
447
    }
448
449
    public static function var_dump($var, $html=true)
450
    {
451
        $info = parseVariable($var);
452
        
453
        if($html) {
454
            return $info->toHTML();
455
        }
456
        
457
        return $info->toString();
458
    }
459
    
460
    public static function print_r($var, $return=false, $html=true)
461
    {
462
        $result = self::var_dump($var, $html);
463
        
464
        if($html) {
465
            $result = 
466
            '<pre style="background:#fff;color:#333;padding:16px;border:solid 1px #bbb;border-radius:4px">'.
467
                $result.
468
            '</pre>';
469
        }
470
        
471
        if($return) {
472
            return $result;
473
        }
474
        
475
        echo $result;
476
    }
477
    
478
    protected static $booleanStrings = array(
479
        1 => true,
480
        0 => false,
481
        '1' => true,
482
        '0' => false,
483
        'true' => true,
484
        'false' => false,
485
        'yes' => true,
486
        'no' => false
487
    );
488
489
    public static function string2bool($string)
490
    {
491
        if($string === '' || $string === null) {
492
            return false;
493
        }
494
        
495
        if (is_bool($string)) {
496
            return $string;
497
        }
498
499
        if (!array_key_exists($string, self::$booleanStrings)) {
500
            throw new \InvalidArgumentException('Invalid string boolean representation');
501
        }
502
503
        return self::$booleanStrings[$string];
504
    }
505
    
506
   /**
507
    * Whether the specified string is a boolean string or boolean value.
508
    * Alias for {@link ConvertHelper::isBoolean()}.
509
    * 
510
    * @param mixed $string
511
    * @return bool
512
    * @deprecated
513
    * @see ConvertHelper::isBoolean()
514
    */
515
    public static function isBooleanString($string) : bool
516
    {
517
        return self::isBoolean($string);
518
    }
519
520
   /**
521
    * Alias for the {@\AppUtils\XMLHelper::string2xml()} method.
522
    * 
523
    * @param string $text
524
    * @return string
525
    * @deprecated
526
    */
527
    public static function text_makeXMLCompliant($text)
528
    {
529
        return XMLHelper::string2xml($text);
530
    }
531
532
    /**
533
     * Transforms a date into a generic human readable date, optionally with time.
534
     * If the year is the same as the current one, it is omitted.
535
     *
536
     * - 6 Jan 2012
537
     * - 12 Dec 2012 17:45
538
     * - 5 Aug
539
     *
540
     * @param \DateTime $date
541
     * @return string
542
     */
543
    public static function date2listLabel(\DateTime $date, $includeTime = false, $shortMonth = false)
544
    {
545
        $today = new \DateTime();
546
        if($date->format('d.m.Y') == $today->format('d.m.Y')) {
547
            $label = t('Today');
548
        } else {
549
            $label = $date->format('d') . '. ' . self::month2string((int)$date->format('m'), $shortMonth) . ' ';
550
            if ($date->format('Y') != date('Y')) {
551
                $label .= $date->format('Y');
552
            }
553
        }
554
        
555
        if ($includeTime) {
556
            $label .= $date->format(' H:i');
557
        }
558
559
        return trim($label);
560
    }
561
562
    protected static $months;
563
564
    /**
565
     * Returns a human readable month name given the month number. Can optionally
566
     * return the shorthand version of the month. Translated into the current
567
     * application locale.
568
     *
569
     * @param int|string $monthNr
570
     * @param boolean $short
571
     * @throws ConvertHelper_Exception
572
     * @return string
573
     */
574
    public static function month2string($monthNr, $short = false)
575
    {
576
        if (!isset(self::$months)) {
577
            self::$months = array(
578
                1 => array(t('January'), t('Jan')),
579
                2 => array(t('February'), t('Feb')),
580
                3 => array(t('March'), t('Mar')),
581
                4 => array(t('April'), t('Apr')),
582
                5 => array(t('May'), t('May')),
583
                6 => array(t('June'), t('Jun')),
584
                7 => array(t('July'), t('Jul')),
585
                8 => array(t('August'), t('Aug')),
586
                9 => array(t('September'), t('Sep')),
587
                10 => array(t('October'), t('Oct')),
588
                11 => array(t('November'), t('Nov')),
589
                12 => array(t('December'), t('Dec'))
590
            );
591
        }
592
593
        $monthNr = intval($monthNr);
594
        if (!isset(self::$months[$monthNr])) {
595
            throw new ConvertHelper_Exception(
596
                'Invalid month number',
597
                sprintf('%1$s is not a valid month number.', $monthNr),
598
                self::ERROR_MONTHTOSTRING_NOT_VALID_MONTH_NUMBER
599
            );
600
        }
601
602
        if ($short) {
603
            return self::$months[$monthNr][1];
604
        }
605
606
        return self::$months[$monthNr][0];
607
    }
608
609
    /**
610
     * Transliterates a string.
611
     *
612
     * @param string $string
613
     * @param string $spaceChar
614
     * @param string $lowercase
615
     * @return string
616
     */
617
    public static function transliterate($string, $spaceChar = '-', $lowercase = true)
618
    {
619
        $translit = new Transliteration();
620
        $translit->setSpaceReplacement($spaceChar);
621
        if ($lowercase) {
622
            $translit->setLowercase();
623
        }
624
625
        return $translit->convert($string);
626
    }
627
    
628
   /**
629
    * Retrieves the HEX character codes for all control
630
    * characters that the {@link stripControlCharacters()} 
631
    * method will remove.
632
    * 
633
    * @return string[]
634
    */
635
    public static function getControlCharactersAsHex()
636
    {
637
        $hexAlphabet = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
638
        
639
        $stack = array();
640
        foreach(self::$controlChars as $char)
641
        {
642
            $tokens = explode('-', $char);
643
            $start = $tokens[0];
644
            $end = $tokens[1];
645
            $prefix = substr($start, 0, 3);
646
            $range = array();
647
            foreach($hexAlphabet as $number) {
648
                $range[] = $prefix.$number;
649
            }
650
            
651
            $use = false;
652
            foreach($range as $number) {
653
                if($number == $start) {
654
                    $use = true;
655
                }
656
                
657
                if($use) {
658
                    $stack[] = $number;
659
                }
660
                
661
                if($number == $end) {
662
                    break;
663
                }
664
            }
665
        }
666
        
667
        return $stack;
668
    }
669
    
670
   /**
671
    * Retrieves an array of all control characters that
672
    * the {@link stripControlCharacters()} method will 
673
    * remove, as the actual UTF-8 characters.
674
    * 
675
    * @return string[]
676
    */
677
    public static function getControlCharactersAsUTF8()
678
    {
679
        $chars = self::getControlCharactersAsHex();
680
        
681
        $result = array();
682
        foreach($chars as $char) {
683
            $result[] = hex2bin($char);
684
        }
685
        
686
        return $result;
687
    }
688
    
689
   /**
690
    * Retrieves all control characters as JSON encoded
691
    * characters, e.g. "\u200b".
692
    * 
693
    * @return string[]
694
    */
695
    public static function getControlCharactersAsJSON()
696
    {
697
        $chars = self::getControlCharactersAsHex();
698
        
699
        $result = array();
700
        foreach($chars as $char) {
701
            $result[] = '\u'.strtolower($char);
702
        }
703
        
704
        return $result;
705
    }
706
    
707
    protected static $controlChars =  array(
708
        '0000-0008', // control chars
709
        '000E-000F', // control chars
710
        '0010-001F', // control chars
711
        '2000-200F', // non-breaking space and co
712
    );
713
    
714
    protected static $controlCharsRegex;
715
716
    /**
717
     * Removes all control characters from the specified string
718
     * that can cause problems in some cases, like creating
719
     * valid XML documents. This includes invisible non-breaking
720
     * spaces.
721
     *
722
     * @param string $string
723
     * @return string
724
     * @see https://stackoverflow.com/a/8171868/2298192
725
     * @see https://unicode-table.com/en
726
     */
727
    public static function stripControlCharacters(string $string) : string
728
    {
729
        if(empty($string)) {
730
            return $string;
731
        }
732
        
733
        // create the regex from the unicode characters list
734
        if(!isset(self::$controlCharsRegex)) 
735
        {
736
            $chars = self::getControlCharactersAsHex();
737
738
            // we use the notation \x{0000} to specify the unicode character key
739
            // in the regular expression.
740
            $stack = array();
741
            foreach($chars as $char) {
742
                $stack[] = '\x{'.$char.'}';
743
            }
744
            
745
            self::$controlCharsRegex = '/['.implode('', $stack).']/u';
746
        }
747
        
748
        return preg_replace(self::$controlCharsRegex, '', $string);
749
    }
750
751
   /**
752
    * Converts a unicode character to the PHPO notation.
753
    * 
754
    * Example:
755
    * 
756
    * <pre>unicodeChar2php('"\u0000"')</pre>
757
    * 
758
    * Returns
759
    * 
760
    * <pre>\x0</pre>
761
    * 
762
    * @param string $unicodeChar
763
    * @return string
764
    */
765
    public static function unicodeChar2php($unicodeChar) 
766
    {
767
        $unicodeChar = json_decode($unicodeChar);
768
        
769
        /** @author Krinkle 2018 */
770
        $output = '';
771
        foreach (str_split($unicodeChar) as $octet) {
772
            $ordInt = ord($octet);
773
            // Convert from int (base 10) to hex (base 16), for PHP \x syntax
774
            $ordHex = base_convert($ordInt, 10, 16);
775
            $output .= '\x' . $ordHex;
776
        }
777
        return $output;
778
    }
779
    
780
    /**
781
     * Removes the extension from the specified filename
782
     * and returns the name without the extension.
783
     *
784
     * Example:
785
     * filename.html > filename
786
     * passed.test.jpg > passed.test
787
     * path/to/file/document.txt > document
788
     *
789
     * @param string $filename
790
     * @return string
791
     */
792
    public static function filenameRemoveExtension($filename)
793
    {
794
        return FileHelper::removeExtension($filename);
795
    }
796
    
797
    public static function areVariablesEqual($a, $b) : bool
798
    {
799
        $a = self::convertScalarForComparison($a);
800
        $b = self::convertScalarForComparison($b);
801
802
        return $a === $b;
803
    }
804
    
805
    protected static function convertScalarForComparison($scalar)
806
    {
807
        if($scalar === '' || is_null($scalar)) {
808
            return null;
809
        }
810
        
811
        if(is_bool($scalar)) {
812
            return self::bool2string($scalar);
813
        }
814
        
815
        if(is_array($scalar)) {
816
            $scalar = md5(serialize($scalar));
817
        }
818
        
819
        if($scalar !== null && !is_scalar($scalar)) {
820
            throw new ConvertHelper_Exception(
821
                'Not a scalar value in comparison',
822
                null,
823
                self::ERROR_CANNOT_NORMALIZE_NON_SCALAR_VALUE
824
            );
825
        }
826
        
827
        return strval($scalar);
828
    }
829
830
    /**
831
     * Compares two strings to check whether they are equal.
832
     * null and empty strings are considered equal.
833
     *
834
     * @param string $a
835
     * @param string $b
836
     * @return boolean
837
     */
838
    public static function areStringsEqual($a, $b) : bool
839
    {
840
        return self::areVariablesEqual($a, $b);
841
    }
842
843
    /**
844
     * Checks whether the two specified numbers are equal.
845
     * null and empty strings are considered as 0 values.
846
     *
847
     * @param number|string $a
848
     * @param number|string $b
849
     * @return boolean
850
     */
851
    public static function areNumbersEqual($a, $b) : bool
852
    {
853
        return self::areVariablesEqual($a, $b);
854
    }
855
856
    /**
857
     * Converts a boolean value to a string. Defaults to returning
858
     * 'true' or 'false', with the additional parameter it can also
859
     * return the 'yes' and 'no' variants.
860
     *
861
     * @param boolean|string $boolean
862
     * @param boolean $yesno
863
     * @return string
864
     */
865
    public static function bool2string($boolean, bool $yesno = false) : string
866
    {
867
        // allow 'yes', 'true', 'no', 'false' string notations as well
868
        if(!is_bool($boolean)) {
869
            $boolean = self::string2bool($boolean);
870
        }
871
        
872
        if ($boolean) {
873
            if ($yesno) {
874
                return 'yes';
875
            }
876
877
            return 'true';
878
        }
879
880
        if ($yesno) {
881
            return 'no';
882
        }
883
884
        return 'false';
885
    }
886
    
887
   /**
888
    * Converts an associative array with attribute name > value pairs
889
    * to an attribute string that can be used in an HTML tag. Empty 
890
    * attribute values are ignored.
891
    * 
892
    * Example:
893
    * 
894
    * array2attributeString(array(
895
    *     'id' => 45,
896
    *     'href' => 'http://www.mistralys.com'
897
    * ));
898
    * 
899
    * Result:
900
    * 
901
    * id="45" href="http://www.mistralys.com"
902
    * 
903
    * @param array $array
904
    * @return string
905
    */
906
    public static function array2attributeString($array)
907
    {
908
        $tokens = array();
909
        foreach($array as $attr => $value) {
910
            if($value == '' || $value == null) {
911
                continue;
912
            }
913
            
914
            $tokens[] = $attr.'="'.$value.'"';
915
        }
916
        
917
        if(empty($tokens)) {
918
            return '';
919
        }
920
        
921
        return ' '.implode(' ', $tokens);
922
    }
923
    
924
   /**
925
    * Converts a string so it can safely be used in a javascript
926
    * statement in an HTML tag: uses single quotes around the string
927
    * and encodes all special characters as needed.
928
    * 
929
    * @param string $string
930
    * @return string
931
    */
932
    public static function string2attributeJS($string, $quoted=true)
933
    {
934
        $converted = addslashes(htmlspecialchars(strip_tags($string), ENT_QUOTES, 'UTF-8'));
935
        if($quoted) {
936
            $converted = "'".$converted."'";
937
        } 
938
        
939
        return $converted;
940
    }
941
    
942
   /**
943
    * Checks if the specified string is a boolean value, which
944
    * includes string representations of boolean values, like 
945
    * <code>yes</code> or <code>no</code>, and <code>true</code>
946
    * or <code>false</code>.
947
    * 
948
    * @param mixed $value
949
    * @return boolean
950
    */
951
    public static function isBoolean($value) : bool
952
    {
953
        if(is_bool($value)) {
954
            return true;
955
        }
956
        
957
        if(!is_scalar($value)) {
958
            return false;
959
        }
960
        
961
        return array_key_exists($value, self::$booleanStrings);
962
    }
963
    
964
   /**
965
    * Converts an associative array to an HTML style attribute value string.
966
    * 
967
    * @param array $subject
968
    * @return string
969
    */
970
    public static function array2styleString(array $subject) : string
971
    {
972
        $tokens = array();
973
        foreach($subject as $name => $value) {
974
            $tokens[] = $name.':'.$value;
975
        }
976
        
977
        return implode(';', $tokens);
978
    }
979
    
980
   /**
981
    * Converts a DateTime object to a timestamp, which
982
    * is PHP 5.2 compatible.
983
    * 
984
    * @param \DateTime $date
985
    * @return integer
986
    */
987
    public static function date2timestamp(\DateTime $date) : int
988
    {
989
        return (int)$date->format('U');
990
    }
991
    
992
   /**
993
    * Converts a timestamp into a DateTime instance.
994
    * @param int $timestamp
995
    * @return \DateTime
996
    */
997
    public static function timestamp2date(int $timestamp) : \DateTime
998
    {
999
        $date = new \DateTime();
1000
        $date->setTimestamp($timestamp);
1001
        return $date;
1002
    }
1003
    
1004
   /**
1005
    * Strips an absolute path to a file within the application
1006
    * to make the path relative to the application root path.
1007
    * 
1008
    * @param string $path
1009
    * @return string
1010
    * 
1011
    * @see FileHelper::relativizePath()
1012
    * @see FileHelper::relativizePathByDepth()
1013
    */
1014
    public static function fileRelativize(string $path) : string
1015
    {
1016
        return FileHelper::relativizePathByDepth($path);
1017
    }
1018
    
1019
    /**
1020
    * Converts a PHP regex to a javascript RegExp object statement.
1021
    * 
1022
    * NOTE: This is an alias for the JSHelper's `convertRegex` method. 
1023
    * More details are available on its usage there.
1024
    *
1025
    * @param string $regex A PHP preg regex
1026
    * @param string $statementType The type of statement to return: Defaults to a statement to create a RegExp object.
1027
    * @return array|string Depending on the specified return type.
1028
    * 
1029
    * @see JSHelper::buildRegexStatement()
1030
    */
1031
    public static function regex2js(string $regex, string $statementType=JSHelper::JS_REGEX_OBJECT)
1032
    {
1033
        return JSHelper::buildRegexStatement($regex, $statementType);
1034
    }
1035
    
1036
   /**
1037
    * Converts the specified variable to JSON. Works just
1038
    * like the native `json_encode` method, except that it
1039
    * will trigger an exception on failure, which has the 
1040
    * json error details included in its developer details.
1041
    * 
1042
    * @param mixed $variable
1043
    * @param int|NULL $options JSON encode options.
1044
    * @param int|NULL $depth 
1045
    * @throws ConvertHelper_Exception
1046
    * @return string
1047
    */
1048
    public static function var2json($variable, int $options=0, int $depth=512) : string
1049
    {
1050
        $result = json_encode($variable, $options, $depth);
1051
        
1052
        if($result !== false) {
1053
            return $result;
1054
        }
1055
        
1056
        throw new ConvertHelper_Exception(
1057
            'Could not create json array'.json_last_error_msg(),
1058
            sprintf(
1059
                'The call to json_encode failed for the variable [%s]. JSON error details: #%s, %s',
1060
                parseVariable($variable)->toString(),
1061
                json_last_error(),
1062
                json_last_error_msg()
1063
            ),
1064
            self::ERROR_JSON_ENCODE_FAILED
1065
        );
1066
    }
1067
    
1068
   /**
1069
    * Strips all known UTF byte order marks from the specified string.
1070
    * 
1071
    * @param string $string
1072
    * @return string
1073
    */
1074
    public static function stripUTFBom($string)
1075
    {
1076
        $boms = FileHelper::getUTFBOMs();
1077
        foreach($boms as $bomChars) {
1078
            $length = mb_strlen($bomChars);
1079
            $text = mb_substr($string, 0, $length);
1080
            if($text==$bomChars) {
1081
                return mb_substr($string, $length);
1082
            }
1083
        }
1084
        
1085
        return $string;
1086
    }
1087
1088
   /**
1089
    * Converts a string to valid utf8, regardless
1090
    * of the string's encoding(s).
1091
    * 
1092
    * @param string $string
1093
    * @return string
1094
    */
1095
    public static function string2utf8($string)
1096
    {
1097
        if(!self::isStringASCII($string)) {
1098
            return \ForceUTF8\Encoding::toUTF8($string);
1099
        }
1100
        
1101
        return $string;
1102
    }
1103
    
1104
   /**
1105
    * Checks whether the specified string is an ASCII
1106
    * string, without any special or UTF8 characters.
1107
    * Note: empty strings and NULL are considered ASCII.
1108
    * Any variable types other than strings are not.
1109
    * 
1110
    * @param string $string
1111
    * @return boolean
1112
    */
1113
    public static function isStringASCII($string)
1114
    {
1115
        if($string === '' || $string === NULL) {
1116
            return true;
1117
        }
1118
        
1119
        if(!is_string($string)) {
0 ignored issues
show
introduced by
The condition is_string($string) is always true.
Loading history...
1120
            return false;
1121
        }
1122
        
1123
        return !preg_match('/[^\x00-\x7F]/', $string);
1124
    }
1125
    
1126
    public static function highlight_url($url)
1127
    {
1128
        $url = htmlspecialchars($url);
1129
        $url = str_replace(
1130
            array('/', '='), 
1131
            array('/<wbr>', '=<wbr>'), 
1132
            $url
1133
        );
1134
        return $url;
1135
    }
1136
1137
   /**
1138
    * Calculates a percentage match of the source string with the target string.
1139
    * 
1140
    * Options are:
1141
    * 
1142
    * - maxLevenshtein, default: 10
1143
    *   Any levenshtein results above this value are ignored.
1144
    *   
1145
    * - precision, default: 1
1146
    *   The precision of the percentage float value
1147
    * 
1148
    * @param string $source
1149
    * @param string $target
1150
    * @param array $options
1151
    * @return float
1152
    */
1153
    public static function matchString($source, $target, $options=array())
1154
    {
1155
        $defaults = array(
1156
            'maxLevenshtein' => 10,
1157
            'precision' => 1
1158
        );
1159
        
1160
        $options = array_merge($defaults, $options);
1161
        
1162
        // avoid doing this via levenshtein
1163
        if($source == $target) {
1164
            return 100;
1165
        }
1166
        
1167
        $diff = levenshtein($source, $target);
1168
        if($diff > $options['maxLevenshtein']) {
1169
            return 0;
1170
        }
1171
        
1172
        $percent = $diff * 100 / ($options['maxLevenshtein'] + 1);
1173
        return round(100 - $percent, $options['precision']);
1174
    }
1175
    
1176
    public static function interval2string(\DateInterval $interval)
1177
    {
1178
        $tokens = array('y', 'm', 'd', 'h', 'i', 's');
1179
        
1180
        $offset = 0;
1181
        $keep = array();
1182
        foreach($tokens as $token) {
1183
            if($interval->$token > 0) {
1184
                $keep = array_slice($tokens, $offset);
1185
                break;
1186
            }
1187
            
1188
            $offset++;
1189
        }
1190
        
1191
        $parts = array();
1192
        foreach($keep as $token) 
1193
        {
1194
            $value = $interval->$token;
1195
            $label = '';
1196
            
1197
            $suffix = 'p';
1198
            if($value == 1) { $suffix = 's'; }
1199
            $token .= $suffix;
1200
            
1201
            switch($token) {
1202
                case 'ys': $label = t('1 year'); break;
1203
                case 'yp': $label = t('%1$s years', $value); break;
1204
                case 'ms': $label = t('1 month'); break;
1205
                case 'mp': $label = t('%1$s months', $value); break;
1206
                case 'ds': $label = t('1 day'); break;
1207
                case 'dp': $label = t('%1$s days', $value); break;
1208
                case 'hs': $label = t('1 hour'); break;
1209
                case 'hp': $label = t('%1$s hours', $value); break;
1210
                case 'is': $label = t('1 minute'); break;
1211
                case 'ip': $label = t('%1$s minutes', $value); break;
1212
                case 'ss': $label = t('1 second'); break;
1213
                case 'sp': $label = t('%1$s seconds', $value); break;
1214
            }
1215
            
1216
            $parts[] = $label;
1217
        }
1218
        
1219
        if(count($parts) == 1) {
1220
            return $parts[0];
1221
        } 
1222
        
1223
        $last = array_pop($parts);
1224
        
1225
        return t('%1$s and %2$s', implode(', ', $parts), $last);
1226
    }
1227
    
1228
    const INTERVAL_DAYS = 'days';
1229
    
1230
    const INTERVAL_HOURS = 'hours';
1231
    
1232
    const INTERVAL_MINUTES = 'minutes';
1233
    
1234
    const INTERVAL_SECONDS = 'seconds';
1235
    
1236
   /**
1237
    * Converts an interval to its total amount of days.
1238
    * @param \DateInterval $interval
1239
    * @return int
1240
    */
1241
    public static function interval2days(\DateInterval $interval) : int
1242
    {
1243
        return self::interval2total($interval, self::INTERVAL_DAYS);
1244
    }
1245
1246
   /**
1247
    * Converts an interval to its total amount of hours.
1248
    * @param \DateInterval $interval
1249
    * @return int
1250
    */
1251
    public static function interval2hours(\DateInterval $interval) : int
1252
    {
1253
        return self::interval2total($interval, self::INTERVAL_HOURS);
1254
    }
1255
    
1256
   /**
1257
    * Converts an interval to its total amount of minutes. 
1258
    * @param \DateInterval $interval
1259
    * @return int
1260
    */
1261
    public static function interval2minutes(\DateInterval $interval) : int
1262
    {
1263
        return self::interval2total($interval, self::INTERVAL_MINUTES);
1264
    }
1265
    
1266
   /**
1267
    * Converts an interval to its total amount of seconds.
1268
    * @param \DateInterval $interval
1269
    * @return int
1270
    */    
1271
    public static function interval2seconds(\DateInterval $interval) : int
1272
    {
1273
        return self::interval2total($interval, self::INTERVAL_SECONDS);
1274
    }
1275
    
1276
   /**
1277
    * Calculates the total amount of days / hours / minutes or seconds
1278
    * of a date interval object (depending in the specified units), and 
1279
    * returns the total amount.
1280
    * 
1281
    * @param \DateInterval $interval
1282
    * @param string $unit What total value to calculate.
1283
    * @return integer
1284
    * 
1285
    * @see ConvertHelper::INTERVAL_SECONDS
1286
    * @see ConvertHelper::INTERVAL_MINUTES
1287
    * @see ConvertHelper::INTERVAL_HOURS
1288
    * @see ConvertHelper::INTERVAL_DAYS
1289
    */
1290
    public static function interval2total(\DateInterval $interval, $unit=self::INTERVAL_SECONDS) : int
1291
    {
1292
        $total = $interval->format('%a');
1293
        if ($unit == self::INTERVAL_DAYS) {
1294
            return (int)$total;
1295
        }
1296
        
1297
        $total = ($total * 24) + ($interval->h );
1298
        if ($unit == self::INTERVAL_HOURS) {
1299
            return (int)$total;
1300
        }
1301
    
1302
        $total = ($total * 60) + ($interval->i );
1303
        if ($unit == self::INTERVAL_MINUTES) {
1304
            return (int)$total;
1305
        }
1306
1307
        $total = ($total * 60) + ($interval->s );
1308
        if ($unit == self::INTERVAL_SECONDS) {
1309
            return (int)$total;
1310
        }
1311
        
1312
        return 0;
1313
    }
1314
1315
    protected static $days;
1316
    
1317
    protected static $daysShort;
1318
1319
    protected static $daysInvariant = array(
1320
        'Monday',
1321
        'Tuesday',
1322
        'Wednesday',
1323
        'Thursday',
1324
        'Friday',
1325
        'Saturday',
1326
        'Sunday'
1327
    );
1328
    
1329
   /**
1330
    * Converts a date to the corresponding day name.
1331
    * 
1332
    * @param \DateTime $date
1333
    * @param string $short
1334
    * @return string|NULL
1335
    */
1336
    public static function date2dayName(\DateTime $date, $short=false)
1337
    {
1338
        $day = $date->format('l');
1339
        $invariant = self::getDayNamesInvariant();
1340
        
1341
        $idx = array_search($day, $invariant);
1342
        if($idx !== false) {
1343
            $localized = self::getDayNames($short);
0 ignored issues
show
Bug introduced by
It seems like $short can also be of type false; however, parameter $short of AppUtils\ConvertHelper::getDayNames() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1343
            $localized = self::getDayNames(/** @scrutinizer ignore-type */ $short);
Loading history...
1344
            return $localized[$idx];
1345
        }
1346
        
1347
        return null;
1348
    }
1349
    
1350
   /**
1351
    * Retrieves a list of english day names.
1352
    * @return string[]
1353
    */
1354
    public static function getDayNamesInvariant()
1355
    {
1356
        return self::$daysInvariant;
1357
    }
1358
    
1359
   /**
1360
    * Retrieves the day names list for the current locale.
1361
    * 
1362
    * @param string $short
1363
    * @return string[]
1364
    */
1365
    public static function getDayNames($short=false)
1366
    {
1367
        if($short) {
1368
            if(!isset(self::$daysShort)) {
1369
                self::$daysShort = array(
1370
                    t('Mon'),
1371
                    t('Tue'),
1372
                    t('Wed'),
1373
                    t('Thu'),
1374
                    t('Fri'),
1375
                    t('Sat'),
1376
                    t('Sun')
1377
                );
1378
            }
1379
            
1380
            return self::$daysShort;
1381
        }
1382
        
1383
        if(!isset(self::$days)) {
1384
            self::$days = array(
1385
                t('Monday'),
1386
                t('Tuesday'),
1387
                t('Wednesday'),
1388
                t('Thursday'),
1389
                t('Friday'),
1390
                t('Saturday'),
1391
                t('Sunday')
1392
            );
1393
        }
1394
        
1395
        return self::$days;
1396
    }
1397
1398
    /**
1399
     * Implodes an array with a separator character, and the last item with "add".
1400
     * 
1401
     * @param array $list The indexed array with items to implode.
1402
     * @param string $sep The separator character to use.
1403
     * @param string $conjunction The word to use as conjunction with the last item in the list. NOTE: include spaces as needed.
1404
     * @return string
1405
     */
1406
    public static function implodeWithAnd(array $list, $sep = ', ', $conjunction = null)
1407
    {
1408
        if(empty($list)) {
1409
            return '';
1410
        }
1411
        
1412
        if(empty($conjunction)) {
1413
            $conjunction = t('and');
1414
        }
1415
        
1416
        $last = array_pop($list);
1417
        if($list) {
1418
            return implode($sep, $list) . $conjunction . ' ' . $last;
1419
        }
1420
        
1421
        return $last;
1422
    }
1423
    
1424
   /**
1425
    * Splits a string into an array of all characters it is composed of.
1426
    * Unicode character safe.
1427
    * 
1428
    * NOTE: Spaces and newlines (both \r and \n) are also considered single
1429
    * characters.
1430
    * 
1431
    * @param string $string
1432
    * @return array
1433
    */
1434
    public static function string2array(string $string) : array
1435
    {
1436
        $result = preg_split('//u', $string, null, PREG_SPLIT_NO_EMPTY);
1437
        if($result !== false) {
1438
            return $result;
1439
        }
1440
        
1441
        return array();
1442
    }
1443
    
1444
   /**
1445
    * Checks whether the specified string contains HTML code.
1446
    * 
1447
    * @param string $string
1448
    * @return boolean
1449
    */
1450
    public static function isStringHTML(string $string) : bool
1451
    {
1452
        if(preg_match('%<[a-z/][\s\S]*>%siU', $string)) {
1453
            return true;
1454
        }
1455
        
1456
        $decoded = html_entity_decode($string);
1457
        if($decoded !== $string) {
1458
            return true;
1459
        }
1460
        
1461
        return false;
1462
    }
1463
    
1464
   /**
1465
    * UTF8-safe wordwrap method: works like the regular wordwrap
1466
    * PHP function but compatible with UTF8. Otherwise the lengths
1467
    * are no calculated correctly.
1468
    * 
1469
    * @param string $str
1470
    * @param int $width
1471
    * @param string $break
1472
    * @param bool $cut
1473
    * @return string
1474
    * @see https://stackoverflow.com/a/4988494/2298192
1475
    */
1476
    public static function wordwrap($str, $width = 75, $break = "\n", $cut = false) 
1477
    {
1478
        $lines = explode($break, $str);
1479
        
1480
        foreach ($lines as &$line) 
1481
        {
1482
            $line = rtrim($line);
1483
            if (mb_strlen($line) <= $width) {
1484
                continue;
1485
            }
1486
        
1487
            $words = explode(' ', $line);
1488
            $line = '';
1489
            $actual = '';
1490
            foreach ($words as $word) 
1491
            {
1492
                if (mb_strlen($actual.$word) <= $width) 
1493
                {
1494
                    $actual .= $word.' ';
1495
                } 
1496
                else 
1497
                {
1498
                    if ($actual != '') {
1499
                        $line .= rtrim($actual).$break;
1500
                    }
1501
                    
1502
                    $actual = $word;
1503
                    if ($cut) 
1504
                    {
1505
                        while (mb_strlen($actual) > $width) {
1506
                            $line .= mb_substr($actual, 0, $width).$break;
1507
                            $actual = mb_substr($actual, $width);
1508
                        }
1509
                    }
1510
                    
1511
                    $actual .= ' ';
1512
                }
1513
            }
1514
            
1515
            $line .= trim($actual);
1516
        }
1517
        
1518
        return implode($break, $lines);
1519
    }
1520
    
1521
   /**
1522
    * Calculates the byte length of a string, taking into 
1523
    * account any unicode characters.
1524
    * 
1525
    * @param string $string
1526
    * @return int
1527
    * @see https://stackoverflow.com/a/9718273/2298192
1528
    */
1529
    public static function string2bytes($string)
1530
    {
1531
        return mb_strlen($string, '8bit');
1532
    }
1533
    
1534
   /**
1535
    * Creates a short, 8-character long hash for the specified string.
1536
    * 
1537
    * WARNING: Not cryptographically safe.
1538
    * 
1539
    * @param string $string
1540
    * @return string
1541
    */
1542
    public static function string2shortHash($string)
1543
    {
1544
        return hash('crc32', $string, false);
1545
    }
1546
    
1547
    public static function string2hash($string)
1548
    {
1549
        return md5($string);
1550
    }
1551
    
1552
    public static function callback2string($callback) : string
1553
    {
1554
        return parseVariable($callback)->toString();
1555
    }
1556
1557
    public static function exception2info(\Throwable $e) : ConvertHelper_ThrowableInfo
1558
    {
1559
        return self::throwable2info($e);
1560
    }
1561
    
1562
    public static function throwable2info(\Throwable $e) : ConvertHelper_ThrowableInfo
1563
    {
1564
        return ConvertHelper_ThrowableInfo::fromThrowable($e);
1565
    }
1566
    
1567
   /**
1568
    * Parses the specified query string like the native 
1569
    * function <code>parse_str</code>, without the key
1570
    * naming limitations.
1571
    * 
1572
    * Using parse_str, dots or spaces in key names are 
1573
    * replaced by underscores. This method keeps all names
1574
    * intact.
1575
    * 
1576
    * It still uses the parse_str implementation as it 
1577
    * is tested and tried, but fixes the parameter names
1578
    * after parsing, as needed.
1579
    * 
1580
    * @param string $queryString
1581
    * @return array
1582
    * @see https://www.php.net/manual/en/function.parse-str.php
1583
    */
1584
    public static function parseQueryString(string $queryString) : array
1585
    {
1586
        // allow HTML entities notation
1587
        $queryString = str_replace('&amp;', '&', $queryString);
1588
        
1589
        $paramNames = array();
1590
        
1591
        // extract parameter names from the query string
1592
        $result = array();
1593
        preg_match_all('/&?([^&]+)=.*/sixU', $queryString, $result, PREG_PATTERN_ORDER);
1594
        if(isset($result[1])) {
1595
            $paramNames = $result[1];
1596
        }
1597
        
1598
        // to avoid iterating over the param names, we simply concatenate it
1599
        $search = implode('', $paramNames);
1600
        
1601
        // store whether we need to adjust any of the names: 
1602
        // this is true if we find dots or spaces in any of them.
1603
        $fixRequired = stristr($search, '.') || stristr($search, ' ');
1604
1605
        unset($search);
1606
        
1607
        $table = array();
1608
        
1609
        // A fix is required: replace all parameter names with placeholders,
1610
        // which do not conflict with parse_str and which will be restored
1611
        // with the actual parameter names after the parsing.
1612
        //
1613
        // It is necessary to do this even before the parsing, to resolve
1614
        // possible naming conflicts like having both parameters "foo.bar" 
1615
        // and "foo_bar" in the query string: since "foo.bar" would be converted
1616
        // to "foo_bar", one of the two would be replaced.
1617
        if($fixRequired) 
1618
        {
1619
            $counter = 1;
1620
            $placeholders = array();
1621
            foreach($paramNames as $paramName)
1622
            {
1623
                 // create a unique placeholder name
1624
                 $placeholder = '__PLACEHOLDER'.$counter.'__';
1625
                 
1626
                 // store the placeholder name to replace later
1627
                 $table[$placeholder] = $paramName;
1628
                 
1629
                 // add the placeholder to replace in the query string before parsing
1630
                 $placeholders[$paramName.'='] = $placeholder.'=';
1631
                 
1632
                 $counter++;
1633
            }
1634
            
1635
            // next challenge: replacing the parameter names by placeholders
1636
            // safely. We sort the list by longest name first, to avoid shorter 
1637
            // parameter names being replaced first that can be part of longer ones.
1638
            uksort($placeholders, function($a, $b) {
1639
                return strlen($b) - strlen($a);
1640
            });
1641
            
1642
            // replace all instances with the placeholder
1643
            $queryString = str_replace(array_keys($placeholders), array_values($placeholders), $queryString);
1644
        }
1645
        
1646
        // parse the query string natively
1647
        $parsed = array();
1648
        parse_str($queryString, $parsed);
1649
        
1650
        // do any of the parameter names need to be fixed?
1651
        if(!$fixRequired) {
1652
            return $parsed;
1653
        }
1654
        
1655
        $keep = array();
1656
        
1657
        foreach($parsed as $name => $value)
1658
        {
1659
             $keep[$table[$name]] = $value;
1660
        }
1661
        
1662
        return $keep;
1663
    }
1664
1665
   /**
1666
    * Searches for needle in the specified string, and returns a list
1667
    * of all occurrences, including the matched string. The matched 
1668
    * string is useful when doing a case insensitive search, as it 
1669
    * shows the exact matched case of needle.
1670
    *   
1671
    * @param string $needle
1672
    * @param string $haystack
1673
    * @param bool $caseInsensitive
1674
    * @return ConvertHelper_StringMatch[]
1675
    */
1676
    public static function findString(string $needle, string $haystack, bool $caseInsensitive=false)
1677
    {
1678
        if($needle === '') {
1679
            return array();
1680
        }
1681
        
1682
        $function = 'mb_strpos';
1683
        if($caseInsensitive) {
1684
            $function = 'mb_stripos';
1685
        }
1686
        
1687
        $pos = 0;
1688
        $positions = array();
1689
        $length = mb_strlen($needle);
1690
        
1691
        while( ($pos = $function($haystack, $needle, $pos)) !== false) 
1692
        {
1693
            $match = mb_substr($haystack, $pos, $length);
1694
            $positions[] = new ConvertHelper_StringMatch($pos, $match);
1695
            $pos += $length;
1696
        }
1697
        
1698
        return $positions;
1699
    }
1700
    
1701
   /**
1702
    * Like explode, but trims all entries, and removes 
1703
    * empty entries from the resulting array.
1704
    * 
1705
    * @param string $delimiter
1706
    * @param string $string
1707
    * @return string[]
1708
    */
1709
    public static function explodeTrim(string $delimiter, string $string) : array
1710
    {
1711
        if(empty($string) || empty($delimiter)) {
1712
            return array();
1713
        }
1714
        
1715
        $tokens = explode($delimiter, $string);
1716
        $tokens = array_map('trim', $tokens);
1717
        
1718
        $keep = array();
1719
        foreach($tokens as $token) {
1720
            if($token !== '') {
1721
                $keep[] = $token;
1722
            }
1723
        }
1724
        
1725
        return $keep;
1726
    }
1727
    
1728
    protected static $eolChars;
1729
1730
   /**
1731
    * Detects the most used end-of-line character in the subject string.
1732
    * 
1733
    * @param string $str The string to check.
1734
    * @return NULL|ConvertHelper_EOL The detected EOL instance, or NULL if none has been detected.
1735
    */
1736
    public static function detectEOLCharacter(string $subjectString) : ?ConvertHelper_EOL
1737
    {
1738
        if(empty($subjectString)) {
1739
            return null;
1740
        }
1741
        
1742
        if(!isset(self::$eolChars))
1743
        {
1744
            $cr = chr((int)hexdec('0d'));
1745
            $lf = chr((int)hexdec('0a'));
1746
            
1747
           self::$eolChars = array(
1748
               array(
1749
                   'char' => $cr.$lf,
1750
                   'type' => ConvertHelper_EOL::TYPE_CRLF,
1751
                   'description' => t('Carriage return followed by a line feed'),
1752
               ),
1753
               array(
1754
                   'char' => $lf.$cr,
1755
                   'type' => ConvertHelper_EOL::TYPE_LFCR,
1756
                   'description' => t('Line feed followed by a carriage return'),
1757
               ),
1758
               array(
1759
                  'char' => $lf,
1760
                  'type' => ConvertHelper_EOL::TYPE_LF,
1761
                  'description' => t('Line feed'),
1762
               ),
1763
               array(
1764
                  'char' => $cr,
1765
                  'type' => ConvertHelper_EOL::TYPE_CR,
1766
                  'description' => t('Carriage Return'),
1767
               ),
1768
            );
1769
        }
1770
        
1771
        $max = 0;
1772
        $results = array();
1773
        foreach(self::$eolChars as $def) 
1774
        {
1775
            $amount = substr_count($subjectString, $def['char']);
1776
            
1777
            if($amount > $max)
1778
            {
1779
                $max = $amount;
1780
                $results[] = $def;
1781
            }
1782
        }
1783
        
1784
        if(empty($results)) {
1785
            return null;
1786
        }
1787
        
1788
        return new ConvertHelper_EOL(
1789
            $results[0]['char'], 
1790
            $results[0]['type'],
1791
            $results[0]['description']
1792
        );
1793
    }
1794
1795
   /**
1796
    * Removes the specified keys from the target array,
1797
    * if they exist.
1798
    * 
1799
    * @param array $array
1800
    * @param array $keys
1801
    */
1802
    public static function arrayRemoveKeys(array &$array, array $keys) : void
1803
    {
1804
        foreach($keys as $key) 
1805
        {
1806
            if(array_key_exists($key, $array)) {
1807
                unset($array[$key]); 
1808
            }
1809
        }
1810
    }
1811
    
1812
   /**
1813
    * Checks if the specified variable is an integer or a string containing an integer.
1814
    * Accepts both positive and negative integers.
1815
    * 
1816
    * @param mixed $value
1817
    * @return bool
1818
    */
1819
    public static function isInteger($value) : bool
1820
    {
1821
        if(is_int($value)) {
1822
            return true;
1823
        }
1824
        
1825
        // booleans get converted to numbers, so they would
1826
        // actually match the regex.
1827
        if(is_bool($value)) {
1828
            return false;
1829
        }
1830
        
1831
        if(is_string($value) && $value !== '') {
1832
            return preg_match('/\A-?\d+\z/', $value) === 1;
1833
        }
1834
        
1835
        return false;    
1836
    }
1837
    
1838
   /**
1839
    * Converts an amount of seconds to a DateInterval object.
1840
    * 
1841
    * @param int $seconds
1842
    * @return \DateInterval
1843
    */
1844
    public static function seconds2interval(int $seconds) : \DateInterval
1845
    {
1846
        // The DateInterval::format() method does not recalculate carry 
1847
        // over points in days / seconds / months etc, so we calculate the
1848
        // actual interval using dates to ensure we get a fully populated
1849
        // interval object.
1850
        $d1 = new \DateTime();
1851
        $d2 = new \DateTime();
1852
        $d2->add(new \DateInterval('PT'.$seconds.'S'));
1853
        
1854
        return $d2->diff($d1);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $d2->diff($d1) could return the type false which is incompatible with the type-hinted return DateInterval. Consider adding an additional type-check to rule them out.
Loading history...
1855
    }
1856
}
1857