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