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 — integration ( a3ab80...7a98f7 )
by Brendan
06:22
created

General::countWords()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 20
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 29
rs 8.8571
1
<?php
2
    /**
3
     * @package toolkit
4
     */
5
6
    /**
7
     * General is a utility class that offers a number miscellaneous of
8
     * functions that are used throughout Symphony.
9
     */
10
    class General
11
    {
12
        /**
13
         * Revert any html entities to their character equivalents.
14
         *
15
         * @param string $str
16
         *  a string to operate on
17
         * @return string
18
         *  the decoded version of the string
19
         */
20
        public static function reverse_sanitize($str)
21
        {
22
            return htmlspecialchars_decode($str, ENT_COMPAT);
23
        }
24
25
        /**
26
         * Validate a string against a set of regular expressions.
27
         *
28
         * @param array|string $string
29
         *  string to operate on
30
         * @param array|string $rule
31
         *  a single rule or array of rules
32
         * @return boolean
33
         *  false if any of the rules in $rule do not match any of the strings in
34
         *  `$string`, return true otherwise.
35
         */
36
        public static function validateString($string, $rule)
37
        {
38
            if (!is_array($rule) && ($rule === '' || $rule === null)) {
39
                return true;
40
            }
41
42
            if (!is_array($string) && ($string === '' || $rule === null)) {
43
                return true;
44
            }
45
46
            if (!is_array($rule)) {
47
                $rule = array($rule);
48
            }
49
50
            if (!is_array($string)) {
51
                $string = array($string);
52
            }
53
54
            foreach ($rule as $r) {
55
                foreach ($string as $s) {
56
                    if (!preg_match($r, $s)) {
57
                        return false;
58
                    }
59
                }
60
            }
61
62
            return true;
63
        }
64
65
        /**
66
         * Replace the tabs with spaces in the input string.
67
         *
68
         * @param string $string
69
         *  the string in which to replace the tabs with spaces.
70
         * @param integer $spaces (optional)
71
         *  the number of spaces to replace each tab with. This argument is optional
72
         *  with a default of 4.
73
         * @return string
74
         *  the resulting string.
75
         */
76
        public static function tabsToSpaces($string, $spaces = 4)
77
        {
78
            return str_replace("\t", str_pad(null, $spaces), $string);
79
        }
80
81
        /**
82
         * Check that a string is a valid URL.
83
         *
84
         * @param string $url
85
         *  string to operate on
86
         * @return string
87
         *  a blank string or a valid URL
88
         */
89
        public static function validateURL($url = null)
90
        {
91
            $url = trim($url);
92
93
            if (is_null($url) || $url === '') {
94
                return $url;
95
            }
96
97
            if (!preg_match('#^http[s]?:\/\/#i', $url)) {
98
                $url = 'http://' . $url;
99
            }
100
101
            include TOOLKIT . '/util.validators.php';
102
103
            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...
104
                $url = '';
105
            }
106
107
            return $url;
108
        }
109
110
        /**
111
         * Strip any slashes from all array values.
112
         *
113
         * @param array &$arr
114
         *  Pointer to an array to operate on. Can be multi-dimensional.
115
         */
116
        public static function cleanArray(array &$arr)
117
        {
118
            foreach ($arr as $k => $v) {
119
                if (is_array($v)) {
120
                    self::cleanArray($arr[$k]);
121
                } else {
122
                    $arr[$k] = stripslashes($v);
123
                }
124
            }
125
        }
126
127
        /**
128
         * Flatten the input array. Any elements of the input array that are
129
         * themselves arrays will be removed and the contents of the removed array
130
         * inserted in its place. The keys for the inserted values will be the
131
         * concatenation of the keys in the original arrays in which it was embedded.
132
         * The elements of the path are separated by periods (.). For example,
133
         * given the following nested array structure:
134
         * `
135
         * array(1 =>
136
         *          array('key' => 'value'),
137
         *      2 =>
138
         *          array('key2' => 'value2', 'key3' => 'value3')
139
         *      )
140
         * `
141
         * will flatten to:
142
         * `array('1.key' => 'value', '2.key2' => 'value2', '2.key3' => 'value3')`
143
         *
144
         * @param array &$source
145
         *  The array to flatten, passed by reference
146
         * @param array &$output (optional)
147
         *  The array in which to store the flattened input, passed by reference.
148
         *  if this is not provided then a new array will be created.
149
         * @param string $path (optional)
150
         *  the current prefix of the keys to insert into the output array. this
151
         *  defaults to null.
152
         */
153
        public static function flattenArray(array &$source, &$output = null, $path = null)
154
        {
155
            if (is_null($output)) {
156
                $output = array();
157
            }
158
159
            foreach ($source as $key => $value) {
160
                if (is_int($key)) {
161
                    $key = (string)($key + 1);
162
                }
163
164
                if (!is_null($path)) {
165
                    $key = $path . '.' . (string)$key;
166
                }
167
168
                if (is_array($value)) {
169
                    self::flattenArray($value, $output, $key);
170
                } else {
171
                    $output[$key] = $value;
172
                }
173
            }
174
175
            $source = $output;
176
        }
177
178
        /**
179
         * This method allows an array of variables with placeholders to replaced against
180
         * a given context. Any placeholders that don't exist in the context will be
181
         * left alone.
182
         *
183
         * @since Symphony 3.0
184
         * @param string $value (by reference)
185
         *  Placeholders will be replaced by inspecting the context eg. `{text}` will look
186
         *  for a `text` key in the `$context`. Dot notation supported, eg. `{date.format}`
187
         *  with this context `['date' => ['format' => 'value']]` will return `value`.
188
         * @param string $key
189
         * @param array $context
190
         */
191
        public static function replacePlaceholdersWithContext(&$value, $key, array $context)
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
192
        {
193
            // Check to see if there is replacement to occur
194
            if (preg_match_all('/{([^}]+)}/', $value, $matches, PREG_SET_ORDER)) {
195
                // Backup the original value and context so each match can use the
196
                // original set.
197
                $base_value = $value;
198
                $base_context = $context;
199
200
                // For each match, reset the context to the full stack
201
                foreach ($matches as $match) {
202
                    $context = $base_context;
203
204
                    // The '.' syntax is support for resolving arrays where each dot represents
205
                    // another level. Each time we go down a level, we narrow the context accordingly.
206
                    foreach (explode('.', $match[1]) as $segment) {
207
                        // If the segment exists in the context, narrow the context to the next level
208
                        // and allow the loop to continue.
209
                        if (is_array($context) && array_key_exists($segment, $context)) {
210
                            $context = $context[$segment];
211
                        }
212
213
                        // If the context has fully resolved to a string, replace the relevant
214
                        // match in the base value.
215
                        if (!is_array($context)) {
216
                            $base_value = str_replace('{' . $match[1] . '}', $context, $base_value);
217
                        }
218
                    }
219
                }
220
221
                $value = $base_value;
222
            }
223
        }
