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.
Completed
Push — master ( 5f362f...2255c0 )
by Nicolas
05:24
created

General::deleteFile()   B

Complexity

Conditions 5
Paths 10

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 10
nop 2
dl 0
loc 18
rs 8.8571
c 0
b 0
f 0
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
     * @param string $source
18
     *  a string to operate on.
19
     * @return string
20
     *  the encoded version of the string.
21
     */
22
    public static function sanitize($source)
23
    {
24
        $source = htmlspecialchars($source, ENT_COMPAT, 'UTF-8', false);
25
26
        return $source;
27
    }
28
29
    /**
30
     * Revert any html entities to their character equivalents.
31
     *
32
     * @param string $str
33
     *  a string to operate on
34
     * @return string
35
     *  the decoded version of the string
36
     */
37
    public static function reverse_sanitize($str)
38
    {
39
        return htmlspecialchars_decode($str, ENT_COMPAT);
40
    }
41
42
    /**
43
     * Validate a string against a set of regular expressions.
44
     *
45
     * @param array|string $string
46
     *  string to operate on
47
     * @param array|string $rule
48
     *  a single rule or array of rules
49
     * @return boolean
50
     *  false if any of the rules in $rule do not match any of the strings in
51
     *  `$string`, return true otherwise.
52
     */
53
    public static function validateString($string, $rule)
54
    {
55
        if (!is_array($rule) && ($rule == '' || $rule == null)) {
56
            return true;
57
        }
58
59
        if (!is_array($string) && ($string == '' || $rule == null)) {
60
            return true;
61
        }
62
63
        if (!is_array($rule)) {
64
            $rule = array($rule);
65
        }
66
67
        if (!is_array($string)) {
68
            $string = array($string);
69
        }
70
71
        foreach ($rule as $r) {
72
            foreach ($string as $s) {
73
                if (!preg_match($r, $s)) {
74
                    return false;
75
                }
76
            }
77
        }
78
        return true;
79
    }
80
81
    /**
82
     * Replace the tabs with spaces in the input string.
83
     *
84
     * @param string $string
85
     *  the string in which to replace the tabs with spaces.
86
     * @param integer $spaces (optional)
87
     *  the number of spaces to replace each tab with. This argument is optional
88
     *  with a default of 4.
89
     * @return string
90
     *  the resulting string.
91
     */
92
    public static function tabsToSpaces($string, $spaces = 4)
93
    {
94
        return str_replace("\t", str_pad(null, $spaces), $string);
95
    }
96
97
    /**
98
     * Checks an xml document for well-formedness.
99
     *
100
     * @param string $data
101
     *  filename, xml document as a string, or arbitrary string
102
     * @param pointer &$errors
103
     *  pointer to an array which will contain any validation errors
104
     * @param boolean $isFile (optional)
105
     *  if this is true, the method will attempt to read from a file, `$data`
106
     *  instead.
107
     * @param XsltProcess $xsltProcessor (optional)
108
     *  if set, the validation will be done using this XSLT processor rather
109
     *  than the built in XML parser. the default is null.
110
     * @param string $encoding (optional)
111
     *  if no XML header is expected, than this should be set to match the
112
     *  encoding of the XML
113
     * @return boolean
114
     *  true if there are no errors in validating the XML, false otherwise.
115
     */
116
    public static function validateXML($data, &$errors, $isFile = true, $xsltProcessor = null, $encoding = 'UTF-8')
117
    {
118
        $_data = ($isFile) ? file_get_contents($data) : $data;
119
        $_data = preg_replace('/<!DOCTYPE[-.:"\'\/\\w\\s]+>/', null, $_data);
120
121
        if (strpos($_data, '<?xml') === false) {
122
            $_data = '<?xml version="1.0" encoding="'.$encoding.'"?><rootelement>'.$_data.'</rootelement>';
123
        }
124
125
        if (is_object($xsltProcessor)) {
126
            $xsl = '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
127
128
            <xsl:template match="/"></xsl:template>
129
130
            </xsl:stylesheet>';
131
132
            $xsltProcessor->process($_data, $xsl, array());
133
134
            if ($xsltProcessor->isErrors()) {
135
                $errors = $xsltProcessor->getError(true);
136
                return false;
137
            }
138
        } else {
139
            $_parser = xml_parser_create();
140
            xml_parser_set_option($_parser, XML_OPTION_SKIP_WHITE, 0);
141
            xml_parser_set_option($_parser, XML_OPTION_CASE_FOLDING, 0);
142
143
            if (!xml_parse($_parser, $_data)) {
144
                $errors = array('error' => xml_get_error_code($_parser) . ': ' . xml_error_string(xml_get_error_code($_parser)),
145
                                'col' => xml_get_current_column_number($_parser),
146
                                'line' => (xml_get_current_line_number($_parser) - 2));
147
                return false;
148
            }
149
150
            xml_parser_free($_parser);
151
        }
152
153
        return true;
154
    }
