<?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.1
*
*/
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' );
}
}