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 ( 0e9349...d2bdfd )
by Nicolas
08:54 queued 04:10
created

General::validateString()   C

Complexity

Conditions 12
Paths 18

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 27
rs 5.1612
cc 12
eloc 14
nc 18
nop 2

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 mixed $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)) {
0 ignored issues
show
Bug introduced by
The variable $validators does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
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
     * Creates a sub 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
     * @param int $start
416
     *  the starting offset
417
     * @param int $start
418
     *  the length of the substring
419
     * @return string
420
     *  the resulting substring
421
     */
422
    public static function substr($str, $start, $length)
423
    {
424
        if (function_exists('mb_substr')) {
425
            return mb_substr($str, $start, $length, 'utf-8');
426
        }
427
        return substr($str, $start, $length);
428
    }
429
430
    /**
431
     * Extract the first `$val` characters of the input string. If `$val`
432
     * is larger than the length of the input string then the original
433
     * input string is returned.
434
     *
435
     * @param string $str
436
     *  the string to operate on
437
     * @param integer $val
438
     *  the number to compare lengths with
439
     * @return string|boolean
440
     *  the resulting string or false on failure.
441
     */
442
    public static function substrmin($str, $val)
443
    {
444
        return(self::substr($str, 0, min(self::strlen($str), $val)));
445
    }
446
447
    /**
448
     * Extract the first `$val` characters of the input string. If
449
     * `$val` is larger than the length of the input string then
450
     * the original input string is returned
451
     *
452
     * @param string $str
453
     *  the string to operate on
454
     * @param integer $val
455
     *  the number to compare lengths with
456
     * @return string|boolean
457
     *  the resulting string or false on failure.
458
     */
459
    public static function substrmax($str, $val)
460
    {
461
        return(self::substr($str, 0, max(self::strlen($str), $val)));
462
    }
463
464
    /**
465
     * Extract the last `$num` characters from a string.
466
     *
467
     * @param string $str
468
     *  the string to extract the characters from.
469
     * @param integer $num
470
     *  the number of characters to extract.
471
     * @return string|boolean
472
     *  a string containing the last `$num` characters of the
473
     *  input string, or false on failure.
474
     */
475
    public static function right($str, $num)
476
    {
477
        $str = self::substr($str, self::strlen($str)-$num, $num);
478
        return $str;
479
    }
480
481
    /**
482
     * Extract the first `$num` characters from a string.
483
     *
484
     * @param string $str
485
     *  the string to extract the characters from.
486
     * @param integer $num
487
     *  the number of characters to extract.
488
     * @return string|boolean
489
     *  a string containing the last `$num` characters of the
490
     *  input string, or false on failure.
491
     */
492
    public static function left($str, $num)
493
    {
494
        $str = self::substr($str, 0, $num);
495
        return $str;
496
    }
497
498
    /**
499
     * Create all the directories as specified by the input path. If the current
500
     * directory already exists, this function will return true.
501
     *
502
     * @param string $path
503
     *  the path containing the directories to create.
504
     * @param integer $mode (optional)
505
     *  the permissions (in octal) of the directories to create. Defaults to 0755
506
     * @param boolean $silent (optional)
507
     *  true if an exception should be raised if an error occurs, false
508
     *  otherwise. this defaults to true.
509
     * @throws Exception
510
     * @return boolean
511
     */
512
    public static function realiseDirectory($path, $mode = 0755, $silent = true)
513
    {
514
        if (is_dir($path)) {
515
            return true;
516
        }
517
518
        try {
519
            $current_umask = umask(0);
520
            $success = @mkdir($path, intval($mode, 8), true);
521
            umask($current_umask);
522
523
            return $success;
524
        } catch (Exception $ex) {
525
            if ($silent === false) {
526
                throw new Exception(__('Unable to create path - %s', array($path)));
527
            }
528
529
            return false;
530
        }
531
    }
532
533
    /**
534
     * Recursively deletes all files and directories given a directory. This
535
     * function has two path. This function optionally takes a `$silent` parameter,
536
     * which when `false` will throw an `Exception` if there is an error deleting a file
537
     * or folder.
538
     *
539
     * @since Symphony 2.3
540
     * @param string $dir
541
     *  the path of the directory to delete
542
     * @param boolean $silent (optional)
543
     *  true if an exception should be raised if an error occurs, false
544
     *  otherwise. this defaults to true.
545
     * @throws Exception
546
     * @return boolean
547
     */
548
    public static function deleteDirectory($dir, $silent = true)
549
    {
550
        try {
551
            if (!file_exists($dir)) {
552
                return true;
553
            }
554
555
            if (!is_dir($dir)) {
556
                return unlink($dir);
557
            }
558
559
            foreach (scandir($dir) as $item) {
560
                if ($item == '.' || $item == '..') {
561
                    continue;
562
                }
563
564
                if (!self::deleteDirectory($dir.DIRECTORY_SEPARATOR.$item)) {
565
                    return false;
566
                }
567
            }
568
569
            return rmdir($dir);
570
        } catch (Exception $ex) {
571
            if ($silent === false) {
572
                throw new Exception(__('Unable to remove - %s', array($dir)));
573
            }
574
575
            return false;
576
        }
577
    }
578
579
    /**
580
     * Search a multi-dimensional array for a value.
581
     *
582
     * @param mixed $needle
583
     *  the value to search for.
584
     * @param array $haystack
585
     *  the multi-dimensional array to search.
586
     * @return boolean
587
     *  true if `$needle` is found in `$haystack`.
588
     *  true if `$needle` == `$haystack`.
589
     *  true if `$needle` is found in any of the arrays contained within `$haystack`.
590
     *  false otherwise.
591
     */
592
    public static function in_array_multi($needle, $haystack)
593
    {
594
        if ($needle == $haystack) {
595
            return true;
596
        }
597
598
        if (is_array($haystack)) {
599
            foreach ($haystack as $key => $val) {
600
                if (is_array($val)) {
601
                    if (self::in_array_multi($needle, $val)) {
602
                        return true;
603
                    }
604
                } elseif (!strcmp($needle, $key) || !strcmp($needle, $val)) {
605
                    return true;
606
                }
607
            }
608
        }
609
610
        return false;
611
    }
612
613
    /**
614
     * Search an array for multiple values.
615
     *
616
     * @param array $needles
617
     *  the values to search the `$haystack` for.
618
     * @param array $haystack
619
     *  the in which to search for the `$needles`
620
     * @return boolean
621
     *  true if any of the `$needles` are in `$haystack`,
622
     *  false otherwise.
623
     */
624
    public static function in_array_all($needles, $haystack)
625
    {
626
        foreach ($needles as $n) {
627
            if (!in_array($n, $haystack)) {
628
                return false;
629
            }
630
        }
631
632
        return true;
633
    }
634
635
    /**
636
     * Transform a multi-dimensional array to a flat array. The input array
637
     * is expected to conform to the structure of the `$_FILES` variable.
638
     *
639
     * @param array $filedata
640
     *  the raw `$_FILES` data structured array
641
     * @return array
642
     *  the flattened array.
643
     */
644
    public static function processFilePostData($filedata)
645
    {
646
        $result = array();
647
648
        foreach ($filedata as $key => $data) {
649
            foreach ($data as $handle => $value) {
650
                if (is_array($value)) {
651
                    foreach ($value as $index => $pair) {
652
                        if (!is_array($result[$handle][$index])) {
653
                            $result[$handle][$index] = array();
654
                        }
655
656
                        if (!is_array($pair)) {
657
                            $result[$handle][$index][$key] = $pair;
658
                        } else {
659
                            $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...
660
                        }
661
                    }
662
                } else {
663
                    $result[$handle][$key] = $value;
664
                }
665
            }
666
        }
667
668
        return $result;
669
    }
670
671
    /**
672
     * Merge `$_POST` with `$_FILES` to produce a flat array of the contents
673
     * of both. If there is no merge_file_post_data function defined then
674
     * such a function is created. This is necessary to overcome PHP's ability
675
     * to handle forms. This overcomes PHP's convoluted `$_FILES` structure
676
     * to make it simpler to access `multi-part/formdata`.
677
     *
678
     * @return array
679
     *  a flat array containing the flattened contents of both `$_POST` and
680
     *  `$_FILES`.
681
     */
682
    public static function getPostData()
0 ignored issues
show
Coding Style introduced by
getPostData uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
getPostData uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
683
    {
684
        if (!function_exists('merge_file_post_data')) {
685
            function merge_file_post_data($type, array $file, &$post)
686
            {
687
                foreach ($file as $key => $value) {
688
                    if (!isset($post[$key])) {
689
                        $post[$key] = array();
690
                    }
691
692
                    if (is_array($value)) {
693
                        merge_file_post_data($type, $value, $post[$key]);
694
                    } else {
695
                        $post[$key][$type] = $value;
696
                    }
697
                }
698
            }
699
        }
700
701
        $files = array(
702
            'name'      => array(),
703
            'type'      => array(),
704
            'tmp_name'  => array(),
705
            'error'     => array(),
706
            'size'      => array()
707
        );
708
        $post = $_POST;
709
710
        if (is_array($_FILES) && !empty($_FILES)) {
711
            foreach ($_FILES as $key_a => $data_a) {
712
                if (!is_array($data_a)) {
713
                    continue;
714
                }
715
716
                foreach ($data_a as $key_b => $data_b) {
717
                    $files[$key_b][$key_a] = $data_b;
718
                }
719
            }
720
        }
721
722
        foreach ($files as $type => $data) {
723
            merge_file_post_data($type, $data, $post);
724
        }
725
726
        return $post;
727
    }
728
729
    /**
730
     * Find the next available index in an array. Works best with numeric keys.
731
     * The next available index is the minimum integer such that the array does
732
     * not have a mapping for that index. Uses the increment operator on the
733
     * index type of the input array, whatever that may do.
734
     *
735
     * @param array $array
736
     *  the array to find the next index for.
737
     * @param mixed $seed (optional)
738
     *  the object with which the search for an empty index is initialized. this
739
     *  defaults to null.
740
     * @return integer
741
     *  the minimum empty index into the input array.
742
     */
743
    public static function array_find_available_index($array, $seed = null)
744
    {
745
        if (!is_null($seed)) {
746
            $index = $seed;
747
        } else {
748
            $keys = array_keys($array);
749
            sort($keys);
750
            $index = array_pop($keys);
751
        }
752
753
        if (isset($array[$index])) {
754
            do {
755
                $index++;
756
            } while (isset($array[$index]));
757
        }
758
759
        return $index;
760
    }
761
762
    /**
763
     * Filter the duplicate values from an array into a new array, optionally
764
     * ignoring the case of the values (assuming they are strings?). A new array
765
     * is returned, the input array is left unchanged.
766
     *
767
     * @param array $array
768
     *  the array to filter.
769
     * @param boolean $ignore_case
770
     *  true if the case of the values in the array should be ignored, false otherwise.
771
     * @return array
772
     *  a new array containing only the unique elements of the input array.
773
     */
774
    public static function array_remove_duplicates(array $array, $ignore_case = false)
775
    {
776
        return ($ignore_case === true ? self::array_iunique($array) : array_unique($array));
777
    }
778
779
    /**
780
     * Test whether a value is in an array based on string comparison, ignoring
781
     * the case of the values.
782
     *
783
     * @param mixed $needle
784
     *  the object to search the array for.
785
     * @param array $haystack
786
     *  the array to search for the `$needle`.
787
     * @return boolean
788
     *  true if the `$needle` is in the `$haystack`, false otherwise.
789
     */
790
    public static function in_iarray($needle, array $haystack)
791
    {
792
        foreach ($haystack as $key => $value) {
793
            if (strcasecmp($value, $needle) == 0) {
794
                return true;
795
            }
796
        }
797
        return false;
798
    }
799
800
    /**
801
     * Filter the input array for duplicates, treating each element in the array
802
     * as a string and comparing them using a case insensitive comparison function.
803
     *
804
     * @param array $array
805
     *  the array to filter.
806
     * @return array
807
     *  a new array containing only the unique elements of the input array.
808
     */
809
    public static function array_iunique(array $array)
810
    {
811
        $tmp = array();
812
813
        foreach ($array as $key => $value) {
814
            if (!self::in_iarray($value, $tmp)) {
815
                $tmp[$key] = $value;
816
            }
817
        }
818
819
        return $tmp;
820
    }
821
822
    /**
823
     * Function recursively apply a function to an array's values.
824
     * This will not touch the keys, just the values.
825
     *
826
     * @since Symphony 2.2
827
     * @param string $function
828
     * @param array $array
829
     * @return array
830
     *  a new array with all the values passed through the given `$function`
831
     */
832
    public static function array_map_recursive($function, array $array)
833
    {
834
        $tmp = array();
835
836
        foreach ($array as $key => $value) {
837
            if (is_array($value)) {
838
                $tmp[$key] = self::array_map_recursive($function, $value);
839
            } else {
840
                $tmp[$key] = call_user_func($function, $value);
841
            }
842
        }
843
844
        return $tmp;
845
    }
846
847
    /**
848
     * Convert an array into an XML fragment and append it to an existing
849
     * XML element. Any arrays contained as elements in the input array will
850
     * also be recursively formatted and appended to the input XML fragment.
851
     * The input XML element will be modified as a result of calling this.
852
     *
853
     * @param XMLElement $parent
854
     *  the XML element to append the formatted array data to.
855
     * @param array $data
856
     *  the array to format and append to the XML fragment.
857
     * @param boolean $validate
858
     *  true if the formatted array data should be validated as it is
859
     *  constructed, false otherwise.
860
     */
861
    public static function array_to_xml(XMLElement $parent, array $data, $validate = false)
862
    {
863
        foreach ($data as $element_name => $value) {
864
            if (!is_numeric($value) && empty($value)) {
865
                continue;
866
            }
867
868
            if (is_int($element_name)) {
869
                $child = new XMLElement('item');
870
                $child->setAttribute('index', $element_name + 1);
871
            } else {
872
                $child = new XMLElement($element_name, null, array(), true);
873
            }
874
875
            if (is_array($value) || is_object($value)) {
876
                self::array_to_xml($child, (array)$value);
877
878
                if ($child->getNumberOfChildren() == 0) {
879
                    continue;
880
                }
881
            } elseif ($validate === true && !self::validateXML(self::sanitize($value), $errors, false, new XSLTProcess)) {
882
                continue;
883
            } else {
884
                $child->setValue(self::sanitize($value));
885
            }
886
887
            $parent->appendChild($child);
888
        }
889
    }
890
891
    /**
892
     * Create a file at the input path with the (optional) input permissions
893
     * with the input content. This function will ignore errors in opening,
894
     * writing, closing and changing the permissions of the resulting file.
895
     * If opening or writing the file fail then this will return false.
896
     * This method calls `clearstatcache()` in order to make sure we do not
897
     * hit the cache when checking for permissions.
898
     *
899
     * @param string $file
900
     *  the path of the file to write.
901
     * @param mixed $data
902
     *  the data to write to the file.
903
     * @param integer|null $perm (optional)
904
     *  the permissions as an octal number to set set on the resulting file.
905
     *  this defaults to 0644 (if omitted or set to null)
906
     * @param string $mode (optional)
907
     *  the mode that the file should be opened with, defaults to 'w'. See modes
908
     *  at http://php.net/manual/en/function.fopen.php
909
     * @param boolean $trim (optional)
910
     *  removes tripple linebreaks
911
     * @return boolean
912
     *  true if the file is successfully opened, written to, closed and has the
913
     *  required permissions set. false, otherwise.
914
     */
915
    public static function writeFile($file, $data, $perm = 0644, $mode = 'w', $trim = false)
916
    {
917
        clearstatcache();
918
919
        if (static::checkFile($file) === false) {
920
            return false;
921
        }
922
923
        if (!$handle = fopen($file, $mode)) {
924
            return false;
925
        }
926
927
        if ($trim === true) {
928
            $data = preg_replace("/(" . PHP_EOL . "([ |\t]+)?){2,}" . PHP_EOL . "/", PHP_EOL . PHP_EOL, trim($data));
929
        }
930
931
        if (fwrite($handle, $data, strlen($data)) === false) {
932
            return false;
933
        }
934
935
        fclose($handle);
936
937
        try {
938
            if (is_null($perm)) {
939
                $perm = 0644;
940
            }
941
942
            chmod($file, intval($perm, 8));
943
        } catch (Exception $ex) {
944
            // If we can't chmod the file, this is probably because our host is
945
            // running PHP with a different user to that of the file. Although we
946
            // can delete the file, create a new one and then chmod it, we run the
947
            // risk of losing the file as we aren't saving it anywhere. For the immediate
948
            // future, atomic saving isn't needed by Symphony and it's recommended that
949
            // if your extension require this logic, it uses it's own function rather
950
            // than this 'General' one.
951
            return true;
952
        }
953
954
        return true;
955
    }
956
957
    /**
958
     * Checks that the file and it's folder are readable and writable.
959
     *
960
     * @since Symphony 2.6.3
961
     * @return boolean
962
     */
963
    public static function checkFile($file)
964
    {
965
        clearstatcache();
966
        $dir = dirname($file);
967
968
        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...
969
            (!is_writable($dir) || !is_readable($dir)) // Folder
970
            || (file_exists($file) && (!is_readable($file) || !is_writable($file))) // File
971
        ) {
972
            return false;
973
        }
974
975
        return true;
976
    }