155
156
    /**
157
     * Check that a string is a valid URL.
158
     *
159
     * @param string $url
160
     *  string to operate on
161
     * @return string
162
     *  a blank string or a valid URL
163
     */
164
    public static function validateURL($url = null)
165
    {
166
        $url = trim($url);
167
168
        if (is_null($url) || $url == '') {
169
            return $url;
170
        }
171
172
        if (!preg_match('#^http[s]?:\/\/#i', $url)) {
173
            $url = 'http://' . $url;
174
        }
175
176
        include TOOLKIT . '/util.validators.php';
177
178
        if (!preg_match($validators['URI'], $url)) {
179
            $url = '';
180
        }
181
182
        return $url;
183
    }
184
185
    /**
186
     * Strip any slashes from all array values.
187
     *
188
     * @param array &$arr
189
     *  Pointer to an array to operate on. Can be multi-dimensional.
190
     */
191
    public static function cleanArray(array &$arr)
192
    {
193
        foreach ($arr as $k => $v) {
194
            if (is_array($v)) {
195
                self::cleanArray($arr[$k]);
196
            } else {
197
                $arr[$k] = stripslashes($v);
198
            }
199
        }
200
    }
201
202
    /**
203
     * Flatten the input array. Any elements of the input array that are
204
     * themselves arrays will be removed and the contents of the removed array
205
     * inserted in its place. The keys for the inserted values will be the
206
     * concatenation of the keys in the original arrays in which it was embedded.
207
     * The elements of the path are separated by periods (.). For example,
208
     * given the following nested array structure:
209
     * `
210
     * array(1 =>
211
     *          array('key' => 'value'),
212
     *      2 =>
213
     *          array('key2' => 'value2', 'key3' => 'value3')
214
     *      )
215
     * `
216
     * will flatten to:
217
     * `array('1.key' => 'value', '2.key2' => 'value2', '2.key3' => 'value3')`
218
     *
219
     * @param array &$source
220
     *  The array to flatten, passed by reference
221
     * @param array &$output (optional)
222
     *  The array in which to store the flattened input, passed by reference.
223
     *  if this is not provided then a new array will be created.
224
     * @param string $path (optional)
225
     *  the current prefix of the keys to insert into the output array. this
226
     *  defaults to null.
227
     */
228
    public static function flattenArray(array &$source, &$output = null, $path = null)
229
    {
230
        if (is_null($output)) {
231
            $output = array();
232
        }
233
234
        foreach ($source as $key => $value) {
235
            if (is_int($key)) {
236
                $key = (string)($key + 1);
237
            }
238
239
            if (!is_null($path)) {
240
                $key = $path . '.' . (string)$key;
241
            }
242
243
            if (is_array($value)) {
244
                self::flattenArray($value, $output, $key);
245
            } else {
246
                $output[$key] = $value;
247
            }
248
        }
249
250
        $source = $output;
251
    }
252
253
    /**
254
     * Flatten the input array. Any elements of the input array that are
255
     * themselves arrays will be removed and the contents of the removed array
256
     * inserted in its place. The keys for the inserted values will be the
257
     * concatenation of the keys in the original arrays in which it was embedded.
258
     * The elements of the path are separated by colons (:). For example, given
259
     * the following nested array structure:
260
     * `
261
     * array(1 =>
262
     *          array('key' => 'value'),
263
     *      2 =>
264
     *          array('key2' => 'value2', 'key3' => 'value3')
265
     *      )
266
     * `
267
     * will flatten to:
268
     * `array('1:key' => 'value', '2:key2' => 'value2', '2:key3' => 'value3')`
269
     *
270
     *
271
     * @param array &$output
272
     *  The array in which to store the flattened input, passed by reference.
273
     * @param array &$source
274
     *  The array to flatten, passed by reference
275
     * @param string $path
276
     *  the current prefix of the keys to insert into the output array.
277
     */
278
    protected static function flattenArraySub(array &$output, array &$source, $path)