224
225
        /**
226
         * Given a string, this will clean it for use as a Symphony handle. Preserves multi-byte characters.
227
         *
228
         * @since Symphony 2.2.1
229
         * @param string $string
230
         *  String to be cleaned up
231
         * @param integer $max_length
232
         *  The maximum number of characters in the handle
233
         * @param string $delim
234
         *  All non-valid characters will be replaced with this
235
         * @param boolean $uriencode
236
         *  Force the resultant string to be uri encoded making it safe for URLs
237
         * @param array $additional_rule_set
238
         *  An array of REGEX patterns that should be applied to the `$string`. This
239
         *  occurs after the string has been trimmed and joined with the `$delim`
240
         * @return string
241
         *  Returns resultant handle
242
         */
243
        public static function createHandle(
244
            $string,
245
            $max_length = 255,
246
            $delim = '-',
247
            $uriencode = false,
248
            $additional_rule_set = null
249
        ) {
250
            $max_length = intval($max_length);
251
252
            // make it lowercase
253
254
            $string = strtolower($string);
255
256
            // strip out tags
257
258
            $string = strip_tags($string);
259
260
            // remove unwanted characters
261
262
            $string = preg_replace('/[^\w- ]+/', ' ', $string);
263
264
            // consolidate whitespace
265
266
            $string = preg_replace('/[\s]+/', ' ', $string);
267
            $string = trim($string);
268
269
            // truncate if too long
270
271
            if (strlen($string) > $max_length) {
272
                $string = wordwrap($string, $max_length, '|');
273
                $string = substr($string, 0, strpos($string, '|'));
274
            }
275
276
            // replace whitespace with delimiter
277
278
            $string = str_replace(' ', $delim, $string);
279
280
            // apply additional rules
281
282
            if (is_array($additional_rule_set) && !empty($additional_rule_set)) {
283
                foreach ($additional_rule_set as $rule => $replacement) {
284
                    $string = preg_replace($rule, $replacement, $string);
285
                }
286
            }
287
288
            // encode for URI use
289
290
            if ($uriencode) {
291
                $string = urlencode($string);
292
            }
293
294
            return $string;
295
        }
296
297
        /**
298
         * Given a string, this will clean it for use as a filename. Preserves multi-byte characters.
299
         *
300
         * @since Symphony 2.2.1
301
         * @param string $string
302
         *  String to be cleaned up
303
         * @param string $delim
304
         *  All non-valid characters will be replaced with this
305
         * @return string
306
         *  Returns created filename
307
         */
308
        public static function createFilename($string, $delim = '-')
309
        {
310
            // Strip out any tag
311
            $string = strip_tags($string);
312
313
            // Find all legal characters
314
            $count = preg_match_all('/[\p{L}\w:;.,+=~]+/u', $string, $matches);
315
            if ($count <= 0 || $count === false) {
316
                preg_match_all('/[\w:;.,+=~]+/', $string, $matches);
317
            }
318
319
            // Join only legal character with the $delim
320
            $string = implode($delim, $matches[0]);
321
322
            // Remove leading or trailing delim characters
323
            $string = trim($string, $delim);
324
325
            // Make it lowercase
326
            $string = strtolower($string);
327
328
            return $string;
329
        }
330
331
        /**
332
         * Extract the first `$val` characters of the input string. If `$val`
333
         * is larger than the length of the input string then the original
334
         * input string is returned.
335
         *
336
         * @param string $str
337
         *  the string to operate on
338
         * @param integer $val
339
         *  the number to compare lengths with
340
         * @return string|boolean
341
         *  the resulting string or false on failure.
342
         */
343
        public static function substrmin($str, $val)
344
        {
345
            return (self::substr($str, 0, min(self::strlen($str), $val)));
346
        }
347
348
        /**
349
         * Creates a sub string.
350
         * This function will attempt to use PHP's `mbstring` functions if they are available.
351
         * This function also forces utf-8 encoding.
352
         *
353
         * @since Symphony 2.5.0
354
         * @param string $str
355
         *  the string to operate on
356
         * @param int $start
357
         *  the starting offset
358
         * @param int $length
359
         *  the length of the substring
360
         * @return string
361
         *  the resulting substring
362
         */
363
        public static function substr($str, $start, $length)
364
        {
365
            if (function_exists('mb_substr')) {
366
                return mb_substr($str, $start, $length, 'utf-8');
367
            }
368
369
            return substr($str, $start, $length);
370
        }
371
372
        /**
373
         * Computes the length of the string.
374
         * This function will attempt to use PHP's `mbstring` functions if they are available.
375
         * This function also forces utf-8 encoding.
376
         *
377
         * @since Symphony 2.5.0
378
         * @param string $str
379
         *  the string to operate on
380
         * @return int
381
         *  the string's length
382
         */
383
        public static function strlen($str)
384
        {
385
            if (function_exists('mb_strlen')) {
386
                return mb_strlen($str, 'utf-8');
387
            }
388
389
            return strlen($str);
390
        }
391
392
        /**
393
         * Extract the first `$val` characters of the input string. If
394
         * `$val` is larger than the length of the input string then
395
         * the original input string is returned
396
         *
397
         * @param string $str
398
         *  the string to operate on
399
         * @param integer $val
400
         *  the number to compare lengths with
401
         * @return string|boolean
402
         *  the resulting string or false on failure.
403
         */
404
        public static function substrmax($str, $val)
405
        {
406
            return (self::substr($str, 0, max(self::strlen($str), $val)));
407
        }
408
409
        /**
410
         * Extract the last `$num` characters from a string.
411
         *
412
         * @param string $str
413
         *  the string to extract the characters from.
414
         * @param integer $num
415
         *  the number of characters to extract.
416
         * @return string|boolean
417
         *  a string containing the last `$num` characters of the
418
         *  input string, or false on failure.
419
         */
420
        public static function right($str, $num)
421
        {
422
            $str = self::substr($str, self::strlen($str) - $num, $num);
423
424
            return $str;
425
        }
426
427
        /**
428
         * Extract the first `$num` characters from a string.
429
         *
430
         * @param string $str
431
         *  the string to extract the characters from.
432
         * @param integer $num
433
         *  the number of characters to extract.
434
         * @return string|boolean
435
         *  a string containing the last `$num` characters of the
436
         *  input string, or false on failure.
437
         */
438
        public static function left($str, $num)
439
        {
440
            $str = self::substr($str, 0, $num);
441
442
            return $str;
443
        }
444
445
        /**
446
         * Create all the directories as specified by the input path. If the current
447
         * directory already exists, this function will return true.
448
         *
449
         * @param string $path
450
         *  the path containing the directories to create.
451
         * @param integer $mode (optional)
452
         *  the permissions (in octal) of the directories to create. Defaults to 0755
453
         * @param boolean $silent (optional)
454
         *  true if an exception should be raised if an error occurs, false
455
         *  otherwise. this defaults to true.
456
         * @throws Exception
457
         * @return boolean
458
         */
459
        public static function realiseDirectory($path, $mode = 0755, $silent = true)
460
        {
461
            if (is_dir($path)) {
462
                return true;
463
            }
464
465
            try {
466
                $current_umask = umask(0);
467
                $success = @mkdir($path, intval($mode, 8), true);
468
                umask($current_umask);
469
470
                return $success;
471
            } catch (Exception $ex) {
472
                if ($silent === false) {
473
                    throw new Exception(__('Unable to create path - %s', array($path)));
474
                }
475
476
                return false;
477
            }
478
        }
