Added a registry for adapters and factories to be held in globally

This commit is contained in:
Christopher Allford 2020-07-02 14:43:14 -07:00
commit 51467f4683
71 changed files with 3130 additions and 6436 deletions

43
.github/workflows/nightly-builds.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: Nightly builds
on:
schedule:
- cron: '0 0 * * *' # Run at 12 AM UTC.
jobs:
build:
name: Nightly builds
strategy:
fail-fast: false
matrix:
build: [master]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
ref: ${{ matrix.build }}
- name: Build
id: build
uses: woocommerce/action-build@master
with:
generate-zip: true
- name: Deploy nightly build
uses: WebFreak001/deploy-nightly@v1.0.3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: https://uploads.github.com/repos/${{ github.repository }}/releases/25945111/assets{?name,label}
release_id: 25945111
asset_path: ${{ steps.build.outputs.zip_path }}
asset_name: woocommerce-${{ matrix.build }}-nightly.zip
asset_content_type: application/zip
max_releases: 1
update:
name: Update nightly tag commit ref
runs-on: ubuntu-latest
steps:
- name: Update nightly tag
uses: richardsimko/github-tag-action@1.0.0
with:
tag_name: nightly
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -4,9 +4,8 @@ language: php
dist: xenial
cache:
directories:
- vendor
- $HOME/.composer/cache
directories:
- $HOME/.composer/cache
# Since Xenial services are not started by default, we need to instruct it below to start.
services:
@ -64,7 +63,7 @@ jobs:
- php: 7.4
env: WP_VERSION=latest WP_MULTISITE=0 RUN_CODE_COVERAGE=1
before_script:
install:
- export PATH="$HOME/.composer/vendor/bin:$PATH"
- |
# Remove Xdebug for a huge performance increase:
@ -80,7 +79,7 @@ before_script:
# Install WP Test suite, install PHPUnit globally:
if [[ ! -z "$WP_VERSION" ]]; then
bash tests/bin/install.sh woocommerce_test root '' localhost $WP_VERSION
composer global require "phpunit/phpunit=5.7.*|7.5.*"
composer global require "phpunit/phpunit=6.5.*|7.5.*"
fi
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16"

View File

@ -6683,6 +6683,7 @@ table.bar_chart {
.select2-selection__clear {
color: #999;
margin-top: -1px;
z-index: 1;
}
.select2-search--inline .select2-search__field {
@ -6776,7 +6777,7 @@ table.bar_chart {
.select2-selection__arrow {
right: 1px;
height: 28px;
width: 28px;
width: 23px;
background: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E") no-repeat right 5px top 55%;
background-size: 16px 16px;

View File

@ -169,25 +169,6 @@ a.button {
z-index: 1;
}
.wc-block-grid__products {
.wc-block-grid__product-onsale {
position: absolute;
top: 0;
display: inline-block;
background: $highlights-color;
color: #fff;
font-family: $headings;
font-weight: 700;
letter-spacing: -0.02em;
line-height: 1.2;
text-transform: uppercase;
z-index: 1;
font-size: 1.5rem;
padding: 1rem;
}
}
.price {
font-family: $headings;
@ -198,6 +179,7 @@ a.button {
ins {
display: inline-block;
text-decoration: none;
}
}
@ -2107,45 +2089,6 @@ a.reset_variations {
}
}
ul.wc-block-grid__products {
display: flex;
align-items: stretch;
flex-direction: row;
flex-wrap: wrap;
li.wc-block-grid__product {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
margin-bottom: 5em;
}
}
.wc-block-grid__product {
.wc-block-grid__product-title {
font-family: $headings;
color: #000;
font-size: 2.5rem;
}
.wc-block-grid__product-price {
.woocommerce-Price-amount {
font-size: 1.8rem;
}
}
.wc-block-grid__product-rating {
.star-rating {
font-size: 0.7em;
}
}
}
@media only screen and (max-width: 600px) {
.woocommerce {
@ -2435,14 +2378,6 @@ ul.wc-block-grid__products {
}
}
.wc-block-grid__products {
.wc-block-grid__product-onsale {
font-size: 1.5rem;
padding: 1rem;
}
}
/**
* Shop page
*/
@ -2596,14 +2531,6 @@ ul.wc-block-grid__products {
}
}
.wc-block-grid__products {
.wc-block-grid__product-onsale {
font-size: 1.7rem;
padding: 1.5rem;
}
}
.woocommerce-breadcrumb {
margin-bottom: 5rem;
font-size: 0.88889em;

View File

@ -123,7 +123,8 @@ jQuery( function ( $ ) {
'attribute': 'data-tip',
'fadeIn': 50,
'fadeOut': 50,
'delay': 200
'delay': 200,
'keepAlive': true
});
},

View File

@ -9,7 +9,8 @@ jQuery( function ( $ ) {
'attribute': 'data-tip',
'fadeIn': 50,
'fadeOut': 50,
'delay': 200
'delay': 200,
'keepAlive': true
});
}

View File

@ -114,6 +114,11 @@ jQuery( function ( $ ) {
wcSystemStatus.init();
$( '.wc_status_table' ).on( 'click', '.run-tool .button', function( evt ) {
evt.stopImmediatePropagation();
return window.confirm( woocommerce_admin_system_status.run_tool_confirmation );
});
$( '#log-viewer-select' ).on( 'click', 'h2 a.page-title-action', function( evt ) {
evt.stopImmediatePropagation();
return window.confirm( woocommerce_admin_system_status.delete_log_confirmation );

View File

@ -58,6 +58,7 @@
$this.$form.find('.woocommerce-importer-progress').val( response.data.percentage );
if ( 'done' === response.data.position ) {
var file_name = wc_product_import_params.file.split( '/' ).pop();
window.location = response.data.url +
'&products-imported=' +
parseInt( $this.imported, 10 ) +
@ -66,7 +67,9 @@
'&products-updated=' +
parseInt( $this.updated, 10 ) +
'&products-skipped=' +
parseInt( $this.skipped, 10 );
parseInt( $this.skipped, 10 ) +
'&file-name=' +
file_name;
} else {
$this.run_import();
}

View File

@ -188,7 +188,8 @@
'attribute': 'data-tip',
'fadeIn': 50,
'fadeOut': 50,
'delay': 200
'delay': 200,
'keepAlive': true
} );
$( '.column-wc_actions .wc-action-button' ).tipTip( {
@ -203,7 +204,8 @@
'attribute': 'data-tip',
'fadeIn': 50,
'fadeOut': 50,
'delay': 200
'delay': 200,
'keepAlive': true
} ).css( 'cursor', 'help' );
});
});

View File

@ -293,7 +293,18 @@
$qty.find( 'input.qty' ).val( '1' ).attr( 'min', '1' ).attr( 'max', '' ).change();
$qty.hide();
} else {
$qty.find( 'input.qty' ).attr( 'min', variation.min_qty ).attr( 'max', variation.max_qty ).val( variation.min_qty ).change();
var $qty_input = $qty.find( 'input.qty' ),
qty_val = parseFloat( $qty_input.val() );
if ( isNaN( qty_val ) ) {
qty_val = variation.min_qty;
} else {
qty_val = qty_val > parseFloat( variation.max_qty ) ? variation.max_qty : qty_val;
qty_val = qty_val < parseFloat( variation.min_qty ) ? variation.min_qty : qty_val;
}
$qty_input.attr( 'min', variation.min_qty ).attr( 'max', variation.max_qty ).val( qty_val ).change();
$qty.show();
}

View File

@ -101,6 +101,10 @@ jQuery( function( $ ) {
field.show();
}
}
// Class changes.
field.removeClass( 'form-row-first form-row-last form-row-wide' );
field.addClass( fieldLocale.class.join( ' ' ) );
});
var fieldsets = $(

View File

@ -657,6 +657,7 @@ jQuery( function( $ ) {
if ( code ) {
$( 'form.woocommerce-checkout' ).before( code );
$( document.body ).trigger( 'removed_coupon_in_checkout', [ data.coupon_code ] );
$( document.body ).trigger( 'update_checkout', { update_shipping_method: false } );
// Remove coupon code from coupon field

View File

@ -66,7 +66,7 @@
org_elem.hover(function(){
active_tiptip();
}, function(){
if(!opts.keepAlive){
if(!opts.keepAlive || !tiptip_holder.is(':hover')){
deactive_tiptip();
}
});
@ -188,4 +188,4 @@
}
});
}
})(jQuery);
})(jQuery);

View File

@ -14,7 +14,7 @@
"maxmind-db/reader": "1.6.0",
"pelago/emogrifier": "^3.1",
"woocommerce/action-scheduler": "3.1.6",
"woocommerce/woocommerce-admin": "1.3.0-rc.1",
"woocommerce/woocommerce-admin": "1.3.0-rc.2",
"woocommerce/woocommerce-blocks": "2.7.1",
"woocommerce/woocommerce-rest-api": "1.0.10"
},

