Completed
Push — feature/travis-php72-1 ( 57a91d )
by Alexandre
12:03 queued 03:53
created

core-functions.php ➔ array_load()   D

Complexity

Conditions 9
Paths 9

Size

Total Lines 34
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 24
nc 9
nop 2
dl 0
loc 34
rs 4.909
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2014 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
/**
9
 * This file is part of the array_column library
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 *
14
 * @copyright Copyright (c) 2013 Ben Ramsey <http://benramsey.com>
15
 * @license http://opensource.org/licenses/MIT MIT
16
 */
17
18
/**
19
 * Returns the values from a single column of the input array, identified by the $columnKey.
20
 *
21
 * Optionally, you may provide an $indexKey to index the values in the returned
22
 * array by the values from the $indexKey column in the input array.
23
 *
24
 * @param array $array A multi-dimensional array (record set) from which to pull a column of values.
25
 * @param int|string|null $columnKey The column of values to return.
26
 * This value may be the integer key of the column you wish to retrieve, or it
27
 * may be the string key name for an associative array.
28
 * @param mixed $indexKey The column to use as the index/keys for the returned array.
29
 * This value may be the integer key of the column, or it may be the string key name.
30
 * @return array Returns an array of values representing a single column from the input array.
31
 * @category Array Functions
32
 */
33
function array_column_php(array $array, $columnKey = null, $indexKey = null) {
34 View Code Duplication
    if (!is_int($columnKey)
35
        && !is_float($columnKey)
36
        && !is_string($columnKey)
37
        && $columnKey !== null
38
        && !(is_object($columnKey) && method_exists($columnKey, '__toString'))
39
    ) {
40
        trigger_error('array_column(): The column key should be either a string or an integer', E_USER_WARNING);
41
    }
42
43 View Code Duplication
    if (isset($indexKey)
44
        && !is_int($indexKey)
45
        && !is_float($indexKey)
46
        && !is_string($indexKey)
47
        && !(is_object($indexKey) && method_exists($indexKey, '__toString'))
48
    ) {
49
        trigger_error('array_column(): The index key should be either a string or an integer', E_USER_WARNING);
50
    }
51
52
    $paramsColumnKey = ($columnKey !== null) ? (string)$columnKey : null;
53
54
    $paramsIndexKey = null;
55
    if (isset($indexKey)) {
56
        if (is_float($indexKey) || is_int($indexKey)) {
57
            $paramsIndexKey = (int)$indexKey;
58
        } else {
59
            $paramsIndexKey = (string)$indexKey;
60
        }
61
    }
62
63
    $resultArray = array();
64
65
    foreach ($array as $row) {
66
67
        $key = $value = null;
68
        $keySet = $valueSet = false;
69
70
        if ($paramsIndexKey !== null && array_key_exists($paramsIndexKey, $row)) {
71
            $keySet = true;
72
            $key = (string)$row[$paramsIndexKey];
73
        }
74
75
        if ($paramsColumnKey === null) {
76
            $valueSet = true;
77
            $value = $row;
78
        } elseif (is_array($row) && array_key_exists($paramsColumnKey, $row)) {
79
            $valueSet = true;
80
            $value = $row[$paramsColumnKey];
81
        }
82
83
        if ($valueSet) {
84
            if ($keySet) {
85
                $resultArray[$key] = $value;
86
            } else {
87
                $resultArray[] = $value;
88
            }
89
        }
90
91
    }
92
93
    return $resultArray;
94
}
95
96
if (!function_exists('array_column')) {
97
    /**
98
     * A custom implementation of array_column for older versions of php.
99
     *
100
     * @param array $array The dataset to test.
101
     * @param int|string $columnKey The column of values to return.
102
     * @param int|string|null $indexKey The column to use as the index/keys for the returned array.
103
     * @return array Returns the columns from the {@link $input} array.
104
     */
105
    function array_column($array, $columnKey, $indexKey = null) {
106
        return array_column_php($array, $columnKey, $indexKey);
107
    }
108
}
109
110
/**
111
 * Converts a quick array into a key/value form.
112
 *
113
 * @param array $array The array to work on.
114
 * @param mixed $default The default value for unspecified keys.
115
 * @return array Returns the array converted to long syntax.
116
 */