279
    {
280
        foreach ($source as $key => $value) {
281
            $key = $path . ':' . $key;
282
283
            if (is_array($value)) {
284
                self::flattenArraySub($output, $value, $key);
285
            } else {
286
                $output[$key] = $value;
287
            }
288
        }
289
    }
290
291
    /**
292
     * Given a string, this will clean it for use as a Symphony handle. Preserves multi-byte characters.
293
     *
294
     * @since Symphony 2.2.1
295
     * @param string $string
296
     *  String to be cleaned up
297
     * @param integer $max_length
298
     *  The maximum number of characters in the handle
299
     * @param string $delim
300
     *  All non-valid characters will be replaced with this
301
     * @param boolean $uriencode
302
     *  Force the resultant string to be uri encoded making it safe for URLs
303
     * @param array $additional_rule_set
304
     *  An array of REGEX patterns that should be applied to the `$string`. This
305
     *  occurs after the string has been trimmed and joined with the `$delim`
306
     * @return string
307
     *  Returns resultant handle
308
     */
309
    public static function createHandle($string, $max_length = 255, $delim = '-', $uriencode = false, $additional_rule_set = null)
310
    {
311
        $max_length = intval($max_length);
312
313
        // Strip out any tag
314
        $string = strip_tags($string);
315
316
        // Remove punctuation
317
        $string = preg_replace('/[\\.\'"]+/', null, $string);
318
319
        // Trim it
320
        if ($max_length > 0) {
321
            $string = General::limitWords($string, $max_length);
322
        }
323
324
        // Replace spaces (tab, newline etc) with the delimiter
325
        $string = preg_replace('/[\s]+/', $delim, $string);
326
327
        // Find all legal characters
328
        preg_match_all('/[^<>?@:!-\/\[-`;‘’…]+/u', $string, $matches);
329
330
        // Join only legal character with the $delim
331
        $string = implode($delim, $matches[0]);
332
333
        // Allow for custom rules
334
        if (is_array($additional_rule_set) && !empty($additional_rule_set)) {
335
            foreach ($additional_rule_set as $rule => $replacement) {
336
                $string = preg_replace($rule, $replacement, $string);
337
            }
338
        }
339
340
        // Remove leading or trailing delim characters
341
        $string = trim($string, $delim);
342
343
        // Encode it for URI use
344
        if ($uriencode) {
345
            $string = urlencode($string);
346
        }
347
348
        // Make it lowercase
349
        $string = strtolower($string);
350
351
        return $string;
352
    }
353
354
    /**
355
     * Given a string, this will clean it for use as a filename. Preserves multi-byte characters.
356
     *
357
     * @since Symphony 2.2.1
358
     * @param string $string
359
     *  String to be cleaned up
360
     * @param string $delim
361
     *  All non-valid characters will be replaced with this
362
     * @return string
363
     *  Returns created filename
364
     */
365
    public static function createFilename($string, $delim = '-')
366
    {
367
        // Strip out any tag
368
        $string = strip_tags($string);
369
370
        // Find all legal characters
371
        $count = preg_match_all('/[\p{L}\w:;.,+=~]+/u', $string, $matches);
372
        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...
373
            preg_match_all('/[\w:;.,+=~]+/', $string, $matches);
374
        }
375
376
        // Join only legal character with the $delim
377
        $string = implode($delim, $matches[0]);
378
379
        // Remove leading or trailing delim characters
380
        $string = trim($string, $delim);
381
382
        // Make it lowercase
383
        $string = strtolower($string);
384
385
        return $string;
386
    }
387
388
    /**
389
     * Computes the length of the string.
390
     * This function will attempt to use PHP's `mbstring` functions if they are available.
391
     * This function also forces utf-8 encoding.
392
     *
393
     * @since Symphony 2.5.0
394
     * @param string $str
395
     *  the string to operate on
396
     * @return int
397
     *  the string's length
398
     */
399
    public static function strlen($str)
400
    {
401
        if (function_exists('mb_strlen')) {
402
            return mb_strlen($str, 'utf-8');
403
        }
404
        return strlen($str);
405
    }
406
407
    /**
408
     * Finds position of the first occurrence of a string in a string.
409
     * This function will attempt to use PHP's `mbstring` functions if they are available.
410
     * This function also forces utf-8 encoding for mbstring.
411
     *
412
     * @since Symphony 2.7.0
413
     * @param string $haystack
414
     *  the string to look into
415
     * @param string $needle
416
     *  the string to look for
417
     * @param int $offset
418
     *  the search offset. If it is not specified, 0 is used.
419
     *  A negative offset counts from the end of the string.
420
     * @return int
421
     *  the numeric position of the first occurrence of needle in the haystack
422
     */
