PageRenderTime 94ms CodeModel.GetById 20ms app.highlight 58ms RepoModel.GetById 1ms app.codeStats 1ms

/app/classes/util.php

https://github.com/schwarzmedia/bolt
PHP | 2106 lines | 1121 code | 201 blank | 784 comment | 300 complexity | b4828945cf5df6f8abe562e60da86959 MD5 | raw file
   1<?php
   2/**
   3 * util.php
   4 *
   5 * util.php is a library of helper functions for common tasks such as
   6 * formatting bytes as a string or displaying a date in terms of how long ago
   7 * it was in human readable terms (E.g. 4 minutes ago). The library is entirely
   8 * contained within a single file and hosts no dependencies. The library is
   9 * designed to avoid any possible conflicts.
  10 *
  11 * @author   Brandon Wamboldt
  12 * @link     http://github.com/brandonwamboldt/utilphp/ Official Documentation
  13 * @version  1.0.003
  14 */
  15
  16if ( ! class_exists( 'util' ) ) {
  17    class util
  18    {
  19        /**
  20         * A constant representing the number of seconds in a minute, for
  21         * making code more verbose
  22         *
  23         * @since  1.0.000
  24         * @var    int
  25         */
  26        const SECONDS_IN_A_MINUTE = 60;
  27
  28        /**
  29         * A constant representing the number of seconds in an hour, for making
  30         * code more verbose
  31         *
  32         * @since  1.0.000
  33         * @var    int
  34         */
  35        const SECONDS_IN_A_HOUR   = 3600;
  36        const SECONDS_IN_AN_HOUR  = 3600;
  37
  38        /**
  39         * A constant representing the number of seconds in a day, for making
  40         * code more verbose
  41         *
  42         * @since  1.0.000
  43         * @var    int
  44         */
  45        const SECONDS_IN_A_DAY    = 86400;
  46
  47        /**
  48         * A constant representing the number of seconds in a week, for making
  49         * code more verbose
  50         *
  51         * @since  1.0.000
  52         * @var    int
  53         */
  54        const SECONDS_IN_A_WEEK   = 604800;
  55
  56        /**
  57         * A constant representing the number of seconds in a month (30 days),
  58         * for making code more verbose
  59         *
  60         * @since  1.0.000
  61         * @var    int
  62         */
  63        const SECONDS_IN_A_MONTH  = 2592000;
  64
  65        /**
  66         * A constant representing the number of seconds in a year (365 days),
  67         * for making code more verbose
  68         *
  69         * @since  1.0.000
  70         * @var    int
  71         */
  72        const SECONDS_IN_A_YEAR   = 31536000;
  73
  74        /**
  75         * A collapse icon, using in the dump_var function to allow collapsing
  76         * an array or object
  77         *
  78         * @access  public
  79         * @since   1.0.000
  80         * @static
  81         * @var     string
  82         */
  83        public static $icon_collapse = 'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAMAAADXT/YiAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3MjlFRjQ2NkM5QzJFMTExOTA0MzkwRkI0M0ZCODY4RCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpFNzFDNDQyNEMyQzkxMUUxOTU4MEM4M0UxRDA0MUVGNSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpFNzFDNDQyM0MyQzkxMUUxOTU4MEM4M0UxRDA0MUVGNSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NDlFRjQ2NkM5QzJFMTExOTA0MzkwRkI0M0ZCODY4RCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3MjlFRjQ2NkM5QzJFMTExOTA0MzkwRkI0M0ZCODY4RCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PuF4AWkAAAA2UExURU9t2DBStczM/1h16DNmzHiW7iNFrypMvrnD52yJ4ezs7Onp6ejo6P///+Tk5GSG7D9h5SRGq0Q2K74AAAA/SURBVHjaLMhZDsAgDANRY3ZISnP/y1ZWeV+jAeuRSky6cKL4ryDdSggP8UC7r6GvR1YHxjazPQDmVzI/AQYAnFQDdVSJ80EAAAAASUVORK5CYII=';
  84
  85        /**
  86         * A collapse icon, using in the dump_var function to allow collapsing
  87         * an array or object
  88         *
  89         * @access  public
  90         * @since   1.0.000
  91         * @static
  92         * @var     string
  93         */
  94        public static $icon_expand = 'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAMAAADXT/YiAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3MTlFRjQ2NkM5QzJFMTExOTA0MzkwRkI0M0ZCODY4RCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpFQzZERTJDNEMyQzkxMUUxODRCQzgyRUNDMzZEQkZFQiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpFQzZERTJDM0MyQzkxMUUxODRCQzgyRUNDMzZEQkZFQiIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3MzlFRjQ2NkM5QzJFMTExOTA0MzkwRkI0M0ZCODY4RCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3MTlFRjQ2NkM5QzJFMTExOTA0MzkwRkI0M0ZCODY4RCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkmDvWIAAABIUExURU9t2MzM/3iW7ubm59/f5urq85mZzOvr6////9ra38zMzObm5rfB8FZz5myJ4SNFrypMvjBStTNmzOvr+mSG7OXl8T9h5SRGq/OfqCEAAABKSURBVHjaFMlbEoAwCEPRULXF2jdW9r9T4czcyUdA4XWB0IgdNSybxU9amMzHzDlPKKu7Fd1e6+wY195jW0ARYZECxPq5Gn8BBgCr0gQmxpjKAwAAAABJRU5ErkJggg==';
  95
  96        /**
  97         * Retrieve a value from the $_POST array, or return a given default if
  98         * the index isn't set
  99         *
 100         * The first n parameters represent the fields to retrieve, being a
 101         * single index or an array of indexes to access multi-level arrays.
 102         *
 103         * Calling the function as util::post_var( ['tags', '1412'] ) is
 104         * identical to using $_POST['tags']['1412'].
 105         *
 106         * @param   string  $fields   The name of the field to retrieve
 107         * @param   mixed   $default  A default value to return if the
 108         *                            requested variable isn't set
 109         * @return  mixed
 110         *
 111         * @see     array_get()
 112         *
 113         * @access  public
 114         * @since   1.0.000
 115         * @static
 116         */
 117        public static function post_var( $fields, $default = NULL )
 118        {
 119            return self::array_get( $_POST, $fields, $default );
 120        }
 121
 122        /**
 123         * Retrieve a value from the $_GET array, or return a given default if
 124         * the index isn't set
 125         *
 126         * The first n parameters represent the fields to retrieve, being a
 127         * single index or an array of indexes to access multi-level arrays.
 128         *
 129         * Calling the function as util::get_var( ['tags', '1412'] ) is
 130         * identical to using $_GET['tags']['1412'].
 131         *
 132         * @param   string  $fields   The name of the field to retrieve
 133         * @param   mixed   $default  A default value to return if the
 134         *                            requested variable isn't set
 135         * @return  mixed
 136         *
 137         * @see     array_get()
 138         *
 139         * @access  public
 140         * @since   1.0.000
 141         * @static
 142         */
 143        public static function get_var( $fields, $default = NULL )
 144        {
 145            return self::array_get( $_GET, $fields, $default );
 146        }
 147
 148        /**
 149         * Retrieve a value from the $_GET or the $_POST array, or return a
 150         * given default if the index isn't set. You may expect this function
 151         * to check the $_REQUEST variable, but the desired behavior is often
 152         * to check $_GET or $_POST. To avoid screwing stuff up if $_COOKIE is
 153         * set, and to avoid relying on the user to set the request_order
 154         * option, we just make that assumption for them.
 155         *
 156         * The first n parameters represent the fields to retrieve, being a
 157         * single index or an array of indexes to access multi-level arrays.
 158         *
 159         * Calling the function as util::request_var( ['tags', '1412'] ) is
 160         * identical to using $_REQUEST['tags']['1412'].
 161         *
 162         * @param   string  $fields   The name of the field to retrieve
 163         * @param   mixed   $default  A default value to return if the requested variable isn't set
 164         * @return  mixed
 165         *
 166         * @see     array_get()
 167         *
 168         * @access  public
 169         * @since   1.0.000
 170         * @static
 171         */
 172        public static function request_var( $fields, $default = NULL )
 173        {
 174            if ( strstr( ini_get( 'request_order' ), 'GP' ) ) {
 175                return self::array_get( array_merge( $_POST, $_GET ), $fields, $default );
 176            } else {
 177                return self::array_get( array_merge( $_GET, $_POST ), $fields, $default );
 178            }
 179        }
 180
 181        /**
 182         * Retrieve a value from the $_SESSION array, or return a given default
 183         * if the index isn't set
 184         *
 185         * The first n parameters represent the fields to retrieve, being a
 186         * single index or an array of indexes to access multi-level arrays.
 187         *
 188         * Calling the function as util::session_var( ['tags', '1412'] ) is
 189         * identical to using $_SESSION['tags']['1412'].
 190         *
 191         * @param   string  $fields   The name of the field to retrieve
 192         * @param   mixed   $default  A default value to return if the
 193         *                            requested variable isn't set
 194         * @return  mixed
 195         *
 196         * @see     array_get()
 197         *
 198         * @access  public
 199         * @since   1.0.000
 200         * @static
 201         */
 202        public static function session_var( $fields, $default = NULL )
 203        {
 204            return self::array_get( $_SESSION, $fields, $default );
 205        }
 206
 207        /**
 208         * Retrieve a value from the $_COOKIE array, or return a given default
 209         * if the index isn't set
 210         *
 211         * The first n parameters represent the fields to retrieve, being a
 212         * single index or an array of indexes to access multi-level arrays.
 213         *
 214         * Calling the function as util::cookie_var( ['tags', '1412'] ) is
 215         * identical to using $_COOKIE['tags']['1412'].
 216         *
 217         * @param   string  $fields   The name of the field to retrieve
 218         * @param   mixed   $default  A default value to return if the
 219         *                            requested variable isn't set
 220         * @return  mixed
 221         *
 222         * @see     array_get()
 223         *
 224         * @access  public
 225         * @since   1.0.000
 226         * @static
 227         */
 228        public static function cookie_var( $fields, $default = NULL )
 229        {
 230            return self::array_get( $_COOKIE, $fields, $default );
 231        }
 232
 233        /**
 234         * Access an array index, retrieving the value stored there if it
 235         * exists or a default if it does not. This function allows you to
 236         * concisely access an index which may or may not exist without
 237         * raising a warning
 238         *
 239         * @param array $array
 240         * @param $fields
 241         * @param   mixed $default Default value to return if the key is not
 242         *                            present in the array
 243         * @internal param array $var Array to access
 244         * @internal param string $field Index to access in the array
 245         * @return  mixed
 246         *
 247         * @access  public
 248         * @since   1.0.000
 249         * @static
 250         */
 251        public static function array_get( array $array, $fields, $default = NULL )
 252        {
 253            if ( ! is_array( $array ) ) {
 254                return $default;
 255            } else if ( ! is_array( $fields ) ) {
 256                if ( isset( $array[$fields] ) ) {
 257                    return $array[$fields];
 258                } else {
 259                    return $default;
 260                }
 261            } else {
 262                foreach ( $fields as $field ) {
 263                    $found_it = false;
 264
 265                    if ( ! is_array( $array ) ) {
 266                        break;
 267                    }
 268
 269                    foreach ( $array as $key => $value ) {
 270                        if ( $key == $field ) {
 271                            $found_it = true;
 272                            $array = $value;
 273
 274                            break;
 275                        }
 276                    }
 277                }
 278
 279                if ( $found_it ) {
 280                    return $array;
 281                } else {
 282                    return $default;
 283                }
 284            }
 285        }
 286
 287        /**
 288         * Display a variable's contents using nice HTML formatting and will
 289         * properly display the value of booleans as true or false
 290         *
 291         * @param   mixed $var The variable to dump
 292         * @param bool $return
 293         * @return  string
 294         *
 295         * @see     var_dump_plain()
 296         *
 297         * @access  public
 298         * @since   1.0.000
 299         * @static
 300         */
 301        public static function var_dump( $var, $return = false )
 302        {
 303            $html = '<pre style="margin-bottom: 18px;' .
 304                'background: #f7f7f9;' .
 305                'border: 1px solid #e1e1e8;' .
 306                'padding: 8px;' .
 307                'border-radius: 4px;' .
 308                '-moz-border-radius: 4px;' .
 309                '-webkit-border radius: 4px;' .
 310                'display: block;' .
 311                'font-size: 12.05px;' .
 312                'white-space: pre-wrap;' .
 313                'word-wrap: break-word;' .
 314                'color: #333;' .
 315                'text-align: left;' .
 316                'font-family: Menlo,Monaco,Consolas,\'Courier New\',monospace;">';
 317            $html .= self::var_dump_plain( $var, true );
 318            $html .= '</pre>';
 319
 320            if ( ! $return ) {
 321                echo $html;
 322            } else {
 323                return $html;
 324            }
 325        }
 326
 327        /**
 328         * Display a variable's contents using nice HTML formatting (Without
 329         * the <pre> tag) and will properly display the values of variables
 330         * like booleans and resources. Supports collapsable arrays and objects
 331         * as well.
 332         *
 333         * @param   mixed $var The variable to dump
 334         * @param bool $traversedeeper
 335         * @return  string
 336         *
 337         * @access  public
 338         * @since   1.0.000
 339         * @static
 340         */
 341        public static function var_dump_plain( $var, $traversedeeper = false )
 342        {
 343
 344            // Don't traverse into Closures or Silex / Symfony objects..
 345            if (is_object($var)) {
 346                list($root) = explode("\\", get_class($var));
 347                // echo "[ " .$root . " - " .get_class($var)." ]";
 348                if ( !$traversedeeper && ( (get_class($var) == "Bolt\\Application") || in_array($root, array('Closure', 'Silex', 'Symfony')) || substr($root, 0, 5)=="Twig_" )) {
 349                    $html = '<span style="color:#588bff;">object</span>(' . get_class( $var ) . ') ';
 350                    // echo "[return]\n";
 351                    return $html;
 352                }
 353                // echo "[stay]\n";
 354            }
 355
 356            $html = '';
 357
 358            if ( is_bool( $var ) ) {
 359                $html .= '<span style="color:#588bff;">boo</span><span style="color:#999;">(</span><strong>' . ( ( $var ) ? 'true' : 'false' ) . '</strong><span style="color:#999;">)</span>';
 360            } else if ( is_int( $var ) ) {
 361                $html .= '<span style="color:#588bff;">int</span><span style="color:#999;">(</span><strong>' . $var . '</strong><span style="color:#999;">)</span>';
 362            } else if ( is_float( $var ) ) {
 363                $html .= '<span style="color:#588bff;">flo</span><span style="color:#999;">(</span><strong>' . $var . '</strong><span style="color:#999;">)</span>';
 364            } else if ( is_string( $var ) ) {
 365                $html .= '<span style="color:#588bff;">str</span><span style="color:#999;">(</span>' . strlen( $var ) . '<span style="color:#999;">)</span> <strong>"' . self::htmlentities( $var ) . '"</strong>';
 366            } else if ( is_null( $var ) ) {
 367                $html .= '<strong>NULL</strong>';
 368            } else if ( is_resource( $var ) ) {
 369                $html .= '<span style="color:#588bff;">res</span>("' . get_resource_type( $var ) . '") <strong>"' . $var . '"</strong>';
 370            } else if ( is_array( $var ) ) {
 371                $uuid =  uniqid('include-php-',true);
 372
 373                $html .= '<span style="color:#588bff;">arr</span>(' . count( $var ) . ')';
 374
 375                if ( ! empty( $var ) ) {
 376                    $html .= ' <img id="' . $uuid . '" data-collapse="data:image/png;base64,' . self::$icon_collapse . '" style="position:relative;left:-5px;top:-1px;cursor:pointer;width:9px!important;height:9px!important;" src="data:image/png;base64,' . self::$icon_expand . '" /> <span id="' . $uuid . '-collapsable" style="display: none;">[<br />';
 377
 378                    $indent = 4;
 379                    $longest_key = 0;
 380
 381                    foreach( $var as $key => $value ) {
 382                        if ( is_string( $key ) ) {
 383                            $longest_key = max( $longest_key, strlen( $key ) + 2 );
 384                        } else {
 385                            $longest_key = max( $longest_key, strlen( $key ) );
 386                        }
 387                    }
 388
 389                    foreach ( $var as $key => $value ) {
 390                        if ( is_numeric( $key ) ) {
 391                            $html .= str_repeat( ' ', $indent ) . str_pad( $key, $longest_key, '_');
 392                        } else {
 393                            $html .= str_repeat( ' ', $indent ) . str_pad( '"' . self::htmlentities( $key ) . '"', $longest_key, ' ' );
 394                        }
 395
 396                        $html .= ' => ';
 397
 398                        $value = explode( '<br />', self::var_dump_plain( $value ) );
 399
 400                        foreach ( $value as $line => $val ) {
 401                            if ( $line != 0 ) {
 402                                $value[$line] = str_repeat( ' ', $indent * 2 ) . $val;
 403                            }
 404                        }
 405
 406                        $html .= implode( '<br />', $value ) . '<br />';
 407                    }
 408
 409                    $html .= ']</span>';
 410
 411                    $html .= preg_replace( '/ +/', ' ', '<script type="text/javascript">(function() {
 412                    var img = document.getElementById("' . $uuid . '");
 413                    img.onclick = function() {
 414                        if ( document.getElementById("' . $uuid . '-collapsable").style.display == "none" ) {
 415                            document.getElementById("' . $uuid . '-collapsable").style.display = "inline";
 416                            img.setAttribute( "data-expand", img.getAttribute("src") );
 417                            img.src = img.getAttribute("data-collapse");
 418                            var previousSibling = document.getElementById("' . $uuid . '-collapsable").previousSibling;
 419                        } else {
 420                            document.getElementById("' . $uuid . '-collapsable").style.display = "none";
 421                            img.src = img.getAttribute("data-expand");
 422                            var previousSibling = document.getElementById("' . $uuid . '-collapsable").previousSibling;
 423                        }
 424                    };
 425                    })();
 426                    </script>' );
 427                }
 428
 429            } else if ( is_object( $var ) ) {
 430                $uuid =  uniqid('include-php-',true);
 431
 432                $html .= '<span style="color:#588bff;">object</span>(' . get_class( $var ) . ') <img id="' . $uuid . '" data-collapse="data:image/png;base64,' . self::$icon_collapse . '" style="position:relative;left:-5px;top:-1px;cursor:pointer;width:9px!important;height:9px!important;" src="data:image/png;base64,' . self::$icon_expand . '" /> <span id="' . $uuid . '-collapsable" style="display: none;">[<br />';
 433
 434                $original = $var;
 435                $var = (array) $var;
 436
 437                $indent = 4;
 438                $longest_key = 0;
 439
 440                foreach( $var as $key => $value ) {
 441                    if ( substr( $key, 0, 2 ) == "\0*" ) {
 442                        unset( $var[$key] );
 443                        $key = 'protected:' . substr( $key, 2 );
 444                        $var[$key] = $value;
 445                    } else if ( substr( $key, 0, 1 ) == "\0" ) {
 446                        unset( $var[$key] );
 447                        $key = 'private:' . substr( $key, 1, strpos( substr( $key, 1 ), "\0" ) ) . ':' . substr( $key, strpos( substr( $key, 1 ), "\0" ) + 1 );
 448                        $var[$key] = $value;
 449                    }
 450
 451                    if ( is_string( $key ) ) {
 452                        $longest_key = max( $longest_key, strlen( $key ) + 2 );
 453                    } else {
 454                        $longest_key = max( $longest_key, strlen( $key ) );
 455                    }
 456                }
 457
 458                foreach ( $var as $key => $value ) {
 459                    if ( is_numeric( $key ) ) {
 460                        $html .= str_repeat( ' ', $indent ) . str_pad( $key, $longest_key, ' ');
 461                    } else {
 462                        $html .= str_repeat( ' ', $indent ) . str_pad( '"' . self::htmlentities( $key ) . '"', $longest_key, ' ' );
 463                    }
 464
 465                    $html .= ' => ';
 466
 467                    $value = explode( '<br />', self::var_dump_plain( $value ) );
 468
 469                    foreach ( $value as $line => $val ) {
 470                        if ( $line != 0 ) {
 471                            $value[$line] = str_repeat( ' ', $indent * 2 ) . $val;
 472                        }
 473                    }
 474
 475                    $html .= implode( '<br />', $value ) . '<br />';
 476                }
 477
 478                $html .= ']</span>';
 479
 480                $html .= preg_replace( '/ +/', ' ', '<script type="text/javascript">(function() {
 481                var img = document.getElementById("' . $uuid . '");
 482                img.onclick = function() {
 483                    if ( document.getElementById("' . $uuid . '-collapsable").style.display == "none" ) {
 484                        document.getElementById("' . $uuid . '-collapsable").style.display = "inline";
 485                        img.setAttribute( "data-expand", img.getAttribute("src") );
 486                        img.src = img.getAttribute("data-collapse");
 487                        var previousSibling = document.getElementById("' . $uuid . '-collapsable").previousSibling;
 488                    } else {
 489                        document.getElementById("' . $uuid . '-collapsable").style.display = "none";
 490                        img.src = img.getAttribute("data-expand");
 491                        var previousSibling = document.getElementById("' . $uuid . '-collapsable").previousSibling;
 492                    }
 493                };
 494                })();
 495                </script>' );
 496            }
 497
 498            return $html;
 499        }
 500
 501        /**
 502         * Converts any accent characters to their equivalent normal characters
 503         * and converts any other non-alphanumeric characters to dashes, then
 504         * converts any sequence of two or more dashes to a single dash. This
 505         * function generates slugs safe for use as URLs, and if you pass true
 506         * as the second parameter, it will create strings safe for use as CSS
 507         * classes or IDs
 508         *
 509         * @param   string  $string    A string to convert to a slug
 510         * @param   bool    $css_mode  Whether or not to generate strings safe
 511         *                             for CSS classes/IDs (Default to false)
 512         * @return  string
 513         *
 514         * @access  public
 515         * @since   1.0.000
 516         * @static
 517         */
 518        public static function slugify( $string, $css_mode = false )
 519        {
 520            $slug = preg_replace( '/([^A-Za-z0-9\-]+)/', '-', strtolower( self::remove_accents( $string ) ) );
 521            $slug = preg_replace( '/(\-+)/', '-', $slug );
 522
 523            if ( $css_mode ) {
 524                $digits = array( 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine' );
 525
 526                if ( is_numeric( substr( $slug, 0, 1 ) ) ) {
 527                    $slug = $digits[substr( $slug, 0, 1 )] . substr( $slug, 1 );
 528                }
 529            }
 530
 531            return $slug;
 532        }
 533
 534        /**
 535         * Converts a string to UTF-8 without the need to specify the source
 536         * encoding
 537         *
 538         * @param   string  $string  A string that may or may not be UTF-8
 539         * @return  string
 540         *
 541         * @link    https://github.com/facebook/libphutil/blob/master/src/utils/utf8.php
 542         *
 543         * @access  public
 544         * @since   1.0.000
 545         * @static
 546         */
 547        public static function str_to_utf8( $string )
 548        {
 549            // Don't re-encode a UTF-8 string since that will mess it up
 550            if ( self::seems_utf8( $string ) ) {
 551                return $string;
 552            } else {
 553                // There is no function to do this in iconv, mbstring or ICU to
 554                // do this, so do it (very very slowly) in pure PHP.
 555
 556                $result = array();
 557
 558                $regex = "/([\x01-\x7F]" .
 559                    "|[\xC2-\xDF][\x80-\xBF]" .
 560                    "|[\xE0-\xEF][\x80-\xBF][\x80-\xBF]" .
 561                    "|[\xF0-\xF4][\x80-\xBF][\x80-\xBF][\x80-\xBF])" .
 562                    "|(.)/";
 563
 564                $offset  = 0;
 565                $matches = NULL;
 566
 567                while ( preg_match( $regex, $string, $matches, 0, $offset ) ) {
 568                    if ( ! isset( $matches[2] ) ) {
 569                        $result[] = $matches[1];
 570                    } else {
 571                        // Unicode replacement character, U+FFFD.
 572                        $result[] = "\xEF\xBF\xBD";
 573                    }
 574
 575                    $offset += strlen( $matches[0] );
 576                }
 577
 578                return implode( '', $result );
 579            }
 580        }
 581
 582        /**
 583         * Checks to see if a string is utf8 encoded.
 584         *
 585         * NOTE: This function checks for 5-Byte sequences, UTF8
 586         *       has Bytes Sequences with a maximum length of 4.
 587         *
 588         * @param   string  $string  The string to be checked
 589         * @return  bool
 590         *
 591         * @link    https://github.com/facebook/libphutil/blob/master/src/utils/utf8.php
 592         *
 593         * @access  public
 594         * @author  bmorel@ssi.fr
 595         * @since   1.0.000
 596         * @static
 597         */
 598        public static function seems_utf8( $string )
 599        {
 600            if ( function_exists( 'mb_check_encoding' ) ) {
 601                // If mbstring is available, this is significantly faster than
 602                // using PHP regexps.
 603                return mb_check_encoding( $string, 'UTF-8' );
 604            }
 605
 606            $regex = '/(
 607    | [\xF8-\xFF] # Invalid UTF-8 Bytes
 608    | [\xC0-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start
 609    | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start
 610    | [\xF0-\xF7](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start
 611    | (?<=[\x0-\x7F\xF8-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle
 612    | (?<![\xC0-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF7]|[\xF0-\xF7][\x80-\xBF]|[\xF0-\xF7][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence
 613    | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence
 614    | (?<=[\xF0-\xF7])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence
 615    | (?<=[\xF0-\xF7][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2)
 616)/x';
 617
 618            return ! preg_match( $regex, $string );
 619        }
 620
 621        /**
 622         * Nice formatting for computer sizes (Bytes)
 623         *
 624         * @param   int  $bytes     The number in bytes to format
 625         * @param   int  $decimals  The number of decimal points to include
 626         * @return  string
 627         *
 628         * @access  public
 629         * @since   1.0.000
 630         * @static
 631         */
 632        public static function size_format( $bytes, $decimals = 0 )
 633        {
 634            $bytes = floatval( $bytes );
 635
 636            if ( $bytes < 1024 ) {
 637                return $bytes . ' B';
 638            } else if ( $bytes < pow( 1024, 2 ) ) {
 639                return number_format( $bytes / 1024, $decimals, '.', '' ) . ' KiB';
 640            } else if ( $bytes < pow( 1024, 3 ) ) {
 641                return number_format( $bytes / pow( 1024, 2 ), $decimals, '.', '' ) . ' MiB';
 642            } else if ( $bytes < pow( 1024, 4 ) ) {
 643                return number_format( $bytes / pow( 1024, 3 ), $decimals, '.', '' ) . ' GiB';
 644            } else if ( $bytes < pow( 1024, 5 ) ) {
 645                return number_format( $bytes / pow( 1024, 4 ), $decimals, '.', '' ) . ' TiB';
 646            } else if ( $bytes < pow( 1024, 6 ) ) {
 647                return number_format( $bytes / pow( 1024, 5 ), $decimals, '.', '' ) . ' PiB';
 648            } else {
 649                return number_format( $bytes / pow( 1024, 5 ), $decimals, '.', '' ) . ' PiB';
 650            }
 651        }
 652        /**
 653         * Checks to see if the page is being server over SSL or not
 654         *
 655         * @return  bool
 656         *
 657         * @access  public
 658         * @since   1.0.000
 659         * @static
 660         */
 661        public static function is_https()
 662        {
 663            if ( isset( $_SERVER['HTTPS'] ) && ! empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] != 'off' ) {
 664                return true;
 665            } else {
 666                return false;
 667            }
 668        }
 669
 670        /**
 671         * Retrieve a modified URL query string.
 672         *
 673         * You can rebuild the URL and append a new query variable to the URL
 674         * query by using this function. You can also retrieve the full URL
 675         * with query data.
 676         *
 677         * Adding a single key & value or an associative array. Setting a key
 678         * value to an empty string removes the key. Omitting oldquery_or_uri
 679         * uses the $_SERVER value. Additional values provided are expected
 680         * to be encoded appropriately with urlencode() or rawurlencode().
 681         *
 682         * @internal param mixed $newkey Either newkey or an associative
 683         *                                  array
 684         * @internal param mixed $newvalue Either newvalue or oldquery or uri
 685         * @internal param mixed $oldquery_or_uri Optionally the old query or uri
 686         * @return  string
 687         *
 688         * @link    http://codex.wordpress.org/Function_Reference/add_query_arg
 689         *
 690         * @access  public
 691         * @since   1.0.000
 692         * @static
 693         */
 694        public static function add_query_arg()
 695        {
 696            $ret = '';
 697
 698            // Was an associative array of key => value pairs passed?
 699            if ( is_array( func_get_arg( 0 ) ) ) {
 700
 701                // Was the URL passed as an argument?
 702                if ( func_num_args() == 2 && func_get_arg( 1 ) ) {
 703                    $uri = func_get_arg( 1 );
 704                } else if ( func_num_args() == 3 && func_get_arg( 2 ) ) {
 705                    $uri = func_get_arg( 2 );
 706                } else {
 707                    $uri = $_SERVER['REQUEST_URI'];
 708                }
 709            } else {
 710
 711                // Was the URL passed as an argument?
 712                if ( func_num_args() == 3 && func_get_arg( 2 ) ) {
 713                    $uri = func_get_arg( 2 );
 714                } else {
 715                    $uri = $_SERVER['REQUEST_URI'];
 716                }
 717            }
 718
 719            // Does the URI contain a fragment section (The part after the #)
 720            if ( $frag = strstr( $uri, '#' ) ) {
 721                $uri = substr( $uri, 0, -strlen( $frag ) );
 722            } else {
 723                $frag = '';
 724            }
 725
 726            // Get the URI protocol if possible
 727            if ( preg_match( '|^https?://|i', $uri, $matches ) ) {
 728                $protocol = $matches[0];
 729                $uri = substr( $uri, strlen( $protocol ) );
 730            } else {
 731                $protocol = '';
 732            }
 733
 734            // Does the URI contain a query string?
 735            if ( strpos( $uri, '?' ) !== false ) {
 736                $parts = explode( '?', $uri, 2 );
 737
 738                if ( 1 == count( $parts ) ) {
 739                    $base  = '?';
 740                    $query = $parts[0];
 741                } else {
 742                    $base  = $parts[0] . '?';
 743                    $query = $parts[1];
 744                }
 745            } else if ( ! empty( $protocol ) || strpos( $uri, '=' ) === false ) {
 746                $base  = $uri . '?';
 747                $query = '';
 748            } else {
 749                $base  = '';
 750                $query = $uri;
 751            }
 752
 753            // Parse the query string into an array
 754            parse_str( $query, $qs );
 755
 756            // This re-URL-encodes things that were already in the query string
 757            $qs = self::array_map_deep( $qs, 'urlencode' );
 758
 759            if ( is_array( func_get_arg( 0 ) ) ) {
 760                $kayvees = func_get_arg( 0 );
 761                $qs = array_merge( $qs, $kayvees );
 762            } else {
 763                $qs[func_get_arg( 0 )] = func_get_arg( 1 );
 764            }
 765
 766            foreach ( (array) $qs as $k => $v ) {
 767                if ( $v === false )
 768                    unset( $qs[$k] );
 769            }
 770
 771            $ret = http_build_query( $qs );
 772            $ret = trim( $ret, '?' );
 773            $ret = preg_replace( '#=(&|$)#', '$1', $ret );
 774            $ret = $protocol . $base . $ret . $frag;
 775            $ret = rtrim( $ret, '?' );
 776            return $ret;
 777        }
 778
 779        /**
 780         * Removes an item or list from the query string.
 781         *
 782         * @param   string|array  $keys  Query key or keys to remove.
 783         * @param   bool          $uri   When false uses the $_SERVER value
 784         * @return  string
 785         *
 786         * @link    http://codex.wordpress.org/Function_Reference/remove_query_arg
 787         *
 788         * @access  public
 789         * @since   1.0.000
 790         * @static
 791         */
 792        public static function remove_query_arg( $keys, $uri = false )
 793        {
 794            if ( is_array( $keys ) ) {
 795                foreach ( $keys as $key ) {
 796                    $uri = self::add_query_arg( $key, false, $uri );
 797                }
 798
 799                return $uri;
 800            }
 801
 802            return self::add_query_arg( $keys, false, $uri );
 803        }
 804
 805        /**
 806         * Converts many english words that equate to true or false to boolean
 807         *
 808         * Supports 'y', 'n', 'yes', 'no' and a few other variations
 809         *
 810         * @param   string  $string   The string to convert to boolean
 811         * @param   bool    $default  The value to return if we can't match any
 812         *                            yes/no words
 813         * @return  bool
 814         *
 815         * @access  public
 816         * @since   1.0.000
 817         * @static
 818         */
 819        public static function str_to_bool( $string, $default = false )
 820        {
 821            $yes_words = 'affirmative|all right|aye|indubitably|most assuredly|ok|of course|okay|sure thing|y|yes+|yea|yep|sure|yeah|true|t';
 822            $no_words = 'no*|no way|nope|nah|na|never|absolutely not|by no means|negative|never ever|false|f';
 823
 824            if ( preg_match( '/^(' . $yes_words . ')$/i', $string ) ) {
 825                return true;
 826            } else if ( preg_match( '/^(' . $no_words . ')$/i', $string ) ) {
 827                return false;
 828            } else {
 829                return $default;
 830            }
 831        }
 832
 833        /**
 834         * Return the absolute integer value of a given variable
 835         *
 836         * @param   mixed  $maybeint  A variable that could be a string,
 837         *                            integer or other value
 838         * @return  int
 839         *
 840         * @access  public
 841         * @since   1.0.000
 842         * @static
 843         */
 844        public static function absint( $maybeint )
 845        {
 846            return abs( intval( $maybeint ) );
 847        }
 848
 849        /**
 850         * Convert entities, while preserving already-encoded entities
 851         *
 852         * @param   string $string The text to be converted
 853         * @param bool $preserve_encoded_entities
 854         * @return  string
 855         *
 856         * @link    http://ca2.php.net/manual/en/function.htmlentities.php#90111
 857         *
 858         * @access  public
 859         * @since   1.0.000
 860         * @static
 861         */
 862        public static function htmlentities( $string, $preserve_encoded_entities = false )
 863        {
 864            if ( $preserve_encoded_entities ) {
 865                $translation_table = get_html_translation_table( HTML_ENTITIES, ENT_QUOTES );
 866                $translation_table[chr(38)] = '&';
 867                return preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,3};)/', '&amp;', strtr( $string, $translation_table ) );
 868            } else {
 869                return htmlentities( $string, ENT_QUOTES );
 870            }
 871        }
 872
 873        /**
 874         * Convert >, <, ', " and & to html entities, but preserves entities
 875         * that are already encoded
 876         *
 877         * @param   string $string The text to be converted
 878         * @param bool $preserve_encoded_entities
 879         * @return  string
 880         *
 881         * @link    http://ca2.php.net/manual/en/function.htmlentities.php#90111
 882         *
 883         * @access  public
 884         * @since   1.0.000
 885         * @static
 886         */
 887        public static function htmlspecialchars( $string, $preserve_encoded_entities = false  )
 888        {
 889            if ( $preserve_encoded_entities ) {
 890                $translation_table            = get_html_translation_table( HTML_SPECIALCHARS, ENT_QUOTES );
 891                $translation_table[chr( 38 )] = '&';
 892
 893                return preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,3};)/', '&amp;', strtr( $string, $translation_table ) );
 894            } else {
 895                return htmlentities( $string, ENT_QUOTES );
 896            }
 897        }
 898
 899        /**
 900         * Converts all accent characters to ASCII characters
 901         *
 902         * If there are no accent characters, then the string given is just
 903         * returned
 904         *
 905         * @param   string  $string  Text that might have accent characters
 906         * @return  string  Filtered string with replaced "nice" characters
 907         *
 908         * @link    http://codex.wordpress.org/Function_Reference/remove_accents
 909         *
 910         * @access  public
 911         * @since   1.0.000
 912         * @static
 913         */
 914        public static function remove_accents( $string )
 915        {
 916            if ( ! preg_match( '/[\x80-\xff]/', $string ) ) {
 917                return $string;
 918            }
 919
 920            if ( self::seems_utf8( $string ) ) {
 921                $chars = array(
 922
 923                    // Decompositions for Latin-1 Supplement
 924                    chr(194).chr(170) => 'a', chr(194).chr(186) => 'o',
 925                    chr(195).chr(128) => 'A', chr(195).chr(129) => 'A',
 926                    chr(195).chr(130) => 'A', chr(195).chr(131) => 'A',
 927                    chr(195).chr(132) => 'A', chr(195).chr(133) => 'A',
 928                    chr(195).chr(134) => 'AE',chr(195).chr(135) => 'C',
 929                    chr(195).chr(136) => 'E', chr(195).chr(137) => 'E',
 930                    chr(195).chr(138) => 'E', chr(195).chr(139) => 'E',
 931                    chr(195).chr(140) => 'I', chr(195).chr(141) => 'I',
 932                    chr(195).chr(142) => 'I', chr(195).chr(143) => 'I',
 933                    chr(195).chr(144) => 'D', chr(195).chr(145) => 'N',
 934                    chr(195).chr(146) => 'O', chr(195).chr(147) => 'O',
 935                    chr(195).chr(148) => 'O', chr(195).chr(149) => 'O',
 936                    chr(195).chr(150) => 'O', chr(195).chr(153) => 'U',
 937                    chr(195).chr(154) => 'U', chr(195).chr(155) => 'U',
 938                    chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y',
 939                    chr(195).chr(158) => 'TH',chr(195).chr(159) => 's',
 940                    chr(195).chr(160) => 'a', chr(195).chr(161) => 'a',
 941                    chr(195).chr(162) => 'a', chr(195).chr(163) => 'a',
 942                    chr(195).chr(164) => 'a', chr(195).chr(165) => 'a',
 943                    chr(195).chr(166) => 'ae',chr(195).chr(167) => 'c',
 944                    chr(195).chr(168) => 'e', chr(195).chr(169) => 'e',
 945                    chr(195).chr(170) => 'e', chr(195).chr(171) => 'e',
 946                    chr(195).chr(172) => 'i', chr(195).chr(173) => 'i',
 947                    chr(195).chr(174) => 'i', chr(195).chr(175) => 'i',
 948                    chr(195).chr(176) => 'd', chr(195).chr(177) => 'n',
 949                    chr(195).chr(178) => 'o', chr(195).chr(179) => 'o',
 950                    chr(195).chr(180) => 'o', chr(195).chr(181) => 'o',
 951                    chr(195).chr(182) => 'o', chr(195).chr(184) => 'o',
 952                    chr(195).chr(185) => 'u', chr(195).chr(186) => 'u',
 953                    chr(195).chr(187) => 'u', chr(195).chr(188) => 'u',
 954                    chr(195).chr(189) => 'y', chr(195).chr(190) => 'th',
 955                    chr(195).chr(191) => 'y', chr(195).chr(152) => 'O',
 956
 957                    // Decompositions for Latin Extended-A
 958                    chr(196).chr(128) => 'A', chr(196).chr(129) => 'a',
 959                    chr(196).chr(130) => 'A', chr(196).chr(131) => 'a',
 960                    chr(196).chr(132) => 'A', chr(196).chr(133) => 'a',
 961                    chr(196).chr(134) => 'C', chr(196).chr(135) => 'c',
 962                    chr(196).chr(136) => 'C', chr(196).chr(137) => 'c',
 963                    chr(196).chr(138) => 'C', chr(196).chr(139) => 'c',
 964                    chr(196).chr(140) => 'C', chr(196).chr(141) => 'c',
 965                    chr(196).chr(142) => 'D', chr(196).chr(143) => 'd',
 966                    chr(196).chr(144) => 'D', chr(196).chr(145) => 'd',
 967                    chr(196).chr(146) => 'E', chr(196).chr(147) => 'e',
 968                    chr(196).chr(148) => 'E', chr(196).chr(149) => 'e',
 969                    chr(196).chr(150) => 'E', chr(196).chr(151) => 'e',
 970                    chr(196).chr(152) => 'E', chr(196).chr(153) => 'e',
 971                    chr(196).chr(154) => 'E', chr(196).chr(155) => 'e',
 972                    chr(196).chr(156) => 'G', chr(196).chr(157) => 'g',
 973                    chr(196).chr(158) => 'G', chr(196).chr(159) => 'g',
 974                    chr(196).chr(160) => 'G', chr(196).chr(161) => 'g',
 975                    chr(196).chr(162) => 'G', chr(196).chr(163) => 'g',
 976                    chr(196).chr(164) => 'H', chr(196).chr(165) => 'h',
 977                    chr(196).chr(166) => 'H', chr(196).chr(167) => 'h',
 978                    chr(196).chr(168) => 'I', chr(196).chr(169) => 'i',
 979                    chr(196).chr(170) => 'I', chr(196).chr(171) => 'i',
 980                    chr(196).chr(172) => 'I', chr(196).chr(173) => 'i',
 981                    chr(196).chr(174) => 'I', chr(196).chr(175) => 'i',
 982                    chr(196).chr(176) => 'I', chr(196).chr(177) => 'i',
 983                    chr(196).chr(178) => 'IJ',chr(196).chr(179) => 'ij',
 984                    chr(196).chr(180) => 'J', chr(196).chr(181) => 'j',
 985                    chr(196).chr(182) => 'K', chr(196).chr(183) => 'k',
 986                    chr(196).chr(184) => 'k', chr(196).chr(185) => 'L',
 987                    chr(196).chr(186) => 'l', chr(196).chr(187) => 'L',
 988                    chr(196).chr(188) => 'l', chr(196).chr(189) => 'L',
 989                    chr(196).chr(190) => 'l', chr(196).chr(191) => 'L',
 990                    chr(197).chr(128) => 'l', chr(197).chr(129) => 'L',
 991                    chr(197).chr(130) => 'l', chr(197).chr(131) => 'N',
 992                    chr(197).chr(132) => 'n', chr(197).chr(133) => 'N',
 993                    chr(197).chr(134) => 'n', chr(197).chr(135) => 'N',
 994                    chr(197).chr(136) => 'n', chr(197).chr(137) => 'N',
 995                    chr(197).chr(138) => 'n', chr(197).chr(139) => 'N',
 996                    chr(197).chr(140) => 'O', chr(197).chr(141) => 'o',
 997                    chr(197).chr(142) => 'O', chr(197).chr(143) => 'o',
 998                    chr(197).chr(144) => 'O', chr(197).chr(145) => 'o',
 999                    chr(197).chr(146) => 'OE',chr(197).chr(147) => 'oe',
1000                    chr(197).chr(148) => 'R',chr(197).chr(149) => 'r',
1001                    chr(197).chr(150) => 'R',chr(197).chr(151) => 'r',
1002                    chr(197).chr(152) => 'R',chr(197).chr(153) => 'r',
1003                    chr(197).chr(154) => 'S',chr(197).chr(155) => 's',
1004                    chr(197).chr(156) => 'S',chr(197).chr(157) => 's',
1005                    chr(197).chr(158) => 'S',chr(197).chr(159) => 's',
1006                    chr(197).chr(160) => 'S', chr(197).chr(161) => 's',
1007                    chr(197).chr(162) => 'T', chr(197).chr(163) => 't',
1008                    chr(197).chr(164) => 'T', chr(197).chr(165) => 't',
1009                    chr(197).chr(166) => 'T', chr(197).chr(167) => 't',
1010                    chr(197).chr(168) => 'U', chr(197).chr(169) => 'u',
1011                    chr(197).chr(170) => 'U', chr(197).chr(171) => 'u',
1012                    chr(197).chr(172) => 'U', chr(197).chr(173) => 'u',
1013                    chr(197).chr(174) => 'U', chr(197).chr(175) => 'u',
1014                    chr(197).chr(176) => 'U', chr(197).chr(177) => 'u',
1015                    chr(197).chr(178) => 'U', chr(197).chr(179) => 'u',
1016                    chr(197).chr(180) => 'W', chr(197).chr(181) => 'w',
1017                    chr(197).chr(182) => 'Y', chr(197).chr(183) => 'y',
1018                    chr(197).chr(184) => 'Y', chr(197).chr(185) => 'Z',
1019                    chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z',
1020                    chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z',
1021                    chr(197).chr(190) => 'z', chr(197).chr(191) => 's',
1022
1023                    // Decompositions for Latin Extended-B
1024                    chr(200).chr(152) => 'S', chr(200).chr(153) => 's',
1025                    chr(200).chr(154) => 'T', chr(200).chr(155) => 't',
1026
1027                    // Euro Sign
1028                    chr(226).chr(130).chr(172) => 'E',
1029                    // GBP (Pound) Sign
1030                    chr(194).chr(163) => ''
1031                );
1032
1033                $string = strtr( $string, $chars );
1034            } else {
1035
1036                // Assume ISO-8859-1 if not UTF-8
1037                $chars['in'] = chr(128).chr(131).chr(138).chr(142).chr(154).chr(158)
1038                     .chr(159).chr(162).chr(165).chr(181).chr(192).chr(193).chr(194)
1039                     .chr(195).chr(196).chr(197).chr(199).chr(200).chr(201).chr(202)
1040                     .chr(203).chr(204).chr(205).chr(206).chr(207).chr(209).chr(210)
1041                     .chr(211).chr(212).chr(213).chr(214).chr(216).chr(217).chr(218)
1042                     .chr(219).chr(220).chr(221).chr(224).chr(225).chr(226).chr(227)
1043                     .chr(228).chr(229).chr(231).chr(232).chr(233).chr(234).chr(235)
1044                     .chr(236).chr(237).chr(238).chr(239).chr(241).chr(242).chr(243)
1045                     .chr(244).chr(245).chr(246).chr(248).chr(249).chr(250).chr(251)
1046                     .chr(252).chr(253).chr(255);
1047
1048                $chars['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy';
1049
1050                $string = strtr( $string, $chars['in'], $chars['out'] );
1051                $double_chars['in'] = array( chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254) );
1052                $double_chars['out'] = array( 'OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th' );
1053                $string = str_replace( $double_chars['in'], $double_chars['out'], $string );
1054            }
1055
1056            return $string;
1057        }
1058
1059        /**
1060         * Pads a given string with zeroes on the left
1061         *
1062         * @param   int  $number  The number to pad
1063         * @param   int  $length  The total length of the desired string
1064         * @return  string
1065         *
1066         * @access  public
1067         * @since   1.0.000
1068         * @static
1069         */
1070        public static function zero_pad( $number, $length )
1071        {
1072            return str_pad( $number, $length, '0', STR_PAD_LEFT );
1073        }
1074
1075        /**
1076         * Converts a unix timestamp to a relative time string, such as "3 days
1077         * ago" or "2 weeks ago"
1078         *
1079         * @param   int $from The date to use as a starting point
1080         * @param int|string $to The date to compare to. Defaults to the
1081         *                           current time
1082         * @param bool $as_text
1083         * @param   string $suffix The string to add to the end, defaults to
1084         *                           " ago"
1085         * @return  string
1086         *
1087         * @access  public
1088         * @since   1.0.000
1089         * @static
1090         */
1091        public static function human_time_diff( $from, $to = '', $as_text = false, $suffix = ' ago' )
1092        {
1093            if ( $to == '' ) {
1094                $to = time();
1095            }
1096
1097            $from = new DateTime( date( 'Y-m-d H:i:s', $from ) );
1098            $to   = new DateTime( date( 'Y-m-d H:i:s', $to ) );
1099            $diff = $from->diff( $to );
1100
1101            if ( $diff->y > 1 ) {
1102                $text = $diff->y . ' years';
1103            } else if ( $diff->y == 1 ) {
1104                $text = '1 year';
1105            } else if ( $diff->m > 1 ) {
1106                $text = $diff->m . ' months';
1107            } else if ( $diff->m == 1 ) {
1108                $text = '1 month';
1109            } else if ( $diff->d > 7 ) {
1110                $text = ceil( $diff->d / 7 ) . ' weeks';
1111            } else if ( $diff->d == 7 ) {
1112                $text = '1 week';
1113            } else if ( $diff->d > 1 ) {
1114                $text = $diff->d . ' days';
1115            } else if ( $diff->d == 1 ) {
1116                $text = '1 day';
1117            } else if ( $diff->h > 1 ) {
1118                $text = $diff->h . ' hours';
1119            } else if ( $diff->h == 1 ) {
1120                $text = ' 1 hour';
1121            } else if ( $diff->i > 1 ) {
1122                $text = $diff->i . ' minutes';
1123            } else if ( $diff->i == 1 ) {
1124                $text = '1 minute';
1125            } else if ( $diff->s > 1 ) {
1126                $text = $diff->s . ' seconds';
1127            } else {
1128                $text = '1 second';
1129            }
1130
1131            if ( $as_text ) {
1132                $text = explode( ' ', $text, 2 );
1133                $text = self::number_to_word( $text[0] ) . ' ' . $text[1];
1134            }
1135
1136            return trim( $text ) . $suffix;
1137        }
1138
1139        /**
1140         * Converts a number into the text equivalent. For example, 456 becomes
1141         * four hundred and fifty-six
1142         *
1143         * @param   int|float  $number  The number to convert into text
1144         * @return  string
1145         *
1146         * @link    http://bloople.net/num2text
1147         *
1148         * @access  public
1149         * @author  Brenton Fletcher
1150         * @since   1.0.000
1151         * @static
1152         */
1153        public static function number_to_word( $number )
1154        {
1155            $number = (string) $number;
1156
1157            if ( strpos( $number, '.' ) !== false ) {
1158                list( $number, $decimal ) = explode( '.', $number );
1159            } else {
1160                $decimal = false;
1161            }
1162
1163            $output = '';
1164
1165            if ( $number[0] == '-' ) {
1166                $output = 'negative ';
1167                $number = ltrim( $number, '-' );
1168            } else if ( $number[0] == '+' ) {
1169                $output = 'positive ';
1170                $number = ltrim( $number, '+' );
1171            }
1172
1173            if ( $number[0] == '0' ) {
1174                $output .= 'zero';
1175            } else {
1176                $number = str_pad( $number, 36, '0', STR_PAD_LEFT );
1177                $group  = rtrim( chunk_split( $number, 3, ' ' ), ' ' );
1178                $groups = explode( ' ', $group );
1179
1180                $groups2 = array();
1181
1182                foreach ( $groups as $group ) {
1183                    $groups2[] = self::_number_to_word_three_digits( $group[0], $group[1], $group[2] );
1184                }
1185
1186                for ( $z = 0; $z < count( $groups2 ); $z++ ) {
1187                    if ( $groups2[$z] != '' ) {
1188                        $output .= $groups2[$z] . self::_number_to_word_convert_group( 11 - $z );
1189                        $output .= ( $z < 11 && ! array_search( '', array_slice( $groups2, $z + 1, -1 ) ) && $groups2[11] != '' && $groups[11][0] == '0' ? ' and ' : ', ' );
1190                    }
1191                }
1192
1193                $output = rtrim( $output, ', ' );
1194            }
1195
1196            if ( $decimal > 0 ) {
1197                $output .= ' point';
1198
1199                for ( $i = 0; $i < strlen( $decimal ); $i++ ) {
1200                    $output .= ' ' . self::_number_to_word_convert_digit( $decimal[$i] );
1201                }
1202            }
1203
1204            return $output;
1205        }
1206
1207        protected static function _number_to_word_convert_group( $index )
1208        {
1209            switch( $index ) {
1210                case 11:
1211                    return ' decillion';
1212                case 10:
1213                    return ' nonillion';
1214                case 9:
1215                    return ' octillion';
1216                case 8:
1217                    return ' septillion';
1218                case 7:
1219                    return ' sextillion';
1220                case 6:
1221                    return ' quintrillion';
1222                case 5:
1223                    return ' quadrillion';
1224                case 4:
1225                    return ' trillion';
1226                case 3:
1227                    return ' billion';
1228                case 2:
1229                    return ' million';
1230                case 1:
1231                    return ' thousand';
1232                case 0:
1233                    return '';
1234            }
1235        }
1236
1237        protected static function _number_to_word_three_digits( $digit1, $digit2, $digit3 )
1238        {
1239            $output = '';
1240
1241            if ( $digit1 == '0' && $digit2 == '0' && $digit3 == '0') {
1242                return '';
1243            }
1244
1245            if ( $digit1 != '0' ) {
1246                $output .= self::_number_to_word_convert_digit( $digit1 ) . ' hundred';
1247
1248                if ( $digit2 != '0' || $digit3 != '0' ) {
1249                    $output .= ' and ';
1250                }
1251            }
1252
1253            if ( $digit2 != '0') {
1254                $output .= self::_number_to_word_two_digits( $digit2, $digit3 );
1255            } else if( $digit3 != '0' ) {
1256                $output .= self::_number_to_word_convert_digit( $digit3 );
1257            }
1258
1259            return $output;
1260        }
1261
1262        protected static function _number_to_word_two_digits( $digit1, $digit2 )
1263        {
1264            if ( $digit2 == '0' ) {
1265                switch ( $digit2 ) {
1266                    case '1':
1267                        return 'ten';
1268                    case '2':
1269                        return 'twenty';
1270                    case '3':
1271                        return 'thirty';
1272                    case '4':
1273                        return 'forty';
1274                    case '5':
1275                        return 'fifty';
1276                    case '6':
1277                        return 'sixty';
1278                    case '7':
1279                        return 'seventy';
1280                    case '8':
1281                        return 'eighty';
1282                    case '9':
1283                        return 'ninety';
1284                }
1285            } else if ( $digit1 == '1' ) {
1286                switch ( $digit2 ) {
1287                    case '1':
1288                        return 'eleven';
1289                    case '2':
1290                        return 'twelve';
1291                    case '3':
1292                        return 'thirteen';
1293                    case '4':
1294                        return 'fourteen';
1295                    case '5':
1296                        return 'fifteen';
1297                    case '6':
1298                        return 'sixteen';
1299                    case '7':
1300                        return 'seventeen';
1301                    case '8':
1302                        return 'eighteen';
1303                    case '9':
1304                        return 'nineteen';
1305                }
1306            } else {
1307                $second_digit = self::_number_to_word_convert_digit( $digit2 );
1308
1309                switch ( $digit1 ) {
1310                    case '2':
1311                        return "twenty-{$second_digit}";
1312                    case '3':
1313                        return "thirty-{$second_digit}";
1314                    case '4':
1315                        return "forty-{$second_digit}";
1316                    case '5':
1317                        return "fifty-{$second_digit}";
1318                    case '6':
1319                        return "sixty-{$second_digit}";
1320                    case '7':
1321                        return "seventy-{$second_digit}";
1322                    case '8':
1323                        return "eighty-{$second_digit}";
1324                    case '9':
1325                        return "ninety-{$second_digit}";
1326                }
1327            }
1328        }
1329
1330        protected static function _number_to_word_convert_digit( $digit )
1331        {
1332            switch ( $digit ) {
1333                case '0':
1334                    return 'zero';
1335                case '1':
1336                    return 'one';
1337                case '2':
1338                    return 'two';
1339                case '3':
1340                    return 'three';
1341                case '4':
1342                    return 'four';
1343                case '5':
1344                    return 'five';
1345                case '6':
1346                    return 'six';
1347                case '7':
1348                    return 'seven';
1349                case '8':
1350                    return 'eight';
1351                case '9':
1352                    return 'nine';
1353            }
1354        }
1355
1356        /**
1357         * Transmit UTF-8 content headers if the headers haven't already been
1358         * sent
1359         *
1360         * @param   string  $content_type  The content type to send out,
1361         *                                 defaults to text/html
1362         * @return  bool
1363         *
1364         * @access  public
1365         * @since   1.0.000
1366         * @static
1367         */
1368        public static function utf8_headers( $content_type = 'text/html' )
1369        {
1370            if ( ! headers_sent() ) {
1371                header( 'Content-type: ' . $content_type . '; charset=utf-8' );
1372
1373                return true;
1374            } else {
1375                return false;
1376            }
1377        }
1378
1379        /**
1380         * Transmit headers that force a browser to display the download file
1381         * dialog. Cross browser compatible. Only fires if headers have not
1382         * already been sent.
1383         *
1384         * @param   string $filename The name of the filename to display to
1385         *                             browsers
1386         * @param bool|string $content The content to output for the download.
1387         *                             If you don't specify this, just the
1388         *                             headers will be sent
1389         * @return  bool
1390         *
1391         * @link    http://www.php.net/manual/en/function.header.php#102175
1392         *
1393         * @access  public
1394         * @since   1.0.000
1395         * @static
1396         */
1397        public static function force_download( $filename, $content = false )
1398        {
1399            if ( ! headers_sent() ) {
1400                // Required for some browsers
1401                if ( ini_get( 'zlib.output_compression' ) ) {
1402                    @ini_set( 'zlib.output_compression', 'Off' );
1403                }
1404
1405                header( 'Pragma: public' );
1406                header( 'Expires: 0' );
1407                header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' );
1408
1409                // Required for certain browsers
1410                header( 'Cache-Control: private', false );
1411
1412                header( 'Content-Disposition: attachment; filename="' . basename( str_replace( '"', '', $filename ) ) . '";' );
1413                header( 'Content-Type: application/force-download' );
1414                header( 'Content-Transfer-Encoding: binary' );
1415
1416                if ( $content ) {
1417                   header( 'Content-Length: ' . strlen( $content ) );
1418                }
1419
1420                ob_clean();
1421                flush();
1422
1423                if ( $content ) {
1424                    echo $content;
1425                }
1426
1427                return true;
1428            } else {
1429                return false;
1430            }
1431        }
1432
1433        /**
1434         * Sets the headers to prevent caching for the different browsers
1435         *
1436         * Different browsers support different nocache headers, so several
1437         * headers must be sent so that all of them get the point that no
1438         * caching should occur
1439         *
1440         * @return  bool
1441         *
1442         * @access  public
1443         * @since   1.0.000
1444         * @static
1445         */
1446        public static function nocache_headers()
1447        {
1448            if ( ! headers_sent() ) {
1449                header( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' );
1450                header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
1451                header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
1452                header( 'Pragma: no-cache' );
1453
1454                return true;
1455            } else {
1456                return false;
1457            }
1458        }
1459
1460        /**
1461         * Generates a string of random characters
1462         *
1463         * @param   int   $length              The length of the string to
1464         *                                     generate
1465         * @param   bool  $human_friendly      Whether or not to make the
1466         *                                     string human friendly by
1467         *                                     removing characters that can be
1468         *                                     confused with other characters (
1469         *                                     O and 0, l and 1, etc)
1470         * @param   bool  $include_symbols     Whether or not to include
1471         *                                     symbols in the string. Can not
1472         *                                     be enabled if $human_friendly is
1473         *                                     true
1474         * @param   bool  $no_duplicate_chars  Whether or not to only use
1475         *                                     characters once in the string.
1476         * @return  string
1477         *
1478         * @throws  LengthException  If $length is bigger than the available
1479         *                           character pool and $no_duplicate_chars is
1480         *                           enabled
1481         *
1482         * @access  public
1483         * @since   1.0.000
1484         * @static
1485         */
1486        public static function random_string( $length, $human_friendly = true, $include_symbols = false, $no_duplicate_chars = false )
1487        {
1488            $nice_chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefhjkmnprstuvwxyz23456789';
1489            $all_an     = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890';
1490            $symbols    = '!@#$%^&*()~_-=+{}[]|:;<>,.?/"\'\\`';
1491            $string     = '';
1492
1493            // Determine the pool of available characters based on the given parameters
1494            if ( $human_friendly ) {
1495                $pool = $nice_chars;
1496            } else {
1497                $pool = $all_an;
1498
1499                if ( $include_symbols ) {
1500                    $pool .= $symbols;
1501                }
1502            }
1503
1504            // Don't allow duplicate letters to be disabled if the length is
1505            // longer than the available characters
1506            if ( $no_duplicate_chars && strlen( $pool ) < $length ) {
1507                throw new LengthException( '$length exceeds the size of the pool and $no_duplicate_chars is enabled' );
1508            }
1509
1510            // Convert the pool of characters into an array of characters and
1511            // shuffle the array
1512            $pool = str_split( $pool );
1513            shuffle( $pool );
1514
1515            // Generate our string
1516            for ( $i = 0; $i < $length; $i++ ) {
1517                if ( $no_duplicate_chars ) {
1518                    $string .= array_shift( $pool );
1519                } else {
1520                    $string .= $pool[0];
1521                    shuffle( $pool );
1522                }
1523            }
1524
1525            return $string;
1526        }
1527
1528        /**
1529         * Validate an email address
1530         *
1531         * @param   string  $possible_email  An email address to validate
1532         * @return  bool
1533         *
1534         * @access  public
1535         * @since   1.0.000
1536         * @static
1537         */
1538        public static function validate_email( $possible_email )
1539        {
1540            return filter_var( $possible_email, FILTER_VALIDATE_EMAIL );
1541        }
1542
1543        /**
1544         * Return the URL to a user's gravatar
1545         *
1546         * @param   string  $email  The email of the user
1547         * @param   int     $size   The size of the gravatar
1548         * @return  string
1549         *
1550         * @access  public
1551         * @since   1.0.000
1552         * @static
1553         */
1554        public static function get_gravatar( $email, $size = 32 )
1555        {
1556            return 'http://www.gravatar.com/avatar/' . md5( $email ) . '?s=' . self::absint( $size );
1557        }
1558
1559        /**
1560         * Turns all of the links in a string into HTML links
1561         *
1562         * @param   string  $text  The string to parse
1563         * @return  string
1564         *
1565         * @link    https://github.com/jmrware/LinkifyURL
1566         *
1567         * @access  public
1568         * @since   1.0.000
1569         * @static
1570         */
1571        public static function linkify( $text )
1572        {
1573            $text = preg_replace( '/&apos;/', '&#39;', $text ); // IE does not handle &apos; entity!
1574            $section_html_pattern = '%# Rev:20100913_0900 github.com/jmrware/LinkifyURL
1575                # Section text into HTML <A> tags  and everything else.
1576                  (                              # $1: Everything not HTML <A> tag.
1577                    [^<]+(?:(?!<a\b)<[^<]*)*     # non A tag stuff starting with non-"<".
1578                  |      (?:(?!<a\b)<[^<]*)+     # non A tag stuff starting with "<".
1579                  )                              # End $1.
1580                | (                              # $2: HTML <A...>...</A> tag.
1581                    <a\b[^>]*>                   # <A...> opening tag.
1582                    [^<]*(?:(?!</a\b)<[^<]*)*    # A tag contents.
1583                    </a\s*>                      # </A> closing tag.
1584                  )                              # End $2:
1585                %ix';
1586
1587            return preg_replace_callback( $section_html_pattern, array( __CLASS__, '_linkify_callback' ), $text );
1588        }
1589
1590        /**
1591         * Callback for the preg_replace in the linkify() method
1592         *
1593         * @param $text
1594         * @internal param array $matches Matches from the preg_ function
1595         * @return  string
1596         *
1597         * @link    https://github.com/jmrware/LinkifyURL
1598         *
1599         * @access  public
1600         * @since   1.0.000
1601         * @static
1602         */
1603        public static function _linkify( $text )
1604        {
1605            $url_pattern = '/# Rev:20100913_0900 github.com\/jmrware\/LinkifyURL
1606                # Match http & ftp URL that is not already linkified.
1607                # Alternative 1: URL delimited by (parentheses).
1608                (\() # $1 "(" start delimiter.
1609                ((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&\'()*+,;=:\/?#[\]@%]+) # $2: URL.
1610                (\)) # $3: ")" end delimiter.
1611                | # Alternative 2: URL delimited by [square brackets].
1612                (\[) # $4: "[" start delimiter.
1613                ((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&\'()*+,;=:\/?#[\]@%]+) # $5: URL.
1614                (\]) # $6: "]" end delimiter.
1615                | # Alternative 3: URL delimited by {curly braces}.
1616                (\{) # $7: "{" start delimiter.
1617                ((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&\'()*+,;=:\/?#[\]@%]+) # $8: URL.
1618                (\}) # $9: "}" end delimiter.
1619                | # Alternative 4: URL delimited by <angle brackets>.
1620                (<|&(?:lt|\#60|\#x3c);) # $10: "<" start delimiter (or HTML entity).
1621                ((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&\'()*+,;=:\/?#[\]@%]+) # $11: URL.
1622                (>|&(?:gt|\#62|\#x3e);) # $12: ">" end delimiter (or HTML entity).
1623                | # Alternative 5: URL not delimited by (), [], {} or <>.
1624                ( # $13: Prefix proving URL not already linked.
1625                (?: ^ # Can be a beginning of line or string, or
1626                | [^=\s\'"\]] # a non-"=", non-quote, non-"]", followed by
1627                ) \s*[\'"]? # optional whitespace and optional quote;
1628                | [^=\s]\s+ # or... a non-equals sign followed by whitespace.
1629                ) # End $13. Non-prelinkified-proof prefix.
1630                ( \b # $14: Other non-delimited URL.
1631                (?:ht|f)tps?:\/\/ # Required literal http, https, ftp or ftps prefix.
1632                [a-z0-9\-._~!$\'()*+,;=:\/?#[\]@%]+ # All URI chars except "&" (normal*).
1633                (?: # Either on a "&" or at the end of URI.
1634                (?! # Allow a "&" char only if not start of an...
1635                &(?:gt|\#0*62|\#x0*3e); # HTML ">" entity, or
1636                | &(?:amp|apos|quot|\#0*3[49]|\#x0*2[27]); # a [&\'"] entity if
1637                [.!&\',:?;]? # followed by optional punctuation then
1638                (?:[^a-z0-9\-._~!$&\'()*+,;=:\/?#[\]@%]|$) # a non-URI char or EOS.
1639                ) & # If neg-assertion true, match "&" (special).
1640                [a-z0-9\-._~!$\'()*+,;=:\/?#[\]@%]* # More non-& URI chars (normal*).
1641                )* # Unroll-the-loop (special normal*)*.
1642                [a-z0-9\-_~$()*+=\/#[\]@%] # Last char can\'t be [.!&\',;:?]
1643                ) # End $14. Other non-delimited URL.
1644                /imx';
1645
1646            $url_replace = '$1$4$7$10$13<a href="$2$5$8$11$14">$2$5$8$11$14</a>$3$6$9$12';
1647            return preg_replace( $url_pattern, $url_replace, $text );
1648        }
1649
1650        /**
1651         * Callback for the preg_replace in the linkify() method
1652         *
1653         * @param   array  $matches  Matches from the preg_ function
1654         * @return  string
1655         *
1656         * @link    https://github.com/jmrware/LinkifyURL
1657         *
1658         * @access  public
1659         * @since    1.0.000
1660         * @static
1661         */
1662        public static function _linkify_callback( $matches )
1663        {
1664            if ( isset( $matches[2] ) ) {
1665                return $matches[2];
1666            }
1667
1668            return self::_linkify( $matches[1] );
1669        }
1670
1671        /**
1672         * Return the current URL
1673         *
1674         * @return  string
1675         *
1676         * @access  public
1677         * @since   1.0.000
1678         * @static
1679         */
1680        public static function get_current_url()
1681        {
1682            $url = '';
1683
1684            // Check to see if it's over https
1685            if ( self::is_https() ) {
1686                $url .= 'https://';
1687            } else {
1688                $url .= 'http://';
1689            }
1690
1691            // Was a username or password passed?
1692            if ( isset( $_SERVER['PHP_AUTH_USER'] ) ) {
1693                $url .= $_SERVER['PHP_AUTH_USER'];
1694
1695                if ( isset( $_SERVER['PHP_AUTH_PW'] ) ) {
1696                    $url .= ':' . $_SERVER['PHP_AUTH_PW'];
1697                }
1698
1699                $url .= '@';
1700            }
1701
1702
1703            // We want the user to stay on the same host they are currently on,
1704            // but beware of security issues
1705            // see http://shiflett.org/blog/2006/mar/server-name-versus-http-host
1706            $url .= $_SERVER['HTTP_HOST'];
1707
1708            // Is it on a non standard port?
1709            if ( $_SERVER['SERVER_PORT'] != 80 ) {
1710                $url .= ':' . $_SERVER['SERVER_PORT'];
1711            }
1712
1713            // Get the rest of the URL
1714            if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
1715
1716                // Microsoft IIS doesn't set REQUEST_URI by default
1717                $url .= substr( $_SERVER['PHP_SELF'], 1 );
1718
1719                if ( isset( $_SERVER['QUERY_STRING'] ) ) {
1720                    $url .= '?' . $_SERVER['QUERY_STRING'];
1721                }
1722            } else {
1723                $url .= $_SERVER['REQUEST_URI'];
1724            }
1725
1726            return $url;
1727        }
1728
1729        /**
1730         * Returns the IP address of the client
1731         *
1732         * @param   bool  $trust_proxy_headers  Whether or not to trust the
1733         *                                      proxy headers HTTP_CLIENT_IP
1734         *                                      and HTTP_X_FORWARDED_FOR. ONLY
1735         *                                      use if your server is behind a
1736         *                                      proxy that sets these values
1737         * @return  string
1738         *
1739         * @access  public
1740         * @since   1.0.000
1741         * @static
1742         */
1743        public static function get_client_ip( $trust_proxy_headers = false )
1744        {
1745            if ( ! $trust_proxy_headers ) {
1746                return $_SERVER['REMOTE_ADDR'];
1747            }
1748
1749            if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
1750                $ip = $_SERVER['HTTP_CLIENT_IP'];
1751            } else if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
1752                $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
1753            } else {
1754                $ip = $_SERVER['REMOTE_ADDR'];
1755            }
1756
1757            return $ip;
1758        }
1759
1760        /**
1761         * Truncate a string to a specified length without cutting a word off
1762         *
1763         * @param   string  $string  The string to truncate
1764         * @param   int     $length  The length to truncate the string to
1765         * @param   string  $append  Text to append to the string IF it gets
1766         *                           truncated, defaults to '...'
1767         * @return  string
1768         *
1769         * @access  public
1770         * @since   1.0.000
1771         * @static
1772         */
1773        public static function safe_truncate( $string, $length, $append = '...' )
1774        {
1775            $ret        = substr( $string, 0, $length );
1776            $last_space = strrpos( $ret, ' ' );
1777
1778            if ( $last_space !== false && $string != $ret ) {
1779                $ret     = substr( $ret, 0, $last_space );
1780            }
1781
1782            if ( $ret != $string ) {
1783                $ret .= $append;
1784            }
1785
1786            return $ret;
1787        }
1788
1789        /**
1790         * Returns the ordinal version of a number (appends th, st, nd, rd)
1791         *
1792         * @param   string  $number  The number to append an ordinal suffix to
1793         * @return  string
1794         *
1795         * @link    http://phpsnips.com/snippet.php?id=37
1796         *
1797         * @access  public
1798         * @since   1.0.000
1799         * @static
1800         */
1801        public static function ordinal( $number )
1802        {
1803            $test_c = abs ($number ) % 10;
1804
1805            $ext = ( ( abs( $number ) % 100 < 21 && abs( $number ) % 100 > 4 ) ? 'th' : ( ( $test_c < 4 ) ? ( $test_c < 3 ) ? ( $test_c < 2 ) ? ( $test_c < 1 ) ? 'th' : 'st' : 'nd' : 'rd' : 'th' ) );
1806
1807            return $number . $ext;
1808        }
1809
1810        /**
1811         * Returns the file permissions as a nice string, like -rw-r--r--
1812         *
1813         * @param   string  $file  The name of the file to get permissions form
1814         * @return  string
1815         *
1816         * @access  public
1817         * @since   1.0.000
1818         * @static
1819         */
1820        public static function full_permissions( $file )
1821        {
1822            $perms = fileperms( $file );
1823
1824            if ( ( $perms & 0xC000 ) == 0xC000 ) {
1825                // Socket
1826                $info = 's';
1827            } else if ( ( $perms & 0xA000 ) == 0xA000 ) {
1828                // Symbolic Link
1829                $info = 'l';
1830            } else if ( ( $perms & 0x8000 ) == 0x8000 ) {
1831                // Regular
1832                $info = '-';
1833            } else if ( ( $perms & 0x6000 ) == 0x6000 ) {
1834                // Block special
1835                $info = 'b';
1836            } else if ( ( $perms & 0x4000 ) == 0x4000 ) {
1837                // Directory
1838                $info = 'd';
1839            } else if ( ( $perms & 0x2000 ) == 0x2000 ) {
1840                // Character special
1841                $info = 'c';
1842            } else if ( ( $perms & 0x1000 ) == 0x1000 ) {
1843                // FIFO pipe
1844                $info = 'p';
1845            } else {
1846                // Unknown
1847                $info = 'u';
1848            }
1849
1850            // Owner
1851            $info .= ( ( $perms & 0x0100 ) ? 'r' : '-' );
1852            $info .= ( ( $perms & 0x0080 ) ? 'w' : '-' );
1853            $info .= ( ( $perms & 0x0040 ) ?
1854                        ( ( $perms & 0x0800 ) ? 's' : 'x' ) :
1855                        ( ( $perms & 0x0800 ) ? 'S' : '-' ) );
1856
1857            // Group
1858            $info .= ( ( $perms & 0x0020 ) ? 'r' : '-' );
1859            $info .= ( ( $perms & 0x0010 ) ? 'w' : '-' );
1860            $info .= ( ( $perms & 0x0008 ) ?
1861                        ( ( $perms & 0x0400 ) ? 's' : 'x' ) :
1862                        ( ( $perms & 0x0400 ) ? 'S' : '-' ) );
1863
1864            // World
1865            $info .= ( ( $perms & 0x0004 ) ? 'r' : '-' );
1866            $info .= ( ( $perms & 0x0002 ) ? 'w' : '-' );
1867            $info .= ( ( $perms & 0x0001 ) ?
1868                        ( ( $perms & 0x0200 ) ? 't' : 'x' ) :
1869                        ( ( $perms & 0x0200 ) ? 'T' : '-' ) );
1870
1871            return $info;
1872        }
1873
1874        /**
1875         * Returns the first element in an array
1876         *
1877         * @param   array  $array  The array
1878         * @return  mixed
1879         *
1880         * @access  public
1881         * @since   1.0.000
1882         * @static
1883         */
1884        public static function array_first( array $array )
1885        {
1886            return reset( $array );
1887        }
1888
1889        /**
1890         * Returns the last element in an array
1891         *
1892         * @param   array  $array  The array
1893         * @return  mixed
1894         *
1895         * @access  public
1896         * @since   1.0.000
1897         * @static
1898         */
1899        public static function array_last( array $array )
1900        {
1901            return end( $array );
1902        }
1903
1904        /**
1905         * Returns the first key in an array
1906         *
1907         * @param   array  $array  The array
1908         * @return  int|string
1909         *
1910         * @access  public
1911         * @since   1.0.000
1912         * @static
1913         */
1914        public static function array_first_key( array $array )
1915        {
1916            reset( $array );
1917
1918            return key( $array );
1919        }
1920
1921        /**
1922         * Returns the last key in an array
1923         *
1924         * @param   array  $array  The array
1925         * @return  int|string
1926         *
1927         * @access  public
1928         * @since   1.0.000
1929         * @static
1930         */
1931        public static function array_last_key( array $array )
1932        {
1933            end( $array );
1934
1935            return key( $array );
1936        }
1937
1938        /**
1939         * Flattens a potentially multi-dimensional array into a one
1940         * dimensional array
1941         *
1942         * @param   array  $array         The array to flatten
1943         * @param   bool   preserve_keys  Whether or not to preserve array
1944         *                                keys. Keys from deeply nested arrays
1945         *                                will overwrite keys from shallowy
1946         *                                nested arrays
1947         * @return  array
1948         *
1949         * @access  public
1950         * @since   1.0.000
1951         * @static
1952         */
1953        public static function array_flatten( array $array, $preserve_keys = true )
1954        {
1955            $flattened = array();
1956
1957            foreach ( $array as $key => $value ) {
1958                if ( is_array( $value ) ) {
1959                    $flattened = array_merge( $flattened, self::array_flatten( $value, $preserve_keys ) );
1960                } else {
1961                    if ( $preserve_keys ) {
1962                        $flattened[$key] = $value;
1963                    } else {
1964                        $flattened[] = $value;
1965                    }
1966                }
1967            }
1968
1969            return $flattened;
1970        }
1971
1972        /**
1973         * Accepts an array, and returns an array of values from that array as
1974         * specified by $field. For example, if the array is full of objects
1975         * and you call util::array_pluck( $array, 'name' ), the function will
1976         * return an array of values from $array[]->name
1977         *
1978         * @param   array   $array             An array
1979         * @param   string  $field             The field to get values from
1980         * @param   bool    $preserve_keys     Whether or not to preserve the
1981         *                                     array keys
1982         * @param   bool    $remove_nomatches  If the field doesn't appear to
1983         *                                     be set, remove it from the array
1984         * @return  array
1985         *
1986         * @link    http://codex.wordpress.org/Function_Reference/wp_list_pluck
1987         *
1988         * @access  public
1989         * @since   1.0.000
1990         * @static
1991         */
1992        public static function array_pluck( array $array, $field, $preserve_keys = true, $remove_nomatches = true )
1993        {
1994            $new_list = array();
1995
1996            foreach ( $array as $key => $value ) {
1997                if ( is_object( $value ) ) {
1998                    if ( isset( $value->{$field} ) ) {
1999                        if ( $preserve_keys ) {
2000                            $new_list[$key] = $value->{$field};
2001                        } else {
2002                            $new_list[] = $value->{$field};
2003                        }
2004                    } else if ( ! $remove_nomatches ) {
2005                        $new_list[$key] = $value;
2006                    }
2007                } else {
2008                    if ( isset( $value[$field] ) ) {
2009                        if ( $preserve_keys ) {
2010                            $new_list[$key] = $value[$field];
2011                        } else {
2012                            $new_list[] = $value[$field];
2013                        }
2014                    } else if ( ! $remove_nomatches ) {
2015                        $new_list[$key] = $value;
2016                    }
2017                }
2018            }
2019
2020            return $new_list;
2021        }
2022
2023        /**
2024         * Searches for a given value in an array of arrays, objects and scalar
2025         * values. You can optionally specify a field of the nested arrays and
2026         * objects to search in
2027         *
2028         * @param   array $array The array to search
2029         * @param   scalar $search The value to search for
2030         * @param bool|string $field The field to search in, if not specified
2031         *                           all fields will be searched
2032         * @return  bool|scalar      False on failure or the array key on
2033         *                           success
2034         *
2035         * @access  public
2036         * @since   1.0.000
2037         * @static
2038         */
2039        public static function array_search_deep( array $array, $search, $field = false )
2040        {
2041            // *grumbles* stupid PHP type system
2042            $search = (string) $search;
2043
2044            foreach ( $array as $key => $elem ) {
2045
2046                // *grumbles* stupid PHP type system
2047                $key = (string) $key;
2048
2049                if ( $field ) {
2050                    if ( is_object( $elem ) && $elem->{$field} === $search ) {
2051                        return $key;
2052                    } else if ( is_array( $elem ) && $elem[$field] === $search ) {
2053                        return $key;
2054                    } else if ( is_scalar( $elem ) && $elem === $search ) {
2055                        return $key;
2056                    }
2057                } else {
2058                    if ( is_object( $elem ) ) {
2059                        $elem = (array) $elem;
2060
2061                        if ( in_array( $search, $elem ) ) {
2062                            return $key;
2063                        }
2064                    } else if ( is_array( $elem ) && in_array( $search, $elem ) ) {
2065                        return array_search( $search, $elem );
2066                    } else if ( is_scalar( $elem ) && $elem === $search ) {
2067                        return $key;
2068                    }
2069                }
2070            }
2071
2072            return false;
2073        }
2074
2075        /**
2076         * Returns an array containing all the elements of arr1 after applying
2077         * the callback function to each one
2078         *
2079         * @param   string  $callback      Callback function to run for each
2080         *                                 element in each array
2081         * @param   array   $array         An array to run through the callback
2082         *                                 function
2083         * @param   bool    $on_nonscalar  Whether or not to call the callback
2084         *                                 function on nonscalar values
2085         *                                 (Objects, resources, etc)
2086         * @return  array
2087         *
2088         * @access  public
2089         * @since   1.0.000
2090         * @static
2091         */
2092        public static function array_map_deep( array $array, $callback, $on_nonscalar = false )
2093        {
2094            foreach ( $array as $key => $value ) {
2095                if ( is_array( $value ) ) {
2096                    $args = array( $value, $callback, $on_nonscalar );
2097                    $array[$key] = call_user_func_array( array( __CLASS__, __FUNCTION__ ), $args );
2098                } else if ( is_scalar( $value ) || $on_nonscalar ) {
2099                    $array[$key] = call_user_func( $callback, $value );
2100                }
2101            }
2102
2103            return $array;
2104        }
2105    }
2106}