Merge branch 'master' into pr-25242

This commit is contained in:
Rodrigo Primo 2020-01-16 15:30:58 -03:00
commit cb85c981ac
32 changed files with 1239 additions and 1096 deletions

View File

@ -7,11 +7,12 @@
"prefer-stable": true,
"minimum-stability": "dev",
"require": {
"automattic/jetpack-autoloader": "^1.2.0",
"php": ">=5.6|>=7.0",
"automattic/jetpack-autoloader": "^1.2.0",
"composer/installers": "1.7.0",
"woocommerce/woocommerce-blocks": "2.5.7",
"woocommerce/woocommerce-rest-api": "1.0.5"
"maxmind-db/reader": "1.6.0",
"woocommerce/woocommerce-blocks": "2.5.10",
"woocommerce/woocommerce-rest-api": "1.0.6"
},
"require-dev": {
"phpunit/phpunit": "7.5.18",

117
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "95355370e5e250e500f8114896d52f7a",
"content-hash": "e8edf85437f4ee3362f0447d20314506",
"packages": [
{
"name": "automattic/jetpack-autoloader",
@ -165,17 +165,77 @@
"time": "2019-08-12T15:00:31+00:00"
},
{
"name": "woocommerce/woocommerce-blocks",
"version": "v2.5.7",
"name": "maxmind-db/reader",
"version": "v1.6.0",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git",
"reference": "24b6552d38204fbbdd87ec5ba76f3ec391b042d0"
"url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git",
"reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/24b6552d38204fbbdd87ec5ba76f3ec391b042d0",
"reference": "24b6552d38204fbbdd87ec5ba76f3ec391b042d0",
"url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/febd4920bf17c1da84cef58e56a8227dfb37fbe4",
"reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"conflict": {
"ext-maxminddb": "<1.6.0,>=2.0.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "2.*",
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpcov": "^3.0",
"phpunit/phpunit": "5.*",
"squizlabs/php_codesniffer": "3.*"
},
"suggest": {
"ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
"ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
"ext-maxminddb": "A C-based database decoder that provides significantly faster lookups"
},
"type": "library",
"autoload": {
"psr-4": {
"MaxMind\\Db\\": "src/MaxMind/Db"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Gregory J. Oschwald",
"email": "goschwald@maxmind.com",
"homepage": "https://www.maxmind.com/"
}
],
"description": "MaxMind DB Reader API",
"homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php",
"keywords": [
"database",
"geoip",
"geoip2",
"geolocation",
"maxmind"
],
"time": "2019-12-19T22:59:03+00:00"
},
{
"name": "woocommerce/woocommerce-blocks",
"version": "v2.5.10",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git",
"reference": "4a6d993c1df7ccd8581873ee56269efa00d49ddc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/4a6d993c1df7ccd8581873ee56269efa00d49ddc",
"reference": "4a6d993c1df7ccd8581873ee56269efa00d49ddc",
"shasum": ""
},
"require": {
@ -209,20 +269,20 @@
"gutenberg",
"woocommerce"
],
"time": "2019-12-20T16:26:08+00:00"
"time": "2020-01-09T15:29:03+00:00"
},
{
"name": "woocommerce/woocommerce-rest-api",
"version": "1.0.5",
"version": "1.0.6",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-rest-api.git",
"reference": "3be425631faefa61ab8b81011ae8a422b9bfca35"
"reference": "78ccf4d4c6bafbc841182b68aa863e7b0caa37c8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-rest-api/zipball/3be425631faefa61ab8b81011ae8a422b9bfca35",
"reference": "3be425631faefa61ab8b81011ae8a422b9bfca35",
"url": "https://api.github.com/repos/woocommerce/woocommerce-rest-api/zipball/78ccf4d4c6bafbc841182b68aa863e7b0caa37c8",
"reference": "78ccf4d4c6bafbc841182b68aa863e7b0caa37c8",
"shasum": ""
},
"require": {
@ -249,7 +309,7 @@
],
"description": "The WooCommerce core REST API.",
"homepage": "https://github.com/woocommerce/woocommerce-rest-api",
"time": "2019-12-18T22:20:59+00:00"
"time": "2020-01-15T23:29:39+00:00"
}
],
"packages-dev": [
@ -527,16 +587,16 @@
},
{
"name": "phpcompatibility/php-compatibility",
"version": "9.3.4",
"version": "9.3.5",
"source": {
"type": "git",
"url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
"reference": "1f37659196e4f3113ea506a7efba201c52303bf1"
"reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/1f37659196e4f3113ea506a7efba201c52303bf1",
"reference": "1f37659196e4f3113ea506a7efba201c52303bf1",
"url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
"reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
"shasum": ""
},
"require": {
@ -581,7 +641,7 @@
"phpcs",
"standards"
],
"time": "2019-11-15T04:12:02+00:00"
"time": "2019-12-27T09:44:58+00:00"
},
{
"name": "phpcompatibility/phpcompatibility-paragonie",
@ -739,16 +799,16 @@
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "4.3.2",
"version": "4.3.4",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e"
"reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e",
"reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c",
"reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c",
"shasum": ""
},
"require": {
@ -760,6 +820,7 @@
"require-dev": {
"doctrine/instantiator": "^1.0.5",
"mockery/mockery": "^1.0",
"phpdocumentor/type-resolver": "0.4.*",
"phpunit/phpunit": "^6.4"
},
"type": "library",
@ -786,7 +847,7 @@
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"time": "2019-09-12T14:27:41+00:00"
"time": "2019-12-28T18:55:12+00:00"
},
{
"name": "phpdocumentor/type-resolver",
@ -837,16 +898,16 @@
},
{
"name": "phpspec/prophecy",
"version": "1.10.0",
"version": "1.10.1",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "d638ebbb58daba25a6a0dc7969e1358a0e3c6682"
"reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/d638ebbb58daba25a6a0dc7969e1358a0e3c6682",
"reference": "d638ebbb58daba25a6a0dc7969e1358a0e3c6682",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/cbe1df668b3fe136bcc909126a0f529a78d4cbbc",
"reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc",
"shasum": ""
},
"require": {
@ -896,7 +957,7 @@
"spy",
"stub"
],
"time": "2019-12-17T16:54:23+00:00"
"time": "2019-12-22T21:05:45+00:00"
},
{
"name": "phpunit/php-code-coverage",

View File

@ -36,6 +36,7 @@ class WC_Admin_Notices {
'no_secure_connection' => 'secure_connection_notice',
'wc_admin' => 'wc_admin_feature_plugin_notice',
WC_PHP_MIN_REQUIREMENTS_NOTICE => 'wp_php_min_requirements_notice',
'maxmind_license_key' => 'maxmind_missing_license_key_notice',
);
/**
@ -87,6 +88,7 @@ class WC_Admin_Notices {
self::add_wc_admin_feature_plugin_notice();
self::add_notice( 'template_files' );
self::add_min_version_notice();
self::add_maxmind_missing_license_key_notice();
}
/**
@ -428,6 +430,41 @@ class WC_Admin_Notices {
include dirname( __FILE__ ) . '/views/html-notice-wp-php-minimum-requirements.php';
}
/**
* Add MaxMind missing license key notice.
*
* @since 3.9.0
*/
public static function add_maxmind_missing_license_key_notice() {
$default_address = get_option( 'woocommerce_default_customer_address' );
if ( ! in_array( $default_address, array( 'geolocation', 'geolocation_ajax' ), true ) ) {
return;
}
$integration_options = get_option( 'woocommerce_maxmind_geolocation_settings' );
if ( empty( $integration_options['license_key'] ) ) {
self::add_notice( 'maxmind_license_key' );
}
}
/**
* Display MaxMind missing license key notice.
*
* @since 3.9.0
*/
public static function maxmind_missing_license_key_notice() {
$user_dismissed_notice = get_user_meta( get_current_user_id(), 'dismissed_maxmind_license_key_notice', true );
$filter_dismissed_notice = ! apply_filters( 'woocommerce_maxmind_geolocation_display_notices', true );
if ( $user_dismissed_notice || $filter_dismissed_notice ) {
self::remove_notice( 'maxmind_license_key' );
return;
}
include dirname( __FILE__ ) . '/views/html-notice-maxmind-license-key.php';
}
/**
* Determine if the store is running SSL.
*

View File

@ -39,17 +39,6 @@ class WC_Settings_General extends WC_Settings_Page {
$currency_code_options[ $code ] = $name . ' (' . get_woocommerce_currency_symbol( $code ) . ')';
}
$woocommerce_default_customer_address_options = array(
'' => __( 'No location by default', 'woocommerce' ),
'base' => __( 'Shop base address', 'woocommerce' ),
'geolocation' => __( 'Geolocate', 'woocommerce' ),
'geolocation_ajax' => __( 'Geolocate (with page caching support)', 'woocommerce' ),
);
if ( version_compare( PHP_VERSION, '5.4', '<' ) ) {
unset( $woocommerce_default_customer_address_options['geolocation'], $woocommerce_default_customer_address_options['geolocation_ajax'] );
}
$settings = apply_filters(
'woocommerce_general_settings',
array(
@ -182,10 +171,15 @@ class WC_Settings_General extends WC_Settings_Page {
'title' => __( 'Default customer location', 'woocommerce' ),
'id' => 'woocommerce_default_customer_address',
'desc_tip' => __( 'This option determines a customers default location. The MaxMind GeoLite Database will be periodically downloaded to your wp-content directory if using geolocation.', 'woocommerce' ),
'default' => 'geolocation',
'default' => 'base',
'type' => 'select',
'class' => 'wc-enhanced-select',
'options' => $woocommerce_default_customer_address_options,
'options' => array(
'' => __( 'No location by default', 'woocommerce' ),
'base' => __( 'Shop base address', 'woocommerce' ),
'geolocation' => __( 'Geolocate', 'woocommerce' ),
'geolocation_ajax' => __( 'Geolocate (with page caching support)', 'woocommerce' ),
),
),
array(

View File

@ -435,25 +435,6 @@ $untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, 'min
</td>
</tr>
<?php if ( $settings['geolocation_enabled'] ) { ?>
<tr>
<td data-export-label="MaxMind GeoIP Database"><?php esc_html_e( 'MaxMind GeoIP database', 'woocommerce' ); ?>:</td>
<td class="help"><?php echo wc_help_tip( esc_html__( 'The GeoIP database from MaxMind is used to geolocate customers.', 'woocommerce' ) ); /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></td>
<td>
<?php
if ( version_compare( $environment['php_version'], '5.4', '<' ) ) {
echo '<mark class="error"><span class="dashicons dashicons-warning"></span> ' . wp_kses_post( __( 'MaxMind GeoIP database requires at least PHP 5.4.', 'woocommerce' ) ) . '</mark>';
} elseif ( file_exists( $database['maxmind_geoip_database'] ) ) {
echo '<mark class="yes"><span class="dashicons dashicons-yes"></span> <code class="private">' . esc_html( $database['maxmind_geoip_database'] ) . '</code></mark> ';
} else {
/* Translators: %1$s: Library url, %2$s: install path. */
printf( '<mark class="error"><span class="dashicons dashicons-warning"></span> ' . sprintf( esc_html__( 'The MaxMind GeoIP Database does not exist - Geolocation will not function. You can download and install it manually from %1$s to the path: %2$s. Scroll down to "Downloads" and download the "MaxMind DB binary, gzipped" file next to "GeoLite2 Country". Please remember to uncompress GeoLite2-Country_xxxxxxxx.tar.gz and upload the GeoLite2-Country.mmdb file only.', 'woocommerce' ), '<a href="https://dev.maxmind.com/geoip/geoip2/geolite2/">https://dev.maxmind.com/geoip/geoip2/geolite2/</a>', '<code class="private">' . esc_html( $database['maxmind_geoip_database'] ) . '</code>' ) . '</mark>', esc_html( WC_LOG_DIR ) );
}
?>
</td>
</tr>
<?php } ?>
<?php if ( ! empty( $database['database_size'] ) && ! empty( $database['database_tables'] ) ) : ?>
<tr>
<td><?php esc_html_e( 'Total Database Size', 'woocommerce' ); ?></td>
@ -584,7 +565,7 @@ $untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, 'min
</thead>
<tbody>
<?php
foreach ( $active_plugins as $plugin ) {
foreach ( $active_plugins as $plugin ) { // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
if ( ! empty( $plugin['name'] ) ) {
$dirname = dirname( $plugin['plugin'] );
@ -636,7 +617,7 @@ $untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, 'min
</thead>
<tbody>
<?php
foreach ( $inactive_plugins as $plugin ) {
foreach ( $inactive_plugins as $plugin ) { // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
if ( ! empty( $plugin['name'] ) ) {
$dirname = dirname( $plugin['plugin'] );
@ -715,7 +696,7 @@ if ( 0 < count( $dropins_mu_plugins['mu_plugins'] ) ) :
</thead>
<tbody>
<?php
foreach ( $dropins_mu_plugins['mu_plugins'] as $mu_plugin ) {
foreach ( $dropins_mu_plugins['mu_plugins'] as $mu_plugin ) { // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$plugin_name = esc_html( $mu_plugin['name'] );
if ( ! empty( $mu_plugin['url'] ) ) {
$plugin_name = '<a href="' . esc_url( $mu_plugin['url'] ) . '" aria-label="' . esc_attr__( 'Visit plugin homepage', 'woocommerce' ) . '" target="_blank">' . $plugin_name . '</a>';

View File

@ -0,0 +1,31 @@
<?php
/**
* Admin View: Notice - Missing MaxMind license key
*
* @package WooCommerce\Admin
*/
defined( 'ABSPATH' ) || exit;
?>
<div id="message" class="updated woocommerce-message">
<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'maxmind_license_key' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php esc_html_e( 'Dismiss', 'woocommerce' ); ?></a>
<p>
<strong><?php esc_html_e( 'Geolocation has not been configured.', 'woocommerce' ); ?></strong>
</p>
<p>
<?php
echo wp_kses_post(
sprintf(
/* translators: %1%s: integration page %2$s: general settings page */
__( 'You must enter a valid license key on the <a href="%1$s">MaxMind integration settings page</a> in order to use the geolocation service. If you do not need geolocation for shipping or taxes, you should change the default customer location on the <a href="%2$s">general settings page</a>.', 'woocommerce' ),
admin_url( 'admin.php?page=wc-settings&tab=integration&section=maxmind_geolocation' ),
admin_url( 'admin.php?page=wc-settings&tab=general' )
)
);
?>
</p>
</div>

View File

@ -88,6 +88,8 @@ class WC_Autoloader {
$path = $this->include_path . 'payment-tokens/';
} elseif ( 0 === strpos( $class, 'wc_log_handler_' ) ) {
$path = $this->include_path . 'log-handlers/';
} elseif ( 0 === strpos( $class, 'wc_integration' ) ) {
$path = $this->include_path . 'integrations/' . substr( str_replace( '_', '-', $class ), 15 ) . '/';
}
if ( empty( $path ) || ! $this->load_file( $path . $file ) ) {

View File

@ -8,12 +8,15 @@
*
* @package WooCommerce\Classes
* @since 3.4.0
* @deprecated 3.9.0
*/
defined( 'ABSPATH' ) || exit;
/**
* Geolite integration class.
*
* @deprecated 3.9.0
*/
class WC_Geolite_Integration {
@ -38,10 +41,6 @@ class WC_Geolite_Integration {
*/
public function __construct( $database ) {
$this->database = $database;
if ( ! class_exists( 'MaxMind\\Db\\Reader', false ) ) {
$this->require_geolite_library();
}
}
/**
@ -50,8 +49,11 @@ class WC_Geolite_Integration {
*
* @param string $ip_address User IP address.
* @return string
* @deprecated 3.9.0
*/
public function get_country_iso( $ip_address ) {
wc_deprecated_function( 'get_country_iso', '3.9.0' );
$iso_code = '';
try {
@ -87,15 +89,4 @@ class WC_Geolite_Integration {
$this->log->log( $level, $message, array( 'source' => 'geoip' ) );
}
/**
* Require geolite library.
*/
private function require_geolite_library() {
require_once WC_ABSPATH . 'includes/libraries/geolite2/Reader/Decoder.php';
require_once WC_ABSPATH . 'includes/libraries/geolite2/Reader/InvalidDatabaseException.php';
require_once WC_ABSPATH . 'includes/libraries/geolite2/Reader/Metadata.php';
require_once WC_ABSPATH . 'includes/libraries/geolite2/Reader/Util.php';
require_once WC_ABSPATH . 'includes/libraries/geolite2/Reader.php';
}
}

View File

@ -7,7 +7,7 @@
* This product includes GeoLite data created by MaxMind, available from http://www.maxmind.com.
*
* @package WooCommerce/Classes
* @version 3.4.0
* @version 3.9.0
*/
defined( 'ABSPATH' ) || exit;
@ -35,6 +35,7 @@ class WC_Geolocation {
* GeoLite2 DB.
*
* @since 3.4.0
* @deprecated 3.9.0
*/
const GEOLITE2_DB = 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz';
@ -60,16 +61,6 @@ class WC_Geolocation {
'ip-api.com' => 'http://ip-api.com/json/%s',
);
/**
* Check if server supports MaxMind GeoLite2 Reader.
*
* @since 3.4.0
* @return bool
*/
private static function supports_geolite2() {
return version_compare( PHP_VERSION, '5.4.0', '>=' );
}
/**
* Check if geolocation is enabled.
*
@ -81,67 +72,20 @@ class WC_Geolocation {
return in_array( $current_settings, array( 'geolocation', 'geolocation_ajax' ), true );
}
/**
* Prevent geolocation via MaxMind when using legacy versions of php.
*
* @since 3.4.0
* @param string $default_customer_address current value.
* @return string
*/
public static function disable_geolocation_on_legacy_php( $default_customer_address ) {
if ( self::is_geolocation_enabled( $default_customer_address ) ) {
$default_customer_address = 'base';
}
return $default_customer_address;
}
/**
* Hook in geolocation functionality.
*/
public static function init() {
if ( self::supports_geolite2() ) {
// Only download the database from MaxMind if the geolocation function is enabled, or a plugin specifically requests it.
if ( self::is_geolocation_enabled( get_option( 'woocommerce_default_customer_address' ) ) || apply_filters( 'woocommerce_geolocation_update_database_periodically', false ) ) {
add_action( 'woocommerce_geoip_updater', array( __CLASS__, 'update_database' ) );
}
// Trigger database update when settings are changed to enable geolocation.
add_filter( 'pre_update_option_woocommerce_default_customer_address', array( __CLASS__, 'maybe_update_database' ), 10, 2 );
} else {
add_filter( 'pre_option_woocommerce_default_customer_address', array( __CLASS__, 'disable_geolocation_on_legacy_php' ) );
}
}
/**
* Maybe trigger a DB update for the first time.
*
* @param string $new_value New value.
* @param string $old_value Old value.
* @return string
*/
public static function maybe_update_database( $new_value, $old_value ) {
if ( $new_value !== $old_value && self::is_geolocation_enabled( $new_value ) ) {
self::update_database();
}
return $new_value;
}
/**
* Get current user IP Address.
*
* @return string
*/
public static function get_ip_address() {
if ( isset( $_SERVER['HTTP_X_REAL_IP'] ) ) { // WPCS: input var ok, CSRF ok.
return sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_REAL_IP'] ) ); // WPCS: input var ok, CSRF ok.
} elseif ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { // WPCS: input var ok, CSRF ok.
if ( isset( $_SERVER['HTTP_X_REAL_IP'] ) ) {
return sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_REAL_IP'] ) );
} elseif ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
// Proxy servers can send through this header like this: X-Forwarded-For: client1, proxy1, proxy2
// Make sure we always only send through the first IP in the list which should always be the client IP.
return (string) rest_is_ip_address( trim( current( preg_split( '/,/', sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) ) ) ) ); // WPCS: input var ok, CSRF ok.
} elseif ( isset( $_SERVER['REMOTE_ADDR'] ) ) { // @codingStandardsIgnoreLine
return sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ); // @codingStandardsIgnoreLine
return (string) rest_is_ip_address( trim( current( preg_split( '/,/', sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) ) ) ) );
} elseif ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
return sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
}
return '';
}
@ -195,119 +139,114 @@ class WC_Geolocation {
// Filter to allow custom geolocation of the IP address.
$country_code = apply_filters( 'woocommerce_geolocate_ip', false, $ip_address, $fallback, $api_fallback );
if ( false === $country_code ) {
// If GEOIP is enabled in CloudFlare, we can use that (Settings -> CloudFlare Settings -> Settings Overview).
if ( ! empty( $_SERVER['HTTP_CF_IPCOUNTRY'] ) ) { // WPCS: input var ok, CSRF ok.
$country_code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_IPCOUNTRY'] ) ) ); // WPCS: input var ok, CSRF ok.
} elseif ( ! empty( $_SERVER['GEOIP_COUNTRY_CODE'] ) ) { // WPCS: input var ok, CSRF ok.
// WP.com VIP has a variable available.
$country_code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER['GEOIP_COUNTRY_CODE'] ) ) ); // WPCS: input var ok, CSRF ok.
} elseif ( ! empty( $_SERVER['HTTP_X_COUNTRY_CODE'] ) ) { // WPCS: input var ok, CSRF ok.
// VIP Go has a variable available also.
$country_code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_COUNTRY_CODE'] ) ) ); // WPCS: input var ok, CSRF ok.
} else {
$ip_address = $ip_address ? $ip_address : self::get_ip_address();
$database = self::get_local_database_path();
if ( false !== $country_code ) {
return array(
'country' => $country_code,
'state' => '',
'city' => '',
'postcode' => '',
);
}
if ( self::supports_geolite2() && file_exists( $database ) ) {
$country_code = self::geolocate_via_db( $ip_address, $database );
} elseif ( $api_fallback ) {
$country_code = self::geolocate_via_api( $ip_address );
} else {
$country_code = '';
}
if ( empty( $ip_address ) ) {
$ip_address = self::get_ip_address();
}
if ( ! $country_code && $fallback ) {
// May be a local environment - find external IP.
return self::geolocate_ip( self::get_external_ip_address(), false, $api_fallback );
}
$country_code = self::get_country_code_from_headers();
/**
* Get geolocation filter.
*
* @since 3.9.0
* @param array $geolocation Geolocation data, including country, state, city, and postcode.
* @param string $ip_address IP Address.
*/
$geolocation = apply_filters(
'woocommerce_get_geolocation',
array(
'country' => $country_code,
'state' => '',
'city' => '',
'postcode' => '',
),
$ip_address
);
// If we still haven't found a country code, let's consider doing an API lookup.
if ( '' === $geolocation['country'] && $api_fallback ) {
$geolocation['country'] = self::geolocate_via_api( $ip_address );
}
// It's possible that we're in a local environment, in which case the geolocation needs to be done from the
// external address.
if ( '' === $geolocation['country'] && $fallback ) {
$external_ip_address = self::get_external_ip_address();
// Only bother with this if the external IP differs.
if ( '0.0.0.0' !== $external_ip_address && $external_ip_address !== $ip_address ) {
return self::geolocate_ip( $external_ip_address, false, $api_fallback );
}
}
return array(
'country' => $country_code,
'state' => '',
'country' => $geolocation['country'],
'state' => $geolocation['state'],
'city' => $geolocation['city'],
'postcode' => $geolocation['postcode'],
);
}
/**
* Path to our local db.
*
* @deprecated 3.9.0
* @param string $deprecated Deprecated since 3.4.0.
* @return string
*/
public static function get_local_database_path( $deprecated = '2' ) {
return apply_filters( 'woocommerce_geolocation_local_database_path', WP_CONTENT_DIR . '/uploads/GeoLite2-Country.mmdb', $deprecated );
wc_deprecated_function( 'WC_Geolocation::get_local_database_path', '3.9.0' );
$integration = wc()->integrations->get_integration( 'maxmind_geolocation' );
return $integration->get_database_service()->get_database_path();
}
/**
* Update geoip database.
*
* @deprecated 3.9.0
* Extract files with PharData. Tool built into PHP since 5.3.
*/
public static function update_database() {
$logger = wc_get_logger();
if ( ! self::supports_geolite2() ) {
$logger->notice( 'Requires PHP 5.4 to be able to download MaxMind GeoLite2 database', array( 'source' => 'geolocation' ) );
return;
}
require_once ABSPATH . 'wp-admin/includes/file.php';
$database = 'GeoLite2-Country.mmdb';
$target_database_path = self::get_local_database_path();
$tmp_database_path = download_url( self::GEOLITE2_DB );
if ( ! is_wp_error( $tmp_database_path ) ) {
WP_Filesystem();
global $wp_filesystem;
try {
// Make sure target dir exists.
$wp_filesystem->mkdir( dirname( $target_database_path ) );
// Extract files with PharData. Tool built into PHP since 5.3.
$file = new PharData( $tmp_database_path ); // phpcs:ignore PHPCompatibility.Classes.NewClasses.phardataFound
$file_path = trailingslashit( $file->current()->getFileName() ) . $database;
$file->extractTo( dirname( $tmp_database_path ), $file_path, true );
// Move file and delete temp.
$wp_filesystem->move( trailingslashit( dirname( $tmp_database_path ) ) . $file_path, $target_database_path, true );
$wp_filesystem->delete( trailingslashit( dirname( $tmp_database_path ) ) . $file->current()->getFileName() );
} catch ( Exception $e ) {
$logger->notice( $e->getMessage(), array( 'source' => 'geolocation' ) );
// Reschedule download of DB.
wp_clear_scheduled_hook( 'woocommerce_geoip_updater' );
wp_schedule_event( strtotime( 'first tuesday of next month' ), 'monthly', 'woocommerce_geoip_updater' );
}
// Delete temp file regardless of success.
$wp_filesystem->delete( $tmp_database_path );
} else {
$logger->notice(
'Unable to download GeoIP Database: ' . $tmp_database_path->get_error_message(),
array( 'source' => 'geolocation' )
);
}
wc_deprecated_function( 'WC_Geolocation::update_database', '3.9.0' );
$integration = wc()->integrations->get_integration( 'maxmind_geolocation' );
$integration->update_database();
}
/**
* Use MAXMIND GeoLite database to geolocation the user.
* Fetches the country code from the request headers, if one is available.
*
* @param string $ip_address IP address.
* @param string $database Database path.
* @return string
* @since 3.9.0
* @return string The country code pulled from the headers, or empty string if one was not found.
*/
private static function geolocate_via_db( $ip_address, $database ) {
if ( ! class_exists( 'WC_Geolite_Integration', false ) ) {
require_once WC_ABSPATH . 'includes/class-wc-geolite-integration.php';
private static function get_country_code_from_headers() {
$country_code = '';
$headers = array(
'MM_COUNTRY_CODE',
'GEOIP_COUNTRY_CODE',
'HTTP_CF_IPCOUNTRY',
'HTTP_X_COUNTRY_CODE',
);
foreach ( $headers as $header ) {
if ( empty( $_SERVER[ $header ] ) ) {
continue;
}
$country_code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) ) );
break;
}
$geolite = new WC_Geolite_Integration( $database );
return $geolite->get_country_iso( $ip_address );
return $country_code;
}
/**
@ -368,6 +307,50 @@ class WC_Geolocation {
return $country_code;
}
}
WC_Geolocation::init();
/**
* Hook in geolocation functionality.
*
* @deprecated 3.9.0
* @return null
*/
public static function init() {
wc_deprecated_function( 'WC_Geolocation::init', '3.9.0' );
return null;
}
/**
* Prevent geolocation via MaxMind when using legacy versions of php.
*
* @deprecated 3.9.0
* @since 3.4.0
* @param string $default_customer_address current value.
* @return string
*/
public static function disable_geolocation_on_legacy_php( $default_customer_address ) {
wc_deprecated_function( 'WC_Geolocation::disable_geolocation_on_legacy_php', '3.9.0' );
if ( self::is_geolocation_enabled( $default_customer_address ) ) {
$default_customer_address = 'base';
}
return $default_customer_address;
}
/**
* Maybe trigger a DB update for the first time.
*
* @deprecated 3.9.0
* @param string $new_value New value.
* @param string $old_value Old value.
* @return string
*/
public static function maybe_update_database( $new_value, $old_value ) {
wc_deprecated_function( 'WC_Geolocation::maybe_update_database', '3.9.0' );
if ( $new_value !== $old_value && self::is_geolocation_enabled( $new_value ) ) {
self::update_database();
}
return $new_value;
}
}

View File

@ -135,6 +135,11 @@ class WC_Install {
'wc_update_370_mro_std_currency',
'wc_update_370_db_version',
),
'3.9.0' => array(
'wc_update_390_move_maxmind_database',
'wc_update_390_change_geolocation_database_update_cron',
'wc_update_390_db_version',
),
);
/**
@ -421,6 +426,10 @@ class WC_Install {
'interval' => 2635200,
'display' => __( 'Monthly', 'woocommerce' ),
);
$schedules['fifteendays'] = array(
'interval' => 1296000,
'display' => __( 'Every 15 Days', 'woocommerce' ),
);
return $schedules;
}
@ -449,11 +458,8 @@ class WC_Install {
wp_schedule_event( time(), 'daily', 'woocommerce_cleanup_personal_data' );
wp_schedule_event( time() + ( 3 * HOUR_IN_SECONDS ), 'daily', 'woocommerce_cleanup_logs' );
wp_schedule_event( time() + ( 6 * HOUR_IN_SECONDS ), 'twicedaily', 'woocommerce_cleanup_sessions' );
wp_schedule_event( strtotime( 'first tuesday of next month' ), 'monthly', 'woocommerce_geoip_updater' );
wp_schedule_event( time() + MINUTE_IN_SECONDS, 'fifteendays', 'woocommerce_geoip_updater' );
wp_schedule_event( time() + 10, apply_filters( 'woocommerce_tracker_event_recurrence', 'daily' ), 'woocommerce_tracker_send_event' );
// Trigger GeoLite2 database download after 1 minute.
wp_schedule_single_event( time() + ( MINUTE_IN_SECONDS * 1 ), 'woocommerce_geoip_updater' );
}
/**
@ -970,7 +976,7 @@ CREATE TABLE {$wpdb->prefix}wc_tax_rate_classes (
$tables = self::get_tables();
foreach ( $tables as $table ) {
$wpdb->query( "DROP TABLE IF EXISTS {$table}" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( "DROP TABLE IF EXISTS {$table}" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
}

View File

@ -4,7 +4,7 @@
*
* Loads Integrations into WooCommerce.
*
* @version 2.3.0
* @version 3.9.0
* @package WooCommerce/Classes/Integrations
*/
@ -29,7 +29,11 @@ class WC_Integrations {
do_action( 'woocommerce_integrations_init' );
$load_integrations = apply_filters( 'woocommerce_integrations', array() );
$load_integrations = array(
'WC_Integration_MaxMind_Geolocation',
);
$load_integrations = apply_filters( 'woocommerce_integrations', $load_integrations );
// Load integration classes.
foreach ( $load_integrations as $integration ) {
@ -48,4 +52,19 @@ class WC_Integrations {
public function get_integrations() {
return $this->integrations;
}
/**
* Return a desired integration.
*
* @since 3.9.0
* @param string $id The id of the integration to get.
* @return mixed|null The integration if one is found, otherwise null.
*/
public function get_integration( $id ) {
if ( isset( $this->integrations[ $id ] ) ) {
return $this->integrations[ $id ];
}
return null;
}
}

View File

@ -294,12 +294,7 @@ class WC_Shipping {
}
$states = WC()->countries->get_states( $country );
if ( is_array( $states ) && ! isset( $states[ $package['destination']['state'] ] ) ) {
return false;
}
$postcode = wc_format_postcode( $package['destination']['postcode'], $country );
if ( ! WC_Validation::is_postcode( $postcode, $country ) ) {
if ( is_array( $states ) && ! empty( $states ) && ! isset( $states[ $package['destination']['state'] ] ) ) {
return false;
}

View File

@ -0,0 +1,167 @@
<?php
/**
* The database service class file.
*
* @version 3.9.0
* @package WooCommerce/Integrations
*/
defined( 'ABSPATH' ) || exit;
/**
* The service class responsible for interacting with MaxMind databases.
*
* @since 3.9.0
*/
class WC_Integration_MaxMind_Database_Service {
/**
* The name of the MaxMind database to utilize.
*/
const DATABASE = 'GeoLite2-Country';
/**
* The extension for the MaxMind database.
*/
const DATABASE_EXTENSION = '.mmdb';
/**
* A prefix for the MaxMind database filename.
*
* @var string
*/
private $database_prefix;
/**
* WC_Integration_MaxMind_Database_Service constructor.
*
* @param string|null $database_prefix A prefix for the MaxMind database filename.
*/
public function __construct( $database_prefix ) {
$this->database_prefix = $database_prefix;
}
/**
* Fetches the path that the database should be stored.
*
* @return string The local database path.
*/
public function get_database_path() {
$uploads_dir = wp_upload_dir();
$database_path = trailingslashit( $uploads_dir['basedir'] ) . 'woocommerce_uploads/';
if ( ! empty( $this->database_prefix ) ) {
$database_path .= $this->database_prefix . '-';
}
$database_path .= self::DATABASE . self::DATABASE_EXTENSION;
/**
* Filter the geolocation database storage path.
*
* @param string $database_path The path to the database.
* @param int $version Deprecated since 3.4.0.
* @deprecated 3.9.0
*/
$database_path = apply_filters_deprecated(
'woocommerce_geolocation_local_database_path',
array( $database_path, 2 ),
'3.9.0',
'woocommerce_maxmind_geolocation_database_path'
);
/**
* Filter the geolocation database storage path.
*
* @since 3.9.0
* @param string $database_path The path to the database.
*/
return apply_filters( 'woocommerce_maxmind_geolocation_database_path', $database_path );
}
/**
* Fetches the database from the MaxMind service.
*
* @param string $license_key The license key to be used when downloading the database.
* @return string|WP_Error The path to the database file or an error if invalid.
*/
public function download_database( $license_key ) {
$download_uri = add_query_arg(
array(
'edition_id' => self::DATABASE,
'license_key' => wc_clean( $license_key ),
'suffix' => 'tar.gz',
),
'https://download.maxmind.com/app/geoip_download'
);
// Needed for the download_url call right below.
require_once ABSPATH . 'wp-admin/includes/file.php';
$tmp_archive_path = download_url( $download_uri );
if ( is_wp_error( $tmp_archive_path ) ) {
// Transform the error into something more informative.
$error_data = $tmp_archive_path->get_error_data();
if ( isset( $error_data['code'] ) ) {
switch ( $error_data['code'] ) {
case 401:
return new WP_Error(
'woocommerce_maxmind_geolocation_database_license_key',
__( 'The MaxMind license key is invalid.', 'woocommerce' )
);
}
}
return new WP_Error( 'woocommerce_maxmind_geolocation_database_download', __( 'Failed to download the MaxMind database.', 'woocommerce' ) );
}
// Extract the database from the archive.
try {
$file = new PharData( $tmp_archive_path );
$tmp_database_path = trailingslashit( dirname( $tmp_archive_path ) ) . trailingslashit( $file->current()->getFilename() ) . self::DATABASE . self::DATABASE_EXTENSION;
$file->extractTo(
dirname( $tmp_archive_path ),
trailingslashit( $file->current()->getFilename() ) . self::DATABASE . self::DATABASE_EXTENSION,
true
);
} catch ( Exception $exception ) {
return new WP_Error( 'woocommerce_maxmind_geolocation_database_archive', $exception->getMessage() );
} finally {
// Remove the archive since we only care about a single file in it.
unlink( $tmp_archive_path );
}
return $tmp_database_path;
}
/**
* Fetches the ISO country code associated with an IP address.
*
* @param string $ip_address The IP address to find the country code for.
* @return string|null The country code for the IP address, or null if none was found.
*/
public function get_iso_country_code_for_ip( $ip_address ) {
$country_code = null;
if ( ! class_exists( 'MaxMind\Db\Reader' ) ) {
wc_get_logger()->notice( __( 'Missing MaxMind Reader library!', 'woocommerce' ), array( 'source' => 'maxmind-geolocation' ) );
return $country_code;
}
try {
$reader = new MaxMind\Db\Reader( $this->get_database_path() );
$data = $reader->get( $ip_address );
if ( isset( $data['country']['iso_code'] ) ) {
$country_code = $data['country']['iso_code'];
}
$reader->close();
} catch ( Exception $e ) {
wc_get_logger()->notice( $e->getMessage(), array( 'source' => 'maxmind-geolocation' ) );
}
return $country_code;
}
}

View File

@ -0,0 +1,289 @@
<?php
/**
* MaxMind Geolocation Integration
*
* @version 3.9.0
* @package WooCommerce/Integrations
*/
defined( 'ABSPATH' ) || exit;
require_once 'class-wc-integration-maxmind-database-service.php';
/**
* WC Integration MaxMind Geolocation
*
* @since 3.9.0
*/
class WC_Integration_MaxMind_Geolocation extends WC_Integration {
/**
* The service responsible for interacting with the MaxMind database.
*
* @var WC_Integration_MaxMind_Database_Service
*/
private $database_service;
/**
* Initialize the integration.
*/
public function __construct() {
$this->id = 'maxmind_geolocation';
$this->method_title = __( 'WooCommerce MaxMind Geolocation', 'woocommerce' );
$this->method_description = __( 'An integration for utilizing MaxMind to do Geolocation lookups. Please note that this integration will only do country lookups.', 'woocommerce' );
/**
* Supports overriding the database service to be used.
*
* @since 3.9.0
* @return mixed|null The geolocation database service.
*/
$this->database_service = apply_filters( 'woocommerce_maxmind_geolocation_database_service', null );
if ( null === $this->database_service ) {
$this->database_service = new WC_Integration_MaxMind_Database_Service( $this->get_database_prefix() );
}
$this->init_form_fields();
$this->init_settings();
// Bind to the save action for the settings.
add_action( 'woocommerce_update_options_integration_' . $this->id, array( $this, 'process_admin_options' ) );
// Trigger notice if license key is missing.
add_action( 'update_option_woocommerce_default_customer_address', array( $this, 'display_missing_license_key_notice' ), 1000, 2 );
/**
* Allows for the automatic database update to be disabled.
*
* @deprecated 3.9.0
* @return bool Whether or not the database should be updated periodically.
*/
$bind_updater = apply_filters_deprecated(
'woocommerce_geolocation_update_database_periodically',
array( true ),
'3.9.0',
'woocommerce_maxmind_geolocation_update_database_periodically'
);
/**
* Allows for the automatic database update to be disabled.
* Note that MaxMind's TOS requires that the databases be updated or removed periodically.
*
* @since 3.9.0
* @param bool $bind_updater Whether or not the database should be updated periodically.
*/
$bind_updater = apply_filters( 'woocommerce_maxmind_geolocation_update_database_periodically', $bind_updater );
// Bind to the scheduled updater action.
if ( $bind_updater ) {
add_action( 'woocommerce_geoip_updater', array( $this, 'update_database' ) );
}
// Bind to the geolocation filter for MaxMind database lookups.
add_filter( 'woocommerce_get_geolocation', array( $this, 'get_geolocation' ), 10, 2 );
}
/**
* Override the normal options so we can print the database file path to the admin,
*/
public function admin_options() {
parent::admin_options();
include dirname( __FILE__ ) . '/views/html-admin-options.php';
}
/**
* Initializes the settings fields.
*/
public function init_form_fields() {
$this->form_fields = array(
'license_key' => array(
'title' => __( 'MaxMind License Key', 'woocommerce' ),
'type' => 'password',
'description' => sprintf(
/* translators: %1$s: Documentation URL */
__(
'The key that will be used when dealing with MaxMind Geolocation services. You can read how to generate one in <a href="%1$s">MaxMind\'s License Key Documentation</a>.',
'woocommerce'
),
'https://support.maxmind.com/account-faq/account-related/how-do-i-generate-a-license-key/'
),
'desc_tip' => false,
'default' => '',
),
);
}
/**
* Get database service.
*
* @return WC_Integration_MaxMind_Database_Service|null
*/
public function get_database_service() {
return $this->database_service;
}
/**
* Checks to make sure that the license key is valid.
*
* @param string $key The key of the field.
* @param mixed $value The value of the field.
* @return mixed
* @throws Exception When the license key is invalid.
*/
public function validate_license_key_field( $key, $value ) {
// Empty license keys have no need to validate the data.
if ( empty( $value ) ) {
return $value;
}
// Check the license key by attempting to download the Geolocation database.
$tmp_database_path = $this->database_service->download_database( $value );
if ( is_wp_error( $tmp_database_path ) ) {
WC_Admin_Settings::add_error( $tmp_database_path->get_error_message() );
// Throw an exception to keep from changing this value. This will prevent
// users from accidentally losing their license key, which cannot
// be viewed again after generating.
throw new Exception( $tmp_database_path->get_error_message() );
}
// We may as well put this archive to good use, now that we've downloaded one.
self::update_database( $tmp_database_path );
// Remove missing license key notice.
$this->remove_missing_license_key_notice();
return $value;
}
/**
* Updates the database used for geolocation queries.
*
* @param string|null $new_database_path The path to the new database file. Null will fetch a new archive.
*/
public function update_database( $new_database_path = null ) {
// Allow us to easily interact with the filesystem.
require_once ABSPATH . 'wp-admin/includes/file.php';
WP_Filesystem();
global $wp_filesystem;
// Remove any existing archives to comply with the MaxMind TOS.
$target_database_path = $this->database_service->get_database_path();
// If there's no database path, we can't store the database.
if ( empty( $target_database_path ) ) {
return;
}
if ( $wp_filesystem->exists( $target_database_path ) ) {
$wp_filesystem->delete( $target_database_path );
}
if ( isset( $new_database_path ) ) {
$tmp_database_path = $new_database_path;
} else {
// We can't download a database if there's no license key configured.
$license_key = $this->get_option( 'license_key' );
if ( empty( $license_key ) ) {
return;
}
$tmp_database_path = $this->database_service->download_database( $license_key );
if ( is_wp_error( $tmp_database_path ) ) {
wc_get_logger()->notice( $tmp_database_path->get_error_message(), array( 'source' => 'maxmind-geolocation' ) );
return;
}
}
// Move the new database into position.
$wp_filesystem->move( $tmp_database_path, $target_database_path, true );
$wp_filesystem->delete( dirname( $tmp_database_path ) );
}
/**
* Performs a geolocation lookup against the MaxMind database for the given IP address.
*
* @param array $data Geolocation data.
* @param string $ip_address The IP address to geolocate.
* @return array Geolocation including country code, state, city and postcode based on an IP address.
*/
public function get_geolocation( $data, $ip_address ) {
// WooCommerce look for headers first, and at this moment could be just enough.
if ( ! empty( $data['country'] ) ) {
return $data;
}
if ( empty( $ip_address ) ) {
return $data;
}
$country_code = $this->database_service->get_iso_country_code_for_ip( $ip_address );
return array(
'country' => $country_code ? $country_code : '',
'state' => '',
'city' => '',
'postcode' => '',
);
}
/**
* Fetches the prefix for the MaxMind database file.
*
* @return string
*/
private function get_database_prefix() {
$prefix = $this->get_option( 'database_prefix' );
if ( empty( $prefix ) ) {
$prefix = wp_generate_password( 32, false );
$this->update_option( 'database_prefix', $prefix );
}
return $prefix;
}
/**
* Add missing license key notice.
*/
private function add_missing_license_key_notice() {
if ( ! class_exists( 'WC_Admin_Notices' ) ) {
include_once WC_ABSPATH . 'includes/admin/class-wc-admin-notices.php';
}
WC_Admin_Notices::add_notice( 'maxmind_license_key' );
}
/**
* Remove missing license key notice.
*/
private function remove_missing_license_key_notice() {
if ( ! class_exists( 'WC_Admin_Notices' ) ) {
include_once WC_ABSPATH . 'includes/admin/class-wc-admin-notices.php';
}
WC_Admin_Notices::remove_notice( 'maxmind_license_key' );
}
/**
* Display notice if license key is missing.
*
* @param mixed $old_value Option old value.
* @param mixed $new_value Current value.
*/
public function display_missing_license_key_notice( $old_value, $new_value ) {
if ( ! apply_filters( 'woocommerce_maxmind_geolocation_display_notices', true ) ) {
return;
}
if ( ! in_array( $new_value, array( 'geolocation', 'geolocation_ajax' ), true ) ) {
$this->remove_missing_license_key_notice();
return;
}
$license_key = $this->get_option( 'license_key' );
if ( ! empty( $license_key ) ) {
return;
}
$this->add_missing_license_key_notice();
}
}

View File

@ -0,0 +1,25 @@
<?php
/**
* Admin View: Page - Admin options.
*
* @package WooCommerce\Integrations
*/
defined( 'ABSPATH' ) || exit;
?>
<table class="form-table">
<tr valign="top">
<th scope="row" class="titledesc">
<label><?php esc_html_e( 'Database File Path', 'woocommerce' ); ?></label>
</th>
<td class="forminp">
<fieldset>
<legend class="screen-reader-text"><span><?php esc_html_e( 'Database File Path', 'woocommerce' ); ?></span></legend>
<input class="input-text regular-input" type="text" value="<?php echo esc_attr( $this->database_service->get_database_path() ); ?>" readonly>
<p class="description"><?php esc_html_e( 'The location that the MaxMind database should be stored. By default, the integration will automatically save the database here.', 'woocommerce' ); ?></p>
</fieldset>
</td>
</tr>
</table>

View File

@ -1,309 +0,0 @@
<?php
namespace MaxMind\Db;
use MaxMind\Db\Reader\Decoder;
use MaxMind\Db\Reader\InvalidDatabaseException;
use MaxMind\Db\Reader\Metadata;
use MaxMind\Db\Reader\Util;
/**
* Instances of this class provide a reader for the MaxMind DB format. IP
* addresses can be looked up using the <code>get</code> method.
*/
class Reader
{
private static $DATA_SECTION_SEPARATOR_SIZE = 16;
private static $METADATA_START_MARKER = "\xAB\xCD\xEFMaxMind.com";
private static $METADATA_START_MARKER_LENGTH = 14;
private static $METADATA_MAX_SIZE = 131072; // 128 * 1024 = 128KB
private $decoder;
private $fileHandle;
private $fileSize;
private $ipV4Start;
private $metadata;
/**
* Constructs a Reader for the MaxMind DB format. The file passed to it must
* be a valid MaxMind DB file such as a GeoIp2 database file.
*
* @param string $database
* the MaxMind DB file to use
*
* @throws \InvalidArgumentException for invalid database path or unknown arguments
* @throws \MaxMind\Db\Reader\InvalidDatabaseException
* if the database is invalid or there is an error reading
* from it
*/
public function __construct($database)
{
if (func_num_args() !== 1) {
throw new \InvalidArgumentException(
'The constructor takes exactly one argument.'
);
}
if (!is_readable($database)) {
throw new \InvalidArgumentException(
"The file \"$database\" does not exist or is not readable."
);
}
$this->fileHandle = @fopen($database, 'rb');
if ($this->fileHandle === false) {
throw new \InvalidArgumentException(
"Error opening \"$database\"."
);
}
$this->fileSize = @filesize($database);
if ($this->fileSize === false) {
throw new \UnexpectedValueException(
"Error determining the size of \"$database\"."
);
}
$start = $this->findMetadataStart($database);
$metadataDecoder = new Decoder($this->fileHandle, $start);
list($metadataArray) = $metadataDecoder->decode($start);
$this->metadata = new Metadata($metadataArray);
$this->decoder = new Decoder(
$this->fileHandle,
$this->metadata->searchTreeSize + self::$DATA_SECTION_SEPARATOR_SIZE
);
}
/**
* Looks up the <code>address</code> in the MaxMind DB.
*
* @param string $ipAddress
* the IP address to look up
*
* @throws \BadMethodCallException if this method is called on a closed database
* @throws \InvalidArgumentException if something other than a single IP address is passed to the method
* @throws InvalidDatabaseException
* if the database is invalid or there is an error reading
* from it
*
* @return array the record for the IP address
*/
public function get($ipAddress)
{
if (func_num_args() !== 1) {
throw new \InvalidArgumentException(
'Method takes exactly one argument.'
);
}
if (!is_resource($this->fileHandle)) {
throw new \BadMethodCallException(
'Attempt to read from a closed MaxMind DB.'
);
}
if (!filter_var($ipAddress, FILTER_VALIDATE_IP)) {
throw new \InvalidArgumentException(
"The value \"$ipAddress\" is not a valid IP address."
);
}
if ($this->metadata->ipVersion === 4 && strrpos($ipAddress, ':')) {
throw new \InvalidArgumentException(
"Error looking up $ipAddress. You attempted to look up an"
. ' IPv6 address in an IPv4-only database.'
);
}
$pointer = $this->findAddressInTree($ipAddress);
if ($pointer === 0) {
return null;
}
return $this->resolveDataPointer($pointer);
}
private function findAddressInTree($ipAddress)
{
// XXX - could simplify. Done as a byte array to ease porting
$rawAddress = array_merge(unpack('C*', inet_pton($ipAddress)));
$bitCount = count($rawAddress) * 8;
// The first node of the tree is always node 0, at the beginning of the
// value
$node = $this->startNode($bitCount);
for ($i = 0; $i < $bitCount; $i++) {
if ($node >= $this->metadata->nodeCount) {
break;
}
$tempBit = 0xFF & $rawAddress[$i >> 3];
$bit = 1 & ($tempBit >> 7 - ($i % 8));
$node = $this->readNode($node, $bit);
}
if ($node === $this->metadata->nodeCount) {
// Record is empty
return 0;
} elseif ($node > $this->metadata->nodeCount) {
// Record is a data pointer
return $node;
}
throw new InvalidDatabaseException('Something bad happened');
}
private function startNode($length)
{
// Check if we are looking up an IPv4 address in an IPv6 tree. If this
// is the case, we can skip over the first 96 nodes.
if ($this->metadata->ipVersion === 6 && $length === 32) {
return $this->ipV4StartNode();
}
// The first node of the tree is always node 0, at the beginning of the
// value
return 0;
}
private function ipV4StartNode()
{
// This is a defensive check. There is no reason to call this when you
// have an IPv4 tree.
if ($this->metadata->ipVersion === 4) {
return 0;
}
if ($this->ipV4Start) {
return $this->ipV4Start;
}
$node = 0;
for ($i = 0; $i < 96 && $node < $this->metadata->nodeCount; $i++) {
$node = $this->readNode($node, 0);
}
$this->ipV4Start = $node;
return $node;
}
private function readNode($nodeNumber, $index)
{
$baseOffset = $nodeNumber * $this->metadata->nodeByteSize;
// XXX - probably could condense this.
switch ($this->metadata->recordSize) {
case 24:
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 3, 3);
list(, $node) = unpack('N', "\x00" . $bytes);
return $node;
case 28:
$middleByte = Util::read($this->fileHandle, $baseOffset + 3, 1);
list(, $middle) = unpack('C', $middleByte);
if ($index === 0) {
$middle = (0xF0 & $middle) >> 4;
} else {
$middle = 0x0F & $middle;
}
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 3);
list(, $node) = unpack('N', chr($middle) . $bytes);
return $node;
case 32:
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4);
list(, $node) = unpack('N', $bytes);
return $node;
default:
throw new InvalidDatabaseException(
'Unknown record size: '
. $this->metadata->recordSize
);
}
}
private function resolveDataPointer($pointer)
{
$resolved = $pointer - $this->metadata->nodeCount
+ $this->metadata->searchTreeSize;
if ($resolved > $this->fileSize) {
throw new InvalidDatabaseException(
"The MaxMind DB file's search tree is corrupt"
);
}
list($data) = $this->decoder->decode($resolved);
return $data;
}
/*
* This is an extremely naive but reasonably readable implementation. There
* are much faster algorithms (e.g., Boyer-Moore) for this if speed is ever
* an issue, but I suspect it won't be.
*/
private function findMetadataStart($filename)
{
$handle = $this->fileHandle;
$fstat = fstat($handle);
$fileSize = $fstat['size'];
$marker = self::$METADATA_START_MARKER;
$markerLength = self::$METADATA_START_MARKER_LENGTH;
$metadataMaxLengthExcludingMarker
= min(self::$METADATA_MAX_SIZE, $fileSize) - $markerLength;
for ($i = 0; $i <= $metadataMaxLengthExcludingMarker; $i++) {
for ($j = 0; $j < $markerLength; $j++) {
fseek($handle, $fileSize - $i - $j - 1);
$matchBit = fgetc($handle);
if ($matchBit !== $marker[$markerLength - $j - 1]) {
continue 2;
}
}
return $fileSize - $i;
}
throw new InvalidDatabaseException(
"Error opening database file ($filename). " .
'Is this a valid MaxMind DB file?'
);
}
/**
* @throws \InvalidArgumentException if arguments are passed to the method
* @throws \BadMethodCallException if the database has been closed
*
* @return Metadata object for the database
*/
public function metadata()
{
if (func_num_args()) {
throw new \InvalidArgumentException(
'Method takes no arguments.'
);
}
// Not technically required, but this makes it consistent with
// C extension and it allows us to change our implementation later.
if (!is_resource($this->fileHandle)) {
throw new \BadMethodCallException(
'Attempt to read from a closed MaxMind DB.'
);
}
return $this->metadata;
}
/**
* Closes the MaxMind DB and returns resources to the system.
*
* @throws \Exception
* if an I/O error occurs
*/
public function close()
{
if (!is_resource($this->fileHandle)) {
throw new \BadMethodCallException(
'Attempt to close a closed MaxMind DB.'
);
}
fclose($this->fileHandle);
}
}

View File

@ -1,311 +0,0 @@
<?php
namespace MaxMind\Db\Reader;
class Decoder
{
private $fileStream;
private $pointerBase;
// This is only used for unit testing
private $pointerTestHack;
private $switchByteOrder;
private $types = [
0 => 'extended',
1 => 'pointer',
2 => 'utf8_string',
3 => 'double',
4 => 'bytes',
5 => 'uint16',
6 => 'uint32',
7 => 'map',
8 => 'int32',
9 => 'uint64',
10 => 'uint128',
11 => 'array',
12 => 'container',
13 => 'end_marker',
14 => 'boolean',
15 => 'float',
];
public function __construct(
$fileStream,
$pointerBase = 0,
$pointerTestHack = false
) {
$this->fileStream = $fileStream;
$this->pointerBase = $pointerBase;
$this->pointerTestHack = $pointerTestHack;
$this->switchByteOrder = $this->isPlatformLittleEndian();
}
public function decode($offset)
{
list(, $ctrlByte) = unpack(
'C',
Util::read($this->fileStream, $offset, 1)
);
$offset++;
$type = $this->types[$ctrlByte >> 5];
// Pointers are a special case, we don't read the next $size bytes, we
// use the size to determine the length of the pointer and then follow
// it.
if ($type === 'pointer') {
list($pointer, $offset) = $this->decodePointer($ctrlByte, $offset);
// for unit testing
if ($this->pointerTestHack) {
return [$pointer];
}
list($result) = $this->decode($pointer);
return [$result, $offset];
}
if ($type === 'extended') {
list(, $nextByte) = unpack(
'C',
Util::read($this->fileStream, $offset, 1)
);
$typeNum = $nextByte + 7;
if ($typeNum < 8) {
throw new InvalidDatabaseException(
'Something went horribly wrong in the decoder. An extended type '
. 'resolved to a type number < 8 ('
. $this->types[$typeNum]
. ')'
);
}
$type = $this->types[$typeNum];
$offset++;
}
list($size, $offset) = $this->sizeFromCtrlByte($ctrlByte, $offset);
return $this->decodeByType($type, $offset, $size);
}
private function decodeByType($type, $offset, $size)
{
switch ($type) {
case 'map':
return $this->decodeMap($size, $offset);
case 'array':
return $this->decodeArray($size, $offset);
case 'boolean':
return [$this->decodeBoolean($size), $offset];
}
$newOffset = $offset + $size;
$bytes = Util::read($this->fileStream, $offset, $size);
switch ($type) {
case 'utf8_string':
return [$this->decodeString($bytes), $newOffset];
case 'double':
$this->verifySize(8, $size);
return [$this->decodeDouble($bytes), $newOffset];
case 'float':
$this->verifySize(4, $size);
return [$this->decodeFloat($bytes), $newOffset];
case 'bytes':
return [$bytes, $newOffset];
case 'uint16':
case 'uint32':
return [$this->decodeUint($bytes), $newOffset];
case 'int32':
return [$this->decodeInt32($bytes), $newOffset];
case 'uint64':
case 'uint128':
return [$this->decodeBigUint($bytes, $size), $newOffset];
default:
throw new InvalidDatabaseException(
'Unknown or unexpected type: ' . $type
);
}
}
private function verifySize($expected, $actual)
{
if ($expected !== $actual) {
throw new InvalidDatabaseException(
"The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)"
);
}
}
private function decodeArray($size, $offset)
{
$array = [];
for ($i = 0; $i < $size; $i++) {
list($value, $offset) = $this->decode($offset);
array_push($array, $value);
}
return [$array, $offset];
}
private function decodeBoolean($size)
{
return $size === 0 ? false : true;
}
private function decodeDouble($bits)
{
// XXX - Assumes IEEE 754 double on platform
list(, $double) = unpack('d', $this->maybeSwitchByteOrder($bits));
return $double;
}
private function decodeFloat($bits)
{
// XXX - Assumes IEEE 754 floats on platform
list(, $float) = unpack('f', $this->maybeSwitchByteOrder($bits));
return $float;
}
private function decodeInt32($bytes)
{
$bytes = $this->zeroPadLeft($bytes, 4);
list(, $int) = unpack('l', $this->maybeSwitchByteOrder($bytes));
return $int;
}
private function decodeMap($size, $offset)
{
$map = [];
for ($i = 0; $i < $size; $i++) {
list($key, $offset) = $this->decode($offset);
list($value, $offset) = $this->decode($offset);
$map[$key] = $value;
}
return [$map, $offset];
}
private $pointerValueOffset = [
1 => 0,
2 => 2048,
3 => 526336,
4 => 0,
];
private function decodePointer($ctrlByte, $offset)
{
$pointerSize = (($ctrlByte >> 3) & 0x3) + 1;
$buffer = Util::read($this->fileStream, $offset, $pointerSize);
$offset = $offset + $pointerSize;
$packed = $pointerSize === 4
? $buffer
: (pack('C', $ctrlByte & 0x7)) . $buffer;
$unpacked = $this->decodeUint($packed);
$pointer = $unpacked + $this->pointerBase
+ $this->pointerValueOffset[$pointerSize];
return [$pointer, $offset];
}
private function decodeUint($bytes)
{
list(, $int) = unpack('N', $this->zeroPadLeft($bytes, 4));
return $int;
}
private function decodeBigUint($bytes, $byteLength)
{
$maxUintBytes = log(PHP_INT_MAX, 2) / 8;
if ($byteLength === 0) {
return 0;
}
$numberOfLongs = ceil($byteLength / 4);
$paddedLength = $numberOfLongs * 4;
$paddedBytes = $this->zeroPadLeft($bytes, $paddedLength);
$unpacked = array_merge(unpack("N$numberOfLongs", $paddedBytes));
$integer = 0;
// 2^32
$twoTo32 = '4294967296';
foreach ($unpacked as $part) {
// We only use gmp or bcmath if the final value is too big
if ($byteLength <= $maxUintBytes) {
$integer = ($integer << 32) + $part;
} elseif (extension_loaded('gmp')) {
$integer = gmp_strval(gmp_add(gmp_mul($integer, $twoTo32), $part));
} elseif (extension_loaded('bcmath')) {
$integer = bcadd(bcmul($integer, $twoTo32), $part);
} else {
throw new \RuntimeException(
'The gmp or bcmath extension must be installed to read this database.'
);
}
}
return $integer;
}
private function decodeString($bytes)
{
// XXX - NOOP. As far as I know, the end user has to explicitly set the
// encoding in PHP. Strings are just bytes.
return $bytes;
}
private function sizeFromCtrlByte($ctrlByte, $offset)
{
$size = $ctrlByte & 0x1f;
$bytesToRead = $size < 29 ? 0 : $size - 28;
$bytes = Util::read($this->fileStream, $offset, $bytesToRead);
$decoded = $this->decodeUint($bytes);
if ($size === 29) {
$size = 29 + $decoded;
} elseif ($size === 30) {
$size = 285 + $decoded;
} elseif ($size > 30) {
$size = ($decoded & (0x0FFFFFFF >> (32 - (8 * $bytesToRead))))
+ 65821;
}
return [$size, $offset + $bytesToRead];
}
private function zeroPadLeft($content, $desiredLength)
{
return str_pad($content, $desiredLength, "\x00", STR_PAD_LEFT);
}
private function maybeSwitchByteOrder($bytes)
{
return $this->switchByteOrder ? strrev($bytes) : $bytes;
}
private function isPlatformLittleEndian()
{
$testint = 0x00FF;
$packed = pack('S', $testint);
return $testint === current(unpack('v', $packed));
}
}

View File

@ -1,10 +0,0 @@
<?php
namespace MaxMind\Db\Reader;
/**
* This class should be thrown when unexpected data is found in the database.
*/
class InvalidDatabaseException extends \Exception
{
}

View File

@ -1,69 +0,0 @@
<?php
namespace MaxMind\Db\Reader;
/**
* This class provides the metadata for the MaxMind DB file.
*
* @property int nodeCount This is an unsigned 32-bit integer indicating
* the number of nodes in the search tree.
* @property int recordSize This is an unsigned 16-bit integer. It
* indicates the number of bits in a record in the search tree. Note that each
* node consists of two records.
* @property int ipVersion This is an unsigned 16-bit integer which is
* always 4 or 6. It indicates whether the database contains IPv4 or IPv6
* address data.
* @property string databaseType This is a string that indicates the structure
* of each data record associated with an IP address. The actual definition of
* these structures is left up to the database creator.
* @property array languages An array of strings, each of which is a language
* code. A given record may contain data items that have been localized to
* some or all of these languages. This may be undefined.
* @property int binaryFormatMajorVersion This is an unsigned 16-bit
* integer indicating the major version number for the database's binary
* format.
* @property int binaryFormatMinorVersion This is an unsigned 16-bit
* integer indicating the minor version number for the database's binary format.
* @property int buildEpoch This is an unsigned 64-bit integer that
* contains the database build timestamp as a Unix epoch value.
* @property array description This key will always point to a map
* (associative array). The keys of that map will be language codes, and the
* values will be a description in that language as a UTF-8 string. May be
* undefined for some databases.
*/
class Metadata
{
private $binaryFormatMajorVersion;
private $binaryFormatMinorVersion;
private $buildEpoch;
private $databaseType;
private $description;
private $ipVersion;
private $languages;
private $nodeByteSize;
private $nodeCount;
private $recordSize;
private $searchTreeSize;
public function __construct($metadata)
{
$this->binaryFormatMajorVersion =
$metadata['binary_format_major_version'];
$this->binaryFormatMinorVersion =
$metadata['binary_format_minor_version'];
$this->buildEpoch = $metadata['build_epoch'];
$this->databaseType = $metadata['database_type'];
$this->languages = $metadata['languages'];
$this->description = $metadata['description'];
$this->ipVersion = $metadata['ip_version'];
$this->nodeCount = $metadata['node_count'];
$this->recordSize = $metadata['record_size'];
$this->nodeByteSize = $this->recordSize / 4;
$this->searchTreeSize = $this->nodeCount * $this->nodeByteSize;
}
public function __get($var)
{
return $this->$var;
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace MaxMind\Db\Reader;
class Util
{
public static function read($stream, $offset, $numberOfBytes)
{
if ($numberOfBytes === 0) {
return '';
}
if (fseek($stream, $offset) === 0) {
$value = fread($stream, $numberOfBytes);
// We check that the number of bytes read is equal to the number
// asked for. We use ftell as getting the length of $value is
// much slower.
if (ftell($stream) - $offset === $numberOfBytes) {
return $value;
}
}
throw new InvalidDatabaseException(
'The MaxMind DB file contains bad data'
);
}
}

View File

@ -1839,7 +1839,7 @@ function wc_update_343_cleanup_foreign_keys() {
if ( $results ) {
foreach ( $results as $fk ) {
$wpdb->query( "ALTER TABLE {$wpdb->prefix}wc_download_log DROP FOREIGN KEY {$fk->CONSTRAINT_NAME}" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( "ALTER TABLE {$wpdb->prefix}wc_download_log DROP FOREIGN KEY {$fk->CONSTRAINT_NAME}" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
}
}
@ -2046,3 +2046,38 @@ function wc_update_370_mro_std_currency() {
function wc_update_370_db_version() {
WC_Install::update_db_version( '3.7.0' );
}
/**
* We've moved the MaxMind database to a new location, as per the TOS' requirement that the database not
* be publicly accessible.
*/
function wc_update_390_move_maxmind_database() {
// Make sure to use all of the correct filters to pull the local database path.
$old_path = apply_filters( 'woocommerce_geolocation_local_database_path', WP_CONTENT_DIR . '/uploads/GeoLite2-Country.mmdb', 2 );
// Generate a prefix for the old file and store it in the integration as it would expect it.
$prefix = wp_generate_password( 32, false );
update_option( 'woocommerce_maxmind_geolocation_settings', array( 'database_prefix' => $prefix ) );
// Generate the new path in the same way that the integration will.
$uploads_dir = wp_upload_dir();
$new_path = trailingslashit( $uploads_dir['basedir'] ) . 'woocommerce_uploads/' . $prefix . '-GeoLite2-Country.mmdb';
$new_path = apply_filters( 'woocommerce_geolocation_local_database_path', $new_path, 2 );
@rename( $old_path, $new_path ); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
}
/**
* So that we can best meet MaxMind's TOS, the geolocation database update cron should run once per 15 days.
*/
function wc_update_390_change_geolocation_database_update_cron() {
wp_clear_scheduled_hook( 'woocommerce_geoip_updater' );
wp_schedule_event( time() + ( DAY_IN_SECONDS * 15 ), 'fifteendays', 'woocommerce_geoip_updater' );
}
/**
* Update DB version.
*/
function wc_update_390_db_version() {
WC_Install::update_db_version( '3.9.0' );
}

166
package-lock.json generated
View File

@ -3608,12 +3608,6 @@
"integrity": "sha512-Jrb/x3HT4PTJp6a4avhmJCDEVrPdqLfl3e8GGMbpkGGdwAV5UGlIs4vVEfsHHfylZVOKZWpOqmqFH8CbfOZ6kg==",
"dev": true
},
"@types/normalize-package-data": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
"integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==",
"dev": true
},
"@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
@ -6252,9 +6246,9 @@
},
"dependencies": {
"commander": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
"integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"lru-cache": {
@ -6876,9 +6870,9 @@
}
},
"fault": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/fault/-/fault-1.0.2.tgz",
"integrity": "sha512-o2eo/X2syzzERAtN5LcGbiVQ0WwZSlN3qLtadwAz3X8Bu+XWD16dja/KMsjZLiQr+BLGPDnHGkc4yUJf1Xpkpw==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/fault/-/fault-1.0.3.tgz",
"integrity": "sha512-sfFuP4X0hzrbGKjAUNXYvNqsZ5F6ohx/dZ9I0KQud/aiZNwg263r5L9yGB0clvXHCkzXh5W3t7RSHchggYIFmA==",
"dev": true,
"requires": {
"format": "^0.2.2"
@ -7126,9 +7120,9 @@
"dev": true
},
"flatten": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz",
"integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz",
"integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==",
"dev": true
},
"flow-parser": {
@ -8679,9 +8673,9 @@
}
},
"handlebars": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz",
"integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==",
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz",
"integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==",
"dev": true,
"requires": {
"neo-async": "^2.6.0",
@ -8922,28 +8916,73 @@
}
},
"husky": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/husky/-/husky-3.1.0.tgz",
"integrity": "sha512-FJkPoHHB+6s4a+jwPqBudBDvYZsoQW5/HBuMSehC8qDiCe50kpcxeqFoDSlow+9I6wg47YxBoT3WxaURlrDIIQ==",
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/husky/-/husky-4.0.10.tgz",
"integrity": "sha512-Ptm4k2DqOwxeK/kzu5RaJmNRoGvESrgDXObFcZ8aJZcyXyMBHhM2FqZj6zYKdetadmP3wCwxEHCBuB9xGlRp8A==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
"chalk": "^3.0.0",
"ci-info": "^2.0.0",
"cosmiconfig": "^5.2.1",
"execa": "^1.0.0",
"get-stdin": "^7.0.0",
"cosmiconfig": "^6.0.0",
"opencollective-postinstall": "^2.0.2",
"pkg-dir": "^4.2.0",
"please-upgrade-node": "^3.2.0",
"read-pkg": "^5.2.0",
"run-node": "^1.0.0",
"slash": "^3.0.0"
"slash": "^3.0.0",
"which-pm-runs": "^1.0.0"
},
"dependencies": {
"get-stdin": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz",
"integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==",
"ansi-styles": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
"dev": true,
"requires": {
"@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"cosmiconfig": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
"dev": true,
"requires": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.1.0",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.7.2"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"parse-json": {
@ -8958,23 +8997,20 @@
"lines-and-columns": "^1.1.6"
}
},
"read-pkg": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
"integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true
},
"supports-color": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
"integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
"dev": true,
"requires": {
"@types/normalize-package-data": "^2.4.0",
"normalize-package-data": "^2.5.0",
"parse-json": "^5.0.0",
"type-fest": "^0.6.0"
"has-flag": "^4.0.0"
}
},
"type-fest": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
"integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
"dev": true
}
}
},
@ -11662,6 +11698,15 @@
"unist-util-visit": "^1.1.0"
}
},
"mem": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
"integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=",
"dev": true,
"requires": {
"mimic-fn": "^1.0.0"
}
},
"memize": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/memize/-/memize-1.0.5.tgz",
@ -13122,15 +13167,6 @@
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
"dev": true
},
"mem": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
"integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=",
"dev": true,
"requires": {
"mimic-fn": "^1.0.0"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
@ -13180,14 +13216,6 @@
"requires": {
"ansi-regex": "^3.0.0",
"ansi-styles": "^3.2.0"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
}
}
},
"private": {
@ -13994,12 +14022,6 @@
"is-promise": "^2.1.0"
}
},
"run-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz",
"integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==",
"dev": true
},
"run-parallel": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz",
@ -16628,6 +16650,12 @@
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"which-pm-runs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=",
"dev": true
},
"wide-align": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",