479
480
        /**
481
         * Recursively deletes all files and directories given a directory. This
482
         * function has two path. This function optionally takes a `$silent` parameter,
483
         * which when `false` will throw an `Exception` if there is an error deleting a file
484
         * or folder.
485
         *
486
         * @since Symphony 2.3
487
         * @param string $dir
488
         *  the path of the directory to delete
489
         * @param boolean $silent (optional)
490
         *  true if an exception should be raised if an error occurs, false
491
         *  otherwise. this defaults to true.
492
         * @throws Exception
493
         * @return boolean
494
         */
495
        public static function deleteDirectory($dir, $silent = true)
496
        {
497
            try {
498
                if (!file_exists($dir)) {
499
                    return true;
500
                }
501
502
                if (!is_dir($dir)) {
503
                    return unlink($dir);
504
                }
505
506
                foreach (scandir($dir) as $item) {
507
                    if ($item === '.' || $item === '..') {
508
                        continue;
509
                    }
510
511
                    if (!self::deleteDirectory($dir . DIRECTORY_SEPARATOR . $item)) {
512
                        return false;
513
                    }
514
                }
515
516
                return rmdir($dir);
517
            } catch (Exception $ex) {
518
                if ($silent === false) {
519
                    throw new Exception(__('Unable to remove - %s', array($dir)));
520
                }
521
522
                return false;
523
            }
524
        }
525
526
        /**
527
         * Search a multi-dimensional array for a value.
528
         *
529
         * @param mixed $needle
530
         *  the value to search for.
531
         * @param array $haystack
532
         *  the multi-dimensional array to search.
533
         * @return boolean
534
         *  true if `$needle` is found in `$haystack`.
535
         *  true if `$needle` === `$haystack`.
536
         *  true if `$needle` is found in any of the arrays contained within `$haystack`.
537
         *  false otherwise.
538
         */
539
        public static function in_array_multi($needle, $haystack)
540
        {
541
            if ($needle === $haystack) {
542
                return true;
543
            }
544
545
            if (is_array($haystack)) {
546
                foreach ($haystack as $key => $val) {
547
                    if (is_array($val)) {
548
                        if (self::in_array_multi($needle, $val)) {
549
                            return true;
550
                        }
551
                    } elseif (!strcmp($needle, $key) || !strcmp($needle, $val)) {
552
                        return true;
553
                    }
554
                }
555
            }
556
557
            return false;
558
        }
559
560
        /**
561
         * Search an array for multiple values.
562
         *
563
         * @param array $needles
564
         *  the values to search the `$haystack` for.
565
         * @param array $haystack
566
         *  the in which to search for the `$needles`
567
         * @return boolean
568
         *  true if any of the `$needles` are in `$haystack`,
569
         *  false otherwise.
570
         */
571
        public static function in_array_all($needles, $haystack)
572
        {
573
            foreach ($needles as $n) {
574
                if (!in_array($n, $haystack)) {
575
                    return false;
576
                }
577
            }
578
579
            return true;
580
        }
581
582
        /**
583
         * Transform a multi-dimensional array to a flat array. The input array
584
         * is expected to conform to the structure of the `$_FILES` variable.
585
         *
586
         * @param array $filedata
587
         *  the raw `$_FILES` data structured array
588
         * @return array
589
         *  the flattened array.
590
         */
591
        public static function processFilePostData($filedata)
592
        {
593
            $result = array();
594
595
            foreach ($filedata as $key => $data) {
596
                foreach ($data as $handle => $value) {
597
                    if (is_array($value)) {
598
                        foreach ($value as $index => $pair) {
599
                            if (!is_array($result[$handle][$index])) {
600
                                $result[$handle][$index] = array();
601
                            }
602
603
                            if (!is_array($pair)) {
604
                                $result[$handle][$index][$key] = $pair;
605
                            } else {
606
                                $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...
607
                            }
608
                        }
609
                    } else {
610
                        $result[$handle][$key] = $value;
611
                    }
612
                }
613
            }
614
615
            return $result;
616
        }
617
618
        /**
619
         * Merge `$_POST` with `$_FILES` to produce a flat array of the contents
620
         * of both. If there is no merge_file_post_data function defined then
621
         * such a function is created. This is necessary to overcome PHP's ability
622
         * to handle forms. This overcomes PHP's convoluted `$_FILES` structure
623
         * to make it simpler to access `multi-part/formdata`.
624
         *
625
         * @return array
626
         *  a flat array containing the flattened contents of both `$_POST` and
627
         *  `$_FILES`.
628
         */
629
        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...
630
        {
631
            if (!function_exists('merge_file_post_data')) {
632
                function merge_file_post_data($type, array $file, &$post)
633
                {
634
                    foreach ($file as $key => $value) {
635
                        if (!isset($post[$key])) {
636
                            $post[$key] = array();
637
                        }
638
639
                        if (is_array($value)) {
640
                            merge_file_post_data($type, $value, $post[$key]);
641
                        } else {
642
                            $post[$key][$type] = $value;
643
                        }
644
                    }
645
                }
646
            }
647
648
            $files = array(
649
                'name' => array(),
650
                'type' => array(),
651
                'tmp_name' => array(),
652
                'error' => array(),
653
                'size' => array()
654
            );
655
            $post = $_POST;
656
657
            if (is_array($_FILES) && !empty($_FILES)) {
658
                foreach ($_FILES as $key_a => $data_a) {
659
                    if (!is_array($data_a)) {
660
                        continue;
661
                    }
662
663
                    foreach ($data_a as $key_b => $data_b) {
664
                        $files[$key_b][$key_a] = $data_b;
665
                    }
666
                }
667
            }
668
669
            foreach ($files as $type => $data) {
670
                merge_file_post_data($type, $data, $post);
671
            }
672
673
            return $post;
674
        }
675
676
        /**
677
         * Find the next available index in an array. Works best with numeric keys.
678
         * The next available index is the minimum integer such that the array does
679
         * not have a mapping for that index. Uses the increment operator on the
680
         * index type of the input array, whatever that may do.
681
         *
682
         * @param array $array
683
         *  the array to find the next index for.
684
         * @param mixed $seed (optional)
685
         *  the object with which the search for an empty index is initialized. this
686
         *  defaults to null.
687
         * @return integer
688
         *  the minimum empty index into the input array.
689
         */
690
        public static function array_find_available_index($array, $seed = null)
691
        {
692
            if (!is_null($seed)) {
693
                $index = $seed;
694
            } else {
695
                $keys = array_keys($array);
696
                sort($keys);
697
                $index = array_pop($keys);
698
            }
699
700
            if (isset($array[$index])) {
701
                do {
702
                    $index++;
703
                } while (isset($array[$index]));
704
            }
705
706
            return $index;
707
        }
708
709
        /**
710
         * Filter the duplicate values from an array into a new array, optionally
711
         * ignoring the case of the values (assuming they are strings?). A new array
712
         * is returned, the input array is left unchanged.
713
         *
714
         * @param array $array
715
         *  the array to filter.
716
         * @param boolean $ignore_case
717
         *  true if the case of the values in the array should be ignored, false otherwise.
718
         * @return array
719
         *  a new array containing only the unique elements of the input array.
720
         */
