API Docs for: 1.38.2
Show:

File: core/ud_api.php

<?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' );
  }

}