View File

@ -53,7 +53,7 @@
"grunt-shell": "3.0.1",
"grunt-stylelint": "0.13.0",
"grunt-wp-i18n": "1.0.3",
"husky": "3.1.0",
"husky": "4.0.10",
"istanbul": "1.0.0-alpha.2",
"jest": "24.9.0",
"jest-puppeteer": "4.4.0",

View File

@ -201,6 +201,7 @@ INTERESTED IN DEVELOPMENT?
* Tweak - Include processing orders in tracker data when opted in. #25071
* Tweak - Centralize check for default themes to fix Storefront appearance in the Setup Wizard. #25216
* Tweak - Adds a WordPress version check before recommending the WooCommerce Admin plugin during setup. #25260
* Fix - Added license key support recent changes from MaxMind GeoLite2. #25378
* Fix - Honor tax rounding preference in edit item and refund flows. #24208
* Fix - Prevent incorrect number of decimal points in prices. #24281
* Fix - Fixed initial support for Gutenberg's Experimental Legacy Widget block. #24292

Binary file not shown.

View File

@ -1,40 +0,0 @@
<?php
/**
* Class Functions.
*
* @package WooCommerce\Tests\Geolocation
*/
/**
* Class WC_Tests_Integrations
*/
class WC_Tests_Geolite_Integration extends WC_Unit_Test_Case {
/**
* Test get country ISO.
*
* @requires PHP 5.4
*/
public function test_get_country_iso() {
require_once dirname( WC_PLUGIN_FILE ) . '/includes/class-wc-geolite-integration.php';
// Download GeoLite2 database.
WC_Geolocation::update_database();
// OpenDNS IP address.
$ipv4 = '208.67.220.220';
$ipv6 = '2620:0:ccc::2';
// Init GeoLite.
$geolite = new WC_Geolite_Integration( WC_Geolocation::get_local_database_path() );
// Check for IPv4.
$this->assertEquals( 'US', $geolite->get_country_iso( $ipv4 ) );
// Check for IPv6.
$this->assertEquals( 'US', $geolite->get_country_iso( $ipv6 ) );
// Check for non-valid IP.
$this->assertEquals( '', $geolite->get_country_iso( 'foobar' ) );
}
}