721
        public static function array_remove_duplicates(array $array, $ignore_case = false)
722
        {
723
            return ($ignore_case === true ? self::array_iunique($array) : array_unique($array));
724
        }
725
726
        /**
727
         * Filter the input array for duplicates, treating each element in the array
728
         * as a string and comparing them using a case insensitive comparison function.
729
         *
730
         * @param array $array
731
         *  the array to filter.
732
         * @return array
733
         *  a new array containing only the unique elements of the input array.
734
         */
735
        public static function array_iunique(array $array)
736
        {
737
            $tmp = array();
738
739
            foreach ($array as $key => $value) {
740
                if (!self::in_iarray($value, $tmp)) {
741
                    $tmp[$key] = $value;
742
                }
743
            }
744
745
            return $tmp;
746
        }
747
748
        /**
749
         * Test whether a value is in an array based on string comparison, ignoring
750
         * the case of the values.
751
         *
752
         * @param mixed $needle
753
         *  the object to search the array for.
754
         * @param array $haystack
755
         *  the array to search for the `$needle`.
756
         * @return boolean
757
         *  true if the `$needle` is in the `$haystack`, false otherwise.
758
         */
759
        public static function in_iarray($needle, array $haystack)
760
        {
761
            foreach ($haystack as $key => $value) {
762
                if (strcasecmp($value, $needle) === 0) {
763
                    return true;
764
                }
765
            }
766
767
            return false;
768
        }
769
770
        /**
771
         * Function recursively apply a function to an array's values.
772
         * This will not touch the keys, just the values.
773
         *
774
         * @since Symphony 2.2
775
         * @param string $function
776
         * @param array $array
777
         * @return array
778
         *  a new array with all the values passed through the given `$function`
779
         */
780
        public static function array_map_recursive($function, array $array)
781
        {
782
            $tmp = array();
783
784
            foreach ($array as $key => $value) {
785
                if (is_array($value)) {
786
                    $tmp[$key] = self::array_map_recursive($function, $value);
787
                } else {
788
                    $tmp[$key] = call_user_func($function, $value);
789
                }
790
            }
791
792
            return $tmp;
793
        }
794
795
        /**
796
         * Convert an array into an XML fragment and append it to an existing
797
         * XML element. Any arrays contained as elements in the input array will
798
         * also be recursively formatted and appended to the input XML fragment.
799
         * The input XML element will be modified as a result of calling this.
800
         *
801
         * @param XMLElement $parent
802
         *  the XML element to append the formatted array data to.
803
         * @param array $data
804
         *  the array to format and append to the XML fragment.
805
         * @param boolean $validate
806
         *  true if the formatted array data should be validated as it is
807
         *  constructed, false otherwise.
808
         */
809
        public static function array_to_xml(XMLElement $parent, array $data, $validate = false)
810
        {
811
            foreach ($data as $element_name => $value) {
812
                if (!is_numeric($value) && empty($value)) {
813
                    continue;
814
                }
815
816
                if (is_int($element_name)) {
817
                    $child = new XMLElement('item');
818
                    $child->setAttribute('index', $element_name + 1);
819
                } else {
820
                    $child = new XMLElement($element_name, null, array(), true);
821
                }
822
823
                if (is_array($value) || is_object($value)) {
824
                    self::array_to_xml($child, (array)$value);
825
826
                    if ($child->getNumberOfChildren() === 0) {
827
                        continue;
828
                    }
829
                } elseif ($validate === true && !self::validateXML(self::sanitize($value), $errors, false,
830
                        new XSLTProcess)
831
                ) {
832
                    continue;
833
                } else {
834
                    $child->setValue(self::sanitize($value));
835
                }
836
837
                $parent->appendChild($child);
838
            }
839
        }
840
841
        /**
842
         * Checks an xml document for well-formedness.
843
         *
844
         * @param string $data
845
         *  filename, xml document as a string, or arbitrary string
846
         * @param array &$errors
847
         *  An array which will contain any validation errors (by reference)
848
         * @param boolean $isFile (optional)
849
         *  if this is true, the method will attempt to read from a file, `$data`
850
         *  instead.
851
         * @param mixed $xsltProcessor (optional)
852
         *  if set, the validation will be done using this XSLT processor rather
853
         *  than the built in XML parser. the default is null.
854
         * @param string $encoding (optional)
855
         *  if no XML header is expected, than this should be set to match the
856
         *  encoding of the XML
857
         * @return boolean
858
         *  true if there are no errors in validating the XML, false otherwise.
859
         */
860
        public static function validateXML($data, &$errors, $isFile = true, $xsltProcessor = null, $encoding = 'UTF-8')
861
        {
862
            $_data = ($isFile) ? file_get_contents($data) : $data;
863
            $_data = preg_replace('/<!DOCTYPE[-.:"\'\/\\w\\s]+>/', null, $_data);
864
865
            if (strpos($_data, '<?xml') === false) {
866
                $_data = '<?xml version="1.0" encoding="' . $encoding . '"?><rootelement>' . $_data . '</rootelement>';
867
            }
868
869
            if (is_object($xsltProcessor)) {
870
                $xsl = '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
871
872
            <xsl:template match="/"></xsl:template>
873
874
            </xsl:stylesheet>';
875
876
                $xsltProcessor->process($_data, $xsl, array());
877
878
                if ($xsltProcessor->isErrors()) {
879
                    $errors = $xsltProcessor->getError(true);
880
881
                    return false;
882
                }
883
            } else {
884
                $_parser = xml_parser_create();
885
                xml_parser_set_option($_parser, XML_OPTION_SKIP_WHITE, 0);
886
                xml_parser_set_option($_parser, XML_OPTION_CASE_FOLDING, 0);
887
888
                if (!xml_parse($_parser, $_data)) {
889
                    $errors = array(
890
                        'error' => xml_get_error_code($_parser) . ': ' . xml_error_string(xml_get_error_code($_parser)),
891
                        'col' => xml_get_current_column_number($_parser),
892
                        'line' => (xml_get_current_line_number($_parser) - 2)
893
                    );
894
895
                    return false;
896
                }
897
898
                xml_parser_free($_parser);
899
            }
900
901
            return true;
902
        }
903
904
        /**
905
         * Convert any special characters into their entity equivalents. Since
906
         * Symphony 2.3, this function assumes UTF-8 and will not double
907
         * encode strings.
908
         *
909
         * @param string $source
910
         *  a string to operate on.
911
         * @return string
912
         *  the encoded version of the string.
913
         */
914
        public static function sanitize($source)
915
        {
916
            $source = htmlspecialchars($source, ENT_COMPAT, 'UTF-8', false);
917
918
            return $source;
919
        }