977
978
    /**
979
     * Delete a file at a given path, silently ignoring errors depending
980
     * on the value of the input variable $silent.
981
     *
982
     * @param string $file
983
     *  the path of the file to delete
984
     * @param boolean $silent (optional)
985
     *  true if an exception should be raised if an error occurs, false
986
     *  otherwise. this defaults to true.
987
     * @throws Exception
988
     * @return boolean
989
     *  true if the file is successfully unlinked, if the unlink fails and
990
     *  silent is set to true then an exception is thrown. if the unlink
991
     *  fails and silent is set to false then this returns false.
992
     */
993
    public static function deleteFile($file, $silent = true)
994
    {
995
        try {
996
            return unlink($file);
997
        } catch (Exception $ex) {
998
            if ($silent === false) {
999
                throw new Exception(__('Unable to remove file - %s', array($file)));
1000
            }
1001
1002
            return false;
1003
        }
1004
    }
1005
1006
    /**
1007
     * Extract the file extension from the input file path.
1008
     *
1009
     * @param string $file
1010
     *  the path of the file to extract the extension of.
1011
     * @return array
1012
     *  an array with a single key 'extension' and a value of the extension
1013
     *  of the input path.
1014
     */
1015
    public static function getExtension($file)
1016
    {
1017
        return pathinfo($file, PATHINFO_EXTENSION);
1018
    }