View File

@ -30,8 +30,8 @@ class WC_Tests_Integrations extends WC_Unit_Test_Case {
*/
public function test_filter() {
$integrations = new WC_Integrations();
$this->assertEquals( array(), $integrations->integrations );
$this->assertEquals( array(), $integrations->get_integrations() );
$this->assertArrayHasKey( 'maxmind_geolocation', $integrations->integrations );
$this->assertArrayHasKey( 'maxmind_geolocation', $integrations->get_integrations() );
require_once dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'class-dummy-integration.php';

View File

@ -0,0 +1,146 @@
<?php
/**
* Class Functions.
*
* @package WooCommerce\Tests\Integrations
*/
/**
* Class WC_Tests_MaxMind_Database
*/
class WC_Tests_MaxMind_Database extends WC_Unit_Test_Case {
/**
* Run setup code for unit tests.
*/
public function setUp() {
parent::setUp();
// Callback used by WP_HTTP_TestCase to decide whether to perform HTTP requests or to provide a mocked response.
$this->http_responder = array( $this, 'mock_http_responses' );
}
/**
* Tests that the database path filters work as intended.
*
* @expectedDeprecated woocommerce_geolocation_local_database_path
*/
public function test_database_path_filters() {
$database_service = new WC_Integration_MaxMind_Database_Service( '' );
$path = $database_service->get_database_path();
$this->assertEquals( WP_CONTENT_DIR . '/uploads/woocommerce_uploads/' . WC_Integration_MaxMind_Database_Service::DATABASE . WC_Integration_MaxMind_Database_Service::DATABASE_EXTENSION, $path );
add_filter( 'woocommerce_geolocation_local_database_path', array( $this, 'filter_database_path_deprecated' ), 1, 2 );
$path = $database_service->get_database_path();
remove_filter( 'woocommerce_geolocation_local_database_path', array( $this, 'filter_database_path_deprecated' ), 1 );
$this->assertEquals( '/deprecated_filter', $path );
add_filter( 'woocommerce_geolocation_local_database_path', array( $this, 'filter_database_path' ) );
$path = $database_service->get_database_path();
remove_filter( 'woocommerce_geolocation_local_database_path', array( $this, 'filter_database_path' ) );
$this->assertEquals( '/filter', $path );
// Now perform any tests with a database file prefix.
$database_service = new WC_Integration_MaxMind_Database_Service( 'testing' );
$path = $database_service->get_database_path();
$this->assertEquals( WP_CONTENT_DIR . '/uploads/woocommerce_uploads/testing-' . WC_Integration_MaxMind_Database_Service::DATABASE . WC_Integration_MaxMind_Database_Service::DATABASE_EXTENSION, $path );
}
/**
* Tests that the database download works as expected.
*/
public function test_download_database_works() {
$database_service = new WC_Integration_MaxMind_Database_Service( '' );
$expected_database = '/tmp/GeoLite2-Country_20200100/GeoLite2-Country.mmdb';
$result = $database_service->download_database( 'testing_license' );
$this->assertEquals( $expected_database, $result );
// Remove the downloaded file and folder.
unlink( $expected_database );
rmdir( dirname( $expected_database ) );
}
/**
* Tests the that database download wraps the download and extraction errors.
*/
public function test_download_database_wraps_errors() {
$database_service = new WC_Integration_MaxMind_Database_Service( '' );
$result = $database_service->download_database( 'invalid_license' );
$this->assertWPError( $result );
$this->assertEquals( 'woocommerce_maxmind_geolocation_database_license_key', $result->get_error_code() );
$result = $database_service->download_database( 'generic_error' );
$this->assertWPError( $result );
$this->assertEquals( 'woocommerce_maxmind_geolocation_database_download', $result->get_error_code() );
$result = $database_service->download_database( 'archive_error' );
$this->assertWPError( $result );
$this->assertEquals( 'woocommerce_maxmind_geolocation_database_archive', $result->get_error_code() );
}
/**
* Hook for the deprecated database path filter.
*
* @param string $database_path The path to the database file.
* @param string $deprecated Deprecated since 3.4.0.
* @return string
*/
public function filter_database_path_deprecated( $database_path, $deprecated ) {
return '/deprecated_filter';
}
/**
* Hook for the database path filter.
*
* @param string $database_path The path to the database file.
* @return string
*/
public function filter_database_path( $database_path ) {
return '/filter';
}
/**
* Helper method to define mocked HTTP responses using WP_HTTP_TestCase.
* Thanks to WP_HTTP_TestCase, it is not necessary to perform a regular request
* to an external server which would significantly slow down the tests.
*
* This function is called by WP_HTTP_TestCase::http_request_listner().
*
* @param array $request Request arguments.
* @param string $url URL of the request.
*
* @return array|WP_Error|false mocked response, error, or false to let WP perform a regular request.
*/
protected function mock_http_responses( $request, $url ) {
$mocked_response = false;
if ( 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=testing_license&suffix=tar.gz' === $url ) {
// We need to copy the file to where the request is supposed to have streamed it.
copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/GeoLite2-Country.tar.gz', $request['filename'] );
$mocked_response = array(
'response' => array( 'code' => 200 ),
);
} elseif ( 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=invalid_license&suffix=tar.gz' === $url ) {
return new WP_Error( 'http_404', 'Unauthorized', array( 'code' => 401 ) );
} elseif ( 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=generic_error&suffix=tar.gz' === $url ) {
return new WP_Error( 'http_404', 'Unauthorized', array( 'code' => 500 ) );
} elseif ( 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=archive_error&suffix=tar.gz' === $url ) {
$mocked_response = array(
'response' => array( 'code' => 200 ),
);
}
return $mocked_response;
}
}

View File

@ -0,0 +1,125 @@
<?php
/**
* Class Functions.
*
* @package WooCommerce\Tests\Integrations
*/
/**
* Class WC_Tests_MaxMind_Integration
*/
class WC_Tests_MaxMind_Integration extends WC_Unit_Test_Case {
/**
* The mock database service that our integration class will utilize.
*
* @var WC_Integration_MaxMind_Database_Service|\PHPUnit\Framework\MockObject\MockObject
*/
private $database_service;
/**
* Run setup code for unit tests.
*/
public function setUp() {
parent::setUp();
// Override the filesystem method that we're using.
add_filter( 'filesystem_method', array( $this, 'override_filesystem_method' ) );
// Have a mock service be used by all integrations.
$this->database_service = $this->getMockBuilder( 'WC_Integration_maxMind_Database_Service' )
->disableOriginalConstructor()
->getMock();
add_filter( 'woocommerce_maxmind_geolocation_database_service', array( $this, 'override_integration_service' ) );
}
/**
* Make sure that the database is not updated if no target database path is given.
*/
public function test_update_database_does_nothing_without_database_path() {
$this->database_service->expects( $this->once() )
->method( 'get_database_path' )
->willReturn( '' );
( new WC_Integration_MaxMind_Geolocation() )->update_database();
}
/**
* Makes sure that the database can be updated to a given database.
*/
public function test_update_database_to_parameter_file() {
$this->database_service->expects( $this->once() )
->method( 'get_database_path' )
->willReturn( '/testing' );
( new WC_Integration_MaxMind_Geolocation() )->update_database( '/tmp/noop.mmdb' );
}
/**
* Makes sure that the integration uses the license key correctly.
*/
public function test_update_database_uses_license_key() {
$this->database_service->expects( $this->once() )
->method( 'get_database_path' )
->willReturn( '/testing' );
$this->database_service->expects( $this->once() )
->method( 'download_database' )
->with( 'test_license' )
->willReturn( '/tmp/' . WC_Integration_MaxMind_Database_Service::DATABASE . '.' . WC_Integration_MaxMind_Database_Service::DATABASE_EXTENSION );
$integration = new WC_Integration_MaxMind_Geolocation();
$integration->update_option( 'license_key', 'test_license' );
$integration->update_database();
}
/**
* Make sure that the geolocate_ip method does not squash existing country codes.
*/
public function test_geolocate_ip_returns_existing_country_code() {
$data = ( new WC_Integration_MaxMind_Geolocation() )->get_geolocation( array( 'country' => 'US' ), '192.168.1.1' );
$this->assertEquals( 'US', $data['country'] );
}
/**
* Make sure that the geolocate_ip method does nothing if IP is not set.
*/
public function test_geolocate_ip_returns_empty_without_ip_address() {
$data = ( new WC_Integration_MaxMind_Geolocation() )->get_geolocation( array(), '' );
$this->assertEmpty( $data );
}
/**
* Make sure that the geolocate_ip method uses the appropriate service methods..
*/
public function test_geolocate_ip_uses_service() {
$this->database_service->expects( $this->once() )
->method( 'get_iso_country_code_for_ip' )
->with( '192.168.1.1' )
->willReturn( 'US' );
$data = ( new WC_Integration_MaxMind_Geolocation() )->get_geolocation( array(), '192.168.1.1' );
$this->assertEquals( 'US', $data['country'] );
}
/**
* Overrides the filesystem method.
*
* @return string
*/
public function override_filesystem_method() {
return 'Base';
}
/**
* Overrides the database service used by the integration.
*
* @return mixed
*/
public function override_integration_service() {
return $this->database_service;
}
}

View File

@ -60,19 +60,6 @@ class WC_Tests_Shipping extends WC_Unit_Test_Case {
);
$this->assertFalse( $result );
// Failure for invalid postcode.
$result = $shipping->is_package_shippable(
array(
'destination' => array(
'country' => 'US',
'state' => 'CA',
'postcode' => 'test',
'address' => '',
),
)
);
$this->assertFalse( $result );
// Success for correct address.
$result = $shipping->is_package_shippable(
array(

View File

@ -82,8 +82,12 @@ class WC_Tests_API_Functions extends WC_Unit_Test_Case {
*/
public function test_wc_rest_upload_image_from_url_should_return_error_when_invalid_image_is_passed() {
// empty file.
$expected_error_message = 'Invalid image: File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini or by post_max_size being defined as smaller than upload_max_filesize in php.ini.';
$result = wc_rest_upload_image_from_url( 'http://somedomain.com/invalid-image-1.png' );
if ( version_compare( get_bloginfo( 'version' ), '5.4-alpha', '>=' ) ) {
$expected_error_message = 'Invalid image: File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini file or by post_max_size being defined as smaller than upload_max_filesize in php.ini.';
} else {
$expected_error_message = 'Invalid image: File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini or by post_max_size being defined as smaller than upload_max_filesize in php.ini.';
}
$result = wc_rest_upload_image_from_url( 'http://somedomain.com/invalid-image-1.png' );
$this->assertWPError( $result );
$this->assertEquals( $expected_error_message, $result->get_error_message() );

View File

@ -3,7 +3,7 @@
* Plugin Name: WooCommerce
* Plugin URI: https://woocommerce.com/
* Description: An eCommerce toolkit that helps you sell anything. Beautifully.
* Version: 3.9.0-rc.2
* Version: 3.9.0-rc.3
* Author: Automattic
* Author URI: https://woocommerce.com
* Text Domain: woocommerce