920
921
        /**
922
         * Create a file at the input path with the (optional) input permissions
923
         * with the input content. This function will ignore errors in opening,
924
         * writing, closing and changing the permissions of the resulting file.
925
         * If opening or writing the file fail then this will return false.
926
         * This method calls `clearstatcache()` in order to make sure we do not
927
         * hit the cache when checking for permissions.
928
         *
929
         * @param string $file
930
         *  the path of the file to write.
931
         * @param mixed $data
932
         *  the data to write to the file.
933
         * @param integer|null $perm (optional)
934
         *  the permissions as an octal number to set set on the resulting file.
935
         *  this defaults to 0644 (if omitted or set to null)
936
         * @param string $mode (optional)
937
         *  the mode that the file should be opened with, defaults to 'w'. See modes
938
         *  at http://php.net/manual/en/function.fopen.php
939
         * @param boolean $trim (optional)
940
         *  removes tripple linebreaks
941
         * @return boolean
942
         *  true if the file is successfully opened, written to, closed and has the
943
         *  required permissions set. false, otherwise.
944
         */
945
        public static function writeFile($file, $data, $perm = 0644, $mode = 'w', $trim = false)
946
        {
947
            clearstatcache();
948
949
            if (static::checkFile($file) === false) {
950
                return false;
951
            }
952
953
            if (!$handle = fopen($file, $mode)) {
954
                return false;
955
            }
956
957
            if ($trim === true) {
958
                $data = preg_replace("/(" . PHP_EOL . "([ |\t]+)?){2,}" . PHP_EOL . "/", PHP_EOL . PHP_EOL,
959
                    trim($data));
960
            }
961
962
            if (fwrite($handle, $data, strlen($data)) === false) {
963
                return false;
964
            }
965
966
            fclose($handle);
967
968
            try {
969
                if (is_null($perm)) {
970
                    $perm = 0644;
971
                }
972
973
                chmod($file, intval($perm, 8));
974
            } catch (Exception $ex) {
975
                // If we can't chmod the file, this is probably because our host is
976
                // running PHP with a different user to that of the file. Although we
977
                // can delete the file, create a new one and then chmod it, we run the
978
                // risk of losing the file as we aren't saving it anywhere. For the immediate
979
                // future, atomic saving isn't needed by Symphony and it's recommended that
980
                // if your extension require this logic, it uses it's own function rather
981
                // than this 'General' one.
982
                return true;
983
            }
984
985
            return true;
986
        }
987
988
        /**
989
         * Checks that the file and it's folder are readable and writable.
990
         *
991
         * @since Symphony 2.6.3
992
         * @param string $file
993
         * @return boolean
994
         */
995
        public static function checkFile($file)
996
        {
997
            clearstatcache();
998
            $dir = dirname($file);
999
1000
            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...
1001
                (!is_writable($dir) || !is_readable($dir)) // Folder
1002
                || (file_exists($file) && (!is_readable($file) || !is_writable($file))) // File
1003
            ) {
1004
                return false;
1005
            }
1006
1007
            return true;
1008
        }
1009
1010
        /**
1011
         * Delete a file at a given path, silently ignoring errors depending
1012
         * on the value of the input variable $silent.
1013
         *
1014
         * @param string $file
1015
         *  the path of the file to delete
1016
         * @param boolean $silent (optional)
1017
         *  true if an exception should be raised if an error occurs, false
1018
         *  otherwise. this defaults to true.
1019
         * @throws Exception
1020
         * @return boolean
1021
         *  true if the file is successfully unlinked, if the unlink fails and
1022
         *  silent is set to true then an exception is thrown. if the unlink
1023
         *  fails and silent is set to false then this returns false.
1024
         */
1025
        public static function deleteFile($file, $silent = true)
1026
        {
1027
            try {
1028
                return unlink($file);
1029
            } catch (Exception $ex) {
1030
                if ($silent === false) {
1031
                    throw new Exception(__('Unable to remove file - %s', array($file)));
1032
                }
1033
1034
                return false;
1035
            }
1036
        }
1037
1038
        /**
1039
         * Construct a multi-dimensional array that reflects the directory
1040
         * structure of a given path.
1041
         *
1042
         * @param string $dir (optional)
1043
         *  the path of the directory to construct the multi-dimensional array
1044
         *  for. this defaults to '.'.
1045
         * @param string $filter (optional)
1046
         *  A regular expression to filter the directories. This is positive filter, ie.
1047
         * if the filter matches, the directory is included. Defaults to null.
1048
         * @param boolean $recurse (optional)
1049
         *  true if sub-directories should be traversed and reflected in the
1050
         *  resulting array, false otherwise.
1051
         * @param mixed $strip_root (optional)
1052
         *  If null, the full path to the file will be returned, otherwise the value
1053
         *  of `strip_root` will be removed from the file path.
1054
         * @param array $exclude (optional)
1055
         *  ignore directories listed in this array. this defaults to an empty array.
1056
         * @param boolean $ignore_hidden (optional)
1057
         *  ignore hidden directory (i.e.directories that begin with a period). this defaults
1058
         *  to true.
1059
         * @return null|array
1060
         *  return the array structure reflecting the input directory or null if
1061
         * the input directory is not actually a directory.
1062
         */
1063
        public static function listDirStructure(
1064
            $dir = '.',
1065
            $filter = null,
1066
            $recurse = true,
1067
            $strip_root = null,
1068
            $exclude = array(),
1069
            $ignore_hidden = true
1070
        ) {
1071
            if (!is_dir($dir)) {
1072
                return null;
1073
            }
1074
1075
            $files = array();
1076
1077
            foreach (scandir($dir) as $file) {
1078 View Code Duplication
                if (
1079
                    ($file === '.' || $file === '..')
1080
                    || ($ignore_hidden && $file{0} === '.')
1081
                    || !is_dir("$dir/$file")
1082
                    || in_array($file, $exclude)
1083
                    || in_array("$dir/$file", $exclude)
1084
                ) {
1085
                    continue;
1086
                }
1087
1088
                if (!is_null($filter)) {
1089
                    if (!preg_match($filter, $file)) {
1090
                        continue;
1091
                    }
1092
                }
1093
1094
                $files[] = rtrim(str_replace($strip_root, '', $dir), '/') . "/$file/";
1095
1096
                if ($recurse) {
1097
                    $files = @array_merge($files,
1098
                        self::listDirStructure("$dir/$file", $filter, $recurse, $strip_root, $exclude, $ignore_hidden));
1099
                }
1100
            }
1101
1102
            return $files;
1103
        }
1104
1105
        /**
1106
         * Construct a multi-dimensional array that reflects the directory
1107
         * structure of a given path grouped into directory and file keys
1108
         * matching any input constraints.
1109
         *
1110
         * @param string $dir (optional)
1111
         *  the path of the directory to construct the multi-dimensional array
1112
         *  for. this defaults to '.'.
1113
         * @param array|string $filters (optional)
1114
         *  either a regular expression to filter the files by or an array of
1115
         *  files to include.
1116
         * @param boolean $recurse (optional)
1117
         *  true if sub-directories should be traversed and reflected in the
1118
         *  resulting array, false otherwise.
1119
         * @param string $sort (optional)
1120
         *  'asc' if the resulting filelist array should be sorted, anything else otherwise.
1121
         *  this defaults to 'asc'.
1122
         * @param mixed $strip_root (optional)
1123
         *  If null, the full path to the file will be returned, otherwise the value
1124
         *  of `strip_root` will be removed from the file path.
1125
         * @param array $exclude (optional)
1126
         *  ignore files listed in this array. this defaults to an empty array.
1127
         * @param boolean $ignore_hidden (optional)
1128
         *  ignore hidden files (i.e. files that begin with a period). this defaults
1129
         *  to true.
1130
         * @return null|array
1131
         *  return the array structure reflecting the input directory or null if
1132
         * the input directory is not actually a directory.
1133
         */