1019
1020
    /**
1021
     * Gets mime type of a file.
1022
     *
1023
     * For email attachments, the mime type is very important.
1024
     * Uses the PHP 5.3 function `finfo_open` when available, otherwise falls
1025
     * back to using a mapping of known of common mimetypes. If no matches
1026
     * are found `application/octet-stream` will be returned.
1027
     *
1028
     * @author Michael Eichelsdoerfer
1029
     * @author Huib Keemink
1030
     * @param string $file
1031
     * @return string|boolean
1032
     *  the mime type of the file, or false is none found
1033
     */
1034
    public function getMimeType($file)
1035
    {
1036
        if (!empty($file)) {
1037
            // in PHP 5.3 we can use 'finfo'
1038
            if (PHP_VERSION_ID >= 50300 && function_exists('finfo_open')) {
1039
                $finfo = finfo_open(FILEINFO_MIME_TYPE);
1040
                $mime_type = finfo_file($finfo, $file);
1041
                finfo_close($finfo);
1042
            } else {
1043
                // A few mimetypes to "guess" using the file extension.
1044
                $mimetypes = array(
1045
                    'txt'   => 'text/plain',
1046
                    'csv'   => 'text/csv',
1047
                    'pdf'   => 'application/pdf',
1048
                    'doc'   => 'application/msword',
1049
                    'docx'  => 'application/msword',
1050
                    'xls'   => 'application/vnd.ms-excel',
1051
                    'ppt'   => 'application/vnd.ms-powerpoint',
1052
                    'eps'   => 'application/postscript',
1053
                    'zip'   => 'application/zip',
1054
                    'gif'   => 'image/gif',
1055
                    'jpg'   => 'image/jpeg',
1056
                    'jpeg'  => 'image/jpeg',
1057
                    'png'   => 'image/png',
1058
                    'mp3'   => 'audio/mpeg',
1059
                    'mp4a'  => 'audio/mp4',
1060
                    'aac'   => 'audio/x-aac',
1061
                    'aif'   => 'audio/x-aiff',
1062
                    'aiff'  => 'audio/x-aiff',
1063
                    'wav'   => 'audio/x-wav',
1064
                    'wma'   => 'audio/x-ms-wma',
1065
                    'mpeg'  => 'video/mpeg',
1066
                    'mpg'   => 'video/mpeg',
1067
                    'mp4'   => 'video/mp4',
1068
                    'mov'   => 'video/quicktime',
1069
                    'avi'   => 'video/x-msvideo',
1070
                    'wmv'   => 'video/x-ms-wmv',
1071
                );
1072
1073
                $extension = substr(strrchr($file, '.'), 1);
1074
1075
                if ($mimetypes[strtolower($extension)] !== null) {
1076
                    $mime_type = $mimetypes[$extension];
1077
                } else {
1078
                    $mime_type = 'application/octet-stream';
1079
                }
1080
            }
1081
1082
            return $mime_type;
1083
        }
1084
        return false;
1085
    }