423
    public static function strpos($haystack, $needle, $offset = 0)
424
    {
425
        if (function_exists('mb_strpos')) {
426
            return mb_strpos($haystack, $needle, $offset, 'utf-8');
427
        }
428
        return strpos($haystack, $needle, $offset);
429
    }
430
431
    /**
432
     * Creates a sub string.
433
     * This function will attempt to use PHP's `mbstring` functions if they are available.
434
     * This function also forces utf-8 encoding.
435
     *
436
     * @since Symphony 2.5.0
437
     * @param string $str
438
     *  the string to operate on
439
     * @param int $start
440
     *  the starting offset
441
     * @param int $length
442
     *  the length of the substring
443
     * @return string
444
     *  the resulting substring
445
     */
446
    public static function substr($str, $start, $length = null)
447
    {
448
        if (function_exists('mb_substr')) {
449
            return mb_substr($str, $start, $length, 'utf-8');
450
        }
451
        if ($length === null) {
452
            return substr($str, $start);
453
        }
454
        return substr($str, $start, $length);
455
    }
456
457
    /**
458
     * Extract the first `$val` characters of the input string. If `$val`
459
     * is larger than the length of the input string then the original
460
     * input string is returned.
461
     *
462
     * @param string $str
463
     *  the string to operate on
464
     * @param integer $val
465
     *  the number to compare lengths with
466
     * @return string|boolean
467
     *  the resulting string or false on failure.
468
     */
469
    public static function substrmin($str, $val)
470
    {
471
        return self::substr($str, 0, min(self::strlen($str), $val));
472
    }
473
474
    /**
475
     * Extract the first `$val` characters of the input string. If
476
     * `$val` is larger than the length of the input string then
477
     * the original input string is returned
478
     *
479
     * @param string $str
480
     *  the string to operate on
481
     * @param integer $val
482
     *  the number to compare lengths with
483
     * @return string|boolean
484
     *  the resulting string or false on failure.
485
     */
486
    public static function substrmax($str, $val)
487
    {
488
        return self::substr($str, 0, max(self::strlen($str), $val));
489
    }
490
491
    /**
492
     * Extract the last `$num` characters from a string.
493
     *
494
     * @param string $str
495
     *  the string to extract the characters from.
496
     * @param integer $num
497
     *  the number of characters to extract.
498
     * @return string|boolean
499
     *  a string containing the last `$num` characters of the
500
     *  input string, or false on failure.
501
     */
502
    public static function right($str, $num)
503
    {
504
        $str = self::substr($str, self::strlen($str)-$num, $num);
505
        return $str;
506
    }
507
508
    /**
509
     * Extract the first `$num` characters from a string.
510
     *
511
     * @param string $str
512
     *  the string to extract the characters from.
513
     * @param integer $num
514
     *  the number of characters to extract.
515
     * @return string|boolean
516
     *  a string containing the last `$num` characters of the
517
     *  input string, or false on failure.
518
     */
519
    public static function left($str, $num)
520
    {
521
        $str = self::substr($str, 0, $num);
522
        return $str;
523
    }
524
525
    /**
526
     * Create all the directories as specified by the input path. If the current
527
     * directory already exists, this function will return true.
528
     *
529
     * @param string $path
530
     *  the path containing the directories to create.
531
     * @param string|integer $mode (optional)
532
     *  the permissions (in octal) of the directories to create. Defaults to 0755
533
     * @param boolean $silent (optional)
534
     *  true if an exception should be raised if an error occurs, false
535
     *  otherwise. this defaults to true.
536
     * @throws Exception
537
     * @return boolean
538
     */
539
    public static function realiseDirectory($path, $mode = 0755, $silent = true)
540
    {
541
        if (is_dir($path)) {
542
            return true;
543
        }
544
545
        try {
546
            $current_umask = umask(0);
547
            $success = @mkdir($path, intval($mode, 8), true);
548
            umask($current_umask);
549
550
            return $success;
551
        } catch (Exception $ex) {
552
            if ($silent === false) {
553
                throw new Exception(__('Unable to create path - %s', array($path)));
554
            }
555
556
            return false;
557
        }
558
    }