1134
        public static function listStructure(
1135
            $dir = ".",
1136
            $filters = array(),
1137
            $recurse = true,
1138
            $sort = "asc",
1139
            $strip_root = null,
1140
            $exclude = array(),
1141
            $ignore_hidden = true
1142
        ) {
1143
            if (!is_dir($dir)) {
1144
                return null;
1145
            }
1146
1147
            // Check to see if $filters is a string containing a regex, or an array of file types
1148
            if (is_array($filters) && !empty($filters)) {
1149
                $filter_type = 'file';
1150
            } elseif (is_string($filters)) {
1151
                $filter_type = 'regex';
1152
            } else {
1153
                $filter_type = null;
1154
            }
1155
            $files = array();
1156
1157
            $prefix = str_replace($strip_root, '', $dir);
1158
1159
            if ($prefix !== "" && substr($prefix, -1) !== "/") {
1160
                $prefix .= "/";
1161
            }
1162
1163
            $files['dirlist'] = array();
1164
            $files['filelist'] = array();
1165
1166
            foreach (scandir($dir) as $file) {
1167 View Code Duplication
                if (
1168
                    ($file === '.' || $file === '..')
1169
                    || ($ignore_hidden && $file{0} === '.')
1170
                    || in_array($file, $exclude)
1171
                    || in_array("$dir/$file", $exclude)
1172
                ) {
1173
                    continue;
1174
                }
1175
1176
                $dir = rtrim($dir, '/');
1177
1178
                if (is_dir("$dir/$file")) {
1179
                    if ($recurse) {
1180
                        $files["$prefix$file/"] = self::listStructure("$dir/$file", $filters, $recurse, $sort,
1181
                            $strip_root, $exclude, $ignore_hidden);
1182
                    }
1183
1184
                    $files['dirlist'][] = "$prefix$file/";
1185
                } elseif ($filter_type === 'regex') {
1186
                    if (preg_match($filters, $file)) {
1187
                        $files['filelist'][] = "$prefix$file";
1188
                    }
1189
                } elseif ($filter_type === 'file') {
1190
                    if (in_array(self::getExtension($file), $filters)) {
1191
                        $files['filelist'][] = "$prefix$file";
1192
                    }
1193
                } elseif (is_null($filter_type)) {
1194
                    $files['filelist'][] = "$prefix$file";
1195
                }
1196
            }
1197
1198
            if (is_array($files['filelist'])) {
1199
                ($sort === 'desc') ? rsort($files['filelist']) : sort($files['filelist']);
1200
            }
1201
1202
            return $files;
1203
        }
1204
1205
        /**
1206
         * Extract the file extension from the input file path.
1207
         *
1208
         * @param string $file
1209
         *  the path of the file to extract the extension of.
1210
         * @return array
1211
         *  an array with a single key 'extension' and a value of the extension
1212
         *  of the input path.
1213
         */
1214
        public static function getExtension($file)
1215
        {
1216
            return pathinfo($file, PATHINFO_EXTENSION);
1217
        }
1218
1219
        /**
1220
         * Count the number of words in a string. Words are delimited by "spaces".
1221
         * The characters included in the set of "spaces" are:
1222
         *  '&#x2002;', '&#x2003;', '&#x2004;', '&#x2005;',
1223
         *  '&#x2006;', '&#x2007;', '&#x2009;', '&#x200a;',
1224
         *  '&#x200b;', '&#x2002f;', '&#x205f;'
1225
         * Any html/xml tags are first removed by strip_tags() and any included html
1226
         * entities are decoded. The resulting string is then split by the above set
1227
         * of spaces and the resulting size of the resulting array returned.
1228
         *
1229
         * @param string $string
1230
         *  the string from which to count the contained words.
1231
         * @return integer
1232
         *  the number of words contained in the input string.
1233
         */
1234
        public static function countWords($string)
1235
        {
1236
            $string = strip_tags($string);
1237
1238
            // Strip spaces:
1239
            $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');
1240
            $spaces = array(
1241
                '&#x2002;',
1242
                '&#x2003;',
1243
                '&#x2004;',
1244
                '&#x2005;',
1245
                '&#x2006;',
1246
                '&#x2007;',
1247
                '&#x2009;',
1248
                '&#x200a;',
1249
                '&#x200b;',
1250
                '&#x2002f;',
1251
                '&#x205f;'
1252
            );
1253
1254
            foreach ($spaces as &$space) {
1255
                $space = html_entity_decode($space, ENT_NOQUOTES, 'UTF-8');
1256
            }
1257
1258
            $string = str_replace($spaces, ' ', $string);
1259
            $string = preg_replace('/[^\w\s]/i', '', $string);
1260
1261
            return str_word_count($string);
1262
        }
1263
1264
        /**
1265
         * Move a file from the source path to the destination path and name and
1266
         * set its permissions to the input permissions. This will ignore errors
1267
         * in the `is_uploaded_file()`, `move_uploaded_file()` and `chmod()` functions.
1268
         *
1269
         * @param string $dest_path
1270
         *  the file path to which the source file is to be moved.
1271
         * @param string $dest_name
1272
         *  the file name within the file path to which the source file is to be moved.
1273
         * @param string $tmp_name
1274
         *  the full path name of the source file to move.
1275
         * @param integer $perm (optional)
1276
         *  the permissions to apply to the moved file. this defaults to 0777.
1277
         * @return boolean
1278
         *  true if the file was moved and its permissions set as required. false otherwise.
1279
         */
1280
        public static function uploadFile($dest_path, $dest_name, $tmp_name, $perm = 0777)
1281
        {
1282
            // Upload the file
1283
            if (@is_uploaded_file($tmp_name)) {
1284
                $dest_path = rtrim($dest_path, '/') . '/';
1285
1286
                // Try place the file in the correction location
1287
                if (@move_uploaded_file($tmp_name, $dest_path . $dest_name)) {
1288
                    chmod($dest_path . $dest_name, intval($perm, 8));
1289
1290
                    return true;
1291
                }
1292
            }
1293
1294
            // Could not move the file
1295
            return false;
1296
        }
1297
1298
        /**
1299
         * Format a number of bytes in human readable format. This will append MB as
1300
         * appropriate for values greater than 1,024*1,024, KB for values between
1301
         * 1,024 and 1,024*1,024-1 and bytes for values between 0 and 1,024.
1302
         *
1303
         * @param integer $file_size
1304
         *  the number to format.
1305
         * @return string
1306
         *  the formatted number.
1307
         */
1308
        public static function formatFilesize($file_size)
1309
        {
1310
            $file_size = intval($file_size);
1311
1312
            if ($file_size >= (1024 * 1024)) {
1313
                $file_size = number_format($file_size * (1 / (1024 * 1024)), 2) . ' MB';
1314
            } elseif ($file_size >= 1024) {
1315
                $file_size = intval($file_size * (1 / 1024)) . ' KB';
1316
            } else {
1317
                $file_size = intval($file_size) . ' bytes';
1318
            }
1319
1320
            return $file_size;
1321
        }