1086
1087
    /**
1088
     * Construct a multi-dimensional array that reflects the directory
1089
     * structure of a given path.
1090
     *
1091
     * @param string $dir (optional)
1092
     *  the path of the directory to construct the multi-dimensional array
1093
     *  for. this defaults to '.'.
1094
     * @param string $filter (optional)
1095
     *  A regular expression to filter the directories. This is positive filter, ie.
1096
     * if the filter matches, the directory is included. Defaults to null.
1097
     * @param boolean $recurse (optional)
1098
     *  true if sub-directories should be traversed and reflected in the
1099
     *  resulting array, false otherwise.
1100
     * @param mixed $strip_root (optional)
1101
     *  If null, the full path to the file will be returned, otherwise the value
1102
     *  of `strip_root` will be removed from the file path.
1103
     * @param array $exclude (optional)
1104
     *  ignore directories listed in this array. this defaults to an empty array.
1105
     * @param boolean $ignore_hidden (optional)
1106
     *  ignore hidden directory (i.e.directories that begin with a period). this defaults
1107
     *  to true.
1108
     * @return null|array
1109
     *  return the array structure reflecting the input directory or null if
1110
     * the input directory is not actually a directory.
1111
     */
1112
    public static function listDirStructure($dir = '.', $filter = null, $recurse = true, $strip_root = null, $exclude = array(), $ignore_hidden = true)
