8889841cPK K[AðMÉ É class-wc-cli.phpnu „[µü¤ includes();
$this->hooks();
}
/**
* Load command files.
*/
private function includes() {
require_once dirname( __FILE__ ) . '/cli/class-wc-cli-runner.php';
require_once dirname( __FILE__ ) . '/cli/class-wc-cli-rest-command.php';
require_once dirname( __FILE__ ) . '/cli/class-wc-cli-tool-command.php';
require_once dirname( __FILE__ ) . '/cli/class-wc-cli-update-command.php';
require_once dirname( __FILE__ ) . '/cli/class-wc-cli-tracker-command.php';
require_once dirname( __FILE__ ) . '/cli/class-wc-cli-com-command.php';
require_once dirname( __FILE__ ) . '/cli/class-wc-cli-com-extension-command.php';
}
/**
* Sets up and hooks WP CLI to our CLI code.
*/
private function hooks() {
WP_CLI::add_hook( 'after_wp_load', 'WC_CLI_Runner::after_wp_load' );
WP_CLI::add_hook( 'after_wp_load', 'WC_CLI_Tool_Command::register_commands' );
WP_CLI::add_hook( 'after_wp_load', 'WC_CLI_Update_Command::register_commands' );
WP_CLI::add_hook( 'after_wp_load', 'WC_CLI_Tracker_Command::register_commands' );
WP_CLI::add_hook( 'after_wp_load', 'WC_CLI_COM_Command::register_commands' );
WP_CLI::add_hook( 'after_wp_load', 'WC_CLI_COM_Extension_Command::register_commands' );
$cli_runner = wc_get_container()->get( CLIRunner::class );
WP_CLI::add_hook( 'after_wp_load', array( $cli_runner, 'register_commands' ) );
}
}
new WC_CLI();
PK K[ÈÛ4´x
x
class-wc-cart-fees.phpnu „[µü¤ cart->fees_api() which will reference this class.
*
* We suggest using the action woocommerce_cart_calculate_fees hook for adding fees.
*
* @package WooCommerce\Classes
* @version 3.2.0
*/
defined( 'ABSPATH' ) || exit;
/**
* WC_Cart_Fees class.
*
* @since 3.2.0
*/
final class WC_Cart_Fees {
/**
* An array of fee objects.
*
* @var object[]
*/
private $fees = array();
/**
* New fees are made out of these props.
*
* @var array
*/
private $default_fee_props = array(
'id' => '',
'name' => '',
'tax_class' => '',
'taxable' => false,
'amount' => 0,
'total' => 0,
);
/**
* Constructor. Reference to the cart.
*
* @param null $deprecated Deprecated since WooCommerce 8.2.0.
*
* @since 3.2.0
*/
public function __construct( $deprecated = null ) {
if ( isset( $deprecated ) ) {
wc_doing_it_wrong(
'new WC_Cart_Fees',
'You don\'t need to pass a cart parameter to the WC_Cart_Fees constructor anymore',
'8.2.0'
);
}
}
/**
* Register methods for this object on the appropriate WordPress hooks.
*/
public function init() {}
/**
* Add a fee. Fee IDs must be unique.
*
* @since 3.2.0
* @param array $args Array of fee properties.
* @return object Either a fee object if added, or a WP_Error if it failed.
*/
public function add_fee( $args = array() ) {
$fee_props = (object) wp_parse_args( $args, $this->default_fee_props );
$fee_props->name = $fee_props->name ? $fee_props->name : __( 'Fee', 'woocommerce' );
$fee_props->tax_class = in_array( $fee_props->tax_class, array_merge( WC_Tax::get_tax_classes(), WC_Tax::get_tax_class_slugs() ), true ) ? $fee_props->tax_class : '';
$fee_props->taxable = wc_string_to_bool( $fee_props->taxable );
$fee_props->amount = wc_format_decimal( $fee_props->amount );
if ( empty( $fee_props->id ) ) {
$fee_props->id = $this->generate_id( $fee_props );
}
if ( array_key_exists( $fee_props->id, $this->fees ) ) {
return new WP_Error( 'fee_exists', __( 'Fee has already been added.', 'woocommerce' ) );
}
$this->fees[ $fee_props->id ] = $fee_props;
return $this->fees[ $fee_props->id ];
}
/**
* Get fees.
*
* @return array
*/
public function get_fees() {
uasort( $this->fees, array( $this, 'sort_fees_callback' ) );
return $this->fees;
}
/**
* Set fees.
*
* @param object[] $raw_fees Array of fees.
*/
public function set_fees( $raw_fees = array() ) {
$this->fees = array();
foreach ( $raw_fees as $raw_fee ) {
$this->add_fee( $raw_fee );
}
}
/**
* Remove all fees.
*
* @since 3.2.0
*/
public function remove_all_fees() {
$this->set_fees();
}
/**
* Sort fees by amount.
*
* @param stdClass $a Fee object.
* @param stdClass $b Fee object.
* @return int
*/
protected function sort_fees_callback( $a, $b ) {
/**
* Filter sort fees callback.
*
* @since 3.8.0
* @param int Sort order, -1 or 1.
* @param stdClass $a Fee object.
* @param stdClass $b Fee object.
*/
return apply_filters( 'woocommerce_sort_fees_callback', $a->amount > $b->amount ? -1 : 1, $a, $b );
}
/**
* Generate a unique ID for the fee being added.
*
* @param string $fee Fee object.
* @return string fee key.
*/
private function generate_id( $fee ) {
return sanitize_title( $fee->name );
}
}
PK K[•P-zšÂ šÂ class-wc-countries.phpnu „[µü¤ get_countries();
} elseif ( 'states' === $key ) {
return $this->get_states();
} elseif ( 'continents' === $key ) {
return $this->get_continents();
}
}
/**
* Get all countries.
*
* @return array
*/
public function get_countries() {
if ( empty( $this->geo_cache['countries'] ) ) {
/**
* Allows filtering of the list of countries in WC.
*
* @since 1.5.3
*
* @param array $countries
*/
$this->geo_cache['countries'] = apply_filters( 'woocommerce_countries', include WC()->plugin_path() . '/i18n/countries.php' );
if ( apply_filters( 'woocommerce_sort_countries', true ) ) {
wc_asort_by_locale( $this->geo_cache['countries'] );
}
}
return $this->geo_cache['countries'];
}
/**
* Check if a given code represents a valid ISO 3166-1 alpha-2 code for a country known to us.
*
* @since 5.1.0
* @param string $country_code The country code to check as a ISO 3166-1 alpha-2 code.
* @return bool True if the country is known to us, false otherwise.
*/
public function country_exists( $country_code ) {
return isset( $this->get_countries()[ $country_code ] );
}
/**
* Get all continents.
*
* @return array
*/
public function get_continents() {
if ( empty( $this->geo_cache['continents'] ) ) {
/**
* Allows filtering of continents in WC.
*
* @since 2.6.0
*
* @param array[array] $continents
*/
$this->geo_cache['continents'] = apply_filters( 'woocommerce_continents', include WC()->plugin_path() . '/i18n/continents.php' );
}
return $this->geo_cache['continents'];
}
/**
* Get continent code for a country code.
*
* @since 2.6.0
* @param string $cc Country code.
* @return string
*/
public function get_continent_code_for_country( $cc ) {
$cc = trim( strtoupper( $cc ) );
$continents = $this->get_continents();
$continents_and_ccs = wp_list_pluck( $continents, 'countries' );
foreach ( $continents_and_ccs as $continent_code => $countries ) {
if ( false !== array_search( $cc, $countries, true ) ) {
return $continent_code;
}
}
return '';
}
/**
* Get calling code for a country code.
*
* @since 3.6.0
* @param string $cc Country code.
* @return string|array Some countries have multiple. The code will be stripped of - and spaces and always be prefixed with +.
*/
public function get_country_calling_code( $cc ) {
$codes = wp_cache_get( 'calling-codes', 'countries' );
if ( ! $codes ) {
$codes = include WC()->plugin_path() . '/i18n/phone.php';
wp_cache_set( 'calling-codes', $codes, 'countries' );
}
$calling_code = isset( $codes[ $cc ] ) ? $codes[ $cc ] : '';
if ( is_array( $calling_code ) ) {
$calling_code = $calling_code[0];
}
return $calling_code;
}
/**
* Get continents that the store ships to.
*
* @since 3.6.0
* @return array
*/
public function get_shipping_continents() {
$continents = $this->get_continents();
$shipping_countries = $this->get_shipping_countries();
$shipping_country_codes = array_keys( $shipping_countries );
$shipping_continents = array();
foreach ( $continents as $continent_code => $continent ) {
if ( count( array_intersect( $continent['countries'], $shipping_country_codes ) ) ) {
$shipping_continents[ $continent_code ] = $continent;
}
}
return $shipping_continents;
}
/**
* Load the states.
*
* @deprecated 3.6.0 This method was used to load state files, but is no longer needed. @see get_states().
*/
public function load_country_states() {
global $states;
$states = include WC()->plugin_path() . '/i18n/states.php';
/**
* Allows filtering of country states in WC.
*
* @since 1.5.3
*
* @param array $states
*/
$this->geo_cache['states'] = apply_filters( 'woocommerce_states', $states );
}
/**
* Get the states for a country.
*
* @param string $cc Country code.
* @return false|array of states
*/
public function get_states( $cc = null ) {
if ( ! isset( $this->geo_cache['states'] ) ) {
/**
* Allows filtering of country states in WC.
*
* @since 1.5.3
*
* @param array $states
*/
$this->geo_cache['states'] = apply_filters( 'woocommerce_states', include WC()->plugin_path() . '/i18n/states.php' );
}
if ( ! is_null( $cc ) ) {
return isset( $this->geo_cache['states'][ $cc ] ) ? $this->geo_cache['states'][ $cc ] : false;
} else {
return $this->geo_cache['states'];
}
}
/**
* Get the base address (first line) for the store.
*
* @since 3.1.1
* @return string
*/
public function get_base_address() {
$base_address = get_option( 'woocommerce_store_address', '' );
return apply_filters( 'woocommerce_countries_base_address', $base_address );
}
/**
* Get the base address (second line) for the store.
*
* @since 3.1.1
* @return string
*/
public function get_base_address_2() {
$base_address_2 = get_option( 'woocommerce_store_address_2', '' );
return apply_filters( 'woocommerce_countries_base_address_2', $base_address_2 );
}
/**
* Get the base country for the store.
*
* @return string
*/
public function get_base_country() {
$default = wc_get_base_location();
return apply_filters( 'woocommerce_countries_base_country', $default['country'] );
}
/**
* Get the base state for the store.
*
* @return string
*/
public function get_base_state() {
$default = wc_get_base_location();
return apply_filters( 'woocommerce_countries_base_state', $default['state'] );
}
/**
* Get the base city for the store.
*
* @version 3.1.1
* @return string
*/
public function get_base_city() {
$base_city = get_option( 'woocommerce_store_city', '' );
return apply_filters( 'woocommerce_countries_base_city', $base_city );
}
/**
* Get the base postcode for the store.
*
* @since 3.1.1
* @return string
*/
public function get_base_postcode() {
$base_postcode = get_option( 'woocommerce_store_postcode', '' );
return apply_filters( 'woocommerce_countries_base_postcode', $base_postcode );
}
/**
* Get countries that the store sells to.
*
* @return array
*/
public function get_allowed_countries() {
if ( 'all' === get_option( 'woocommerce_allowed_countries' ) ) {
return apply_filters( 'woocommerce_countries_allowed_countries', $this->countries );
}
if ( 'all_except' === get_option( 'woocommerce_allowed_countries' ) ) {
$except_countries = get_option( 'woocommerce_all_except_countries', array() );
if ( ! $except_countries ) {
return $this->countries;
} else {
$all_except_countries = $this->countries;
foreach ( $except_countries as $country ) {
unset( $all_except_countries[ $country ] );
}
return apply_filters( 'woocommerce_countries_allowed_countries', $all_except_countries );
}
}
$countries = array();
$raw_countries = get_option( 'woocommerce_specific_allowed_countries', array() );
if ( $raw_countries ) {
foreach ( $raw_countries as $country ) {
$countries[ $country ] = $this->countries[ $country ];
}
}
return apply_filters( 'woocommerce_countries_allowed_countries', $countries );
}
/**
* Get countries that the store ships to.
*
* @return array
*/
public function get_shipping_countries() {
if ( '' === get_option( 'woocommerce_ship_to_countries' ) ) {
return $this->get_allowed_countries();
}
if ( 'all' === get_option( 'woocommerce_ship_to_countries' ) ) {
return $this->countries;
}
$countries = array();
$raw_countries = get_option( 'woocommerce_specific_ship_to_countries' );
if ( $raw_countries ) {
foreach ( $raw_countries as $country ) {
$countries[ $country ] = $this->countries[ $country ];
}
}
return apply_filters( 'woocommerce_countries_shipping_countries', $countries );
}
/**
* Get allowed country states.
*
* @return array
*/
public function get_allowed_country_states() {
if ( get_option( 'woocommerce_allowed_countries' ) !== 'specific' ) {
return $this->states;
}
$states = array();
$raw_countries = get_option( 'woocommerce_specific_allowed_countries' );
if ( $raw_countries ) {
foreach ( $raw_countries as $country ) {
if ( isset( $this->states[ $country ] ) ) {
$states[ $country ] = $this->states[ $country ];
}
}
}
return apply_filters( 'woocommerce_countries_allowed_country_states', $states );
}
/**
* Get shipping country states.
*
* @return array
*/
public function get_shipping_country_states() {
if ( get_option( 'woocommerce_ship_to_countries' ) === '' ) {
return $this->get_allowed_country_states();
}
if ( get_option( 'woocommerce_ship_to_countries' ) !== 'specific' ) {
return $this->states;
}
$states = array();
$raw_countries = get_option( 'woocommerce_specific_ship_to_countries' );
if ( $raw_countries ) {
foreach ( $raw_countries as $country ) {
if ( ! empty( $this->states[ $country ] ) ) {
$states[ $country ] = $this->states[ $country ];
}
}
}
return apply_filters( 'woocommerce_countries_shipping_country_states', $states );
}
/**
* Gets an array of countries in the EU.
*
* @param string $type Type of countries to retrieve. Blank for EU member countries. eu_vat for EU VAT countries.
* @return string[]
*/
public function get_european_union_countries( $type = '' ) {
$countries = array( 'AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK' );
if ( 'eu_vat' === $type ) {
$countries[] = 'MC';
}
return apply_filters( 'woocommerce_european_union_countries', $countries, $type );
}
/**
* Gets an array of Non-EU countries that use VAT as the Local name for their taxes based on this list - https://en.wikipedia.org/wiki/Value-added_tax#Non-European_Union_countries
*
* @deprecated 4.0.0
* @since 3.9.0
* @return string[]
*/
public function countries_using_vat() {
wc_deprecated_function( 'countries_using_vat', '4.0', 'WC_Countries::get_vat_countries' );
$countries = array( 'AE', 'AL', 'AR', 'AZ', 'BB', 'BH', 'BO', 'BS', 'BY', 'CL', 'CO', 'EC', 'EG', 'ET', 'FJ', 'GH', 'GM', 'GT', 'IL', 'IN', 'IR', 'KN', 'KR', 'KZ', 'LK', 'MD', 'ME', 'MK', 'MN', 'MU', 'MX', 'NA', 'NG', 'NP', 'PS', 'PY', 'RS', 'RU', 'RW', 'SA', 'SV', 'TH', 'TR', 'UA', 'UY', 'UZ', 'VE', 'VN', 'ZA' );
return apply_filters( 'woocommerce_countries_using_vat', $countries );
}
/**
* Gets an array of countries using VAT.
*
* @since 4.0.0
* @return string[] of country codes.
*/
public function get_vat_countries() {
$eu_countries = $this->get_european_union_countries();
$vat_countries = array( 'AE', 'AL', 'AR', 'AZ', 'BB', 'BH', 'BO', 'BS', 'BY', 'CL', 'CO', 'EC', 'EG', 'ET', 'FJ', 'GB', 'GH', 'GM', 'GT', 'IL', 'IM', 'IN', 'IR', 'KN', 'KR', 'KZ', 'LK', 'MC', 'MD', 'ME', 'MK', 'MN', 'MU', 'MX', 'NA', 'NG', 'NO', 'NP', 'PS', 'PY', 'RS', 'RU', 'RW', 'SA', 'SV', 'TH', 'TR', 'UA', 'UY', 'UZ', 'VE', 'VN', 'ZA' );
return apply_filters( 'woocommerce_vat_countries', array_merge( $eu_countries, $vat_countries ) );
}
/**
* Gets the correct string for shipping - either 'to the' or 'to'.
*
* @param string $country_code Country code.
* @return string
*/
public function shipping_to_prefix( $country_code = '' ) {
$country_code = $country_code ? $country_code : WC()->customer->get_shipping_country();
$countries = array( 'AE', 'CZ', 'DO', 'GB', 'NL', 'PH', 'US', 'USAF' );
$return = in_array( $country_code, $countries, true ) ? _x( 'to the', 'shipping country prefix', 'woocommerce' ) : _x( 'to', 'shipping country prefix', 'woocommerce' );
return apply_filters( 'woocommerce_countries_shipping_to_prefix', $return, $country_code );
}
/**
* Prefix certain countries with 'the'.
*
* @param string $country_code Country code.
* @return string
*/
public function estimated_for_prefix( $country_code = '' ) {
$country_code = $country_code ? $country_code : $this->get_base_country();
$countries = array( 'AE', 'CZ', 'DO', 'GB', 'NL', 'PH', 'US', 'USAF' );
$return = in_array( $country_code, $countries, true ) ? __( 'the', 'woocommerce' ) . ' ' : '';
return apply_filters( 'woocommerce_countries_estimated_for_prefix', $return, $country_code );
}
/**
* Correctly name tax in some countries VAT on the frontend.
*
* @return string
*/
public function tax_or_vat() {
$return = in_array( $this->get_base_country(), $this->get_vat_countries(), true ) ? __( 'VAT', 'woocommerce' ) : __( 'Tax', 'woocommerce' );
return apply_filters( 'woocommerce_countries_tax_or_vat', $return );
}
/**
* Include the Inc Tax label.
*
* @return string
*/
public function inc_tax_or_vat() {
$return = in_array( $this->get_base_country(), $this->get_vat_countries(), true ) ? __( '(incl. VAT)', 'woocommerce' ) : __( '(incl. tax)', 'woocommerce' );
return apply_filters( 'woocommerce_countries_inc_tax_or_vat', $return );
}
/**
* Include the Ex Tax label.
*
* @return string
*/
public function ex_tax_or_vat() {
$return = in_array( $this->get_base_country(), $this->get_vat_countries(), true ) ? __( '(ex. VAT)', 'woocommerce' ) : __( '(ex. tax)', 'woocommerce' );
return apply_filters( 'woocommerce_countries_ex_tax_or_vat', $return );
}
/**
* Outputs the list of countries and states for use in dropdown boxes.
*
* @param string $selected_country Selected country.
* @param string $selected_state Selected state.
* @param bool $escape If we should escape HTML.
*/
public function country_dropdown_options( $selected_country = '', $selected_state = '', $escape = false ) {
if ( $this->countries ) {
foreach ( $this->countries as $key => $value ) {
$states = $this->get_states( $key );
if ( $states ) {
// Maybe default the selected state as the first one.
if ( $selected_country === $key && '*' === $selected_state ) {
$selected_state = key( $states ) ?? '*';
}
echo '';
} else {
echo ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
}
}
/**
* Get country address formats.
*
* These define how addresses are formatted for display in various countries.
*
* @return array
*/
public function get_address_formats() {
if ( empty( $this->address_formats ) ) {
$this->address_formats = apply_filters(
'woocommerce_localisation_address_formats',
array(
'default' => "{name}\n{company}\n{address_1}\n{address_2}\n{city}\n{state}\n{postcode}\n{country}",
'AT' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'AU' => "{name}\n{company}\n{address_1}\n{address_2}\n{city} {state} {postcode}\n{country}",
'BE' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'CA' => "{company}\n{name}\n{address_1}\n{address_2}\n{city} {state_code} {postcode}\n{country}",
'CH' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'CL' => "{company}\n{name}\n{address_1}\n{address_2}\n{state}\n{postcode} {city}\n{country}",
'CN' => "{country} {postcode}\n{state}, {city}, {address_2}, {address_1}\n{company}\n{name}",
'CZ' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'DE' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'DK' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'EE' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'ES' => "{name}\n{company}\n{address_1}\n{address_2}\n{postcode} {city}\n{state}\n{country}",
'FI' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'FR' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city_upper}\n{country}",
'HK' => "{company}\n{first_name} {last_name_upper}\n{address_1}\n{address_2}\n{city_upper}\n{state_upper}\n{country}",
'HU' => "{last_name} {first_name}\n{company}\n{city}\n{address_1}\n{address_2}\n{postcode}\n{country}",
'IN' => "{company}\n{name}\n{address_1}\n{address_2}\n{city} {postcode}\n{state}, {country}",
'IS' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'IT' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode}\n{city}\n{state_upper}\n{country}",
'JM' => "{name}\n{company}\n{address_1}\n{address_2}\n{city}\n{state}\n{postcode_upper}\n{country}",
'JP' => "{postcode}\n{state} {city} {address_1}\n{address_2}\n{company}\n{last_name} {first_name}\n{country}",
'LI' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'NL' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'NO' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'NZ' => "{name}\n{company}\n{address_1}\n{address_2}\n{city} {postcode}\n{country}",
'PL' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'PR' => "{company}\n{name}\n{address_1} {address_2}\n{city} \n{country} {postcode}",
'PT' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'RS' => "{name}\n{company}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'SE' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'SI' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'SK' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
'TR' => "{name}\n{company}\n{address_1}\n{address_2}\n{postcode} {city} {state}\n{country}",
'TW' => "{company}\n{last_name} {first_name}\n{address_1}\n{address_2}\n{state}, {city} {postcode}\n{country}",
'UG' => "{name}\n{company}\n{address_1}\n{address_2}\n{city}\n{state}, {country}",
'US' => "{name}\n{company}\n{address_1}\n{address_2}\n{city}, {state_code} {postcode}\n{country}",
'VN' => "{name}\n{company}\n{address_1}\n{address_2}\n{city} {postcode}\n{country}",
)
);
}
return $this->address_formats;
}
/**
* Get country address format.
*
* @param array $args Arguments.
* @param string $separator How to separate address lines. @since 3.5.0.
* @return string
*/
public function get_formatted_address( $args = array(), $separator = '
' ) {
$default_args = array(
'first_name' => '',
'last_name' => '',
'company' => '',
'address_1' => '',
'address_2' => '',
'city' => '',
'state' => '',
'postcode' => '',
'country' => '',
);
$args = array_map( 'trim', wp_parse_args( $args, $default_args ) );
$state = $args['state'];
$country = $args['country'];
// Get all formats.
$formats = $this->get_address_formats();
// Get format for the address' country.
$format = ( $country && isset( $formats[ $country ] ) ) ? $formats[ $country ] : $formats['default'];
// Handle full country name.
$full_country = ( isset( $this->countries[ $country ] ) ) ? $this->countries[ $country ] : $country;
// Country is not needed if the same as base.
if ( $country === $this->get_base_country() && ! apply_filters( 'woocommerce_formatted_address_force_country_display', false ) ) {
$format = str_replace( '{country}', '', $format );
}
// Handle full state name.
$full_state = ( $country && $state && isset( $this->states[ $country ][ $state ] ) ) ? $this->states[ $country ][ $state ] : $state;
// Substitute address parts into the string.
$replace = array_map(
'esc_html',
apply_filters(
'woocommerce_formatted_address_replacements',
array(
'{first_name}' => $args['first_name'],
'{last_name}' => $args['last_name'],
'{name}' => sprintf(
/* translators: 1: first name 2: last name */
_x( '%1$s %2$s', 'full name', 'woocommerce' ),
$args['first_name'],
$args['last_name']
),
'{company}' => $args['company'],
'{address_1}' => $args['address_1'],
'{address_2}' => $args['address_2'],
'{city}' => $args['city'],
'{state}' => $full_state,
'{postcode}' => $args['postcode'],
'{country}' => $full_country,
'{first_name_upper}' => wc_strtoupper( $args['first_name'] ),
'{last_name_upper}' => wc_strtoupper( $args['last_name'] ),
'{name_upper}' => wc_strtoupper(
sprintf(
/* translators: 1: first name 2: last name */
_x( '%1$s %2$s', 'full name', 'woocommerce' ),
$args['first_name'],
$args['last_name']
)
),
'{company_upper}' => wc_strtoupper( $args['company'] ),
'{address_1_upper}' => wc_strtoupper( $args['address_1'] ),
'{address_2_upper}' => wc_strtoupper( $args['address_2'] ),
'{city_upper}' => wc_strtoupper( $args['city'] ),
'{state_upper}' => wc_strtoupper( $full_state ),
'{state_code}' => wc_strtoupper( $state ),
'{postcode_upper}' => wc_strtoupper( $args['postcode'] ),
'{country_upper}' => wc_strtoupper( $full_country ),
),
$args
)
);
$formatted_address = str_replace( array_keys( $replace ), $replace, $format );
// Clean up white space.
$formatted_address = preg_replace( '/ +/', ' ', trim( $formatted_address ) );
$formatted_address = preg_replace( '/\n\n+/', "\n", $formatted_address );
// Break newlines apart and remove empty lines/trim commas and white space.
$formatted_address = array_filter( array_map( array( $this, 'trim_formatted_address_line' ), explode( "\n", $formatted_address ) ) );
// Add html breaks.
$formatted_address = implode( $separator, $formatted_address );
// We're done!
return $formatted_address;
}
/**
* Trim white space and commas off a line.
*
* @param string $line Line.
* @return string
*/
private function trim_formatted_address_line( $line ) {
return trim( $line, ', ' );
}
/**
* Returns the fields we show by default. This can be filtered later on.
*
* @return array
*/
public function get_default_address_fields() {
$address_2_label = __( 'Apartment, suite, unit, etc.', 'woocommerce' );
// If necessary, append '(optional)' to the placeholder: we don't need to worry about the
// label, though, as woocommerce_form_field() takes care of that.
if ( 'optional' === get_option( 'woocommerce_checkout_address_2_field', 'optional' ) ) {
$address_2_placeholder = __( 'Apartment, suite, unit, etc. (optional)', 'woocommerce' );
} else {
$address_2_placeholder = $address_2_label;
}
$fields = array(
'first_name' => array(
'label' => __( 'First name', 'woocommerce' ),
'required' => true,
'class' => array( 'form-row-first' ),
'autocomplete' => 'given-name',
'priority' => 10,
),
'last_name' => array(
'label' => __( 'Last name', 'woocommerce' ),
'required' => true,
'class' => array( 'form-row-last' ),
'autocomplete' => 'family-name',
'priority' => 20,
),
'company' => array(
'label' => __( 'Company name', 'woocommerce' ),
'class' => array( 'form-row-wide' ),
'autocomplete' => 'organization',
'priority' => 30,
'required' => 'required' === get_option( 'woocommerce_checkout_company_field', 'optional' ),
),
'country' => array(
'type' => 'country',
'label' => __( 'Country / Region', 'woocommerce' ),
'required' => true,
'class' => array( 'form-row-wide', 'address-field', 'update_totals_on_change' ),
'autocomplete' => 'country',
'priority' => 40,
),
'address_1' => array(
'label' => __( 'Street address', 'woocommerce' ),
/* translators: use local order of street name and house number. */
'placeholder' => esc_attr__( 'House number and street name', 'woocommerce' ),
'required' => true,
'class' => array( 'form-row-wide', 'address-field' ),
'autocomplete' => 'address-line1',
'priority' => 50,
),
'address_2' => array(
'label' => $address_2_label,
'label_class' => array( 'screen-reader-text' ),
'placeholder' => esc_attr( $address_2_placeholder ),
'class' => array( 'form-row-wide', 'address-field' ),
'autocomplete' => 'address-line2',
'priority' => 60,
'required' => 'required' === get_option( 'woocommerce_checkout_address_2_field', 'optional' ),
),
'city' => array(
'label' => __( 'Town / City', 'woocommerce' ),
'required' => true,
'class' => array( 'form-row-wide', 'address-field' ),
'autocomplete' => 'address-level2',
'priority' => 70,
),
'state' => array(
'type' => 'state',
'label' => __( 'State / County', 'woocommerce' ),
'required' => true,
'class' => array( 'form-row-wide', 'address-field' ),
'validate' => array( 'state' ),
'autocomplete' => 'address-level1',
'priority' => 80,
),
'postcode' => array(
'label' => __( 'Postcode / ZIP', 'woocommerce' ),
'required' => true,
'class' => array( 'form-row-wide', 'address-field' ),
'validate' => array( 'postcode' ),
'autocomplete' => 'postal-code',
'priority' => 90,
),
);
if ( 'hidden' === get_option( 'woocommerce_checkout_company_field', 'optional' ) ) {
unset( $fields['company'] );
}
if ( 'hidden' === get_option( 'woocommerce_checkout_address_2_field', 'optional' ) ) {
unset( $fields['address_2'] );
}
$default_address_fields = apply_filters( 'woocommerce_default_address_fields', $fields );
// Sort each of the fields based on priority.
uasort( $default_address_fields, 'wc_checkout_fields_uasort_comparison' );
return $default_address_fields;
}
/**
* Get JS selectors for fields which are shown/hidden depending on the locale.
*
* @return array
*/
public function get_country_locale_field_selectors() {
$locale_fields = array(
'address_1' => '#billing_address_1_field, #shipping_address_1_field',
'address_2' => '#billing_address_2_field, #shipping_address_2_field',
'state' => '#billing_state_field, #shipping_state_field, #calc_shipping_state_field',
'postcode' => '#billing_postcode_field, #shipping_postcode_field, #calc_shipping_postcode_field',
'city' => '#billing_city_field, #shipping_city_field, #calc_shipping_city_field',
);
return apply_filters( 'woocommerce_country_locale_field_selectors', $locale_fields );
}
/**
* Get country locale settings.
*
* These locales override the default country selections after a country is chosen.
*
* @return array
*/
public function get_country_locale() {
if ( empty( $this->locale ) ) {
$this->locale = apply_filters(
'woocommerce_get_country_locale',
array(
'AE' => array(
'postcode' => array(
'required' => false,
'hidden' => true,
),
'state' => array(
'required' => false,
),
),
'AF' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'AL' => array(
'state' => array(
'label' => __( 'County', 'woocommerce' ),
),
),
'AO' => array(
'postcode' => array(
'required' => false,
'hidden' => true,
),
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
'AT' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'AU' => array(
'city' => array(
'label' => __( 'Suburb', 'woocommerce' ),
),
'postcode' => array(
'label' => __( 'Postcode', 'woocommerce' ),
),
'state' => array(
'label' => __( 'State', 'woocommerce' ),
),
),
'AX' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'BA' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'label' => __( 'Canton', 'woocommerce' ),
'required' => false,
'hidden' => true,
),
),
'BD' => array(
'postcode' => array(
'required' => false,
),
'state' => array(
'label' => __( 'District', 'woocommerce' ),
),
),
'BE' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'BG' => array(
'state' => array(
'required' => false,
),
),
'BH' => array(
'postcode' => array(
'required' => false,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'BI' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'BO' => array(
'postcode' => array(
'required' => false,
'hidden' => true,
),
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
),
),
'BS' => array(
'postcode' => array(
'required' => false,
'hidden' => true,
),
),
'BZ' => array(
'postcode' => array(
'required' => false,
'hidden' => true,
),
'state' => array(
'required' => false,
),
),
'CA' => array(
'postcode' => array(
'label' => __( 'Postal code', 'woocommerce' ),
),
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
'CH' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'label' => __( 'Canton', 'woocommerce' ),
'required' => false,
),
),
'CL' => array(
'city' => array(
'required' => true,
),
'postcode' => array(
'required' => false,
// Hidden for stores within Chile. @see https://github.com/woocommerce/woocommerce/issues/36546.
'hidden' => 'CL' === $this->get_base_country(),
),
'state' => array(
'label' => __( 'Region', 'woocommerce' ),
),
),
'CN' => array(
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
'CO' => array(
'postcode' => array(
'required' => false,
),
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
),
),
'CR' => array(
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
'CW' => array(
'postcode' => array(
'required' => false,
'hidden' => true,
),
'state' => array(
'required' => false,
),
),
'CZ' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'DE' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'DK' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'DO' => array(
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
'EC' => array(
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
'EE' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'ET' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'FI' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'FR' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'GG' => array(
'state' => array(
'required' => false,
'label' => __( 'Parish', 'woocommerce' ),
),
),
'GH' => array(
'postcode' => array(
'required' => false,
),
'state' => array(
'label' => __( 'Region', 'woocommerce' ),
),
),
'GP' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'GF' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'GR' => array(
'state' => array(
'required' => false,
),
),
'GT' => array(
'postcode' => array(
'required' => false,
),
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
),
),
'HK' => array(
'postcode' => array(
'required' => false,
),
'city' => array(
'label' => __( 'Town / District', 'woocommerce' ),
),
'state' => array(
'label' => __( 'Region', 'woocommerce' ),
),
),
'HN' => array(
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
),
),
'HU' => array(
'last_name' => array(
'class' => array( 'form-row-first' ),
'priority' => 10,
),
'first_name' => array(
'class' => array( 'form-row-last' ),
'priority' => 20,
),
'postcode' => array(
'class' => array( 'form-row-first', 'address-field' ),
'priority' => 65,
),
'city' => array(
'class' => array( 'form-row-last', 'address-field' ),
),
'address_1' => array(
'priority' => 71,
),
'address_2' => array(
'priority' => 72,
),
'state' => array(
'label' => __( 'County', 'woocommerce' ),
'required' => false,
),
),
'ID' => array(
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
'IE' => array(
'postcode' => array(
'required' => false,
'label' => __( 'Eircode', 'woocommerce' ),
),
'state' => array(
'label' => __( 'County', 'woocommerce' ),
),
),
'IS' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'IL' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'IM' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'IN' => array(
'postcode' => array(
'label' => __( 'PIN Code', 'woocommerce' ),
),
'state' => array(
'label' => __( 'State', 'woocommerce' ),
),
),
'IR' => array(
'state' => array(
'priority' => 50,
),
'city' => array(
'priority' => 60,
),
'address_1' => array(
'priority' => 70,
),
'address_2' => array(
'priority' => 80,
),
),
'IT' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => true,
'label' => __( 'Province', 'woocommerce' ),
),
),
'JM' => array(
'city' => array(
'label' => __( 'Town / City / Post Office', 'woocommerce' ),
),
'postcode' => array(
'required' => false,
'label' => __( 'Postal Code', 'woocommerce' ),
),
'state' => array(
'required' => true,
'label' => __( 'Parish', 'woocommerce' ),
),
),
'JP' => array(
'last_name' => array(
'class' => array( 'form-row-first' ),
'priority' => 10,
),
'first_name' => array(
'class' => array( 'form-row-last' ),
'priority' => 20,
),
'postcode' => array(
'class' => array( 'form-row-first', 'address-field' ),
'priority' => 65,
),
'state' => array(
'label' => __( 'Prefecture', 'woocommerce' ),
'class' => array( 'form-row-last', 'address-field' ),
'priority' => 66,
),
'city' => array(
'priority' => 67,
),
'address_1' => array(
'priority' => 68,
),
'address_2' => array(
'priority' => 69,
),
),
'KN' => array(
'postcode' => array(
'required' => false,
'label' => __( 'Postal code', 'woocommerce' ),
),
'state' => array(
'required' => true,
'label' => __( 'Parish', 'woocommerce' ),
),
),
'KR' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'KW' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'LV' => array(
'state' => array(
'label' => __( 'Municipality', 'woocommerce' ),
'required' => false,
),
),
'LB' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'MF' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'MQ' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'MT' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'MZ' => array(
'postcode' => array(
'required' => false,
'hidden' => true,
),
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
'NI' => array(
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
),
),
'NL' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'NG' => array(
'postcode' => array(
'label' => __( 'Postcode', 'woocommerce' ),
'required' => false,
'hidden' => true,
),
'state' => array(
'label' => __( 'State', 'woocommerce' ),
),
),
'NZ' => array(
'postcode' => array(
'label' => __( 'Postcode', 'woocommerce' ),
),
'state' => array(
'required' => false,
'label' => __( 'Region', 'woocommerce' ),
),
),
'NO' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'NP' => array(
'state' => array(
'label' => __( 'State / Zone', 'woocommerce' ),
),
'postcode' => array(
'required' => false,
),
),
'PA' => array(
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
'PL' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'PR' => array(
'city' => array(
'label' => __( 'Municipality', 'woocommerce' ),
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'PT' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'PY' => array(
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
),
),
'RE' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'RO' => array(
'state' => array(
'label' => __( 'County', 'woocommerce' ),
'required' => true,
),
),
'RS' => array(
'city' => array(
'required' => true,
),
'postcode' => array(
'required' => true,
),
'state' => array(
'label' => __( 'District', 'woocommerce' ),
'required' => false,
),
),
'RW' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'SG' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
'city' => array(
'required' => false,
),
),
'SK' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'SI' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'SR' => array(
'postcode' => array(
'required' => false,
'hidden' => true,
),
),
'SV' => array(
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
),
),
'ES' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
'LI' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'LK' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'LU' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'MD' => array(
'state' => array(
'label' => __( 'Municipality / District', 'woocommerce' ),
),
),
'SE' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => false,
'hidden' => true,
),
),
'TR' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
'UG' => array(
'postcode' => array(
'required' => false,
'hidden' => true,
),
'city' => array(
'label' => __( 'Town / Village', 'woocommerce' ),
'required' => true,
),
'state' => array(
'label' => __( 'District', 'woocommerce' ),
'required' => true,
),
),
'US' => array(
'postcode' => array(
'label' => __( 'ZIP Code', 'woocommerce' ),
),
'state' => array(
'label' => __( 'State', 'woocommerce' ),
),
),
'UY' => array(
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
),
),
'GB' => array(
'postcode' => array(
'label' => __( 'Postcode', 'woocommerce' ),
),
'state' => array(
'label' => __( 'County', 'woocommerce' ),
'required' => false,
),
),
'ST' => array(
'postcode' => array(
'required' => false,
'hidden' => true,
),
'state' => array(
'label' => __( 'District', 'woocommerce' ),
),
),
'VN' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
'postcode' => array(
'priority' => 65,
'required' => false,
'hidden' => false,
),
'address_2' => array(
'required' => false,
'hidden' => false,
),
),
'WS' => array(
'postcode' => array(
'required' => false,
'hidden' => true,
),
),
'YT' => array(
'state' => array(
'required' => false,
'hidden' => true,
),
),
'ZA' => array(
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
'ZW' => array(
'postcode' => array(
'required' => false,
'hidden' => true,
),
),
)
);
$this->locale = array_intersect_key( $this->locale, array_merge( $this->get_allowed_countries(), $this->get_shipping_countries() ) );
// Default Locale Can be filtered to override fields in get_address_fields(). Countries with no specific locale will use default.
$this->locale['default'] = apply_filters( 'woocommerce_get_country_locale_default', $this->get_default_address_fields() );
// Filter default AND shop base locales to allow overrides via a single function. These will be used when changing countries on the checkout.
if ( ! isset( $this->locale[ $this->get_base_country() ] ) ) {
$this->locale[ $this->get_base_country() ] = $this->locale['default'];
}
$this->locale['default'] = apply_filters( 'woocommerce_get_country_locale_base', $this->locale['default'] );
$this->locale[ $this->get_base_country() ] = apply_filters( 'woocommerce_get_country_locale_base', $this->locale[ $this->get_base_country() ] );
}
return $this->locale;
}
/**
* Apply locale and get address fields.
*
* @param mixed $country Country.
* @param string $type Address type, defaults to 'billing_'.
* @return array
*/
public function get_address_fields( $country = '', $type = 'billing_' ) {
if ( ! $country ) {
$country = $this->get_base_country();
}
$fields = $this->get_default_address_fields();
$locale = $this->get_country_locale();
if ( isset( $locale[ $country ] ) ) {
$fields = wc_array_overlay( $fields, $locale[ $country ] );
}
// Prepend field keys.
$address_fields = array();
foreach ( $fields as $key => $value ) {
if ( 'state' === $key ) {
$value['country_field'] = $type . 'country';
$value['country'] = $country;
}
$address_fields[ $type . $key ] = $value;
}
// Add email and phone fields.
if ( 'billing_' === $type ) {
if ( 'hidden' !== get_option( 'woocommerce_checkout_phone_field', 'required' ) ) {
$address_fields['billing_phone'] = array(
'label' => __( 'Phone', 'woocommerce' ),
'required' => 'required' === get_option( 'woocommerce_checkout_phone_field', 'required' ),
'type' => 'tel',
'class' => array( 'form-row-wide' ),
'validate' => array( 'phone' ),
'autocomplete' => 'tel',
'priority' => 100,
);
}
$address_fields['billing_email'] = array(
'label' => __( 'Email address', 'woocommerce' ),
'required' => true,
'type' => 'email',
'class' => array( 'form-row-wide' ),
'validate' => array( 'email' ),
'autocomplete' => 'no' === get_option( 'woocommerce_registration_generate_username' ) ? 'email' : 'email username',
'priority' => 110,
);
}
/**
* Important note on this filter: Changes to address fields can and will be overridden by
* the woocommerce_default_address_fields. The locales/default locales apply on top based
* on country selection. If you want to change things like the required status of an
* address field, filter woocommerce_default_address_fields instead.
*/
$address_fields = apply_filters( 'woocommerce_' . $type . 'fields', $address_fields, $country );
// Sort each of the fields based on priority.
uasort( $address_fields, 'wc_checkout_fields_uasort_comparison' );
return $address_fields;
}
}
PK K[-kô ô class-wc-geolite-integration.phpnu „[µü¤ database = $database;
}
/**
* Get country 2-letters ISO by IP address.
* Returns empty string when not able to find any ISO code.
*
* @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 {
$reader = new MaxMind\Db\Reader( $this->database ); // phpcs:ignore PHPCompatibility.LanguageConstructs.NewLanguageConstructs.t_ns_separatorFound
$data = $reader->get( $ip_address );
if ( isset( $data['country']['iso_code'] ) ) {
$iso_code = $data['country']['iso_code'];
}
$reader->close();
} catch ( Exception $e ) {
$this->log( $e->getMessage(), 'warning' );
}
return sanitize_text_field( strtoupper( $iso_code ) );
}
/**
* Logging method.
*
* @param string $message Log message.
* @param string $level Log level.
* Available options: 'emergency', 'alert',
* 'critical', 'error', 'warning', 'notice',
* 'info' and 'debug'.
* Defaults to 'info'.
*/
private function log( $message, $level = 'info' ) {
if ( is_null( $this->log ) ) {
$this->log = wc_get_logger();
}
$this->log->log( $level, $message, array( 'source' => 'geoip' ) );
}
}
PK K[ !A[ÿp ÿp class-wc-cart-totals.phpnu „[µü¤ 0,
'fees_total_tax' => 0,
'items_subtotal' => 0,
'items_subtotal_tax' => 0,
'items_total' => 0,
'items_total_tax' => 0,
'total' => 0,
'shipping_total' => 0,
'shipping_tax_total' => 0,
'discounts_total' => 0,
);
/**
* Cache of tax rates for a given tax class.
*
* @var array
*/
protected $item_tax_rates;
/**
* Sets up the items provided, and calculate totals.
*
* @since 3.2.0
* @throws Exception If missing WC_Cart object.
* @param WC_Cart $cart Cart object to calculate totals for.
*/
public function __construct( &$cart = null ) {
if ( ! is_a( $cart, 'WC_Cart' ) ) {
throw new Exception( 'A valid WC_Cart object is required' );
}
$this->cart = $cart;
$this->calculate_tax = wc_tax_enabled() && ! $cart->get_customer()->get_is_vat_exempt();
$this->calculate();
}
/**
* Run all calculation methods on the given items in sequence.
*
* @since 3.2.0
*/
protected function calculate() {
$this->calculate_item_totals();
$this->calculate_shipping_totals();
$this->calculate_fee_totals();
$this->calculate_totals();
}
/**
* Get default blank set of props used per item.
*
* @since 3.2.0
* @return array
*/
protected function get_default_item_props() {
return (object) array(
'object' => null,
'tax_class' => '',
'taxable' => false,
'quantity' => 0,
'product' => false,
'price_includes_tax' => false,
'subtotal' => 0,
'subtotal_tax' => 0,
'subtotal_taxes' => array(),
'total' => 0,
'total_tax' => 0,
'taxes' => array(),
);
}
/**
* Get default blank set of props used per fee.
*
* @since 3.2.0
* @return array
*/
protected function get_default_fee_props() {
return (object) array(
'object' => null,
'tax_class' => '',
'taxable' => false,
'total_tax' => 0,
'taxes' => array(),
);
}
/**
* Get default blank set of props used per shipping row.
*
* @since 3.2.0
* @return array
*/
protected function get_default_shipping_props() {
return (object) array(
'object' => null,
'tax_class' => '',
'taxable' => false,
'total' => 0,
'total_tax' => 0,
'taxes' => array(),
);
}
/**
* Handles a cart or order object passed in for calculation. Normalises data
* into the same format for use by this class.
*
* Each item is made up of the following props, in addition to those returned by get_default_item_props() for totals.
* - key: An identifier for the item (cart item key or line item ID).
* - cart_item: For carts, the cart item from the cart which may include custom data.
* - quantity: The qty for this line.
* - price: The line price in cents.
* - product: The product object this cart item is for.
*
* @since 3.2.0
*/
protected function get_items_from_cart() {
$this->items = array();
foreach ( $this->cart->get_cart() as $cart_item_key => $cart_item ) {
$item = $this->get_default_item_props();
$item->key = $cart_item_key;
$item->object = $cart_item;
$item->tax_class = $cart_item['data']->get_tax_class();
$item->taxable = 'taxable' === $cart_item['data']->get_tax_status();
$item->price_includes_tax = wc_prices_include_tax();
$item->quantity = $cart_item['quantity'];
$item->price = wc_add_number_precision_deep( (float) $cart_item['data']->get_price() * (float) $cart_item['quantity'] );
$item->product = $cart_item['data'];
$item->tax_rates = $this->get_item_tax_rates( $item );
$this->items[ $cart_item_key ] = $item;
}
}
/**
* Get item costs grouped by tax class.
*
* @since 3.2.0
* @return array
*/
protected function get_tax_class_costs() {
$item_tax_classes = wp_list_pluck( $this->items, 'tax_class' );
$shipping_tax_classes = wp_list_pluck( $this->shipping, 'tax_class' );
$fee_tax_classes = wp_list_pluck( $this->fees, 'tax_class' );
$costs = array_fill_keys( $item_tax_classes + $shipping_tax_classes + $fee_tax_classes, 0 );
$costs['non-taxable'] = 0;
foreach ( $this->items + $this->fees + $this->shipping as $item ) {
if ( 0 > $item->total ) {
continue;
}
if ( ! $item->taxable ) {
$costs['non-taxable'] += $item->total;
} elseif ( 'inherit' === $item->tax_class ) {
$costs[ reset( $item_tax_classes ) ] += $item->total;
} else {
$costs[ $item->tax_class ] += $item->total;
}
}
return array_filter( $costs );
}
/**
* Get fee objects from the cart. Normalises data
* into the same format for use by this class.
*
* @since 3.2.0
*/
protected function get_fees_from_cart() {
$this->fees = array();
$this->cart->calculate_fees();
$fee_running_total = 0;
foreach ( $this->cart->get_fees() as $fee_key => $fee_object ) {
$fee = $this->get_default_fee_props();
$fee->object = $fee_object;
$fee->tax_class = $fee->object->tax_class;
$fee->taxable = $fee->object->taxable;
$fee->total = wc_add_number_precision_deep( $fee->object->amount );
// Negative fees should not make the order total go negative.
if ( 0 > $fee->total ) {
$max_discount = NumberUtil::round( $this->get_total( 'items_total', true ) + $fee_running_total + $this->get_total( 'shipping_total', true ) ) * -1;
if ( $fee->total < $max_discount ) {
$fee->total = $max_discount;
}
}
$fee_running_total += $fee->total;
if ( $this->calculate_tax ) {
if ( 0 > $fee->total ) {
// Negative fees should have the taxes split between all items so it works as a true discount.
$tax_class_costs = $this->get_tax_class_costs();
$total_cost = array_sum( $tax_class_costs );
if ( $total_cost ) {
foreach ( $tax_class_costs as $tax_class => $tax_class_cost ) {
if ( 'non-taxable' === $tax_class ) {
continue;
}
$proportion = $tax_class_cost / $total_cost;
$cart_discount_proportion = $fee->total * $proportion;
$fee->taxes = wc_array_merge_recursive_numeric( $fee->taxes, WC_Tax::calc_tax( $fee->total * $proportion, WC_Tax::get_rates( $tax_class ) ) );
}
}
} elseif ( $fee->object->taxable ) {
$fee->taxes = WC_Tax::calc_tax( $fee->total, WC_Tax::get_rates( $fee->tax_class, $this->cart->get_customer() ), false );
}
}
$fee->taxes = apply_filters( 'woocommerce_cart_totals_get_fees_from_cart_taxes', $fee->taxes, $fee, $this );
$fee->total_tax = array_sum( array_map( array( $this, 'round_line_tax' ), $fee->taxes ) );
// Set totals within object.
$fee->object->total = wc_remove_number_precision_deep( $fee->total );
$fee->object->tax_data = wc_remove_number_precision_deep( $fee->taxes );
$fee->object->tax = wc_remove_number_precision_deep( $fee->total_tax );
$this->fees[ $fee_key ] = $fee;
}
}
/**
* Get shipping methods from the cart and normalise.
*
* @since 3.2.0
*/
protected function get_shipping_from_cart() {
$this->shipping = array();
if ( ! $this->cart->show_shipping() ) {
return;
}
foreach ( $this->cart->calculate_shipping() as $key => $shipping_object ) {
$shipping_line = $this->get_default_shipping_props();
$shipping_line->object = $shipping_object;
$shipping_line->tax_class = get_option( 'woocommerce_shipping_tax_class' );
$shipping_line->taxable = true;
$shipping_line->total = wc_add_number_precision_deep( $shipping_object->cost );
$shipping_line->taxes = wc_add_number_precision_deep( $shipping_object->taxes, false );
$shipping_line->taxes = array_map( array( $this, 'round_item_subtotal' ), $shipping_line->taxes );
$shipping_line->total_tax = array_sum( $shipping_line->taxes );
$this->shipping[ $key ] = $shipping_line;
}
}
/**
* Return array of coupon objects from the cart. Normalises data
* into the same format for use by this class.
*
* @since 3.2.0
*/
protected function get_coupons_from_cart() {
$this->coupons = $this->cart->get_coupons();
foreach ( $this->coupons as $coupon ) {
switch ( $coupon->get_discount_type() ) {
case 'fixed_product':
$coupon->sort = 1;
break;
case 'percent':
$coupon->sort = 2;
break;
case 'fixed_cart':
$coupon->sort = 3;
break;
default:
$coupon->sort = 0;
break;
}
// Allow plugins to override the default order.
$coupon->sort = apply_filters( 'woocommerce_coupon_sort', $coupon->sort, $coupon );
}
uasort( $this->coupons, array( $this, 'sort_coupons_callback' ) );
}
/**
* Sort coupons so discounts apply consistently across installs.
*
* In order of priority;
* - sort param
* - usage restriction
* - coupon value
* - ID
*
* @param WC_Coupon $a Coupon object.
* @param WC_Coupon $b Coupon object.
* @return int
*/
protected function sort_coupons_callback( $a, $b ) {
if ( $a->sort === $b->sort ) {
if ( $a->get_limit_usage_to_x_items() === $b->get_limit_usage_to_x_items() ) {
if ( $a->get_amount() === $b->get_amount() ) {
return $b->get_id() - $a->get_id();
}
return ( $a->get_amount() < $b->get_amount() ) ? -1 : 1;
}
return ( $a->get_limit_usage_to_x_items() < $b->get_limit_usage_to_x_items() ) ? -1 : 1;
}
return ( $a->sort < $b->sort ) ? -1 : 1;
}
/**
* Ran to remove all base taxes from an item. Used when prices include tax, and the customer is tax exempt.
*
* @since 3.2.2
* @param object $item Item to adjust the prices of.
* @return object
*/
protected function remove_item_base_taxes( $item ) {
if ( $item->price_includes_tax && $item->taxable ) {
if ( apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
$base_tax_rates = WC_Tax::get_base_tax_rates( $item->product->get_tax_class( 'unfiltered' ) );
} else {
/**
* If we want all customers to pay the same price on this store, we should not remove base taxes from a VAT exempt user's price,
* but just the relevant tax rate. See issue #20911.
*/
$base_tax_rates = $item->tax_rates;
}
// Work out a new base price without the shop's base tax.
$taxes = WC_Tax::calc_tax( $item->price, $base_tax_rates, true );
// Now we have a new item price (excluding TAX).
$item->price = NumberUtil::round( $item->price - array_sum( $taxes ) );
$item->price_includes_tax = false;
}
return $item;
}
/**
* Only ran if woocommerce_adjust_non_base_location_prices is true.
*
* If the customer is outside of the base location, this removes the base
* taxes. This is off by default unless the filter is used.
*
* Uses edit context so unfiltered tax class is returned.
*
* @since 3.2.0
* @param object $item Item to adjust the prices of.
* @return object
*/
protected function adjust_non_base_location_price( $item ) {
if ( $item->price_includes_tax && $item->taxable ) {
$base_tax_rates = WC_Tax::get_base_tax_rates( $item->product->get_tax_class( 'unfiltered' ) );
if ( $item->tax_rates !== $base_tax_rates ) {
// Work out a new base price without the shop's base tax.
$taxes = WC_Tax::calc_tax( $item->price, $base_tax_rates, true );
$new_taxes = WC_Tax::calc_tax( $item->price - array_sum( $taxes ), $item->tax_rates, false );
// Now we have a new item price.
$item->price = $item->price - array_sum( $taxes ) + array_sum( $new_taxes );
}
}
return $item;
}
/**
* Get discounted price of an item with precision (in cents).
*
* @since 3.2.0
* @param object $item_key Item to get the price of.
* @return int
*/
protected function get_discounted_price_in_cents( $item_key ) {
$item = $this->items[ $item_key ];
$price = isset( $this->coupon_discount_totals[ $item_key ] ) ? $item->price - $this->coupon_discount_totals[ $item_key ] : $item->price;
return $price;
}
/**
* Get tax rates for an item. Caches rates in class to avoid multiple look ups.
*
* @param object $item Item to get tax rates for.
* @return array of taxes
*/
protected function get_item_tax_rates( $item ) {
if ( ! wc_tax_enabled() ) {
return array();
}
$tax_class = $item->product->get_tax_class();
$item_tax_rates = isset( $this->item_tax_rates[ $tax_class ] ) ? $this->item_tax_rates[ $tax_class ] : $this->item_tax_rates[ $tax_class ] = WC_Tax::get_rates( $item->product->get_tax_class(), $this->cart->get_customer() );
// Allow plugins to filter item tax rates.
return apply_filters( 'woocommerce_cart_totals_get_item_tax_rates', $item_tax_rates, $item, $this->cart );
}
/**
* Get item costs grouped by tax class.
*
* @since 3.2.0
* @return array
*/
protected function get_item_costs_by_tax_class() {
$tax_classes = array(
'non-taxable' => 0,
);
foreach ( $this->items + $this->fees + $this->shipping as $item ) {
if ( ! isset( $tax_classes[ $item->tax_class ] ) ) {
$tax_classes[ $item->tax_class ] = 0;
}
if ( $item->taxable ) {
$tax_classes[ $item->tax_class ] += $item->total;
} else {
$tax_classes['non-taxable'] += $item->total;
}
}
return $tax_classes;
}
/**
* Get a single total with or without precision (in cents).
*
* @since 3.2.0
* @param string $key Total to get.
* @param bool $in_cents Should the totals be returned in cents, or without precision.
* @return int|float
*/
public function get_total( $key = 'total', $in_cents = false ) {
$totals = $this->get_totals( $in_cents );
return isset( $totals[ $key ] ) ? $totals[ $key ] : 0;
}
/**
* Set a single total.
*
* @since 3.2.0
* @param string $key Total name you want to set.
* @param int $total Total to set.
*/
protected function set_total( $key, $total ) {
$this->totals[ $key ] = $total;
}
/**
* Get all totals with or without precision (in cents).
*
* @since 3.2.0
* @param bool $in_cents Should the totals be returned in cents, or without precision.
* @return array.
*/
public function get_totals( $in_cents = false ) {
return $in_cents ? $this->totals : wc_remove_number_precision_deep( $this->totals );
}
/**
* Returns array of values for totals calculation.
*
* @param string $field Field name. Will probably be `total` or `subtotal`.
* @return array Items object
*/
protected function get_values_for_total( $field ) {
return array_values( wp_list_pluck( $this->items, $field ) );
}
/**
* Get taxes merged by type.
*
* @since 3.2.0
* @param bool $in_cents If returned value should be in cents.
* @param array|string $types Types to merge and return. Defaults to all.
* @return array
*/
protected function get_merged_taxes( $in_cents = false, $types = array( 'items', 'fees', 'shipping' ) ) {
$items = array();
$taxes = array();
if ( is_string( $types ) ) {
$types = array( $types );
}
foreach ( $types as $type ) {
if ( isset( $this->$type ) ) {
$items = array_merge( $items, $this->$type );
}
}
foreach ( $items as $item ) {
foreach ( $item->taxes as $rate_id => $rate ) {
if ( ! isset( $taxes[ $rate_id ] ) ) {
$taxes[ $rate_id ] = 0;
}
$taxes[ $rate_id ] += $this->round_line_tax( $rate );
}
}
return $in_cents ? $taxes : wc_remove_number_precision_deep( $taxes );
}
/**
* Round merged taxes.
*
* @deprecated 3.9.0 `calculate_item_subtotals` should already appropriately round the tax values.
* @since 3.5.4
* @param array $taxes Taxes to round.
* @return array
*/
protected function round_merged_taxes( $taxes ) {
foreach ( $taxes as $rate_id => $tax ) {
$taxes[ $rate_id ] = $this->round_line_tax( $tax );
}
return $taxes;
}
/**
* Combine item taxes into a single array, preserving keys.
*
* @since 3.2.0
* @param array $item_taxes Taxes to combine.
* @return array
*/
protected function combine_item_taxes( $item_taxes ) {
$merged_taxes = array();
foreach ( $item_taxes as $taxes ) {
foreach ( $taxes as $tax_id => $tax_amount ) {
if ( ! isset( $merged_taxes[ $tax_id ] ) ) {
$merged_taxes[ $tax_id ] = 0;
}
$merged_taxes[ $tax_id ] += $tax_amount;
}
}
return $merged_taxes;
}
/*
|--------------------------------------------------------------------------
| Calculation methods.
|--------------------------------------------------------------------------
*/
/**
* Calculate item totals.
*
* @since 3.2.0
*/
protected function calculate_item_totals() {
$this->get_items_from_cart();
$this->calculate_item_subtotals();
$this->calculate_discounts();
foreach ( $this->items as $item_key => $item ) {
$item->total = $this->get_discounted_price_in_cents( $item_key );
$item->total_tax = 0;
if ( has_filter( 'woocommerce_get_discounted_price' ) ) {
/**
* Allow plugins to filter this price like in the legacy cart class.
*
* This is legacy and should probably be deprecated in the future.
* $item->object is the cart item object.
* $this->cart is the cart object.
*/
$item->total = wc_add_number_precision(
apply_filters( 'woocommerce_get_discounted_price', wc_remove_number_precision( $item->total ), $item->object, $this->cart )
);
}
if ( $this->calculate_tax && $item->product->is_taxable() ) {
$total_taxes = apply_filters( 'woocommerce_calculate_item_totals_taxes', WC_Tax::calc_tax( $item->total, $item->tax_rates, $item->price_includes_tax ), $item, $this );
$item->taxes = $total_taxes;
$item->total_tax = array_sum( array_map( array( $this, 'round_line_tax' ), $item->taxes ) );
if ( $item->price_includes_tax ) {
// Use unrounded taxes so we can re-calculate from the orders screen accurately later.
$item->total = $item->total - array_sum( $item->taxes );
}
}
$this->cart->cart_contents[ $item_key ]['line_tax_data']['total'] = wc_remove_number_precision_deep( $item->taxes );
$this->cart->cart_contents[ $item_key ]['line_total'] = wc_remove_number_precision( $item->total );
$this->cart->cart_contents[ $item_key ]['line_tax'] = wc_remove_number_precision( $item->total_tax );
}
$items_total = $this->get_rounded_items_total( $this->get_values_for_total( 'total' ) );
$this->set_total( 'items_total', $items_total );
$this->set_total( 'items_total_tax', array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) );
$this->cart->set_cart_contents_total( $this->get_total( 'items_total' ) );
$this->cart->set_cart_contents_tax( array_sum( $this->get_merged_taxes( false, 'items' ) ) );
$this->cart->set_cart_contents_taxes( $this->get_merged_taxes( false, 'items' ) );
}
/**
* Subtotals are costs before discounts.
*
* To prevent rounding issues we need to work with the inclusive price where possible
* otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would
* be 8.325 leading to totals being 1p off.
*
* Pre tax coupons come off the price the customer thinks they are paying - tax is calculated
* afterwards.
*
* e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that.
*
* @since 3.2.0
*/
protected function calculate_item_subtotals() {
$merged_subtotal_taxes = array(); // Taxes indexed by tax rate ID for storage later.
$adjust_non_base_location_prices = apply_filters( 'woocommerce_adjust_non_base_location_prices', true );
$is_customer_vat_exempt = $this->cart->get_customer()->get_is_vat_exempt();
foreach ( $this->items as $item_key => $item ) {
if ( $item->price_includes_tax ) {
if ( $is_customer_vat_exempt ) {
$item = $this->remove_item_base_taxes( $item );
} elseif ( $adjust_non_base_location_prices ) {
$item = $this->adjust_non_base_location_price( $item );
}
}
$item->subtotal = $item->price;
if ( $this->calculate_tax && $item->product->is_taxable() ) {
$item->subtotal_taxes = WC_Tax::calc_tax( $item->subtotal, $item->tax_rates, $item->price_includes_tax );
$item->subtotal_tax = array_sum( array_map( array( $this, 'round_line_tax' ), $item->subtotal_taxes ) );
if ( $item->price_includes_tax ) {
// Use unrounded taxes so we can re-calculate from the orders screen accurately later.
$item->subtotal = $item->subtotal - array_sum( $item->subtotal_taxes );
}
foreach ( $item->subtotal_taxes as $rate_id => $rate ) {
if ( ! isset( $merged_subtotal_taxes[ $rate_id ] ) ) {
$merged_subtotal_taxes[ $rate_id ] = 0;
}
$merged_subtotal_taxes[ $rate_id ] += $this->round_line_tax( $rate );
}
}
$this->cart->cart_contents[ $item_key ]['line_tax_data'] = array( 'subtotal' => wc_remove_number_precision_deep( $item->subtotal_taxes ) );
$this->cart->cart_contents[ $item_key ]['line_subtotal'] = wc_remove_number_precision( $item->subtotal );
$this->cart->cart_contents[ $item_key ]['line_subtotal_tax'] = wc_remove_number_precision( $item->subtotal_tax );
}
$items_subtotal = $this->get_rounded_items_total( $this->get_values_for_total( 'subtotal' ) );
// Prices are not rounded here because they should already be rounded based on settings in `get_rounded_items_total` and in `round_line_tax` method calls.
$this->set_total( 'items_subtotal', $items_subtotal );
$this->set_total( 'items_subtotal_tax', array_sum( $merged_subtotal_taxes ), 0 );
$this->cart->set_subtotal( $this->get_total( 'items_subtotal' ) );
$this->cart->set_subtotal_tax( $this->get_total( 'items_subtotal_tax' ) );
}
/**
* Calculate COUPON based discounts which change item prices.
*
* @since 3.2.0
* @uses WC_Discounts class.
*/
protected function calculate_discounts() {
$this->get_coupons_from_cart();
$discounts = new WC_Discounts( $this->cart );
// Set items directly so the discounts class can see any tax adjustments made thus far using subtotals.
$discounts->set_items( $this->items );
foreach ( $this->coupons as $coupon ) {
$discounts->apply_coupon( $coupon );
}
$coupon_discount_amounts = $discounts->get_discounts_by_coupon( true );
$coupon_discount_tax_amounts = array();
// See how much tax was 'discounted' per item and per coupon.
if ( $this->calculate_tax ) {
foreach ( $discounts->get_discounts( true ) as $coupon_code => $coupon_discounts ) {
$coupon_discount_tax_amounts[ $coupon_code ] = 0;
foreach ( $coupon_discounts as $item_key => $coupon_discount ) {
$item = $this->items[ $item_key ];
if ( $item->product->is_taxable() ) {
// Item subtotals were sent, so set 3rd param.
$item_tax = array_sum( WC_Tax::calc_tax( $coupon_discount, $item->tax_rates, $item->price_includes_tax ) );
// Sum total tax.
$coupon_discount_tax_amounts[ $coupon_code ] += $item_tax;
// Remove tax from discount total.
if ( $item->price_includes_tax ) {
$coupon_discount_amounts[ $coupon_code ] -= $item_tax;
}
}
}
}
}
$this->coupon_discount_totals = (array) $discounts->get_discounts_by_item( true );
$this->coupon_discount_tax_totals = $coupon_discount_tax_amounts;
if ( wc_prices_include_tax() ) {
$this->set_total( 'discounts_total', array_sum( $this->coupon_discount_totals ) - array_sum( $this->coupon_discount_tax_totals ) );
$this->set_total( 'discounts_tax_total', array_sum( $this->coupon_discount_tax_totals ) );
} else {
$this->set_total( 'discounts_total', array_sum( $this->coupon_discount_totals ) );
$this->set_total( 'discounts_tax_total', array_sum( $this->coupon_discount_tax_totals ) );
}
$this->cart->set_coupon_discount_totals( wc_remove_number_precision_deep( $coupon_discount_amounts ) );
$this->cart->set_coupon_discount_tax_totals( wc_remove_number_precision_deep( $coupon_discount_tax_amounts ) );
// Add totals to cart object. Note: Discount total for cart is excl tax.
$this->cart->set_discount_total( $this->get_total( 'discounts_total' ) );
$this->cart->set_discount_tax( $this->get_total( 'discounts_tax_total' ) );
}
/**
* Triggers the cart fees API, grabs the list of fees, and calculates taxes.
*
* Note: This class sets the totals for the 'object' as they are calculated. This is so that APIs like the fees API can see these totals if needed.
*
* @since 3.2.0
*/
protected function calculate_fee_totals() {
$this->get_fees_from_cart();
$this->set_total( 'fees_total', array_sum( wp_list_pluck( $this->fees, 'total' ) ) );
$this->set_total( 'fees_total_tax', array_sum( wp_list_pluck( $this->fees, 'total_tax' ) ) );
$this->cart->fees_api()->set_fees( wp_list_pluck( $this->fees, 'object' ) );
$this->cart->set_fee_total( wc_remove_number_precision_deep( array_sum( wp_list_pluck( $this->fees, 'total' ) ) ) );
$this->cart->set_fee_tax( wc_remove_number_precision_deep( array_sum( wp_list_pluck( $this->fees, 'total_tax' ) ) ) );
$this->cart->set_fee_taxes( wc_remove_number_precision_deep( $this->combine_item_taxes( wp_list_pluck( $this->fees, 'taxes' ) ) ) );
}
/**
* Calculate any shipping taxes.
*
* @since 3.2.0
*/
protected function calculate_shipping_totals() {
$this->get_shipping_from_cart();
$this->set_total( 'shipping_total', array_sum( wp_list_pluck( $this->shipping, 'total' ) ) );
$this->set_total( 'shipping_tax_total', array_sum( wp_list_pluck( $this->shipping, 'total_tax' ) ) );
$this->cart->set_shipping_total( $this->get_total( 'shipping_total' ) );
$this->cart->set_shipping_tax( $this->get_total( 'shipping_tax_total' ) );
$this->cart->set_shipping_taxes( wc_remove_number_precision_deep( $this->combine_item_taxes( wp_list_pluck( $this->shipping, 'taxes' ) ) ) );
}
/**
* Main cart totals.
*
* @since 3.2.0
*/
protected function calculate_totals() {
$this->set_total( 'total', NumberUtil::round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + array_sum( $this->get_merged_taxes( true ) ), 0 ) );
$items_tax = array_sum( $this->get_merged_taxes( false, array( 'items' ) ) );
// Shipping and fee taxes are rounded separately because they were entered excluding taxes (as opposed to item prices, which may or may not be including taxes depending upon settings).
$shipping_and_fee_taxes = NumberUtil::round( array_sum( $this->get_merged_taxes( false, array( 'fees', 'shipping' ) ) ), wc_get_price_decimals() );
$this->cart->set_total_tax( $items_tax + $shipping_and_fee_taxes );
// Allow plugins to hook and alter totals before final total is calculated.
if ( has_action( 'woocommerce_calculate_totals' ) ) {
do_action( 'woocommerce_calculate_totals', $this->cart );
}
// Allow plugins to filter the grand total, and sum the cart totals in case of modifications.
$this->cart->set_total( max( 0, apply_filters( 'woocommerce_calculated_total', $this->get_total( 'total' ), $this->cart ) ) );
}
}
PK K[Žâ´ßE” E” wc-order-functions.phpnu „[µü¤ 'limit',
'post_type' => 'type',
'post_status' => 'status',
'post_parent' => 'parent',
'author' => 'customer',
'email' => 'billing_email',
'posts_per_page' => 'limit',
'paged' => 'page',
);
foreach ( $map_legacy as $from => $to ) {
if ( isset( $args[ $from ] ) ) {
$args[ $to ] = $args[ $from ];
}
}
// Map legacy date args to modern date args.
$date_before = false;
$date_after = false;
if ( ! empty( $args['date_before'] ) ) {
$datetime = wc_string_to_datetime( $args['date_before'] );
$date_before = strpos( $args['date_before'], ':' ) ? $datetime->getOffsetTimestamp() : $datetime->date( 'Y-m-d' );
}
if ( ! empty( $args['date_after'] ) ) {
$datetime = wc_string_to_datetime( $args['date_after'] );
$date_after = strpos( $args['date_after'], ':' ) ? $datetime->getOffsetTimestamp() : $datetime->date( 'Y-m-d' );
}
if ( $date_before && $date_after ) {
$args['date_created'] = $date_after . '...' . $date_before;
} elseif ( $date_before ) {
$args['date_created'] = '<' . $date_before;
} elseif ( $date_after ) {
$args['date_created'] = '>' . $date_after;
}
$query = new WC_Order_Query( $args );
return $query->get_orders();
}
/**
* Main function for returning orders, uses the WC_Order_Factory class.
*
* @since 2.2
*
* @param mixed $the_order Post object or post ID of the order.
*
* @return bool|WC_Order|WC_Order_Refund
*/
function wc_get_order( $the_order = false ) {
if ( ! did_action( 'woocommerce_after_register_post_type' ) ) {
wc_doing_it_wrong( __FUNCTION__, 'wc_get_order should not be called before post types are registered (woocommerce_after_register_post_type action)', '2.5' );
return false;
}
return WC()->order_factory->get_order( $the_order );
}
/**
* Get all order statuses.
*
* @since 2.2
* @used-by WC_Order::set_status
* @return array
*/
function wc_get_order_statuses() {
$order_statuses = array(
'wc-pending' => _x( 'Pending payment', 'Order status', 'woocommerce' ),
'wc-processing' => _x( 'Processing', 'Order status', 'woocommerce' ),
'wc-on-hold' => _x( 'On hold', 'Order status', 'woocommerce' ),
'wc-completed' => _x( 'Completed', 'Order status', 'woocommerce' ),
'wc-cancelled' => _x( 'Cancelled', 'Order status', 'woocommerce' ),
'wc-refunded' => _x( 'Refunded', 'Order status', 'woocommerce' ),
'wc-failed' => _x( 'Failed', 'Order status', 'woocommerce' ),
);
return apply_filters( 'wc_order_statuses', $order_statuses );
}
/**
* See if a string is an order status.
*
* @param string $maybe_status Status, including any wc- prefix.
* @return bool
*/
function wc_is_order_status( $maybe_status ) {
$order_statuses = wc_get_order_statuses();
return isset( $order_statuses[ $maybe_status ] );
}
/**
* Get list of statuses which are consider 'paid'.
*
* @since 3.0.0
* @return array
*/
function wc_get_is_paid_statuses() {
return apply_filters( 'woocommerce_order_is_paid_statuses', array( 'processing', 'completed' ) );
}
/**
* Get list of statuses which are consider 'pending payment'.
*
* @since 3.6.0
* @return array
*/
function wc_get_is_pending_statuses() {
return apply_filters( 'woocommerce_order_is_pending_statuses', array( 'pending' ) );
}
/**
* Get the nice name for an order status.
*
* @since 2.2
* @param string $status Status.
* @return string
*/
function wc_get_order_status_name( $status ) {
$statuses = wc_get_order_statuses();
$status = 'wc-' === substr( $status, 0, 3 ) ? substr( $status, 3 ) : $status;
$status = isset( $statuses[ 'wc-' . $status ] ) ? $statuses[ 'wc-' . $status ] : $status;
return $status;
}
/**
* Generate an order key with prefix.
*
* @since 3.5.4
* @param string $key Order key without a prefix. By default generates a 13 digit secret.
* @return string The order key.
*/
function wc_generate_order_key( $key = '' ) {
if ( '' === $key ) {
$key = wp_generate_password( 13, false );
}
return 'wc_' . apply_filters( 'woocommerce_generate_order_key', 'order_' . $key );
}
/**
* Finds an Order ID based on an order key.
*
* @param string $order_key An order key has generated by.
* @return int The ID of an order, or 0 if the order could not be found.
*/
function wc_get_order_id_by_order_key( $order_key ) {
$data_store = WC_Data_Store::load( 'order' );
return $data_store->get_order_id_by_order_key( $order_key );
}
/**
* Get all registered order types.
*
* @since 2.2
* @param string $for Optionally define what you are getting order types for so
* only relevant types are returned.
* e.g. for 'order-meta-boxes', 'order-count'.
* @return array
*/
function wc_get_order_types( $for = '' ) {
global $wc_order_types;
if ( ! is_array( $wc_order_types ) ) {
$wc_order_types = array();
}
$order_types = array();
switch ( $for ) {
case 'order-count':
foreach ( $wc_order_types as $type => $args ) {
if ( ! $args['exclude_from_order_count'] ) {
$order_types[] = $type;
}
}
break;
case 'order-meta-boxes':
foreach ( $wc_order_types as $type => $args ) {
if ( $args['add_order_meta_boxes'] ) {
$order_types[] = $type;
}
}
break;
case 'view-orders':
foreach ( $wc_order_types as $type => $args ) {
if ( ! $args['exclude_from_order_views'] ) {
$order_types[] = $type;
}
}
break;
case 'reports':
foreach ( $wc_order_types as $type => $args ) {
if ( ! $args['exclude_from_order_reports'] ) {
$order_types[] = $type;
}
}
break;
case 'sales-reports':
foreach ( $wc_order_types as $type => $args ) {
if ( ! $args['exclude_from_order_sales_reports'] ) {
$order_types[] = $type;
}
}
break;
case 'order-webhooks':
foreach ( $wc_order_types as $type => $args ) {
if ( ! $args['exclude_from_order_webhooks'] ) {
$order_types[] = $type;
}
}
break;
case 'cot-migration':
foreach ( $wc_order_types as $type => $args ) {
if ( DataSynchronizer::PLACEHOLDER_ORDER_POST_TYPE !== $type ) {
$order_types[] = $type;
}
}
break;
case 'admin-menu':
$order_types = array_intersect(
array_keys( $wc_order_types ),
get_post_types(
array(
'show_ui' => true,
'show_in_menu' => 'woocommerce',
)
)
);
break;
default:
$order_types = array_keys( $wc_order_types );
break;
}
return apply_filters( 'wc_order_types', $order_types, $for );
}
/**
* Get an order type by post type name.
*
* @param string $type Post type name.
* @return bool|array Details about the order type.
*/
function wc_get_order_type( $type ) {
global $wc_order_types;
if ( isset( $wc_order_types[ $type ] ) ) {
return $wc_order_types[ $type ];
}
return false;
}
/**
* Register order type. Do not use before init.
*
* Wrapper for register post type, as well as a method of telling WC which.
* post types are types of orders, and having them treated as such.
*
* $args are passed to register_post_type, but there are a few specific to this function:
* - exclude_from_orders_screen (bool) Whether or not this order type also get shown in the main.
* orders screen.
* - add_order_meta_boxes (bool) Whether or not the order type gets shop_order meta boxes.
* - exclude_from_order_count (bool) Whether or not this order type is excluded from counts.
* - exclude_from_order_views (bool) Whether or not this order type is visible by customers when.
* viewing orders e.g. on the my account page.
* - exclude_from_order_reports (bool) Whether or not to exclude this type from core reports.
* - exclude_from_order_sales_reports (bool) Whether or not to exclude this type from core sales reports.
*
* @since 2.2
* @see register_post_type for $args used in that function
* @param string $type Post type. (max. 20 characters, can not contain capital letters or spaces).
* @param array $args An array of arguments.
* @return bool Success or failure
*/
function wc_register_order_type( $type, $args = array() ) {
if ( post_type_exists( $type ) ) {
return false;
}
global $wc_order_types;
if ( ! is_array( $wc_order_types ) ) {
$wc_order_types = array();
}
// Register as a post type.
if ( is_wp_error( register_post_type( $type, $args ) ) ) {
return false;
}
// Register for WC usage.
$order_type_args = array(
'exclude_from_orders_screen' => false,
'add_order_meta_boxes' => true,
'exclude_from_order_count' => false,
'exclude_from_order_views' => false,
'exclude_from_order_webhooks' => false,
'exclude_from_order_reports' => false,
'exclude_from_order_sales_reports' => false,
'class_name' => 'WC_Order',
);
$args = array_intersect_key( $args, $order_type_args );
$args = wp_parse_args( $args, $order_type_args );
$wc_order_types[ $type ] = $args;
return true;
}
/**
* Return the count of processing orders.
*
* @return int
*/
function wc_processing_order_count() {
return wc_orders_count( 'processing' );
}
/**
* Return the orders count of a specific order status.
*
* @param string $status Status.
* @param string $type (Optional) Order type. Leave empty to include all 'for order-count' order types. @{see wc_get_order_types()}.
* @return int
*/
function wc_orders_count( $status, string $type = '' ) {
$count = 0;
$legacy_statuses = array( 'draft', 'trash' );
$valid_statuses = array_merge( array_keys( wc_get_order_statuses() ), $legacy_statuses );
$status = ( ! in_array( $status, $legacy_statuses, true ) && 0 !== strpos( $status, 'wc-' ) ) ? 'wc-' . $status : $status;
$valid_types = wc_get_order_types( 'order-count' );
$type = trim( $type );
if ( ! in_array( $status, $valid_statuses, true ) || ( $type && ! in_array( $type, $valid_types, true ) ) ) {
return 0;
}
$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . $status . $type;
$cached_count = wp_cache_get( $cache_key, 'counts' );
if ( false !== $cached_count ) {
return $cached_count;
}
$types_for_count = $type ? array( $type ) : $valid_types;
foreach ( $types_for_count as $type ) {
$data_store = WC_Data_Store::load( 'shop_order' === $type ? 'order' : $type );
if ( $data_store ) {
$count += $data_store->get_order_count( $status );
}
}
wp_cache_set( $cache_key, $count, 'counts' );
return $count;
}
/**
* Grant downloadable product access to the file identified by $download_id.
*
* @param string $download_id File identifier.
* @param int|WC_Product $product Product instance or ID.
* @param WC_Order $order Order data.
* @param int $qty Quantity purchased.
* @param WC_Order_Item $item Item of the order.
* @return int|bool insert id or false on failure.
*/
function wc_downloadable_file_permission( $download_id, $product, $order, $qty = 1, $item = null ) {
if ( is_numeric( $product ) ) {
$product = wc_get_product( $product );
}
$download = new WC_Customer_Download();
$download->set_download_id( $download_id );
$download->set_product_id( $product->get_id() );
$download->set_user_id( $order->get_customer_id() );
$download->set_order_id( $order->get_id() );
$download->set_user_email( $order->get_billing_email() );
$download->set_order_key( $order->get_order_key() );
$download->set_downloads_remaining( 0 > $product->get_download_limit() ? '' : $product->get_download_limit() * $qty );
$download->set_access_granted( time() );
$download->set_download_count( 0 );
$expiry = $product->get_download_expiry();
if ( $expiry > 0 ) {
$from_date = $order->get_date_completed() ? $order->get_date_completed()->format( 'Y-m-d' ) : current_time( 'mysql', true );
$download->set_access_expires( strtotime( $from_date . ' + ' . $expiry . ' DAY' ) );
}
$download = apply_filters( 'woocommerce_downloadable_file_permission', $download, $product, $order, $qty, $item );
return $download->save();
}
/**
* Order Status completed - give downloadable product access to customer.
*
* @param int $order_id Order ID.
* @param bool $force Force downloadable permissions.
*/
function wc_downloadable_product_permissions( $order_id, $force = false ) {
$order = wc_get_order( $order_id );
if ( ! $order || ( $order->get_data_store()->get_download_permissions_granted( $order ) && ! $force ) ) {
return;
}
if ( $order->has_status( 'processing' ) && 'no' === get_option( 'woocommerce_downloads_grant_access_after_payment' ) ) {
return;
}
if ( count( $order->get_items() ) > 0 ) {
foreach ( $order->get_items() as $item ) {
$product = $item->get_product();
if ( $product && $product->exists() && $product->is_downloadable() ) {
$downloads = $product->get_downloads();
foreach ( array_keys( $downloads ) as $download_id ) {
wc_downloadable_file_permission( $download_id, $product, $order, $item->get_quantity(), $item );
}
}
}
}
$order->get_data_store()->set_download_permissions_granted( $order, true );
do_action( 'woocommerce_grant_product_download_permissions', $order_id );
}
add_action( 'woocommerce_order_status_completed', 'wc_downloadable_product_permissions' );
add_action( 'woocommerce_order_status_processing', 'wc_downloadable_product_permissions' );
/**
* Clear all transients cache for order data.
*
* @param int|WC_Order $order Order instance or ID.
*/
function wc_delete_shop_order_transients( $order = 0 ) {
if ( is_numeric( $order ) ) {
$order = wc_get_order( $order );
}
$reports = WC_Admin_Reports::get_reports();
$transients_to_clear = array(
'wc_admin_report',
);
foreach ( $reports as $report_group ) {
foreach ( $report_group['reports'] as $report_key => $report ) {
$transients_to_clear[] = 'wc_report_' . $report_key;
}
}
foreach ( $transients_to_clear as $transient ) {
delete_transient( $transient );
}
// Clear customer's order related caches.
if ( is_a( $order, 'WC_Order' ) ) {
$order_id = $order->get_id();
delete_user_meta( $order->get_customer_id(), '_money_spent' );
delete_user_meta( $order->get_customer_id(), '_order_count' );
delete_user_meta( $order->get_customer_id(), '_last_order' );
} else {
$order_id = 0;
}
// Increments the transient version to invalidate cache.
WC_Cache_Helper::get_transient_version( 'orders', true );
// Do the same for regular cache.
WC_Cache_Helper::invalidate_cache_group( 'orders' );
do_action( 'woocommerce_delete_shop_order_transients', $order_id );
}
/**
* See if we only ship to billing addresses.
*
* @return bool
*/
function wc_ship_to_billing_address_only() {
return 'billing_only' === get_option( 'woocommerce_ship_to_destination' );
}
/**
* Create a new order refund programmatically.
*
* Returns a new refund object on success which can then be used to add additional data.
*
* @since 2.2
* @throws Exception Throws exceptions when fail to create, but returns WP_Error instead.
* @param array $args New refund arguments.
* @return WC_Order_Refund|WP_Error
*/
function wc_create_refund( $args = array() ) {
$default_args = array(
'amount' => 0,
'reason' => null,
'order_id' => 0,
'refund_id' => 0,
'line_items' => array(),
'refund_payment' => false,
'restock_items' => false,
);
try {
$args = wp_parse_args( $args, $default_args );
$order = wc_get_order( $args['order_id'] );
if ( ! $order ) {
throw new Exception( __( 'Invalid order ID.', 'woocommerce' ) );
}
$remaining_refund_amount = $order->get_remaining_refund_amount();
$remaining_refund_items = $order->get_remaining_refund_items();
$refund_item_count = 0;
$refund = new WC_Order_Refund( $args['refund_id'] );
if ( 0 > $args['amount'] || $args['amount'] > $remaining_refund_amount ) {
throw new Exception( __( 'Invalid refund amount.', 'woocommerce' ) );
}
$refund->set_currency( $order->get_currency() );
$refund->set_amount( $args['amount'] );
$refund->set_parent_id( absint( $args['order_id'] ) );
$refund->set_refunded_by( get_current_user_id() ? get_current_user_id() : 1 );
$refund->set_prices_include_tax( $order->get_prices_include_tax() );
if ( ! is_null( $args['reason'] ) ) {
$refund->set_reason( $args['reason'] );
}
// Negative line items.
if ( count( $args['line_items'] ) > 0 ) {
$items = $order->get_items( array( 'line_item', 'fee', 'shipping' ) );
foreach ( $items as $item_id => $item ) {
if ( ! isset( $args['line_items'][ $item_id ] ) ) {
continue;
}
$qty = isset( $args['line_items'][ $item_id ]['qty'] ) ? $args['line_items'][ $item_id ]['qty'] : 0;
$refund_total = $args['line_items'][ $item_id ]['refund_total'];
$refund_tax = isset( $args['line_items'][ $item_id ]['refund_tax'] ) ? array_filter( (array) $args['line_items'][ $item_id ]['refund_tax'] ) : array();
if ( empty( $qty ) && empty( $refund_total ) && empty( $args['line_items'][ $item_id ]['refund_tax'] ) ) {
continue;
}
$class = get_class( $item );
$refunded_item = new $class( $item );
$refunded_item->set_id( 0 );
$refunded_item->add_meta_data( '_refunded_item_id', $item_id, true );
$refunded_item->set_total( wc_format_refund_total( $refund_total ) );
$refunded_item->set_taxes(
array(
'total' => array_map( 'wc_format_refund_total', $refund_tax ),
'subtotal' => array_map( 'wc_format_refund_total', $refund_tax ),
)
);
if ( is_callable( array( $refunded_item, 'set_subtotal' ) ) ) {
$refunded_item->set_subtotal( wc_format_refund_total( $refund_total ) );
}
if ( is_callable( array( $refunded_item, 'set_quantity' ) ) ) {
$refunded_item->set_quantity( $qty * -1 );
}
$refund->add_item( $refunded_item );
$refund_item_count += $qty;
}
}
$refund->update_taxes();
$refund->calculate_totals( false );
$refund->set_total( $args['amount'] * -1 );
// this should remain after update_taxes(), as this will save the order, and write the current date to the db
// so we must wait until the order is persisted to set the date.
if ( isset( $args['date_created'] ) ) {
$refund->set_date_created( $args['date_created'] );
}
/**
* Action hook to adjust refund before save.
*
* @since 3.0.0
*/
do_action( 'woocommerce_create_refund', $refund, $args );
if ( $refund->save() ) {
if ( $args['refund_payment'] ) {
$result = wc_refund_payment( $order, $refund->get_amount(), $refund->get_reason() );
if ( is_wp_error( $result ) ) {
$refund->delete();
return $result;
}
$refund->set_refunded_payment( true );
$refund->save();
}
if ( $args['restock_items'] ) {
wc_restock_refunded_items( $order, $args['line_items'] );
}
/**
* Trigger notification emails.
*
* Filter hook to modify the partially-refunded status conditions.
*
* @since 6.7.0
*
* @param bool $is_partially_refunded Whether the order is partially refunded.
* @param int $order_id The order id.
* @param int $refund_id The refund id.
*/
if ( (bool) apply_filters( 'woocommerce_order_is_partially_refunded', ( $remaining_refund_amount - $args['amount'] ) > 0 || ( $order->has_free_item() && ( $remaining_refund_items - $refund_item_count ) > 0 ), $order->get_id(), $refund->get_id() ) ) {
do_action( 'woocommerce_order_partially_refunded', $order->get_id(), $refund->get_id() );
} else {
do_action( 'woocommerce_order_fully_refunded', $order->get_id(), $refund->get_id() );
$parent_status = apply_filters( 'woocommerce_order_fully_refunded_status', 'refunded', $order->get_id(), $refund->get_id() );
if ( $parent_status ) {
$order->update_status( $parent_status );
}
}
}
$order->set_date_modified( time() );
$order->save();
do_action( 'woocommerce_refund_created', $refund->get_id(), $args );
do_action( 'woocommerce_order_refunded', $order->get_id(), $refund->get_id() );
} catch ( Exception $e ) {
if ( isset( $refund ) && is_a( $refund, 'WC_Order_Refund' ) ) {
$refund->delete( true );
}
return new WP_Error( 'error', $e->getMessage() );
}
return $refund;
}
/**
* Try to refund the payment for an order via the gateway.
*
* @since 3.0.0
* @throws Exception Throws exceptions when fail to refund, but returns WP_Error instead.
* @param WC_Order $order Order instance.
* @param string $amount Amount to refund.
* @param string $reason Refund reason.
* @return bool|WP_Error
*/
function wc_refund_payment( $order, $amount, $reason = '' ) {
try {
if ( ! is_a( $order, 'WC_Order' ) ) {
throw new Exception( __( 'Invalid order.', 'woocommerce' ) );
}
$gateway_controller = WC_Payment_Gateways::instance();
$all_gateways = $gateway_controller->payment_gateways();
$payment_method = $order->get_payment_method();
$gateway = isset( $all_gateways[ $payment_method ] ) ? $all_gateways[ $payment_method ] : false;
if ( ! $gateway ) {
throw new Exception( __( 'The payment gateway for this order does not exist.', 'woocommerce' ) );
}
if ( ! $gateway->supports( 'refunds' ) ) {
throw new Exception( __( 'The payment gateway for this order does not support automatic refunds.', 'woocommerce' ) );
}
$result = $gateway->process_refund( $order->get_id(), $amount, $reason );
if ( ! $result ) {
throw new Exception( __( 'An error occurred while attempting to create the refund using the payment gateway API.', 'woocommerce' ) );
}
if ( is_wp_error( $result ) ) {
throw new Exception( $result->get_error_message() );
}
return true;
} catch ( Exception $e ) {
return new WP_Error( 'error', $e->getMessage() );
}
}
/**
* Restock items during refund.
*
* @since 3.0.0
* @param WC_Order $order Order instance.
* @param array $refunded_line_items Refunded items list.
*/
function wc_restock_refunded_items( $order, $refunded_line_items ) {
if ( ! apply_filters( 'woocommerce_can_restock_refunded_items', true, $order, $refunded_line_items ) ) {
return;
}
$line_items = $order->get_items();
foreach ( $line_items as $item_id => $item ) {
if ( ! isset( $refunded_line_items[ $item_id ], $refunded_line_items[ $item_id ]['qty'] ) ) {
continue;
}
$product = $item->get_product();
$item_stock_reduced = $item->get_meta( '_reduced_stock', true );
$restock_refunded_items = (int) $item->get_meta( '_restock_refunded_items', true );
$qty_to_refund = $refunded_line_items[ $item_id ]['qty'];
if ( ! $item_stock_reduced || ! $qty_to_refund || ! $product || ! $product->managing_stock() ) {
continue;
}
$old_stock = $product->get_stock_quantity();
$new_stock = wc_update_product_stock( $product, $qty_to_refund, 'increase' );
// Update _reduced_stock meta to track changes.
$item_stock_reduced = $item_stock_reduced - $qty_to_refund;
// Keeps track of total running tally of reduced stock.
$item->update_meta_data( '_reduced_stock', $item_stock_reduced );
// Keeps track of only refunded items that needs restock.
$item->update_meta_data( '_restock_refunded_items', $qty_to_refund + $restock_refunded_items );
/* translators: 1: product ID 2: old stock level 3: new stock level */
$restock_note = sprintf( __( 'Item #%1$s stock increased from %2$s to %3$s.', 'woocommerce' ), $product->get_id(), $old_stock, $new_stock );
/**
* Allow the restock note to be modified.
*
* @since 6.4.0
*
* @param string $restock_note The original note.
* @param int $old_stock The old stock.
* @param bool|int|null $new_stock The new stock.
* @param WC_Order $order The order the refund was done for.
* @param bool|WC_Product $product The product the refund was done for.
*/
$restock_note = apply_filters( 'woocommerce_refund_restock_note', $restock_note, $old_stock, $new_stock, $order, $product );
$order->add_order_note( $restock_note );
$item->save();
do_action( 'woocommerce_restock_refunded_item', $product->get_id(), $old_stock, $new_stock, $order, $product );
}
}
/**
* Get tax class by tax id.
*
* @since 2.2
* @param int $tax_id Tax ID.
* @return string
*/
function wc_get_tax_class_by_tax_id( $tax_id ) {
global $wpdb;
return $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_class FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d", $tax_id ) );
}
/**
* Get payment gateway class by order data.
*
* @since 2.2
* @param int|WC_Order $order Order instance.
* @return WC_Payment_Gateway|bool
*/
function wc_get_payment_gateway_by_order( $order ) {
if ( WC()->payment_gateways() ) {
$payment_gateways = WC()->payment_gateways()->payment_gateways();
} else {
$payment_gateways = array();
}
if ( ! is_object( $order ) ) {
$order_id = absint( $order );
$order = wc_get_order( $order_id );
}
return is_a( $order, 'WC_Order' ) && isset( $payment_gateways[ $order->get_payment_method() ] ) ? $payment_gateways[ $order->get_payment_method() ] : false;
}
/**
* When refunding an order, create a refund line item if the partial refunds do not match order total.
*
* This is manual; no gateway refund will be performed.
*
* @since 2.4
* @param int $order_id Order ID.
*/
function wc_order_fully_refunded( $order_id ) {
$order = wc_get_order( $order_id );
$max_refund = wc_format_decimal( $order->get_total() - $order->get_total_refunded() );
if ( ! $max_refund ) {
return;
}
// Create the refund object.
wc_switch_to_site_locale();
wc_create_refund(
array(
'amount' => $max_refund,
'reason' => __( 'Order fully refunded.', 'woocommerce' ),
'order_id' => $order_id,
'line_items' => array(),
)
);
wc_restore_locale();
$order->add_order_note( __( 'Order status set to refunded. To return funds to the customer you will need to issue a refund through your payment gateway.', 'woocommerce' ) );
}
add_action( 'woocommerce_order_status_refunded', 'wc_order_fully_refunded' );
/**
* Search orders.
*
* @since 2.6.0
* @param string $term Term to search.
* @return array List of orders ID.
*/
function wc_order_search( $term ) {
$data_store = WC_Data_Store::load( 'order' );
return $data_store->search_orders( str_replace( 'Order #', '', wc_clean( $term ) ) );
}
/**
* Update total sales amount for each product within a paid order.
*
* @since 3.0.0
* @param int $order_id Order ID.
*/
function wc_update_total_sales_counts( $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order ) {
return;
}
$recorded_sales = $order->get_data_store()->get_recorded_sales( $order );
$reflected_order = in_array( $order->get_status(), array( 'cancelled', 'trash' ), true );
if ( ! $reflected_order && 'woocommerce_before_delete_order' === current_action() ) {
$reflected_order = true;
}
if ( $recorded_sales xor $reflected_order ) {
return;
}
$operation = $recorded_sales && $reflected_order ? 'decrease' : 'increase';
if ( count( $order->get_items() ) > 0 ) {
foreach ( $order->get_items() as $item ) {
$product_id = $item->get_product_id();
if ( $product_id ) {
$data_store = WC_Data_Store::load( 'product' );
$data_store->update_product_sales( $product_id, absint( $item->get_quantity() ), $operation );
}
}
}
if ( 'decrease' === $operation ) {
$order->get_data_store()->set_recorded_sales( $order, false );
} else {
$order->get_data_store()->set_recorded_sales( $order, true );
}
/**
* Called when sales for an order are recorded
*
* @param int $order_id order id
*/
do_action( 'woocommerce_recorded_sales', $order_id );
}
add_action( 'woocommerce_order_status_completed', 'wc_update_total_sales_counts' );
add_action( 'woocommerce_order_status_processing', 'wc_update_total_sales_counts' );
add_action( 'woocommerce_order_status_on-hold', 'wc_update_total_sales_counts' );
add_action( 'woocommerce_order_status_completed_to_cancelled', 'wc_update_total_sales_counts' );
add_action( 'woocommerce_order_status_processing_to_cancelled', 'wc_update_total_sales_counts' );
add_action( 'woocommerce_order_status_on-hold_to_cancelled', 'wc_update_total_sales_counts' );
add_action( 'woocommerce_trash_order', 'wc_update_total_sales_counts' );
add_action( 'woocommerce_untrash_order', 'wc_update_total_sales_counts' );
add_action( 'woocommerce_before_delete_order', 'wc_update_total_sales_counts' );
/**
* Update used coupon amount for each coupon within an order.
*
* @since 3.0.0
* @param int $order_id Order ID.
*/
function wc_update_coupon_usage_counts( $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order ) {
return;
}
$has_recorded = $order->get_data_store()->get_recorded_coupon_usage_counts( $order );
if ( $order->has_status( 'cancelled' ) && $has_recorded ) {
$action = 'reduce';
$order->get_data_store()->set_recorded_coupon_usage_counts( $order, false );
} elseif ( ! $order->has_status( 'cancelled' ) && ! $has_recorded ) {
$action = 'increase';
$order->get_data_store()->set_recorded_coupon_usage_counts( $order, true );
} elseif ( $order->has_status( 'cancelled' ) ) {
$order->get_data_store()->release_held_coupons( $order, true );
return;
} else {
return;
}
if ( count( $order->get_coupon_codes() ) > 0 ) {
foreach ( $order->get_coupon_codes() as $code ) {
if ( StringUtil::is_null_or_whitespace( $code ) ) {
continue;
}
$coupon = new WC_Coupon( $code );
$used_by = $order->get_user_id();
if ( ! $used_by ) {
$used_by = $order->get_billing_email();
}
switch ( $action ) {
case 'reduce':
$coupon->decrease_usage_count( $used_by );
break;
case 'increase':
$coupon->increase_usage_count( $used_by, $order );
break;
}
}
$order->get_data_store()->release_held_coupons( $order, true );
}
}
add_action( 'woocommerce_order_status_pending', 'wc_update_coupon_usage_counts' );
add_action( 'woocommerce_order_status_completed', 'wc_update_coupon_usage_counts' );
add_action( 'woocommerce_order_status_processing', 'wc_update_coupon_usage_counts' );
add_action( 'woocommerce_order_status_on-hold', 'wc_update_coupon_usage_counts' );
add_action( 'woocommerce_order_status_cancelled', 'wc_update_coupon_usage_counts' );
/**
* Cancel all unpaid orders after held duration to prevent stock lock for those products.
*/
function wc_cancel_unpaid_orders() {
$held_duration = get_option( 'woocommerce_hold_stock_minutes' );
// Re-schedule the event before cancelling orders
// this way in case of a DB timeout or (plugin) crash the event is always scheduled for retry.
wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
$cancel_unpaid_interval = apply_filters( 'woocommerce_cancel_unpaid_orders_interval_minutes', absint( $held_duration ) );
wp_schedule_single_event( time() + ( absint( $cancel_unpaid_interval ) * 60 ), 'woocommerce_cancel_unpaid_orders' );
if ( $held_duration < 1 || 'yes' !== get_option( 'woocommerce_manage_stock' ) ) {
return;
}
$data_store = WC_Data_Store::load( 'order' );
$unpaid_orders = $data_store->get_unpaid_orders( strtotime( '-' . absint( $held_duration ) . ' MINUTES', current_time( 'timestamp' ) ) );
if ( $unpaid_orders ) {
foreach ( $unpaid_orders as $unpaid_order ) {
$order = wc_get_order( $unpaid_order );
if ( apply_filters( 'woocommerce_cancel_unpaid_order', 'checkout' === $order->get_created_via(), $order ) ) {
$order->update_status( 'cancelled', __( 'Unpaid order cancelled - time limit reached.', 'woocommerce' ) );
}
}
}
}
add_action( 'woocommerce_cancel_unpaid_orders', 'wc_cancel_unpaid_orders' );
/**
* Sanitize order id removing unwanted characters.
*
* E.g Users can sometimes try to track an order id using # with no success.
* This function will fix this.
*
* @since 3.1.0
* @param int $order_id Order ID.
*/
function wc_sanitize_order_id( $order_id ) {
return (int) filter_var( $order_id, FILTER_SANITIZE_NUMBER_INT );
}
add_filter( 'woocommerce_shortcode_order_tracking_order_id', 'wc_sanitize_order_id' );
/**
* Get an order note.
*
* @since 3.2.0
* @param int|WP_Comment $data Note ID (or WP_Comment instance for internal use only).
* @return stdClass|null Object with order note details or null when does not exists.
*/
function wc_get_order_note( $data ) {
if ( is_numeric( $data ) ) {
$data = get_comment( $data );
}
if ( ! is_a( $data, 'WP_Comment' ) ) {
return null;
}
return (object) apply_filters(
'woocommerce_get_order_note',
array(
'id' => (int) $data->comment_ID,
'date_created' => wc_string_to_datetime( $data->comment_date ),
'content' => $data->comment_content,
'customer_note' => (bool) get_comment_meta( $data->comment_ID, 'is_customer_note', true ),
'added_by' => __( 'WooCommerce', 'woocommerce' ) === $data->comment_author ? 'system' : $data->comment_author,
),
$data
);
}
/**
* Get order notes.
*
* @since 3.2.0
* @param array $args Query arguments {
* Array of query parameters.
*
* @type string $limit Maximum number of notes to retrieve.
* Default empty (no limit).
* @type int $order_id Limit results to those affiliated with a given order ID.
* Default 0.
* @type array $order__in Array of order IDs to include affiliated notes for.
* Default empty.
* @type array $order__not_in Array of order IDs to exclude affiliated notes for.
* Default empty.
* @type string $orderby Define how should sort notes.
* Accepts 'date_created', 'date_created_gmt' or 'id'.
* Default: 'id'.
* @type string $order How to order retrieved notes.
* Accepts 'ASC' or 'DESC'.
* Default: 'DESC'.
* @type string $type Define what type of note should retrieve.
* Accepts 'customer', 'internal' or empty for both.
* Default empty.
* }
* @return stdClass[] Array of stdClass objects with order notes details.
*/
function wc_get_order_notes( $args ) {
$key_mapping = array(
'limit' => 'number',
'order_id' => 'post_id',
'order__in' => 'post__in',
'order__not_in' => 'post__not_in',
);
foreach ( $key_mapping as $query_key => $db_key ) {
if ( isset( $args[ $query_key ] ) ) {
$args[ $db_key ] = $args[ $query_key ];
unset( $args[ $query_key ] );
}
}
// Define orderby.
$orderby_mapping = array(
'date_created' => 'comment_date',
'date_created_gmt' => 'comment_date_gmt',
'id' => 'comment_ID',
);
$args['orderby'] = ! empty( $args['orderby'] ) && in_array( $args['orderby'], array( 'date_created', 'date_created_gmt', 'id' ), true ) ? $orderby_mapping[ $args['orderby'] ] : 'comment_ID';
// Set WooCommerce order type.
if ( isset( $args['type'] ) && 'customer' === $args['type'] ) {
$args['meta_query'] = array( // WPCS: slow query ok.
array(
'key' => 'is_customer_note',
'value' => 1,
'compare' => '=',
),
);
} elseif ( isset( $args['type'] ) && 'internal' === $args['type'] ) {
$args['meta_query'] = array( // WPCS: slow query ok.
array(
'key' => 'is_customer_note',
'compare' => 'NOT EXISTS',
),
);
}
// Set correct comment type.
$args['type'] = 'order_note';
// Always approved.
$args['status'] = 'approve';
// Does not support 'count' or 'fields'.
unset( $args['count'], $args['fields'] );
remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
$notes = get_comments( $args );
add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
return array_filter( array_map( 'wc_get_order_note', $notes ) );
}
/**
* Create an order note.
*
* @since 3.2.0
* @param int $order_id Order ID.
* @param string $note Note to add.
* @param bool $is_customer_note If is a costumer note.
* @param bool $added_by_user If note is create by an user.
* @return int|WP_Error Integer when created or WP_Error when found an error.
*/
function wc_create_order_note( $order_id, $note, $is_customer_note = false, $added_by_user = false ) {
$order = wc_get_order( $order_id );
if ( ! $order ) {
return new WP_Error( 'invalid_order_id', __( 'Invalid order ID.', 'woocommerce' ), array( 'status' => 400 ) );
}
return $order->add_order_note( $note, (int) $is_customer_note, $added_by_user );
}
/**
* Delete an order note.
*
* @since 3.2.0
* @param int $note_id Order note.
* @return bool True on success, false on failure.
*/
function wc_delete_order_note( $note_id ) {
return wp_delete_comment( $note_id, true );
}
PK K[\,iT T class-wc-product-factory.phpnu „[µü¤ get_product_id( $product_id );
if ( ! $product_id ) {
return false;
}
$product_type = $this->get_product_type( $product_id );
// Backwards compatibility.
if ( ! empty( $deprecated ) ) {
wc_deprecated_argument( 'args', '3.0', 'Passing args to the product factory is deprecated. If you need to force a type, construct the product class directly.' );
if ( isset( $deprecated['product_type'] ) ) {
$product_type = $this->get_classname_from_product_type( $deprecated['product_type'] );
}
}
$classname = $this->get_product_classname( $product_id, $product_type );
try {
return new $classname( $product_id, $deprecated );
} catch ( Exception $e ) {
return false;
}
}
/**
* Gets a product classname and allows filtering. Returns WC_Product_Simple if the class does not exist.
*
* @since 3.0.0
* @param int $product_id Product ID.
* @param string $product_type Product type.
* @return string
*/
public static function get_product_classname( $product_id, $product_type ) {
$classname = apply_filters( 'woocommerce_product_class', self::get_classname_from_product_type( $product_type ), $product_type, 'variation' === $product_type ? 'product_variation' : 'product', $product_id );
if ( ! $classname || ! class_exists( $classname ) ) {
$classname = 'WC_Product_Simple';
}
return $classname;
}
/**
* Get the product type for a product.
*
* @since 3.0.0
* @param int $product_id Product ID.
* @return string|false
*/
public static function get_product_type( $product_id ) {
// Allow the overriding of the lookup in this function. Return the product type here.
$override = apply_filters( 'woocommerce_product_type_query', false, $product_id );
if ( ! $override ) {
return WC_Data_Store::load( 'product' )->get_product_type( $product_id );
} else {
return $override;
}
}
/**
* Create a WC coding standards compliant class name e.g. WC_Product_Type_Class instead of WC_Product_type-class.
*
* @param string $product_type Product type.
* @return string|false
*/
public static function get_classname_from_product_type( $product_type ) {
return $product_type ? 'WC_Product_' . implode( '_', array_map( 'ucfirst', explode( '-', $product_type ) ) ) : false;
}
/**
* Get the product ID depending on what was passed.
*
* @since 3.0.0
* @param WC_Product|WP_Post|int|bool $product Product instance, post instance, numeric or false to use global $post.
* @return int|bool false on failure
*/
private function get_product_id( $product ) {
global $post;
if ( false === $product && isset( $post, $post->ID ) && 'product' === get_post_type( $post->ID ) ) {
return absint( $post->ID );
} elseif ( is_numeric( $product ) ) {
return $product;
} elseif ( $product instanceof WC_Product ) {
return $product->get_id();
} elseif ( ! empty( $product->ID ) ) {
return $product->ID;
} else {
return false;
}
}
}
PK K[¶3có ó &