<?php
/**
* UD API Distributable - Common Functions Used in Usability Dynamics, Inc. Products.
*
* @copyright Copyright (c) 2010 - 2012, Usability Dynamics, Inc.
* @license https://usabilitydynamics.com/services/theme-and-plugin-eula/
* @link http://api.usabilitydynamics.com/readme/ud_api.txt UD API Changelog
*
* @version 1.0.2
*/
if ( class_exists( 'UD_API' ) ) {
return;
}
define( 'UD_API_Transdomain', 'UD_API_Transdomain' );
/**
* Used for performing various useful functions applicable to different plugins.
*
* @package UsabilityDynamics
*/
class UD_API {
/**
* PHP4 style Constructor - Calls PHP5 Style Constructor
*
* @since 1.0.0
* @return WP_Http
*/
function UD_API() {
$this->__construct();
}
/**
* Handler for general API calls to UD
*
* @since 1.0.0
* @author potanin@UD
*/
static function get_service( $args = '' ) {
$args = wp_parse_args( $args, array(
'service' => false,
'args' => array(),
'method' => 'POST',
'timeout' => 60,
'sslverify' => false
) );
if ( !$args[ 'service' ] ) {
return new WP_Error( 'error', sprintf( __( 'API service not specified.', UD_API_Transdomain ) ) );
}
$response = wp_remote_request( add_query_arg( 'api', get_option( 'ud_api_key' ), trailingslashit( 'http://api.usabilitydynamics.com' ) . trailingslashit( $args[ 'service' ] ) ), array(
'method' => $args[ 'method' ],
'timeout' => $args[ 'timeout' ],
'sslverify' => $args[ 'sslverify' ],
'body' => array(
'args' => $args[ 'args' ]
) ) );
if ( is_wp_error( $response ) ) {
return $response;
}
if ( $response[ 'response' ][ 'code' ] == 200 ) {
return json_decode( $response[ 'body' ] ) ? json_decode( $response[ 'body' ] ) : $response[ 'body' ];
} else {
return new WP_Error( 'error', sprintf( __( 'API Failure: %1s.', UD_API_Transdomain ), $response[ 'response' ][ 'message' ] ) );
}
}
/**
* Converts slashes for Windows paths.
*
* @since 1.0.0
* @source Flawless
* @author potanin@UD
*/
static function fix_path( $path ) {
return str_replace( '\\', '/', $path );
}
/**
* Applies trim() function to all values in an array
*
* @source WP-Property
* @since 0.6.0
*/
static function trim_array( $array = array() ) {
foreach ( (array)$array as $key => $value ) {
$array[ $key ] = trim( $value );
}
return $array;
}
/**
* Returns image sizes for a passed image size slug
*
* @source WP-Property
* @since 0.5.4
* @returns array keys: 'width' and 'height' if image type sizes found.
*/
static function image_sizes( $type = false, $args = '' ) {
global $_wp_additional_image_sizes;
$image_sizes = (array)$_wp_additional_image_sizes;
$image_sizes[ 'thumbnail' ] = array(
'width' => intval( get_option( 'thumbnail_size_w' ) ),
'height' => intval( get_option( 'thumbnail_size_h' ) )
);
$image_sizes[ 'medium' ] = array(
'width' => intval( get_option( 'medium_size_w' ) ),
'height' => intval( get_option( 'medium_size_h' ) )
);
$image_sizes[ 'large' ] = array(
'width' => intval( get_option( 'large_size_w' ) ),
'height' => intval( get_option( 'large_size_h' ) )
);
foreach ( (array)$image_sizes as $size => $data ) {
$image_sizes[ $size ] = array_filter( (array)$data );
}
return array_filter( (array)$image_sizes );
}
/**
* Insert array into an associative array before a specific key
*
* @source http://stackoverflow.com/questions/6501845/php-need-help-inserting-arrays-into-associative-arrays-at-given-keys
* @author potanin@UD
*/
static function array_insert_before( $array, $key, $new ) {
$array = (array)$array;
$keys = array_keys( $array );
$pos = (int)array_search( $key, $keys );
return array_merge(
array_slice( $array, 0, $pos ),
$new,
array_slice( $array, $pos )
);
}
/**
* Insert array into an associative array after a specific key
*
* @source http://stackoverflow.com/questions/6501845/php-need-help-inserting-arrays-into-associative-arrays-at-given-keys
* @author potanin@UD
*/
static function array_insert_after( $array, $key, $new ) {
$array = (array)$array;
$keys = array_keys( $array );
$pos = (int)array_search( $key, $keys ) + 1;
return array_merge(
array_slice( $array, 0, $pos ),
$new,
array_slice( $array, $pos )
);
}
/**
* Attemp to convert a plural US word into a singular.
*
* @todo API Service Candidate since we ideally need a dictionary reference.
* @author potanin@UD
*/
static function depluralize( $word ) {
$rules = array( 'ss' => false, 'os' => 'o', 'ies' => 'y', 'xes' => 'x', 'oes' => 'o', 'ies' => 'y', 'ves' => 'f', 's' => '' );
foreach ( array_keys( $rules ) as $key ) {
if ( substr( $word, ( strlen( $key ) * -1 ) ) != $key )
continue;
if ( $key === false )
return $word;
return substr( $word, 0, strlen( $word ) - strlen( $key ) ) . $rules[ $key ];
}
return $word;
}
/**
* Convert bytes into the logical unit of measure based on size.
*
* @source Flawless
* @since 1.0.0
* @author potanin@UD
*/
static function format_bytes( $bytes, $precision = 2 ) {
$kilobyte = 1024;
$megabyte = $kilobyte * 1024;
$gigabyte = $megabyte * 1024;
$terabyte = $gigabyte * 1024;
if ( ( $bytes >= 0 ) && ( $bytes < $kilobyte ) ) {
return $bytes . ' B';
} elseif ( ( $bytes >= $kilobyte ) && ( $bytes < $megabyte ) ) {
return round( $bytes / $kilobyte, $precision ) . ' KB';
} elseif ( ( $bytes >= $megabyte ) && ( $bytes < $gigabyte ) ) {
return round( $bytes / $megabyte, $precision ) . ' MB';
} elseif ( ( $bytes >= $gigabyte ) && ( $bytes < $terabyte ) ) {
return round( $bytes / $gigabyte, $precision ) . ' GB';
} elseif ( $bytes >= $terabyte ) {
return round( $bytes / $terabyte, $precision ) . ' TB';
} else {
return $bytes . ' B';
}
}
/**
* Used to enable/disable/print SQL log
*
* Usage:
* self::sql_log( 'enable' );
* self::sql_log( 'disable' );
* $queries= self::sql_log( 'print_log' );
*
* @since 0.1.0
*/
static function sql_log( $action = 'attach_filter' ) {
global $wpdb;
if ( !in_array( $action, array( 'enable', 'disable', 'print_log' ) ) ) {
$wpdb->ud_queries[ ] = array( $action, $wpdb->timer_stop(), $wpdb->get_caller() );
return $action;
}
if ( $action == 'enable' ) {
add_filter( 'query', array( 'UD_API', 'sql_log' ), 75 );
}
if ( $action == 'disable' ) {
remove_filter( 'query', array( 'UD_API', 'sql_log' ), 75 );
}
if ( $action == 'print_log' ) {
$result = array();
foreach ( (array)$wpdb->ud_queries as $query ) {
$result[ ] = $query[ 0 ] ? $query[ 0 ] . ' (' . $query[ 1 ] . ')' : $query[ 2 ];
}
return $result;
}
}
/**
* Helpder function for figuring out if another specific function is a predecesor of current function.
*
* @since 1.0.0
* @author potanin@UD
*/
static function _backtrace_function( $function = false ) {
foreach ( debug_backtrace() as $step ) {
if ( $function && $step[ 'function' ] == $function ) {
return true;
}
}
}
/**
* Helpder function for figuring out if a specific file is a predecesor of current file.
*
* @since 1.0.0
* @author potanin@UD
*/
static function _backtrace_file( $file = false ) {
foreach ( debug_backtrace() as $step ) {
if ( $file && basename( $step[ 'file' ] ) == $file ) {
return true;
}
}
}
/**
* Parse standard WordPress readme file
*
* @author potanin@UD
*/
static function parse_readme( $readme_file = false ) {
if ( !$readme_file || !is_file( $readme_file ) ) {
return false;
}
$_api_response = self::get_service( array(
'service' => 'parser',
'args' => array(
'string' => file_get_contents( $readme_file ),
'type' => 'readme' )
) );
if ( is_wp_error( $_api_response ) ) {
return false;
} else {
return is_wp_error( $_api_response ) ? false : $_api_response;
}
}
/**
* Fixed serialized arrays which sometimes get messed up in WordPress
*
* @source http://shauninman.com/archive/2008/01/08/recovering_truncated_php_serialized_arrays
*/
static function repair_serialized_array( $serialized ) {
$tmp = preg_replace( '/^a:\d+:\{/', '', $serialized );
return self::repair_serialized_array_callback( $tmp ); // operates on and whittles down the actual argument
}
/**
* The recursive function that does all of the heavy lifing. Do not call directly.
*
*
*/
static function repair_serialized_array_callback( &$broken ) {
$data = array();
$index = null;
$len = strlen( $broken );
$i = 0;
while ( strlen( $broken ) ) {
$i++;
if ( $i > $len ) {
break;
}
if ( substr( $broken, 0, 1 ) == '}' ) // end of array
{
$broken = substr( $broken, 1 );
return $data;
} else {
$bite = substr( $broken, 0, 2 );
switch ( $bite ) {
case 's:': // key or value
$re = '/^s:\d+:"([^\"]*)";/';
if ( preg_match( $re, $broken, $m ) ) {
if ( $index === null ) {
$index = $m[ 1 ];
} else {
$data[ $index ] = $m[ 1 ];
$index = null;
}
$broken = preg_replace( $re, '', $broken );
}
break;
case 'i:': // key or value
$re = '/^i:(\d+);/';
if ( preg_match( $re, $broken, $m ) ) {
if ( $index === null ) {
$index = (int)$m[ 1 ];
} else {
$data[ $index ] = (int)$m[ 1 ];
$index = null;
}
$broken = preg_replace( $re, '', $broken );
}
break;
case 'b:': // value only
$re = '/^b:[01];/';
if ( preg_match( $re, $broken, $m ) ) {
$data[ $index ] = (bool)$m[ 1 ];
$index = null;
$broken = preg_replace( $re, '', $broken );
}
break;
case 'a:': // value only
$re = '/^a:\d+:\{/';
if ( preg_match( $re, $broken, $m ) ) {
$broken = preg_replace( '/^a:\d+:\{/', '', $broken );
$data[ $index ] = self::repair_serialized_array_callback( $broken );
$index = null;
}
break;
case 'N;': // value only
$broken = substr( $broken, 2 );
$data[ $index ] = null;
$index = null;
break;
}
}
}
return $data;
}
/**
* Determine if an item is in array and return checked
*
* @since 0.5.0
*/
static function checked_in_array( $item, $array ) {
if ( is_array( $array ) && in_array( $item, $array ) ) {
echo ' checked="checked" ';
}
}
/**
* Check if the current WP version is older then given parameter $version.
* @param string $version
* @since 1.0.0
* @author peshkov@UD
*/
static function is_older_wp_version( $version = '' ) {
if ( empty( $version ) || (float)$version == 0 ) return false;
$current_version = get_bloginfo( 'version' );
/** Clear version numbers */
$current_version = preg_replace( "/^([0-9\.]+)-(.)+$/", "$1", $current_version );
$version = preg_replace( "/^([0-9\.]+)-(.)+$/", "$1", $version );
return ( (float)$current_version < (float)$version ) ? true : false;
}
/**
* Determine if any requested template exists and return path to it.
*
* @todo Merge with x_get_template_part() to support $slug and $name, as well as $path.
* @name array $name List of requested templates. Will be return the first found
* @path array $path [optional]. Method tries to find template in theme, but also it can be found in given list of pathes.
* @author peshkov@UD
* @version 1.0
*/
static function get_template_part( $name, $path = array(), $opts = array() ) {
$name = (array)$name;
$template = "";
/**
* Set default instance.
* Template can depend on instance. For example: facebook, PDF, etc.
*/
$instance = apply_filters( "ud::current_instance", "default" );
$opts = wp_parse_args( $opts, array(
'instance' => $instance,
) );
foreach ( $name as $n ) {
$n = "{$n}.php";
$template = locate_template( $n, false );
if ( empty( $template ) && !empty( $path ) ) {
foreach ( (array)$path as $p ) {
if ( file_exists( $p . "/" . $n ) ) {
$template = $p . "/" . $n;
break( 2 );
}
}
}
if ( !empty( $template ) ) break;
}
$template = apply_filters( "ud::template_part::{$opts['instance']}", $template, array( 'name' => $name, 'path' => $path, 'opts' => $opts ) );
WPP_F::console_log( $template, $instance );
return !empty( $template ) ? $template : false;
}
/**
* The goal of function is going through specific filters and return (or print) classes.
* This function should not be called directly.
* Every ud plugin/theme should have own short function ( wrapper ) for calling it. E.g., see: wpp_css().
* So, use it in template as: <div id="my_element" class="<?php wpp_css("{name_of_template}::my_element"); ?>"> </div>
*
* Arguments:
* - instance [string] - UD plugin|theme's slug. E.g.: wpp, denali, wpi, etc
* - element [string] - specific element in template which will use the current classes.
* Element should be called as {template}::{specific_name_of_element}. Where {template} is name of template,
* where current classes will be used. This standart is optional. You can set any element's name if you want.
* - classes [array] - set of classes which will be used for element.
* - return [boolean] - If false, the function prints all classes like 'class1 class2 class3'
*
* @param array $args
* @author peshkov@UD
* @version 0.1
*/
static function get_css_classes( $args = array() ) {
//** Set arguments */
$args = wp_parse_args( (array)$args, array(
'classes' => array(),
'instance' => '',
'element' => '',
'return' => false,
) );
extract( $args );
//** Cast (set correct types) to avoid issues */
if ( !is_array( $classes ) ) {
$classes = trim( $classes );
$classes = str_replace( ',', ' ', $classes );
$classes = explode( ' ', $classes );
}
foreach ( $classes as &$c ) $c = trim( $c );
$instance = (string)$instance;
$element = (string)$element;
//** Now go through the filters */
$classes = apply_filters( "$instance::css::$element", $classes, $args );
if ( !$return ) {
echo implode( " ", (array)$classes );
}
return $classes;
}
/**
* Return simple array of column tables in a table
*
* @version 0.6
*/
static function get_column_names( $table ) {
global $wpdb;
$table_info = $wpdb->get_results( "SHOW COLUMNS FROM $table" );
if ( empty( $table_info ) ) {
return array();
}
foreach ( (array)$table_info as $row ) {
$columns[ ] = $row->Field;
}
return $columns;
}
/**
* Creates a Quick-Access table for post
*
* @param $table_name Can be anything but for consistency should use Post Type slug.
* @param $args
* - update - Either existing Post Type or ID of a post. Post Type will trigger update for all posts.
*
* @author potanin@UD
* @version 0.6
*/
static function update_qa_table( $table_name = false, $args = false ) {
global $wpdb;
$args = array_filter( wp_parse_args( $args, array(
'table_name' => $wpdb->base_prefix . 'ud_qa_' . $table_name,
'drop_current' => false,
'attributes' => array(),
'update' => array(),
'debug' => false
) ) );
$return = array();
if ( $args[ 'debug' ] ) {
self::sql_log( 'enable' );
}
/* Remove current table */
if ( $args[ 'drop_current' ] ) {
$wpdb->query( "DROP TABLE {$args[table_name]}" );
}
/* Check if this table exists */
if ( $wpdb->get_var( "SHOW TABLES LIKE '{$args[table_name]}' " ) != $args[ 'table_name' ] ) {
$wpdb->query( "CREATE TABLE {$args[table_name]} (
post_id mediumint(9) NOT NULL,
ud_last_update timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY post_id ( post_id ) ) ENGINE = MyISAM" );
}
$args[ 'current_columns' ] = self::get_column_names( $args[ 'table_name' ] );
/* Add attributes, if they don't exist, to table */
foreach ( (array)$args[ 'attributes' ] as $attribute => $type ) {
$type = is_array( $type ) ? $type[ 'type' ] : $type;
if ( $type == 'taxonomy' ) {
$wpdb->query( "ALTER TABLE {$args[table_name] } ADD {$attribute}_ids VARCHAR( 512 ) NULL DEFAULT NULL, COMMENT '{$type}', ADD FULLTEXT INDEX ( {$attribute}_ids ) ;" );
$wpdb->query( "ALTER TABLE {$args[table_name] } ADD {$attribute} VARCHAR( 512 ) NULL DEFAULT NULL, COMMENT '{$type}', ADD FULLTEXT INDEX ( {$attribute} )" );
} else {
$wpdb->query( "ALTER TABLE {$args[table_name] } ADD {$attribute} VARCHAR( 512 ) NULL DEFAULT NULL, COMMENT '{$type}', ADD FULLTEXT INDEX ( {$attribute} )" );
}
}
/* If no update requested, leave */
if ( !$args[ 'update' ] ) {
return true;
}
/* Determine update type and initiate updater */
foreach ( (array)$args[ 'update' ] as $update_type ) {
if ( is_numeric( $update_type ) ) {
$insert_id = self::update_qa_table_item( $update_type, $args );
if ( !is_wp_error( $insert_id ) ) {
$return[ 'updated' ][ ] = $insert_id;
} else {
$return[ 'error' ][ ] = $insert_id->get_error_message();
}
}
if ( post_type_exists( $update_type ) ) {
foreach ( (object)$wpdb->get_col( "SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type = '{$update_type}' " ) as $post_id ) {
$insert_id = self::update_qa_table_item( $post_id, $args );
if ( !is_wp_error( $insert_id ) ) {
$return[ 'updated' ][ ] = $insert_id;
} else {
$return[ 'error' ][ ] = $insert_id->get_error_message();
}
}
}
}
if ( $args[ 'debug' ] ) {
self::sql_log( 'disable' );
$return[ 'debug' ] = self::sql_log( 'print_log' );
}
return $return;
}
/**
* Update post data in QA table
*
* @author potanin@UD
* @version 0.6
*/
static function update_qa_table_item( $post_id = false, $args ) {
global $wpdb;
$types = array();
/* Organize requested meta by type */
foreach ( (array)$args[ 'attributes' ] as $attribute_key => $type ) {
$type = is_array( $type ) ? $type[ 'type' ] : $type;
$types[ $type ][ ] = $attribute_key;
$types[ $type ] = array_filter( (array)$types[ $type ] );
}
/* Get Primary Data */
if ( !empty( $types[ 'primary' ] ) ) {
$insert = $wpdb->get_row( "SELECT ID as post_id, " . implode( ', ', $types[ 'primary' ] ) . " FROM {$wpdb->posts} WHERE ID = {$post_id} ", ARRAY_A );
}
/* Get Meta Data */
if ( !empty( $types[ 'post_meta' ] ) ) {
foreach ( (object)$wpdb->get_results( "SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = {$post_id} AND meta_key IN ( '" . implode( "', '", $types[ 'post_meta' ] ) . "' ); " ) as $row ) {
$insert[ $row->meta_key ] .= $row->meta_value . ',';
}
/* Remove leading/trailing commas */
foreach ( (array)$types[ 'post_meta' ] as $type ) {
$insert[ $type ] = trim( $insert[ $type ], ',' );
}
}
if ( !empty( $types[ 'taxonomy' ] ) ) {
foreach ( (object)$wpdb->get_results( "
SELECT {$wpdb->term_taxonomy}.term_id, taxonomy, name FROM {$wpdb->terms}
LEFT JOIN {$wpdb->term_taxonomy} on {$wpdb->terms}.term_id = {$wpdb->term_taxonomy}.term_id
LEFT JOIN {$wpdb->term_relationships} on {$wpdb->term_relationships}.term_taxonomy_id = {$wpdb->term_taxonomy}.term_taxonomy_id
WHERE object_id = $post_id AND taxonomy IN ( '" . implode( "', '", $types[ 'taxonomy' ] ) . "' ); " ) as $row ) {
$insert[ $row->taxonomy . '_ids' ] .= $row->term_id . ',';
$insert[ $row->taxonomy ] .= $row->name . ',';
}
/* Loop again, removing trailing/leading commas */
foreach ( (array)$types[ 'taxonomy' ] as $taxonomy ) {
$insert[ $taxonomy ] = trim( $insert[ $taxonomy ], ',' );
$insert[ $taxonomy . '_ids' ] = trim( $insert[ $taxonomy . '_ids' ], ',' );
}
}
$insert = array_filter( (array)$insert );
if ( $wpdb->get_var( "SELECT post_id FROM {$args['table_name']} WHERE post_id = {$post_id} " ) == $post_id ) {
$wpdb->update( $args[ 'table_name' ], $insert, array( 'post_id' => $post_id ) );
$response = $post_id;
} else {
if ( $wpdb->insert( $args[ 'table_name' ], $insert ) ) {
$response = $wpdb->insert_id;
}
}
return $response ? $response : new WP_Error( 'error', $wpdb->print_error() ? $wpdb->print_error() : __( 'Unknown error.' . $wpdb->last_query ) );
}
/**
* Merges any number of arrays / parameters recursively,
*
* Replacing entries with string keys with values from latter arrays.
* If the entry or the next value to be assigned is an array, then it
* automagically treats both arguments as an array.
* Numeric entries are appended, not replaced, but only if they are
* unique
*
* @source http://us3.php.net/array_merge_recursive
* @version 0.4
*/
static function array_merge_recursive_distinct() {
$arrays = func_get_args();
$base = array_shift( $arrays );
if ( !is_array( $base ) ) $base = empty( $base ) ? array() : array( $base );
foreach ( (array)$arrays as $append ) {
if ( !is_array( $append ) ) $append = empty( $append ) ? array() : array( $append );
foreach ( (array)$append as $key => $value ) {
if ( !array_key_exists( $key, $base ) and !is_numeric( $key ) ) {
$base[ $key ] = $append[ $key ];
continue;
}
if ( @is_array( $value ) && isset( $base[ $key ] ) && isset( $append[ $key ] ) && is_array( $base[ $key ] ) && is_array( $append[ $key ] ) ) {
$base[ $key ] = self::array_merge_recursive_distinct( $base[ $key ], $append[ $key ] );
} else if ( is_numeric( $key ) ) {
if ( !in_array( $value, $base ) ) $base[ ] = $value;
} else {
$base[ $key ] = $value;
}
}
}
return $base;
}
/**
* Returns a URL to a post object based on passed variable.
*
* If its a number, then assumes its the id, If it resembles a slug, then get the first slug match.
*
* @since 1.0
* @param string $title A page title, although ID integer can be passed as well
* @return string The page's URL if found, otherwise the general blog URL
*/
static function post_link( $title = false ) {
global $wpdb;
if ( !$title )
return get_bloginfo( 'url' );
if ( is_numeric( $title ) )
return get_permalink( $title );
if ( $id = $wpdb->get_var( "SELECT ID FROM $wpdb->posts WHERE post_name = '$title' AND post_status='publish'" ) )
return get_permalink( $id );
if ( $id = $wpdb->get_var( "SELECT ID FROM $wpdb->posts WHERE LOWER(post_title) = '" . strtolower( $title ) . "' AND post_status='publish'" ) )
return get_permalink( $id );
}
/**
* Add an entry to the plugin-specifig log.
*
* Creates log if one does not exist.
*
* = USAGE =
* self::log( "Settings updated" );
*
*/
static function log( $message = false, $args = array() ) {
$args = wp_parse_args( $args, array(
'type' => 'default',
'object' => false,
'prefix' => 'ud',
) );
extract( $args );
$log = "{$prefix}_log";
if ( !did_action( 'init' ) ) {
_doing_it_wrong( __FUNCTION__, sprintf( __( 'You cannot call UD_API::log() before the %1$s hook, since the current user is not yet known.' ), 'init' ), '3.4' );
return false;
}
$current_user = wp_get_current_user();
$this_log = get_option( $log );
if ( empty( $this_log ) ) {
$this_log = array();
$entry = array(
'time' => time(),
'message' => __( 'Log Started.', UD_API_Transdomain ),
'user' => $current_user->ID,
'type' => $type
);
}
if ( $message ) {
$entry = array(
'time' => time(),
'message' => $message,
'user' => $type == 'system' ? 'system' : $current_user->ID,
'type' => $type,
'object' => $object
);
}
if ( !is_array( $entry ) ) {
return false;
}
array_push( $this_log, $entry );
$this_log = array_filter( $this_log );
update_option( $log, $this_log );
return true;
}
/**
* Used to get the current plugin's log created via UD class
*
* If no log exists, it creates one, and then returns it in chronological order.
*
* Example to view log:
* <code>
* print_r( self::get_log() );
* </code>
*
* $param string Event description
* @uses get_option()
* @uses update_option()
* @return array Using the get_option function returns the contents of the log.
*
*/
static function get_log( $args = false ) {
$args = wp_parse_args( $args, array(
'limit' => 20,
'prefix' => 'ud'
) );
extract( $args );
$log = "{$prefix}_log";
$this_log = get_option( $log );
if ( empty( $this_log ) ) {
$this_log = self::log( false, array( 'prefix' => $prefix ) );
}
$entries = (array)get_option( $log );
$entries = array_reverse( $entries );
$entries = array_slice( $entries, 0, $args[ 'args' ] ? $args[ 'args' ] : 10 );
return $entries;
}
/**
* Delete UD log for this plugin.
*
* @uses update_option()
*/
static function delete_log( $args = array() ) {
$args = wp_parse_args( $args, array(
'prefix' => 'ud'
) );
extract( $args );
$log = "{$prefix}_log";
delete_option( $log );
}
/**
* Creates Admin Menu page for UD Log
*
* @todo Need to make sure this will work if multiple plugins utilize the UD classes
* @see function show_log_page
* @since 1.0
* @uses add_action() Calls 'admin_menu' hook with an anonymous ( lambda-style ) function which uses add_menu_page to create a UI Log page
*/
static function add_log_page() {
if ( did_action( 'admin_menu' ) ) {
_doing_it_wrong( __FUNCTION__, sprintf( __( 'You cannot call UD_API::add_log_page() after the %1$s hook.' ), 'init' ), '3.4' );
return false;
}
add_action( 'admin_menu', create_function( '', "add_menu_page( __( 'Log' ,UD_API_Transdomain ), __( 'Log',UD_API_Transdomain ), 10, 'ud_log', array( 'UD_API', 'show_log_page' ) );" ) );
}
/**
* !DISABLED. Displays the UD UI log page.
*
* @todo Add button or link to delete log
* @todo Add nonce to clear_log functions
* @todo Should be refactored to implement adding LOG tabs for different instances (wpp, wpi, wp-crm). peshkov@UD
*
* @since 1.0.0
*/
static function show_log_page() {
if ( $_REQUEST[ 'ud_action' ] == 'clear_log' ) {
self::delete_log();
}
$output = array();
$output[ ] = '<style type="text/css">.ud_event_row b { background:none repeat scroll 0 0 #F6F7DC; padding:2px 6px;}</style>';
$output[ ] = '<div class="wrap">';
$output[ ] = '<h2>' . __( 'Log Page for', UD_API_Transdomain ) . ' ud_log ';
$output[ ] = '<a href="' . admin_url( "admin.php?page=ud_log&ud_action=clear_log" ) . '" class="button">' . __( 'Clear Log', UD_API_Transdomain ) . '</a></h2>';
//die( '<pre>' . print_r( self::get_log() , true ) . '</pre>' );
$output[ ] = '<table class="widefat"><thead><tr>';
$output[ ] = '<th style="width: 150px">' . __( 'Timestamp', UD_API_Transdomain ) . '</th>';
$output[ ] = '<th>' . __( 'Type', UD_API_Transdomain ) . '</th>';
$output[ ] = '<th>' . __( 'Event', UD_API_Transdomain ) . '</th>';
$output[ ] = '<th>' . __( 'User', UD_API_Transdomain ) . '</th>';
$output[ ] = '<th>' . __( 'Related Object', UD_API_Transdomain ) . '</th>';
$output[ ] = '</tr></thead>';
$output[ ] = '<tbody>';
foreach ( (array)self::get_log() as $event ) {
$output[ ] = '<tr class="ud_event_row">';
$output[ ] = '<td>' . self::nice_time( $event[ 'time' ] ) . '</td>';
$output[ ] = '<td>' . $event[ 'type' ] . '</td>';
$output[ ] = '<td>' . $event[ 'message' ] . '</td>';
$output[ ] = '<td>' . ( is_numeric( $event[ 'user' ] ) ? get_userdata( $event[ 'user' ] )->display_name : __( 'None' ) ) . '</td>';
$output[ ] = '<td>' . $event[ 'object' ] . '</td>';
$output[ ] = '</tr>';
}
$output[ ] = '</tbody></table>';
$output[ ] = '</div>';
echo implode( '', (array)$output );
}
/**
* Turns a passed string into a URL slug
*
* Argument 'check_existance' will make the function check if the slug is used by a WordPress post
*
* @param string $content
* @param string $args Optional list of arguments to overwrite the defaults.
* @since 1.0
* @uses add_action() Calls 'admin_menu' hook with an anonymous (lambda-style) function which uses add_menu_page to create a UI Log page
* @return string
*/
static function create_slug( $content, $args = false ) {
$defaults = array(
'separator' => '-',
'check_existance' => false
);
extract( wp_parse_args( $args, $defaults ), EXTR_SKIP );
$content = preg_replace( '~[^\\pL0-9_]+~u', $separator, $content ); // substitutes anything but letters, numbers and '_' with separator
$content = trim( $content, $separator );
$content = iconv( "utf-8", "us-ascii//TRANSLIT", $content ); // TRANSLIT does the whole job
$content = strtolower( $content );
$slug = preg_replace( '~[^-a-z0-9_]+~', '', $content ); // keep only letters, numbers, '_' and separator
return $slug;
}
/**
* Convert a slug to a more readable string
*
* @since 1.3
* @return string
*/
static function de_slug( $string ) {
return ucwords( str_replace( "_", " ", $string ) );
}
/**
* Returns location information from Google Maps API call
*
* @version 1.1
* @since 1.0.0
* @return object
*/
static function geo_locate_address( $address = false, $localization = "en", $return_obj_on_fail = false, $latlng = false ) {
if ( !$address && !$latlng ) {
return false;
}
if ( is_array( $address ) ) {
return false;
}
$return = new stdClass();
$address = urlencode( $address );
$url = str_replace( " ", "+", "http://maps.google.com/maps/api/geocode/json?" . ( ( is_array( $latlng ) ) ? "latlng={$latlng['lat']},{$latlng['lng']}" : "address={$address}" ) . "&sensor=true&language={$localization}" );
$obj = ( json_decode( wp_remote_fopen( $url ) ) );
if ( $obj->status != "OK" ) {
// Return Google result if needed instead of just false
if ( $return_obj_on_fail ) {
return $obj;
}
return false;
}
$results = $obj->results;
$results_object = $results[ 0 ];
$geometry = $results_object->geometry;
$return->formatted_address = $results_object->formatted_address;
$return->latitude = $geometry->location->lat;
$return->longitude = $geometry->location->lng;
// Cycle through address component objects picking out the needed elements, if they exist
foreach ( (array)$results_object->address_components as $ac ) {
// types is returned as an array, look through all of them
foreach ( (array)$ac->types as $type ) {
switch ( $type ) {
case 'street_number':
$return->street_number = $ac->long_name;
break;
case 'route':
$return->route = $ac->long_name;
break;
case 'locality':
$return->city = $ac->long_name;
break;
case 'administrative_area_level_3':
if ( empty( $return->city ) )
$return->city = $ac->long_name;
break;
case 'administrative_area_level_2':
$return->county = $ac->long_name;
break;
case 'administrative_area_level_1':
$return->state = $ac->long_name;
$return->state_code = $ac->short_name;
break;
case 'country':
$return->country = $ac->long_name;
$return->country_code = $ac->short_name;
break;
case 'postal_code':
$return->postal_code = $ac->long_name;
break;
case 'sublocality':
$return->district = $ac->long_name;
break;
}
}
}
//** API Callback */
$return = apply_filters( 'ud::geo_locate_address', $return, $results_object, $address, $localization );
//** API Callback (Legacy) - If no actions have been registered for the new hook, we support the old one. */
if ( !has_action( 'ud::geo_locate_address' ) ) {
$return = apply_filters( 'geo_locate_address', $return, $results_object, $address, $localization );
}
return $return;
}
/**
* Returns avaliability of Google's Geocoding Service based on time of last returned status OVER_QUERY_LIMIT
* @uses const self::blocking_for_new_validation_interval
* @uses option ud::geo_locate_address_last_OVER_QUERY_LIMIT
* @param type $update used to set option value in time()
* @return boolean
* @author odokienko@UD
*/
static function available_address_validation( $update = false ) {
global $wpdb;
if ( empty( $update ) ) {
$last_error = (int)get_option( 'ud::geo_locate_address_last_OVER_QUERY_LIMIT' );
if ( !empty( $last_error ) && ( time() - (int)$last_error ) < 2 ) {
sleep( 1 );
}
/*if (!empty($last_error) && (((int)$last_error + self::blocking_for_new_validation_interval ) > time()) ){
sleep(1);
//return false;
}else{
//** if last success validation was less than a seccond ago we will wait for 1 seccond
$last = $wpdb->get_var("
SELECT if(DATE_ADD(FROM_UNIXTIME(pm.meta_value), INTERVAL 1 SECOND) < NOW(), 0, UNIX_TIMESTAMP()-pm.meta_value) LAST
FROM {$wpdb->postmeta} pm
WHERE pm.meta_key='wpp::last_address_validation'
LIMIT 1
");
usleep((int)$last);
}*/
} else {
update_option( 'ud::geo_locate_address_last_OVER_QUERY_LIMIT', time() );
return false;
}
return true;
}
/**
* Returns date and/or time using the WordPress date or time format, as configured.
*
* @param string $time Date or time to use for calculation.
* @param string $args List of arguments to overwrite the defaults.
*
* @uses wp_parse_args()
* @uses get_option()
* @return string|bool Returns formatted date or time, or false if no time passed.
* @updated 3.0
*/
static function nice_time( $time = false, $args = false ) {
$args = wp_parse_args( $args, array(
'format' => 'date_and_time'
) );
if ( !$time ) {
return false;
}
if ( $args[ 'format' ] == 'date' ) {
return date( get_option( 'date_format' ), $time );
}
if ( $args[ 'format' ] == 'time' ) {
return date( get_option( 'time_format' ), $time );
}
if ( $args[ 'format' ] == 'date_and_time' ) {
return date( get_option( 'date_format' ), $time ) . ' ' . date( get_option( 'time_format' ), $time );
}
return false;
}
/**
* Depreciated. Displays the numbers of days elapsed between a provided date and today.
*
* @deprecated 3.4.0
* @author potanin@UD
*/
static function days_since( $from, $to = false ) {
_deprecated_function( __FUNCTION__, '3.4', 'human_time_diff' );
human_time_diff( $from, $to );
}
/**
* Depreciated.
*
* @deprecated 3.4.0
* @author potanin@UD
*/
static function is_url( $url ) {
_deprecated_function( __FUNCTION__, '3.4', 'esc_url' );
return preg_match( '|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $url );
}
/**
* Wrapper function to send notification with WP-CRM or without one
* @param mixed $args['user']
* @param sting $args['trigger_action']
* @param sting $args['data'] aka $notification_data
* @param sting $args['crm_log_message']
* @param sting $args['subject'] using in email notification
* @param sting $args['message'] using in email notification
* @uses self::replace_data()
* @uses wp_crm_send_notification()
* @return boolean false if notification was not sent successfully
* @autor odokienko@UD
*/
static function send_notification( $args = array() ) {
$args = wp_parse_args( $args, array(
'ignore_wp_crm' => false,
'user' => false,
'trigger_action' => false,
'data' => array(),
'message' => '',
'subject' => '',
'crm_log_message' => ''
) );
if ( is_numeric( $args[ 'user' ] ) ) {
$args[ 'user' ] = get_user_by( 'id', $args[ 'user' ] );
} elseif ( filter_var( $args[ 'user' ], FILTER_VALIDATE_EMAIL ) ) {
$args[ 'user' ] = get_user_by( 'email', $args[ 'user' ] );
} elseif ( is_string( $args[ 'user' ] ) ) {
$args[ 'user' ] = get_user_by( 'login', $args[ 'user' ] );
}
if ( !is_object( $args[ 'user' ] ) || empty( $args[ 'user' ]->data->user_email ) ) {
return false;
}
if ( function_exists( 'wp_crm_send_notification' ) &&
empty( $args[ 'ignore_wp_crm' ] )
) {
if ( !empty( $args[ 'crm_log_message' ] ) ) {
wp_crm_add_to_user_log( $args[ 'user' ]->ID, self::replace_data( $args[ 'crm_log_message' ], $args[ 'data' ] ) );
}
if ( !empty( $args[ 'trigger_action' ] ) ) {
$notifications = WP_CRM_F::get_trigger_action_notification( $args[ 'trigger_action' ] );
if ( !empty( $notifications ) ) {
return wp_crm_send_notification( $args[ 'trigger_action' ], $args[ 'data' ] );
}
}
}
if ( empty( $args[ 'message' ] ) ) {
return false;
}
return wp_mail( $args[ 'user' ]->data->user_email, self::replace_data( $args[ 'subject' ], $args[ 'data' ] ), self::replace_data( $args[ 'message' ], $args[ 'data' ] ) );
}
/**
* Replace in $str all entries of keys of the given $values
* where each key will be rounded by $brackets['left'] and $brackets['right']
* with the relevant values of the $values
* @param string|array $str
* @param array $values
* @param array $brackets
* @return string|array
* @author odokienko@UD
*/
static function replace_data( $str = '', $values = array(), $brackets = array( 'left' => '[', 'right' => ']' ) ) {
$values = (array)$values;
$replacements = array_keys( $values );
array_walk( $replacements, create_function( '&$val', '$val = "' . $brackets[ 'left' ] . '".$val."' . $brackets[ 'right' ] . '";' ) );
return str_replace( $replacements, array_values( $values ), $str );
}
/**
* Gets complicated html entity e.g. Table and ou|ol
* and removes whitespace characters include new line.
* we should to do this before use nl2br
*
* @author odokienko@UD
*/
static function cleanup_extra_whitespace( $content ) {
$content = preg_replace_callback( '~<(?:table|ul|ol )[^>]*>.*?<\/( ?:table|ul|ol )>~ims', create_function( '$matches', 'return preg_replace(\'~>[\s]+<((?:t[rdh]|li|\/tr|/table|/ul ))~ims\',\'><$1\',$matches[0]);' ), $content );
return $content;
}
/**
* Wrapper for json_encode function.
* Emulates JSON_UNESCAPED_UNICODE.
*
* @param type $arr
* @return JSON
* @author peshkov@UD
*/
function json_encode( $arr ) {
// convmap since 0x80 char codes so it takes all multibyte codes (above ASCII 127). So such characters are being "hidden" from normal json_encoding
array_walk_recursive( $arr, create_function( '&$item, $key', 'if (is_string($item)) $item = mb_encode_numericentity($item, array (0x80, 0xffff, 0, 0xffff), "UTF-8");' ) );
return mb_decode_numericentity( json_encode( $arr ), array( 0x80, 0xffff, 0, 0xffff ), 'UTF-8' );
}
}