521 lines
12 KiB
PHP
521 lines
12 KiB
PHP
<?php
|
|
|
|
// misc help functions and utilities
|
|
|
|
function rand_str( $len = 32 ) {
|
|
return substr( md5( uniqid( rand() ) ), 0, $len );
|
|
}
|
|
|
|
function rand_long_str( $length ) {
|
|
$chars = 'abcdefghijklmnopqrstuvwxyz';
|
|
$string = '';
|
|
|
|
for ( $i = 0; $i < $length; $i++ ) {
|
|
$rand = rand( 0, strlen( $chars ) - 1 );
|
|
$string .= substr( $chars, $rand, 1 );
|
|
}
|
|
|
|
return $string;
|
|
}
|
|
|
|
// strip leading and trailing whitespace from each line in the string
|
|
function strip_ws( $txt ) {
|
|
$lines = explode( "\n", $txt );
|
|
$result = array();
|
|
foreach ( $lines as $line ) {
|
|
if ( trim( $line ) ) {
|
|
$result[] = trim( $line );
|
|
}
|
|
}
|
|
|
|
return trim( join( "\n", $result ) );
|
|
}
|
|
|
|
// helper class for testing code that involves actions and filters
|
|
// typical use:
|
|
// $ma = new MockAction();
|
|
// add_action('foo', array(&$ma, 'action'));
|
|
class MockAction {
|
|
var $events;
|
|
var $debug;
|
|
|
|
/**
|
|
* PHP5 constructor.
|
|
*/
|
|
function __construct( $debug = 0 ) {
|
|
$this->reset();
|
|
$this->debug = $debug;
|
|
}
|
|
|
|
function reset() {
|
|
$this->events = array();
|
|
}
|
|
|
|
function current_filter() {
|
|
if ( is_callable( 'current_filter' ) ) {
|
|
return current_filter();
|
|
}
|
|
global $wp_actions;
|
|
return end( $wp_actions );
|
|
}
|
|
|
|
function action( $arg ) {
|
|
if ( $this->debug ) {
|
|
dmp( __FUNCTION__, $this->current_filter() );
|
|
}
|
|
$args = func_get_args();
|
|
$this->events[] = array(
|
|
'action' => __FUNCTION__,
|
|
'tag' => $this->current_filter(),
|
|
'args' => $args,
|
|
);
|
|
return $arg;
|
|
}
|
|
|
|
function action2( $arg ) {
|
|
if ( $this->debug ) {
|
|
dmp( __FUNCTION__, $this->current_filter() );
|
|
}
|
|
|
|
$args = func_get_args();
|
|
$this->events[] = array(
|
|
'action' => __FUNCTION__,
|
|
'tag' => $this->current_filter(),
|
|
'args' => $args,
|
|
);
|
|
return $arg;
|
|
}
|
|
|
|
function filter( $arg ) {
|
|
if ( $this->debug ) {
|
|
dmp( __FUNCTION__, $this->current_filter() );
|
|
}
|
|
|
|
$args = func_get_args();
|
|
$this->events[] = array(
|
|
'filter' => __FUNCTION__,
|
|
'tag' => $this->current_filter(),
|
|
'args' => $args,
|
|
);
|
|
return $arg;
|
|
}
|
|
|
|
function filter2( $arg ) {
|
|
if ( $this->debug ) {
|
|
dmp( __FUNCTION__, $this->current_filter() );
|
|
}
|
|
|
|
$args = func_get_args();
|
|
$this->events[] = array(
|
|
'filter' => __FUNCTION__,
|
|
'tag' => $this->current_filter(),
|
|
'args' => $args,
|
|
);
|
|
return $arg;
|
|
}
|
|
|
|
function filter_append( $arg ) {
|
|
if ( $this->debug ) {
|
|
dmp( __FUNCTION__, $this->current_filter() );
|
|
}
|
|
|
|
$args = func_get_args();
|
|
$this->events[] = array(
|
|
'filter' => __FUNCTION__,
|
|
'tag' => $this->current_filter(),
|
|
'args' => $args,
|
|
);
|
|
return $arg . '_append';
|
|
}
|
|
|
|
function filterall( $tag, ...$args ) {
|
|
// this one doesn't return the result, so it's safe to use with the new 'all' filter
|
|
if ( $this->debug ) {
|
|
dmp( __FUNCTION__, $this->current_filter() );
|
|
}
|
|
|
|
$this->events[] = array(
|
|
'filter' => __FUNCTION__,
|
|
'tag' => $tag,
|
|
'args' => $args,
|
|
);
|
|
}
|
|
|
|
// return a list of all the actions, tags and args
|
|
function get_events() {
|
|
return $this->events;
|
|
}
|
|
|
|
// return a count of the number of times the action was called since the last reset
|
|
function get_call_count( $tag = '' ) {
|
|
if ( $tag ) {
|
|
$count = 0;
|
|
foreach ( $this->events as $e ) {
|
|
if ( $e['action'] === $tag ) {
|
|
++$count;
|
|
}
|
|
}
|
|
return $count;
|
|
}
|
|
return count( $this->events );
|
|
}
|
|
|
|
// return an array of the tags that triggered calls to this action
|
|
function get_tags() {
|
|
$out = array();
|
|
foreach ( $this->events as $e ) {
|
|
$out[] = $e['tag'];
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
// return an array of args passed in calls to this action
|
|
function get_args() {
|
|
$out = array();
|
|
foreach ( $this->events as $e ) {
|
|
$out[] = $e['args'];
|
|
}
|
|
return $out;
|
|
}
|
|
}
|
|
|
|
// convert valid xml to an array tree structure
|
|
// kinda lame but it works with a default php 4 installation
|
|
class TestXMLParser {
|
|
var $xml;
|
|
var $data = array();
|
|
|
|
/**
|
|
* PHP5 constructor.
|
|
*/
|
|
function __construct( $in ) {
|
|
$this->xml = xml_parser_create();
|
|
xml_set_object( $this->xml, $this );
|
|
xml_parser_set_option( $this->xml, XML_OPTION_CASE_FOLDING, 0 );
|
|
xml_set_element_handler( $this->xml, array( $this, 'start_handler' ), array( $this, 'end_handler' ) );
|
|
xml_set_character_data_handler( $this->xml, array( $this, 'data_handler' ) );
|
|
$this->parse( $in );
|
|
}
|
|
|
|
function parse( $in ) {
|
|
$parse = xml_parse( $this->xml, $in, true );
|
|
if ( ! $parse ) {
|
|
trigger_error(
|
|
sprintf(
|
|
'XML error: %s at line %d',
|
|
xml_error_string( xml_get_error_code( $this->xml ) ),
|
|
xml_get_current_line_number( $this->xml )
|
|
),
|
|
E_USER_ERROR
|
|
);
|
|
xml_parser_free( $this->xml );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function start_handler( $parser, $name, $attributes ) {
|
|
$data['name'] = $name;
|
|
if ( $attributes ) {
|
|
$data['attributes'] = $attributes; }
|
|
$this->data[] = $data;
|
|
}
|
|
|
|
function data_handler( $parser, $data ) {
|
|
$index = count( $this->data ) - 1;
|
|
|
|
if ( ! isset( $this->data[ $index ]['content'] ) ) {
|
|
$this->data[ $index ]['content'] = '';
|
|
}
|
|
$this->data[ $index ]['content'] .= $data;
|
|
}
|
|
|
|
function end_handler( $parser, $name ) {
|
|
if ( count( $this->data ) > 1 ) {
|
|
$data = array_pop( $this->data );
|
|
$index = count( $this->data ) - 1;
|
|
$this->data[ $index ]['child'][] = $data;
|
|
}
|
|
}
|
|
}
|
|
|
|
function xml_to_array( $in ) {
|
|
$p = new TestXMLParser( $in );
|
|
return $p->data;
|
|
}
|
|
|
|
function xml_find( $tree, ...$elements ) {
|
|
$n = count( $elements );
|
|
$out = array();
|
|
|
|
if ( $n < 1 ) {
|
|
return $out;
|
|
}
|
|
|
|
for ( $i = 0; $i < count( $tree ); $i++ ) {
|
|
# echo "checking '{$tree[$i][name]}' == '{$elements[0]}'\n";
|
|
# var_dump( $tree[$i]['name'], $elements[0] );
|
|
if ( $tree[ $i ]['name'] === $elements[0] ) {
|
|
# echo "n == {$n}\n";
|
|
if ( 1 === $n ) {
|
|
$out[] = $tree[ $i ];
|
|
} else {
|
|
$subtree =& $tree[ $i ]['child'];
|
|
$out = array_merge( $out, xml_find( $subtree, ...array_slice( $elements, 1 ) ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
function xml_join_atts( $atts ) {
|
|
$a = array();
|
|
foreach ( $atts as $k => $v ) {
|
|
$a[] = $k . '="' . $v . '"';
|
|
}
|
|
return join( ' ', $a );
|
|
}
|
|
|
|
function xml_array_dumbdown( &$data ) {
|
|
$out = array();
|
|
|
|
foreach ( array_keys( $data ) as $i ) {
|
|
$name = $data[ $i ]['name'];
|
|
if ( ! empty( $data[ $i ]['attributes'] ) ) {
|
|
$name .= ' ' . xml_join_atts( $data[ $i ]['attributes'] );
|
|
}
|
|
|
|
if ( ! empty( $data[ $i ]['child'] ) ) {
|
|
$out[ $name ][] = xml_array_dumbdown( $data[ $i ]['child'] );
|
|
} else {
|
|
$out[ $name ] = $data[ $i ]['content'];
|
|
}
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
function dmp( ...$args ) {
|
|
foreach ( $args as $thing ) {
|
|
echo ( is_scalar( $thing ) ? strval( $thing ) : var_export( $thing, true ) ), "\n";
|
|
}
|
|
}
|
|
|
|
function dmp_filter( $a ) {
|
|
dmp( $a );
|
|
return $a;
|
|
}
|
|
|
|
function get_echo( $callable, $args = array() ) {
|
|
ob_start();
|
|
call_user_func_array( $callable, $args );
|
|
return ob_get_clean();
|
|
}
|
|
|
|
// recursively generate some quick assertEquals tests based on an array
|
|
function gen_tests_array( $name, $array ) {
|
|
$out = array();
|
|
foreach ( $array as $k => $v ) {
|
|
if ( is_numeric( $k ) ) {
|
|
$index = strval( $k );
|
|
} else {
|
|
$index = "'" . addcslashes( $k, "\n\r\t'\\" ) . "'";
|
|
}
|
|
|
|
if ( is_string( $v ) ) {
|
|
$out[] = '$this->assertEquals( \'' . addcslashes( $v, "\n\r\t'\\" ) . '\', $' . $name . '[' . $index . '] );';
|
|
} elseif ( is_numeric( $v ) ) {
|
|
$out[] = '$this->assertEquals( ' . $v . ', $' . $name . '[' . $index . '] );';
|
|
} elseif ( is_array( $v ) ) {
|
|
$out[] = gen_tests_array( "{$name}[{$index}]", $v );
|
|
}
|
|
}
|
|
return join( "\n", $out ) . "\n";
|
|
}
|
|
|
|
/**
|
|
* Use to create objects by yourself
|
|
*/
|
|
class MockClass {};
|
|
|
|
/**
|
|
* Drops all tables from the WordPress database
|
|
*/
|
|
function drop_tables() {
|
|
global $wpdb;
|
|
$tables = $wpdb->get_col( 'SHOW TABLES;' );
|
|
foreach ( $tables as $table ) {
|
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
$wpdb->query( "DROP TABLE IF EXISTS {$table}" );
|
|
}
|
|
}
|
|
|
|
function print_backtrace() {
|
|
$bt = debug_backtrace();
|
|
echo "Backtrace:\n";
|
|
$i = 0;
|
|
foreach ( $bt as $stack ) {
|
|
echo ++$i, ': ';
|
|
if ( isset( $stack['class'] ) ) {
|
|
echo $stack['class'] . '::';
|
|
}
|
|
if ( isset( $stack['function'] ) ) {
|
|
echo $stack['function'] . '() ';
|
|
}
|
|
echo "line {$stack[line]} in {$stack[file]}\n";
|
|
}
|
|
echo "\n";
|
|
}
|
|
|
|
// mask out any input fields matching the given name
|
|
function mask_input_value( $in, $name = '_wpnonce' ) {
|
|
return preg_replace( '@<input([^>]*) name="' . preg_quote( $name ) . '"([^>]*) value="[^>]*" />@', '<input$1 name="' . preg_quote( $name ) . '"$2 value="***" />', $in );
|
|
}
|
|
|
|
/**
|
|
* Removes the post type and its taxonomy associations.
|
|
*/
|
|
function _unregister_post_type( $cpt_name ) {
|
|
unregister_post_type( $cpt_name );
|
|
}
|
|
|
|
function _unregister_taxonomy( $taxonomy_name ) {
|
|
unregister_taxonomy( $taxonomy_name );
|
|
}
|
|
|
|
/**
|
|
* Unregister a post status.
|
|
*
|
|
* @since 4.2.0
|
|
*
|
|
* @param string $status
|
|
*/
|
|
function _unregister_post_status( $status ) {
|
|
unset( $GLOBALS['wp_post_statuses'][ $status ] );
|
|
}
|
|
|
|
function _cleanup_query_vars() {
|
|
// clean out globals to stop them polluting wp and wp_query
|
|
foreach ( $GLOBALS['wp']->public_query_vars as $v ) {
|
|
unset( $GLOBALS[ $v ] );
|
|
}
|
|
|
|
foreach ( $GLOBALS['wp']->private_query_vars as $v ) {
|
|
unset( $GLOBALS[ $v ] );
|
|
}
|
|
|
|
foreach ( get_taxonomies( array(), 'objects' ) as $t ) {
|
|
if ( $t->publicly_queryable && ! empty( $t->query_var ) ) {
|
|
$GLOBALS['wp']->add_query_var( $t->query_var );
|
|
}
|
|
}
|
|
|
|
foreach ( get_post_types( array(), 'objects' ) as $t ) {
|
|
if ( is_post_type_viewable( $t ) && ! empty( $t->query_var ) ) {
|
|
$GLOBALS['wp']->add_query_var( $t->query_var );
|
|
}
|
|
}
|
|
}
|
|
|
|
function _clean_term_filters() {
|
|
remove_filter( 'get_terms', array( 'Featured_Content', 'hide_featured_term' ), 10, 2 );
|
|
remove_filter( 'get_the_terms', array( 'Featured_Content', 'hide_the_featured_term' ), 10, 3 );
|
|
}
|
|
|
|
/**
|
|
* Special class for exposing protected wpdb methods we need to access
|
|
*/
|
|
class WpdbExposedMethodsForTesting extends wpdb {
|
|
public function __construct() {
|
|
global $wpdb;
|
|
$this->dbh = $wpdb->dbh;
|
|
$this->use_mysqli = $wpdb->use_mysqli;
|
|
$this->is_mysql = $wpdb->is_mysql;
|
|
$this->ready = true;
|
|
$this->field_types = $wpdb->field_types;
|
|
$this->charset = $wpdb->charset;
|
|
|
|
$this->dbuser = $wpdb->dbuser;
|
|
$this->dbpassword = $wpdb->dbpassword;
|
|
$this->dbname = $wpdb->dbname;
|
|
$this->dbhost = $wpdb->dbhost;
|
|
}
|
|
|
|
public function __call( $name, $arguments ) {
|
|
return call_user_func_array( array( $this, $name ), $arguments );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine approximate backtrack count when running PCRE.
|
|
*
|
|
* @return int The backtrack count.
|
|
*/
|
|
function benchmark_pcre_backtracking( $pattern, $subject, $strategy ) {
|
|
$saved_config = ini_get( 'pcre.backtrack_limit' );
|
|
|
|
// Attempt to prevent PHP crashes. Adjust lower when needed.
|
|
$limit = 1000000;
|
|
|
|
// Start with small numbers, so if a crash is encountered at higher numbers we can still debug the problem.
|
|
for ( $i = 4; $i <= $limit; $i *= 2 ) {
|
|
|
|
ini_set( 'pcre.backtrack_limit', $i );
|
|
|
|
switch ( $strategy ) {
|
|
case 'split':
|
|
preg_split( $pattern, $subject );
|
|
break;
|
|
case 'match':
|
|
preg_match( $pattern, $subject );
|
|
break;
|
|
case 'match_all':
|
|
$matches = array();
|
|
preg_match_all( $pattern, $subject, $matches );
|
|
break;
|
|
}
|
|
|
|
ini_set( 'pcre.backtrack_limit', $saved_config );
|
|
|
|
switch ( preg_last_error() ) {
|
|
case PREG_NO_ERROR:
|
|
return $i;
|
|
case PREG_BACKTRACK_LIMIT_ERROR:
|
|
break;
|
|
case PREG_RECURSION_LIMIT_ERROR:
|
|
trigger_error( 'PCRE recursion limit encountered before backtrack limit.' );
|
|
return;
|
|
case PREG_BAD_UTF8_ERROR:
|
|
trigger_error( 'UTF-8 error during PCRE benchmark.' );
|
|
return;
|
|
case PREG_INTERNAL_ERROR:
|
|
trigger_error( 'Internal error during PCRE benchmark.' );
|
|
return;
|
|
default:
|
|
trigger_error( 'Unexpected error during PCRE benchmark.' );
|
|
return;
|
|
}
|
|
}
|
|
|
|
return $i;
|
|
}
|
|
|
|
function test_rest_expand_compact_links( $links ) {
|
|
if ( empty( $links['curies'] ) ) {
|
|
return $links;
|
|
}
|
|
foreach ( $links as $rel => $links_array ) {
|
|
if ( ! strpos( $rel, ':' ) ) {
|
|
continue;
|
|
}
|
|
|
|
$name = explode( ':', $rel );
|
|
|
|
$curie = wp_list_filter( $links['curies'], array( 'name' => $name[0] ) );
|
|
$full_uri = str_replace( '{rel}', $name[1], $curie[0]['href'] );
|
|
$links[ $full_uri ] = $links_array;
|
|
unset( $links[ $rel ] );
|
|
}
|
|
return $links;
|
|
}
|