Merge branch 'wc-geolocation'

This commit is contained in:
Mike Jolley 2015-01-05 10:18:25 +00:00
commit f847b92a0f
13 changed files with 2190 additions and 44 deletions

View File

@ -55,7 +55,8 @@ module.exports = function( grunt ) {
'<%= dirs.js %>/admin/jquery.flot.resize.min.js': ['<%= dirs.js %>/admin/jquery.flot.resize.js'],
'<%= dirs.js %>/admin/jquery.flot.stack.min.js': ['<%= dirs.js %>/admin/jquery.flot.stack.js'],
'<%= dirs.js %>/admin/jquery.flot.time.min.js': ['<%= dirs.js %>/admin/jquery.flot.time.js'],
'<%= dirs.js %>/jquery-payment/jquery.payment.min.js': ['<%= dirs.js %>/jquery-payment/jquery.payment.js']
'<%= dirs.js %>/jquery-payment/jquery.payment.min.js': ['<%= dirs.js %>/jquery-payment/jquery.payment.js'],
'<%= dirs.js %>/jquery-blockui/jquery.blockUI.min.js': ['<%= dirs.js %>/jquery-blockui/jquery.blockUI.js']
}
},
frontend: {

View File

@ -1,6 +1,6 @@
/*!
* jQuery blockUI plugin
* Version 2.66.0-2013.10.09
* Version 2.70.0-2014.11.23
* Requires jQuery v1.7 or later
*
* Examples at: http://malsup.com/jquery/block/
@ -11,7 +11,6 @@
*
* Thanks to Amir-Hossein Sobhi for some excellent contributions!
*/
;(function() {
/*jshint eqeqeq:false curly:false latedef:false */
"use strict";
@ -107,7 +106,7 @@
});
};
$.blockUI.version = 2.66; // 2nd generation blocking at no extra cost!
$.blockUI.version = 2.70; // 2nd generation blocking at no extra cost!
// override these in your code to change the default behavior and style
$.blockUI.defaults = {
@ -426,7 +425,7 @@
if (msg)
lyr3.show();
if (opts.onBlock)
opts.onBlock();
opts.onBlock.bind(lyr3)();
}
// bind key and mouse events
@ -515,6 +514,7 @@
if (data && data.el) {
data.el.style.display = data.display;
data.el.style.position = data.position;
data.el.style.cursor = 'default'; // #59
if (data.parent)
data.parent.appendChild(data.el);
$el.removeData('blockUI.history');
@ -616,4 +616,4 @@
setup(jQuery);
}
})();
})();

File diff suppressed because one or more lines are too long

View File