559
560
    /**
561
     * Recursively deletes all files and directories given a directory. This
562
     * function has two path. This function optionally takes a `$silent` parameter,
563
     * which when `false` will throw an `Exception` if there is an error deleting a file
564
     * or folder.
565
     *
566
     * @since Symphony 2.3
567
     * @param string $dir
568
     *  the path of the directory to delete
569
     * @param boolean $silent (optional)
570
     *  true if an exception should be raised if an error occurs, false
571
     *  otherwise. this defaults to true.
572
     * @throws Exception
573
     * @return boolean
574
     */
575
    public static function deleteDirectory($dir, $silent = true)
576
    {
577
        try {
578
            if (!@file_exists($dir)) {
579
                return true;
580
            }
581
582
            if (!@is_dir($dir)) {
583
                return @unlink($dir);
584
            }
585
586
            foreach (scandir($dir) as $item) {
587
                if ($item == '.' || $item == '..') {
588
                    continue;
589
                }
590
591
                if (!self::deleteDirectory($dir.DIRECTORY_SEPARATOR.$item)) {
592
                    return false;
593
                }
594
            }
595
596
            return rmdir($dir);
597
        } catch (Exception $ex) {
598
            if ($silent === false) {
599
                throw new Exception(__('Unable to remove - %s', array($dir)));
600
            }
601
602
            return false;
603
        }
604
    }
605
606
    /**
607
     * Search a multi-dimensional array for a value.
608
     *
609
     * @param mixed $needle
610
     *  the value to search for.
611
     * @param array $haystack
612
     *  the multi-dimensional array to search.
613
     * @return boolean
614
     *  true if `$needle` is found in `$haystack`.
615
     *  true if `$needle` == `$haystack`.
616
     *  true if `$needle` is found in any of the arrays contained within `$haystack`.
617
     *  false otherwise.
618
     */
619
    public static function in_array_multi($needle, $haystack)
620
    {
621
        if ($needle == $haystack) {
622
            return true;
623
        }
624
625
        if (is_array($haystack)) {
626
            foreach ($haystack as $key => $val) {
627
                if (is_array($val)) {
628
                    if (self::in_array_multi($needle, $val)) {
629
                        return true;
630
                    }
631
                } elseif (!strcmp($needle, $key) || !strcmp($needle, $val)) {
632
                    return true;
633
                }
634
            }
635
        }
636
637
        return false;
638
    }
639
640
    /**
641
     * Search an array for multiple values.
642
     *
643
     * @param array $needles
644
     *  the values to search the `$haystack` for.
645
     * @param array $haystack
646
     *  the in which to search for the `$needles`
647
     * @return boolean
648
     *  true if any of the `$needles` are in `$haystack`,
649
     *  false otherwise.
650
     */
651
    public static function in_array_all($needles, $haystack)
652
    {
653
        foreach ($needles as $n) {
654
            if (!in_array($n, $haystack)) {
655
                return false;
656
            }
657
        }
658
659
        return true;
660
    }
661
662
    /**
663
     * Transform a multi-dimensional array to a flat array. The input array
664
     * is expected to conform to the structure of the `$_FILES` variable.
665
     *
666
     * @param array $filedata
667
     *  the raw `$_FILES` data structured array
668
     * @return array
669
     *  the flattened array.
670
     */
671
    public static function processFilePostData($filedata)
672
    {
673
        $result = array();
674
675
        foreach ($filedata as $key => $data) {
676
            foreach ($data as $handle => $value) {
677
                if (is_array($value)) {
678
                    foreach ($value as $index => $pair) {
679
                        if (!is_array($result[$handle][$index])) {
680
                            $result[$handle][$index] = array();
681
                        }
682
683
                        if (!is_array($pair)) {
684
                            $result[$handle][$index][$key] = $pair;
685
                        } else {
686
                            $result[$handle][$index][array_pop(array_keys($pair))][$key] = array_pop(array_values($pair));
0 ignored issues
show
Bug introduced by
array_keys($pair) cannot be passed to array_pop() as the parameter $array expects a reference.
Loading history...
Bug introduced by
array_values($pair) cannot be passed to array_pop() as the parameter $array expects a reference.
Loading history...
687
                        }
688
                    }
689
                } else {
690
                    $result[$handle][$key] = $value;
691
                }
692
            }
693
        }
694
695
        return $result;
696
    }
