mihdan
7/24/2018 - 9:37 PM

WordPress Geo Products. Depends on ACF for location meta. Replaces product search to include distance query.

WordPress Geo Products. Depends on ACF for location meta. Replaces product search to include distance query.

class WP_Query_Geo extends WP_Query {
  private $lat = NULL;
  private $lng = NULL;
  private $distance = NULL;

  /**
   * Constructor - adds necessary filters to extend Query hooks
   */
  public function __construct( $args = array() ) {
    // Extract Latitude
    if(!empty($args['lat'])) {
      $this->lat = $args['lat'];
    }
    // Extract Longitude
    if(!empty($args['lng'])) {
      $this->lng = $args['lng'];
    }
    // Extract Longitude
    if(!empty($args['distance'])) {
      $this->distance = $args['distance'];
    }
    // unset lat/long
    unset($args['lat'], $args['lng'], $args['distance']);

    add_filter('posts_fields', array($this, 'posts_fields'), 10, 2);
    add_filter('posts_join', array($this, 'posts_join'), 10, 2);
    add_filter('posts_where', array($this, 'posts_where'), 10, 2);
    add_filter('posts_orderby', array($this, 'posts_orderby'), 10, 2);
    add_filter('posts_distinct', array($this, 'posts_distinct'), 10, 2);

    parent::query($args);

    // Remove filters so only WP_GeoQuery queries this way
    remove_filter('posts_fields', array($this, 'posts_fields'));
    remove_filter('posts_join', array($this, 'posts_join'));
    remove_filter('posts_where', array($this, 'posts_where'));
    remove_filter('posts_orderby', array($this, 'posts_orderby'));
    remove_filter('posts_distinct', array($this, 'posts_distinct'));
  }

  /**
   * Return only distinct results
   */
  public function posts_distinct() {
    return "DISTINCT";
  }

  /**
   * Selects the distance from a haversine formula
   */
  public function posts_fields($fields) {
    global $wpdb;
    if(!empty($this->lat) && !empty($this->lng)) {
      $fields .= sprintf(", ( 6371 * acos(
          cos( radians(%s) ) *
          cos( radians( latitude.meta_value ) ) *
          cos( radians( longitude.meta_value ) - radians(%s) ) +
          sin( radians(%s) ) *
          sin( radians( latitude.meta_value ) )
        ) ) AS distance ", $this->lat, $this->lng, $this->lat);
    }
    $fields .= ", latitude.meta_value AS latitude ";
    $fields .= ", longitude.meta_value AS longitude ";
    echo '<pre>FIELDS: ' . $fields . '</pre>';
    return $fields;
  }


  /**
   * Makes joins as necessary in order to select lat/long metadata
   */
  public function posts_join($join, $query) {
    global $wpdb;
    $join .= " INNER JOIN {$wpdb->postmeta} AS latitude ON {$wpdb->posts}.ID = latitude.post_id ";
    $join .= " INNER JOIN {$wpdb->postmeta} AS longitude ON {$wpdb->posts}.ID = longitude.post_id ";
    echo '<pre>JOIN: ' . $join . '</pre>';
    return $join;
  }

  /**
   * Adds where clauses to compliment joins
   */
  public function posts_where($where) {
    $where .= ' AND latitude.meta_key="lat" ';
    $where .= ' AND longitude.meta_key="lng" ';
    if(!empty($this->lat) && !empty($this->lng) && !empty($this->distance)) {
      if(is_numeric($this->distance)) {
        $where .= sprintf(' HAVING distance <= %s ', $this->distance);
      }
    }
    echo '<pre>WHERE: ' . $where . '</pre>';
    return $where;
  }
  
  /**
   * Adds where clauses to compliment joins
   */
  public function posts_orderby($orderby) {
    if(!empty($this->lat) && !empty($this->lng)) {
      $orderby = " distance ASC, " . $orderby;
    }
    return $orderby;
  }

}

// Override product search to include post_meta lat/lng, order by distance (if search provides location)
add_action('pre_get_posts','product_location_search_filter');
function product_location_search_filter( $query ) {
  if ( !is_admin() && is_post_type_archive('product') && $query->is_search ) {
    # How do I get search terms up in dis?
    $args = array(
      'post_type'       => $query->get('post_type') ,
      'posts_per_page'  => 20,
      'fields'          => 'all',
      'lat'             => $_REQUEST['lat'],
      'lng'             => $_REQUEST['lng'],
      'distance'        => $_REQUEST['dist'],
      # UNCOMMENTING THIS MAKES THE SKY FALL. But why?
      #'s' => $_REQUEST['s']
    );

    $geo_query = new WP_Query_Geo( $args );

    # Actually affect search query
    global $wp_query;
    $wp_query = $geo_query;
  }
}