117 View Code Duplication
function array_quick(array $array, $default) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
118
    $result = [];
119
    foreach ($array as $key => $value) {
120
        if (is_int($key)) {
121
            $result[$value] = $default;
122
        } else {
123
            $result[$key] = $value;
124
        }
125
    }
126
    return $result;
127
}
128
129
/**
130
 * Converts a quick array into a key/value form using a callback to convert the short items.
131
 *
132
 * @param array $array The array to work on.
133
 * @param callable $callback The callback used to generate the default values.
134
 * @return array Returns the array converted to long syntax.
135
 */
136 View Code Duplication
function array_uquick(array $array, callable $callback) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
137
    $result = [];
138
    foreach ($array as $key => $value) {
139
        if (is_int($key)) {
140
            $result[$value] = $callback($value);
141
        } else {
142
            $result[$key] = $value;
143
        }
144
    }
145
    return $result;
146
}
147
148
/**
149
 * Load configuration data from a file into an array.
150
 *
151
 * @param string $path The path to load the file from.
152
 * @param string $php_var The name of the php variable to load from if using the php file type.
153
 * @return array The configuration data.
154
 * @throws InvalidArgumentException Throws an exception when the file type isn't supported.
155
 *
156
 * @category Array Functions
157
 */
158
function array_load($path, $php_var = 'config') {
159
    if (!file_exists($path)) {
160
        return false;
161
    }
162
163
    // Get the extension of the file, but allow for .ini.php, .json.php etc.
164
    $ext = strstr(basename($path), '.');
165
166
    switch ($ext) {
167
//            case '.ini':
168
//            case '.ini.php':
169
//                $loaded = parse_ini_file($path, false, INI_SCANNER_RAW);
170
//                break;
171
        case '.json':
172
        case '.json.php':
173
            $loaded = json_decode(file_get_contents($path), true);
174
            break;
175
        case '.php':
176
            include $path;
177
            $loaded = $$php_var;
178
            break;
179
        case '.ser':
180
        case '.ser.php':
181
            $loaded = unserialize(file_get_contents($path));
182
            break;
183
        case '.yml':
184
        case '.yml.php':
185
            $loaded = yaml_parse_file($path);
186
            break;
187
        default:
188
            throw new InvalidArgumentException("Invalid config extension $ext on $path.", 500);
189
    }
190
    return $loaded;
191
}
192
193
/**
194
 * Save an array of data to a specified path.
195
 *
196
 * @param array $data The data to save.
197
 * @param string $path The path to save to.
198
 * @param string $php_var The name of the php variable to load from if using the php file type.
199
 * @return bool Returns true if the save was successful or false otherwise.
200
 * @throws InvalidArgumentException Throws an exception when the file type isn't supported.
201
 *
202
 * @category Array Functions
203
 */