12
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c35209de8f965f88aa5121e3ba76fc65",
"content-hash": "fbf33d1f1bf847639d9871512aaf0158",
"packages": [
{
"name": "automattic/jetpack-autoloader",
@ -419,16 +419,16 @@
},
{
"name": "woocommerce/woocommerce-admin",
"version": "v1.3.0-rc.1",
"version": "v1.3.0-rc.2",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-admin.git",
"reference": "12bc8bf522298a099bb725990cd50bae944e667f"
"reference": "241b8b14a40f1fb426b57747fd72e245a86cd608"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/12bc8bf522298a099bb725990cd50bae944e667f",
"reference": "12bc8bf522298a099bb725990cd50bae944e667f",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/241b8b14a40f1fb426b57747fd72e245a86cd608",
"reference": "241b8b14a40f1fb426b57747fd72e245a86cd608",
"shasum": ""
},
"require": {
@ -462,7 +462,7 @@
],
"description": "A modern, javascript-driven WooCommerce Admin experience.",
"homepage": "https://github.com/woocommerce/woocommerce-admin",
"time": "2020-06-23T02:57:05+00:00"
"time": "2020-06-25T01:22:20+00:00"
},
{
"name": "woocommerce/woocommerce-blocks",

View File

@ -292,6 +292,56 @@ return array(
'CZ' => array(),
'DE' => array(),
'DK' => array(),
'DZ' => array(
'DZ-01' => __( 'Adrar', 'woocommerce' ),
'DZ-02' => __( 'Chlef', 'woocommerce' ),
'DZ-03' => __( 'Laghouat', 'woocommerce' ),
'DZ-04' => __( 'Oum El Bouaghi', 'woocommerce' ),
'DZ-05' => __( 'Batna', 'woocommerce' ),
'DZ-06' => __( 'B&eacute;ja&iuml;a', 'woocommerce' ),
'DZ-07' => __( 'Biskra', 'woocommerce' ),
'DZ-08' => __( 'B&eacute;char', 'woocommerce' ),
'DZ-09' => __( 'Blida', 'woocommerce' ),
'DZ-10' => __( 'Bouira', 'woocommerce' ),
'DZ-11' => __( 'Tamanghasset', 'woocommerce' ),
'DZ-12' => __( 'T&eacute;bessa', 'woocommerce' ),
'DZ-13' => __( 'Tlemcen', 'woocommerce' ),
'DZ-14' => __( 'Tiaret', 'woocommerce' ),
'DZ-15' => __( 'Tizi Ouzou', 'woocommerce' ),
'DZ-16' => __( 'Algiers', 'woocommerce' ),
'DZ-17' => __( 'Djelfa', 'woocommerce' ),
'DZ-18' => __( 'Jijel', 'woocommerce' ),
'DZ-19' => __( 'S&eacute;tif', 'woocommerce' ),
'DZ-20' => __( 'Sa&iuml;da', 'woocommerce' ),
'DZ-21' => __( 'Skikda', 'woocommerce' ),
'DZ-22' => __( 'Sidi Bel Abb&egrave;s', 'woocommerce' ),
'DZ-23' => __( 'Annaba', 'woocommerce' ),
'DZ-24' => __( 'Guelma', 'woocommerce' ),
'DZ-25' => __( 'Constantine', 'woocommerce' ),
'DZ-26' => __( 'M&eacute;d&eacute;a', 'woocommerce' ),
'DZ-27' => __( 'Mostaganem', 'woocommerce' ),
'DZ-28' => __( 'M&rsquo;Sila', 'woocommerce' ),
'DZ-29' => __( 'Mascara', 'woocommerce' ),
'DZ-30' => __( 'Ouargla', 'woocommerce' ),
'DZ-31' => __( 'Oran', 'woocommerce' ),
'DZ-32' => __( 'El Bayadh', 'woocommerce' ),
'DZ-33' => __( 'Illizi', 'woocommerce' ),
'DZ-34' => __( 'Bordj Bou Arr&eacute;ridj', 'woocommerce' ),
'DZ-35' => __( 'Boumerd&egrave;s', 'woocommerce' ),
'DZ-36' => __( 'El Tarf', 'woocommerce' ),
'DZ-37' => __( 'Tindouf', 'woocommerce' ),
'DZ-38' => __( 'Tissemsilt', 'woocommerce' ),
'DZ-39' => __( 'El Oued', 'woocommerce' ),
'DZ-40' => __( 'Khenchela', 'woocommerce' ),
'DZ-41' => __( 'Souk Ahras', 'woocommerce' ),
'DZ-42' => __( 'Tipasa', 'woocommerce' ),
'DZ-43' => __( 'Mila', 'woocommerce' ),
'DZ-44' => __( 'A&iuml;n Defla', 'woocommerce' ),
'DZ-45' => __( 'Naama', 'woocommerce' ),
'DZ-46' => __( 'A&iuml;n T&eacute;mouchent', 'woocommerce' ),
'DZ-47' => __( 'Gharda&iuml;a', 'woocommerce' ),
'DZ-48' => __( 'Relizane', 'woocommerce' ),
),
'EE' => array(),
'ES' => array( // Spanish states.
'C' => __( 'A Coru&ntilde;a', 'woocommerce' ),
@ -370,19 +420,19 @@ return array(
),
'GP' => array(),
'GR' => array( // Greek Regions.
'I' => __( 'Αττική', 'woocommerce' ),
'A' => __( 'Ανατολική Μακεδονία και Θράκη', 'woocommerce' ),
'B' => __( 'Κεντρική Μακεδονία', 'woocommerce' ),
'C' => __( 'Δυτική Μακεδονία', 'woocommerce' ),
'D' => __( 'Ήπειρος', 'woocommerce' ),
'E' => __( 'Θεσσαλία', 'woocommerce' ),
'F' => __( 'Ιόνιοι Νήσοι', 'woocommerce' ),
'G' => __( 'Δυτική Ελλάδα', 'woocommerce' ),
'H' => __( 'Στερεά Ελλάδα', 'woocommerce' ),
'J' => __( 'Πελοπόννησος', 'woocommerce' ),
'K' => __( 'Βόρειο Αιγαίο', 'woocommerce' ),
'L' => __( 'Νότιο Αιγαίο', 'woocommerce' ),
'M' => __( 'Κρήτη', 'woocommerce' ),
'I' => __( 'Attica', 'woocommerce' ),
'A' => __( 'East Macedonia and Thrace', 'woocommerce' ),
'B' => __( 'Central Macedonia', 'woocommerce' ),
'C' => __( 'West Macedonia', 'woocommerce' ),
'D' => __( 'Epirus', 'woocommerce' ),
'E' => __( 'Thessaly', 'woocommerce' ),
'F' => __( 'Ionian Islands', 'woocommerce' ),
'G' => __( 'West Greece', 'woocommerce' ),
'H' => __( 'Central Greece', 'woocommerce' ),
'J' => __( 'Peloponnese', 'woocommerce' ),
'K' => __( 'North Aegean', 'woocommerce' ),
'L' => __( 'South Aegean', 'woocommerce' ),
'M' => __( 'Crete', 'woocommerce' ),
),
'GF' => array(),
'HK' => array( // Hong Kong states.
@ -909,6 +959,22 @@ return array(
'MZT' => __( 'Tete', 'woocommerce' ),
'MZQ' => __( 'Zambézia', 'woocommerce' ),
),
'NA' => array( // Namibia regions.
'ER' => __( 'Erongo', 'woocommerce' ),
'HA' => __( 'Hardap', 'woocommerce' ),
'KA' => __( 'Karas', 'woocommerce' ),
'KE' => __( 'Kavango East', 'woocommerce' ),
'KW' => __( 'Kavango West', 'woocommerce' ),
'KH' => __( 'Khomas', 'woocommerce' ),
'KU' => __( 'Kunene', 'woocommerce' ),
'OW' => __( 'Ohangwena', 'woocommerce' ),
'OH' => __( 'Omaheke', 'woocommerce' ),
'OS' => __( 'Omusati', 'woocommerce' ),
'ON' => __( 'Oshana', 'woocommerce' ),
'OT' => __( 'Oshikoto', 'woocommerce' ),
'OD' => __( 'Otjozondjupa', 'woocommerce' ),
'CA' => __( 'Zambezi', 'woocommerce' ),
),
'NG' => array( // Nigerian provinces.
'AB' => __( 'Abia', 'woocommerce' ),
'FC' => __( 'Abuja', 'woocommerce' ),

View File

@ -87,8 +87,8 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
/**
* Get the order if ID is passed, otherwise the order is new and empty.
* This class should NOT be instantiated, but the get_order function or new WC_Order_Factory.
* should be used. It is possible, but the aforementioned are preferred and are the only.
* This class should NOT be instantiated, but the wc_get_order function or new WC_Order_Factory
* should be used. It is possible, but the aforementioned are preferred and are the only
* methods that will be maintained going forward.
*
* @param int|object|WC_Order $order Order to read.

View File

@ -1336,19 +1336,21 @@ class WC_Product extends WC_Abstract_Legacy_Product {
$this->set_stock_quantity( '' );
$this->set_backorders( 'no' );
$this->set_low_stock_amount( '' );
// If we are stock managing and we don't have stock, force out of stock status.
} elseif ( $this->get_stock_quantity() <= get_option( 'woocommerce_notify_no_stock_amount', 0 ) && 'no' === $this->get_backorders() ) {
$this->set_stock_status( 'outofstock' );
// If we are stock managing, backorders are allowed, and we don't have stock, force on backorder status.
} elseif ( $this->get_stock_quantity() <= get_option( 'woocommerce_notify_no_stock_amount', 0 ) && 'no' !== $this->get_backorders() ) {
$this->set_stock_status( 'onbackorder' );
// If the stock level is changing and we do now have enough, force in stock status.
} elseif ( $this->get_stock_quantity() > get_option( 'woocommerce_notify_no_stock_amount', 0 ) ) {
$this->set_stock_status( 'instock' );
return;
}
$stock_is_above_notification_threshold = ( $this->get_stock_quantity() > get_option( 'woocommerce_notify_no_stock_amount', 0 ) );
$backorders_are_allowed = ( 'no' !== $this->get_backorders() );
if ( $stock_is_above_notification_threshold ) {
$new_stock_status = 'instock';
} elseif ( $backorders_are_allowed ) {
$new_stock_status = 'onbackorder';
} else {
$new_stock_status = 'outofstock';
}
$this->set_stock_status( $new_stock_status );
}
/**
@ -1378,9 +1380,7 @@ class WC_Product extends WC_Abstract_Legacy_Product {
$this->data_store->create( $this );
}
if ( $this->get_parent_id() ) {
wc_deferred_product_sync( $this->get_parent_id() );
}
$this->maybe_defer_product_sync();
/**
* Trigger action after saving to the DB.
@ -1393,6 +1393,32 @@ class WC_Product extends WC_Abstract_Legacy_Product {
return $this->get_id();
}
/**
* Delete the product, set its ID to 0, and return result.
*
* @param bool $force_delete Should the product be deleted permanently.
* @return bool result
*/
public function delete( $force_delete = false ) {
$deleted = parent::delete( $force_delete );
if ( $deleted ) {
$this->maybe_defer_product_sync();
}
return $deleted;
}
/**
* If this is a child product, queue its parent for syncing at the end of the request.
*/
protected function maybe_defer_product_sync() {
$parent_id = $this->get_parent_id();
if ( $parent_id ) {
wc_deferred_product_sync( $parent_id );
}
}
/*
|--------------------------------------------------------------------------
| Conditionals
@ -1871,7 +1897,8 @@ class WC_Product extends WC_Abstract_Legacy_Product {
* @return string
*/
public function get_shipping_class() {
if ( $class_id = $this->get_shipping_class_id() ) { // @phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found, WordPress.CodeAnalysis.AssignmentInCondition.Found
$class_id = $this->get_shipping_class_id();
if ( $class_id ) {
$term = get_term_by( 'id', $class_id, 'product_shipping_class' );
if ( $term && ! is_wp_error( $term ) ) {
@ -1963,7 +1990,8 @@ class WC_Product extends WC_Abstract_Legacy_Product {
public function get_price_suffix( $price = '', $qty = 1 ) {
$html = '';
if ( ( $suffix = get_option( 'woocommerce_price_display_suffix' ) ) && wc_tax_enabled() && 'taxable' === $this->get_tax_status() ) { // @phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found, WordPress.CodeAnalysis.AssignmentInCondition.Found
$suffix = get_option( 'woocommerce_price_display_suffix' );
if ( $suffix && wc_tax_enabled() && 'taxable' === $this->get_tax_status() ) {
if ( '' === $price ) {
$price = $this->get_price();
}

View File

@ -430,6 +430,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
'woocommerce_admin_system_status',
array(
'delete_log_confirmation' => esc_js( __( 'Are you sure you want to delete this log?', 'woocommerce' ) ),
'run_tool_confirmation' => esc_js( __( 'Are you sure you want to run this tool?', 'woocommerce' ) ),
)
);
}

View File

@ -456,11 +456,12 @@ class WC_Product_CSV_Importer_Controller {
*/
protected function done() {
check_admin_referer( 'woocommerce-csv-importer' );
$imported = isset( $_GET['products-imported'] ) ? absint( $_GET['products-imported'] ) : 0;
$updated = isset( $_GET['products-updated'] ) ? absint( $_GET['products-updated'] ) : 0;
$failed = isset( $_GET['products-failed'] ) ? absint( $_GET['products-failed'] ) : 0;
$skipped = isset( $_GET['products-skipped'] ) ? absint( $_GET['products-skipped'] ) : 0;
$errors = array_filter( (array) get_user_option( 'product_import_error_log' ) );
$imported = isset( $_GET['products-imported'] ) ? absint( $_GET['products-imported'] ) : 0;
$updated = isset( $_GET['products-updated'] ) ? absint( $_GET['products-updated'] ) : 0;
$failed = isset( $_GET['products-failed'] ) ? absint( $_GET['products-failed'] ) : 0;
$skipped = isset( $_GET['products-skipped'] ) ? absint( $_GET['products-skipped'] ) : 0;
$file_name = isset( $_GET['file-name'] ) ? sanitize_text_field( wp_unslash( $_GET['file-name'] ) ) : '';
$errors = array_filter( (array) get_user_option( 'product_import_error_log' ) );
include_once dirname( __FILE__ ) . '/views/html-csv-import-done.php';
}

View File

@ -50,6 +50,14 @@ if ( ! defined( 'ABSPATH' ) ) {
$results[] = '<a href="#" class="woocommerce-importer-done-view-errors">' . __( 'View import log', 'woocommerce' ) . '</a>';
}
if ( ! empty( $file_name ) ) {
$results[] = sprintf(
/* translators: %s: File name */
__( 'File uploaded: %s', 'woocommerce' ),
'<strong>' . $file_name . '</strong>'
);
}
/* translators: %d: import results */
echo wp_kses_post( __( 'Import complete!', 'woocommerce' ) . ' ' . implode( '. ', $results ) );
?>

View File

@ -89,19 +89,15 @@ class WC_Notes_Run_Db_Update {
* - actions are set up for the first 'Update database' notice, and
* - URL for note's action is equal to the given URL (to check for potential nonce update).
*
* @param WC_Admin_Note $note Note to check.
* @param string $update_url URL to check the note against.
* @param array( string ) $current_actions List of actions to check for.
* @param WC_Admin_Note $note Note to check.
* @param string $update_url URL to check the note against.
* @param array<int, string> $current_actions List of actions to check for.
* @return bool
*/
private static function note_up_to_date( $note, $update_url, $current_actions ) {
$actions = $note->get_actions();
if ( count( $current_actions ) === count( array_intersect( wp_list_pluck( $actions, 'name' ), $current_actions ) )
&& in_array( $update_url, wp_list_pluck( $actions, 'query' ), true ) ) {
return true;
}
return false;
return count( $current_actions ) === count( array_intersect( wp_list_pluck( $actions, 'name' ), $current_actions ) )
&& in_array( $update_url, wp_list_pluck( $actions, 'query' ), true );
}
/**

View File

@ -74,6 +74,7 @@ $exporter = new WC_Product_CSV_Exporter();
echo '<option value="' . esc_attr( $category->slug ) . '">' . esc_html( $category->name ) . '</option>';
}
?>
</select>
</td>
</tr>
<tr>

View File

@ -908,7 +908,7 @@ class WC_AJAX {
$validation_error = apply_filters( 'woocommerce_ajax_add_order_item_validation', $validation_error, $product, $order, $qty );
if ( $validation_error->get_error_code() ) {
throw new Exception( '<strong>' . __( 'Error:', 'woocommerce' ) . '</strong> ' . $validation_error->get_error_message() );
throw new Exception( sprintf( __( 'Error: %s', 'woocommerce' ), $validation_error->get_error_message() ) );
}
$item_id = $order->add_product( $product, $qty );
$item = apply_filters( 'woocommerce_ajax_order_item', $order->get_item( $item_id ), $item_id, $order, $product );

View File

@ -40,12 +40,14 @@ class WC_Cache_Helper {
* @since 3.6.0
*/
public static function additional_nocache_headers( $headers ) {
$agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
/**
* Allow CDN plugins to disable nocache headers.
* Allow plugins to enable nocache headers. Enabled for Google weblight.
*
* @param bool $enable_nocache_headers Flag indicating whether to add nocache headers. Default: true.
* @see https://support.google.com/webmasters/answer/1061943?hl=en
* @param bool $enable_nocache_headers Flag indicating whether to add nocache headers. Default: false.
*/
if ( apply_filters( 'woocommerce_enable_nocache_headers', true ) ) {
if ( false !== strpos( $agent, 'googleweblight' ) || apply_filters( 'woocommerce_enable_nocache_headers', false ) ) {
// no-transform: Opt-out of Google weblight. https://support.google.com/webmasters/answer/6211428?hl=en.
$headers['Cache-Control'] = 'no-transform, no-cache, no-store, must-revalidate';
}

View File

@ -41,13 +41,6 @@ class WC_Cart extends WC_Legacy_Cart {
*/
public $applied_coupons = array();
/**
* Are prices in the cart displayed inc or excl tax?
*
* @var string
*/
public $tax_display_cart = 'incl';
/**
* This stores the chosen shipping methods for the cart item packages.
*
@ -102,9 +95,8 @@ class WC_Cart extends WC_Legacy_Cart {
* Constructor for the cart class. Loads options and hooks in the init method.
*/
public function __construct() {
$this->session = new WC_Cart_Session( $this );
$this->fees_api = new WC_Cart_Fees( $this );
$this->tax_display_cart = $this->is_tax_displayed();
$this->session = new WC_Cart_Session( $this );
$this->fees_api = new WC_Cart_Fees( $this );
// Register hooks for the objects.
$this->session->init();
@ -363,7 +355,7 @@ class WC_Cart extends WC_Legacy_Cart {
public function display_prices_including_tax() {
$customer_exempt = $this->get_customer() && $this->get_customer()->get_is_vat_exempt();
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, 'incl' === $this->tax_display_cart && ! $customer_exempt );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, 'incl' === $this->is_tax_displayed() && ! $customer_exempt );
}
/*

View File

@ -516,7 +516,7 @@ class WC_Countries {
'DK' => "{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' => "{name}\n{company}\n{city}\n{address_1}\n{address_2}\n{postcode}\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}",
@ -527,6 +527,7 @@ class WC_Countries {
'NZ' => "{name}\n{company}\n{address_1}\n{address_2}\n{city} {postcode}\n{country}",
'NO' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\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{state} \n{country} {postcode}",
'PT' => "{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}",
'RS' => "{name}\n{company}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}",
@ -981,6 +982,27 @@ class WC_Countries {
),
),
'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' ),
),
@ -1039,12 +1061,12 @@ class WC_Countries {
'priority' => 20,
),
'postcode' => array(
'class' => array( 'form-row-first' ),
'class' => array( 'form-row-first', 'address-field' ),
'priority' => 65,
),
'state' => array(
'label' => __( 'Prefecture', 'woocommerce' ),
'class' => array( 'form-row-last' ),
'class' => array( 'form-row-last', 'address-field' ),
'priority' => 66,
),
'city' => array(
@ -1149,6 +1171,15 @@ class WC_Countries {
'required' => false,
),
),
'PR' => array(
'city' => array(
'required' => false,
'hidden' => true,
),
'state' => array(
'label' => __( 'Municipality', 'woocommerce' ),
),
),
'PT' => array(
'state' => array(
'required' => false,

View File

@ -1688,7 +1688,7 @@ class WC_Order extends WC_Abstract_Order {
return 0;
}
if ( is_user_logged_in() && current_user_can( 'edit_shop_order', $this->get_id() ) && $added_by_user ) {
if ( is_user_logged_in() && current_user_can( 'edit_shop_orders', $this->get_id() ) && $added_by_user ) {
$user = get_user_by( 'id', get_current_user_id() );
$comment_author = $user->display_name;
$comment_author_email = $user->user_email;
@ -1731,6 +1731,16 @@ class WC_Order extends WC_Abstract_Order {
);
}
/**
* Action hook fired after an order note is added.
*
* @param int $order_note_id Order note ID.
* @param WC_Order $order Order data.
*
* @since 4.4.0
*/
do_action( 'woocommerce_order_note_added', $comment_id, $this );
return $comment_id;
}

View File

@ -23,6 +23,7 @@ class WC_Post_Types {
add_action( 'init', array( __CLASS__, 'register_post_types' ), 5 );
add_action( 'init', array( __CLASS__, 'register_post_status' ), 9 );
add_action( 'init', array( __CLASS__, 'support_jetpack_omnisearch' ) );
add_filter( 'term_updated_messages', array( __CLASS__, 'updated_term_messages' ) );
add_filter( 'rest_api_allowed_post_types', array( __CLASS__, 'rest_api_allowed_post_types' ) );
add_action( 'woocommerce_after_register_post_type', array( __CLASS__, 'maybe_flush_rewrite_rules' ) );
add_action( 'woocommerce_flush_rewrite_rules', array( __CLASS__, 'flush_rewrite_rules' ) );
@ -481,6 +482,66 @@ class WC_Post_Types {
do_action( 'woocommerce_after_register_post_type' );
}
/**
* Customize taxonomies update messages.
*
* @param array $messages The list of available messages.
* @since 4.4.0
* @return bool
*/
public function updated_term_messages( $messages ) {
$messages['product_cat'] = array(
0 => '',
1 => __( 'Category added.', 'woocommerce' ),
2 => __( 'Category deleted.', 'woocommerce' ),
3 => __( 'Category updated.', 'woocommerce' ),
4 => __( 'Category not added.', 'woocommerce' ),
5 => __( 'Category not updated.', 'woocommerce' ),
6 => __( 'Category not deleted.', 'woocommerce' ),
);
$messages['product_tag'] = array(
0 => '',
1 => __( 'Tag added.', 'woocommerce' ),
2 => __( 'Tag deleted.', 'woocommerce' ),
3 => __( 'Tag updated.', 'woocommerce' ),
4 => __( 'Tag not added.', 'woocommerce' ),
5 => __( 'Tag not updated.', 'woocommerce' ),
6 => __( 'Tag not deleted.', 'woocommerce' ),
);
$wc_product_attributes = array();
$attribute_taxonomies = wc_get_attribute_taxonomies();
if ( $attribute_taxonomies ) {
foreach ( $attribute_taxonomies as $tax ) {
$name = wc_attribute_taxonomy_name( $tax->attribute_name );
if ( $name ) {
$label = ! empty( $tax->attribute_label ) ? $tax->attribute_label : $tax->attribute_name;
$messages[ $name ] = array(
0 => '',
/* translators: %s: taxonomy label */
1 => sprintf( _x( '%s added', 'taxonomy term messages', 'woocommerce' ), $label ),
/* translators: %s: taxonomy label */
2 => sprintf( _x( '%s deleted', 'taxonomy term messages', 'woocommerce' ), $label ),
/* translators: %s: taxonomy label */
3 => sprintf( _x( '%s updated', 'taxonomy term messages', 'woocommerce' ), $label ),
/* translators: %s: taxonomy label */
4 => sprintf( _x( '%s not added', 'taxonomy term messages', 'woocommerce' ), $label ),
/* translators: %s: taxonomy label */
5 => sprintf( _x( '%s not updated', 'taxonomy term messages', 'woocommerce' ), $label ),
/* translators: %s: taxonomy label */
6 => sprintf( _x( '%s not deleted', 'taxonomy term messages', 'woocommerce' ), $label ),
);
}
}
}
return $messages;
}
/**
* Register our custom post statuses, used for order status.
*/

View File

@ -413,28 +413,10 @@ class WC_Product_Variable extends WC_Product {
* @since 3.0.0
*/
public function validate_props() {
// Before updating, ensure stock props are all aligned. Qty and backorders are not needed if not stock managed.
parent::validate_props();
if ( ! $this->get_manage_stock() ) {
$this->set_stock_quantity( '' );
$this->set_backorders( 'no' );
$this->set_low_stock_amount( '' );
$this->data_store->sync_stock_status( $this );
// If we are stock managing, backorders are allowed, and we don't have stock, force on backorder status.
} elseif ( $this->get_stock_quantity() <= get_option( 'woocommerce_notify_no_stock_amount', 0 ) && 'no' !== $this->get_backorders() ) {
$this->set_stock_status( 'onbackorder' );
// If we are stock managing and we don't have stock, force out of stock status.
} elseif ( $this->get_stock_quantity() <= get_option( 'woocommerce_notify_no_stock_amount', 0 ) && 'no' === $this->get_backorders() ) {
$this->set_stock_status( 'outofstock' );
// If the stock level is changing and we do now have enough, force in stock status.
} elseif ( $this->get_stock_quantity() > get_option( 'woocommerce_notify_no_stock_amount', 0 ) && array_key_exists( 'stock_quantity', $this->get_changes() ) ) {
$this->set_stock_status( 'instock' );
// Otherwise revert to status the children have.
} else {
$this->set_stock_status( $this->child_is_in_stock() ? 'instock' : 'outofstock' );
}
}

View File

@ -70,6 +70,9 @@ class WC_Validation {
case 'GB':
$valid = self::is_gb_postcode( $postcode );
break;
case 'HU':
$valid = (bool) preg_match( '/^([0-9]{4})$/i', $postcode );
break;
case 'IE':
$valid = (bool) preg_match( '/([AC-FHKNPRTV-Y]\d{2}|D6W)[0-9AC-FHKNPRTV-Y]{4}/', wc_normalize_postcode( $postcode ) );
break;

View File

@ -57,8 +57,10 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
*/
public function create( &$order ) {
$order->set_version( Constants::get_constant( 'WC_VERSION' ) );
$order->set_date_created( time() );
$order->set_currency( $order->get_currency() ? $order->get_currency() : get_woocommerce_currency() );
if ( ! $order->get_date_created( 'edit' ) ) {
$order->set_date_created( time() );
}
$id = wp_insert_post(
apply_filters(
@ -71,7 +73,7 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
'ping_status' => 'closed',
'post_author' => 1,
'post_title' => $this->get_post_title(),
'post_password' => wc_generate_order_key(),
'post_password' => $this->get_order_key( $order ),
'post_parent' => $order->get_parent_id( 'edit' ),
'post_excerpt' => $this->get_post_excerpt( $order ),
)
@ -264,6 +266,17 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
// @codingStandardsIgnoreEnd
}
/**
* Get order key.
*
* @since 4.3.0
* @param WC_order $order Order object.
* @return string
*/
protected function get_order_key( $order ) {
return wc_generate_order_key();
}
/**
* Read order data. Can be overridden by child classes to load other props.
*

View File

@ -81,7 +81,9 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
* @param WC_Order $order Order object.
*/
public function create( &$order ) {
$order->set_order_key( wc_generate_order_key() );
if ( '' === $order->get_order_key() ) {
$order->set_order_key( wc_generate_order_key() );
}
parent::create( $order );
do_action( 'woocommerce_new_order', $order->get_id(), $order );
}
@ -302,6 +304,21 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
return $order->get_customer_note();
}
/**
* Get order key.
*
* @since 4.3.0
* @param WC_order $order Order object.
* @return string
*/
protected function get_order_key( $order ) {
if ( '' !== $order->get_order_key() ) {
return $order->get_order_key();
}
return parent::get_order_key( $order );
}
/**
* Get amount already refunded.
*

View File

@ -120,7 +120,13 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
* @return array
*/
public function get_parsed_data() {
return apply_filters( 'woocommerce_product_importer_parsed_data', $this->parsed_data, $this->get_raw_data() );
/**
* Filter product importer parsed data.
*
* @param array $parsed_data Parsed data.
* @param WC_Product_Importer $importer Importer instance.
*/
return apply_filters( 'woocommerce_product_importer_parsed_data', $this->parsed_data, $this );
}
/**

View File

@ -593,7 +593,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
* Just skip current field.
*
* By default is applied wc_clean() to all not listed fields
* in self::get_formating_callback(), use this method to skip any formating.
* in self::get_formatting_callback(), use this method to skip any formatting.
*
* @param string $value Field value.
*
@ -675,11 +675,22 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
}
/**
* Get formatting callback.
* Deprecated get formatting callback method.
*
* @deprecated 4.3.0
* @return array
*/
protected function get_formating_callback() {
return $this->get_formatting_callback();
}
/**
* Get formatting callback.
*
* @since 4.3.0
* @return array
*/
protected function get_formatting_callback() {
/**
* Columns not mentioned here will get parsed with 'wc_clean'.
@ -936,7 +947,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
* Map and format raw data to known fields.
*/
protected function set_parsed_data() {
$parse_functions = $this->get_formating_callback();
$parse_functions = $this->get_formatting_callback();
$mapped_keys = $this->get_mapped_keys();
$use_mb = function_exists( 'mb_convert_encoding' );
@ -974,6 +985,12 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
$data[ $mapped_keys[ $id ] ] = call_user_func( $parse_functions[ $id ], $value );
}
/**
* Filter product importer parsed data.
*
* @param array $parsed_data Parsed data.
* @param WC_Product_Importer $importer Importer instance.
*/
$this->parsed_data[] = apply_filters( 'woocommerce_product_importer_parsed_data', $this->expand_data( $data ), $this );
}
}

View File

@ -58,15 +58,50 @@ abstract class WC_Legacy_Cart {
* @param mixed $value Value to set.
*/
public function __isset( $name ) {
if ( array_key_exists( $name, $this->cart_session_data ) || 'fees' === $name ) {
$legacy_keys = array_merge(
array(
'dp',
'prices_include_tax',
'round_at_subtotal',
'cart_contents_total',
'total',
'subtotal',
'subtotal_ex_tax',
'tax_total',
'fee_total',
'discount_cart',
'discount_cart_tax',
'shipping_total',
'shipping_tax_total',
'display_totals_ex_tax',
'display_cart_ex_tax',
'cart_contents_weight',
'cart_contents_count',
'coupons',
'taxes',
'shipping_taxes',
'coupon_discount_amounts',
'coupon_discount_tax_amounts',
'fees',
'tax',
'discount_total',
'tax_display_cart',
),
is_array( $this->cart_session_data ) ? array_keys( $this->cart_session_data ) : array()
);
if ( in_array( $name, $legacy_keys, true ) ) {
return true;
}
return false;
}
/**
* Magic getters.
*
* If you add/remove cases here please update $legacy_keys in __isset accordingly.
*
* @param string $name Property name.
* @return mixed
*/
@ -164,6 +199,10 @@ abstract class WC_Legacy_Cart {
wc_deprecated_argument( 'WC_Cart->discount_total', '2.3', 'After tax coupons are no longer supported. For more information see: https://woocommerce.wordpress.com/2014/12/upcoming-coupon-changes-in-woocommerce-2-3/' );
$value = 0;
break;
case 'tax_display_cart':
wc_deprecated_argument( 'WC_Cart->tax_display_cart', '4.3', 'Use WC_Cart->is_tax_displayed() instead.' );
$value = $this->is_tax_displayed();
break;
}
return $value;
}

View File

@ -95,6 +95,11 @@ class WC_Site_Tracking {
*/
public static function add_enable_tracking_function() {
global $wp_scripts;
if ( ! isset( $wp_scripts->registered['woo-tracks'] ) ) {
return;
}
$woo_tracks_script = $wp_scripts->registered['woo-tracks']->src;
?>

View File

@ -80,6 +80,7 @@ class WC_Orders_Tracking {
'previous_status' => $previous_status,
'date_created' => $order->get_date_created() ? $order->get_date_created()->date( 'Y-m-d' ) : '',
'payment_method' => $order->get_payment_method(),
'order_total' => $order->get_total(),
);
WC_Tracks::record_event( 'orders_edit_status_change', $properties );

View File

@ -99,7 +99,7 @@ function wc_get_account_menu_items() {
'dashboard' => __( 'Dashboard', 'woocommerce' ),
'orders' => __( 'Orders', 'woocommerce' ),
'downloads' => __( 'Downloads', 'woocommerce' ),
'edit-address' => __( 'Addresses', 'woocommerce' ),
'edit-address' => _n( 'Addresses', 'Address', (int) wc_shipping_enabled(), 'woocommerce' ),
'payment-methods' => __( 'Payment methods', 'woocommerce' ),
'edit-account' => __( 'Account details', 'woocommerce' ),
'customer-logout' => __( 'Logout', 'woocommerce' ),

View File

@ -51,7 +51,7 @@ function wc_get_attribute_taxonomies() {
$cache_key = $prefix . 'attributes';
$cache_value = wp_cache_get( $cache_key, 'woocommerce-attributes' );
if ( $cache_value ) {
if ( false !== $cache_value ) {
return $cache_value;
}

View File

@ -314,7 +314,7 @@ function wc_get_template( $template_name, $args = array(), $template_path = '',
if ( $filter_template !== $template ) {
if ( ! file_exists( $filter_template ) ) {
/* translators: %s template */
wc_doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'woocommerce' ), '<code>' . $template . '</code>' ), '2.1' );
wc_doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'woocommerce' ), '<code>' . $filter_template . '</code>' ), '2.1' );
return;
}
$template = $filter_template;
@ -2418,6 +2418,10 @@ function wc_load_cart() {
return;
}
// Ensure dependencies are loaded in all contexts.
include_once WC_ABSPATH . 'includes/wc-cart-functions.php';
include_once WC_ABSPATH . 'includes/wc-notice-functions.php';
WC()->initialize_session();
WC()->initialize_cart();
}

View File

@ -582,7 +582,7 @@ function wc_price( $price, $args = array() ) {
}
$formatted_price = ( $negative ? '-' : '' ) . sprintf( $args['price_format'], '<span class="woocommerce-Price-currencySymbol">' . get_woocommerce_currency_symbol( $args['currency'] ) . '</span>', $price );
$return = '<span class="woocommerce-Price-amount amount">' . $formatted_price . '</span>';
$return = '<span class="woocommerce-Price-amount amount"><bdi>' . $formatted_price . '</bdi></span>';
if ( $args['ex_tax_label'] && wc_tax_enabled() ) {
$return .= ' <small class="woocommerce-Price-taxLabel tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';

View File

@ -149,13 +149,18 @@ function wc_get_order_status_name( $status ) {
}
/**
* Generate an order key.
* 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() {
return 'wc_' . apply_filters( 'woocommerce_generate_order_key', 'order_' . wp_generate_password( 13, false ) );
function wc_generate_order_key( $key = '' ) {
if ( '' === $key ) {
$key = wp_generate_password( 13, false );
}
return 'wc_' . apply_filters( 'woocommerce_generate_order_key', 'order_' . $key );
}
/**

View File

@ -46,7 +46,6 @@ function wc_update_product_stock( $product, $stock_quantity = null, $operation =
// If this is not being called during an update routine, save the product so stock status etc is in sync, and caches are cleared.
if ( ! $updating ) {
$product_with_stock->set_stock_status();
$product_with_stock->save();
}
@ -66,10 +65,11 @@ function wc_update_product_stock( $product, $stock_quantity = null, $operation =
* Update a product's stock status.
*
* @param int $product_id Product ID.
* @param int $status Status.
* @param string $status Status.
*/
function wc_update_product_stock_status( $product_id, $status ) {
$product = wc_get_product( $product_id );
if ( $product ) {
$product->set_stock_status( $status );
$product->save();

View File

@ -2703,7 +2703,7 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
} else {
$field = '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="country_to_state country_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . '><option value="">' . esc_html__( 'Select a country / region&hellip;', 'woocommerce' ) . '</option>';
$field = '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="country_to_state country_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . '><option value="default">' . esc_html__( 'Select a country / region&hellip;', 'woocommerce' ) . '</option>';
foreach ( $countries as $ckey => $cvalue ) {
$field .= '<option value="' . esc_attr( $ckey ) . '" ' . selected( $value, $ckey, false ) . '>' . esc_html( $cvalue ) . '</option>';

View File

@ -44,7 +44,7 @@ if ( ! function_exists( 'wc_create_new_customer' ) ) {
}
if ( email_exists( $email ) ) {
return new WP_Error( 'registration-error-email-exists', apply_filters( 'woocommerce_registration_error_email_exists', __( 'An account is already registered with your email address. Please log in.', 'woocommerce' ), $email ) );
return new WP_Error( 'registration-error-email-exists', apply_filters( 'woocommerce_registration_error_email_exists', __( 'An account is already registered with your email address. <a href="#" class="showlogin">Please log in.</a>', 'woocommerce' ), $email ) );
}
if ( 'yes' === get_option( 'woocommerce_registration_generate_username', 'yes' ) && empty( $username ) ) {

View File

@ -8,6 +8,23 @@
defined( 'ABSPATH' ) || exit;
/**
* Process the synchronous web hooks at the end of the request.
*
* @since 4.4.0
*/
function wc_webhook_execute_synchronous_queue() {
global $wc_queued_sync_webhooks;
if ( empty( $wc_queued_sync_webhooks ) ) {
return;
}
foreach ( $wc_queued_sync_webhooks as $data ) {
$data['webhook']->deliver( $data['arg'] );
}
}
register_shutdown_function( 'wc_webhook_execute_synchronous_queue' );
/**
* Process webhook delivery.
*
@ -33,8 +50,16 @@ function wc_webhook_process_delivery( $webhook, $arg ) {
WC()->queue()->add( 'woocommerce_deliver_webhook_async', $queue_args, 'woocommerce-webhooks' );
}
} else {
// Deliver immediately.
$webhook->deliver( $arg );
// We need to queue the webhook so that it can be ran after the request has finished processing.
// This must be done in order to keep parity with how they are executed asynchronously.
global $wc_queued_sync_webhooks;
if ( ! isset( $wc_queued_sync_webhooks ) ) {
$wc_queued_sync_webhooks = array();
}
$wc_queued_sync_webhooks[] = array(
'webhook' => $webhook,
'arg' => $arg,
);
}
}
add_action( 'woocommerce_webhook_process_delivery', 'wc_webhook_process_delivery', 10, 2 );

8550
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@
"license": "GPL-3.0+",
"main": "Gruntfile.js",
"scripts": {
"build": "grunt && npm run makepot",
"build": "grunt && npm run makepot && npm run build:packages",
"build-watch": "grunt watch",
"build:packages": "lerna run build",
"build:zip": "./bin/build-zip.sh",
@ -39,7 +39,7 @@
"@wordpress/babel-preset-default": "3.0.2",
"@wordpress/e2e-test-utils": "4.6.0",
"@wordpress/eslint-plugin": "7.1.0",
"autoprefixer": "9.7.5",
"autoprefixer": "9.8.4",
"babel-eslint": "10.1.0",
"chai": "4.2.0",
"chai-as-promised": "7.1.1",

View File

@ -73,7 +73,7 @@ defined( 'ABSPATH' ) || exit;
}
if ( 'itemized' === get_option( 'woocommerce_tax_total_display' ) ) {
foreach ( WC()->cart->get_tax_totals() as $code => $tax ) { // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited
foreach ( WC()->cart->get_tax_totals() as $code => $tax ) { // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
?>
<tr class="tax-rate tax-rate-<?php echo esc_attr( sanitize_title( $code ) ); ?>">
<th><?php echo esc_html( $tax->label ) . $estimated_text; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></th>

View File

@ -11,8 +11,8 @@
* the readme will list any important changes.
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.8.0
* @package WooCommerce\Templates
* @version 4.4.0
*/
defined( 'ABSPATH' ) || exit;
@ -77,9 +77,9 @@ do_action( 'woocommerce_before_cart' ); ?>
<td class="product-name" data-title="<?php esc_attr_e( 'Product', 'woocommerce' ); ?>">
<?php
if ( ! $product_permalink ) {
echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', $_product->get_name(), $cart_item, $cart_item_key ) . '&nbsp;' );
echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', esc_html( $_product->get_name() ), $cart_item, $cart_item_key ) . '&nbsp;' );
} else {
echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', sprintf( '<a href="%s">%s</a>', esc_url( $product_permalink ), $_product->get_name() ), $cart_item, $cart_item_key ) );
echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', sprintf( '<a href="%s">%s</a>', esc_url( $product_permalink ), esc_html( $_product->get_name() ) ), $cart_item, $cart_item_key ) );
}
do_action( 'woocommerce_after_cart_item_name', $cart_item, $cart_item_key );

View File

@ -12,7 +12,7 @@
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.0.0
* @version 4.4.0
*/
defined( 'ABSPATH' ) || exit;
@ -20,8 +20,13 @@ defined( 'ABSPATH' ) || exit;
if ( $cross_sells ) : ?>
<div class="cross-sells">
<?php
$heading = apply_filters( 'woocommerce_product_cross_sells_products_heading', __( 'You may be interested in&hellip;', 'woocommerce' ) );
<h2><?php esc_html_e( 'You may be interested in&hellip;', 'woocommerce' ); ?></h2>
if ( $heading ) :
?>
<h2><?php echo esc_html( $heading ); ?></h2>
<?php endif; ?>
<?php woocommerce_product_loop_start(); ?>
@ -30,7 +35,7 @@ if ( $cross_sells ) : ?>
<?php
$post_object = get_post( $cross_sell->get_id() );
setup_postdata( $GLOBALS['post'] =& $post_object ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited, Squiz.PHP.DisallowMultipleAssignments.Found
setup_postdata( $GLOBALS['post'] =& $post_object ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited, Squiz.PHP.DisallowMultipleAssignments.Found
wc_get_template_part( 'content', 'product' );
?>

View File

@ -28,7 +28,7 @@ do_action( 'woocommerce_before_shipping_calculator' ); ?>
<?php if ( apply_filters( 'woocommerce_shipping_calculator_enable_country', true ) ) : ?>
<p class="form-row form-row-wide" id="calc_shipping_country_field">
<select name="calc_shipping_country" id="calc_shipping_country" class="country_to_state country_select" rel="calc_shipping_state">
<option value=""><?php esc_html_e( 'Select a country / region&hellip;', 'woocommerce' ); ?></option>
<option value="default"><?php esc_html_e( 'Select a country / region&hellip;', 'woocommerce' ); ?></option>
<?php
foreach ( WC()->countries->get_shipping_countries() as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '"' . selected( WC()->customer->get_shipping_country(), esc_attr( $key ), false ) . '>' . esc_html( $value ) . '</option>';

View File

@ -17,7 +17,7 @@
defined( 'ABSPATH' ) || exit;
$totals = $order->get_order_item_totals(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited
$totals = $order->get_order_item_totals(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
?>
<form id="order_review" method="post">

View File

@ -83,7 +83,7 @@ defined( 'ABSPATH' ) || exit;
<?php if ( wc_tax_enabled() && ! WC()->cart->display_prices_including_tax() ) : ?>
<?php if ( 'itemized' === get_option( 'woocommerce_tax_total_display' ) ) : ?>
<?php foreach ( WC()->cart->get_tax_totals() as $code => $tax ) : // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited ?>
<?php foreach ( WC()->cart->get_tax_totals() as $code => $tax ) : // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited ?>
<tr class="tax-rate tax-rate-<?php echo esc_attr( sanitize_title( $code ) ); ?>">
<th><?php echo esc_html( $tax->label ); ?></th>
<td><?php echo wp_kses_post( $tax->formatted_amount ); ?></td>

View File

@ -164,7 +164,7 @@ body {
}
.link {
color: <?php echo esc_attr( $base ); ?>;
color: <?php echo esc_attr( $link_color ); ?>;
}
#header_wrapper {

View File

@ -24,7 +24,7 @@ echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
/* translators: %s: Customer username */
echo sprintf( esc_html__( 'Hi %s,', 'woocommerce' ), esc_html( $user_login ) ) . "\n\n";
/* translators: %1$s: Site title, %2$s: Username, %3$s: My account link */
echo sprintf( esc_html__( 'Thanks for creating an account on %1$s. Your username is %2$s. You can access your account area to view orders, change your password, and more at: %3$s', 'woocommerce' ), esc_html( $blogname ), '<strong>' . esc_html( $user_login ) . '</strong>', esc_html( wc_get_page_permalink( 'myaccount' ) ) ) . "\n\n";
echo sprintf( esc_html__( 'Thanks for creating an account on %1$s. Your username is %2$s. You can access your account area to view orders, change your password, and more at: %3$s', 'woocommerce' ), esc_html( $blogname ), esc_html( $user_login ), esc_html( wc_get_page_permalink( 'myaccount' ) ) ) . "\n\n";
if ( 'yes' === get_option( 'woocommerce_registration_generate_password' ) && $password_generated ) {
/* translators: %s: Auto generated password */

View File

@ -12,21 +12,27 @@
* happen. When this occurs the version of the template file will be bumped and
* the readme will list any important changes.
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 2.6.0
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 4.4.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
exit; // Exit if accessed directly.
}
$allowed_html = array(
'a' => array(
'href' => array(),
),
);
?>
<p>
<?php
printf(
/* translators: 1: user display name 2: logout url */
__( 'Hello %1$s (not %1$s? <a href="%2$s">Log out</a>)', 'woocommerce' ),
wp_kses( __( 'Hello %1$s (not %1$s? <a href="%2$s">Log out</a>)', 'woocommerce' ), $allowed_html ),
'<strong>' . esc_html( $current_user->display_name ) . '</strong>',
esc_url( wc_logout_url() )
);
@ -35,8 +41,14 @@ if ( ! defined( 'ABSPATH' ) ) {
<p>
<?php
/* translators: 1: Orders URL 2: Address URL 3: Account URL. */
$dashboard_desc = __( 'From your account dashboard you can view your <a href="%1$s">recent orders</a>, manage your <a href="%2$s">billing address</a>, and <a href="%3$s">edit your password and account details</a>.', 'woocommerce' );
if ( wc_shipping_enabled() ) {
/* translators: 1: Orders URL 2: Addresses URL 3: Account URL. */
$dashboard_desc = __( 'From your account dashboard you can view your <a href="%1$s">recent orders</a>, manage your <a href="%2$s">shipping and billing addresses</a>, and <a href="%3$s">edit your password and account details</a>.', 'woocommerce' );
}
printf(
__( 'From your account dashboard you can view your <a href="%1$s">recent orders</a>, manage your <a href="%2$s">shipping and billing addresses</a>, and <a href="%3$s">edit your password and account details</a>.', 'woocommerce' ),
wp_kses( $dashboard_desc, $allowed_html ),
esc_url( wc_get_endpoint_url( 'orders' ) ),
esc_url( wc_get_endpoint_url( 'edit-address' ) ),
esc_url( wc_get_endpoint_url( 'edit-account' ) )

View File

@ -49,7 +49,7 @@ if ( $customer_orders ) : ?>
<tbody>
<?php
foreach ( $customer_orders as $customer_order ) :
$order = wc_get_order( $customer_order ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited
$order = wc_get_order( $customer_order ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$item_count = $order->get_item_count();
?>
<tr class="order">
@ -80,7 +80,7 @@ if ( $customer_orders ) : ?>
$actions = wc_get_account_orders_actions( $order );
if ( ! empty( $actions ) ) {
foreach ( $actions as $key => $action ) { // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited
foreach ( $actions as $key => $action ) { // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
echo '<a href="' . esc_url( $action['url'] ) . '" class="button ' . sanitize_html_class( $key ) . '">' . esc_html( $action['name'] ) . '</a>';
}
}

View File

@ -35,7 +35,7 @@ do_action( 'woocommerce_before_account_orders', $has_orders ); ?>
<tbody>
<?php
foreach ( $customer_orders->orders as $customer_order ) {
$order = wc_get_order( $customer_order ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited
$order = wc_get_order( $customer_order ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$item_count = $order->get_item_count() - $order->get_item_count_refunded();
?>
<tr class="woocommerce-orders-table__row woocommerce-orders-table__row--status-<?php echo esc_attr( $order->get_status() ); ?> order">
@ -66,7 +66,7 @@ do_action( 'woocommerce_before_account_orders', $has_orders ); ?>
$actions = wc_get_account_orders_actions( $order );
if ( ! empty( $actions ) ) {
foreach ( $actions as $key => $action ) { // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited
foreach ( $actions as $key => $action ) { // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
echo '<a href="' . esc_url( $action['url'] ) . '" class="woocommerce-button button ' . sanitize_html_class( $key ) . '">' . esc_html( $action['name'] ) . '</a>';
}
}

View File

@ -35,7 +35,7 @@ do_action( 'woocommerce_before_account_payment_methods', $has_methods ); ?>
<?php endforeach; ?>
</tr>
</thead>
<?php foreach ( $saved_methods as $type => $methods ) : // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited ?>
<?php foreach ( $saved_methods as $type => $methods ) : // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited ?>
<?php foreach ( $methods as $method ) : ?>
<tr class="payment-method<?php echo ! empty( $method['is_default'] ) ? ' default-payment-method' : ''; ?>">
<?php foreach ( wc_get_account_payment_methods_columns() as $column_id => $column_name ) : ?>
@ -53,7 +53,7 @@ do_action( 'woocommerce_before_account_payment_methods', $has_methods ); ?>
} elseif ( 'expires' === $column_id ) {
echo esc_html( $method['expires'] );
} elseif ( 'actions' === $column_id ) {
foreach ( $method['actions'] as $key => $action ) { // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited
foreach ( $method['actions'] as $key => $action ) { // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
echo '<a href="' . esc_url( $action['url'] ) . '" class="button ' . sanitize_html_class( $key ) . '">' . esc_html( $action['name'] ) . '</a>&nbsp;';
}
}

View File

@ -17,7 +17,7 @@
defined( 'ABSPATH' ) || exit;
$order = wc_get_order( $order_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited
$order = wc_get_order( $order_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
if ( ! $order ) {
return;

View File

@ -10,13 +10,16 @@
* happen. When this occurs the version of the template file will be bumped and
* the readme will list any important changes.
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 1.6.4
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce\Templates
* @version 4.4.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
the_title( '<h1 class="product_title entry-title">', '</h1>' );
?>
<h1 class="product_title entry-title">
<?php echo esc_html( get_the_title() ); ?>
</h1>

View File

@ -1098,7 +1098,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
WC()->cart->add_to_cart( $product2->get_id(), 1 );
WC()->cart->calculate_totals();
$expected_price = '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&euro;</span>68,50</span>';
$expected_price = '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&euro;</span>68,50</bdi></span>';
$this->assertEquals( $expected_price, WC()->cart->get_total() );
$this->assertEquals( '12.36', wc_round_tax_total( WC()->cart->get_total_tax( 'edit' ) ) );
@ -1107,7 +1107,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
WC()->cart->add_to_cart( $product4->get_id(), 1 );
WC()->cart->calculate_totals();
$expected_price = '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&euro;</span>112,00</span>';
$expected_price = '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&euro;</span>112,00</bdi></span>';
$this->assertEquals( $expected_price, WC()->cart->get_total() );
$this->assertEquals( '20.19', wc_round_tax_total( WC()->cart->get_total_tax( 'edit' ) ) );
@ -1118,7 +1118,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
WC()->cart->add_to_cart( $product6->get_id(), 1 );
WC()->cart->calculate_totals();
$expected_price = '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&euro;</span>239,00</span>';
$expected_price = '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&euro;</span>239,00</bdi></span>';
$this->assertEquals( $expected_price, WC()->cart->get_total() );
$this->assertEquals( '43.09', wc_round_tax_total( WC()->cart->get_total_tax( 'edit' ) ) );
}

View File

@ -593,32 +593,32 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
*/
public function test_wc_price() {
// Common prices.
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>1.00</span>', wc_price( 1 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>1.10</span>', wc_price( 1.1 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>1.17</span>', wc_price( 1.17 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>1,111.17</span>', wc_price( 1111.17 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>0.00</span>', wc_price( 0 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>1.00</bdi></span>', wc_price( 1 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>1.10</bdi></span>', wc_price( 1.1 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>1.17</bdi></span>', wc_price( 1.17 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>1,111.17</bdi></span>', wc_price( 1111.17 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>0.00</bdi></span>', wc_price( 0 ) );
// Different currency.
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&#36;</span>1,111.17</span>', wc_price( 1111.17, array( 'currency' => 'USD' ) ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>1,111.17</bdi></span>', wc_price( 1111.17, array( 'currency' => 'USD' ) ) );
// Negative price.
$this->assertEquals( '<span class="woocommerce-Price-amount amount">-<span class="woocommerce-Price-currencySymbol">&pound;</span>1.17</span>', wc_price( -1.17 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi>-<span class="woocommerce-Price-currencySymbol">&pound;</span>1.17</bdi></span>', wc_price( -1.17 ) );
// Bogus prices.
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>0.00</span>', wc_price( null ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>0.00</span>', wc_price( 'Q' ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>0.00</span>', wc_price( 'ಠ_ಠ' ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>0.00</bdi></span>', wc_price( null ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>0.00</bdi></span>', wc_price( 'Q' ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>0.00</bdi></span>', wc_price( 'ಠ_ಠ' ) );
// Trim zeros.
add_filter( 'woocommerce_price_trim_zeros', '__return_true' );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>1</span>', wc_price( 1.00 ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>1</bdi></span>', wc_price( 1.00 ) );
remove_filter( 'woocommerce_price_trim_zeros', '__return_true' );
// Ex tax label.
$calc_taxes = get_option( 'woocommerce_calc_taxes' );
update_option( 'woocommerce_calc_taxes', 'yes' );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>1,111.17</span> <small class="woocommerce-Price-taxLabel tax_label">(ex. VAT)</small>', wc_price( '1111.17', array( 'ex_tax_label' => true ) ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>1,111.17</bdi></span> <small class="woocommerce-Price-taxLabel tax_label">(ex. VAT)</small>', wc_price( '1111.17', array( 'ex_tax_label' => true ) ) );
update_option( 'woocommerce_calc_taxes', $calc_taxes );
}
@ -926,7 +926,7 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
* @since 3.3.0
*/
public function test_wc_format_sale_price() {
$this->assertEquals( '<del><span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>10.00</span></del> <ins><span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>5.00</span></ins>', wc_format_sale_price( '10', '5' ) );
$this->assertEquals( '<del><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>10.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>5.00</bdi></span></ins>', wc_format_sale_price( '10', '5' ) );
}
/**
@ -935,7 +935,7 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
* @since 3.3.0
*/
public function test_wc_format_price_range() {
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>10.00</span> &ndash; <span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>5.00</span>', wc_format_price_range( '10', '5' ) );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>10.00</bdi></span> &ndash; <span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>5.00</bdi></span>', wc_format_price_range( '10', '5' ) );
}
/**

View File

@ -941,7 +941,7 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
$object = new WC_Order();
$object->set_total( 100 );
$object->set_currency( 'USD' );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&#36;</span>100.00</span>', $object->get_formatted_order_total() );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&#36;</span>100.00</bdi></span>', $object->get_formatted_order_total() );
}
/**

View File

@ -1520,4 +1520,23 @@ class WC_Tests_Order_Functions extends WC_Unit_Test_Case {
remove_filter( 'woocommerce_hold_stock_for_checkout', '__return_false' );
}
/**
* Test wc_generate_order_key().
*
* @since 4.3.0
*/
public function test_wc_generate_order_key() {
// Test custom key.
$key = 'foo123bar';
$order_key = wc_generate_order_key( $key );
$expected = 'wc_' . apply_filters( 'woocommerce_generate_order_key', 'order_' . $key );
$this->assertEquals( $expected, $order_key );
// Test default key.
$order_key = wc_generate_order_key();
$prefix = 'wc_' . apply_filters( 'woocommerce_generate_order_key', 'order_' );
$this->assertStringStartsWith( $prefix, $order_key );
$this->assertEquals( 13, strlen( str_replace( $prefix, '', $order_key ) ) );
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* Unit tests for the base product class.
*
* @package WooCommerce\Tests\Product
*/
/**
* Tests for Product class.
* @package WooCommerce\Tests\Product
* @since 2.3
*/
class WC_Tests_Product extends WC_Unit_Test_Case {
/**
* @var WC_Product
*/
protected $product;
/**
* Runs before every test.
*/
public function setUp() {
parent::setUp();
$this->product = new WC_Product();
$this->product->save();
}
/**
* @testdox When a product is saved or deleted its parent should be scheduled for sync at the end of the request.
*
* @testWith ["save"]
* ["delete"]
*
* @param string $operation The method to test, "save" or "delete".
*/
public function test_deferred_sync_on_save_and_delete( $operation ) {
$defer_sync_invoked = false;
$defer_product_callback = function () use ( &$defer_sync_invoked ) {
$defer_sync_invoked = true;
};
$product = $this->getMockBuilder( WC_Product::class )
->setMethods( array( 'maybe_defer_product_sync' ) )
->getMock();
$product->method( 'maybe_defer_product_sync' )
->will( $this->returnCallback( $defer_product_callback ) );
$product->$operation();
$this->assertTrue( $defer_sync_invoked );
}
/**
* @testdox Test that stock status is set to the proper value when saving, if the product manages stock levels.
*
* @testWith [5, 4, true, "instock"]
* [5, 4, false, "instock"]
* [4, 4, true, "onbackorder"]
* [4, 4, false, "outofstock"]
* [3, 4, true, "onbackorder"]
* [3, 4, false, "outofstock"]
*
* @param int $stock_quantity Current stock quantity for the product.
* @param bool $notify_no_stock_amount Value for the woocommerce_notify_no_stock_amount option.
* @param bool $accepts_backorders Whether the product accepts backorders or not.
* @param string $expected_stock_status The expected stock status of the product after being saved.
*/
public function test_stock_status_on_save_when_managing_stock( $stock_quantity, $notify_no_stock_amount, $accepts_backorders, $expected_stock_status ) {
update_option( 'woocommerce_notify_no_stock_amount', $notify_no_stock_amount );
$this->product->set_backorders( $accepts_backorders ? 'yes' : 'no' );
$this->product->set_manage_stock( 'yes' );
$this->product->set_stock_status( '' );
$this->product->set_stock_quantity( $stock_quantity );
$this->product->save();
$this->assertEquals( $expected_stock_status, $this->product->get_stock_status() );
}
}

View File

@ -259,15 +259,15 @@ class WC_Tests_Product_Data extends WC_Unit_Test_Case {
$product = wc_get_product( $product1_id );
$this->assertEquals( $product1_id, $product->get_id() );
$this->assertEquals( '<del><span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>10.00</span></del> <ins><span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>7.00</span></ins>', $product->get_price_html() );
$this->assertEquals( '<del><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>10.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>7.00</bdi></span></ins>', $product->get_price_html() );
$product = wc_get_product( $product2_id );
$this->assertEquals( $product2_id, $product->get_id() );
$this->assertEquals( '<del><span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>20.00</span></del> <ins><span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>16.00</span></ins>', $product->get_price_html() );
$this->assertEquals( '<del><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>20.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>16.00</bdi></span></ins>', $product->get_price_html() );
$product = wc_get_product( $product3_id );
$this->assertEquals( $product3_id, $product->get_id() );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">&pound;</span>50.00</span>', $product->get_price_html() );
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">&pound;</span>50.00</bdi></span>', $product->get_price_html() );
}
/**

View File

@ -76,11 +76,11 @@ class WC_Tests_Product_Variable extends WC_Unit_Test_Case {
}
/**
* Test that variable products have the correct status when syncing with their children.
* Create a variable product with two variations.
*
* @since 3.3.0
* @return array An array containing first the main product, and then the two variation products.
*/
public function test_variable_product_stock_status_sync() {
private function get_variable_product_with_children() {
$product = new WC_Product_Variable();
$product->save();
@ -94,6 +94,17 @@ class WC_Tests_Product_Variable extends WC_Unit_Test_Case {
$product->set_children( array( $child1->get_id(), $child2->get_id() ) );
return array( $product, $child1, $child2 );
}
/**
* Test that variable products have the correct status when syncing with their children.
*
* @since 3.3.0
*/
public function test_variable_product_stock_status_sync() {
list($product, $child1, $child2) = $this->get_variable_product_with_children();
// Product should be in stock if a child is in stock.
$child1->set_stock_status( 'instock' );
$child1->save();
@ -131,4 +142,38 @@ class WC_Tests_Product_Variable extends WC_Unit_Test_Case {
WC_Product_Variable::sync( $product );
$this->assertEquals( 'onbackorder', $product->get_stock_status() );
}
/**
* @testdox Test that stock status is set to the proper value when saving, if the product manages stock levels.
*
* @testWith [5, 4, true, "instock"]
* [5, 4, false, "instock"]
* [4, 4, true, "onbackorder"]
* [4, 4, false, "outofstock"]
* [3, 4, true, "onbackorder"]
* [3, 4, false, "outofstock"]
*
* @param int $stock_quantity Current stock quantity for the product.
* @param bool $notify_no_stock_amount Value for the woocommerce_notify_no_stock_amount option.
* @param bool $accepts_backorders Whether the product accepts backorders or not.
* @param string $expected_stock_status The expected stock status of the product after being saved.
*/
public function test_stock_status_on_save_when_managing_stock( $stock_quantity, $notify_no_stock_amount, $accepts_backorders, $expected_stock_status ) {
list($product, $child1, $child2) = $this->get_variable_product_with_children();
update_option( 'woocommerce_notify_no_stock_amount', $notify_no_stock_amount );
$child1->set_stock_status( '' );
$child1->save();
$child2->set_stock_status( '' );
$child2->save();
$product->set_manage_stock( 'yes' );
$product->set_stock_status( '' );
$product->set_backorders( $accepts_backorders ? 'yes' : 'no' );
$product->set_stock_quantity( $stock_quantity );
$product->save();
$this->assertEquals( $expected_stock_status, $product->get_stock_status() );
}
}

View File

@ -238,6 +238,29 @@ class WC_Tests_Webhook_Functions extends WC_Unit_Test_Case {
remove_action( 'woocommerce_webhook_process_delivery', array( $this, 'woocommerce_webhook_process_delivery' ), 1, 2 );
}
/**
* Verify that a webhook is queued when intended to be delivered synchronously. This allows us to then execute them
* all in a `register_shutdown_function` after the request has processed. Since async jobs are handled in
* this way, we can be more confident that it is consistent.
*/
public function test_woocommerce_webhook_synchronous_is_queued() {
add_filter( 'woocommerce_webhook_deliver_async', '__return_false' );
$webhook = wc_get_webhook( $this->create_webhook( 'customer.created' )->get_id() );
wc_load_webhooks( 'active' );
add_action( 'woocommerce_webhook_process_delivery', array( $this, 'woocommerce_webhook_process_delivery' ), 1, 2 );
$customer = WC_Helper_Customer::create_customer( 'test1', 'pw1', 'user1@example.com' );
global $wc_queued_sync_webhooks;
$this->assertCount( 1, $wc_queued_sync_webhooks );
$this->assertEquals( $webhook->get_id(), $wc_queued_sync_webhooks[0]['webhook']->get_id() );
$this->assertEquals( $customer->get_id(), $wc_queued_sync_webhooks[0]['arg'] );
$wc_queued_sync_webhooks = null;
remove_filter( 'woocommerce_webhook_deliver_async', '__return_false' );
$webhook->delete( true );
$customer->delete( true );
}
/**
* Helper function to keep track of which webhook (and corresponding arg) has been delivered
* within the current request.