1322
1323
        /**
1324
         * Gets the number of bytes from 'human readable' size value. Supports
1325
         * the output of `General::formatFilesize` as well as reading values
1326
         * from the PHP configuration. eg. 1 MB or 1M
1327
         *
1328
         * @since Symphony 2.5.2
1329
         * @param string $file_size
1330
         * @return integer
1331
         */
1332
        public static function convertHumanFileSizeToBytes($file_size)
1333
        {
1334
            $file_size = str_replace(
1335
                array(' MB', ' KB', ' bytes'),
1336
                array('M', 'K', 'B'),
1337
                trim($file_size)
1338
            );
1339
1340
            $last = strtolower($file_size[strlen($file_size) - 1]);
1341 View Code Duplication
            switch ($last) {
1342
                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...
1343
                    $file_size *= 1024;
1344
                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...
1345
                    $file_size *= 1024;
1346
                case 'k':
1347
                    $file_size *= 1024;
1348
            }
1349
1350
            return $file_size;
1351
        }
1352
1353
        /**
1354
         * Construct an XML fragment that reflects the structure of the input timestamp.
1355
         *
1356
         * @param integer $timestamp
1357
         *  the timestamp to construct the XML element from.
1358
         * @param string $element (optional)
1359
         *  the name of the element to append to the namespace of the constructed XML.
1360
         *  this defaults to "date".
1361
         * @param string $date_format (optional)
1362
         *  the format to apply to the date, defaults to `Y-m-d`
1363
         * @param string $time_format (optional)
1364
         *  the format to apply to the date, defaults to `H:i`
1365
         * @param string $namespace (optional)
1366
         *  the namespace in which the resulting XML entity will reside. this defaults
1367
         *  to null.
1368
         * @return boolean|XMLElement
1369
         *  false if there is no XMLElement class on the system, the constructed XML element
1370
         *  otherwise.
1371
         */
1372
        public static function createXMLDateObject(
1373
            $timestamp,
1374
            $element = 'date',
1375
            $date_format = 'Y-m-d',
1376
            $time_format = 'H:i',
1377
            $namespace = null
1378
        ) {
1379
            if (!class_exists('XMLElement')) {
1380
                return false;
1381
            }
1382
1383
            $xDate = new XMLElement(
1384
                (!is_null($namespace) ? $namespace . ':' : '') . $element,
1385
                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...
1386
                array(
1387
                    'iso' => DateTimeObj::get('c', $timestamp),
1388
                    'timestamp' => DateTimeObj::get('U', $timestamp),
1389
                    'time' => DateTimeObj::get($time_format, $timestamp),
1390
                    'weekday' => DateTimeObj::get('N', $timestamp),
1391
                    'offset' => DateTimeObj::get('O', $timestamp)
1392
                )
1393
            );
1394
1395
            return $xDate;
1396
        }
1397
1398
        /**
1399
         * Construct an XML fragment that describes a pagination structure.
1400
         *
1401
         * @param integer $total_entries (optional)
1402
         *  the total number of entries that this structure is paginating. this
1403
         *  defaults to 0.
1404
         * @param integer $total_pages (optional)
1405
         *  the total number of pages within the pagination structure. this defaults
1406
         *  to 0.
1407
         * @param integer $entries_per_page (optional)
1408
         *  the number of entries per page. this defaults to 1.
1409
         * @param integer $current_page (optional)
1410
         *  the current page within the total number of pages within this pagination
1411
         *  structure. this defaults to 1.
1412
         * @return XMLElement
1413
         *  the constructed XML fragment.
1414
         */
1415
        public static function buildPaginationElement(
1416
            $total_entries = 0,
1417
            $total_pages = 0,
1418
            $entries_per_page = 1,
1419
            $current_page = 1
1420
        ) {
1421
            $pageinfo = new XMLElement('pagination');
1422
1423
            $pageinfo->setAttribute('total-entries', $total_entries);
1424
            $pageinfo->setAttribute('total-pages', $total_pages);
1425
            $pageinfo->setAttribute('entries-per-page', $entries_per_page);
1426
            $pageinfo->setAttribute('current-page', $current_page);
1427
1428
            return $pageinfo;
1429
        }
1430
1431
        /**
1432
         * Uses `SHA1` or `MD5` to create a hash based on some input
1433
         * This function is currently very basic, but would allow
1434
         * future expansion. Salting the hash comes to mind.
1435
         *
1436
         * @param string $input
1437
         *  the string to be hashed
1438
         * @param string $algorithm
1439
         *  This function supports 'sha1' and 'pbkdf2'. Any
1440
         *  other algorithm will default to 'pbkdf2'.
1441
         * @return string
1442
         *  the hashed string
1443
         */
1444
        public static function hash($input, $algorithm = 'sha1')
1445
        {
1446
            switch ($algorithm) {
1447
                case 'sha1':
1448
                    return SHA1::hash($input);
1449
                case 'pbkdf2':
1450
                    return PBKDF2::hash($input);
1451
                default:
1452
                    return Cryptography::hash($input);
1453
            }
1454
        }
1455
1456
        /**
1457
         * Helper to cut down on variables' type check.
1458
         * Currently known types are the PHP defaults.
1459
         * Uses `is_XXX()` functions internally.
1460
         *
1461
         * @since Symphony 2.3
1462
         *
1463
         * @param array $params - an array of arrays containing variables info
1464
         *
1465
         *  Array[
1466
         *      $key1 => $value1
1467
         *      $key2 => $value2
1468
         *      ...
1469
         *  ]
1470
         *
1471
         *  $key = the name of the variable
1472
         *  $value = Array[
1473
         *      'var' => the variable to check
1474
         *      'type' => enforced type. Must match the XXX part from an `is_XXX()` function
1475
         *      'optional' => boolean. If this is set, the default value of the variable must be null
1476
         *  ]
1477
         *
1478
         * @throws InvalidArgumentException if validator doesn't exist.
1479
         * @throws InvalidArgumentException if variable type validation fails.
1480
         *
1481
         * @example
1482
         *  $color = 'red';
1483
         *  $foo = null;
1484
         *  $bar = 21;
1485
         *
1486
         *  General::ensureType(array(
1487
         *      'color' => array('var' => $color, 'type'=> 'string'),               // success
1488
         *      'foo' => array('var' => $foo, 'type'=> 'int',  'optional' => true), // success
1489
         *      'bar' => array('var' => $bar, 'type'=> 'string')                    // fail
1490
         *  ));
1491
         */
1492
        public static function ensureType(array $params)
1493
        {
1494
            foreach ($params as $name => $param) {
1495
                if (isset($param['optional']) && ($param['optional'] === true)) {
1496
                    if (is_null($param['var'])) {
1497
                        continue;
1498
                    }
1499
                    // if not null, check it's type
1500
                }
1501
1502
                // validate the validator
1503
                $validator = 'is_' . $param['type'];
1504
1505
                if (!function_exists($validator)) {
1506
                    throw new InvalidArgumentException(__('Enforced type `%1$s` for argument `$%2$s` does not match any known variable types.',
1507
                        array($param['type'], $name)));
1508
                }
1509
1510
                // validate variable type
1511
                if (!call_user_func($validator, $param['var'])) {
1512
                    throw new InvalidArgumentException(__('Argument `$%1$s` is not of type `%2$s`, given `%3$s`.',
1513
                        array($name, $param['type'], gettype($param['var']))));
1514
                }
1515
            }
1516
        }