204
function array_save($data, $path, $php_var = 'config') {
205
    if (!is_array($data)) {
206
        throw new \InvalidArgumentException('Config::saveArray(): Argument #1 is not an array.', 500);
207
    }
208
209
    // Get the extension of the file, but allow for .ini.php, .json.php etc.
210
    $ext = strstr(basename($path), '.');
211
212
    switch ($ext) {
213
//            case '.ini':
214
//            case '.ini.php':
215
//                $ini = static::iniEncode($config);
216
//                $result = file_put_contents_safe($path, $ini);
217
//                break;
218
        case '.json':
219
        case '.json.php':
220
            if (defined('JSON_PRETTY_PRINT')) {
221
                $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
222
            } else {
223
                $json = json_encode($data);
224
            }
225
            $result = file_put_contents_safe($path, $json);
226
            break;
227
        case '.php':
228
            $php = "<?php\n".php_encode($data, $php_var)."\n";
229
            $result = file_put_contents_safe($path, $php);
230
            break;
231
        case '.ser':
232
        case '.ser.php':
233
            $ser = serialize($data);
234
            $result = file_put_contents_safe($path, $ser);
235
            break;
236
        case '.yml':
237
        case '.yml.php':
238
            $yml = yaml_emit($data, YAML_UTF8_ENCODING, YAML_LN_BREAK);
239
            $result = file_put_contents_safe($path, $yml);
240
            break;
241
        default:
242
            throw new \InvalidArgumentException("Invalid config extension $ext on $path.", 500);
243
    }
244
    return $result;
245
}
246
247
/**
248
 * Search an array for a value with a user-defined comparison function.
249
 *
250
 * @param mixed $needle The value to search for.
251
 * @param array $haystack The array to search.
252
 * @param callable $cmp The comparison function to use in the search.
253
 * @return mixed|false Returns the found value or false if the value is not found.
254
 */
255
function array_usearch($needle, array $haystack, callable $cmp) {
256
    $found = array_uintersect($haystack, [$needle], $cmp);
257
258
    if (empty($found)) {
259
        return false;
260
    } else {
261
        return array_pop($found);
262
    }
263
}
264
265
/**
266
 * Select the first non-empty value from an array.
267
 *
268
 * @param array $keys An array of keys to try.
269
 * @param array $array The array to select from.
270
 * @param mixed $default The default value if non of the keys exist.
271
 * @return mixed Returns the first non-empty value of {@link $default} if none are found.
272
 * @category Array Functions
273
 */
274
function array_select(array $keys, array $array, $default = null) {
275
    foreach ($keys as $key) {
276
        if (isset($array[$key]) && $array[$key]) {
277
            return $array[$key];
278
        }
279
    }
280
    return $default;
281
}
282
283
/**
284
 * Make sure that a key exists in an array.
285
 *
286
 * @param string|int $key The array key to ensure.
287
 * @param array &$array The array to modify.
288
 * @param mixed $default The default value to set if key does not exist.
289
 * @category Array Functions
290
 */
291
function array_touch($key, &$array, $default) {
292
    if (!array_key_exists($key, $array)) {
293
        $array[$key] = $default;
294
    }
295
}
296
297
/**
298
 * Take all of the items in an array and make a new array with them specified by mappings.
299
 *
300
 * @param array $array The input array to translate.
301
 * @param array $mappings The mappings to translate the array.
302
 * @return array
303
 *
304
 * @category Array Functions
305
 */
306
function array_translate($array, $mappings) {
307
    $array = (array)$array;
308
    $result = array();
309
    foreach ($mappings as $index => $value) {
310
        if (is_numeric($index)) {
311
            $key = $value;
312
            $newKey = $value;
313
        } else {
314
            $key = $index;
315
            $newKey = $value;
316
        }
317
        if (isset($array[$key])) {
318
            $result[$newKey] = $array[$key];
319
        } else {
320
            $result[$newKey] = null;
321
        }
322
    }
323
    return $result;
324
}
325
326
/**
327
 * Returns the average rating based in the Wilson score interval.
328
 *
329
 *
330
 * @param int $positive The number of positive ratings.
331
 * @param int $total The total number of ratings.
332
 * @param float $confidence Your confidence level.
333
 * @return int
334
 *
335
 * @see http://stackoverflow.com/questions/9478741/mysql-php-for-wilson-score-interval-with-time-gravity
336
 * @see http://evanmiller.org/how-not-to-sort-by-average-rating.html
337
 */