697
698
    /**
699
     * Merge `$_POST` with `$_FILES` to produce a flat array of the contents
700
     * of both. If there is no merge_file_post_data function defined then
701
     * such a function is created. This is necessary to overcome PHP's ability
702
     * to handle forms. This overcomes PHP's convoluted `$_FILES` structure
703
     * to make it simpler to access `multi-part/formdata`.
704
     *
705
     * @return array
706
     *  a flat array containing the flattened contents of both `$_POST` and
707
     *  `$_FILES`.
708
     */
709
    public static function getPostData()
710
    {
711
        if (!function_exists('merge_file_post_data')) {
712
            function merge_file_post_data($type, array $file, &$post)
713
            {
714
                foreach ($file as $key => $value) {
715
                    if (!isset($post[$key])) {
716
                        $post[$key] = array();
717
                    }
718
719
                    if (is_array($value)) {
720
                        merge_file_post_data($type, $value, $post[$key]);
721
                    } else {
722
                        $post[$key][$type] = $value;
723
                    }
724
                }
725
            }
726
        }
727
728
        $files = array(
729
            'name'      => array(),
730
            'type'      => array(),
731
            'tmp_name'  => array(),
732
            'error'     => array(),
733
            'size'      => array()
734
        );
735
        $post = $_POST;
736
737
        if (is_array($_FILES) && !empty($_FILES)) {
738
            foreach ($_FILES as $key_a => $data_a) {
739
                if (!is_array($data_a)) {
740
                    continue;
741
                }
742
743
                foreach ($data_a as $key_b => $data_b) {
744
                    $files[$key_b][$key_a] = $data_b;
745
                }
746
            }
747
        }
748
749
        foreach ($files as $type => $data) {
750
            merge_file_post_data($type, $data, $post);
751
        }
752
753
        return $post;
754
    }
755
756
    /**
757
     * Find the next available index in an array. Works best with numeric keys.
758
     * The next available index is the minimum integer such that the array does
759
     * not have a mapping for that index. Uses the increment operator on the
760
     * index type of the input array, whatever that may do.
761
     *
762
     * @param array $array
763
     *  the array to find the next index for.
764
     * @param mixed $seed (optional)
765
     *  the object with which the search for an empty index is initialized. this
766
     *  defaults to null.
767
     * @return integer
768
     *  the minimum empty index into the input array.
769
     */
770
    public static function array_find_available_index($array, $seed = null)
771
    {
772
        if (!is_null($seed)) {
773
            $index = $seed;
774
        } else {
775
            $keys = array_keys($array);
776
            sort($keys);
777
            $index = array_pop($keys);
778
        }
779
780
        if (isset($array[$index])) {
781
            do {
782
                $index++;
783
            } while (isset($array[$index]));
784
        }
785
786
        return $index;
787
    }
788
789
    /**
790
     * Filter the duplicate values from an array into a new array, optionally
791
     * ignoring the case of the values (assuming they are strings?). A new array
792
     * is returned, the input array is left unchanged.
793
     *
794
     * @param array $array
795
     *  the array to filter.
796
     * @param boolean $ignore_case
797
     *  true if the case of the values in the array should be ignored, false otherwise.
798
     * @return array
799
     *  a new array containing only the unique elements of the input array.
800
     */
801
    public static function array_remove_duplicates(array $array, $ignore_case = false)
802
    {
803
        return ($ignore_case === true ? self::array_iunique($array) : array_unique($array));
804
    }
805
806
    /**
807
     * Test whether a value is in an array based on string comparison, ignoring
808
     * the case of the values.
809
     *
810
     * @param mixed $needle
811
     *  the object to search the array for.
812
     * @param array $haystack
813
     *  the array to search for the `$needle`.
814
     * @return boolean
815
     *  true if the `$needle` is in the `$haystack`, false otherwise.
816
     */
817
    public static function in_iarray($needle, array $haystack)
818
    {
819
        foreach ($haystack as $key => $value) {
820
            if (strcasecmp($value, $needle) == 0) {
821
                return true;
822
            }
823
        }
824
        return false;
825
    }
826
827
    /**
828
     * Filter the input array for duplicates, treating each element in the array
829
     * as a string and comparing them using a case insensitive comparison function.
830
     *
831
     * @param array $array
832
     *  the array to filter.
833
     * @return array
834
     *  a new array containing only the unique elements of the input array.
835
     */
836
    public static function array_iunique(array $array)
837
    {
838
        $tmp = array();
839
840
        foreach ($array as $key => $value) {
841
            if (!self::in_iarray($value, $tmp)) {
842
                $tmp[$key] = $value;
843
            }
844
        }
845
846
        return $tmp;
847
    }