1113
    {
1114
        if (!is_dir($dir)) {
1115
            return null;
1116
        }
1117
1118
        $files = array();
1119
1120
        foreach (scandir($dir) as $file) {
1121
            if (
1122
                ($file == '.' || $file == '..')
1123
                || ($ignore_hidden && $file{0} == '.')
1124
                || !is_dir("$dir/$file")
1125
                || in_array($file, $exclude)
1126
                || in_array("$dir/$file", $exclude)
1127
            ) {
1128
                continue;
1129
            }
1130
1131
            if (!is_null($filter)) {
1132
                if (!preg_match($filter, $file)) {
1133
                    continue;
1134
                }
1135
            }
1136
1137
            $files[] = rtrim(str_replace($strip_root, '', $dir), '/') ."/$file/";
1138
1139
            if ($recurse) {
1140
                $files = @array_merge($files, self::listDirStructure("$dir/$file", $filter, $recurse, $strip_root, $exclude, $ignore_hidden));
1141
            }
1142
        }
1143
1144
        return $files;
1145
    }
1146
1147
    /**
1148
     * Construct a multi-dimensional array that reflects the directory
1149
     * structure of a given path grouped into directory and file keys
1150
     * matching any input constraints.
1151
     *
1152
     * @param string $dir (optional)
1153
     *  the path of the directory to construct the multi-dimensional array
1154
     *  for. this defaults to '.'.
1155
     * @param array|string $filters (optional)
1156
     *  either a regular expression to filter the files by or an array of
1157
     *  files to include.
1158
     * @param boolean $recurse (optional)
1159
     *  true if sub-directories should be traversed and reflected in the
1160
     *  resulting array, false otherwise.
1161
     * @param string $sort (optional)
1162
     *  'asc' if the resulting filelist array should be sorted, anything else otherwise.
1163
     *  this defaults to 'asc'.
1164
     * @param mixed $strip_root (optional)
1165
     *  If null, the full path to the file will be returned, otherwise the value
1166
     *  of `strip_root` will be removed from the file path.
1167
     * @param array $exclude (optional)
1168
     *  ignore files listed in this array. this defaults to an empty array.
1169
     * @param boolean $ignore_hidden (optional)
1170
     *  ignore hidden files (i.e. files that begin with a period). this defaults
1171
     *  to true.
1172
     * @return null|array
1173
     *  return the array structure reflecting the input directory or null if
1174
     * the input directory is not actually a directory.
1175
     */
1176
    public static function listStructure($dir = ".", $filters = array(), $recurse = true, $sort = "asc", $strip_root = null, $exclude = array(), $ignore_hidden = true)
1177
    {
1178
        if (!is_dir($dir)) {
1179
            return null;
1180
        }
1181
1182
        // Check to see if $filters is a string containing a regex, or an array of file types
1183
        if (is_array($filters) && !empty($filters)) {
1184
            $filter_type = 'file';
1185
        } elseif (is_string($filters)) {
1186
            $filter_type = 'regex';
1187
        } else {
1188
            $filter_type = null;
1189
        }
1190
        $files = array();
1191
1192
        $prefix = str_replace($strip_root, '', $dir);
1193
1194
        if ($prefix !== "" && substr($prefix, -1) !== "/") {
1195
            $prefix .= "/";
1196
        }
1197
1198
        $files['dirlist'] = array();
1199
        $files['filelist'] = array();
1200
1201
        foreach (scandir($dir) as $file) {
1202
            if (
1203
                ($file == '.' || $file === '..')
1204
                || ($ignore_hidden && $file{0} === '.')
1205
                || in_array($file, $exclude)
1206
                || in_array("$dir/$file", $exclude)
1207
            ) {
1208
                continue;
1209
            }
1210
1211
            $dir = rtrim($dir, '/');
1212
1213
            if (is_dir("$dir/$file")) {
1214
                if ($recurse) {
1215
                    $files["$prefix$file/"] = self::listStructure("$dir/$file", $filters, $recurse, $sort, $strip_root, $exclude, $ignore_hidden);
1216
                }
1217
1218
                $files['dirlist'][] = "$prefix$file/";
1219
            } elseif ($filter_type === 'regex') {
1220
                if (preg_match($filters, $file)) {
1221
                    $files['filelist'][] = "$prefix$file";
1222
                }
1223
            } elseif ($filter_type === 'file') {
1224
                if (in_array(self::getExtension($file), $filters)) {
1225
                    $files['filelist'][] = "$prefix$file";
1226
                }
1227
            } elseif (is_null($filter_type)) {
1228
                $files['filelist'][] = "$prefix$file";
1229
            }
1230
        }
1231
1232
        if (is_array($files['filelist'])) {
1233
            ($sort == 'desc') ? rsort($files['filelist']) : sort($files['filelist']);
1234
        }
1235
1236
        return $files;
1237
    }