338
//function averageRating($positive, $total, $confidence = 0.95) {
339
//   if ($total == 0)
340
//      return 0;
341
//
342
//   if ($confidence == 0.95)
343
//      $z = 1.96;
344
//   else
345
//      $z = pnormaldist(1 - (1 - $confidence) / 2, 0, 1);
346
//   $p = 1.0 * $positive / $total;
347
//   $s = ($p + $z * $z / (2 * $total) - $z * sqrt(($p * (1 - $p) + $z * $z / (4 * $total)) / $total)) / (1 + $z * $z / $total);
348
//   return $s;
349
//}
350
351
/**
352
 * Base64 Encode a string, but make it suitable to be passed in a url.
353
 *
354
 * @param string $str The string to encode.
355
 * @return string Returns the encoded string.
356
 * @category String Functions
357
 * @see base64_urldecode()
358
 * @see base64_encode()
359
 */
360
function base64url_encode($str) {
361
    return trim(strtr(base64_encode($str), '+/', '-_'), '=');
362
}
363
364
/**
365
 * Decode a string that was encoded using {@link base64_urlencode()}.
366
 *
367
 * @param string $str The encoded string.
368
 * @return string The decoded string.
369
 * @category String Functions
370
 * @see base64_urldecode()
371
 * @see base64_decode()
372
 */
373
function base64url_decode($str) {
374
    return base64_decode(strtr($str, '-_', '+/'));
375
}
376
377
/**
378
 * An alias of {@link config()}.
379
 *
380
 * @param string $key The config key.
381
 * @param string $default The default value if the config setting isn't available.
382
 * @return string The config value.
383
 * @see config()
384
 */
385
function c($key, $default) {
386
    return config($key, $default);
387
}
388
389
//function checkRoute($className, $methodName, &$routed) {
390
//   if ($routed)
391
//      return false;
392
//   if (class_exists($className) && method_exists($className, $methodName))
393
//      return $routed = true;
394
//   return $routed = false;
395
//}
396
397
/**
398
 * Get a value from the config.
399
 *
400
 * @param string $key The config key.
401
 * @param mixed $default The default value if the config setting isn't available.
402
 * @return mixed The config value.
403
 */
404
function config($key, $default = null) {
405
    return Garden\Config::get($key, $default);
406
}
407
408
/**
409
 * Compare two dates formatted as either timestamps or strings.
410
 *
411
 * @param mixed $date1 The first date to compare expressed as an integer timestamp or a string date.
412
 * @param mixed $date2 The second date to compare expressed as an integer timestamp or a string date.
413
 * @return int Returns `1` if {@link $date1} > {@link $date2}, `-1` if {@link $date1} > {@link $date2},
414
 * or `0` if the two dates are equal.
415
 * @category Date/Time Functions
416
 */
417
function datecmp($date1, $date2) {
418
    if (is_numeric($date1)) {
419
        $timestamp1 = $date1;
420
    } else {
421
        $timestamp1 = strtotime($date1);
422
    }
423
424
    if (is_numeric($date2)) {
425
        $timestamp2 = $date2;
426
    } else {
427
        $timestamp2 = strtotime($date2);
428
    }
429
430
    if ($timestamp1 == $timestamp2) {
431
        return 0;
432
    } elseif ($timestamp1 > $timestamp2) {
433
        return 1;
434
    } else {
435
        return -1;
436
    }
437
}
438
439
/**
440
 * Mark something as deprecated.
441
 *
442
 * When passing the {@link $name} argument, try using the following naming convention for names.
443
 *
444
 * - Functions: function_name()
445
 * - Classes: ClassName
446
 * - Static methods: ClassName::methodName()
447
 * - Instance methods: ClassName->methodName()
448
 *
449
 * @param string $name The name of the deprecated function.
450
 * @param string $newname The name of the new function that should be used instead.
451
 */
452
function deprecated($name, $newname = '') {
453
    $msg = $name.' is deprecated.';
454
    if ($newname) {
455
        $msg .= " Use $newname instead.";
456
    }
457
458
    trigger_error($msg, E_USER_DEPRECATED);
459
}
460
461
/**
462
 * A version of file_put_contents() that is multi-thread safe.
463
 *
464
 * @param string $filename Path to the file where to write the data.
465
 * @param mixed $data The data to write. Can be either a string, an array or a stream resource.
466
 * @param int $mode The permissions to set on a new file.
467
 * @return boolean
468
 * @category Filesystem Functions
469
 * @see http://php.net/file_put_contents
470
 */
