GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — 3.0.x ( 30be0d...35d3f6 )
by Nicolas
03:36
created

General::hash()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 9
rs 9.6666
1
<?php
2
/**
3
 * @package toolkit
4
 */
5
/**
6
 * General is a utility class that offers a number miscellaneous of
7
 * functions that are used throughout Symphony.
8
 */
9
10
class General
11
{
12
    /**
13
     * Convert any special characters into their entity equivalents. Since
14
     * Symphony 2.3, this function assumes UTF-8 and will not double
15
     * encode strings.
16
     *
17
     * @uses htmlspecialchars()
18
     * @param string $source
19
     *  a string to operate on.
20
     * @return string
21
     *  the encoded version of the string.
22
     */
23
    public static function sanitize($source)
24
    {
25
        $source = htmlspecialchars($source, ENT_COMPAT, 'UTF-8', false);
26
27
        return $source;
28
    }
29
30
    /**
31
     * Convert any special characters into their entity equivalents.
32
     * Contrary to `sanitize()`, this version does double encode existing entities.
33
     *
34
     * @since Symphony 2.7.5
35
     * @uses htmlspecialchars()
36
     * @param string $source
37
     *  a string to operate on.
38
     * @return string
39
     *  the fully encoded version of the string.
40
     */
41
    public static function sanitizeDouble($source)
42
    {
43
        $source = htmlspecialchars($source, ENT_COMPAT, 'UTF-8', true);
44
45
        return $source;
46
    }
47
48
    /**
49
     * Revert any html entities to their character equivalents.
50
     *
51
     * @param string $str
52
     *  a string to operate on
53
     * @return string
54
     *  the decoded version of the string
55
     */
56
    public static function reverse_sanitize($str)
57
    {
58
        return htmlspecialchars_decode($str, ENT_COMPAT);
59
    }
60
61
    /**
62
     * Validate a string against a set of regular expressions.
63
     *
64
     * @param array|string $string
65
     *  string to operate on
66
     * @param array|string $rule
67
     *  a single rule or array of rules
68
     * @return boolean
69
     *  false if any of the rules in $rule do not match any of the strings in
70
     *  `$string`, return true otherwise.
71
     */
72
    public static function validateString($string, $rule)
73
    {
74
        if (!is_array($rule) && ($rule == '' || $rule == null)) {
75
            return true;
76
        }
77
78
        if (!is_array($string) && ($string == '' || $rule == null)) {
79
            return true;
80
        }
81
82
        if (!is_array($rule)) {
83
            $rule = array($rule);
84
        }
85
86
        if (!is_array($string)) {
87
            $string = array($string);
88
        }
89
90
        foreach ($rule as $r) {
91
            foreach ($string as $s) {
92
                if (!preg_match($r, $s)) {
93
                    return false;
94
                }
95
            }
96
        }
97
        return true;
98
    }
99
100
    /**
101
     * Replace the tabs with spaces in the input string.
102
     *
103
     * @param string $string
104
     *  the string in which to replace the tabs with spaces.
105
     * @param integer $spaces (optional)
106
     *  the number of spaces to replace each tab with. This argument is optional
107
     *  with a default of 4.
108
     * @return string
109
     *  the resulting string.
110
     */
111
    public static function tabsToSpaces($string, $spaces = 4)
112
    {
113
        return str_replace("\t", str_pad(null, $spaces), $string);
114
    }
115
116
    /**
117
     * Checks an xml document for well-formedness.
118
     *
119
     * @param string $data
120
     *  filename, xml document as a string, or arbitrary string
121
     * @param pointer &$errors
0 ignored issues
show
Bug introduced by
The type pointer was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
122
     *  pointer to an array which will contain any validation errors
123
     * @param boolean $isFile (optional)
124
     *  if this is true, the method will attempt to read from a file, `$data`
125
     *  instead.
126
     * @param XsltProcess $xsltProcessor (optional)
127
     *  if set, the validation will be done using this XSLT processor rather
128
     *  than the built in XML parser. the default is null.
129
     * @param string $encoding (optional)
130
     *  if no XML header is expected, than this should be set to match the
131
     *  encoding of the XML
132
     * @return boolean
133
     *  true if there are no errors in validating the XML, false otherwise.
134
     */
135
    public static function validateXML($data, &$errors, $isFile = true, $xsltProcessor = null, $encoding = 'UTF-8')
136
    {
137
        $_data = ($isFile) ? file_get_contents($data) : $data;
138
        $_data = preg_replace('/<!DOCTYPE[-.:"\'\/\\w\\s]+>/', null, $_data);
139
140
        if (strpos($_data, '<?xml') === false) {
141
            $_data = '<?xml version="1.0" encoding="'.$encoding.'"?><rootelement>'.$_data.'</rootelement>';
142
        }
143
144
        if (is_object($xsltProcessor)) {
145
            $xsl = '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
146
147
            <xsl:template match="/"></xsl:template>
148
149
            </xsl:stylesheet>';
150
151
            $xsltProcessor->process($_data, $xsl);
152
153
            if ($xsltProcessor->isErrors()) {
154
                $errors = $xsltProcessor->getError(true);
155
                return false;
156
            }
157
        } else {
158
            $_parser = xml_parser_create();
159
            xml_parser_set_option($_parser, XML_OPTION_SKIP_WHITE, 0);
160
            xml_parser_set_option($_parser, XML_OPTION_CASE_FOLDING, 0);
161
162
            if (!xml_parse($_parser, $_data)) {
163
                $errors = array('error' => xml_get_error_code($_parser) . ': ' . xml_error_string(xml_get_error_code($_parser)),
164
                                'col' => xml_get_current_column_number($_parser),
165
                                'line' => (xml_get_current_line_number($_parser) - 2));
166
                return false;
167
            }
168
169
            xml_parser_free($_parser);
170
        }
171
172
        return true;
173
    }
174
175
    /**
176
     * Check that a string is a valid URL.
177
     *
178
     * @param string $url
179
     *  string to operate on
180
     * @return string
181
     *  a blank string or a valid URL
182
     */
183
    public static function validateURL($url = null)
184
    {
185
        $url = trim($url);
186
187
        if (is_null($url) || $url == '') {
188
            return $url;
189
        }
190
191
        if (!preg_match('#^http[s]?:\/\/#i', $url)) {
192
            $url = 'http://' . $url;
193
        }
194
195
        include TOOLKIT . '/util.validators.php';
0 ignored issues
show
Bug introduced by
The constant TOOLKIT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
196
197
        if (!preg_match($validators['URI'], $url)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $validators seems to be never defined.
Loading history...
198
            $url = '';
199
        }
200
201
        return $url;
202
    }
203
204
    /**
205
     * Strip any slashes from all array values.
206
     *
207
     * @param array &$arr
208
     *  Pointer to an array to operate on. Can be multi-dimensional.
209
     */
210
    public static function cleanArray(array &$arr)
211
    {
212
        foreach ($arr as $k => $v) {
213
            if (is_array($v)) {
214
                self::cleanArray($arr[$k]);
215
            } else {
216
                $arr[$k] = stripslashes($v);
217
            }
218
        }
219
    }
220
221
    /**
222
     * Flatten the input array. Any elements of the input array that are
223
     * themselves arrays will be removed and the contents of the removed array
224
     * inserted in its place. The keys for the inserted values will be the
225
     * concatenation of the keys in the original arrays in which it was embedded.
226
     * The elements of the path are separated by periods (.). For example,
227
     * given the following nested array structure:
228
     * `
229
     * array(1 =>
230
     *          array('key' => 'value'),
231
     *      2 =>
232
     *          array('key2' => 'value2', 'key3' => 'value3')
233
     *      )
234
     * `
235
     * will flatten to:
236
     * `array('1.key' => 'value', '2.key2' => 'value2', '2.key3' => 'value3')`
237
     *
238
     * @param array &$source
239
     *  The array to flatten, passed by reference
240
     * @param array &$output (optional)
241
     *  The array in which to store the flattened input, passed by reference.
242
     *  if this is not provided then a new array will be created.
243
     * @param string $path (optional)
244
     *  the current prefix of the keys to insert into the output array. this
245
     *  defaults to null.
246
     */
247
    public static function flattenArray(array &$source, &$output = null, $path = null)
248
    {
249
        if (is_null($output)) {
250
            $output = array();
251
        }
252
253
        foreach ($source as $key => $value) {
254
            if (is_int($key)) {
255
                $key = (string)($key + 1);
256
            }
257
258
            if (!is_null($path)) {
259
                $key = $path . '.' . (string)$key;
260
            }
261
262
            if (is_array($value)) {
263
                self::flattenArray($value, $output, $key);
264
            } else {
265
                $output[$key] = $value;
266
            }
267
        }
268
269
        $source = $output;
270
    }
271
272
    /**
273
     * Flatten the input array. Any elements of the input array that are
274
     * themselves arrays will be removed and the contents of the removed array
275
     * inserted in its place. The keys for the inserted values will be the
276
     * concatenation of the keys in the original arrays in which it was embedded.
277
     * The elements of the path are separated by colons (:). For example, given
278
     * the following nested array structure:
279
     * `
280
     * array(1 =>
281
     *          array('key' => 'value'),
282
     *      2 =>
283
     *          array('key2' => 'value2', 'key3' => 'value3')
284
     *      )
285
     * `
286
     * will flatten to:
287
     * `array('1:key' => 'value', '2:key2' => 'value2', '2:key3' => 'value3')`
288
     *
289
     *
290
     * @param array &$output
291
     *  The array in which to store the flattened input, passed by reference.
292
     * @param array &$source
293
     *  The array to flatten, passed by reference
294
     * @param string $path
295
     *  the current prefix of the keys to insert into the output array.
296
     */
297
    protected static function flattenArraySub(array &$output, array &$source, $path)
298
    {
299
        foreach ($source as $key => $value) {
300
            $key = $path . ':' . $key;
301
302
            if (is_array($value)) {
303
                self::flattenArraySub($output, $value, $key);
304
            } else {
305
                $output[$key] = $value;
306
            }
307
        }
308
    }
309
310
    /**
311
     * Given a string, this will clean it for use as a Symphony handle. Preserves multi-byte characters.
312
     *
313
     * @since Symphony 2.2.1
314
     * @param string $string
315
     *  String to be cleaned up
316
     * @param integer $max_length
317
     *  The maximum number of characters in the handle
318
     * @param string $delim
319
     *  All non-valid characters will be replaced with this
320
     * @param boolean $uriencode
321
     *  Force the resultant string to be uri encoded making it safe for URLs
322
     * @param array $additional_rule_set
323
     *  An array of REGEX patterns that should be applied to the `$string`. This
324
     *  occurs after the string has been trimmed and joined with the `$delim`
325
     * @return string
326
     *  Returns resultant handle
327
     */
328
    public static function createHandle($string, $max_length = 255, $delim = '-', $uriencode = false, $additional_rule_set = null)
329
    {
330
        $max_length = intval($max_length);
331
332
        // Strip out any tag
333
        $string = strip_tags($string);
334
335
        // Replace existing delimiters in the string with a space
336
        $string = str_replace($delim, ' ', $string);
337
338
        // Remove punctuation
339
        $string = preg_replace('/[\\.\'",!?]+/u', null, $string);
340
341
        // Remove weird characters
342
        $string = preg_replace('/[^\w-\s]+/u', null, $string);
343
344
        // Replace spaces (tab, newline etc) with the delimiter
345
        $string = preg_replace('/[\s]+/u', $delim, $string);
346
347
        // Allow for custom rules
348
        if (is_array($additional_rule_set) && !empty($additional_rule_set)) {
349
            foreach ($additional_rule_set as $rule => $replacement) {
350
                $string = preg_replace($rule, $replacement, $string);
351
            }
352
        }
353
354
        // Trim it
355
        if ($max_length > 0) {
356
            $string = General::limitWords($string, $max_length);
357
        }
358
359
        // Remove leading or trailing delim characters
360
        $string = trim($string, $delim);
361
362
        // Encode it for URI use
363
        if ($uriencode) {
364
            $string = urlencode($string);
365
        }
366
367
        // Make it lowercase
368
        $string = strtolower($string);
369
370
        return $string;
371
    }
372
373
    /**
374
     * Given a string, this will clean it for use as a filename. Preserves multi-byte characters.
375
     *
376
     * @since Symphony 2.2.1
377
     * @param string $string
378
     *  String to be cleaned up
379
     * @param string $delim
380
     *  All non-valid characters will be replaced with this
381
     * @return string
382
     *  Returns created filename
383
     */
384
    public static function createFilename($string, $delim = '-')
385
    {
386
        // Strip out any tag
387
        $string = strip_tags($string);
388
389
        // Find all legal characters
390
        $count = preg_match_all('/[\p{L}\w:;.,+=~]+/u', $string, $matches);
391
        if ($count <= 0 || $count == false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $count of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
392
            preg_match_all('/[\w:;.,+=~]+/', $string, $matches);
393
        }
394
395
        // Join only legal character with the $delim
396
        $string = implode($delim, $matches[0]);
397
398
        // Remove leading or trailing delim characters
399
        $string = trim($string, $delim);
400
401
        // Make it lowercase
402
        $string = strtolower($string);
403
404
        return $string;
405
    }
406
407
    /**
408
     * Computes the length of the string.
409
     * This function will attempt to use PHP's `mbstring` functions if they are available.
410
     * This function also forces utf-8 encoding.
411
     *
412
     * @since Symphony 2.5.0
413
     * @param string $str
414
     *  the string to operate on
415
     * @return int
416
     *  the string's length
417
     */
418
    public static function strlen($str)
419
    {
420
        if (function_exists('mb_strlen')) {
421
            return mb_strlen($str, 'utf-8');
422
        }
423
        return strlen($str);
424
    }
425
426
    /**
427
     * Finds position of the first occurrence of a string in a string.
428
     * This function will attempt to use PHP's `mbstring` functions if they are available.
429
     * This function also forces utf-8 encoding for mbstring.
430
     *
431
     * @since Symphony 2.7.0
432
     * @param string $haystack
433
     *  the string to look into
434
     * @param string $needle
435
     *  the string to look for
436
     * @param int $offset
437
     *  the search offset. If it is not specified, 0 is used.
438
     *  A negative offset counts from the end of the string.
439
     * @return int
440
     *  the numeric position of the first occurrence of needle in the haystack
441
     */
442
    public static function strpos($haystack, $needle, $offset = 0)
443
    {
444
        if (function_exists('mb_strpos')) {
445
            return mb_strpos($haystack, $needle, $offset, 'utf-8');
446
        }
447
        return strpos($haystack, $needle, $offset);
448
    }
449
450
    /**
451
     * Creates a sub string.
452
     * This function will attempt to use PHP's `mbstring` functions if they are available.
453
     * This function also forces utf-8 encoding.
454
     *
455
     * @since Symphony 2.5.0
456
     * @param string $str
457
     *  the string to operate on
458
     * @param int $start
459
     *  the starting offset
460
     * @param int $length
461
     *  the length of the substring
462
     * @return string
463
     *  the resulting substring
464
     */
465
    public static function substr($str, $start, $length = null)
466
    {
467
        if (function_exists('mb_substr')) {
468
            return mb_substr($str, $start, $length, 'utf-8');
469
        }
470
        if ($length === null) {
471
            return substr($str, $start);
472
        }
473
        return substr($str, $start, $length);
474
    }
475
476
    /**
477
     * Extract the first `$val` characters of the input string. If `$val`
478
     * is larger than the length of the input string then the original
479
     * input string is returned.
480
     *
481
     * @param string $str
482
     *  the string to operate on
483
     * @param integer $val
484
     *  the number to compare lengths with
485
     * @return string|boolean
486
     *  the resulting string or false on failure.
487
     */
488
    public static function substrmin($str, $val)
489
    {
490
        return self::substr($str, 0, min(self::strlen($str), $val));
491
    }
492
493
    /**
494
     * Extract the first `$val` characters of the input string. If
495
     * `$val` is larger than the length of the input string then
496
     * the original input string is returned
497
     *
498
     * @param string $str
499
     *  the string to operate on
500
     * @param integer $val
501
     *  the number to compare lengths with
502
     * @return string|boolean
503
     *  the resulting string or false on failure.
504
     */
505
    public static function substrmax($str, $val)
506
    {
507
        return self::substr($str, 0, max(self::strlen($str), $val));
508
    }
509
510
    /**
511
     * Extract the last `$num` characters from a string.
512
     *
513
     * @param string $str
514
     *  the string to extract the characters from.
515
     * @param integer $num
516
     *  the number of characters to extract.
517
     * @return string|boolean
518
     *  a string containing the last `$num` characters of the
519
     *  input string, or false on failure.
520
     */
521
    public static function right($str, $num)
522
    {
523
        $str = self::substr($str, self::strlen($str)-$num, $num);
524
        return $str;
525
    }
526
527
    /**
528
     * Extract the first `$num` characters from a string.
529
     *
530
     * @param string $str
531
     *  the string to extract the characters from.
532
     * @param integer $num
533
     *  the number of characters to extract.
534
     * @return string|boolean
535
     *  a string containing the last `$num` characters of the
536
     *  input string, or false on failure.
537
     */
538
    public static function left($str, $num)
539
    {
540
        $str = self::substr($str, 0, $num);
541
        return $str;
542
    }
543
544
    /**
545
     * Create all the directories as specified by the input path. If the current
546
     * directory already exists, this function will return true.
547
     *
548
     * @param string $path
549
     *  the path containing the directories to create.
550
     * @param string|integer $mode (optional)
551
     *  the permissions (in octal) of the directories to create. Defaults to 0755
552
     * @param boolean $silent (optional)
553
     *  true if an exception should be raised if an error occurs, false
554
     *  otherwise. this defaults to true.
555
     * @throws Exception
556
     * @return boolean
557
     */
558
    public static function realiseDirectory($path, $mode = 0755, $silent = true)
559
    {
560
        if (is_dir($path)) {
561
            return true;
562
        }
563
564
        try {
565
            $current_umask = umask(0);
566
            $success = @mkdir($path, intval($mode, 8), true);
567
            umask($current_umask);
568
569
            return $success;
570
        } catch (Exception $ex) {
571
            if ($silent === false) {
572
                throw new Exception(__('Unable to create path - %s', array($path)));
573
            }
574
575
            return false;
576
        }
577
    }
578
579
    /**
580
     * Recursively deletes all files and directories given a directory. This
581
     * function has two path. This function optionally takes a `$silent` parameter,
582
     * which when `false` will throw an `Exception` if there is an error deleting a file
583
     * or folder.
584
     *
585
     * @since Symphony 2.3
586
     * @param string $dir
587
     *  the path of the directory to delete
588
     * @param boolean $silent (optional)
589
     *  true if an exception should be raised if an error occurs, false
590
     *  otherwise. this defaults to true.
591
     * @throws Exception
592
     * @return boolean
593
     */
594
    public static function deleteDirectory($dir, $silent = true)
595
    {
596
        try {
597
            if (!@file_exists($dir)) {
598
                return true;
599
            }
600
601
            if (!@is_dir($dir)) {
602
                return @unlink($dir);
603
            }
604
605
            foreach (scandir($dir) as $item) {
606
                if ($item == '.' || $item == '..') {
607
                    continue;
608
                }
609
610
                if (!self::deleteDirectory($dir.DIRECTORY_SEPARATOR.$item)) {
611
                    return false;
612
                }
613
            }
614
615
            return rmdir($dir);
616
        } catch (Exception $ex) {
617
            if ($silent === false) {
618
                throw new Exception(__('Unable to remove - %s', array($dir)));
619
            }
620
621
            return false;
622
        }
623
    }
624
625
    /**
626
     * Search a multi-dimensional array for a value.
627
     *
628
     * @param mixed $needle
629
     *  the value to search for.
630
     * @param array $haystack
631
     *  the multi-dimensional array to search.
632
     * @return boolean
633
     *  true if `$needle` is found in `$haystack`.
634
     *  true if `$needle` == `$haystack`.
635
     *  true if `$needle` is found in any of the arrays contained within `$haystack`.
636
     *  false otherwise.
637
     */
638
    public static function in_array_multi($needle, $haystack)
639
    {
640
        if ($needle == $haystack) {
641
            return true;
642
        }
643
644
        if (is_array($haystack)) {
0 ignored issues
show
introduced by
The condition is_array($haystack) is always true.
Loading history...
645
            foreach ($haystack as $key => $val) {
646
                if (is_array($val)) {
647
                    if (self::in_array_multi($needle, $val)) {
648
                        return true;
649
                    }
650
                } elseif (!strcmp($needle, $key) || !strcmp($needle, $val)) {
651
                    return true;
652
                }
653
            }
654
        }
655
656
        return false;
657
    }
658
659
    /**
660
     * Search an array for multiple values.
661
     *
662
     * @param array $needles
663
     *  the values to search the `$haystack` for.
664
     * @param array $haystack
665
     *  the in which to search for the `$needles`
666
     * @return boolean
667
     *  true if any of the `$needles` are in `$haystack`,
668
     *  false otherwise.
669
     */
670
    public static function in_array_all($needles, $haystack)
671
    {
672
        foreach ($needles as $n) {
673
            if (!in_array($n, $haystack)) {
674
                return false;
675
            }
676
        }
677
678
        return true;
679
    }
680
681
    /**
682
     * Transform a multi-dimensional array to a flat array. The input array
683
     * is expected to conform to the structure of the `$_FILES` variable.
684
     *
685
     * @param array $filedata
686
     *  the raw `$_FILES` data structured array
687
     * @return array
688
     *  the flattened array.
689
     */
690
    public static function processFilePostData($filedata)
691
    {
692
        $result = array();
693
694
        foreach ($filedata as $key => $data) {
695
            foreach ($data as $handle => $value) {
696
                if (is_array($value)) {
697
                    foreach ($value as $index => $pair) {
698
                        if (!is_array($result[$handle][$index])) {
699
                            $result[$handle][$index] = array();
700
                        }
701
702
                        if (!is_array($pair)) {
703
                            $result[$handle][$index][$key] = $pair;
704
                        } else {
705
                            $result[$handle][$index][array_pop(array_keys($pair))][$key] = array_pop(array_values($pair));
0 ignored issues
show
Bug introduced by
array_values($pair) cannot be passed to array_pop() as the parameter $array expects a reference. ( Ignorable by Annotation )

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

705
                            $result[$handle][$index][array_pop(array_keys($pair))][$key] = array_pop(/** @scrutinizer ignore-type */ array_values($pair));
Loading history...
Bug introduced by
array_keys($pair) cannot be passed to array_pop() as the parameter $array expects a reference. ( Ignorable by Annotation )

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

705
                            $result[$handle][$index][array_pop(/** @scrutinizer ignore-type */ array_keys($pair))][$key] = array_pop(array_values($pair));
Loading history...
706
                        }
707
                    }
708
                } else {
709
                    $result[$handle][$key] = $value;
710
                }
711
            }
712
        }
713
714
        return $result;
715
    }
716
717
    /**
718
     * Merge `$_POST` with `$_FILES` to produce a flat array of the contents
719
     * of both. If there is no merge_file_post_data function defined then
720
     * such a function is created. This is necessary to overcome PHP's ability
721
     * to handle forms. This overcomes PHP's convoluted `$_FILES` structure
722
     * to make it simpler to access `multi-part/formdata`.
723
     *
724
     * @return array
725
     *  a flat array containing the flattened contents of both `$_POST` and
726
     *  `$_FILES`.
727
     */
728
    public static function getPostData()
729
    {
730
        if (!function_exists('merge_file_post_data')) {
731
            function merge_file_post_data($type, array $file, &$post)
732
            {
733
                foreach ($file as $key => $value) {
734
                    if (!isset($post[$key])) {
735
                        $post[$key] = array();
736
                    }
737
738
                    if (is_array($value)) {
739
                        merge_file_post_data($type, $value, $post[$key]);
740
                    } else {
741
                        $post[$key][$type] = $value;
742
                    }
743
                }
744
            }
745
        }
746
747
        $files = array(
748
            'name'      => array(),
749
            'type'      => array(),
750
            'tmp_name'  => array(),
751
            'error'     => array(),
752
            'size'      => array()
753
        );
754
        $post = $_POST;
755
756
        if (is_array($_FILES) && !empty($_FILES)) {
757
            foreach ($_FILES as $key_a => $data_a) {
758
                if (!is_array($data_a)) {
759
                    continue;
760
                }
761
762
                foreach ($data_a as $key_b => $data_b) {
763
                    $files[$key_b][$key_a] = $data_b;
764
                }
765
            }
766
        }
767
768
        foreach ($files as $type => $data) {
769
            merge_file_post_data($type, $data, $post);
770
        }
771
772
        return $post;
773
    }
774
775
    /**
776
     * Find the next available index in an array. Works best with numeric keys.
777
     * The next available index is the minimum integer such that the array does
778
     * not have a mapping for that index. Uses the increment operator on the
779
     * index type of the input array, whatever that may do.
780
     *
781
     * @param array $array
782
     *  the array to find the next index for.
783
     * @param mixed $seed (optional)
784
     *  the object with which the search for an empty index is initialized. this
785
     *  defaults to null.
786
     * @return integer
787
     *  the minimum empty index into the input array.
788
     */
789
    public static function array_find_available_index($array, $seed = null)
790
    {
791
        if (!is_null($seed)) {
792
            $index = $seed;
793
        } else {
794
            $keys = array_keys($array);
795
            sort($keys);
796
            $index = array_pop($keys);
797
        }
798
799
        if (isset($array[$index])) {
800
            do {
801
                $index++;
802
            } while (isset($array[$index]));
803
        }
804
805
        return $index;
806
    }
807
808
    /**
809
     * Filter the duplicate values from an array into a new array, optionally
810
     * ignoring the case of the values (assuming they are strings?). A new array
811
     * is returned, the input array is left unchanged.
812
     *
813
     * @param array $array
814
     *  the array to filter.
815
     * @param boolean $ignore_case
816
     *  true if the case of the values in the array should be ignored, false otherwise.
817
     * @return array
818
     *  a new array containing only the unique elements of the input array.
819
     */
820
    public static function array_remove_duplicates(array $array, $ignore_case = false)
821
    {
822
        return ($ignore_case === true ? self::array_iunique($array) : array_unique($array));
823
    }
824
825
    /**
826
     * Test whether a value is in an array based on string comparison, ignoring
827
     * the case of the values.
828
     *
829
     * @param mixed $needle
830
     *  the object to search the array for.
831
     * @param array $haystack
832
     *  the array to search for the `$needle`.
833
     * @return boolean
834
     *  true if the `$needle` is in the `$haystack`, false otherwise.
835
     */
836
    public static function in_iarray($needle, array $haystack)
837
    {
838
        foreach ($haystack as $key => $value) {
839
            if (strcasecmp($value, $needle) == 0) {
840
                return true;
841
            }
842
        }
843
        return false;
844
    }
845
846
    /**
847
     * Filter the input array for duplicates, treating each element in the array
848
     * as a string and comparing them using a case insensitive comparison function.
849
     *
850
     * @param array $array
851
     *  the array to filter.
852
     * @return array
853
     *  a new array containing only the unique elements of the input array.
854
     */
855
    public static function array_iunique(array $array)
856
    {
857
        $tmp = array();
858
859
        foreach ($array as $key => $value) {
860
            if (!self::in_iarray($value, $tmp)) {
861
                $tmp[$key] = $value;
862
            }
863
        }
864
865
        return $tmp;
866
    }
867
868
    /**
869
     * Function recursively apply a function to an array's values.
870
     * This will not touch the keys, just the values.
871
     *
872
     * @since Symphony 2.2
873
     * @param string $function
874
     * @param array $array
875
     * @return array
876
     *  a new array with all the values passed through the given `$function`
877
     */
878
    public static function array_map_recursive($function, array $array)
879
    {
880
        $tmp = array();
881
882
        foreach ($array as $key => $value) {
883
            if (is_array($value)) {
884
                $tmp[$key] = self::array_map_recursive($function, $value);
885
            } else {
886
                $tmp[$key] = call_user_func($function, $value);
887
            }
888
        }
889
890
        return $tmp;
891
    }
892
893
    /**
894
     * Keyed version of php's array_map function.
895
     * The callback's signature is:
896
     *  `function ($key, $value, $array)`
897
     *
898
     * @since Symphony 3.0.0
899
     * @param string $function
900
     * @param array $array
901
     * @return array
902
     *  a new array with all the values passed through the given `$function`
903
     */
904
    public static function array_map($function, array $array)
905
    {
906
        return array_map(function ($key) use ($function, $array) {
907
            return $function($key, $array[$key], $array);
908
        }, array_keys($array));
909
    }
910
911
    /**
912
     * Convert an array into an XML fragment and append it to an existing
913
     * XML element. Any arrays contained as elements in the input array will
914
     * also be recursively formatted and appended to the input XML fragment.
915
     * The input XML element will be modified as a result of calling this.
916
     *
917
     * @param XMLElement $parent
918
     *  the XML element to append the formatted array data to.
919
     * @param array $data
920
     *  the array to format and append to the XML fragment.
921
     * @param boolean $validate
922
     *  true if the formatted array data should be validated as it is
923
     *  constructed, false otherwise.
924
     */
925
    public static function array_to_xml(XMLElement $parent, array $data, $validate = false)
926
    {
927
        foreach ($data as $element_name => $value) {
928
            if (!is_numeric($value) && empty($value)) {
929
                continue;
930
            }
931
932
            if (is_int($element_name)) {
933
                $child = new XMLElement('item');
934
                $child->setAttribute('index', $element_name + 1);
935
            } else {
936
                $child = new XMLElement($element_name, null, array(), true);
937
            }
938
939
            if (is_array($value) || is_object($value)) {
940
                self::array_to_xml($child, (array)$value);
941
942
                if ($child->getNumberOfChildren() == 0) {
943
                    continue;
944
                }
945
            } elseif ($validate === true && !self::validateXML(self::sanitize($value), $errors, false, new XSLTProcess)) {
946
                continue;
947
            } else {
948
                $child->setValue(self::sanitize($value));
949
            }
950
951
            $parent->appendChild($child);
952
        }
953
    }
954
955
    /**
956
     * Create a file at the input path with the (optional) input permissions
957
     * with the input content. This function will ignore errors in opening,
958
     * writing, closing and changing the permissions of the resulting file.
959
     * If opening or writing the file fail then this will return false.
960
     * This method calls `General::checkFileWritable()` which properly checks
961
     * for permissions.
962
     *
963
     * @uses General::checkFileWritable()
964
     * @param string $file
965
     *  the path of the file to write.
966
     * @param mixed $data
967
     *  the data to write to the file.
968
     * @param integer|string $perm (optional)
969
     *  the permissions as an octal number to set set on the resulting file.
970
     *  this defaults to 0644 (if omitted or set to null)
971
     * @param string $mode (optional)
972
     *  the mode that the file should be opened with, defaults to 'w'. See modes
973
     *  at http://php.net/manual/en/function.fopen.php
974
     * @param boolean $trim (optional)
975
     *  removes tripple linebreaks
976
     * @return boolean
977
     *  true if the file is successfully opened, written to, closed and has the
978
     *  required permissions set. false, otherwise.
979
     */
980
    public static function writeFile($file, $data, $perm = 0644, $mode = 'w', $trim = false)
981
    {
982
        if (static::checkFileWritable($file) === false) {
983
            return false;
984
        }
985
986
        if (!$handle = fopen($file, $mode)) {
987
            return false;
988
        }
989
990
        if ($trim === true) {
991
            $data = preg_replace("/(" . PHP_EOL . "([ |\t]+)?){2,}" . PHP_EOL . "/", PHP_EOL . PHP_EOL, trim($data));
992
        }
993
994
        if (fwrite($handle, $data, strlen($data)) === false) {
995
            return false;
996
        }
997
998
        fclose($handle);
999
1000
        try {
1001
            if (is_null($perm)) {
0 ignored issues
show
introduced by
The condition is_null($perm) is always false.
Loading history...
1002
                $perm = 0644;
1003
            }
1004
1005
            @chmod($file, intval($perm, 8));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

1005
            /** @scrutinizer ignore-unhandled */ @chmod($file, intval($perm, 8));

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1006
        } catch (Exception $ex) {
1007
            // If we can't chmod the file, this is probably because our host is
1008
            // running PHP with a different user to that of the file. Although we
1009
            // can delete the file, create a new one and then chmod it, we run the
1010
            // risk of losing the file as we aren't saving it anywhere. For the immediate
1011
            // future, atomic saving isn't needed by Symphony and it's recommended that
1012
            // if your extension require this logic, it uses it's own function rather
1013
            // than this 'General' one.
1014
            return true;
1015
        }
1016
1017
        return true;
1018
    }
1019
1020
    /**
1021
     * Checks that the file is readable.
1022
     * It first checks to see if the $file path exists
1023
     * and if it does, checks that it is readable.
1024
     *
1025
     * @uses clearstatcache()
1026
     * @since Symphony 2.7.0
1027
     * @param string $file
1028
     *  The path of the file
1029
     * @return boolean
1030
     */
1031
    public static function checkFileReadable($file)
1032
    {
1033
        clearstatcache();
1034
        // Reading a file requires that is exists and can be read
1035
        return @file_exists($file) && @is_readable($file);
1036
    }
1037
1038
    /**
1039
     * Checks that the file is writable.
1040
     * It first checks to see if the $file path exists
1041
     * and if it does, checks that is it writable. If the file
1042
     * does not exits, it checks that the directory exists and if it does,
1043
     * checks that it is writable.
1044
     *
1045
     * @uses clearstatcache()
1046
     * @since Symphony 2.7.0
1047
     * @param string $file
1048
     *  The path of the file
1049
     * @return boolean
1050
     */
1051
    public static function checkFileWritable($file)
1052
    {
1053
        clearstatcache();
1054
        if (@file_exists($file)) {
1055
            // Writing to an existing file does not require write
1056
            // permissions on the directory.
1057
            return @is_writable($file);
1058
        }
1059
        $dir = dirname($file);
1060
        // Creating a file requires write permissions on the directory.
1061
        return @file_exists($dir) && @is_writable($dir);
1062
    }
1063
1064
    /**
1065
     * Checks that the file is deletable.
1066
     * It first checks to see if the $file path exists
1067
     * and if it does, checks that is it writable.
1068
     *
1069
     * @uses clearstatcache()
1070
     * @since Symphony 2.7.0
1071
     * @param string $file
1072
     *  The path of the file
1073
     * @return boolean
1074
     */
1075
    public static function checkFileDeletable($file)
1076
    {
1077
        clearstatcache();
1078
        $dir = dirname($file);
1079
        // Deleting a file requires write permissions on the directory.
1080
        // It does not require write permissions on the file
1081
        return @file_exists($dir) && @is_writable($dir);
1082
    }
1083
1084
    /**
1085
     * Delete a file at a given path, silently ignoring errors depending
1086
     * on the value of the input variable $silent.
1087
     *
1088
     * @uses General::checkFileDeletable()
1089
     * @param string $file
1090
     *  the path of the file to delete
1091
     * @param boolean $silent (optional)
1092
     *  true if an exception should be raised if an error occurs, false
1093
     *  otherwise. this defaults to true.
1094
     * @throws Exception
1095
     * @return boolean
1096
     *  true if the file is successfully unlinked, if the unlink fails and
1097
     *  silent is set to true then an exception is thrown. if the unlink
1098
     *  fails and silent is set to false then this returns false.
1099
     */
1100
    public static function deleteFile($file, $silent = true)
1101
    {
1102
        try {
1103
            if (static::checkFileDeletable($file) === false) {
1104
                throw new Exception(__('Denied by permission'));
1105
            }
1106
            if (!@file_exists($file)) {
1107
                return true;
1108
            }
1109
            return @unlink($file);
1110
        } catch (Exception $ex) {
1111
            if ($silent === false) {
1112
                throw new Exception(__('Unable to remove file - %s', array($file)), 0, $ex);
1113
            }
1114
1115
            return false;
1116
        }
1117
    }
1118
1119
    /**
1120
     * Extract the file extension from the input file path.
1121
     *
1122
     * @param string $file
1123
     *  the path of the file to extract the extension of.
1124
     * @return array
1125
     *  an array with a single key 'extension' and a value of the extension
1126
     *  of the input path.
1127
     */
1128
    public static function getExtension($file)
1129
    {
1130
        return pathinfo($file, PATHINFO_EXTENSION);
1131
    }
1132
1133
    /**
1134
     * Gets mime type of a file.
1135
     *
1136
     * For email attachments, the mime type is very important.
1137
     * Uses the PHP 5.3 function `finfo_open` when available, otherwise falls
1138
     * back to using a mapping of known of common mimetypes. If no matches
1139
     * are found `application/octet-stream` will be returned.
1140
     *
1141
     * @author Michael Eichelsdoerfer
1142
     * @author Huib Keemink
1143
     * @param string $file
1144
     * @return string|boolean
1145
     *  the mime type of the file, or false is none found
1146
     */
1147
    public function getMimeType($file)
1148
    {
1149
        if (!empty($file)) {
1150
            // in PHP 5.3 we can use 'finfo'
1151
            if (PHP_VERSION_ID >= 50300 && function_exists('finfo_open')) {
1152
                $finfo = finfo_open(FILEINFO_MIME_TYPE);
1153
                $mime_type = finfo_file($finfo, $file);
1154
                finfo_close($finfo);
1155
            } else {
1156
                // A few mimetypes to "guess" using the file extension.
1157
                $mimetypes = array(
1158
                    'txt'   => 'text/plain',
1159
                    'csv'   => 'text/csv',
1160
                    'pdf'   => 'application/pdf',
1161
                    'doc'   => 'application/msword',
1162
                    'docx'  => 'application/msword',
1163
                    'xls'   => 'application/vnd.ms-excel',
1164
                    'ppt'   => 'application/vnd.ms-powerpoint',
1165
                    'eps'   => 'application/postscript',
1166
                    'zip'   => 'application/zip',
1167
                    'gif'   => 'image/gif',
1168
                    'jpg'   => 'image/jpeg',
1169
                    'jpeg'  => 'image/jpeg',
1170
                    'png'   => 'image/png',
1171
                    'mp3'   => 'audio/mpeg',
1172
                    'mp4a'  => 'audio/mp4',
1173
                    'aac'   => 'audio/x-aac',
1174
                    'aif'   => 'audio/x-aiff',
1175
                    'aiff'  => 'audio/x-aiff',
1176
                    'wav'   => 'audio/x-wav',
1177
                    'wma'   => 'audio/x-ms-wma',
1178
                    'mpeg'  => 'video/mpeg',
1179
                    'mpg'   => 'video/mpeg',
1180
                    'mp4'   => 'video/mp4',
1181
                    'mov'   => 'video/quicktime',
1182
                    'avi'   => 'video/x-msvideo',
1183
                    'wmv'   => 'video/x-ms-wmv',
1184
                );
1185
1186
                $extension = substr(strrchr($file, '.'), 1);
1187
1188
                if ($mimetypes[strtolower($extension)] !== null) {
0 ignored issues
show
introduced by
The condition $mimetypes[strtolower($extension)] !== null is always true.
Loading history...
1189
                    $mime_type = $mimetypes[$extension];
1190
                } else {
1191
                    $mime_type = 'application/octet-stream';
1192
                }
1193
            }
1194
1195
            return $mime_type;
1196
        }
1197
        return false;
1198
    }
1199
1200
    /**
1201
     * Construct a multi-dimensional array that reflects the directory
1202
     * structure of a given path.
1203
     *
1204
     * @param string $dir (optional)
1205
     *  the path of the directory to construct the multi-dimensional array
1206
     *  for. this defaults to '.'.
1207
     * @param string $filter (optional)
1208
     *  A regular expression to filter the directories. This is positive filter, ie.
1209
     * if the filter matches, the directory is included. Defaults to null.
1210
     * @param boolean $recurse (optional)
1211
     *  true if sub-directories should be traversed and reflected in the
1212
     *  resulting array, false otherwise.
1213
     * @param mixed $strip_root (optional)
1214
     *  If null, the full path to the file will be returned, otherwise the value
1215
     *  of `strip_root` will be removed from the file path.
1216
     * @param array $exclude (optional)
1217
     *  ignore directories listed in this array. this defaults to an empty array.
1218
     * @param boolean $ignore_hidden (optional)
1219
     *  ignore hidden directory (i.e.directories that begin with a period). this defaults
1220
     *  to true.
1221
     * @return null|array
1222
     *  return the array structure reflecting the input directory or null if
1223
     * the input directory is not actually a directory.
1224
     */
1225
    public static function listDirStructure($dir = '.', $filter = null, $recurse = true, $strip_root = null, $exclude = array(), $ignore_hidden = true)
1226
    {
1227
        if (!is_dir($dir)) {
1228
            return null;
1229
        }
1230
1231
        $files = array();
1232
1233
        foreach (scandir($dir) as $file) {
1234
            if (
1235
                ($file == '.' || $file == '..')
1236
                || ($ignore_hidden && $file{0} == '.')
1237
                || !is_dir("$dir/$file")
1238
                || in_array($file, $exclude)
1239
                || in_array("$dir/$file", $exclude)
1240
            ) {
1241
                continue;
1242
            }
1243
1244
            if (!is_null($filter)) {
1245
                if (!preg_match($filter, $file)) {
1246
                    continue;
1247
                }
1248
            }
1249
1250
            $files[] = rtrim(str_replace($strip_root, '', $dir), '/') ."/$file/";
1251
1252
            if ($recurse) {
1253
                $files = @array_merge($files, self::listDirStructure("$dir/$file", $filter, $recurse, $strip_root, $exclude, $ignore_hidden));
1254
            }
1255
        }
1256
1257
        return $files;
1258
    }
1259
1260
    /**
1261
     * Construct a multi-dimensional array that reflects the directory
1262
     * structure of a given path grouped into directory and file keys
1263
     * matching any input constraints.
1264
     *
1265
     * @param string $dir (optional)
1266
     *  the path of the directory to construct the multi-dimensional array
1267
     *  for. this defaults to '.'.
1268
     * @param array|string $filters (optional)
1269
     *  either a regular expression to filter the files by or an array of
1270
     *  files to include.
1271
     * @param boolean $recurse (optional)
1272
     *  true if sub-directories should be traversed and reflected in the
1273
     *  resulting array, false otherwise.
1274
     * @param string $sort (optional)
1275
     *  'asc' if the resulting filelist array should be sorted, anything else otherwise.
1276
     *  this defaults to 'asc'.
1277
     * @param mixed $strip_root (optional)
1278
     *  If null, the full path to the file will be returned, otherwise the value
1279
     *  of `strip_root` will be removed from the file path.
1280
     * @param array $exclude (optional)
1281
     *  ignore files listed in this array. this defaults to an empty array.
1282
     * @param boolean $ignore_hidden (optional)
1283
     *  ignore hidden files (i.e. files that begin with a period). this defaults
1284
     *  to true.
1285
     * @return null|array
1286
     *  return the array structure reflecting the input directory or null if
1287
     * the input directory is not actually a directory.
1288
     */
1289
    public static function listStructure($dir = ".", $filters = array(), $recurse = true, $sort = "asc", $strip_root = null, $exclude = array(), $ignore_hidden = true)
1290
    {
1291
        if (!is_dir($dir)) {
1292
            return null;
1293
        }
1294
1295
        // Check to see if $filters is a string containing a regex, or an array of file types
1296
        if (is_array($filters) && !empty($filters)) {
1297
            $filter_type = 'file';
1298
        } elseif (is_string($filters)) {
1299
            $filter_type = 'regex';
1300
        } else {
1301
            $filter_type = null;
1302
        }
1303
        $files = array();
1304
1305
        $prefix = str_replace($strip_root, '', $dir);
1306
1307
        if ($prefix !== "" && substr($prefix, -1) !== "/") {
1308
            $prefix .= "/";
1309
        }
1310
1311
        $files['dirlist'] = array();
1312
        $files['filelist'] = array();
1313
1314
        foreach (scandir($dir) as $file) {
1315
            if (
1316
                ($file == '.' || $file === '..')
1317
                || ($ignore_hidden && $file{0} === '.')
1318
                || in_array($file, $exclude)
1319
                || in_array("$dir/$file", $exclude)
1320
            ) {
1321
                continue;
1322
            }
1323
1324
            $dir = rtrim($dir, '/');
1325
1326
            if (is_dir("$dir/$file")) {
1327
                if ($recurse) {
1328
                    $files["$prefix$file/"] = self::listStructure("$dir/$file", $filters, $recurse, $sort, $strip_root, $exclude, $ignore_hidden);
1329
                }
1330
1331
                $files['dirlist'][] = "$prefix$file/";
1332
            } elseif ($filter_type === 'regex') {
1333
                if (preg_match($filters, $file)) {
1334
                    $files['filelist'][] = "$prefix$file";
1335
                }
1336
            } elseif ($filter_type === 'file') {
1337
                if (in_array(self::getExtension($file), $filters)) {
1338
                    $files['filelist'][] = "$prefix$file";
1339
                }
1340
            } elseif (is_null($filter_type)) {
1341
                $files['filelist'][] = "$prefix$file";
1342
            }
1343
        }
1344
1345
        if (is_array($files['filelist'])) {
1346
            ($sort == 'desc') ? rsort($files['filelist']) : sort($files['filelist']);
1347
        }
1348
1349
        return $files;
1350
    }
1351
1352
    /**
1353
     * Count the number of words in a string. Words are delimited by "spaces".
1354
     * The characters included in the set of "spaces" are:
1355
     *  '&#x2002;', '&#x2003;', '&#x2004;', '&#x2005;',
1356
     *  '&#x2006;', '&#x2007;', '&#x2009;', '&#x200a;',
1357
     *  '&#x200b;', '&#x2002f;', '&#x205f;'
1358
     * Any html/xml tags are first removed by strip_tags() and any included html
1359
     * entities are decoded. The resulting string is then split by the above set
1360
     * of spaces and the resulting size of the resulting array returned.
1361
     *
1362
     * @param string $string
1363
     *  the string from which to count the contained words.
1364
     * @return integer
1365
     *  the number of words contained in the input string.
1366
     */
1367
    public static function countWords($string)
1368
    {
1369
        $string = strip_tags($string);
1370
1371
        // Strip spaces:
1372
        $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');
1373
        $spaces = array(
1374
            '&#x2002;', '&#x2003;', '&#x2004;', '&#x2005;',
1375
            '&#x2006;', '&#x2007;', '&#x2009;', '&#x200a;',
1376
            '&#x200b;', '&#x2002f;', '&#x205f;'
1377
        );
1378
1379
        foreach ($spaces as &$space) {
1380
            $space = html_entity_decode($space, ENT_NOQUOTES, 'UTF-8');
1381
        }
1382
1383
        $string = str_replace($spaces, ' ', $string);
1384
        $string = preg_replace('/[^\w\s]/i', '', $string);
1385
1386
        return str_word_count($string);
1387
    }
1388
1389
    /**
1390
     * Truncate a string to a given length, respecting word boundaries. The returned
1391
     * string will always be less than `$maxChars`. Newlines, HTML elements and
1392
     * leading or trailing spaces are removed from the string.
1393
     *
1394
     * @param string $string
1395
     *  the string to truncate.
1396
     * @param integer $maxChars (optional)
1397
     *  the maximum length of the string to truncate the input string to. this
1398
     *  defaults to 200 characters.
1399
     * @param boolean $appendHellip (optional)
1400
     *  true if the ellipses should be appended to the result in circumstances
1401
     *  where the result is shorter than the input string. false otherwise. this
1402
     *  defaults to false.
1403
     * @return null|string
1404
     *  if the resulting string contains only spaces then null is returned. otherwise
1405
     *  a string that satisfies the input constraints.
1406
     */
1407
    public static function limitWords($string, $maxChars = 200, $appendHellip = false)
1408
    {
1409
        if ($appendHellip) {
1410
            $maxChars -= 1;
1411
        }
1412
1413
        $string = trim(strip_tags(nl2br($string)));
1414
        $original_length = strlen($string);
1415
1416
        if ($original_length == 0) {
1417
            return null;
1418
        } elseif ($original_length <= $maxChars) {
1419
            return $string;
1420
        }
1421
1422
        // Compute the negative offset
1423
        $offset = $maxChars - $original_length;
1424
        // Find the first word break char before the maxChars limit is hit.
1425
        $word_break = array_filter(array_map(function ($wb) use ($string, $offset) {
1426
            return strrpos($string, $wb, $offset);
1427
        }, array(' ', '-', ',', '.', '!', '?', PHP_EOL)));
1428
1429
        // If no word break is found
1430
        if (empty($word_break)) {
1431
            $last_word_break = $maxChars;
1432
        } else {
1433
            $last_word_break = max($word_break);
1434
        }
1435
1436
        // Create the sub string
1437
        $result = substr($string, 0, $last_word_break);
1438
1439
        if ($appendHellip) {
1440
            $result .= "&#8230;";
1441
        }
1442
1443
        return $result;
1444
    }
1445
1446
    /**
1447
     * Move a file from the source path to the destination path and name and
1448
     * set its permissions to the input permissions. This will ignore errors
1449
     * in the `is_uploaded_file()`, `move_uploaded_file()` and `chmod()` functions.
1450
     *
1451
     * @uses General::checkFileWritable()
1452
     * @param string $dest_path
1453
     *  the file path to which the source file is to be moved.
1454
     * @param string $dest_name
1455
     *  the file name within the file path to which the source file is to be moved.
1456
     * @param string $tmp_name
1457
     *  the full path name of the source file to move.
1458
     * @param integer|string $perm (optional)
1459
     *  the permissions to apply to the moved file. this defaults to 0644 @since
1460
     *  Symphony 2.7.0. It was 0777 in 2.6.x and less.
1461
     * @return boolean
1462
     *  true if the file was moved and its permissions set as required. false otherwise.
1463
     */
1464
    public static function uploadFile($dest_path, $dest_name, $tmp_name, $perm = 0644)
1465
    {
1466
        // Upload the file
1467
        if (@is_uploaded_file($tmp_name)) {
1468
            $dest_path = rtrim($dest_path, '/') . '/';
1469
            $dest = $dest_path . $dest_name;
1470
1471
            // Check that destination is writable
1472
            if (!static::checkFileWritable($dest)) {
1473
                return false;
1474
            }
1475
            // Try place the file in the correction location
1476
            if (@move_uploaded_file($tmp_name, $dest)) {
1477
                if (is_null($perm)) {
0 ignored issues
show
introduced by
The condition is_null($perm) is always false.
Loading history...
1478
                    $perm = 0644;
1479
                }
1480
                @chmod($dest, intval($perm, 8));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

1480
                /** @scrutinizer ignore-unhandled */ @chmod($dest, intval($perm, 8));

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1481
                return true;
1482
            }
1483
        }
1484
1485
        // Could not move the file
1486
        return false;
1487
    }
1488
1489
    /**
1490
     * Format a number of bytes in human readable format. This will append MB as
1491
     * appropriate for values greater than 1,024*1,024, KB for values between
1492
     * 1,024 and 1,024*1,024-1 and bytes for values between 0 and 1,024.
1493
     *
1494
     * @param integer $file_size
1495
     *  the number to format.
1496
     * @return string
1497
     *  the formatted number.
1498
     */
1499
    public static function formatFilesize($file_size)
1500
    {
1501
        $file_size = intval($file_size);
1502
1503
        if ($file_size >= (1024 * 1024)) {
1504
            $file_size = number_format($file_size * (1 / (1024 * 1024)), 2) . ' MB';
1505
        } elseif ($file_size >= 1024) {
1506
            $file_size = intval($file_size * (1/1024)) . ' KB';
1507
        } else {
1508
            $file_size = intval($file_size) . ' bytes';
1509
        }
1510
1511
        return $file_size;
1512
    }
1513
1514
    /**
1515
     * Gets the number of bytes from 'human readable' size value. Supports
1516
     * the output of `General::formatFilesize` as well as reading values
1517
     * from the PHP configuration. eg. 1 MB or 1M
1518
     *
1519
     * @since Symphony 2.5.2
1520
     * @param string $file_size
1521
     * @return integer
1522
     */
1523
    public static function convertHumanFileSizeToBytes($file_size)
1524
    {
1525
        $file_size = str_replace(
1526
            array(' MB', ' KB', ' bytes'),
1527
            array('M', 'K', 'B'),
1528
            trim($file_size)
1529
        );
1530
1531
        $last = strtolower($file_size[strlen($file_size)-1]);
1532
1533
        $file_size = (int) $file_size;
1534
1535
        switch ($last) {
1536
            case 'g':
1537
                $file_size *= 1024;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
1538
            case 'm':
1539
                $file_size *= 1024;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
1540
            case 'k':
1541
                $file_size *= 1024;
1542
        }
1543
1544
        return $file_size;
1545
    }
1546
1547
    /**
1548
     * Construct an XML fragment that reflects the structure of the input timestamp.
1549
     *
1550
     * @param integer $timestamp
1551
     *  the timestamp to construct the XML element from.
1552
     * @param string $element (optional)
1553
     *  the name of the element to append to the namespace of the constructed XML.
1554
     *  this defaults to "date".
1555
     * @param string $date_format (optional)
1556
     *  the format to apply to the date, defaults to `Y-m-d`.
1557
     *  if empty, uses DateTimeObj settings.
1558
     * @param string $time_format (optional)
1559
     *  the format to apply to the date, defaults to `H:i`.
1560
     *  if empty, uses DateTimeObj settings.
1561
     * @param string $namespace (optional)
1562
     *  the namespace in which the resulting XML entity will reside. this defaults
1563
     *  to null.
1564
     * @return boolean|XMLElement
1565
     *  false if there is no XMLElement class on the system, the constructed XML element
1566
     *  otherwise.
1567
     */
1568
    public static function createXMLDateObject($timestamp, $element = 'date', $date_format = 'Y-m-d', $time_format = 'H:i', $namespace = null)
1569
    {
1570
        if (!class_exists('XMLElement')) {
1571
            return false;
1572
        }
1573
1574
        if (empty($date_format)) {
1575
            $date_format = DateTimeObj::getSetting('date_format');
1576
        }
1577
        if (empty($time_format)) {
1578
            $time_format = DateTimeObj::getSetting('time_format');
1579
        }
1580
1581
        $xDate = new XMLElement(
1582
            (!is_null($namespace) ? $namespace . ':' : '') . $element,
1583
            DateTimeObj::get($date_format, $timestamp),
0 ignored issues
show
Bug introduced by
It seems like $date_format can also be of type array; however, parameter $format of DateTimeObj::get() 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

1583
            DateTimeObj::get(/** @scrutinizer ignore-type */ $date_format, $timestamp),
Loading history...
1584
            array(
1585
                'iso' => DateTimeObj::get('c', $timestamp),
1586
                'timestamp' => DateTimeObj::get('U', $timestamp),
1587
                'time' => DateTimeObj::get($time_format, $timestamp),
1588
                'weekday' => DateTimeObj::get('N', $timestamp),
1589
                'offset' => DateTimeObj::get('O', $timestamp)
1590
            )
1591
        );
1592
1593
        return $xDate;
1594
    }
1595
1596
    /**
1597
     * Construct an XML fragment that describes a pagination structure.
1598
     *
1599
     * @param integer $total_entries (optional)
1600
     *  the total number of entries that this structure is paginating. this
1601
     *  defaults to 0.
1602
     * @param integer $total_pages (optional)
1603
     *  the total number of pages within the pagination structure. this defaults
1604
     *  to 0.
1605
     * @param integer $entries_per_page (optional)
1606
     *  the number of entries per page. this defaults to 1.
1607
     * @param integer $current_page (optional)
1608
     *  the current page within the total number of pages within this pagination
1609
     *  structure. this defaults to 1.
1610
     * @return XMLElement
1611
     *  the constructed XML fragment.
1612
     */
1613
    public static function buildPaginationElement($total_entries = 0, $total_pages = 0, $entries_per_page = 1, $current_page = 1)
1614
    {
1615
        $pageinfo = new XMLElement('pagination');
1616
1617
        $pageinfo->setAttribute('total-entries', $total_entries);
1618
        $pageinfo->setAttribute('total-pages', $total_pages);
1619
        $pageinfo->setAttribute('entries-per-page', $entries_per_page);
1620
        $pageinfo->setAttribute('current-page', $current_page);
1621
1622
        return $pageinfo;
1623
    }
1624
1625
    /**
1626
     * Helper to cut down on variables' type check.
1627
     * Currently known types are the PHP defaults.
1628
     * Uses `is_XXX()` functions internally.
1629
     *
1630
     * @since Symphony 2.3
1631
     *
1632
     * @param array $params - an array of arrays containing variables info
1633
     *
1634
     *  Array[
1635
     *      $key1 => $value1
1636
     *      $key2 => $value2
1637
     *      ...
1638
     *  ]
1639
     *
1640
     *  $key = the name of the variable
1641
     *  $value = Array[
1642
     *      'var' => the variable to check
1643
     *      'type' => enforced type. Must match the XXX part from an `is_XXX()` function
1644
     *      'optional' => boolean. If this is set, the default value of the variable must be null
1645
     *  ]
1646
     *
1647
     * @throws InvalidArgumentException if validator doesn't exist.
1648
     * @throws InvalidArgumentException if variable type validation fails.
1649
     *
1650
     * @example
1651
     *  $color = 'red';
1652
     *  $foo = null;
1653
     *  $bar = 21;
1654
     *
1655
     *  General::ensureType(array(
1656
     *      'color' => array('var' => $color, 'type'=> 'string'),               // success
1657
     *      'foo' => array('var' => $foo, 'type'=> 'int',  'optional' => true), // success
1658
     *      'bar' => array('var' => $bar, 'type'=> 'string')                    // fail
1659
     *  ));
1660
     */
1661
    public static function ensureType(array $params)
1662
    {
1663
        foreach ($params as $name => $param) {
1664
            if (isset($param['optional']) && ($param['optional'] === true)) {
1665
                if (empty($param['var'])) {
1666
                    continue;
1667
                }
1668
                // if not null, check it's type
1669
            }
1670
1671
            if (empty($param['type'])) {
1672
                $param['type'] = 'Undefined';
1673
            }
1674
1675
            // validate the validator
1676
            $validator = 'is_'.$param['type'];
1677
1678
            if (!function_exists($validator)) {
1679
                throw new InvalidArgumentException(__('Enforced type `%1$s` for argument `$%2$s` does not match any known variable types.', array($param['type'], $name)));
1680
            }
1681
1682
            // validate variable type
1683
            if (!call_user_func($validator, $param['var'])) {
1684
                throw new InvalidArgumentException(__('Argument `$%1$s` is not of type `%2$s`, given `%3$s`.', array($name, $param['type'], gettype($param['var']))));
1685
            }
1686
        }
1687
    }
1688
1689
1690
    /**
1691
     * Wrap a value in CDATA tags for XSL output of non encoded data, only
1692
     * if not already wrapped.
1693
     *
1694
     * @since Symphony 2.3.2
1695
     *
1696
     * @param string $value
1697
     *  The string to wrap in CDATA
1698
     * @return string
1699
     *  The wrapped string
1700
     */
1701
    public static function wrapInCDATA($value)
1702
    {
1703
        if (empty($value)) {
1704
            return $value;
1705
        }
1706
1707
        $startRegExp = '/^' . preg_quote(CDATA_BEGIN) . '/';
0 ignored issues
show
Bug introduced by
The constant CDATA_BEGIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1708
        $endRegExp = '/' . preg_quote(CDATA_END) . '$/';
0 ignored issues
show
Bug introduced by
The constant CDATA_END was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1709
1710
        if (!preg_match($startRegExp, $value)) {
1711
            $value = CDATA_BEGIN . $value;
1712
        }
1713
1714
        if (!preg_match($endRegExp, $value)) {
1715
            $value .= CDATA_END;
1716
        }
1717
1718
        return $value;
1719
    }
1720
1721
    /**
1722
     * Unwrap a value from CDATA tags to return the raw string
1723
     *
1724
     * @since Symphony 2.3.4
1725
     * @param string $value
1726
     *  The string to unwrap from CDATA
1727
     * @return string
1728
     *  The unwrapped string
1729
     */
1730
    public static function unwrapCDATA($value)
1731
    {
1732
        return str_replace(array(CDATA_BEGIN, CDATA_END), '', $value);
0 ignored issues
show
Bug introduced by
The constant CDATA_BEGIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant CDATA_END was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1733
    }
1734
1735
    /**
1736
     * Converts a value to a positive integer. This method makes sure that the
1737
     * value is a valid positive integer representation before doing the cast.
1738
     *
1739
     * @since Symphony 2.5
1740
     * @param mixed $value
1741
     *  The value to cast to an integer
1742
     * @return int
1743
     *  The casted integer value if the input is valid, -1 otherwise.
1744
     */
1745
    public static function intval($value)
1746
    {
1747
        if (is_numeric($value) && preg_match('/^[0-9]+$/i', $value) === 1) {
1748
            return intval($value);
1749
        }
1750
1751
        return -1;
1752
    }
1753
}
1754