: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Utility class for manipulating various data formats. Includes methods for
* transforming array data to another format based on key mapping, methods for
* generating XML-RPC method call strings, methods for working with arrays, and more.
class ET_Core_Data_Utils {
private static $_instance;
private $_pick_value = '_undefined_';
* Sort arguments being passed through to callbacks.
protected $sort_arguments = array(
'sort' => '__return_false',
'comparison' => '__return_false',
* Generate an XML-RPC array.
private function _create_xmlrpc_array( $values ) {
foreach ( $values as $value ) {
$output .= $this->_create_xmlrpc_value( $value );
return "<array><data>{$output}</data></array>";
* Generate an XML-RPC struct.
private function _create_xmlrpc_struct( $members ) {
foreach ( $members as $name => $value ) {
$output .= sprintf( '<member><name>%1$s</name>%2$s</member>', esc_html( $name ), $this->_create_xmlrpc_value( $value ) );
return "<struct>{$output}</struct>";
* Generate an XML-RPC value.
private function _create_xmlrpc_value( $value ) {
if ( is_string( $value ) ) {
$value = esc_html( wp_strip_all_tags( $value ) );
$output = "<string>{$value}</string>";
} else if ( is_bool( $value ) ) {
$output = "<boolean>{$value}</boolean>";
} else if ( is_int( $value ) ) {
$output = "<int>{$value}</int>";
} else if ( is_array( $value ) && $this->is_assoc_array( $value ) ) {
$output = $this->_create_xmlrpc_struct( $value );
} else if ( is_array( $value ) ) {
$output = $this->_create_xmlrpc_array( $value );
return "<value>{$output}</value>";
* Convert a SimpleXMLElement to a native PHP data type.
* @param SimpleXMLElement $value
private function _parse_value( $value ) {
case is_string( $value ):
case count( $value->struct ) > 0:
$result = new stdClass();
foreach ( $value->struct->member as $member ) {
$name = (string) $member->name;
$member_value = $this->_parse_value( $member->value );
$result->$name = $member_value;
case count( $value->array ) > 0:
foreach ( $value->array->data->value as $array_value ) {
$result[] = $this->_parse_value( $array_value );
case count( $value->i4 ) > 0:
$result = (int) $value->i4;
case count( $value->int ) > 0:
$result = (int) $value->int;
case count( $value->boolean ) > 0:
$result = (boolean) $value->boolean;
case count( $value->double ) > 0:
$result = (double) $value->double;
$result = (string) $value;
private function _remove_empty_directories( $path ) {
if ( ! is_dir( $path ) ) {
$directory_contents = glob( untrailingslashit( $path ) . '/*' );
foreach ( (array) $directory_contents as $item ) {
if ( ! $this->_remove_empty_directories( $item ) ) {
return $empty ? @rmdir( $path ) : false;
public function _array_pick_callback( $item ) {
$value = $this->_pick_value;
if ( is_array( $item ) && isset( $item[ $pick ] ) ) {
return '_undefined_' !== $value ? $value === $item[ $pick ] : $item[ $pick ];
} else if ( is_object( $item ) && isset( $item->$pick ) ) {
return '_undefined_' !== $value ? $value === $item->$pick : $item->$pick;
public function _array_sort_by_callback( $a, $b ) {
$sort_by = $this->_sort_by;
return strcmp( $a[ $sort_by ], $b[ $sort_by ] );
} else if ( is_object( $a ) ) {
return strcmp( $a->$sort_by, $b->$sort_by );
* Returns `true` if all values in `$array` are not empty, `false` otherwise.
* If `$condition` is provided then values are checked against it instead of `empty()`.
* @param bool $condition Compare values to this instead of `empty()`. Optional.
public function all( array $array, $condition = null ) {
if ( null === $condition ) {
foreach( $array as $key => $value ) {
foreach( $array as $key => $value ) {
if ( $value !== $condition ) {
* Flattens a multi-dimensional array.
* @param array $array An array to flatten.
function array_flatten( array $array ) {
$iterator = new RecursiveIteratorIterator( new RecursiveArrayIterator( $array ) );
return iterator_to_array( $iterator, $use_keys );
* Gets a value from a nested array using an address string.
* @param array $array An array which contains value located at `$address`.
* @param string|array $address The location of the value within `$array` (dot notation).
* @param mixed $default Value to return if not found. Default is an empty string.
* @return mixed The value, if found, otherwise $default.
public function array_get( $array, $address, $default = '' ) {
$keys = is_array( $address ) ? $address : explode( '.', $address );
foreach ( $keys as $key ) {
$index = substr( $key, 1, -1 );
if ( is_numeric( $index ) ) {
if ( ! isset( $value[ $key ] ) ) {
* Wrapper for {@see self::array_get()} that sanitizes the value before returning it.
* @param array $array An array which contains value located at `$address`.
* @param string $address The location of the value within `$array` (dot notation).
* @param mixed $default Value to return if not found. Default is an empty string.
* @param string $sanitizer Sanitize function to use. Default is 'sanitize_text_field'.
* @return mixed The sanitized value if found, otherwise $default.
public function array_get_sanitized( $array, $address, $default = '', $sanitizer = 'sanitize_text_field' ) {
if ( $value = $this->array_get( $array, $address, $default ) ) {
$value = $sanitizer( $value );
* Creates a new array containing only the items that have a key or property or only the items that
* have a key or property that is equal to a certain value.
* @param array $array The array to pick from.
* @param string|array $pick_by The key or property to look for or an array mapping the key or property
* to a value to look for.
public function array_pick( $array, $pick_by ) {
if ( is_string( $pick_by ) || is_int( $pick_by ) ) {
} else if ( is_array( $pick_by ) && 1 === count( $pick_by ) ) {
$this->_pick = key( $pick_by );
$this->_pick_value = array_pop( $pick_by );
return array_filter( $array, array( $this, '_array_pick_callback' ) );
* Sets a value in a nested array using an address string (dot notation)
* @see http://stackoverflow.com/a/9628276/419887
* @param array $array The array to modify
* @param string|array $path The path in the array
* @param mixed $value The value to set
public function array_set( &$array, $path, $value ) {
$path_parts = is_array( $path ) ? $path : explode( '.', $path );
foreach ( $path_parts as $key ) {
if ( ! is_array( $current ) ) {
if ( '[' === $key[0] && is_numeric( substr( $key, 1, - 1 ) ) ) {
$current = &$current[ $key ];
public function array_sort_by( $array, $key_or_prop ) {
if ( ! is_string( $key_or_prop ) && ! is_int( $key_or_prop ) ) {
$this->_sort_by = $key_or_prop;
if ( $this->is_assoc_array( $array ) ) {
uasort( $array, array( $this, '_array_sort_by_callback' ) );
usort( $array, array( $this, '_array_sort_by_callback' ) );
* Update a nested array value found at the provided path using {@see array_merge()}.
public function array_update( &$array, $path, $value ) {
$current_value = $this->array_get( $array, $path, array() );
$this->array_set( $array, $path, array_merge( $current_value, $value ) );
* Whether or not a string ends with a substring.
* @param string $haystack The string to look in.
* @param string $needle The string to look for.
public function ends_with( $haystack, $needle ) {
$length = strlen( $needle );
return ( substr( $haystack, -$length ) === $needle );
public function ensure_directory_exists( $path ) {
if ( file_exists( $path ) ) {
// Try to create the directory
$path = $this->normalize_path( $path );
if ( ! $this->WPFS()->mkdir( $path ) ) {
// Walk up the tree and create any missing parent directories
$this->ensure_directory_exists( dirname( $path ) );
$this->WPFS()->mkdir( $path );
public static function instance() {
if ( ! self::$_instance ) {
self::$_instance = new ET_Core_Data_Utils();
* Determine if an array has any `string` keys (thus would be considered an object in JSON)
public function is_assoc_array( $array ) {
return is_array( $array ) && count( array_filter( array_keys( $array ), 'is_string' ) ) > 0;
* Determine if value is an XML-RPC error.
* @param SimpleXMLElement $value
public function is_xmlrpc_error( $value ) {
return is_object( $value ) && isset( $value->faultCode );
* Replaces any Windows style directory separators in $path with Linux style separators.
* Windows actually supports both styles, even mixed together. However, its better not
* to mix them (especially when doing string comparisons on paths).
* @since 4.0.8 Use {@see wp_normalize_path()} if it exists. Remove all occurrences of '..' from paths.
public function normalize_path( $path = '' ) {
$path = str_replace( '..', '', $path );
if ( function_exists( 'wp_normalize_path' ) ) {
return wp_normalize_path( $path );
return str_replace( '\\', '/', $path );
* Generate post data for a XML-RPC method call
* @param string $method_name
public function prepare_xmlrpc_method_call( $method_name, $params = array() ) {
foreach ( $params as $param ) {
$value = $this->_create_xmlrpc_value( $param );
$output .= "<param>{$value}</param>";
$method_name = esc_html( $method_name );
"<?xml version='1.0' encoding='UTF-8'?>
<methodName>{$method_name}</methodName>
* Disable XML entity loader.
public function libxml_disable_entity_loader( $disable ) {
if ( function_exists( 'libxml_disable_entity_loader' ) ) {
libxml_disable_entity_loader( $disable );
* Securely use simplexml_load_string.
* @param string $data XML data string.
* @return SimpleXMLElement
public function simplexml_load_string( $data ) {