1238
1239
    /**
1240
     * Count the number of words in a string. Words are delimited by "spaces".
1241
     * The characters included in the set of "spaces" are:
1242
     *  '&#x2002;', '&#x2003;', '&#x2004;', '&#x2005;',
1243
     *  '&#x2006;', '&#x2007;', '&#x2009;', '&#x200a;',
1244
     *  '&#x200b;', '&#x2002f;', '&#x205f;'
1245
     * Any html/xml tags are first removed by strip_tags() and any included html
1246
     * entities are decoded. The resulting string is then split by the above set
1247
     * of spaces and the resulting size of the resulting array returned.
1248
     *
1249
     * @param string $string
1250
     *  the string from which to count the contained words.
1251
     * @return integer
1252
     *  the number of words contained in the input string.
1253
     */
1254
    public static function countWords($string)
1255
    {
1256
        $string = strip_tags($string);
1257
1258
        // Strip spaces:
1259
        $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');
1260
        $spaces = array(
1261
            '&#x2002;', '&#x2003;', '&#x2004;', '&#x2005;',
1262
            '&#x2006;', '&#x2007;', '&#x2009;', '&#x200a;',
1263
            '&#x200b;', '&#x2002f;', '&#x205f;'
1264
        );
1265
1266
        foreach ($spaces as &$space) {
1267
            $space = html_entity_decode($space, ENT_NOQUOTES, 'UTF-8');
1268
        }
1269
1270
        $string = str_replace($spaces, ' ', $string);
1271
        $string = preg_replace('/[^\w\s]/i', '', $string);
1272
1273
        return str_word_count($string);
1274
    }
1275
1276
    /**
1277
     * Truncate a string to a given length. Newlines are replaced with `<br />`
1278
     * html elements and html tags are removed from the string. If the resulting
1279
     * string contains only spaces then null is returned. If the resulting string
1280
     * is less than the input length then it is returned. If the option to
1281
     * truncate the string to a space character is provided then the string is
1282
     * truncated to the character prior to the last space in the string. Words
1283
     * (contiguous non-' ' characters) are then removed from the end of the string
1284
     * until the length of resulting string is within the input bound. Initial
1285
     * and trailing spaces are removed. Provided the user requested an
1286
     * ellipsis suffix and the resulting string is shorter than the input string
1287
     * then the ellipses are appended to the result which is then returned.
1288
     *
1289
     * @param string $string
1290
     *  the string to truncate.
1291
     * @param integer maxChars (optional)
1292
     *  the maximum length of the string to truncate the input string to. this
1293
     *  defaults to 200 characters.
1294
     * @param boolean $appendHellip (optional)
1295
     *  true if the ellipses should be appended to the result in circumstances
1296
     *  where the result is shorter than the input string. false otherwise. this
1297
     *  defaults to false.
1298
     * @return null|string
1299
     *  if the resulting string contains only spaces then null is returned. otherwise
1300
     *  a string that satisfies the input constraints.
1301
     */
1302
    public static function limitWords($string, $maxChars = 200, $appendHellip = false)
1303
    {
1304
        if ($appendHellip) {
1305
            $maxChars -= 1;
1306
        }
1307
1308
        $string = trim(strip_tags(nl2br($string)));
1309
        $original_length = strlen($string);
1310
1311
        if ($original_length == 0) {
1312
            return null;
1313
        } elseif ($original_length < $maxChars) {
1314
            return $string;
1315
        }
1316
1317
        $string = trim(substr($string, 0, $maxChars));
1318
1319
        $array = explode(' ', $string);
1320
        $length = 0;
1321
1322
        while (!empty($array) && $length > $maxChars) {
1323
            $length += strlen(array_pop($array)) + 1;
1324
        }
1325
1326
        $result = implode(' ', $array);
1327
1328
        if ($appendHellip && strlen($result) < $original_length) {
1329
            $result .= "&#8230;";
1330
        }
1331
1332
        return($result);
1333
    }
1334
1335
    /**
1336
     * Move a file from the source path to the destination path and name and
1337
     * set its permissions to the input permissions. This will ignore errors
1338
     * in the `is_uploaded_file()`, `move_uploaded_file()` and `chmod()` functions.
1339
     *
1340
     * @param string $dest_path
1341
     *  the file path to which the source file is to be moved.
1342
     * @param string #dest_name
1343
     *  the file name within the file path to which the source file is to be moved.
1344
     * @param string $tmp_name
1345
     *  the full path name of the source file to move.
1346
     * @param integer $perm (optional)
1347
     *  the permissions to apply to the moved file. this defaults to 0777.
1348
     * @return boolean
1349
     *  true if the file was moved and its permissions set as required. false otherwise.
1350
     */
1351
    public static function uploadFile($dest_path, $dest_name, $tmp_name, $perm = 0777)
