PageRenderTime 64ms CodeModel.GetById 2ms app.highlight 51ms RepoModel.GetById 1ms app.codeStats 0ms

/class.csv-to-api.php

https://github.com/jmmnn/csv-to-api
PHP | 1342 lines | 1266 code | 44 blank | 32 comment | 14 complexity | e84f3468928acfa012575f70d72805c7 MD5 | raw file
   1<?php
   2
   3class CSV_To_API {
   4
   5  public $ttl = 3600;
   6  public $source = null;
   7  public $source_format = null;
   8  public $format = null;
   9  public $callback = null;
  10  public $sort = null;
  11  public $sort_dir = null;
  12  public $data;
  13
  14  /**
  15   * Use the query (often the requested URL) to define some settings.
  16   */
  17  function parse_query( $query = null ) {
  18
  19    // If a query has been passed to the function, turn it into an array.
  20    if ( is_string( $query ) ) {
  21      $query = $this->parse_args( $query );
  22    }
  23
  24    // If a query has not been passed to this function, just use the array of variables that
  25    // were passed in the URL.
  26    if (is_null($query)) {
  27      $query = $_GET;
  28    }
  29
  30    // Define a series of configuration variables based on what was requested in the query.
  31    $this->source = isset( $query['source'] ) ? $this->esc_url( $query['source'] ) : null;
  32    $this->source_format = isset( $query['source_format'] ) ? $query['source_format'] : $this->get_extension( $this->source );
  33    $this->format = isset( $query['format'] ) ? $query['format'] : 'json';
  34    $this->callback = isset( $query['callback'] ) ? $this->jsonp_callback_filter( $query['callback'] ) : false;
  35    $this->sort = isset( $query['sort'] ) ? $query['sort'] : null;
  36    $this->sort_dir = isset( $query['sort_dir'] ) ? $query['sort_dir'] : "desc";
  37
  38    return get_object_vars( $this );
  39
  40  }
  41
  42
  43  /**
  44   * Fetch the requested file and turn it into a PHP array.
  45   */
  46  function parse() {
  47
  48    // Create an instance of the parser for the requested file format (e.g. CSV)
  49    $parser = 'parse_' . $this->source_format;
  50
  51    if ( !method_exists( $this, $parser ) ) {
  52      header( '400 Bad Request' );
  53      die( 'Format not supported' );
  54    }
  55
  56    // Attempt to retrieve the data from cache
  57    $key = 'csv_to_api_' . md5( $this->source );
  58    $this->data = $this->get_cache( $key );
  59    if ( !$this->data ) {
  60
  61      // Retrieve the requested source material via HTTP GET.
  62      if (ini_get('allow_url_fopen') == true) {
  63        $this->data = file_get_contents( $this->source );
  64      }
  65      else {
  66        $this->data = $this->curl_get( $this->source );
  67      }
  68
  69
  70      if ( !$this->data ) {
  71        header( '502 Bad Gateway' );
  72        die( 'Bad data source' );
  73      }
  74
  75      // Turn the raw file data (e.g. CSV) into a PHP array.
  76      $this->data = $this->$parser( $this->data );
  77
  78      // Save the data to WordPress' cache via its Transients API.
  79      $this->set_cache( $key, $this->data, $this->ttl );
  80
  81    }
  82    
  83    $this->data = $this->query( $this->data );
  84    
  85    return $this->data;
  86
  87  }
  88
  89
  90  /**
  91   * Return to the client the requested data in the requested format (e.g. JSON).
  92   */
  93  function output() {
  94
  95    $function = 'object_to_' . $this->format;
  96
  97    if ( !method_exists( $this, $function) ) {
  98      return false;
  99    }
 100
 101    // Send to the browser a header specifying the proper MIME type for the requested format.
 102    $this->header( $this->format );
 103    $output = $this->$function( $this->data );
 104
 105    // Prepare a JSONP callback.
 106    $callback = $this->jsonp_callback_filter( $this->callback );
 107
 108    // Only send back JSONP if that's appropriate for the request.
 109    if ( $this->format == 'json' && $this->callback ) {
 110      return "{$this->callback}($output);";
 111    }
 112
 113    // If not JSONP, send back the data.
 114    return $output;
 115
 116  }
 117
 118
 119  /**
 120   * Create a key name, based on a CSV column header name, that is safe to embed in JavaScript
 121   * or XML.
 122   */
 123  function sanitize_key( $key ) {
 124
 125    $key = $this->sanitize_title( $key );
 126    $key = str_replace( '-', '_', $key );
 127    return $key;
 128
 129  }
 130
 131
 132  /**
 133   * Determine the file type of the requested file based on its extension.
 134   */
 135  function get_extension( $source = null ) {
 136
 137    if ( $source == null ) {
 138      $source = $this->source;
 139    }
 140
 141    $url_parts = parse_url( $source );
 142    $url_parts = pathinfo( $url_parts['path'] );
 143    
 144    return isset( $url_parts['extension'] ) ? $url_parts['extension'] : '';
 145
 146  }
 147
 148
 149  /**
 150   * Convert reserved XML characters into their entitity equivalents.
 151   */
 152  function xml_entities( $string ) {
 153
 154    return str_replace( array("&", "<", ">", "\"", "'"), array("&amp;", "&lt;", "&gt;", "&quot;", "&apos;"), $string );
 155
 156  }
 157
 158  /** 
 159   * Normalize all line endings to Unix line endings
 160   * @param string the mixed-ending string to normalized
 161   * @return string the normalized string
 162   */
 163  function normalize_line_endings( $string ) {
 164    $string = str_replace( "\r\n", "\n", $string );
 165    $string = str_replace( "\r", "\n", $string );
 166    return $string;    
 167  }
 168  
 169  /**
 170   * Turn CSV into a PHP array.
 171   */
 172  function parse_csv( $csv ) {
 173
 174    $csv = $this->normalize_line_endings( $csv );
 175
 176    $lines = explode( "\n", $csv );
 177    $lines = $this->parse_lines( $lines );
 178
 179    $headers = array_shift( $lines );
 180    $data = array();
 181    foreach ( $lines as $line ) {
 182
 183      $row = array();
 184
 185      foreach ( $line as $key => $field ) {
 186        $row[ $this->sanitize_key( $headers[ $key ] ) ] = $field;
 187      }
 188
 189      $row = array_filter( $row );
 190      $row = $this->array_to_object( $row );
 191      $data[] = $row;
 192
 193    }
 194
 195    return $data;
 196
 197  }
 198
 199  /**
 200   * Parse CSV into array of arrays
 201   * Wrapper function to allow pre 5.3 compatability
 202   * @param array the CSV data as an array of lines
 203   * @return array array of preset objects
 204   */
 205  function parse_lines( $lines ) {
 206
 207    //php 5.3+
 208    if ( function_exists( 'str_getcsv' ) ) {
 209
 210      foreach ( $lines as &$line )
 211        $line = str_getcsv( $line );
 212
 213      //php 5.2
 214      // fgetcsv needs a file handle,
 215      // so write the string to a temp file before parsing
 216    } else {
 217
 218      $fh = tmpfile();
 219      fwrite( $fh, implode( "\n", $lines ) );
 220      fseek( $fh, 0 );
 221      $lines = array();
 222
 223      while( $line = fgetcsv( $fh ) )
 224        $lines[] = $line;
 225
 226      fclose( $fh );
 227
 228    }
 229
 230    return $lines;
 231
 232  }
 233
 234  /**
 235   * Turn a PHP array into a PHP object.
 236   */
 237  function array_to_object( $array ) {
 238
 239    $output = new stdClass();
 240    foreach ( $array as $key => $value ) {
 241      $output->$key = $value;
 242    }
 243
 244    return $output;
 245
 246  }
 247
 248
 249  /**
 250   * Turn a PHP object into JSON text.
 251   */
 252  function object_to_json( $data ) {
 253
 254    return json_encode( $data );
 255
 256  }
 257
 258
 259  /**
 260   * Turn a PHP object into XML text.
 261   */
 262  function object_to_xml( $array, $xml = null, $tidy = true ) {
 263
 264    if ( $xml == null ) {
 265      $xml = new SimpleXMLElement( '<records></records>' );
 266    }
 267
 268    // Array of keys that will be treated as attributes, not children.
 269    $attributes = array( 'id' );
 270
 271    // Recursively loop through each item.
 272    foreach ( $array as $key => $value ) {
 273
 274      // If this is a numbered array, grab the parent node to determine the node name.
 275      if ( is_numeric( $key ) ) {
 276        $key = 'record';
 277      }
 278
 279      // If this is an attribute, treat as an attribute.
 280      if ( in_array( $key, $attributes ) ) {
 281        $xml->addAttribute( $key, $value );
 282      }
 283
 284      // If this value is an object or array, add a child node and treat recursively.
 285      elseif ( is_object( $value ) || is_array( $value ) ) {
 286        $child = $xml->addChild( $key );
 287        $child = $this->object_to_xml( $value, $child, false );
 288      }
 289
 290      // Simple key/value child pair.
 291      else {
 292        $value = $this->xml_entities( $value );
 293        $xml->addChild( $key, $value );
 294      }
 295
 296    }
 297
 298    if ( $tidy ) {
 299      $xml = $this->tidy_xml( $xml );
 300    }
 301
 302    return $xml;
 303
 304  }
 305
 306
 307  /**
 308   * Turn a PHP object into an HTML table.
 309   */
 310  function object_to_html( $data ) {
 311
 312    $output = "<table>\n<thead>\n";
 313    $output .= "<tr>";
 314
 315    foreach ( array_keys( get_object_vars( reset( $data ) ) ) as $header ) {
 316      $output .= "\t<th>$header</th>";
 317    }
 318
 319    $output .= "</tr>\n</thead>\n<tbody>";
 320
 321    foreach ( $data as $row ) {
 322
 323      $output .= "<tr>\n";
 324
 325      foreach ( $row as $key => $value ) {
 326        $output .= "\t<td>$value</td>\n";
 327      }
 328
 329      $output .= "</tr>\n";
 330
 331    }
 332
 333    $output .= "</tbody>\n</table>";
 334
 335    return $output;
 336
 337  }
 338
 339
 340  /**
 341   * Pass XML through PHP's DOMDocument class, which will tidy it up.
 342   */
 343  function tidy_xml( $xml ) {
 344
 345    $dom = new DOMDocument();
 346    $dom->preserveWhiteSpace = false;
 347    $dom->formatOutput = true;
 348    $dom->loadXML( $xml->asXML() );
 349    return $dom->saveXML();
 350
 351  }
 352
 353
 354  /**
 355   * Send to the browser the MIME type that defines this content (JSON, XML, or HTML).
 356   */
 357  function header( $extension = null ) {
 358
 359    if ( $extension == null ) {
 360      $extension = $this->extension;
 361    }
 362
 363    $mimes = $this->get_mimes();
 364
 365    if ( !isset( $mimes[ $extension ] ) || headers_sent() ) {
 366      return;
 367    }
 368
 369    header( 'Content-Type: ' . $mimes[ $extension ] );
 370
 371  }
 372
 373
 374  /**
 375   * Return MIME types
 376   * This way we do not allow additional MIME types elsewhere.
 377   */
 378  function get_mimes() {
 379
 380    return array(
 381      'json' => 'application/json',
 382      'xml' => 'text/xml',
 383      'htm|html' => 'text/html',
 384    );
 385
 386
 387  }
 388
 389
 390  /**
 391   * Prevent malicious callbacks from being used in JSONP requests.
 392   */
 393  function jsonp_callback_filter( $callback ) {
 394
 395    // As per <http://stackoverflow.com/a/10900911/1082542>.
 396    if ( preg_match( '/[^0-9a-zA-Z\$_]|^(abstract|boolean|break|byte|case|catch|char|class|const|continue|debugger|default|delete|do|double|else|enum|export|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|long|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|var|volatile|void|while|with|NaN|Infinity|undefined)$/', $callback) ) {
 397      return false;
 398    }
 399
 400    return $callback;
 401
 402  }
 403
 404
 405  /**
 406   * Parse the query parameters passed in the URL and use them to populate a complete list of
 407   * settings.
 408   */
 409  function query( $data, $query = null ) {
 410
 411    if ( $query == null ) {
 412      $query = $_GET;
 413    }
 414
 415    // Fill in any defaults that are missing.
 416    $query = $this->default_vars( $this->get_query_vars( $data ), $query );
 417
 418    // Eliminate any value in $query that equal false.
 419    $query = array_filter( $query );
 420
 421    $data = $this->list_filter( $data, $query );
 422
 423    if ( $this->sort == null ) {
 424      return $data;
 425    }
 426
 427    // Optionally sort the object.
 428    if ( $this->sort != null )
 429      usort( $data, array( &$this, 'object_sort' ) );
 430
 431    return $data;
 432
 433  }
 434
 435
 436  /**
 437   * Uses a PHP array to generate a list of all column names.
 438   */
 439  function get_query_vars( $data ) {
 440
 441    $vars = array();
 442    foreach ( $data as $row ) {
 443
 444      foreach ( $row as $key=>$value ) {
 445
 446        if ( !array_key_exists( $key, $vars ) ) {
 447          $vars[ $key ] = null;
 448        }
 449
 450      }
 451
 452    }
 453
 454    return $vars;
 455
 456  }
 457
 458
 459  /**
 460   * Sorts an object, in either ascending or descending order.
 461   * @param object $a the first object
 462   * @param object $b the second object
 463   * @return int 0, 1, or -1 the ranking of $a to $b
 464   * 
 465   * The comparison function must return an integer less than, equal to, or greater than zero 
 466   * if the first argument is considered to be respectively less than, equal to, or greater than the second.
 467   * see http://php.net/manual/en/function.usort.php for more information
 468   */
 469  function object_sort( $a, $b ) {
 470
 471    $sorter = $this->sort;
 472
 473    //no sort by field supplied or invalid sort field, tell usort not to do anything
 474    if ( $sorter == null || !isset( $a->$sorter ) || !isset( $b->$sorter ) ) {
 475      return 0;
 476    }
 477    
 478    // A = B, tell usort not to do anything
 479    if ( $a->$sorter == $b->$sorter )
 480      return 0;
 481    
 482    //flip the return values depending on the sort direction
 483    if ( $this->sort_dir == "desc" ) {
 484      $up = -1;
 485      $down = 1;
 486    } else {
 487      $up = 1;
 488      $down = -1;
 489    }
 490    
 491    if ( $a->$sorter < $b->$sorter )
 492      return $down;
 493      
 494    if ( $a->$sorter > $b->$sorter )
 495      return $up;
 496        
 497  }
 498
 499
 500  /**
 501   * Retrieve data from Alternative PHP Cache (APC).
 502   */
 503  function get_cache( $key ) {
 504
 505    if ( !extension_loaded('apc') || (ini_get('apc.enabled') != 1) ) {
 506      if ( isset( $this->cache[ $key ] ) ) {
 507        return $this->cache[ $key ];
 508      }
 509    }
 510    else {
 511      return apc_fetch( $key );
 512    }
 513
 514    return false;
 515
 516  }
 517
 518
 519  /**
 520   * Store data in Alternative PHP Cache (APC).
 521   */
 522  function set_cache( $key, $value, $ttl = null ) {
 523
 524    if ( $ttl == null )
 525      $ttl = $this->ttl;
 526
 527    if ( extension_loaded('apc') && (ini_get('apc.enabled') == 1) ) {
 528      return apc_store( $key, $value, $ttl );
 529    }
 530
 531    $this->cache[$key] = $value;
 532
 533  }
 534
 535  function curl_get( $url ) {
 536    if ( !isset($url) ) {
 537      return false;
 538    }
 539    $ch = curl_init();
 540    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1);
 541    curl_setopt($ch, CURLOPT_TIMEOUT_MS, 1200);
 542    curl_setopt($ch, CURLOPT_URL, $url);
 543    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 544    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
 545    $result = curl_exec($ch);
 546    curl_close($ch);
 547    return $result;
 548  }
 549
 550
 551  // All functions below this point taken from WordPress
 552
 553  /**
 554   * Merge user defined arguments into defaults array.
 555   *
 556   * This function is used throughout WordPress to allow for both string or array
 557   * to be merged into another array.
 558   *
 559   * @since 2.2.0
 560   *
 561   * @param string|array $args Value to merge with $defaults
 562   * @param array $defaults Array that serves as the defaults.
 563   * @return array Merged user defined values with defaults.
 564   *
 565   * Source: WordPress, used under GPLv3 or Later
 566   */
 567  function parse_args( $args, $defaults = '' ) {
 568    if ( is_object( $args ) )
 569      $r = get_object_vars( $args );
 570    elseif ( is_array( $args ) )
 571      $r =& $args;
 572    else
 573      $this->parse_str( $args, $r );
 574
 575    if ( is_array( $defaults ) )
 576      return array_merge( $defaults, $r );
 577    return $r;
 578  }
 579
 580
 581  /**
 582   * Parses a string into variables to be stored in an array.
 583   *
 584   * Uses {@link http://www.php.net/parse_str parse_str()} and stripslashes if
 585   * {@link http://www.php.net/magic_quotes magic_quotes_gpc} is on.
 586   *
 587   * @since 2.2.1
 588   * @uses apply_filters() for the 'wp_parse_str' filter.
 589   *
 590   * @param string $string The string to be parsed.
 591   * @param array $array Variables will be stored in this array.
 592   *
 593   * Source: WordPress, used under GPLv3 or Later
 594   */
 595  function parse_str( $string, &$array ) {
 596    parse_str( $string, $array );
 597    if ( get_magic_quotes_gpc() )
 598      $array = stripslashes_deep( $array );
 599  }
 600
 601
 602  /**
 603   * Checks and cleans a URL.
 604   *
 605   * A number of characters are removed from the URL. If the URL is for displaying
 606   * (the default behaviour) ampersands are also replaced. The 'clean_url' filter
 607   * is applied to the returned cleaned URL.
 608   *
 609   * @since 2.8.0
 610   * @uses wp_kses_bad_protocol() To only permit protocols in the URL set
 611   *  via $protocols or the common ones set in the function.
 612   *
 613   * @param string $url The URL to be cleaned.
 614   * @param array $protocols Optional. An array of acceptable protocols.
 615   *  Defaults to 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'svn' if not set.
 616   * @param string $_context Private. Use esc_url_raw() for database usage.
 617   * @return string The cleaned $url after the 'clean_url' filter is applied.
 618   *
 619   * Source: WordPress, used under GPLv3 or Later
 620   *
 621   */
 622  function esc_url( $url, $protocols = null, $_context = 'display' ) {
 623    $original_url = $url;
 624
 625    if ( '' == $url )
 626      return $url;
 627    $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\\x80-\\xff]|i', '', $url);
 628    $strip = array('%0d', '%0a', '%0D', '%0A');
 629    $url = $this->_deep_replace($strip, $url);
 630    $url = str_replace(';//', '://', $url);
 631    /* If the URL doesn't appear to contain a scheme, we
 632  	 * presume it needs http:// appended (unless a relative
 633  	 * link starting with /, # or ? or a php file).
 634  	 */
 635    if ( strpos($url, ':') === false && ! in_array( $url[0], array( '/', '#', '?' ) ) &&
 636      ! preg_match('/^[a-z0-9-]+?\.php/i', $url) )
 637      $url = 'http://' . $url;
 638
 639    // Replace ampersands and single quotes only when displaying.
 640    if ( 'display' == $_context ) {
 641      $url = $this->kses_normalize_entities( $url );
 642      $url = str_replace( '&amp;', '&#038;', $url );
 643      $url = str_replace( "'", '&#039;', $url );
 644    }
 645
 646    if ( ! is_array( $protocols ) )
 647      $protocols = $this->allowed_protocols();
 648    if ( $this->kses_bad_protocol( $url, $protocols ) != $url )
 649      return '';
 650
 651    return $url;
 652
 653  }
 654
 655
 656  /**
 657   * Perform a deep string replace operation to ensure the values in $search are no longer present
 658   *
 659   * Repeats the replacement operation until it no longer replaces anything so as to remove "nested" values
 660   * e.g. $subject = '%0%0%0DDD', $search ='%0D', $result ='' rather than the '%0%0DD' that
 661   * str_replace would return
 662   *
 663   * @since 2.8.1
 664   * @access private
 665   *
 666   * @param string|array $search
 667   * @param string $subject
 668   * @return string The processed string
 669   *
 670   * Source: WordPress, used under GPLv3 or Later
 671   *
 672   */
 673  function _deep_replace( $search, $subject ) {
 674    $found = true;
 675    $subject = (string) $subject;
 676    while ( $found ) {
 677      $found = false;
 678      foreach ( (array) $search as $val ) {
 679        while ( strpos( $subject, $val ) !== false ) {
 680          $found = true;
 681          $subject = str_replace( $val, '', $subject );
 682        }
 683      }
 684    }
 685
 686    return $subject;
 687  }
 688
 689
 690  /**
 691   * Converts and fixes HTML entities.
 692   *
 693   * This function normalizes HTML entities. It will convert "AT&T" to the correct
 694   * "AT&amp;T", "&#00058;" to "&#58;", "&#XYZZY;" to "&amp;#XYZZY;" and so on.
 695   *
 696   * @since 1.0.0
 697   *
 698   * @param string $string Content to normalize entities
 699   * @return string Content with normalized entities
 700   *
 701   * Source: WordPress, used under GPLv3 or Later
 702   *
 703   */
 704  function kses_normalize_entities($string) {
 705    # Disarm all entities by converting & to &amp;
 706
 707    $string = str_replace('&', '&amp;', $string);
 708
 709    # Change back the allowed entities in our entity whitelist
 710
 711    $string = preg_replace_callback('/&amp;([A-Za-z]{2,8});/', array( $this, 'kses_named_entities' ), $string);
 712    $string = preg_replace_callback('/&amp;#(0*[0-9]{1,7});/', array( $this, 'kses_normalize_entities2' ), $string);
 713    $string = preg_replace_callback('/&amp;#[Xx](0*[0-9A-Fa-f]{1,6});/', array( $this, 'kses_normalize_entities3' ), $string);
 714
 715    return $string;
 716  }
 717
 718
 719  /**
 720   * Retrieve a list of protocols to allow in HTML attributes.
 721   *
 722   * @since 3.3.0
 723   * @see wp_kses()
 724   * @see esc_url()
 725   *
 726   * @return array Array of allowed protocols
 727   *
 728   * Source: WordPress, used under GPLv3 or Later
 729   *
 730   */
 731  function allowed_protocols() {
 732    static $protocols;
 733
 734    if ( empty( $protocols ) ) {
 735      $protocols = array( 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'svn', 'tel', 'fax', 'xmpp' );
 736    }
 737
 738    return $protocols;
 739  }
 740
 741
 742  /**
 743   * Sanitize string from bad protocols.
 744   *
 745   * This function removes all non-allowed protocols from the beginning of
 746   * $string. It ignores whitespace and the case of the letters, and it does
 747   * understand HTML entities. It does its work in a while loop, so it won't be
 748   * fooled by a string like "javascript:javascript:alert(57)".
 749   *
 750   * @since 1.0.0
 751   *
 752   * @param string $string Content to filter bad protocols from
 753   * @param array $allowed_protocols Allowed protocols to keep
 754   * @return string Filtered content
 755   *
 756   * Source: WordPress, used under GPLv3 or Later
 757   *
 758   */
 759  function kses_bad_protocol($string, $allowed_protocols) {
 760    $string = $this->kses_no_null($string);
 761    $iterations = 0;
 762
 763    do {
 764      $original_string = $string;
 765      $string = $this->kses_bad_protocol_once($string, $allowed_protocols);
 766    } while ( $original_string != $string && ++$iterations < 6 );
 767
 768    if ( $original_string != $string )
 769      return '';
 770
 771    return $string;
 772  }
 773
 774
 775  /**
 776   * Removes any null characters in $string.
 777   *
 778   * @since 1.0.0
 779   *
 780   * @param string $string
 781   * @return string
 782   *
 783   * Source: WordPress, used under GPLv3 or Later
 784   *
 785   */
 786  function kses_no_null($string) {
 787    $string = preg_replace('/\0+/', '', $string);
 788    $string = preg_replace('/(\\\\0)+/', '', $string);
 789
 790    return $string;
 791  }
 792
 793
 794  /**
 795   * Sanitizes content from bad protocols and other characters.
 796   *
 797   * This function searches for URL protocols at the beginning of $string, while
 798   * handling whitespace and HTML entities.
 799   *
 800   * @since 1.0.0
 801   *
 802   * @param string $string Content to check for bad protocols
 803   * @param string $allowed_protocols Allowed protocols
 804   * @return string Sanitized content
 805   *
 806   * Source: WordPress, used under GPLv3 or Later
 807   *
 808   */
 809  function kses_bad_protocol_once($string, $allowed_protocols, $count = 1 ) {
 810    $string2 = preg_split( '/:|&#0*58;|&#x0*3a;/i', $string, 2 );
 811    if ( isset($string2[1]) && ! preg_match('%/\?%', $string2[0]) ) {
 812      $string = trim( $string2[1] );
 813      $protocol = $this->kses_bad_protocol_once2( $string2[0], $allowed_protocols );
 814      if ( 'feed:' == $protocol ) {
 815        if ( $count > 2 )
 816          return '';
 817        $string = $this->kses_bad_protocol_once( $string, $allowed_protocols, ++$count );
 818        if ( empty( $string ) )
 819          return $string;
 820      }
 821      $string = $protocol . $string;
 822    }
 823
 824    return $string;
 825  }
 826
 827
 828  /**
 829   * Callback for wp_kses_bad_protocol_once() regular expression.
 830   *
 831   * This function processes URL protocols, checks to see if they're in the
 832   * whitelist or not, and returns different data depending on the answer.
 833   *
 834   * @access private
 835   * @since 1.0.0
 836   *
 837   * @param string $string URI scheme to check against the whitelist
 838   * @param string $allowed_protocols Allowed protocols
 839   * @return string Sanitized content
 840   *
 841   * Source: WordPress, used under GPLv3 or Later
 842   *
 843   */
 844  function kses_bad_protocol_once2( $string, $allowed_protocols ) {
 845    $string2 = $this->kses_decode_entities($string);
 846    $string2 = preg_replace('/\s/', '', $string2);
 847    $string2 = $this->kses_no_null($string2);
 848    $string2 = strtolower($string2);
 849
 850    $allowed = false;
 851    foreach ( (array) $allowed_protocols as $one_protocol )
 852      if ( strtolower($one_protocol) == $string2 ) {
 853        $allowed = true;
 854        break;
 855      }
 856
 857    if ($allowed)
 858      return "$string2:";
 859    else
 860      return '';
 861  }
 862
 863
 864  /**
 865   * Convert all entities to their character counterparts.
 866   *
 867   * This function decodes numeric HTML entities (&#65; and &#x41;). It doesn't do
 868   * anything with other entities like &auml;, but we don't need them in the URL
 869   * protocol whitelisting system anyway.
 870   *
 871   * @since 1.0.0
 872   *
 873   * @param string $string Content to change entities
 874   * @return string Content after decoded entities
 875   *
 876   * Source: WordPress, used under GPLv3 or Later
 877   *
 878   */
 879  function kses_decode_entities($string) {
 880    $string = preg_replace_callback('/&#([0-9]+);/', array( $this, '_kses_decode_entities_chr' ), $string);
 881    $string = preg_replace_callback('/&#[Xx]([0-9A-Fa-f]+);/', array( $this, '_kses_decode_entities_chr_hexdec' ), $string);
 882
 883    return $string;
 884  }
 885
 886
 887  /**
 888   * Regex callback for wp_kses_decode_entities()
 889   *
 890   * @param array $match preg match
 891   * @return string
 892   *
 893   * Source: WordPress, used under GPLv3 or Later
 894   *
 895   */
 896  function _kses_decode_entities_chr( $match ) {
 897    return chr( $match[1] );
 898  }
 899
 900
 901  /**
 902   * Regex callback for wp_kses_decode_entities()
 903   *
 904   * @param array $match preg match
 905   * @return string
 906   *
 907   * Source: WordPress, used under GPLv3 or Later
 908   *
 909   */
 910  function _kses_decode_entities_chr_hexdec( $match ) {
 911    return chr( hexdec( $match[1] ) );
 912  }
 913
 914
 915  /**
 916   * Sanitizes title or use fallback title.
 917   *
 918   * Specifically, HTML and PHP tags are stripped. Further actions can be added
 919   * via the plugin API. If $title is empty and $fallback_title is set, the latter
 920   * will be used.
 921   *
 922   * @since 1.0.0
 923   *
 924   * @param string $title The string to be sanitized.
 925   * @param string $fallback_title Optional. A title to use if $title is empty.
 926   * @param string $context Optional. The operation for which the string is sanitized
 927   * @return string The sanitized string.
 928   *
 929   * Source: WordPress, used under GPLv3 or Later
 930   *
 931   */
 932  function sanitize_title($title, $fallback_title = '', $context = 'save') {
 933    $raw_title = $title;
 934
 935    if ( 'save' == $context )
 936      $title = $this->remove_accents($title);
 937
 938    if ( '' === $title || false === $title )
 939      $title = $fallback_title;
 940
 941    return $title;
 942  }
 943
 944
 945  /**
 946   * Converts all accent characters to ASCII characters.
 947   *
 948   * If there are no accent characters, then the string given is just returned.
 949   *
 950   * @since 1.2.1
 951   *
 952   * @param string $string Text that might have accent characters
 953   * @return string Filtered string with replaced "nice" characters.
 954   *
 955   * Source: WordPress, used under GPLv3 or Later
 956   *
 957   */
 958  function remove_accents($string) {
 959    if ( !preg_match('/[\x80-\xff]/', $string) )
 960      return $string;
 961
 962    if (seems_utf8($string)) {
 963      $chars = array(
 964        // Decompositions for Latin-1 Supplement
 965        chr(194).chr(170) => 'a', chr(194).chr(186) => 'o',
 966        chr(195).chr(128) => 'A', chr(195).chr(129) => 'A',
 967        chr(195).chr(130) => 'A', chr(195).chr(131) => 'A',
 968        chr(195).chr(132) => 'A', chr(195).chr(133) => 'A',
 969        chr(195).chr(134) => 'AE', chr(195).chr(135) => 'C',
 970        chr(195).chr(136) => 'E', chr(195).chr(137) => 'E',
 971        chr(195).chr(138) => 'E', chr(195).chr(139) => 'E',
 972        chr(195).chr(140) => 'I', chr(195).chr(141) => 'I',
 973        chr(195).chr(142) => 'I', chr(195).chr(143) => 'I',
 974        chr(195).chr(144) => 'D', chr(195).chr(145) => 'N',
 975        chr(195).chr(146) => 'O', chr(195).chr(147) => 'O',
 976        chr(195).chr(148) => 'O', chr(195).chr(149) => 'O',
 977        chr(195).chr(150) => 'O', chr(195).chr(153) => 'U',
 978        chr(195).chr(154) => 'U', chr(195).chr(155) => 'U',
 979        chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y',
 980        chr(195).chr(158) => 'TH', chr(195).chr(159) => 's',
 981        chr(195).chr(160) => 'a', chr(195).chr(161) => 'a',
 982        chr(195).chr(162) => 'a', chr(195).chr(163) => 'a',
 983        chr(195).chr(164) => 'a', chr(195).chr(165) => 'a',
 984        chr(195).chr(166) => 'ae', chr(195).chr(167) => 'c',
 985        chr(195).chr(168) => 'e', chr(195).chr(169) => 'e',
 986        chr(195).chr(170) => 'e', chr(195).chr(171) => 'e',
 987        chr(195).chr(172) => 'i', chr(195).chr(173) => 'i',
 988        chr(195).chr(174) => 'i', chr(195).chr(175) => 'i',
 989        chr(195).chr(176) => 'd', chr(195).chr(177) => 'n',
 990        chr(195).chr(178) => 'o', chr(195).chr(179) => 'o',
 991        chr(195).chr(180) => 'o', chr(195).chr(181) => 'o',
 992        chr(195).chr(182) => 'o', chr(195).chr(184) => 'o',
 993        chr(195).chr(185) => 'u', chr(195).chr(186) => 'u',
 994        chr(195).chr(187) => 'u', chr(195).chr(188) => 'u',
 995        chr(195).chr(189) => 'y', chr(195).chr(190) => 'th',
 996        chr(195).chr(191) => 'y', chr(195).chr(152) => 'O',
 997        // Decompositions for Latin Extended-A
 998        chr(196).chr(128) => 'A', chr(196).chr(129) => 'a',
 999        chr(196).chr(130) => 'A', chr(196).chr(131) => 'a',
1000        chr(196).chr(132) => 'A', chr(196).chr(133) => 'a',
1001        chr(196).chr(134) => 'C', chr(196).chr(135) => 'c',
1002        chr(196).chr(136) => 'C', chr(196).chr(137) => 'c',
1003        chr(196).chr(138) => 'C', chr(196).chr(139) => 'c',
1004        chr(196).chr(140) => 'C', chr(196).chr(141) => 'c',
1005        chr(196).chr(142) => 'D', chr(196).chr(143) => 'd',
1006        chr(196).chr(144) => 'D', chr(196).chr(145) => 'd',
1007        chr(196).chr(146) => 'E', chr(196).chr(147) => 'e',
1008        chr(196).chr(148) => 'E', chr(196).chr(149) => 'e',
1009        chr(196).chr(150) => 'E', chr(196).chr(151) => 'e',
1010        chr(196).chr(152) => 'E', chr(196).chr(153) => 'e',
1011        chr(196).chr(154) => 'E', chr(196).chr(155) => 'e',
1012        chr(196).chr(156) => 'G', chr(196).chr(157) => 'g',
1013        chr(196).chr(158) => 'G', chr(196).chr(159) => 'g',
1014        chr(196).chr(160) => 'G', chr(196).chr(161) => 'g',
1015        chr(196).chr(162) => 'G', chr(196).chr(163) => 'g',
1016        chr(196).chr(164) => 'H', chr(196).chr(165) => 'h',
1017        chr(196).chr(166) => 'H', chr(196).chr(167) => 'h',
1018        chr(196).chr(168) => 'I', chr(196).chr(169) => 'i',
1019        chr(196).chr(170) => 'I', chr(196).chr(171) => 'i',
1020        chr(196).chr(172) => 'I', chr(196).chr(173) => 'i',
1021        chr(196).chr(174) => 'I', chr(196).chr(175) => 'i',
1022        chr(196).chr(176) => 'I', chr(196).chr(177) => 'i',
1023        chr(196).chr(178) => 'IJ', chr(196).chr(179) => 'ij',
1024        chr(196).chr(180) => 'J', chr(196).chr(181) => 'j',
1025        chr(196).chr(182) => 'K', chr(196).chr(183) => 'k',
1026        chr(196).chr(184) => 'k', chr(196).chr(185) => 'L',
1027        chr(196).chr(186) => 'l', chr(196).chr(187) => 'L',
1028        chr(196).chr(188) => 'l', chr(196).chr(189) => 'L',
1029        chr(196).chr(190) => 'l', chr(196).chr(191) => 'L',
1030        chr(197).chr(128) => 'l', chr(197).chr(129) => 'L',
1031        chr(197).chr(130) => 'l', chr(197).chr(131) => 'N',
1032        chr(197).chr(132) => 'n', chr(197).chr(133) => 'N',
1033        chr(197).chr(134) => 'n', chr(197).chr(135) => 'N',
1034        chr(197).chr(136) => 'n', chr(197).chr(137) => 'N',
1035        chr(197).chr(138) => 'n', chr(197).chr(139) => 'N',
1036        chr(197).chr(140) => 'O', chr(197).chr(141) => 'o',
1037        chr(197).chr(142) => 'O', chr(197).chr(143) => 'o',
1038        chr(197).chr(144) => 'O', chr(197).chr(145) => 'o',
1039        chr(197).chr(146) => 'OE', chr(197).chr(147) => 'oe',
1040        chr(197).chr(148) => 'R', chr(197).chr(149) => 'r',
1041        chr(197).chr(150) => 'R', chr(197).chr(151) => 'r',
1042        chr(197).chr(152) => 'R', chr(197).chr(153) => 'r',
1043        chr(197).chr(154) => 'S', chr(197).chr(155) => 's',
1044        chr(197).chr(156) => 'S', chr(197).chr(157) => 's',
1045        chr(197).chr(158) => 'S', chr(197).chr(159) => 's',
1046        chr(197).chr(160) => 'S', chr(197).chr(161) => 's',
1047        chr(197).chr(162) => 'T', chr(197).chr(163) => 't',
1048        chr(197).chr(164) => 'T', chr(197).chr(165) => 't',
1049        chr(197).chr(166) => 'T', chr(197).chr(167) => 't',
1050        chr(197).chr(168) => 'U', chr(197).chr(169) => 'u',
1051        chr(197).chr(170) => 'U', chr(197).chr(171) => 'u',
1052        chr(197).chr(172) => 'U', chr(197).chr(173) => 'u',
1053        chr(197).chr(174) => 'U', chr(197).chr(175) => 'u',
1054        chr(197).chr(176) => 'U', chr(197).chr(177) => 'u',
1055        chr(197).chr(178) => 'U', chr(197).chr(179) => 'u',
1056        chr(197).chr(180) => 'W', chr(197).chr(181) => 'w',
1057        chr(197).chr(182) => 'Y', chr(197).chr(183) => 'y',
1058        chr(197).chr(184) => 'Y', chr(197).chr(185) => 'Z',
1059        chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z',
1060        chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z',
1061        chr(197).chr(190) => 'z', chr(197).chr(191) => 's',
1062        // Decompositions for Latin Extended-B
1063        chr(200).chr(152) => 'S', chr(200).chr(153) => 's',
1064        chr(200).chr(154) => 'T', chr(200).chr(155) => 't',
1065        // Euro Sign
1066        chr(226).chr(130).chr(172) => 'E',
1067        // GBP (Pound) Sign
1068        chr(194).chr(163) => '',
1069        // Vowels with diacritic (Vietnamese)
1070        // unmarked
1071        chr(198).chr(160) => 'O', chr(198).chr(161) => 'o',
1072        chr(198).chr(175) => 'U', chr(198).chr(176) => 'u',
1073        // grave accent
1074        chr(225).chr(186).chr(166) => 'A', chr(225).chr(186).chr(167) => 'a',
1075        chr(225).chr(186).chr(176) => 'A', chr(225).chr(186).chr(177) => 'a',
1076        chr(225).chr(187).chr(128) => 'E', chr(225).chr(187).chr(129) => 'e',
1077        chr(225).chr(187).chr(146) => 'O', chr(225).chr(187).chr(147) => 'o',
1078        chr(225).chr(187).chr(156) => 'O', chr(225).chr(187).chr(157) => 'o',
1079        chr(225).chr(187).chr(170) => 'U', chr(225).chr(187).chr(171) => 'u',
1080        chr(225).chr(187).chr(178) => 'Y', chr(225).chr(187).chr(179) => 'y',
1081        // hook
1082        chr(225).chr(186).chr(162) => 'A', chr(225).chr(186).chr(163) => 'a',
1083        chr(225).chr(186).chr(168) => 'A', chr(225).chr(186).chr(169) => 'a',
1084        chr(225).chr(186).chr(178) => 'A', chr(225).chr(186).chr(179) => 'a',
1085        chr(225).chr(186).chr(186) => 'E', chr(225).chr(186).chr(187) => 'e',
1086        chr(225).chr(187).chr(130) => 'E', chr(225).chr(187).chr(131) => 'e',
1087        chr(225).chr(187).chr(136) => 'I', chr(225).chr(187).chr(137) => 'i',
1088        chr(225).chr(187).chr(142) => 'O', chr(225).chr(187).chr(143) => 'o',
1089        chr(225).chr(187).chr(148) => 'O', chr(225).chr(187).chr(149) => 'o',
1090        chr(225).chr(187).chr(158) => 'O', chr(225).chr(187).chr(159) => 'o',
1091        chr(225).chr(187).chr(166) => 'U', chr(225).chr(187).chr(167) => 'u',
1092        chr(225).chr(187).chr(172) => 'U', chr(225).chr(187).chr(173) => 'u',
1093        chr(225).chr(187).chr(182) => 'Y', chr(225).chr(187).chr(183) => 'y',
1094        // tilde
1095        chr(225).chr(186).chr(170) => 'A', chr(225).chr(186).chr(171) => 'a',
1096        chr(225).chr(186).chr(180) => 'A', chr(225).chr(186).chr(181) => 'a',
1097        chr(225).chr(186).chr(188) => 'E', chr(225).chr(186).chr(189) => 'e',
1098        chr(225).chr(187).chr(132) => 'E', chr(225).chr(187).chr(133) => 'e',
1099        chr(225).chr(187).chr(150) => 'O', chr(225).chr(187).chr(151) => 'o',
1100        chr(225).chr(187).chr(160) => 'O', chr(225).chr(187).chr(161) => 'o',
1101        chr(225).chr(187).chr(174) => 'U', chr(225).chr(187).chr(175) => 'u',
1102        chr(225).chr(187).chr(184) => 'Y', chr(225).chr(187).chr(185) => 'y',
1103        // acute accent
1104        chr(225).chr(186).chr(164) => 'A', chr(225).chr(186).chr(165) => 'a',
1105        chr(225).chr(186).chr(174) => 'A', chr(225).chr(186).chr(175) => 'a',
1106        chr(225).chr(186).chr(190) => 'E', chr(225).chr(186).chr(191) => 'e',
1107        chr(225).chr(187).chr(144) => 'O', chr(225).chr(187).chr(145) => 'o',
1108        chr(225).chr(187).chr(154) => 'O', chr(225).chr(187).chr(155) => 'o',
1109        chr(225).chr(187).chr(168) => 'U', chr(225).chr(187).chr(169) => 'u',
1110        // dot below
1111        chr(225).chr(186).chr(160) => 'A', chr(225).chr(186).chr(161) => 'a',
1112        chr(225).chr(186).chr(172) => 'A', chr(225).chr(186).chr(173) => 'a',
1113        chr(225).chr(186).chr(182) => 'A', chr(225).chr(186).chr(183) => 'a',
1114        chr(225).chr(186).chr(184) => 'E', chr(225).chr(186).chr(185) => 'e',
1115        chr(225).chr(187).chr(134) => 'E', chr(225).chr(187).chr(135) => 'e',
1116        chr(225).chr(187).chr(138) => 'I', chr(225).chr(187).chr(139) => 'i',
1117        chr(225).chr(187).chr(140) => 'O', chr(225).chr(187).chr(141) => 'o',
1118        chr(225).chr(187).chr(152) => 'O', chr(225).chr(187).chr(153) => 'o',
1119        chr(225).chr(187).chr(162) => 'O', chr(225).chr(187).chr(163) => 'o',
1120        chr(225).chr(187).chr(164) => 'U', chr(225).chr(187).chr(165) => 'u',
1121        chr(225).chr(187).chr(176) => 'U', chr(225).chr(187).chr(177) => 'u',
1122        chr(225).chr(187).chr(180) => 'Y', chr(225).chr(187).chr(181) => 'y',
1123        // Vowels with diacritic (Chinese, Hanyu Pinyin)
1124        chr(201).chr(145) => 'a',
1125        // macron
1126        chr(199).chr(149) => 'U', chr(199).chr(150) => 'u',
1127        // acute accent
1128        chr(199).chr(151) => 'U', chr(199).chr(152) => 'u',
1129        // caron
1130        chr(199).chr(141) => 'A', chr(199).chr(142) => 'a',
1131        chr(199).chr(143) => 'I', chr(199).chr(144) => 'i',
1132        chr(199).chr(145) => 'O', chr(199).chr(146) => 'o',
1133        chr(199).chr(147) => 'U', chr(199).chr(148) => 'u',
1134        chr(199).chr(153) => 'U', chr(199).chr(154) => 'u',
1135        // grave accent
1136        chr(199).chr(155) => 'U', chr(199).chr(156) => 'u',
1137      );
1138
1139      $string = strtr($string, $chars);
1140    } else {
1141      // Assume ISO-8859-1 if not UTF-8
1142      $chars['in'] = chr(128).chr(131).chr(138).chr(142).chr(154).chr(158)
1143        .chr(159).chr(162).chr(165).chr(181).chr(192).chr(193).chr(194)
1144        .chr(195).chr(196).chr(197).chr(199).chr(200).chr(201).chr(202)
1145        .chr(203).chr(204).chr(205).chr(206).chr(207).chr(209).chr(210)
1146        .chr(211).chr(212).chr(213).chr(214).chr(216).chr(217).chr(218)
1147        .chr(219).chr(220).chr(221).chr(224).chr(225).chr(226).chr(227)
1148        .chr(228).chr(229).chr(231).chr(232).chr(233).chr(234).chr(235)
1149        .chr(236).chr(237).chr(238).chr(239).chr(241).chr(242).chr(243)
1150        .chr(244).chr(245).chr(246).chr(248).chr(249).chr(250).chr(251)
1151        .chr(252).chr(253).chr(255);
1152
1153      $chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy";
1154
1155      $string = strtr($string, $chars['in'], $chars['out']);
1156      $double_chars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254));
1157      $double_chars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th');
1158      $string = str_replace($double_chars['in'], $double_chars['out'], $string);
1159    }
1160
1161    return $string;
1162  }
1163
1164
1165  /**
1166   * Combine user attributes with known attributes and fill in defaults when needed.
1167   *
1168   * The pairs should be considered to be all of the attributes which are
1169   * supported by the caller and given as a list. The returned attributes will
1170   * only contain the attributes in the $pairs list.
1171   *
1172   * If the $atts list has unsupported attributes, then they will be ignored and
1173   * removed from the final returned list.
1174   *
1175   * @since 2.5
1176   *
1177   * @param array $pairs Entire list of supported attributes and their defaults.
1178   * @param array $atts User defined attributes in shortcode tag.
1179   * @return array Combined and filtered attribute list.
1180   *
1181   * Source: WordPress, used under GPLv3 or Later
1182   * Original name: shortcode_atts
1183   *
1184   */
1185  function default_vars($pairs, $atts) {
1186    $atts = (array)$atts;
1187    $out = array();
1188    foreach ($pairs as $name => $default) {
1189      if ( array_key_exists($name, $atts) )
1190        $out[$name] = $atts[$name];
1191      else
1192        $out[$name] = $default;
1193    }
1194    return $out;
1195  }
1196
1197
1198  /**
1199   * Callback for wp_kses_normalize_entities() regular expression.
1200   *
1201   * This function only accepts valid named entity references, which are finite,
1202   * case-sensitive, and highly scrutinized by HTML and XML validators.
1203   *
1204   * @since 3.0.0
1205   *
1206   * @param array $matches preg_replace_callback() matches array
1207   * @return string Correctly encoded entity
1208   *
1209   * Source: WordPress, used under GPLv3 or Later
1210   *
1211   */
1212  function kses_named_entities($matches) {
1213    global $allowedentitynames;
1214
1215    if ( empty($matches[1]) )
1216      return '';
1217
1218    $i = $matches[1];
1219    return ( ! in_array($i, $allowedentitynames) ) ? "&amp;$i;" : "&$i;";
1220  }
1221
1222
1223  /**
1224   * Callback for wp_kses_normalize_entities() regular expression.
1225   *
1226   * This function helps wp_kses_normalize_entities() to only accept 16-bit values
1227   * and nothing more for &#number; entities.
1228   *
1229   * @access private
1230   * @since 1.0.0
1231   *
1232   * @param array $matches preg_replace_callback() matches array
1233   * @return string Correctly encoded entity
1234   *
1235   * Source: WordPress, used under GPLv3 or Later
1236   *
1237   */
1238  function kses_normalize_entities2($matches) {
1239    if ( empty($matches[1]) )
1240      return '';
1241
1242    $i = $matches[1];
1243    if ($this->valid_unicode($i)) {
1244      $i = str_pad(ltrim($i, '0'), 3, '0', STR_PAD_LEFT);
1245      $i = "&#$i;";
1246    } else {
1247      $i = "&amp;#$i;";
1248    }
1249
1250    return $i;
1251  }
1252
1253
1254  /**
1255   * Callback for wp_kses_normalize_entities() for regular expression.
1256   *
1257   * This function helps wp_kses_normalize_entities() to only accept valid Unicode
1258   * numeric entities in hex form.
1259   *
1260   * @access private
1261   *
1262   * @param array $matches preg_replace_callback() matches array
1263   * @return string Correctly encoded entity
1264   *
1265   * Source: WordPress, used under GPLv3 or Later
1266   *
1267   */
1268  function kses_normalize_entities3($matches) {
1269    if ( empty($matches[1]) )
1270      return '';
1271
1272    $hexchars = $matches[1];
1273    return ( ! $this->valid_unicode(hexdec($hexchars)) ) ? "&amp;#x$hexchars;" : '&#x'.ltrim($hexchars, '0').';';
1274  }
1275
1276
1277  /**
1278   * Helper function to determine if a Unicode value is valid.
1279   *
1280   * @param int $i Unicode value
1281   * @return bool True if the value was a valid Unicode number
1282   *
1283   * Source: WordPress, used under GPLv3 or Later
1284   *
1285   */
1286  function valid_unicode($i) {
1287    return ( $i == 0x9 || $i == 0xa || $i == 0xd ||
1288      ($i >= 0x20 && $i <= 0xd7ff) ||
1289      ($i >= 0xe000 && $i <= 0xfffd) ||
1290      ($i >= 0x10000 && $i <= 0x10ffff) );
1291  }
1292
1293
1294  /**
1295   * Filters a list of objects, based on a set of key => value arguments.
1296   *
1297   * @since 3.1.0
1298   *
1299   * @param array $list An array of objects to filter
1300   * @param array $args An array of key => value arguments to match against each object
1301   * @param string $operator The logical operation to perform:
1302   *    'AND' means all elements from the array must match;
1303   *    'OR' means only one element needs to match;
1304   *    'NOT' means no elements may match.
1305   *   The default is 'AND'.
1306   * @return array
1307   *
1308   * Source: WordPress, used under GPLv3 or Later
1309   *
1310   */
1311  function list_filter( $list, $args = array(), $operator = 'AND' ) {
1312    if ( ! is_array( $list ) )
1313      return array();
1314
1315    if ( empty( $args ) )
1316      return $list;
1317
1318    $operator = strtoupper( $operator );
1319    $count = count( $args );
1320    $filtered = array();
1321
1322    foreach ( $list as $key => $obj ) {
1323      $to_match = (array) $obj;
1324
1325      $matched = 0;
1326      foreach ( $args as $m_key => $m_value ) {
1327        if ( array_key_exists( $m_key, $to_match ) && $m_value == $to_match[ $m_key ] )
1328          $matched++;
1329      }
1330
1331      if ( ( 'AND' == $operator && $matched == $count )
1332        || ( 'OR' == $operator && $matched > 0 )
1333        || ( 'NOT' == $operator && 0 == $matched ) ) {
1334        $filtered[$key] = $obj;
1335      }
1336    }
1337
1338    return $filtered;
1339  }
1340
1341
1342}