848
849
    /**
850
     * Function recursively apply a function to an array's values.
851
     * This will not touch the keys, just the values.
852
     *
853
     * @since Symphony 2.2
854
     * @param string $function
855
     * @param array $array
856
     * @return array
857
     *  a new array with all the values passed through the given `$function`
858
     */
859
    public static function array_map_recursive($function, array $array)
860
    {
861
        $tmp = array();
862
863
        foreach ($array as $key => $value) {
864
            if (is_array($value)) {
865
                $tmp[$key] = self::array_map_recursive($function, $value);
866
            } else {
867
                $tmp[$key] = call_user_func($function, $value);
868
            }
869
        }
870
871
        return $tmp;
872
    }
873
874
    /**
875
     * Convert an array into an XML fragment and append it to an existing
876
     * XML element. Any arrays contained as elements in the input array will
877
     * also be recursively formatted and appended to the input XML fragment.
878
     * The input XML element will be modified as a result of calling this.
879
     *
880
     * @param XMLElement $parent
881
     *  the XML element to append the formatted array data to.
882
     * @param array $data
883
     *  the array to format and append to the XML fragment.
884
     * @param boolean $validate
885
     *  true if the formatted array data should be validated as it is
886
     *  constructed, false otherwise.
887
     */
888
    public static function array_to_xml(XMLElement $parent, array $data, $validate = false)
889
    {
890
        foreach ($data as $element_name => $value) {
891
            if (!is_numeric($value) && empty($value)) {
892
                continue;
893
            }
894
895
            if (is_int($element_name)) {
896
                $child = new XMLElement('item');
897
                $child->setAttribute('index', $element_name + 1);
898
            } else {
899
                $child = new XMLElement($element_name, null, array(), true);
900
            }
901
902
            if (is_array($value) || is_object($value)) {
903
                self::array_to_xml($child, (array)$value);
904
905
                if ($child->getNumberOfChildren() == 0) {
906
                    continue;
907
                }
908
            } elseif ($validate === true && !self::validateXML(self::sanitize($value), $errors, false, new XSLTProcess)) {
909
                continue;
910
            } else {
911
                $child->setValue(self::sanitize($value));
912
            }
913
914
            $parent->appendChild($child);
915
        }
916
    }
917
918
    /**
919
     * Create a file at the input path with the (optional) input permissions
920
     * with the input content. This function will ignore errors in opening,
921
     * writing, closing and changing the permissions of the resulting file.
922
     * If opening or writing the file fail then this will return false.
923
     * This method calls `General::checkFileWritable()` which properly checks
924
     * for permissions.
925
     *
926
     * @uses General::checkFileWritable()
927
     * @param string $file
928
     *  the path of the file to write.
929
     * @param mixed $data
930
     *  the data to write to the file.
931
     * @param integer|string $perm (optional)
932
     *  the permissions as an octal number to set set on the resulting file.
933
     *  this defaults to 0644 (if omitted or set to null)
934
     * @param string $mode (optional)
935
     *  the mode that the file should be opened with, defaults to 'w'. See modes
936
     *  at http://php.net/manual/en/function.fopen.php
937
     * @param boolean $trim (optional)
938
     *  removes tripple linebreaks
939
     * @return boolean
940
     *  true if the file is successfully opened, written to, closed and has the
941
     *  required permissions set. false, otherwise.
942
     */
943
    public static function writeFile($file, $data, $perm = 0644, $mode = 'w', $trim = false)
944
    {
945
        if (static::checkFileWritable($file) === false) {
946
            return false;
947
        }
948
949
        if (!$handle = fopen($file, $mode)) {
950
            return false;
951
        }
952
953
        if ($trim === true) {
954
            $data = preg_replace("/(" . PHP_EOL . "([ |\t]+)?){2,}" . PHP_EOL . "/", PHP_EOL . PHP_EOL, trim($data));
955
        }
956
957
        if (fwrite($handle, $data, strlen($data)) === false) {
958
            return false;
959
        }
960
961
        fclose($handle);
962
963
        try {
964
            if (is_null($perm)) {
965
                $perm = 0644;
966
            }
967
968
            @chmod($file, intval($perm, 8));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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...
1456
                return true;
1457
            }
1458
        }
1459
1460
        // Could not move the file
1461
        return false;
1462
    }