1352
    {
1353
        // Upload the file
1354
        if (@is_uploaded_file($tmp_name)) {
1355
            $dest_path = rtrim($dest_path, '/') . '/';
1356
1357
            // Try place the file in the correction location
1358
            if (@move_uploaded_file($tmp_name, $dest_path . $dest_name)) {
1359
                chmod($dest_path . $dest_name, intval($perm, 8));
1360
                return true;
1361
            }
1362
        }
1363
1364
        // Could not move the file
1365
        return false;
1366
    }
1367
1368
    /**
1369
     * Format a number of bytes in human readable format. This will append MB as
1370
     * appropriate for values greater than 1,024*1,024, KB for values between
1371
     * 1,024 and 1,024*1,024-1 and bytes for values between 0 and 1,024.
1372
     *
1373
     * @param integer $file_size
1374
     *  the number to format.
1375
     * @return string
1376
     *  the formatted number.
1377
     */
1378
    public static function formatFilesize($file_size)
1379
    {
1380
        $file_size = intval($file_size);
1381
1382
        if ($file_size >= (1024 * 1024)) {
1383
            $file_size = number_format($file_size * (1 / (1024 * 1024)), 2) . ' MB';
1384
        } elseif ($file_size >= 1024) {
1385
            $file_size = intval($file_size * (1/1024)) . ' KB';
1386
        } else {
1387
            $file_size = intval($file_size) . ' bytes';
1388
        }
1389
1390
        return $file_size;
1391
    }
1392
1393
    /**
1394
     * Gets the number of bytes from 'human readable' size value. Supports
1395
     * the output of `General::formatFilesize` as well as reading values
1396
     * from the PHP configuration. eg. 1 MB or 1M
1397
     *
1398
     * @since Symphony 2.5.2
1399
     * @param string $file_size
1400
     * @return integer
1401
     */
1402
    public static function convertHumanFileSizeToBytes($file_size)
1403
    {
1404
        $file_size = str_replace(
1405
            array(' MB', ' KB', ' bytes'),
1406
            array('M', 'K', 'B'),
1407
            trim($file_size)
1408
        );
1409
1410
        $last = strtolower($file_size[strlen($file_size)-1]);
1411 View Code Duplication
        switch ($last) {
1412
            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...
1413
                $file_size *= 1024;
1414
            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...
1415
                $file_size *= 1024;
1416
            case 'k':
1417
                $file_size *= 1024;
1418
        }
1419
1420
        return $file_size;
1421
    }
1422
1423
    /**
1424
     * Construct an XML fragment that reflects the structure of the input timestamp.
1425
     *
1426
     * @param integer $timestamp
1427
     *  the timestamp to construct the XML element from.
1428
     * @param string $element (optional)
1429
     *  the name of the element to append to the namespace of the constructed XML.
1430
     *  this defaults to "date".
1431
     * @param string $date_format (optional)
1432
     *  the format to apply to the date, defaults to `Y-m-d`
1433
     * @param string $time_format (optional)
1434
     *  the format to apply to the date, defaults to `H:i`
1435
     * @param string $namespace (optional)
1436
     *  the namespace in which the resulting XML entity will reside. this defaults
1437
     *  to null.
1438
     * @return boolean|XMLElement
1439
     *  false if there is no XMLElement class on the system, the constructed XML element
1440
     *  otherwise.
1441
     */
1442
    public static function createXMLDateObject($timestamp, $element = 'date', $date_format = 'Y-m-d', $time_format = 'H:i', $namespace = null)
1443
    {
1444
        if (!class_exists('XMLElement')) {
1445
            return false;
1446
        }
1447
1448
        $xDate = new XMLElement(
1449
            (!is_null($namespace) ? $namespace . ':' : '') . $element,
1450
            DateTimeObj::get($date_format, $timestamp),
0 ignored issues
show
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...
1451
            array(
1452
                'iso' => DateTimeObj::get('c', $timestamp),
1453
                'timestamp' => DateTimeObj::get('U', $timestamp),
1454
                'time' => DateTimeObj::get($time_format, $timestamp),
1455
                'weekday' => DateTimeObj::get('N', $timestamp),
1456
                'offset' => DateTimeObj::get('O', $timestamp)
1457
            )
1458
        );
1459
1460
        return $xDate;
1461
    }
1462
1463
    /**
1464
     * Construct an XML fragment that describes a pagination structure.
1465
     *
1466
     * @param integer $total_entries (optional)
1467
     *  the total number of entries that this structure is paginating. this
1468
     *  defaults to 0.
1469
     * @param integer $total_pages (optional)
1470
     *  the total number of pages within the pagination structure. this defaults
1471
     *  to 0.
1472
     * @param integer $entries_per_page (optional)
1473
     *  the number of entries per page. this defaults to 1.
1474
     * @param integer $current_page (optional)
1475
     *  the current page within the total number of pages within this pagination
1476
     *  structure. this defaults to 1.
1477
     * @return XMLElement
1478
     *  the constructed XML fragment.
1479
     */
1480
    public static function buildPaginationElement($total_entries = 0, $total_pages = 0, $entries_per_page = 1, $current_page = 1)
1481
    {
1482
        $pageinfo = new XMLElement('pagination');
1483
1484
        $pageinfo->setAttribute('total-entries', $total_entries);
1485
        $pageinfo->setAttribute('total-pages', $total_pages);
1486
        $pageinfo->setAttribute('entries-per-page', $entries_per_page);
1487
        $pageinfo->setAttribute('current-page', $current_page);
1488
1489
        return $pageinfo;
1490
    }