1517
1518
        /**
1519
         * Wrap a value in CDATA tags for XSL output of non encoded data, only
1520
         * if not already wrapped.
1521
         *
1522
         * @since Symphony 2.3.2
1523
         *
1524
         * @param string $value
1525
         *  The string to wrap in CDATA
1526
         * @return string
1527
         *  The wrapped string
1528
         */
1529
        public static function wrapInCDATA($value)
1530
        {
1531
            if (empty($value)) {
1532
                return $value;
1533
            }
1534
1535
            $startRegExp = '/^' . preg_quote(CDATA_BEGIN) . '/';
1536
            $endRegExp = '/' . preg_quote(CDATA_END) . '$/';
1537
1538
            if (!preg_match($startRegExp, $value)) {
1539
                $value = CDATA_BEGIN . $value;
1540
            }
1541
1542
            if (!preg_match($endRegExp, $value)) {
1543
                $value .= CDATA_END;
1544
            }
1545
1546
            return $value;
1547
        }
1548
1549
        /**
1550
         * Unwrap a value from CDATA tags to return the raw string
1551
         *
1552
         * @since Symphony 2.3.4
1553
         * @param string $value
1554
         *  The string to unwrap from CDATA
1555
         * @return string
1556
         *  The unwrapped string
1557
         */
1558
        public static function unwrapCDATA($value)
1559
        {
1560
            return str_replace(array(CDATA_BEGIN, CDATA_END), '', $value);
1561
        }
1562
1563
        /**
1564
         * Converts a value to a positive integer. This method makes sure that the
1565
         * value is a valid positive integer representation before doing the cast.
1566
         *
1567
         * @since Symphony 2.5
1568
         * @param mixed $value
1569
         *  The value to cast to an integer
1570
         * @return int
1571
         *  The casted integer value if the input is valid, -1 otherwise.
1572
         */
1573
        public static function intval($value)
1574
        {
1575
            if (is_numeric($value) && preg_match('/^[0-9]+$/i', $value) === 1) {
1576
                return intval($value);
1577
            }
1578
1579
            return -1;
1580
        }
1581
1582
        /**
1583
         * Convert a PHP time string like '2 weeks' to seconds
1584
         *
1585
         * @since Symphony 3.0.0
1586
         * @param  string $string
1587
         *  The valid PHP time string
1588
         * @return int
1589
         *  The seconds
1590
         */
1591
        public static function stringToSeconds($string)
1592
        {
1593
            return strtotime($string) - time();
1594
        }
1595
1596
        /**
1597
         * Flatten the input array. Any elements of the input array that are
1598
         * themselves arrays will be removed and the contents of the removed array
1599
         * inserted in its place. The keys for the inserted values will be the
1600
         * concatenation of the keys in the original arrays in which it was embedded.
1601
         * The elements of the path are separated by colons (:). For example, given
1602
         * the following nested array structure:
1603
         * `
1604
         * array(1 =>
1605
         *          array('key' => 'value'),
1606
         *      2 =>
1607
         *          array('key2' => 'value2', 'key3' => 'value3')
1608
         *      )
1609
         * `
1610
         * will flatten to:
1611
         * `array('1:key' => 'value', '2:key2' => 'value2', '2:key3' => 'value3')`
1612
         *
1613
         *
1614
         * @param array &$output
1615
         *  The array in which to store the flattened input, passed by reference.
1616
         * @param array &$source
1617
         *  The array to flatten, passed by reference
1618
         * @param string $path
1619
         *  the current prefix of the keys to insert into the output array.
1620
         */
1621
        protected static function flattenArraySub(array &$output, array &$source, $path)
1622
        {
1623
            foreach ($source as $key => $value) {
1624
                $key = $path . ':' . $key;
1625
1626
                if (is_array($value)) {
1627
                    self::flattenArraySub($output, $value, $key);
1628
                } else {
1629
                    $output[$key] = $value;
1630
                }
1631
            }
1632
        }
1633
1634
        /**
1635
         * Gets mime type of a file.
1636
         *
1637
         * For email attachments, the mime type is very important.
1638
         * Uses the PHP 5.3 function `finfo_open` when available, otherwise falls
1639
         * back to using a mapping of known of common mimetypes. If no matches
1640
         * are found `application/octet-stream` will be returned.
1641
         *
1642
         * @author Michael Eichelsdoerfer
1643
         * @author Huib Keemink
1644
         * @param string $file
1645
         * @return string|boolean
1646
         *  the mime type of the file, or false is none found
1647
         */
1648
        public function getMimeType($file)
1649
        {
1650
            if (!empty($file)) {
1651
                // in PHP 5.3 we can use 'finfo'
1652
                if (PHP_VERSION_ID >= 50300 && function_exists('finfo_open')) {
1653
                    $finfo = finfo_open(FILEINFO_MIME_TYPE);
1654
                    $mime_type = finfo_file($finfo, $file);
1655
                    finfo_close($finfo);
1656
                } else {
1657
                    // A few mimetypes to "guess" using the file extension.
1658
                    $mimetypes = array(
1659
                        'txt' => 'text/plain',
1660
                        'csv' => 'text/csv',
1661
                        'pdf' => 'application/pdf',
1662
                        'doc' => 'application/msword',
1663
                        'docx' => 'application/msword',
1664
                        'xls' => 'application/vnd.ms-excel',
1665
                        'ppt' => 'application/vnd.ms-powerpoint',
1666
                        'eps' => 'application/postscript',
1667
                        'zip' => 'application/zip',
1668
                        'gif' => 'image/gif',
1669
                        'jpg' => 'image/jpeg',
1670
                        'jpeg' => 'image/jpeg',
1671
                        'png' => 'image/png',
1672
                        'mp3' => 'audio/mpeg',
1673
                        'mp4a' => 'audio/mp4',
1674
                        'aac' => 'audio/x-aac',
1675
                        'aif' => 'audio/x-aiff',
1676
                        'aiff' => 'audio/x-aiff',
1677
                        'wav' => 'audio/x-wav',
1678
                        'wma' => 'audio/x-ms-wma',
1679
                        'mpeg' => 'video/mpeg',
1680
                        'mpg' => 'video/mpeg',
1681
                        'mp4' => 'video/mp4',
1682
                        'mov' => 'video/quicktime',
1683
                        'avi' => 'video/x-msvideo',
1684
                        'wmv' => 'video/x-ms-wmv',
1685
                    );
1686
1687
                    $extension = substr(strrchr($file, '.'), 1);
1688
1689
                    if ($mimetypes[strtolower($extension)] !== null) {
1690
                        $mime_type = $mimetypes[$extension];
1691
                    } else {
1692
                        $mime_type = 'application/octet-stream';
1693
                    }
1694
                }
1695
1696
                return $mime_type;
1697
            }
1698
1699
            return false;
1700
        }
1701
    }
1702