1463
1464
    /**
1465
     * Format a number of bytes in human readable format. This will append MB as
1466
     * appropriate for values greater than 1,024*1,024, KB for values between
1467
     * 1,024 and 1,024*1,024-1 and bytes for values between 0 and 1,024.
1468
     *
1469
     * @param integer $file_size
1470
     *  the number to format.
1471
     * @return string
1472
     *  the formatted number.
1473
     */
1474
    public static function formatFilesize($file_size)
1475
    {
1476
        $file_size = intval($file_size);
1477
1478
        if ($file_size >= (1024 * 1024)) {
1479
            $file_size = number_format($file_size * (1 / (1024 * 1024)), 2) . ' MB';
1480
        } elseif ($file_size >= 1024) {
1481
            $file_size = intval($file_size * (1/1024)) . ' KB';
1482
        } else {
1483
            $file_size = intval($file_size) . ' bytes';
1484
        }
1485
1486
        return $file_size;
1487
    }
1488
1489
    /**
1490
     * Gets the number of bytes from 'human readable' size value. Supports
1491
     * the output of `General::formatFilesize` as well as reading values
1492
     * from the PHP configuration. eg. 1 MB or 1M
1493
     *
1494
     * @since Symphony 2.5.2
1495
     * @param string $file_size
1496
     * @return integer
1497
     */
1498
    public static function convertHumanFileSizeToBytes($file_size)
1499
    {
1500
        $file_size = str_replace(
1501
            array(' MB', ' KB', ' bytes'),
1502
            array('M', 'K', 'B'),
1503
            trim($file_size)
1504
        );
1505
1506
        $last = strtolower($file_size[strlen($file_size)-1]);
1507
1508
        $file_size = (int) $file_size;
1509
1510 View Code Duplication
        switch ($last) {
1511
            case 'g':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
1512
                $file_size *= 1024;
1513
            case 'm':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
1514
                $file_size *= 1024;
1515
            case 'k':
1516
                $file_size *= 1024;
1517
        }
1518
1519
        return $file_size;
1520
    }
1521
1522
    /**
1523
     * Construct an XML fragment that reflects the structure of the input timestamp.
1524
     *
1525
     * @param integer $timestamp
1526
     *  the timestamp to construct the XML element from.
1527
     * @param string $element (optional)
1528
     *  the name of the element to append to the namespace of the constructed XML.
1529
     *  this defaults to "date".
1530
     * @param string $date_format (optional)
1531
     *  the format to apply to the date, defaults to `Y-m-d`.
1532
     *  if empty, uses DateTimeObj settings.
1533
     * @param string $time_format (optional)
1534
     *  the format to apply to the date, defaults to `H:i`.
1535
     *  if empty, uses DateTimeObj settings.
1536
     * @param string $namespace (optional)
1537
     *  the namespace in which the resulting XML entity will reside. this defaults
1538
     *  to null.
1539
     * @return boolean|XMLElement
1540
     *  false if there is no XMLElement class on the system, the constructed XML element
1541
     *  otherwise.
1542
     */
1543
    public static function createXMLDateObject($timestamp, $element = 'date', $date_format = 'Y-m-d', $time_format = 'H:i', $namespace = null)
1544
    {
1545
        if (!class_exists('XMLElement')) {
1546
            return false;
1547
        }
1548
1549
        if (empty($date_format)) {
1550
            $date_format = DateTimeObj::getSetting('date_format');
1551
        }
1552
        if (empty($time_format)) {
1553
            $time_format = DateTimeObj::getSetting('time_format');
1554
        }
1555
1556
        $xDate = new XMLElement(
1557
            (!is_null($namespace) ? $namespace . ':' : '') . $element,
1558
            DateTimeObj::get($date_format, $timestamp),
0 ignored issues
show
Bug introduced by
It seems like $date_format defined by \DateTimeObj::getSetting('date_format') on line 1550 can also be of type array or null; however, DateTimeObj::get() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Security Bug introduced by
It seems like \DateTimeObj::get($date_format, $timestamp) targeting DateTimeObj::get() can also be of type false; however, XMLElement::__construct() does only seem to accept string|object<XMLElement>|null, did you maybe forget to handle an error condition?
Loading history...
1559
            array(
1560
                'iso' => DateTimeObj::get('c', $timestamp),
1561
                'timestamp' => DateTimeObj::get('U', $timestamp),
1562
                'time' => DateTimeObj::get($time_format, $timestamp),
0 ignored issues
show
Bug introduced by
It seems like $time_format defined by \DateTimeObj::getSetting('time_format') on line 1553 can also be of type array or null; however, DateTimeObj::get() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

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