471
function file_put_contents_safe($filename, $data, $mode = 0644) {
472
    $temp = tempnam(dirname($filename), 'atomic');
473
474
    if (!($fp = @fopen($temp, 'wb'))) {
475
        $temp = dirname($filename).DIRECTORY_SEPARATOR.uniqid('atomic');
476
        if (!($fp = @fopen($temp, 'wb'))) {
477
            trigger_error("file_put_contents_safe() : error writing temporary file '$temp'", E_USER_WARNING);
478
            return false;
479
        }
480
    }
481
482
    fwrite($fp, $data);
483
    fclose($fp);
484
485
    if (!@rename($temp, $filename)) {
486
        @unlink($filename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
487
        @rename($temp, $filename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
488
    }
489
490
    @chmod($filename, $mode);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
491
    return true;
492
}
493
494
/**
495
 * Force a value into a boolean.
496
 *
497
 * @param mixed $value The value to force.
498
 * @return boolean Returns the boolean value of {@link $value}.
499
 * @category Type Functions
500
 */
501
function force_bool($value) {
502
    if (is_string($value)) {
503
        switch (strtolower($value)) {
504
            case 'disabled':
505
            case 'false':
506
            case 'no':
507
            case 'off':
508
            case '':
509
                return false;
510
        }
511
        return true;
512
    }
513
    return (bool)$value;
514
}
515
516
/**
517
 * Force a string to look like an ip address (v4).
518
 *
519
 * @param string $ip The ip string to look at.
520
 * @return string|null The ipv4 address or null if {@link $ip} is empty.
521
 */
522
function force_ipv4($ip) {
523
    if (!$ip) {
524
        return null;
525
    }
526
527
    if (strpos($ip, ',') !== false) {
528
        $ip = substr($ip, 0, strpos($ip, ','));
529
    }
530
531
    // Make sure we have a valid ip.
532
    if (preg_match('`(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})`', $ip, $m)) {
533
        $ip = $m[1];
534
    } elseif ($ip === '::1') {
535
        $ip = '127.0.0.1';
536
    } else {
537
        $ip = '0.0.0.0'; // unknown ip
538
    }
539
    return $ip;
540
}
541
542
/**
543
 * Force a value to be an integer.
544
 *
545
 * @param mixed $value The value to force.
546
 * @return int Returns the integer value of {@link $value}.
547
 * @category Type Functions
548
 */
549
function force_int($value) {
550
    if (is_string($value)) {
551
        switch (strtolower($value)) {
552
            case 'disabled':
553
            case 'false':
554
            case 'no':
555
            case 'off':
556
            case '':
557
                return 0;
558
            case 'enabled':
559
            case 'true':
560
            case 'yes':
561
            case 'on':
562
                return 1;
563
        }
564
    }
565
    return intval($value);
566
}
567
568
function garden_error_handler($number, $message, $file, $line, $args) {
569
    $error_reporting = error_reporting();
570
    // Ignore errors that are below the current error reporting level.
571
    if (($error_reporting & $number) != $number) {
572
        return false;
573
    }
574
575
    $backtrace = debug_backtrace();
576
577
    throw new Garden\Exception\ErrorException($message, $number, $file, $line, $args, $backtrace);
578
}
579
580
/**
581
 * Like {@link implode()}, but joins array keys and values.
582
 *
583
 * @param string $elemglue The string that separates each element of the array.
584
 * @param string $keyglue The string that separates keys and values.
585
 * @param array $pieces The array of strings to implode.
586
 * @return string Returns the imploded array as a string.
587
 *
588
 * @category Array Functions
589
 * @category String Functions
590
 */
591
function implode_assoc($elemglue, $keyglue, $pieces) {
592
    $result = '';
593
594
    foreach ($pieces as $key => $value) {
595
        if ($result) {
596
            $result .= $elemglue;
597
        }
598
599
        $result .= $key.$keyglue.$value;
600
    }
601
    return $result;
602
}
603
604
/**
605
 * Whether or not a string is a url in the form http://, https://, or //.
606
 *
607
 * @param string $str The string to check.
608
 * @return bool
609
 *
610
 * @category String Functions
611
 * @category Internet Functions
612
 */
613
function is_url($str) {
614
    if (!$str) {
615
        return false;
616
    }
617
    if (substr($str, 0, 2) == '//') {
618
        return true;
619
    }
620
    if (strpos($str, '://', 1) !== false) {
621
        return true;
622
    }
623
    return false;
624
}
625
626
/**
627
 * Strip a substring from the beginning of a string.
628
 *
629
 * @param string $mainstr The main string to look at (the haystack).
630
 * @param string $substr The substring to search trim (the needle).
631
 * @return string
632
 *
633
 * @category String Functions
634
 */
635
function ltrim_substr($mainstr, $substr) {
636
    if (strncasecmp($mainstr, $substr, strlen($substr)) === 0) {
637
        return substr($mainstr, strlen($substr));
638
    }
639
    return $mainstr;
640
}
641
642
/**
643
 * Get the file extension from a mime-type.
644
 *
645
 * @param string $mime The mime type.
646
 * @param string $ext If this argument is specified then this extension will be added to the list of known types.
647
 * @return string The file extension without the dot.
648
 * @category Internet Functions
649
 * @category String Functions
650
 */
651
function mime2ext($mime, $ext = null) {
652
    static $known = array('text/plain' => '.txt', 'image/jpeg' => '.jpg', 'application/rss+xml' => '.rss');
653
    $mime = strtolower($mime);
654
655
    if ($ext !== null) {
656
        $known[$mime] = '.'.ltrim($ext, '.');
657
    }
658
659
    if (array_key_exists($mime, $known)) {
660
        return $known[$mime];
661
    }
662
663
    // We don't know the mime type so we need to just return the second part as the extension.
664
    $result = trim(strrchr($mime, '/'), '/');
665
666
    if (substr($result, 0, 2) === 'x-') {
667
        $result = substr($result, 2);
668
    }
669
670
    return '.'.$result;
671
}
672
673
/**
674
 * Encode a php array nicely.
675
 *
676
 * @param array $data The data to encode.
677
 * @param string $php_var The name of the php variable.
678
 * @return string Returns a string of the encoded data.
679
 *
680
 * @category Array Functions
681
 */
682
function php_encode($data, $php_var = 'config') {
683
    if (is_array($data)) {
684
        $result = '';
685
        $lastHeading = '';
686
        foreach ($data as $key => $value) {
687
            // Figure out the heading.
688
            if (($pos = strpos($key, '.')) !== false) {
689
                $heading = str_replace(array("\n", "\r"), ' ', substr($key, 0, $pos));
690
            } else {
691
                $heading = substr($key, 0, 1);
692
            }
693
694
            if ($heading !== $lastHeading) {
695
                if (strlen($heading) === 1) {
696
                    // Don't emit single letter headings, but space them out.
697
                    $result .= "\n";
698
                } else {
699
                    $result .= "\n// ".$heading."\n";
700
                }
701
                $lastHeading = $heading;
702
            }
703
704
            $result .= '$'.$php_var.'['.var_export($key, true).'] = '.var_export($value, true).";\n";
705
        }
706
    } else {
707
        $result = "\$$php_var = ".var_export($data, true).";\n";
708
    }
709
    return $result;
710
}
711
712
/**
713
 * Reflect the arguments on a callback and returns them as an associative array.
714
 *
715
 * @param callable $callback A callback to the function.
716
 * @param array $args An array of arguments.
717
 * @param array $get An optional other array of arguments.
718
 * @return array The arguments in an associative array, in order ready to be passed to call_user_func_array().
719
 * @throws Exception Throws an exception when {@link callback} isn't a valid callback.
720
 * @category Type Functions
721
 */
722
function reflect_args(callable $callback, $args, $get = null) {
723
    if (is_array($get)) {
724
        $args = array_merge($get, $args);
725
    }
726
    $args = array_change_key_case($args);
727
728
    if (is_string($callback) || (is_object($callback) && $callback instanceof Closure)) {
729
        $meth = new ReflectionFunction($callback);
730
        $meth_name = $meth;
731
    } else {
732
        $meth = new ReflectionMethod($callback[0], $callback[1]);
733
        if (is_string($callback[0])) {
734
            $meth_name = $callback[0].'::'.$meth->getName();
735
        } else {
736
            $meth_name = get_class($callback[0]).'->'.$meth->getName();
737
        }
738
    }
739
740
    $meth_params = $meth->getParameters();
741
742
    $call_args = array();
743
    $missing_args = array();
744
745
    // Set all of the parameters.
746
    foreach ($meth_params as $index => $meth_param) {
747
        $param_name = $meth_param->getName();
748
        $param_namel = strtolower($param_name);
749
750
        if (isset($args[$param_namel])) {
751
            $param_value = $args[$param_namel];
752
        } elseif (isset($args[$index])) {
753
            $param_value = $args[$index];
754
        } elseif ($meth_param->isDefaultValueAvailable()) {
755
            $param_value = $meth_param->getDefaultValue();
756
        } else {
757
            $param_value = null;
758
            $missing_args[] = '$'.$param_name;
759
        }
760
761
        $call_args[$param_name] = $param_value;
762
    }
763
764
    // Add optional parameters so that methods that use get_func_args() will still work.
765
    for ($index = count($call_args); array_key_exists($index, $args); $index++) {
766
        $call_args[$index] = $args[$index];
767
    }
768
769
    if (count($missing_args) > 0) {
770
        trigger_error("$meth_name() expects the following parameters: ".implode(', ', $missing_args).'.', E_USER_NOTICE);
771
    }
772
773
    return $call_args;
774
}
775
776
/**
777
 * Strip a substring rom the end of a string.
778
 *
779
 * @param string $mainstr The main string to search (the haystack).
780
 * @param string $substr The substring to trim (the needle).
781
 * @return string Returns the trimmed string or {@link $mainstr} if {@link $substr} was not found.
782
 * @category String Functions
783
 */
784
function rtrim_substr($mainstr, $substr) {
785
    if (strcasecmp(substr($mainstr, -strlen($substr)), $substr) === 0) {
786
        return substr($mainstr, 0, -strlen($substr));
787
    }
788
    return $mainstr;
789
}
790
791
/**
792
 * Returns whether or not a string begins with another string.
793
 *
794
 * This function is not case-sensitive.
795
 *
796
 * @param string $haystack The string to test.
797
 * @param string $needle The substring to test against.
798
 * @return bool Whether or not `$string` begins with `$with`.
799
 * @category String Functions
800
 */
801
function str_begins($haystack, $needle) {
802
    return strncasecmp($haystack, $needle, strlen($needle)) === 0;
803
}
804
805
/**
806
 * Returns whether or not a string ends with another string.
807
 *
808
 * This function is not case-sensitive.
809
 *
810
 * @param string $haystack The string to test.
811
 * @param string $needle The substring to test against.
812
 * @return bool Whether or not `$string` ends with `$with`.
813
 * @category String Functions
814
 */
815
function str_ends($haystack, $needle) {
816
    return strcasecmp(substr($haystack, -strlen($needle)), $needle) === 0;
817
}
818
819
$translations = [];
820
821
/**
822
 * Translate a string.
823
 *
824
 * @param string $code The translation code.
825
 * @param string $default The default if the translation is not found.
826
 * @return string The translated string.
827
 *
828
 * @category String Functions
829
 * @category Localization Functions
830
 */
831
function t($code, $default = null) {
832
    global $translations;
833
834
    if (substr($code, 0, 1) === '@') {
835
        return substr($code, 1);
836
    } elseif (isset($translations[$code])) {
837
        return $translations[$code];
838
    } elseif ($default !== null) {
839
        return $default;
840
    } else {
841
        return $code;
842
    }
843
}
844
845
/**
846
 * A version of {@link sprintf()} That translates the string format.
847
 *
848
 * @param string $formatCode The format translation code.
849
 * @param mixed $arg1 The arguments to pass to {@link sprintf()}.
850
 * @return string The translated string.
851
 */
852
function sprintft($formatCode, $arg1 = null) {
853
    $args = func_get_args();
854
    $args[0] = t($formatCode);
855
    return call_user_func_array('sprintf', $args);
856
}
857
858
/**
859
 * Make sure that a directory exists.
860
 *
861
 * @param string $dir The name of the directory.
862
 * @param int $mode The file permissions on the folder if it's created.
863
 * @throws Exception Throws an exception when {@link $dir} is a file.
864
 * @category Filesystem Functions
865
 */
866
function touchdir($dir, $mode = 0777) {
867
    if (!file_exists($dir)) {
868
        mkdir($dir, $mode, true);
869
    } elseif (!is_dir($dir)) {
870
        throw new Exception("The specified directory already exists as a file. ($dir)", 400);
871
    }
872
}
873
874
/**
875
 * Safely get a value out of an array.
876
 *
877
 * This function will always return a value even if the array key doesn't exist.
878
 * The val() function is one of the biggest workhorses of Vanilla and shows up a lot throughout other code.
879
 * It's much preferable to use this function if your not sure whether or not an array key exists rather than
880
 * using @ error suppression.
881
 *
882
 * This function uses optimizations found in the [facebook libphputil library](https://github.com/facebook/libphutil).
883
 *
884
 * @param string|int $key The array key.
885
 * @param array|object $array The array to get the value from.
886
 * @param mixed $default The default value to return if the key doesn't exist.
887
 * @return mixed The item from the array or `$default` if the array key doesn't exist.
888
 * @category Array Functions
889
 */
890
function val($key, $array, $default = null) {
891
    if (is_array($array)) {
892
        // isset() is a micro-optimization - it is fast but fails for null values.
893
        if (isset($array[$key])) {
894
            return $array[$key];
895
        }
896
897
        // Comparing $default is also a micro-optimization.
898
        if ($default === null || array_key_exists($key, $array)) {
899
            return null;
900
        }
901
    } elseif (is_object($array)) {
902
        if (isset($array->$key)) {
903
            return $array->$key;
904
        }
905
906
        if ($default === null || property_exists($array, $key)) {
907
            return null;
908
        }
909
    }
910
911
    return $default;
912
}
913
914
/**
915
 * Return the value from an associative array.
916
 *
917
 * This function differs from val() in that $key can be an array that will be used to walk a nested array.
918
 *
919
 * @param array|string $keys The keys or property names of the value. This can be an array or dot-seperated string.
920
 * @param array|object $array The array or object to search.
921
 * @param mixed $default The value to return if the key does not exist.
922
 * @return mixed The value from the array or object.
923
 * @category Array Functions
924
 */
925
function valr($keys, $array, $default = null) {
926
    if (is_string($keys)) {
927
        $keys = explode('.', $keys);
928
    }
929
930
    $value = $array;
931
    for ($i = 0; $i < count($keys); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
932
        $SubKey = $keys[$i];
933
934
        if (is_array($value) && isset($value[$SubKey])) {
935
            $value = $value[$SubKey];
936
        } elseif (is_object($value) && isset($value->$SubKey)) {
937
            $value = $value->$SubKey;
938
        } else {
939
            return $default;
940
        }
941
    }
942
    return $value;
943
}
944