1491
1492
    /**
1493
     * Uses `SHA1` or `MD5` to create a hash based on some input
1494
     * This function is currently very basic, but would allow
1495
     * future expansion. Salting the hash comes to mind.
1496
     *
1497
     * @param string $input
1498
     *  the string to be hashed
1499
     * @param string $algorithm
1500
     *  This function supports 'md5', 'sha1' and 'pbkdf2'. Any
1501
     *  other algorithm will default to 'pbkdf2'.
1502
     * @return string
1503
     *  the hashed string
1504
     */
1505
    public static function hash($input, $algorithm = 'sha1')
1506
    {
1507
        switch ($algorithm) {
1508
            case 'sha1':
1509
                return SHA1::hash($input);
1510
1511
            case 'md5':
1512
                return MD5::hash($input);
1513
1514
            case 'pbkdf2':
1515
            default:
1516
                return Crytography::hash($input, $algorithm);
1517
        }
1518
    }
1519
1520
    /**
1521
     * Helper to cut down on variables' type check.
1522
     * Currently known types are the PHP defaults.
1523
     * Uses `is_XXX()` functions internally.
1524
     *
1525
     * @since Symphony 2.3
1526
     *
1527
     * @param array $params - an array of arrays containing variables info
1528
     *
1529
     *  Array[
1530
     *      $key1 => $value1
1531
     *      $key2 => $value2
1532
     *      ...
1533
     *  ]
1534
     *
1535
     *  $key = the name of the variable
1536
     *  $value = Array[
1537
     *      'var' => the variable to check
1538
     *      'type' => enforced type. Must match the XXX part from an `is_XXX()` function
1539
     *      'optional' => boolean. If this is set, the default value of the variable must be null
1540
     *  ]
1541
     *
1542
     * @throws InvalidArgumentException if validator doesn't exist.
1543
     * @throws InvalidArgumentException if variable type validation fails.
1544
     *
1545
     * @example
1546
     *  $color = 'red';
1547
     *  $foo = null;
1548
     *  $bar = 21;
1549
     *
1550
     *  General::ensureType(array(
1551
     *      'color' => array('var' => $color, 'type'=> 'string'),               // success
1552
     *      'foo' => array('var' => $foo, 'type'=> 'int',  'optional' => true), // success
1553
     *      'bar' => array('var' => $bar, 'type'=> 'string')                    // fail
1554
     *  ));
1555
     */
1556
    public static function ensureType(array $params)
1557
    {
1558
        foreach ($params as $name => $param) {
1559
            if (isset($param['optional']) && ($param['optional'] === true)) {
1560
                if (is_null($param['var'])) {
1561
                    continue;
1562
                }
1563
                // if not null, check it's type
1564
            }
1565
1566
            // validate the validator
1567
            $validator = 'is_'.$param['type'];
1568
1569
            if (!function_exists($validator)) {
1570
                throw new InvalidArgumentException(__('Enforced type `%1$s` for argument `$%2$s` does not match any known variable types.', array($param['type'], $name)));
1571
            }
1572
1573
            // validate variable type
1574
            if (!call_user_func($validator, $param['var'])) {
1575
                throw new InvalidArgumentException(__('Argument `$%1$s` is not of type `%2$s`, given `%3$s`.', array($name, $param['type'], gettype($param['var']))));
1576
            }
1577
        }
1578
    }
1579
1580
1581
    /**
1582
     * Wrap a value in CDATA tags for XSL output of non encoded data, only
1583
     * if not already wrapped.
1584
     *
1585
     * @since Symphony 2.3.2
1586
     *
1587
     * @param string $value
1588
     *  The string to wrap in CDATA
1589
     * @return string
1590
     *  The wrapped string
1591
     */
1592
    public static function wrapInCDATA($value)
1593
    {
1594
        if (empty($value)) {
1595
            return $value;
1596
        }
1597
1598
        $startRegExp = '/^' . preg_quote(CDATA_BEGIN) . '/';
1599
        $endRegExp = '/' . preg_quote(CDATA_END) . '$/';
1600
1601
        if (!preg_match($startRegExp, $value)) {
1602
            $value = CDATA_BEGIN . $value;
1603
        }
1604
1605
        if (!preg_match($endRegExp, $value)) {
1606
            $value .= CDATA_END;
1607
        }
1608
1609
        return $value;
1610
    }
1611
1612
    /**
1613
     * Unwrap a value from CDATA tags to return the raw string
1614
     *
1615
     * @since Symphony 2.3.4
1616
     * @param string $value
1617
     *  The string to unwrap from CDATA
1618
     * @return string
1619
     *  The unwrapped string
1620
     */
1621
    public static function unwrapCDATA($value)
1622
    {
1623
        return str_replace(array(CDATA_BEGIN, CDATA_END), '', $value);
1624
    }
1625
1626
    /**
1627
     * Converts a value to a positive integer. This method makes sure that the
1628
     * value is a valid positive integer representation before doing the cast.
1629
     *
1630
     * @since Symphony 2.5
1631
     * @param mixed $value
1632
     *  The value to cast to an integer
1633
     * @return int
1634
     *  The casted integer value if the input is valid, -1 otherwise.
1635
     */
1636
    public static function intval($value)
1637
    {
1638
        if (is_numeric($value) && preg_match('/^[0-9]+$/i', $value) === 1) {
1639
            return intval($value);
1640
        }
1641
1642
        return -1;
1643
    }
1644
}
1645