@ -525,7 +525,7 @@ abstract class WC_Abstract_Order {
if ( 'base' === $tax_based_on ) {
$default = wc_get_default_location();
$default = wc_get_base_location();
$country = $default['country'];
$state = $default['state'];
$postcode = '';

View File

@ -44,12 +44,13 @@ return apply_filters( 'woocommerce_tax_settings', array(
array(
'title' => __( 'Default Customer Address:', 'woocommerce' ),
'id' => 'woocommerce_default_customer_address',
'desc_tip' => __( 'This option determines the customers default address (before they input their own).', 'woocommerce' ),
'default' => 'base',
'desc_tip' => __( 'This option determines the customers default address (before they input their details).', 'woocommerce' ),
'default' => 'geolocation',
'type' => 'select',
'options' => array(
'' => __( 'No address', 'woocommerce' ),
'base' => __( 'Shop base address', 'woocommerce' ),
'' => __( 'No address', 'woocommerce' ),
'base' => __( 'Shop base address', 'woocommerce' ),
'geolocation' => __( 'Geolocate address', 'woocommerce' ),
),
),

View File

@ -118,10 +118,8 @@ class WC_Countries {
* @return string
*/
public function get_base_country() {
$default = esc_attr( get_option('woocommerce_default_country') );
$country = ( ( $pos = strrpos( $default, ':' ) ) === false ) ? $default : substr( $default, 0, $pos );
return apply_filters( 'woocommerce_countries_base_country', $country );
$default = wc_get_base_location();
return apply_filters( 'woocommerce_countries_base_country', $default['country'] );
}
/**
@ -131,10 +129,8 @@ class WC_Countries {
* @return string
*/
public function get_base_state() {
$default = wc_clean( get_option( 'woocommerce_default_country' ) );
$state = ( ( $pos = strrpos( $default, ':' ) ) === false ) ? '' : substr( $default, $pos + 1 );
return apply_filters( 'woocommerce_countries_base_state', $state );
$default = wc_get_base_location();
return apply_filters( 'woocommerce_countries_base_state', $default['state'] );
}
/**

View File

@ -32,7 +32,7 @@ class WC_Customer {
*
* @var array
*/
protected $_data;
protected $_data = array();
/**
* Stores bool when data is changed
@ -49,15 +49,12 @@ class WC_Customer {
$this->_data = WC()->session->get( 'customer' );
if ( empty( $this->_data ) ) {
// Defaults
$this->_data = array(
'country' => esc_html( $this->get_default_country() ),
'state' => '',
'postcode' => '',
'city' => '',
'address' => '',
'address_2' => '',
'shipping_country' => esc_html( $this->get_default_country() ),
'shipping_state' => '',
'shipping_postcode' => '',
'shipping_city' => '',
'shipping_address' => '',
@ -65,6 +62,9 @@ class WC_Customer {
'is_vat_exempt' => false,
'calculated_shipping' => false
);
$this->_data['country'] = $this->_data['shipping_country'] = $this->get_default_country();
$this->_data['state'] = $this->_data['shipping_state'] = $this->get_default_state();
}
// When leaving or ending page load, store data
@ -116,8 +116,7 @@ class WC_Customer {
* @return string
*/
public function get_default_country() {
$default = wc_get_default_location();
$default = wc_get_customer_default_location();
return $default['country'];
}
@ -126,8 +125,7 @@ class WC_Customer {
* @return string
*/
public function get_default_state() {
$default = wc_get_default_location();
$default = wc_get_customer_default_location();
return $default['state'];
}
@ -170,7 +168,7 @@ class WC_Customer {
if ( $country ) {
$default = wc_get_default_location();
$default = wc_get_base_location();
if ( $default['country'] !== $country ) {
return true;
@ -329,8 +327,7 @@ class WC_Customer {
if ( $tax_based_on == 'base' ) {
$default = wc_get_default_location();
$default = wc_get_base_location();
$country = $default['country'];
$state = $default['state'];
$postcode = '';

View File

@ -0,0 +1,228 @@
<?php
/**
* Geolocation class.
*
* Handles geolocation and updating the geolocation database.
*
* This product uses GeoLite2 data created by MaxMind, available from http://www.maxmind.com
*
* @author WooThemes
* @category Admin
* @package WooCommerce/Classes
* @version 2.3.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Geolocation Class
*/
class WC_Geolocation {
/** URL to the geolocation database we're using */
const GEOLITE_DB = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz';
/** @var array API endpoints for looking up user IP address */
private static $ip_lookup_apis = array(
'icanhazip' => 'http://ipv4.icanhazip.com',
'ipify' => 'http://api.ipify.org/',
'ipecho' => 'http://ipecho.net/plain',
'ident' => 'http://v4.ident.me',
'whatismyipaddress' => 'http://bot.whatismyipaddress.com',
'ip.appspot' => 'http://ip.appspot.com'
);
/** @var array API endpoints for geolocating an IP address */
private static $geoip_apis = array(
'freegeoip' => 'https://freegeoip.net/json/%s',
'telize' => 'http://www.telize.com/geoip/%s',
'ip-api' => 'http://ip-api.com/json/%s',
'geoip-api.meteor' => 'http://geoip-api.meteor.com/lookup/%s'
);
/**
* Hook in tabs.
*/
public static function init() {
add_action( 'woocommerce_geoip_updater', array( __CLASS__, 'update_database' ) );
}
/**
* Get current user IP Address
* @return string
*/
public static function get_ip_address() {
return isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
}
/**
* Get user IP Address using a service
* @return string
*/
public static function get_external_ip_address() {
$transient_name = 'external_ip_address_' . self::get_ip_address();
$external_ip_address = get_transient( $transient_name );
if ( false === $external_ip_address ) {
$external_ip_address = '0.0.0.0';
$ip_lookup_services = apply_filters( 'woocommerce_geolocation_ip_lookup_apis', self::$ip_lookup_apis );
$ip_lookup_services_keys = array_keys( $ip_lookup_services );
shuffle( $ip_lookup_services_keys );
foreach ( $ip_lookup_services_keys as $service_name ) {
$service_endpoint = $ip_lookup_services[ $service_name ];
$response = wp_remote_get( $service_endpoint, array( 'timeout' => 2 ) );
if ( ! is_wp_error( $response ) && $response['body'] ) {
$external_ip_address = apply_filters( 'woocommerce_geolocation_ip_lookup_api_response', $response['body'], $service_name );
break;
}
}
set_transient( $transient_name, $external_ip_address, WEEK_IN_SECONDS );
}
return $external_ip_address;
}
/**
* Geolocate an IP address
* @param string $ip_address
* @return array
*/
public static function geolocate_ip( $ip_address = '', $fallback = true ) {
// If GEOIP is enabled in CloudFlare, we can use that (Settings -> CloudFlare Settings -> Settings Overview)
if ( ! empty( $_SERVER[ "HTTP_CF_IPCOUNTRY" ] ) ) {
$country_code = sanitize_text_field( strtoupper( $_SERVER["HTTP_CF_IPCOUNTRY"] ) );
} else {
$ip_address = $ip_address ? $ip_address : self::get_ip_address();
if ( file_exists( self::get_local_database_path() ) ) {
$country_code = self::geolocate_via_db( $ip_address );
} else {
$country_code = self::geolocate_via_api( $ip_address );
}
if ( ! $country_code && $fallback ) {
// May be a local environment - find external IP
return self::geolocate_ip( self::get_external_ip_address(), false );
}
}
return array(
'country' => $country_code,
'state' => ''
);
}
/**
* Path to our local db
* @return string
*/
private static function get_local_database_path() {
$upload_dir = wp_upload_dir();
return $upload_dir['basedir'] . '/GeoIP.dat';
}
/**
* Update geoip database. Adapted from https://wordpress.org/plugins/geoip-detect/.
*/
public static function update_database() {
require_once( ABSPATH . 'wp-admin/includes/file.php' );
$tmp_database = download_url( self::GEOLITE_DB );
$logger = new WC_Logger();
if ( ! is_wp_error( $tmp_database ) ) {
$gzhandle = @gzopen( $tmp_database, 'r' );
$handle = @fopen( self::get_local_database_path(), 'w' );
if ( $gzhandle && $handle ) {
while ( ( $string = gzread( $gzhandle, 4096 ) ) != false ) {
fwrite( $handle, $string, strlen( $string ) );
}
gzclose( $gzhandle );
fclose( $handle );
} else {
$logger->add( 'geolocation', 'Unable to open database file' );
}
@unlink( $tmp_database );
} else {
$logger->add( 'geolocation', 'Unable to download GeoIP Database: ' . $tmp_database->get_message() );
}
}
/**
* Use MAXMIND GeoLite database to geolocation the user.
* @param string $ip_address
* @return string
*/
private static function geolocate_via_db( $ip_address ) {
if ( ! class_exists( 'GeoIP' ) && ! class_exists( 'geoiprecord' ) ) {
include_once( 'libraries/geoip.php' );
}
$database = self::get_local_database_path();
$gi = geoip_open( $database, GEOIP_STANDARD );
$country_code = geoip_country_code_by_addr( $gi, $ip_address );
geoip_close( $gi );
return sanitize_text_field( strtoupper( $country_code ) );
}
/**
* Use APIs to Geolocate the user.
* @param string $ip_address
* @return string|bool
*/
private static function geolocate_via_api( $ip_address ) {
$country_code = get_transient( 'geoip_' . $ip_address );
if ( false === $country_code ) {
$geoip_services = apply_filters( 'woocommerce_geolocation_geoip_apis', self::$geoip_apis );
$geoip_services_keys = array_keys( $geoip_services );
shuffle( $geoip_services_keys );
foreach ( $geoip_services_keys as $service_name ) {
$service_endpoint = $geoip_services[ $service_name ];
$response = wp_remote_get( sprintf( $service_endpoint, $ip_address ), array( 'timeout' => 2 ) );
if ( ! is_wp_error( $response ) && $response['body'] ) {
$country_code = '';
switch ( $service_name ) {
case 'ip-api' :
$data = json_decode( $response['body'] );
$country_code = isset( $data->countryCode ) ? $data->countryCode : '';
break;
case 'geoip-api.meteor' :
$data = json_decode( $response['body'] );
$country_code = isset( $data->country ) ? $data->country : '';
break;
case 'freegeoip' :
case 'telize' :
$data = json_decode( $response['body'] );
$country_code = isset( $data->country_code ) ? $data->country_code : '';
break;
default :
$country_code = apply_filters( 'woocommerce_geolocation_geoip_response_' . $service_name, '', $response['body'] );
break;
}
$country_code = sanitize_text_field( strtoupper( $country_code ) );
if ( $country_code ) {
break;
}
}
}
set_transient( 'geoip_' . $ip_address, $country_code, WEEK_IN_SECONDS );
}
return $country_code;
}
}
WC_Geolocation::init();

View File

@ -125,6 +125,7 @@ class WC_Install {
// Flush rules after install
flush_rewrite_rules();
delete_transient( 'wc_attribute_taxonomies' );
// Redirect to welcome screen
set_transient( '_wc_activation_redirect', 1, HOUR_IN_SECONDS );
@ -160,6 +161,7 @@ class WC_Install {
wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
wp_clear_scheduled_hook( 'woocommerce_cleanup_sessions' );
wp_clear_scheduled_hook( 'woocommerce_language_pack_updater_check' );
wp_clear_scheduled_hook( 'woocommerce_geoip_updater' );
$ve = get_option( 'gmt_offset' ) > 0 ? '+' : '-';
@ -173,6 +175,8 @@ class WC_Install {
wp_schedule_event( time(), 'twicedaily', 'woocommerce_cleanup_sessions' );
wp_schedule_single_event( time(), 'woocommerce_language_pack_updater_check' );
wp_schedule_single_event( time(), 'woocommerce_geoip_updater' );
wp_schedule_event( strtotime( 'first tuesday of next month' ), 'monthly', 'woocommerce_geoip_updater' );
}
/**

View File

@ -337,9 +337,9 @@ class WC_Tax {
public static function get_tax_location( $tax_class = '' ) {
$location = array();
if ( defined( 'WOOCOMMERCE_CHECKOUT' ) || ( ! empty( WC()->customer ) && WC()->customer->has_calculated_shipping() ) ) {
if ( ! empty( WC()->customer ) ) {
$location = WC()->customer->get_taxable_address();
} elseif ( wc_prices_include_tax() || get_option( 'woocommerce_default_customer_address' ) == 'base' ) {
} elseif ( wc_prices_include_tax() || 'base' === get_option( 'woocommerce_default_customer_address' ) ) {
$location = array(
WC()->countries->get_base_country(),
WC()->countries->get_base_state(),

1881
includes/libraries/geoip.php Executable file

File diff suppressed because it is too large Load Diff

View File

@ -681,23 +681,59 @@ function wc_deliver_webhook_async( $webhook_id, $arg ) {
add_action( 'woocommerce_deliver_webhook_async', 'wc_deliver_webhook_async', 10, 2 );
/**
* Get the default location
*
* Formats a string in the format COUNTRY:STATE into an array.
* @since 2.3.0
* @param string $country_string
* @return array
*/
function wc_get_default_location() {
$default = apply_filters( 'woocommerce_customer_default_location', get_option( 'woocommerce_default_country' ) );
if ( strstr( $default, ':' ) ) {
list( $country, $state ) = explode( ':', $default );
function wc_format_country_state_string( $country_string ) {
if ( strstr( $country_string, ':' ) ) {
list( $country, $state ) = explode( ':', $country_string );
} else {
$country = $default;
$country = $country_string;
$state = '';
}
return array(
'country' => $country,
'state' => $state
);
}
/**
* Get the store's base location.
* @todo should the woocommerce_default_country option be renamed to contain 'base'?
* @since 2.3.0
* @return array
*/
function wc_get_base_location() {
$default = apply_filters( 'woocommerce_get_base_location', get_option( 'woocommerce_default_country' ) );
return wc_format_country_state_string( $default );
}
/**
* Get the customer's default location. Filtered, and set to base location or left blank.
* @todo should the woocommerce_default_country option be renamed to contain 'base'?
* @since 2.3.0
* @return array
*/
function wc_get_customer_default_location() {
switch ( get_option( 'woocommerce_default_customer_address' ) ) {
case 'geolocation' :
$location = WC_Geolocation::geolocate_ip();
// Base fallback
if ( empty( $location['country'] ) ) {
$location = wc_format_country_state_string( apply_filters( 'woocommerce_customer_default_location', get_option( 'woocommerce_default_country' ) ) );
}
break;
case 'base' :
$location = wc_format_country_state_string( apply_filters( 'woocommerce_customer_default_location', get_option( 'woocommerce_default_country' ) ) );
break;
default :
$location = wc_format_country_state_string( apply_filters( 'woocommerce_customer_default_location', '' ) );
break;
}
return $location;
}

View File

@ -202,6 +202,7 @@ final class WooCommerce {
include_once( 'includes/wc-widget-functions.php' );
include_once( 'includes/wc-webhook-functions.php' );
include_once( 'includes/class-wc-install.php' );
include_once( 'includes/class-wc-geolocation.php' );
include_once( 'includes/class-wc-download-handler.php' );
include_once( 'includes/class-wc-comments.php' );
include_once( 'includes/class-wc-post-data.php' );