plugin updates

This commit is contained in:
Tony Volpe
2024-09-17 10:43:54 -04:00
parent 44b413346f
commit b7c8882c8c
1359 changed files with 58219 additions and 11364 deletions

View File

@@ -149,7 +149,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
wp_register_script( 'wc-shipping-zone-methods', WC()->plugin_url() . '/assets/js/admin/wc-shipping-zone-methods' . $suffix . '.js', array( 'jquery', 'wp-util', 'underscore', 'backbone', 'jquery-ui-sortable', 'wc-backbone-modal' ), $version );
wp_register_script( 'wc-shipping-classes', WC()->plugin_url() . '/assets/js/admin/wc-shipping-classes' . $suffix . '.js', array( 'jquery', 'wp-util', 'underscore', 'backbone', 'wc-backbone-modal' ), $version, array( 'in_footer' => false ) );
wp_register_script( 'wc-clipboard', WC()->plugin_url() . '/assets/js/admin/wc-clipboard' . $suffix . '.js', array( 'jquery' ), $version );
wp_register_script( 'select2', WC()->plugin_url() . '/assets/js/select2/select2.full' . $suffix . '.js', array( 'jquery' ), '4.0.3' );
wp_register_script( 'select2', WC()->plugin_url() . '/assets/js/select2/select2.full' . $suffix . '.js', array( 'jquery' ), '4.0.3', array( 'in_footer' => false ) );
wp_register_script( 'selectWoo', WC()->plugin_url() . '/assets/js/selectWoo/selectWoo.full' . $suffix . '.js', array( 'jquery' ), '1.0.6' );
wp_register_script( 'wc-enhanced-select', WC()->plugin_url() . '/assets/js/admin/wc-enhanced-select' . $suffix . '.js', array( 'jquery', 'selectWoo' ), $version );
wp_register_script( 'js-cookie', WC()->plugin_url() . '/assets/js/js-cookie/js.cookie' . $suffix . '.js', array(), '2.1.4', true );

View File

@@ -1,4 +1,4 @@
<?php
<?php //phpcs:ignore Generic.PHP.RequireStrictTypes.MissingDeclaration
/**
* Init WooCommerce data importers.
*
@@ -34,6 +34,7 @@ class WC_Admin_Importers {
add_action( 'admin_head', array( $this, 'hide_from_menus' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
add_action( 'wp_ajax_woocommerce_do_ajax_product_import', array( $this, 'do_ajax_product_import' ) );
add_action( 'in_admin_footer', array( $this, 'track_importer_exporter_view' ) );
// Register WooCommerce importers.
$this->importers['product_importer'] = array(
@@ -96,10 +97,15 @@ class WC_Admin_Importers {
*/
public function product_importer() {
if ( Constants::is_defined( 'WP_LOAD_IMPORTERS' ) ) {
wp_safe_redirect( admin_url( 'edit.php?post_type=product&page=product_importer' ) );
wp_safe_redirect( admin_url( 'edit.php?post_type=product&page=product_importer&source=wordpress-importer' ) );
exit;
}
// phpcs:ignore
if ( isset( $_GET['source'] ) && 'wordpress-importer' === sanitize_text_field( wp_unslash( $_GET['source'] ) ) ) {
wc_admin_record_tracks_event( 'product_importer_view_from_wp_importer' );
}
include_once WC_ABSPATH . 'includes/import/class-wc-product-csv-importer.php';
include_once WC_ABSPATH . 'includes/admin/importers/class-wc-product-csv-importer-controller.php';
@@ -132,7 +138,9 @@ class WC_Admin_Importers {
}
}
require dirname( __FILE__ ) . '/importers/class-wc-tax-rate-importer.php';
wc_admin_record_tracks_event( 'tax_rates_importer_view_from_wp_importer' );
require __DIR__ . '/importers/class-wc-tax-rate-importer.php';
$importer = new WC_Tax_Rate_Importer();
$importer->dispatch();
@@ -182,7 +190,9 @@ class WC_Admin_Importers {
// Register the taxonomy now so that the import works!
register_taxonomy(
$term['domain'],
// phpcs:ignore
apply_filters( 'woocommerce_taxonomy_objects_' . $term['domain'], array( 'product' ) ),
// phpcs:ignore
apply_filters(
'woocommerce_taxonomy_args_' . $term['domain'],
array(
@@ -205,113 +215,34 @@ class WC_Admin_Importers {
* Ajax callback for importing one batch of products from a CSV.
*/
public function do_ajax_product_import() {
global $wpdb;
check_ajax_referer( 'wc-product-import', 'security' );
if ( ! $this->import_allowed() || ! isset( $_POST['file'] ) ) { // PHPCS: input var ok.
if ( ! $this->import_allowed() ) {
wp_send_json_error( array( 'message' => __( 'Insufficient privileges to import products.', 'woocommerce' ) ) );
}
include_once WC_ABSPATH . 'includes/admin/importers/class-wc-product-csv-importer-controller.php';
include_once WC_ABSPATH . 'includes/import/class-wc-product-csv-importer.php';
WC_Product_CSV_Importer_Controller::dispatch_ajax();
}
$file = wc_clean( wp_unslash( $_POST['file'] ) ); // PHPCS: input var ok.
$params = array(
'delimiter' => ! empty( $_POST['delimiter'] ) ? wc_clean( wp_unslash( $_POST['delimiter'] ) ) : ',', // PHPCS: input var ok.
'start_pos' => isset( $_POST['position'] ) ? absint( $_POST['position'] ) : 0, // PHPCS: input var ok.
'mapping' => isset( $_POST['mapping'] ) ? (array) wc_clean( wp_unslash( $_POST['mapping'] ) ) : array(), // PHPCS: input var ok.
'update_existing' => isset( $_POST['update_existing'] ) ? (bool) $_POST['update_existing'] : false, // PHPCS: input var ok.
'character_encoding' => isset( $_POST['character_encoding'] ) ? wc_clean( wp_unslash( $_POST['character_encoding'] ) ) : '',
/**
* Track importer/exporter view.
*
* @return void
*/
public function track_importer_exporter_view() {
$screen = get_current_screen();
/**
* Batch size for the product import process.
*
* @param int $size Batch size.
*
* @since
*/
'lines' => apply_filters( 'woocommerce_product_import_batch_size', 30 ),
'parse' => true,
);
// Log failures.
if ( 0 !== $params['start_pos'] ) {
$error_log = array_filter( (array) get_user_option( 'product_import_error_log' ) );
} else {
$error_log = array();
if ( ! isset( $screen->id ) ) {
return;
}
$importer = WC_Product_CSV_Importer_Controller::get_importer( $file, $params );
$results = $importer->import();
$percent_complete = $importer->get_percent_complete();
$error_log = array_merge( $error_log, $results['failed'], $results['skipped'] );
// Don't track if we're in a specific import screen.
// phpcs:ignore
if ( isset( $_GET['import'] ) ) {
return;
}
update_user_option( get_current_user_id(), 'product_import_error_log', $error_log );
if ( 100 === $percent_complete ) {
// @codingStandardsIgnoreStart.
$wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_original_id' ) );
$wpdb->delete( $wpdb->posts, array(
'post_type' => 'product',
'post_status' => 'importing',
) );
$wpdb->delete( $wpdb->posts, array(
'post_type' => 'product_variation',
'post_status' => 'importing',
) );
// @codingStandardsIgnoreEnd.
// Clean up orphaned data.
$wpdb->query(
"
DELETE {$wpdb->posts}.* FROM {$wpdb->posts}
LEFT JOIN {$wpdb->posts} wp ON wp.ID = {$wpdb->posts}.post_parent
WHERE wp.ID IS NULL AND {$wpdb->posts}.post_type = 'product_variation'
"
);
$wpdb->query(
"
DELETE {$wpdb->postmeta}.* FROM {$wpdb->postmeta}
LEFT JOIN {$wpdb->posts} wp ON wp.ID = {$wpdb->postmeta}.post_id
WHERE wp.ID IS NULL
"
);
// @codingStandardsIgnoreStart.
$wpdb->query( "
DELETE tr.* FROM {$wpdb->term_relationships} tr
LEFT JOIN {$wpdb->posts} wp ON wp.ID = tr.object_id
LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
WHERE wp.ID IS NULL
AND tt.taxonomy IN ( '" . implode( "','", array_map( 'esc_sql', get_object_taxonomies( 'product' ) ) ) . "' )
" );
// @codingStandardsIgnoreEnd.
// Send success.
wp_send_json_success(
array(
'position' => 'done',
'percentage' => 100,
'url' => add_query_arg( array( '_wpnonce' => wp_create_nonce( 'woocommerce-csv-importer' ) ), admin_url( 'edit.php?post_type=product&page=product_importer&step=done' ) ),
'imported' => is_countable( $results['imported'] ) ? count( $results['imported'] ) : 0,
'imported_variations' => is_countable( $results['imported_variations'] ) ? count( $results['imported_variations'] ) : 0,
'failed' => is_countable( $results['failed'] ) ? count( $results['failed'] ) : 0,
'updated' => is_countable( $results['updated'] ) ? count( $results['updated'] ) : 0,
'skipped' => is_countable( $results['skipped'] ) ? count( $results['skipped'] ) : 0,
)
);
} else {
wp_send_json_success(
array(
'position' => $importer->get_file_position(),
'percentage' => $percent_complete,
'imported' => is_countable( $results['imported'] ) ? count( $results['imported'] ) : 0,
'imported_variations' => is_countable( $results['imported_variations'] ) ? count( $results['imported_variations'] ) : 0,
'failed' => is_countable( $results['failed'] ) ? count( $results['failed'] ) : 0,
'updated' => is_countable( $results['updated'] ) ? count( $results['updated'] ) : 0,
'skipped' => is_countable( $results['skipped'] ) ? count( $results['skipped'] ) : 0,
)
);
if ( 'import' === $screen->id || 'export' === $screen->id ) {
wc_admin_record_tracks_event( 'wordpress_' . $screen->id . '_view' );
}
}
}

View File

@@ -219,8 +219,8 @@ class WC_Admin_Marketplace_Promotions {
/**
* From the array of promotions, select those of a given format.
*
* @param ? array $promotions Array of data about promotions of all formats.
* @param ? string $format Format we want to filter for.
* @param ?array $promotions Array of data about promotions of all formats.
* @param ?string $format Format we want to filter for.
*
* @return array
*/

View File

@@ -176,7 +176,7 @@ if ( ! class_exists( 'WC_Admin_Profile', false ) ) :
</th>
<td>
<?php if ( ! empty( $field['type'] ) && 'select' === $field['type'] ) : ?>
<select name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" class="<?php echo esc_attr( $field['class'] ); ?>" style="width: 25em;">
<select name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" class="<?php echo isset( $field['class'] ) ? esc_attr( $field['class'] ) : ''; ?>" style="width: 25em;">
<?php
$selected = esc_attr( get_user_meta( $user->ID, $key, true ) );
foreach ( $field['options'] as $option_key => $option_value ) :

View File

@@ -53,7 +53,11 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
$settings[] = include __DIR__ . '/settings/class-wc-settings-products.php';
$settings[] = include __DIR__ . '/settings/class-wc-settings-tax.php';
$settings[] = include __DIR__ . '/settings/class-wc-settings-shipping.php';
$settings[] = include __DIR__ . '/settings/class-wc-settings-payment-gateways.php';
if ( \Automattic\WooCommerce\Admin\Features\Features::is_enabled( 'reactify-classic-payments-settings' ) ) {
$settings[] = include __DIR__ . '/settings/class-wc-settings-payment-gateways-react.php';
} else {
$settings[] = include __DIR__ . '/settings/class-wc-settings-payment-gateways.php';
}
$settings[] = include __DIR__ . '/settings/class-wc-settings-accounts.php';
$settings[] = include __DIR__ . '/settings/class-wc-settings-emails.php';
$settings[] = include __DIR__ . '/settings/class-wc-settings-integrations.php';

View File

@@ -27,7 +27,19 @@ class WC_Helper_Admin {
* @return void
*/
public static function load() {
add_filter( 'woocommerce_admin_shared_settings', array( __CLASS__, 'add_marketplace_settings' ) );
global $pagenow;
if ( is_admin() ) {
$is_in_app_marketplace = ( 'admin.php' === $pagenow
&& isset( $_GET['page'] ) && 'wc-admin' === $_GET['page'] //phpcs:ignore WordPress.Security.NonceVerification.Recommended
&& isset( $_GET['path'] ) && '/extensions' === $_GET['path'] //phpcs:ignore WordPress.Security.NonceVerification.Recommended
);
if ( $is_in_app_marketplace ) {
add_filter( 'woocommerce_admin_shared_settings', array( __CLASS__, 'add_marketplace_settings' ) );
}
}
add_filter( 'rest_api_init', array( __CLASS__, 'register_rest_routes' ) );
}

View File

@@ -139,6 +139,8 @@ class WC_Helper_Subscriptions_API {
*/
public static function refresh() {
WC_Helper::refresh_helper_subscriptions();
WC_Helper::get_subscriptions();
WC_Helper::get_product_usage_notice_rules();
self::get_subscriptions();
}

View File

@@ -259,8 +259,13 @@ class WC_Helper_Updater {
// Extract product ID from the response.
$product_id = preg_replace( '/[^0-9]/', '', $response->id );
$installed_or_unconnected = array_merge(
WC_Helper::get_installed_subscriptions(),
WC_Helper::get_unconnected_subscriptions()
);
// Product subscriptions.
$subscriptions = wp_list_filter( WC_Helper::get_installed_subscriptions(), array( 'product_id' => $product_id ) );
$subscriptions = wp_list_filter( $installed_or_unconnected, array( 'product_id' => $product_id ) );
if ( empty( $subscriptions ) ) {
return;
}

View File

@@ -1007,6 +1007,7 @@ class WC_Helper {
self::_flush_authentication_cache();
self::_flush_subscriptions_cache();
self::_flush_updates_cache();
self::flush_product_usage_notice_rules_cache();
}
/**
@@ -1334,6 +1335,34 @@ class WC_Helper {
return $installed_subscriptions;
}
/**
* Get the user's unconnected subscriptions.
*
* @return array
*/
public static function get_unconnected_subscriptions() {
static $unconnected_subscriptions = null;
// Cache unconnected_subscriptions in the current request.
if ( is_null( $unconnected_subscriptions ) ) {
$auth = WC_Helper_Options::get( 'auth' );
$site_id = isset( $auth['site_id'] ) ? absint( $auth['site_id'] ) : 0;
if ( 0 === $site_id ) {
$unconnected_subscriptions = array();
return $unconnected_subscriptions;
}
$unconnected_subscriptions = array_filter(
self::get_subscriptions(),
function ( $subscription ) use ( $site_id ) {
return empty( $subscription['connections'] );
}
);
}
return $unconnected_subscriptions;
}
/**
* Get subscription state of a given product ID.
*
@@ -1561,6 +1590,7 @@ class WC_Helper {
'product-usage-notice-rules',
array(
'authenticated' => false,
'timeout' => 2,
)
);
@@ -1835,8 +1865,22 @@ class WC_Helper {
return false;
}
// If there are multiple subscriptions, but no active subscriptions, then mark the first one as installed.
$product_subscription = array_shift( $product_subscriptions );
// Find subscriptions that can be activated.
$product_subscriptions_without_maxed_connections = wp_list_filter(
$product_subscriptions,
array(
'maxed' => false,
)
);
if ( 0 < count( $product_subscriptions_without_maxed_connections ) ) {
// Pick the first subscription available for activation.
$product_subscription = array_shift( $product_subscriptions_without_maxed_connections );
} else {
// If there are multiple subscriptions, but no active subscriptions, then mark the first one as installed.
$product_subscription = array_shift( $product_subscriptions );
}
if ( $product_subscription['product_key'] === $subscription['product_key'] ) {
return true;
}
@@ -2172,6 +2216,13 @@ class WC_Helper {
delete_transient( '_woocommerce_helper_subscriptions' );
}
/**
* Flush product-usage-notice-rules cache.
*/
public static function flush_product_usage_notice_rules_cache() {
delete_transient( '_woocommerce_helper_product_usage_notice_rules' );
}
/**
* Flush auth cache.
*/
@@ -2271,6 +2322,7 @@ class WC_Helper {
self::_flush_subscriptions_cache();
self::_flush_updates_cache();
self::flush_product_usage_notice_rules_cache();
}
/**
@@ -2360,6 +2412,7 @@ class WC_Helper {
self::_flush_subscriptions_cache();
self::_flush_updates_cache();
self::flush_product_usage_notice_rules_cache();
}
/**

View File

@@ -103,6 +103,56 @@ class WC_Product_CSV_Importer_Controller {
return wc_is_file_valid_csv( $file, $check_path );
}
/**
* Runs before controller actions to check that the file used during the import is valid.
*
* @since 9.3.0
*
* @param string $path Path to test.
*
* @throws \Exception When file validation fails.
*/
protected static function check_file_path( string $path ): void {
$is_valid_file = false;
if ( ! empty( $path ) ) {
$path = realpath( $path );
$is_valid_file = false !== $path;
}
// File must be readable.
$is_valid_file = $is_valid_file && is_readable( $path );
// Check that file is within an allowed location.
if ( $is_valid_file ) {
$in_valid_location = false;
$valid_locations = array();
$valid_locations[] = ABSPATH;
$upload_dir = wp_get_upload_dir();
if ( false === $upload_dir['error'] ) {
$valid_locations[] = $upload_dir['basedir'];
}
foreach ( $valid_locations as $valid_location ) {
if ( 0 === stripos( $path, trailingslashit( realpath( $valid_location ) ) ) ) {
$in_valid_location = true;
break;
}
}
$is_valid_file = $in_valid_location;
}
if ( ! $is_valid_file ) {
throw new \Exception( esc_html__( 'File path provided for import is invalid.', 'woocommerce' ) );
}
if ( ! self::is_file_valid_csv( $path ) ) {
throw new \Exception( esc_html__( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) );
}
}
/**
* Get all the valid filetypes for a CSV file.
*
@@ -263,17 +313,151 @@ class WC_Product_CSV_Importer_Controller {
* Dispatch current step and show correct view.
*/
public function dispatch() {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( ! empty( $_POST['save_step'] ) && ! empty( $this->steps[ $this->step ]['handler'] ) ) {
call_user_func( $this->steps[ $this->step ]['handler'], $this );
$output = '';
try {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( ! empty( $_POST['save_step'] ) && ! empty( $this->steps[ $this->step ]['handler'] ) ) {
if ( is_callable( $this->steps[ $this->step ]['handler'] ) ) {
call_user_func( $this->steps[ $this->step ]['handler'], $this );
}
}
ob_start();
if ( is_callable( $this->steps[ $this->step ]['view'] ) ) {
call_user_func( $this->steps[ $this->step ]['view'], $this );
}
$output = ob_get_clean();
} catch ( \Exception $e ) {
$this->add_error( $e->getMessage() );
}
$this->output_header();
$this->output_steps();
$this->output_errors();
call_user_func( $this->steps[ $this->step ]['view'], $this );
echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output is HTML we've generated ourselves.
$this->output_footer();
}
/**
* Processes AJAX requests related to a product CSV import.
*
* @since 9.3.0
*/
public static function dispatch_ajax() {
global $wpdb;
check_ajax_referer( 'wc-product-import', 'security' );
try {
$file = wc_clean( wp_unslash( $_POST['file'] ?? '' ) ); // PHPCS: input var ok.
self::check_file_path( $file );
$params = array(
'delimiter' => ! empty( $_POST['delimiter'] ) ? wc_clean( wp_unslash( $_POST['delimiter'] ) ) : ',', // PHPCS: input var ok.
'start_pos' => isset( $_POST['position'] ) ? absint( $_POST['position'] ) : 0, // PHPCS: input var ok.
'mapping' => isset( $_POST['mapping'] ) ? (array) wc_clean( wp_unslash( $_POST['mapping'] ) ) : array(), // PHPCS: input var ok.
'update_existing' => isset( $_POST['update_existing'] ) ? (bool) $_POST['update_existing'] : false, // PHPCS: input var ok.
'character_encoding' => isset( $_POST['character_encoding'] ) ? wc_clean( wp_unslash( $_POST['character_encoding'] ) ) : '',
/**
* Batch size for the product import process.
*
* @param int $size Batch size.
*
* @since 3.1.0
*/
'lines' => apply_filters( 'woocommerce_product_import_batch_size', 1 ),
'parse' => true,
);
// Log failures.
if ( 0 !== $params['start_pos'] ) {
$error_log = array_filter( (array) get_user_option( 'product_import_error_log' ) );
} else {
$error_log = array();
}
include_once WC_ABSPATH . 'includes/import/class-wc-product-csv-importer.php';
$importer = self::get_importer( $file, $params );
$results = $importer->import();
$percent_complete = $importer->get_percent_complete();
$error_log = array_merge( $error_log, $results['failed'], $results['skipped'] );
update_user_option( get_current_user_id(), 'product_import_error_log', $error_log );
if ( 100 === $percent_complete ) {
// @codingStandardsIgnoreStart.
$wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_original_id' ) );
$wpdb->delete( $wpdb->posts, array(
'post_type' => 'product',
'post_status' => 'importing',
) );
$wpdb->delete( $wpdb->posts, array(
'post_type' => 'product_variation',
'post_status' => 'importing',
) );
// @codingStandardsIgnoreEnd.
// Clean up orphaned data.
$wpdb->query(
"
DELETE {$wpdb->posts}.* FROM {$wpdb->posts}
LEFT JOIN {$wpdb->posts} wp ON wp.ID = {$wpdb->posts}.post_parent
WHERE wp.ID IS NULL AND {$wpdb->posts}.post_type = 'product_variation'
"
);
$wpdb->query(
"
DELETE {$wpdb->postmeta}.* FROM {$wpdb->postmeta}
LEFT JOIN {$wpdb->posts} wp ON wp.ID = {$wpdb->postmeta}.post_id
WHERE wp.ID IS NULL
"
);
// @codingStandardsIgnoreStart.
$wpdb->query( "
DELETE tr.* FROM {$wpdb->term_relationships} tr
LEFT JOIN {$wpdb->posts} wp ON wp.ID = tr.object_id
LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
WHERE wp.ID IS NULL
AND tt.taxonomy IN ( '" . implode( "','", array_map( 'esc_sql', get_object_taxonomies( 'product' ) ) ) . "' )
" );
// @codingStandardsIgnoreEnd.
// Send success.
wp_send_json_success(
array(
'position' => 'done',
'percentage' => 100,
'url' => add_query_arg( array( '_wpnonce' => wp_create_nonce( 'woocommerce-csv-importer' ) ), admin_url( 'edit.php?post_type=product&page=product_importer&step=done' ) ),
'imported' => is_countable( $results['imported'] ) ? count( $results['imported'] ) : 0,
'imported_variations' => is_countable( $results['imported_variations'] ) ? count( $results['imported_variations'] ) : 0,
'failed' => is_countable( $results['failed'] ) ? count( $results['failed'] ) : 0,
'updated' => is_countable( $results['updated'] ) ? count( $results['updated'] ) : 0,
'skipped' => is_countable( $results['skipped'] ) ? count( $results['skipped'] ) : 0,
)
);
} else {
wp_send_json_success(
array(
'position' => $importer->get_file_position(),
'percentage' => $percent_complete,
'imported' => is_countable( $results['imported'] ) ? count( $results['imported'] ) : 0,
'imported_variations' => is_countable( $results['imported_variations'] ) ? count( $results['imported_variations'] ) : 0,
'failed' => is_countable( $results['failed'] ) ? count( $results['failed'] ) : 0,
'updated' => is_countable( $results['updated'] ) ? count( $results['updated'] ) : 0,
'skipped' => is_countable( $results['skipped'] ) ? count( $results['skipped'] ) : 0,
)
);
}
} catch ( \Exception $e ) {
wp_send_json_error( array( 'message' => $e->getMessage() ) );
}
}
/**
* Output information about the uploading process.
*/
@@ -314,60 +498,20 @@ class WC_Product_CSV_Importer_Controller {
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce already verified in WC_Product_CSV_Importer_Controller::upload_form_handler()
$file_url = isset( $_POST['file_url'] ) ? wc_clean( wp_unslash( $_POST['file_url'] ) ) : '';
if ( empty( $file_url ) ) {
if ( ! isset( $_FILES['import'] ) ) {
return new WP_Error( 'woocommerce_product_csv_importer_upload_file_empty', __( 'File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini or by post_max_size being defined as smaller than upload_max_filesize in php.ini.', 'woocommerce' ) );
try {
if ( ! empty( $file_url ) ) {
$path = ABSPATH . $file_url;
self::check_file_path( $path );
} else {
$csv_import_util = wc_get_container()->get( Automattic\WooCommerce\Internal\Admin\ImportExport\CSVUploadHelper::class );
$upload = $csv_import_util->handle_csv_upload( 'product', 'import', self::get_valid_csv_filetypes() );
$path = $upload['file'];
}
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
if ( ! self::is_file_valid_csv( wc_clean( wp_unslash( $_FILES['import']['name'] ) ), false ) ) {
return new WP_Error( 'woocommerce_product_csv_importer_upload_file_invalid', __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) );
}
$overrides = array(
'test_form' => false,
'mimes' => self::get_valid_csv_filetypes(),
);
$import = $_FILES['import']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
$upload = wp_handle_upload( $import, $overrides );
if ( isset( $upload['error'] ) ) {
return new WP_Error( 'woocommerce_product_csv_importer_upload_error', $upload['error'] );
}
// Construct the object array.
$object = array(
'post_title' => basename( $upload['file'] ),
'post_content' => $upload['url'],
'post_mime_type' => $upload['type'],
'guid' => $upload['url'],
'context' => 'import',
'post_status' => 'private',
);
// Save the data.
$id = wp_insert_attachment( $object, $upload['file'] );
/*
* Schedule a cleanup for one day from now in case of failed
* import or missing wp_import_cleanup() call.
*/
wp_schedule_single_event( time() + DAY_IN_SECONDS, 'importer_scheduled_cleanup', array( $id ) );
return $upload['file'];
} elseif (
( 0 === stripos( realpath( ABSPATH . $file_url ), ABSPATH ) ) &&
file_exists( ABSPATH . $file_url )
) {
if ( ! self::is_file_valid_csv( ABSPATH . $file_url ) ) {
return new WP_Error( 'woocommerce_product_csv_importer_upload_file_invalid', __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) );
}
return ABSPATH . $file_url;
return $path;
} catch ( \Exception $e ) {
return new \WP_Error( 'woocommerce_product_csv_importer_upload_invalid_file', $e->getMessage() );
}
// phpcs:enable
return new WP_Error( 'woocommerce_product_csv_importer_upload_invalid_file', __( 'Please upload or provide the link to a valid CSV file.', 'woocommerce' ) );
}
/**
@@ -375,6 +519,8 @@ class WC_Product_CSV_Importer_Controller {
*/
protected function mapping_form() {
check_admin_referer( 'woocommerce-csv-importer' );
self::check_file_path( $this->file );
$args = array(
'lines' => 1,
'delimiter' => $this->delimiter,
@@ -412,18 +558,7 @@ class WC_Product_CSV_Importer_Controller {
// Displaying this page triggers Ajax action to run the import with a valid nonce,
// therefore this page needs to be nonce protected as well.
check_admin_referer( 'woocommerce-csv-importer' );
if ( ! self::is_file_valid_csv( $this->file ) ) {
$this->add_error( __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) );
$this->output_errors();
return;
}
if ( ! is_file( $this->file ) ) {
$this->add_error( __( 'The file does not exist, please try again.', 'woocommerce' ) );
$this->output_errors();
return;
}
self::check_file_path( $this->file );
if ( ! empty( $_POST['map_from'] ) && ! empty( $_POST['map_to'] ) ) {
$mapping_from = wc_clean( wp_unslash( $_POST['map_from'] ) );

View File

@@ -32,7 +32,8 @@ if ( ! defined( 'ABSPATH' ) ) {
array(
'id' => '_global_unique_id',
'value' => $product_object->get_global_unique_id( 'edit' ),
'label' => __( 'GTIN, UPC, EAN or ISBN', 'woocommerce' ),
// translators: %1$s GTIN %2$s UPC %3$s EAN %4$s ISBN.
'label' => sprintf( __( '%1$s, %2$s, %3$s, or %4$s', 'woocommerce' ), '<abbr title="' . esc_attr__( 'Global Trade Item Number', 'woocommerce' ) . '">' . esc_html__( 'GTIN', 'woocommerce' ) . '</abbr>', '<abbr title="' . esc_attr__( 'Universal Product Code', 'woocommerce' ) . '">' . esc_html__( 'UPC', 'woocommerce' ) . '</abbr>', '<abbr title="' . esc_attr__( 'European Article Number', 'woocommerce' ) . '">' . esc_html__( 'EAN', 'woocommerce' ) . '</abbr>', '<abbr title="' . esc_attr__( 'International Standard Book Number', 'woocommerce' ) . '">' . esc_html__( 'ISBN', 'woocommerce' ) . '</abbr>' ),
'desc_tip' => true,
'description' => __( 'Enter a barcode or any other identifier unique to this product. It can help you list this product on other channels or marketplaces.', 'woocommerce' ),
)

View File

@@ -32,9 +32,9 @@ if ( ! defined( 'ABSPATH' ) ) {
$selected_value = 'yes' === ( isset( $option['default'] ) ? $option['default'] : 'no' );
}
?>
<label for="<?php echo esc_attr( $option['id'] ); ?>" class="<?php echo esc_attr( $option['wrapper_class'] ); ?> tips" data-tip="<?php echo esc_attr( $option['description'] ); ?>">
<?php echo esc_html( $option['label'] ); ?>:
<label for="<?php echo esc_attr( $option['id'] ); ?>" class="<?php echo esc_attr( $option['wrapper_class'] ); ?> tips has-checkbox" data-tip="<?php echo esc_attr( $option['description'] ); ?>">
<input type="checkbox" name="<?php echo esc_attr( $option['id'] ); ?>" id="<?php echo esc_attr( $option['id'] ); ?>" data-product-type-option-id="<?php echo esc_attr( $option['id'] ); ?>" <?php echo checked( $selected_value, true, false ); ?> />
<?php echo esc_html( $option['label'] ); ?>
</label>
<?php endforeach; ?>
</span>

View File

@@ -98,7 +98,8 @@ defined( 'ABSPATH' ) || exit;
'name' => "variable_global_unique_id[{$loop}]",
'value' => $variation_object->get_global_unique_id( 'edit' ),
'placeholder' => $variation_object->get_global_unique_id(),
'label' => __( 'GTIN, UPC, EAN or ISBN', 'woocommerce' ),
// translators: %1$s GTIN %2$s UPC %3$s EAN %4$s ISBN.
'label' => sprintf( __( '%1$s, %2$s, %3$s, or %4$s', 'woocommerce' ), '<abbr title="' . esc_attr__( 'Global Trade Item Number', 'woocommerce' ) . '">' . esc_html__( 'GTIN', 'woocommerce' ) . '</abbr>', '<abbr title="' . esc_attr__( 'Universal Product Code', 'woocommerce' ) . '">' . esc_html__( 'UPC', 'woocommerce' ) . '</abbr>', '<abbr title="' . esc_attr__( 'European Article Number', 'woocommerce' ) . '">' . esc_html__( 'EAN', 'woocommerce' ) . '</abbr>', '<abbr title="' . esc_attr__( 'International Standard Book Number', 'woocommerce' ) . '">' . esc_html__( 'ISBN', 'woocommerce' ) . '</abbr>' ),
'desc_tip' => true,
'description' => __( 'Enter a barcode or any other identifier unique to this product. It can help you list this product on other channels or marketplaces.', 'woocommerce' ),
'wrapper_class' => 'form-row',

View File

@@ -38,13 +38,19 @@ class WC_Settings_Advanced extends WC_Settings_Page {
* @return array
*/
protected function get_own_sections() {
return array(
$sections = array(
'' => __( 'Page setup', 'woocommerce' ),
'keys' => __( 'REST API', 'woocommerce' ),
'webhooks' => __( 'Webhooks', 'woocommerce' ),
'legacy_api' => __( 'Legacy API', 'woocommerce' ),
'woocommerce_com' => __( 'WooCommerce.com', 'woocommerce' ),
);
if ( Features::is_enabled( 'blueprint' ) ) {
$sections['blueprint'] = __( 'Blueprint', 'woocommerce' );
}
return $sections;
}
/**
@@ -426,6 +432,27 @@ class WC_Settings_Advanced extends WC_Settings_Page {
return apply_filters( 'woocommerce_settings_rest_api', $settings );
}
/**
* Get settings for the Blueprint section.
*
* @return array
*/
protected function get_settings_for_blueprint_section() {
$settings =
array(
array(
'title' => esc_html__( 'Blueprint', 'woocommerce' ),
'type' => 'title',
),
array(
'id' => 'wc_settings_blueprint_slotfill',
'type' => 'slotfill_placeholder',
),
);
return $settings;
}
/**
* Form method.
*

View File

@@ -130,19 +130,6 @@ class WC_Settings_Emails extends WC_Settings_Page {
'desc_tip' => true,
),
array(
'title' => __( 'Footer text', 'woocommerce' ),
/* translators: %s: Available placeholders for use */
'desc' => __( 'The text to appear in the footer of all WooCommerce emails.', 'woocommerce' ) . ' ' . sprintf( __( 'Available placeholders: %s', 'woocommerce' ), '{site_title} {site_url}' ),
'id' => 'woocommerce_email_footer_text',
'css' => 'width:400px; height: 75px;',
'placeholder' => __( 'N/A', 'woocommerce' ),
'type' => 'textarea',
'default' => '{site_title} &mdash; Built with {WooCommerce}',
'autoload' => false,
'desc_tip' => true,
),
array(
'title' => __( 'Base color', 'woocommerce' ),
/* translators: %s: default color */
@@ -191,6 +178,31 @@ class WC_Settings_Emails extends WC_Settings_Page {
'desc_tip' => true,
),
array(
'title' => __( 'Footer text', 'woocommerce' ),
/* translators: %s: Available placeholders for use */
'desc' => __( 'The text to appear in the footer of all WooCommerce emails.', 'woocommerce' ) . ' ' . sprintf( __( 'Available placeholders: %s', 'woocommerce' ), '{site_title} {site_url}' ),
'id' => 'woocommerce_email_footer_text',
'css' => 'width:400px; height: 75px;',
'placeholder' => __( 'N/A', 'woocommerce' ),
'type' => 'textarea',
'default' => '{site_title} &mdash; Built with {WooCommerce}',
'autoload' => false,
'desc_tip' => true,
),
array(
'title' => __( 'Footer text color', 'woocommerce' ),
/* translators: %s: footer default color */
'desc' => sprintf( __( 'The footer text color. Default %s.', 'woocommerce' ), '<code>#3c3c3c</code>' ),
'id' => 'woocommerce_email_footer_text_color',
'type' => 'color',
'css' => 'width:6em;',
'default' => '#3c3c3c',
'autoload' => false,
'desc_tip' => true,
),
array(
'type' => 'sectionend',
'id' => 'email_template_options',

View File

@@ -0,0 +1,172 @@
<?php
declare( strict_types = 1);
// @codingStandardsIgnoreLine.
/**
* WooCommerce Checkout Settings
*
* @package WooCommerce\Admin
*/
defined( 'ABSPATH' ) || exit;
if ( class_exists( 'WC_Settings_Payment_Gateways_React', false ) ) {
return new WC_Settings_Payment_Gateways_React();
}
/**
* WC_Settings_Payment_Gateways_React.
*/
class WC_Settings_Payment_Gateways_React extends WC_Settings_Page {
/**
* Get the whitelist of sections to render using React.
*
* @return array List of section identifiers.
*/
private function get_reactify_render_sections() {
$sections = array(
'offline',
'woocommerce_payments',
'main',
);
/**
* Filters the list of payment settings sections to be rendered using React.
*
* @since 9.3.0
*
* @param array $sections List of section identifiers.
*/
return apply_filters( 'experimental_woocommerce_admin_payment_reactify_render_sections', $sections );
}
/**
* Constructor.
*/
public function __construct() {
$this->id = 'checkout';
$this->label = _x( 'Payments', 'Settings tab label', 'woocommerce' );
parent::__construct();
}
/**
* Output the settings.
*/
public function output() {
//phpcs:disable WordPress.Security.NonceVerification.Recommended
global $current_section;
// Load gateways so we can show any global options they may have.
$payment_gateways = WC()->payment_gateways->payment_gateways();
if ( $this->should_render_react_section( $current_section ) ) {
$this->render_react_section( $current_section );
} elseif ( $current_section ) {
$this->render_classic_gateway_settings_page( $payment_gateways, $current_section );
} else {
$this->render_react_section( 'main' );
}
parent::output();
//phpcs:enable
}
/**
* Check if the given section should be rendered using React.
*
* @param string $section The section to check.
* @return bool Whether the section should be rendered using React.
*/
private function should_render_react_section( $section ) {
return in_array( $section, $this->get_reactify_render_sections(), true );
}
/**
* Render the React section.
*
* @param string $section The section to render.
*/
private function render_react_section( $section ) {
global $hide_save_button;
$hide_save_button = true;
echo '<div id="experimental_wc_settings_payments_' . esc_attr( $section ) . '"></div>';
}
/**
* Render the classic gateway settings page.
*
* @param array $payment_gateways The payment gateways.
* @param string $current_section The current section.
*/
private function render_classic_gateway_settings_page( $payment_gateways, $current_section ) {
foreach ( $payment_gateways as $gateway ) {
if ( in_array( $current_section, array( $gateway->id, sanitize_title( get_class( $gateway ) ) ), true ) ) {
if ( isset( $_GET['toggle_enabled'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$enabled = $gateway->get_option( 'enabled' );
if ( $enabled ) {
$gateway->settings['enabled'] = wc_string_to_bool( $enabled ) ? 'no' : 'yes';
}
}
$this->run_gateway_admin_options( $gateway );
break;
}
}
}
/**
* Run the 'admin_options' method on a given gateway.
* This method exists to easy unit testing.
*
* @param object $gateway The gateway object to run the method on.
*/
protected function run_gateway_admin_options( $gateway ) {
$gateway->admin_options();
}
/**
* Don't show any section links.
*
* @return array
*/
public function get_sections() {
return array();
}
/**
* Save settings.
*/
public function save() {
global $current_section;
$wc_payment_gateways = WC_Payment_Gateways::instance();
$this->save_settings_for_current_section();
if ( ! $current_section ) {
// If section is empty, we're on the main settings page. This makes sure 'gateway ordering' is saved.
$wc_payment_gateways->process_admin_options();
$wc_payment_gateways->init();
} else {
// There is a section - this may be a gateway or custom section.
foreach ( $wc_payment_gateways->payment_gateways() as $gateway ) {
if ( in_array( $current_section, array( $gateway->id, sanitize_title( get_class( $gateway ) ) ), true ) ) {
/**
* Fires update actions for payment gateways.
*
* @since 3.4.0
*
* @param int $gateway->id Gateway ID.
*/
do_action( 'woocommerce_update_options_payment_gateways_' . $gateway->id );
$wc_payment_gateways->init();
}
}
$this->do_update_options_action();
}
}
}
return new WC_Settings_Payment_Gateways_React();

View File

@@ -8,7 +8,20 @@ if ( ! defined( 'ABSPATH' ) ) {
<span><?php esc_html_e( 'Shipping zones', 'woocommerce' ); ?></span>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-settings&tab=shipping&zone_id=new' ) ); ?>" class="page-title-action"><?php esc_html_e( 'Add zone', 'woocommerce' ); ?></a>
</h2>
<p class="wc-shipping-zone-heading-help-text"><?php echo esc_html_e( 'A shipping zone consists of the region(s) you\'d like to ship to and the shipping method(s) offered. A shopper can only be matched to one zone, and we\'ll use their shipping address to show them the methods available in their area.', 'woocommerce' ); ?></p>
<p class="wc-shipping-zone-heading-help-text">
<?php
echo wp_kses_post(
sprintf(
/* translators: %s: URL to local pickup settings */
__(
"A shipping zone consists of the region(s) you'd like to ship to and the shipping method(s) offered. A shopper can only be matched to one zone, and we'll use their shipping address to show them the methods available in their area. To offer local pickup, configure your pickup locations in the <a href='%s'>local pickup settings</a>.",
'woocommerce'
),
esc_url( admin_url( 'admin.php?page=wc-settings&tab=shipping&section=pickup_location' ) )
)
);
?>
</p>
<table class="wc-shipping-zones widefat">
<thead>
<tr>
@@ -56,14 +69,13 @@ if ( ! defined( 'ABSPATH' ) ) {
<?php if ( 0 === $method_count ) : ?>
<tr>
<td class="wc-shipping-zones-blank-state" colspan="5">
<p class="main"><?php _e( 'A shipping zone is a geographic region where a certain set of shipping methods and rates apply.', 'woocommerce' ); ?></p>
<p><?php _e( 'For example:', 'woocommerce' ); ?></p>
<p class="main"><?php esc_html_e( 'A shipping zone is a geographic region where a certain set of shipping methods and rates apply.', 'woocommerce' ); ?></p>
<p><?php esc_html_e( 'For example:', 'woocommerce' ); ?></p>
<ul>
<li><?php _e( 'Local zone = California ZIP 90210 = Local pickup', 'woocommerce' ); ?>
<li><?php _e( 'US domestic zone = All US states = Flat rate shipping', 'woocommerce' ); ?>
<li><?php _e( 'Europe zone = Any country in Europe = Flat rate shipping', 'woocommerce' ); ?>
<li><?php esc_html_e( 'US domestic zone = All US states = Flat rate shipping', 'woocommerce' ); ?>
<li><?php esc_html_e( 'Europe zone = Any country in Europe = Flat rate shipping', 'woocommerce' ); ?>
</ul>
<p><?php _e( 'Add as many zones as you need &ndash; customers will only see the methods available for their address.', 'woocommerce' ); ?></p>
<p><?php esc_html_e( 'Add as many zones as you need &ndash; customers will only see the methods available for their address.', 'woocommerce' ); ?></p>
<a class="button button-primary wc-shipping-zone-add" href="<?php echo esc_url( admin_url( 'admin.php?page=wc-settings&tab=shipping&zone_id=new' ) ); ?>"><?php _e( 'Add shipping zone', 'woocommerce' ); ?></a>
</td>
</tr>
@@ -95,9 +107,9 @@ if ( ! defined( 'ABSPATH' ) ) {
<div class="wc-backbone-modal-content">
<section class="wc-backbone-modal-main" role="main">
<header class="wc-backbone-modal-header">
<h1><?php _e( 'Add shipping method', 'woocommerce' ); ?></h1>
<h1><?php esc_html_e( 'Add shipping method', 'woocommerce' ); ?></h1>
<button class="modal-close modal-close-link dashicons dashicons-no-alt">
<span class="screen-reader-text"><?php _e( 'Close modal panel', 'woocommerce' ); ?></span>
<span class="screen-reader-text"><?php esc_html_e( 'Close modal panel', 'woocommerce' ); ?></span>
</button>
</header>
<article>

View File

@@ -1060,6 +1060,12 @@ if ( 0 < $mu_plugins_count ) :
</mark>
<a href="https://woocommerce.com/document/fix-outdated-templates-woocommerce/" target="_blank">
<?php esc_html_e( 'Learn how to update', 'woocommerce' ); ?>
</a> |
<mark class="info">
<span class="dashicons dashicons-info"></span>
</mark>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-status&tab=tools' ) ); ?>">
<?php esc_html_e( 'Clear system status theme info cache', 'woocommerce' ); ?>
</a>
</td>
</tr>

View File

@@ -129,7 +129,18 @@ class WC_Cache_Helper {
$location['state'] = $customer->get_billing_state();
$location['postcode'] = $customer->get_billing_postcode();
$location['city'] = $customer->get_billing_city();
return apply_filters( 'woocommerce_geolocation_ajax_get_location_hash', substr( md5( implode( '', $location ) ), 0, 12 ), $location, $customer );
$location_hash = substr( md5( strtolower( implode( '', $location ) ) ), 0, 12 );
/**
* Controls the location hash used in geolocation-based caching.
*
* @since 3.6.0
*
* @param string $location_hash The hash used for geolocation.
* @param array $location The location/address data.
* @param WC_Customer $customer The current customer object.
*/
return apply_filters( 'woocommerce_geolocation_ajax_get_location_hash', $location_hash, $location, $customer );
}
/**

View File

@@ -1170,7 +1170,7 @@ class WC_Cart extends WC_Legacy_Cart {
$message = apply_filters( 'woocommerce_cart_product_cannot_add_another_message', $message, $product_data );
$wp_button_class = wc_wp_theme_get_element_class_name( 'button' ) ? ' ' . wc_wp_theme_get_element_class_name( 'button' ) : '';
throw new Exception( sprintf( '<a href="%s" class="button wc-forward%s">%s</a> %s', wc_get_cart_url(), esc_attr( $wp_button_class ), __( 'View cart', 'woocommerce' ), $message ) );
throw new Exception( sprintf( '%s <a href="%s" class="button wc-forward%s">%s</a>', $message, wc_get_cart_url(), esc_attr( $wp_button_class ), __( 'View cart', 'woocommerce' ) ) );
}
}
@@ -1232,12 +1232,12 @@ class WC_Cart extends WC_Legacy_Cart {
$wp_button_class = wc_wp_theme_get_element_class_name( 'button' ) ? ' ' . wc_wp_theme_get_element_class_name( 'button' ) : '';
$message = sprintf(
'<a href="%s" class="button wc-forward%s">%s</a> %s',
'%s <a href="%s" class="button wc-forward%s">%s</a>',
/* translators: 1: quantity in stock 2: current quantity */
sprintf( __( 'You cannot add that amount to the cart &mdash; we have %1$s in stock and you already have %2$s in your cart.', 'woocommerce' ), wc_format_stock_quantity_for_display( $stock_quantity, $product_data ), wc_format_stock_quantity_for_display( $stock_quantity_in_cart, $product_data ) ),
wc_get_cart_url(),
esc_attr( $wp_button_class ),
__( 'View cart', 'woocommerce' ),
/* translators: 1: quantity in stock 2: current quantity */
sprintf( __( 'You cannot add that amount to the cart &mdash; we have %1$s in stock and you already have %2$s in your cart.', 'woocommerce' ), wc_format_stock_quantity_for_display( $stock_quantity, $product_data ), wc_format_stock_quantity_for_display( $stock_quantity_in_cart, $product_data ) )
__( 'View cart', 'woocommerce' )
);
/**

View File

@@ -34,6 +34,7 @@ class WC_CLI {
require_once __DIR__ . '/cli/class-wc-cli-tracker-command.php';
require_once __DIR__ . '/cli/class-wc-cli-com-command.php';
require_once __DIR__ . '/cli/class-wc-cli-com-extension-command.php';
$this->maybe_include_blueprint_cli();
}
/**
@@ -50,6 +51,24 @@ class WC_CLI {
WP_CLI::add_hook( 'after_wp_load', array( $cli_runner, 'register_commands' ) );
$cli_runner = wc_get_container()->get( ProductAttributesLookupCLIRunner::class );
WP_CLI::add_hook( 'after_wp_load', fn() => \WP_CLI::add_command( 'wc palt', $cli_runner ) );
if ( class_exists( \Automattic\WooCommerce\Blueprint\Cli::class ) ) {
WP_CLI::add_hook( 'after_wp_load', 'Automattic\WooCommerce\Blueprint\Cli::register_commands' );
}
}
/**
* Include Blueprint CLI if it's available.
*/
private function maybe_include_blueprint_cli() {
if ( ! function_exists( 'wc_admin_get_feature_config' ) ) {
require_once WC_ABSPATH . 'includes/react-admin/feature-config.php';
}
$features = wc_admin_get_feature_config();
if ( isset( $features['blueprint'] ) ) {
require_once dirname( WC_PLUGIN_FILE ) . '/vendor/woocommerce/blueprint/src/Cli.php';
}
}
}

View File

@@ -13,7 +13,7 @@ use Automattic\WooCommerce\Utilities\StringUtil;
defined( 'ABSPATH' ) || exit;
require_once dirname( __FILE__ ) . '/legacy/class-wc-legacy-coupon.php';
require_once __DIR__ . '/legacy/class-wc-legacy-coupon.php';
/**
* Coupon class.
@@ -1066,7 +1066,7 @@ class WC_Coupon extends WC_Legacy_Coupon {
$err = __( 'Sorry, this coupon is not applicable to your cart contents.', 'woocommerce' );
break;
case self::E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK:
if ( is_user_logged_in() && wc_get_page_id( 'myaccount' ) > 0 ) {
if ( is_user_logged_in() && wc_get_page_id( 'myaccount' ) > 0 && ! WC()->is_store_api_request() ) {
/* translators: %s: myaccount page link. */
$err = sprintf( __( 'Coupon usage limit has been reached. If you were using this coupon just now but your order was not complete, you can retry or cancel the order by going to the <a href="%s">my account page</a>.', 'woocommerce' ), wc_get_endpoint_url( 'orders', '', wc_get_page_permalink( 'myaccount' ) ) );
} else {

View File

@@ -582,7 +582,6 @@ class WC_Form_Handler {
wp_safe_redirect( wc_get_account_endpoint_url( 'payment-methods' ) );
exit();
}
}
/**
@@ -607,7 +606,6 @@ class WC_Form_Handler {
wp_safe_redirect( wc_get_account_endpoint_url( 'payment-methods' ) );
exit();
}
}
/**
@@ -653,10 +651,10 @@ class WC_Form_Handler {
wc_add_notice( $removed_notice, apply_filters( 'woocommerce_cart_item_removed_notice_type', 'success' ) );
}
$referer = wp_get_referer() ? remove_query_arg( array( 'remove_item', 'add-to-cart', 'added-to-cart', 'order_again', '_wpnonce' ), add_query_arg( 'removed_item', '1', wp_get_referer() ) ) : wc_get_cart_url();
wp_safe_redirect( $referer );
exit;
if ( wp_get_referer() ) {
wp_safe_redirect( remove_query_arg( array( 'remove_item', 'add-to-cart', 'added-to-cart', 'order_again', '_wpnonce' ), add_query_arg( 'removed_item', '1', wp_get_referer() ) ) );
exit;
}
} elseif ( ! empty( $_GET['undo_item'] ) && isset( $_GET['_wpnonce'] ) && wp_verify_nonce( $nonce_value, 'woocommerce-cart' ) ) {
// Undo Cart Item.
@@ -664,10 +662,10 @@ class WC_Form_Handler {
WC()->cart->restore_cart_item( $cart_item_key );
$referer = wp_get_referer() ? remove_query_arg( array( 'undo_item', '_wpnonce' ), wp_get_referer() ) : wc_get_cart_url();
wp_safe_redirect( $referer );
exit;
if ( wp_get_referer() ) {
wp_safe_redirect( remove_query_arg( array( 'undo_item', '_wpnonce' ), wp_get_referer() ) );
exit;
}
}
// Update Cart - checks apply_coupon too because they are in the same form.
@@ -722,9 +720,11 @@ class WC_Form_Handler {
exit;
} elseif ( $cart_updated ) {
wc_add_notice( __( 'Cart updated.', 'woocommerce' ), apply_filters( 'woocommerce_cart_updated_notice_type', 'success' ) );
$referer = remove_query_arg( array( 'remove_coupon', 'add-to-cart' ), ( wp_get_referer() ? wp_get_referer() : wc_get_cart_url() ) );
wp_safe_redirect( $referer );
exit;
if ( wp_get_referer() ) {
wp_safe_redirect( remove_query_arg( array( 'remove_coupon', 'add-to-cart' ), wp_get_referer() ) );
exit;
}
}
}
}

View File

@@ -262,6 +262,10 @@ class WC_Install {
'9.2.0' => array(
'wc_update_920_add_wc_hooked_blocks_version_option',
),
'9.3.0' => array(
'wc_update_930_add_woocommerce_coming_soon_option',
'wc_update_930_migrate_user_meta_for_launch_your_store_tour',
),
);
/**
@@ -292,6 +296,7 @@ class WC_Install {
add_action( 'init', array( __CLASS__, 'check_version' ), 5 );
add_action( 'init', array( __CLASS__, 'manual_database_update' ), 20 );
add_action( 'woocommerce_newly_installed', array( __CLASS__, 'maybe_enable_hpos' ), 20 );
add_action( 'woocommerce_newly_installed', array( __CLASS__, 'add_coming_soon_option' ), 20 );
add_action( 'admin_init', array( __CLASS__, 'wc_admin_db_update_notice' ) );
add_action( 'admin_init', array( __CLASS__, 'add_admin_note_after_page_created' ) );
add_action( 'woocommerce_run_update_callback', array( __CLASS__, 'run_update_callback' ) );
@@ -986,6 +991,17 @@ class WC_Install {
}
}
/**
* Add the woocommerce_coming_soon option for new shops.
*
* Ensure that the option is set for all shops, even if core profiler is disabled on the host.
*
* @since 9.3.0
*/
public static function add_coming_soon_option() {
add_option( 'woocommerce_coming_soon', 'no' );
}
/**
* Checks whether HPOS should be enabled for new shops.
*

View File

@@ -74,4 +74,28 @@ class WC_Product_Simple extends WC_Product {
return apply_filters( 'woocommerce_product_add_to_cart_description', sprintf( $text, $this->get_name() ), $this );
}
/**
* Get the add to cart button success message - used to update the mini cart live region.
*
* @return string
*/
public function add_to_cart_success_message() {
$text = '';
if ( $this->is_purchasable() && $this->is_in_stock() ) {
/* translators: %s: Product title */
$text = __( '&ldquo;%s&rdquo; has been added to your cart', 'woocommerce' );
$text = sprintf( $text, $this->get_name() );
}
/**
* Filter product add to cart success message.
*
* @since 9.2.0
* @param string $text The success message when a product is added to the cart.
* @param WC_Product_Simple $this Reference to the current WC_Product_Simple instance.
*/
return apply_filters( 'woocommerce_product_add_to_cart_success_message', $text, $this );
}
}

View File

@@ -214,6 +214,12 @@ class WC_Structured_Data {
$markup['sku'] = $product->get_id();
}
// Add GTIN only if it's a valid number.
$gtin = $product->get_global_unique_id();
if ( $gtin && is_numeric( $gtin ) ) {
$markup['gtin'] = $gtin;
}
if ( '' !== $product->get_price() ) {
// Assume prices will be valid until the end of next year, unless on sale and there is an end date.
$price_valid_until = gmdate( 'Y-12-31', time() + YEAR_IN_SECONDS );

View File

@@ -112,13 +112,20 @@ class WC_Tracker {
* However, there are version of JP where \Automattic\Jetpack\Status exists, but does *not* contain is_staging_site method,
* so with those, code still needs to use the previous check as a fallback.
*
* After upgrading Jetpack Status to v3.3.2 is_staging_site is also deprecated and in_safe_mode is the new replacement.
* So we check this first of all.
*
* @return bool
*/
private static function is_jetpack_staging_site() {
if ( class_exists( '\Automattic\Jetpack\Status' ) ) {
// Preferred way of checking with Jetpack 8.1+.
$jp_status = new \Automattic\Jetpack\Status();
if ( is_callable( array( $jp_status, 'is_staging_site' ) ) ) {
if ( is_callable( array( $jp_status, 'in_safe_mode' ) ) ) {
return $jp_status->in_safe_mode();
} elseif ( is_callable( array( $jp_status, 'is_staging_site' ) ) ) {
// Preferred way of checking with Jetpack 8.1+.
return $jp_status->is_staging_site();
}
}

View File

@@ -10,6 +10,7 @@ defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
use Automattic\WooCommerce\Internal\BatchProcessing\BatchProcessingController;
use Automattic\WooCommerce\Internal\ComingSoon\ComingSoonAdminBarBadge;
use Automattic\WooCommerce\Internal\ComingSoon\ComingSoonCacheInvalidator;
use Automattic\WooCommerce\Internal\ComingSoon\ComingSoonRequestHandler;
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
@@ -26,8 +27,10 @@ use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
use Automattic\WooCommerce\Internal\Utilities\LegacyRestApiStub;
use Automattic\WooCommerce\Internal\Utilities\WebhookUtil;
use Automattic\WooCommerce\Internal\Admin\Marketplace;
use Automattic\WooCommerce\Internal\McStats;
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Utilities\{LoggingUtil, RestApiUtil, TimeUtil};
use Automattic\WooCommerce\Internal\Logging\RemoteLogger;
/**
* Main WooCommerce Class.
@@ -43,7 +46,7 @@ final class WooCommerce {
*
* @var string
*/
public $version = '9.2.3';
public $version = '9.3.1';
/**
* WooCommerce Schema version.
@@ -309,6 +312,7 @@ final class WooCommerce {
self::add_filter( 'robots_txt', array( $this, 'robots_txt' ) );
add_filter( 'wp_plugin_dependencies_slug', array( $this, 'convert_woocommerce_slug' ) );
self::add_filter( 'woocommerce_register_log_handlers', array( $this, 'register_remote_log_handler' ) );
// These classes set up hooks on instantiation.
$container = wc_get_container();
@@ -326,6 +330,7 @@ final class WooCommerce {
$container->get( WebhookUtil::class );
$container->get( Marketplace::class );
$container->get( TimeUtil::class );
$container->get( ComingSoonAdminBarBadge::class );
$container->get( ComingSoonCacheInvalidator::class );
$container->get( ComingSoonRequestHandler::class );
@@ -402,6 +407,12 @@ final class WooCommerce {
$context
);
// Record fatal error stats.
$container = wc_get_container();
$mc_stats = $container->get( McStats::class );
$mc_stats->add( 'error', 'fatal-errors-during-shutdown' );
$mc_stats->do_server_side_stats();
/**
* Action triggered when there are errors during shutdown.
*
@@ -415,8 +426,6 @@ final class WooCommerce {
* Define WC Constants.
*/
private function define_constants() {
$upload_dir = wp_upload_dir( null, false );
$this->define( 'WC_ABSPATH', dirname( WC_PLUGIN_FILE ) . '/' );
$this->define( 'WC_PLUGIN_BASENAME', plugin_basename( WC_PLUGIN_FILE ) );
$this->define( 'WC_VERSION', $this->version );
@@ -435,8 +444,9 @@ final class WooCommerce {
*/
if ( defined( 'WC_LOG_DIR' ) ) {
$this->define( 'WC_LOG_DIR_CUSTOM', true );
} else {
$this->define( 'WC_LOG_DIR', LoggingUtil::get_log_directory( false ) );
}
$this->define( 'WC_LOG_DIR', LoggingUtil::get_log_directory() );
// These three are kept defined for compatibility, but are no longer used.
$this->define( 'WC_NOTICE_MIN_PHP_VERSION', '7.2' );
@@ -702,6 +712,11 @@ final class WooCommerce {
*/
include_once WC_ABSPATH . 'includes/wccom-site/class-wc-wccom-site.php';
/**
* Product Usage
*/
include_once WC_ABSPATH . 'includes/product-usage/class-wc-product-usage.php';
/**
* Libraries and packages.
*/
@@ -1304,4 +1319,16 @@ final class WooCommerce {
}
return $slug;
}
/**
* Register the remote log handler.
*
* @param \WC_Log_Handler[] $handlers The handlers to register.
*
* @return \WC_Log_Handler[]
*/
private function register_remote_log_handler( $handlers ) {
$handlers[] = wc_get_container()->get( RemoteLogger::class );
return $handlers;
}
}

View File

@@ -131,6 +131,20 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
$sku,
$sku
);
/**
* Filter to bail early on the SKU lock query.
*
* @since 9.3.0
*
* @param bool|null $locked Set to a boolean value to short-circuit the SKU lock query.
* @param WC_Product $product The product being created.
*/
$locked = apply_filters( 'wc_product_pre_lock_on_sku', null, $product );
if ( ! is_null( $locked ) ) {
return boolval( $locked );
}
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$result = $wpdb->query( $query );
@@ -642,21 +656,21 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
// Fire actions to let 3rd parties know the stock is about to be changed.
if ( $product->is_type( 'variation' ) ) {
/**
* Action to signal that the value of 'stock_quantity' for a variation is about to change.
*
* @since 4.9
*
* @param int $product The variation whose stock is about to change.
*/
* Action to signal that the value of 'stock_quantity' for a variation is about to change.
*
* @param WC_Product $product The variation whose stock is about to change.
*
* @since 4.9
*/
do_action( 'woocommerce_variation_before_set_stock', $product );
} else {
/**
* Action to signal that the value of 'stock_quantity' for a product is about to change.
*
* @since 4.9
*
* @param int $product The product whose stock is about to change.
*/
* Action to signal that the value of 'stock_quantity' for a product is about to change.
*
* @param WC_Product $product The product whose stock is about to change.
*
* @since 4.9
*/
do_action( 'woocommerce_product_before_set_stock', $product );
}
break;
@@ -732,16 +746,48 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
if ( in_array( 'stock_quantity', $this->updated_props, true ) ) {
if ( $product->is_type( 'variation' ) ) {
/**
* Action to signal that the value of 'stock_quantity' for a variation has changed.
*
* @since 3.0
*
* @param WC_Product $product The variation whose stock has changed.
*/
do_action( 'woocommerce_variation_set_stock', $product );
} else {
/**
* Action to signal that the value of 'stock_quantity' for a product has changed.
*
* @since 3.0
*
* @param WC_Product $product The variation whose stock has changed.
*/
do_action( 'woocommerce_product_set_stock', $product );
}
}
if ( in_array( 'stock_status', $this->updated_props, true ) ) {
if ( $product->is_type( 'variation' ) ) {
/**
* Action to signal that the `stock_status` for a variation has changed.
*
* @since 3.0
*
* @param int $product_id The ID of the variation.
* @param string $stock_status The new stock status of the variation.
* @param WC_Product $product The product object.
*/
do_action( 'woocommerce_variation_set_stock_status', $product->get_id(), $product->get_stock_status(), $product );
} else {
/**
* Action to signal that the `stock_status` for a product has changed.
*
* @since 3.0
*
* @param int $product_id The ID of the product.
* @param string $stock_status The new stock status of the product.
* @param WC_Product $product The product object.
*/
do_action( 'woocommerce_product_set_stock_status', $product->get_id(), $product->get_stock_status(), $product );
}
}

View File

@@ -5,6 +5,8 @@
* @package WooCommerce\Classes\Payment
*/
use Automattic\WooCommerce\Utilities\LoggingUtil;
defined( 'ABSPATH' ) || exit;
return array(
@@ -55,7 +57,11 @@ return array(
'label' => __( 'Enable logging', 'woocommerce' ),
'default' => 'no',
/* translators: %s: URL */
'description' => sprintf( __( 'Log PayPal events, such as IPN requests, inside %s Note: this may log personal information. We recommend using this for debugging purposes only and deleting the logs when finished.', 'woocommerce' ), '<code>' . WC_Log_Handler_File::get_log_file_path( 'paypal' ) . '</code>' ),
'description' => sprintf(
// translators: %s is a placeholder for a URL.
__( 'Log PayPal events such as IPN requests and review them on the <a href="%s">Logs screen</a>. Note: this may log personal information. We recommend using this for debugging purposes only and deleting the logs when finished.', 'woocommerce' ),
esc_url( LoggingUtil::get_logs_tab_url() )
),
),
'ipn_notification' => array(
'title' => __( 'IPN email notifications', 'woocommerce' ),

View File

@@ -0,0 +1,50 @@
<?php
/**
* WooCommerce Product Usage Rule.
*
* This class defines the DTO for passing product feature restriction rules to WooCommerce extensions.
*
* @package WooCommerce\Admin\ProductUsage
*/
declare( strict_types = 1 );
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Product_Usage_Rule_Set.
*/
class WC_Product_Usage_Rule_Set {
/**
* Set of product feature restriction rules.
*
* @var array|null $rules
*/
protected $rules;
/**
* Constructor
*
* @param array $rules product feature restriction rules.
*/
public function __construct( $rules ) {
$this->rules = $rules;
}
/**
* Retrieve the value of a rule by name
*
* @param string $rule_name name of the rule to retrieve value.
* @return mixed|null
*/
public function get_rule( string $rule_name ) {
if ( ! isset( $this->rules[ $rule_name ] ) ) {
return null;
}
return $this->rules[ $rule_name ];
}
}

View File

@@ -0,0 +1,87 @@
<?php
/**
* WooCommerce Product Usage.
*
* This class defines method to be used by Woo extensions to control product usage based on subscription status.
*
* @package WooCommerce\ProductUsage
*/
declare( strict_types = 1 );
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Product usagee
*/
class WC_Product_Usage {
/**
* Load Product Usage class.
*
* @since 9.3.0
*/
public static function load() {
self::includes();
}
/**
* Include support files.
*
* @since 9.3.0
*/
protected static function includes() {
require_once WC_ABSPATH . 'includes/product-usage/class-wc-product-usage-rule-set.php';
}
/**
* Get product usage rule if it needs to be applied to the given product id.
*
* @param int $product_id product id to get feature restriction rules.
* @since 9.3.0
*/
public static function get_rules_for_product( int $product_id ): ?WC_Product_Usage_Rule_Set {
$rules = self::get_product_usage_restriction_rule( $product_id );
if ( null === $rules ) {
return null;
}
// When there is no subscription for the product, restrict usage.
if ( ! WC_Helper::has_product_subscription( $product_id ) ) {
return new WC_Product_Usage_Rule_Set( $rules );
}
$subscriptions = wp_list_filter( WC_Helper::get_installed_subscriptions(), array( 'product_id' => $product_id ) );
if ( empty( $subscriptions ) ) {
return new WC_Product_Usage_Rule_Set( $rules );
}
// Product should only have a single connected subscription on current store.
$product_subscription = current( $subscriptions );
if ( $product_subscription['expired'] ) {
return new WC_Product_Usage_Rule_Set( $rules );
}
return null;
}
/**
* Get the product usage rule for a product.
*
* @param int $product_id product id to get feature restriction rules.
* @return array|null
* @since 9.3.0
*/
private static function get_product_usage_restriction_rule( int $product_id ): ?array {
$rules = WC_Helper::get_product_usage_notice_rules();
if ( empty( $rules['restricted_products'][ $product_id ] ) ) {
return null;
}
return $rules['restricted_products'][ $product_id ];
}
}
WC_Product_Usage::load();

View File

@@ -41,6 +41,8 @@ if ( ! function_exists( 'wc_admin_get_feature_config' ) ) {
'async-product-editor-category-field' => false,
'launch-your-store' => true,
'product-editor-template-system' => false,
'blueprint' => false,
'reactify-classic-payments-settings' => false,
);
}
}

View File

@@ -123,42 +123,42 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
*/
public function get_tools() {
$tools = array(
'clear_transients' => array(
'clear_transients' => array(
'name' => __( 'WooCommerce transients', 'woocommerce' ),
'button' => __( 'Clear transients', 'woocommerce' ),
'desc' => __( 'This tool will clear the product/shop transients cache.', 'woocommerce' ),
),
'clear_expired_transients' => array(
'clear_expired_transients' => array(
'name' => __( 'Expired transients', 'woocommerce' ),
'button' => __( 'Clear transients', 'woocommerce' ),
'desc' => __( 'This tool will clear ALL expired transients from WordPress.', 'woocommerce' ),
),
'delete_orphaned_variations' => array(
'delete_orphaned_variations' => array(
'name' => __( 'Orphaned variations', 'woocommerce' ),
'button' => __( 'Delete orphaned variations', 'woocommerce' ),
'desc' => __( 'This tool will delete all variations which have no parent.', 'woocommerce' ),
),
'clear_expired_download_permissions' => array(
'clear_expired_download_permissions' => array(
'name' => __( 'Used-up download permissions', 'woocommerce' ),
'button' => __( 'Clean up download permissions', 'woocommerce' ),
'desc' => __( 'This tool will delete expired download permissions and permissions with 0 remaining downloads.', 'woocommerce' ),
),
'regenerate_product_lookup_tables' => array(
'regenerate_product_lookup_tables' => array(
'name' => __( 'Product lookup tables', 'woocommerce' ),
'button' => __( 'Regenerate', 'woocommerce' ),
'desc' => __( 'This tool will regenerate product lookup table data. This process may take a while.', 'woocommerce' ),
),
'recount_terms' => array(
'recount_terms' => array(
'name' => __( 'Term counts', 'woocommerce' ),
'button' => __( 'Recount terms', 'woocommerce' ),
'desc' => __( 'This tool will recount product terms - useful when changing your settings in a way which hides products from the catalog.', 'woocommerce' ),
),
'reset_roles' => array(
'reset_roles' => array(
'name' => __( 'Capabilities', 'woocommerce' ),
'button' => __( 'Reset capabilities', 'woocommerce' ),
'desc' => __( 'This tool will reset the admin, customer and shop_manager roles to default. Use this if your users cannot access all of the WooCommerce admin pages.', 'woocommerce' ),
),
'clear_sessions' => array(
'clear_sessions' => array(
'name' => __( 'Clear customer sessions', 'woocommerce' ),
'button' => __( 'Clear', 'woocommerce' ),
'desc' => sprintf(
@@ -167,7 +167,7 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
__( 'This tool will delete all customer session data from the database, including current carts and saved carts in the database.', 'woocommerce' )
),
),
'clear_template_cache' => array(
'clear_template_cache' => array(
'name' => __( 'Clear template cache', 'woocommerce' ),
'button' => __( 'Clear', 'woocommerce' ),
'desc' => sprintf(
@@ -176,7 +176,16 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
__( 'This tool will empty the template cache.', 'woocommerce' )
),
),
'install_pages' => array(
'clear_system_status_theme_info_cache' => array(
'name' => __( 'Clear system status theme info cache', 'woocommerce' ),
'button' => __( 'Clear', 'woocommerce' ),
'desc' => sprintf(
'<strong class="red">%1$s</strong> %2$s',
__( 'Note:', 'woocommerce' ),
__( 'This tool will empty the system status theme info cache.', 'woocommerce' )
),
),
'install_pages' => array(
'name' => __( 'Create default WooCommerce pages', 'woocommerce' ),
'button' => __( 'Create pages', 'woocommerce' ),
'desc' => sprintf(
@@ -185,7 +194,7 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
__( 'This tool will install all the missing WooCommerce pages. Pages already defined and set up will not be replaced.', 'woocommerce' )
),
),
'delete_taxes' => array(
'delete_taxes' => array(
'name' => __( 'Delete WooCommerce tax rates', 'woocommerce' ),
'button' => __( 'Delete tax rates', 'woocommerce' ),
'desc' => sprintf(
@@ -194,12 +203,12 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
__( 'This option will delete ALL of your tax rates, use with caution. This action cannot be reversed.', 'woocommerce' )
),
),
'regenerate_thumbnails' => array(
'regenerate_thumbnails' => array(
'name' => __( 'Regenerate shop thumbnails', 'woocommerce' ),
'button' => __( 'Regenerate', 'woocommerce' ),
'desc' => __( 'This will regenerate all shop thumbnails to match your theme and/or image settings.', 'woocommerce' ),
),
'db_update_routine' => array(
'db_update_routine' => array(
'name' => __( 'Update database', 'woocommerce' ),
'button' => __( 'Update database', 'woocommerce' ),
'desc' => sprintf(
@@ -567,6 +576,11 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
}
break;
case 'clear_system_status_theme_info_cache':
wc_clear_system_status_theme_info_cache();
$message = __( 'System status theme info cache cleared.', 'woocommerce' );
break;
case 'verify_db_tables':
if ( ! method_exists( 'WC_Install', 'verify_base_tables' ) ) {
$message = __( 'You need WooCommerce 4.2 or newer to run this tool.', 'woocommerce' );

View File

@@ -864,7 +864,7 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
}
$database_version = wc_get_server_database_version();
$log_directory = LoggingUtil::get_log_directory();
$log_directory = LoggingUtil::get_log_directory( false );
// Return all environment info. Described by JSON Schema.
return array(

View File

@@ -131,7 +131,7 @@ class WC_REST_Orders_Controller extends WC_REST_Orders_V2_Controller {
foreach ( $value as $item ) {
if ( is_array( $item ) ) {
if ( $this->item_is_null( $item ) || ( isset( $item['quantity'] ) && 0 === $item['quantity'] ) ) {
$order->remove_item( $item['id'] );
$this->remove_item( $order, $key, $item['id'] );
} else {
$this->set_item( $order, $key, $item );
}
@@ -170,6 +170,46 @@ class WC_REST_Orders_Controller extends WC_REST_Orders_V2_Controller {
return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $order, $request, $creating );
}
/**
* Wrapper method to remove order items.
* When updating, the item ID provided is checked to ensure it is associated
* with the order.
*
* @param WC_Order $order The order to remove the item from.
* @param string $item_type The item type (from the request, not from the item, e.g. 'line_items' rather than 'line_item').
* @param int $item_id The ID of the item to remove.
*
* @return void
* @throws WC_REST_Exception If item ID is not associated with order.
*/
protected function remove_item( WC_Order $order, string $item_type, int $item_id ): void {
$item = $order->get_item( $item_id );
if ( ! $item ) {
throw new WC_REST_Exception(
'woocommerce_rest_invalid_item_id',
esc_html__( 'Order item ID provided is not associated with order.', 'woocommerce' ),
400
);
}
if ( 'line_items' === $item_type ) {
require_once WC_ABSPATH . 'includes/admin/wc-admin-functions.php';
wc_maybe_adjust_line_item_product_stock( $item, 0 );
}
/**
* Allow extensions be notified before the item is removed.
*
* @param WC_Order_Item $item The item object.
*
* @since 9.3.0.
*/
do_action( 'woocommerce_rest_remove_order_item', $item );
$order->remove_item( $item_id );
}
/**
* Save an object data.
*

View File

@@ -472,6 +472,10 @@ class WC_REST_Product_Reviews_Controller extends WC_REST_Controller {
update_comment_meta( $review_id, 'rating', ! empty( $request['rating'] ) ? $request['rating'] : '0' );
if ( isset( $request['verified'] ) && ! empty( $request['verified'] ) ) {
update_comment_meta( $review_id, 'verified', $request['verified'] );
}
$review = get_comment( $review_id );
/**
@@ -586,6 +590,10 @@ class WC_REST_Product_Reviews_Controller extends WC_REST_Controller {
update_comment_meta( $id, 'rating', $request['rating'] );
}
if ( isset( $request['verified'] ) && ! empty( $request['verified'] ) ) {
update_comment_meta( $id, 'verified', $request['verified'] );
}
$review = get_comment( $id );
/** This action is documented in includes/api/class-wc-rest-product-reviews-controller.php */

View File

@@ -136,21 +136,15 @@ function wc_get_account_menu_items() {
}
/**
* Get account menu item classes.
* Find current item in account menu.
*
* @since 2.6.0
* @since 9.3.0
* @param string $endpoint Endpoint.
* @return string
* @return bool
*/
function wc_get_account_menu_item_classes( $endpoint ) {
function wc_is_current_account_menu_item( $endpoint ) {
global $wp;
$classes = array(
'woocommerce-MyAccount-navigation-link',
'woocommerce-MyAccount-navigation-link--' . $endpoint,
);
// Set current item class.
$current = isset( $wp->query_vars[ $endpoint ] );
if ( 'dashboard' === $endpoint && ( isset( $wp->query_vars['page'] ) || empty( $wp->query_vars ) ) ) {
$current = true; // Dashboard is not an endpoint, so needs a custom check.
@@ -160,7 +154,23 @@ function wc_get_account_menu_item_classes( $endpoint ) {
$current = true;
}
if ( $current ) {
return $current;
}
/**
* Get account menu item classes.
*
* @since 2.6.0
* @param string $endpoint Endpoint.
* @return string
*/
function wc_get_account_menu_item_classes( $endpoint ) {
$classes = array(
'woocommerce-MyAccount-navigation-link',
'woocommerce-MyAccount-navigation-link--' . $endpoint,
);
if ( wc_is_current_account_menu_item( $endpoint ) ) {
$classes[] = 'is-active';
}
@@ -181,11 +191,13 @@ function wc_get_account_endpoint_url( $endpoint ) {
return wc_get_page_permalink( 'myaccount' );
}
$url = wc_get_endpoint_url( $endpoint, '', wc_get_page_permalink( 'myaccount' ) );
if ( 'customer-logout' === $endpoint ) {
return wc_logout_url();
return wp_nonce_url( $url, 'customer-logout' );
}
return wc_get_endpoint_url( $endpoint, '', wc_get_page_permalink( 'myaccount' ) );
return $url;
}
/**

View File

@@ -123,9 +123,9 @@ function wc_add_to_cart_message( $products, $show_qty = false, $return = false )
$wp_button_class = wc_wp_theme_get_element_class_name( 'button' ) ? ' ' . wc_wp_theme_get_element_class_name( 'button' ) : '';
if ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
$return_to = apply_filters( 'woocommerce_continue_shopping_redirect', wc_get_raw_referer() ? wp_validate_redirect( wc_get_raw_referer(), false ) : wc_get_page_permalink( 'shop' ) );
$message = sprintf( '<a href="%s" tabindex="1" class="button wc-forward%s">%s</a> %s', esc_url( $return_to ), esc_attr( $wp_button_class ), esc_html__( 'Continue shopping', 'woocommerce' ), esc_html( $added_text ) );
$message = sprintf( '%s <a href="%s" class="button wc-forward%s">%s</a>', esc_html( $added_text ), esc_url( $return_to ), esc_attr( $wp_button_class ), esc_html__( 'Continue shopping', 'woocommerce' ) );
} else {
$message = sprintf( '<a href="%s" tabindex="1" class="button wc-forward%s">%s</a> %s', esc_url( wc_get_cart_url() ), esc_attr( $wp_button_class ), esc_html__( 'View cart', 'woocommerce' ), esc_html( $added_text ) );
$message = sprintf( '%s <a href="%s" class="button wc-forward%s">%s</a>', esc_html( $added_text ), esc_url( wc_get_cart_url() ), esc_attr( $wp_button_class ), esc_html__( 'View cart', 'woocommerce' ) );
}
if ( has_filter( 'wc_add_to_cart_message' ) ) {
@@ -170,6 +170,10 @@ function wc_format_list_of_items( $items ) {
function wc_clear_cart_after_payment() {
global $wp;
$should_clear_cart_after_payment = false;
$after_payment = false;
// If the order has been received, clear the cart.
if ( ! empty( $wp->query_vars['order-received'] ) ) {
$order_id = absint( $wp->query_vars['order-received'] );
@@ -179,21 +183,39 @@ function wc_clear_cart_after_payment() {
$order = wc_get_order( $order_id );
if ( $order instanceof WC_Order && hash_equals( $order->get_order_key(), $order_key ) ) {
WC()->cart->empty_cart();
$should_clear_cart_after_payment = true;
$after_payment = true;
}
}
}
if ( is_object( WC()->session ) && WC()->session->order_awaiting_payment > 0 ) {
// If the order is awaiting payment, and we haven't already decided to clear the cart, check the order status.
if ( is_object( WC()->session ) && WC()->session->order_awaiting_payment > 0 && ! $should_clear_cart_after_payment ) {
$order = wc_get_order( WC()->session->order_awaiting_payment );
if ( $order instanceof WC_Order && $order->get_id() > 0 ) {
// If the order has not failed, or is not pending, the order must have gone through.
if ( ! $order->has_status( array( 'failed', 'pending', 'cancelled' ) ) ) {
WC()->cart->empty_cart();
}
// If the order status is neither pending, failed, nor cancelled, the order must have gone through.
$should_clear_cart_after_payment = ! $order->has_status( array( 'failed', 'pending', 'cancelled' ) );
$after_payment = true;
}
}
// If it doesn't look like a payment happened, bail early.
if ( ! $after_payment ) {
return;
}
/**
* Determine whether the cart should be cleared after payment.
*
* @since 9.3.0
* @param bool $should_clear_cart_after_payment Whether the cart should be cleared after payment.
*/
$should_clear_cart_after_payment = apply_filters( 'woocommerce_should_clear_cart_after_payment', $should_clear_cart_after_payment );
if ( $should_clear_cart_after_payment ) {
WC()->cart->empty_cart();
}
}
add_action( 'template_redirect', 'wc_clear_cart_after_payment', 20 );
@@ -391,7 +413,12 @@ function wc_cart_round_discount( $value, $precision ) {
*/
function wc_get_chosen_shipping_method_ids() {
$method_ids = array();
$chosen_methods = WC()->session->get( 'chosen_shipping_methods', array() );
$chosen_methods = array();
if ( is_callable( array( WC()->session, 'get' ) ) ) {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods', array() );
}
foreach ( $chosen_methods as $chosen_method ) {
if ( ! is_string( $chosen_method ) ) {
continue;
@@ -399,6 +426,7 @@ function wc_get_chosen_shipping_method_ids() {
$chosen_method = explode( ':', $chosen_method );
$method_ids[] = current( $chosen_method );
}
return $method_ids;
}

View File

@@ -456,6 +456,15 @@ function wc_clear_template_cache() {
}
}
/**
* Clear the system status theme info cache.
*
* @since 9.4.0
*/
function wc_clear_system_status_theme_info_cache() {
delete_transient( 'wc_system_status_theme_info' );
}
/**
* Get Base Currency Code.
*
@@ -1469,12 +1478,27 @@ function wc_transaction_query( $type = 'start', $force = false ) {
/**
* Gets the url to the cart page.
*
* @since 2.5.0
* @since 2.5.0
* @since 9.3.0 To support shortcodes on other pages besides the main cart page, this returns the current URL if it is the cart page.
*
* @return string Url to cart page
*/
function wc_get_cart_url() {
return apply_filters( 'woocommerce_get_cart_url', wc_get_page_permalink( 'cart' ) );
if ( is_cart() && isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
$protocol = is_ssl() ? 'https' : 'http';
$current_url = esc_url_raw( $protocol . '://' . wp_unslash( $_SERVER['HTTP_HOST'] ) . wp_unslash( $_SERVER['REQUEST_URI'] ) );
$cart_url = remove_query_arg( array( 'remove_item', 'add-to-cart', 'added-to-cart', 'order_again', '_wpnonce' ), $current_url );
} else {
$cart_url = wc_get_page_permalink( 'cart' );
}
/**
* Filter the cart URL.
*
* @since 2.5.0
* @param string $cart_url Cart URL.
*/
return apply_filters( 'woocommerce_get_cart_url', $cart_url );
}
/**
@@ -1580,6 +1604,8 @@ function wc_help_tip( $tip, $allow_html = false ) {
$sanitized_tip = esc_attr( $tip );
}
$aria_label = wp_strip_all_tags( $tip );
/**
* Filter the help tip.
*
@@ -1592,7 +1618,7 @@ function wc_help_tip( $tip, $allow_html = false ) {
*
* @return string
*/
return apply_filters( 'wc_help_tip', '<span class="woocommerce-help-tip" tabindex="0" aria-label="' . $sanitized_tip . '" data-tip="' . $sanitized_tip . '"></span>', $sanitized_tip, $tip, $allow_html );
return apply_filters( 'wc_help_tip', '<span class="woocommerce-help-tip" tabindex="0" aria-label="' . esc_attr( $aria_label ) . '" data-tip="' . $sanitized_tip . '"></span>', $sanitized_tip, $tip, $allow_html );
}
/**
@@ -1612,7 +1638,7 @@ function wc_get_wildcard_postcodes( $postcode, $country = '' ) {
$formatted_postcode . '*',
);
for ( $i = 0; $i < $length; $i ++ ) {
for ( $i = 0; $i < $length; $i++ ) {
$postcodes[] = ( function_exists( 'mb_substr' ) ? mb_substr( $formatted_postcode, 0, ( $i + 1 ) * -1 ) : substr( $formatted_postcode, 0, ( $i + 1 ) * -1 ) ) . '*';
}
@@ -1707,7 +1733,7 @@ function wc_get_shipping_method_count( $include_legacy = false, $enabled_only =
foreach ( $methods as $method ) {
if ( isset( $method->enabled ) && 'yes' === $method->enabled && ! $method->supports( 'shipping-zones' ) ) {
$method_count++;
++$method_count;
}
}
}
@@ -2264,7 +2290,7 @@ function wc_get_var( &$var, $default = null ) {
*/
function wc_enable_wc_plugin_headers( $headers ) {
if ( ! class_exists( 'WC_Plugin_Updates' ) ) {
include_once dirname( __FILE__ ) . '/admin/plugin-updates/class-wc-plugin-updates.php';
include_once __DIR__ . '/admin/plugin-updates/class-wc-plugin-updates.php';
}
// WC requires at least - allows developers to define which version of WooCommerce the plugin requires to run.
@@ -2299,7 +2325,7 @@ function wc_prevent_dangerous_auto_updates( $should_update, $plugin ) {
}
if ( ! class_exists( 'WC_Plugin_Updates' ) ) {
include_once dirname( __FILE__ ) . '/admin/plugin-updates/class-wc-plugin-updates.php';
include_once __DIR__ . '/admin/plugin-updates/class-wc-plugin-updates.php';
}
$new_version = wc_clean( $plugin->new_version );

View File

@@ -119,26 +119,32 @@ function wc_get_endpoint_url( $endpoint, $value = '', $permalink = '' ) {
}
/**
* Hide menu items conditionally.
* Hide or adjust menu items conditionally.
*
* @param array $items Navigation items.
* @return array
*/
function wc_nav_menu_items( $items ) {
if ( ! is_user_logged_in() ) {
$customer_logout = get_option( 'woocommerce_logout_endpoint', 'customer-logout' );
$logout_endpoint = get_option( 'woocommerce_logout_endpoint', 'customer-logout' );
if ( ! empty( $customer_logout ) && ! empty( $items ) && is_array( $items ) ) {
foreach ( $items as $key => $item ) {
if ( empty( $item->url ) ) {
continue;
}
$path = wp_parse_url( $item->url, PHP_URL_PATH ) ?? '';
$query = wp_parse_url( $item->url, PHP_URL_QUERY ) ?? '';
if ( ! empty( $logout_endpoint ) && ! empty( $items ) && is_array( $items ) ) {
foreach ( $items as $key => $item ) {
if ( empty( $item->url ) ) {
continue;
}
if ( strstr( $path, $customer_logout ) || strstr( $query, $customer_logout ) ) {
unset( $items[ $key ] );
}
$path = wp_parse_url( $item->url, PHP_URL_PATH ) ?? '';
$query = wp_parse_url( $item->url, PHP_URL_QUERY ) ?? '';
$is_logout_link = strstr( $path, $logout_endpoint ) || strstr( $query, $logout_endpoint );
if ( ! $is_logout_link ) {
continue;
}
if ( is_user_logged_in() ) {
$items[ $key ]->url = wp_nonce_url( $item->url, 'customer-logout' );
} else {
unset( $items[ $key ] );
}
}
}
@@ -147,6 +153,40 @@ function wc_nav_menu_items( $items ) {
}
add_filter( 'wp_nav_menu_objects', 'wc_nav_menu_items', 10 );
/**
* Hide menu items in navigation blocks conditionally.
*
* Does the same thing as wc_nav_menu_items but for block themes.
*
* @since 9.3.0
* @param \WP_Block_list $inner_blocks Inner blocks.
* @return \WP_Block_list
*/
function wc_nav_menu_inner_blocks( $inner_blocks ) {
$logout_endpoint = get_option( 'woocommerce_logout_endpoint', 'customer-logout' );
if ( ! empty( $logout_endpoint ) && $inner_blocks ) {
foreach ( $inner_blocks as $inner_block_key => $inner_block ) {
$url = $inner_block->parsed_block['attrs']['url'] ?? '';
$path = wp_parse_url( $url, PHP_URL_PATH ) ?? '';
$query = wp_parse_url( $url, PHP_URL_QUERY ) ?? '';
$is_logout_link = strstr( $path, $logout_endpoint ) || strstr( $query, $logout_endpoint );
if ( ! $is_logout_link ) {
continue;
}
if ( is_user_logged_in() ) {
$inner_block->parsed_block['attrs']['url'] = wp_nonce_url( $inner_block->parsed_block['attrs']['url'], 'customer-logout' );
} else {
unset( $inner_blocks[ $inner_block_key ] );
}
}
}
return $inner_blocks;
}
add_filter( 'block_core_navigation_render_inner_blocks', 'wc_nav_menu_inner_blocks' );
/**
* Fix active class in nav for shop page.

View File

@@ -42,8 +42,12 @@ function wc_update_product_stock( $product, $stock_quantity = null, $operation =
// Fire actions to let 3rd parties know the stock is about to be changed.
if ( $product_with_stock->is_type( 'variation' ) ) {
// phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
/** This action is documented in includes/data-stores/class-wc-product-data-store-cpt.php */
do_action( 'woocommerce_variation_before_set_stock', $product_with_stock );
} else {
// phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
/** This action is documented in includes/data-stores/class-wc-product-data-store-cpt.php */
do_action( 'woocommerce_product_before_set_stock', $product_with_stock );
}
@@ -60,8 +64,12 @@ function wc_update_product_stock( $product, $stock_quantity = null, $operation =
// Fire actions to let 3rd parties know the stock changed.
if ( $product_with_stock->is_type( 'variation' ) ) {
// phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
/** This action is documented in includes/data-stores/class-wc-product-data-store-cpt.php */
do_action( 'woocommerce_variation_set_stock', $product_with_stock );
} else {
// phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
/** This action is documented in includes/data-stores/class-wc-product-data-store-cpt.php */
do_action( 'woocommerce_product_set_stock', $product_with_stock );
}
@@ -234,19 +242,23 @@ function wc_trigger_stock_change_notifications( $order, $changes ) {
return;
}
$order_notes = array();
$no_stock_amount = absint( get_option( 'woocommerce_notify_no_stock_amount', 0 ) );
$order_notes = array();
foreach ( $changes as $change ) {
$order_notes[] = $change['product']->get_formatted_name() . ' ' . $change['from'] . '&rarr;' . $change['to'];
$low_stock_amount = absint( wc_get_low_stock_amount( wc_get_product( $change['product']->get_id() ) ) );
if ( $change['to'] <= $no_stock_amount ) {
do_action( 'woocommerce_no_stock', wc_get_product( $change['product']->get_id() ) );
} elseif ( $change['to'] <= $low_stock_amount ) {
do_action( 'woocommerce_low_stock', wc_get_product( $change['product']->get_id() ) );
}
$order_notes[] = $change['product']->get_formatted_name() . ' ' . $change['from'] . '&rarr;' . $change['to'];
if ( $change['to'] < 0 ) {
/**
* Action fires when an item in an order is backordered.
*
* @since 3.0
*
* @param array $args {
* @type WC_Product $product The product that is on backorder.
* @type int $order_id The ID of the order.
* @type int|float $quantity The amount of product on backorder.
* }
*/
do_action(
'woocommerce_product_on_backorder',
array(
@@ -261,6 +273,48 @@ function wc_trigger_stock_change_notifications( $order, $changes ) {
$order->add_order_note( __( 'Stock levels reduced:', 'woocommerce' ) . ' ' . implode( ', ', $order_notes ) );
}
/**
* Check if a product's stock quantity has reached certain thresholds and trigger appropriate actions.
*
* This functionality was moved out of `wc_trigger_stock_change_notifications` in order to decouple it from orders,
* since stock quantity can also be updated in other ways.
*
* @param WC_Product $product The product whose stock level has changed.
*
* @return void
*/
function wc_trigger_stock_change_actions( $product ) {
if ( true !== $product->get_manage_stock() ) {
return;
}
$no_stock_amount = absint( get_option( 'woocommerce_notify_no_stock_amount', 0 ) );
$low_stock_amount = absint( wc_get_low_stock_amount( $product ) );
$stock_quantity = $product->get_stock_quantity();
if ( $stock_quantity <= $no_stock_amount ) {
/**
* Action fires when a product's stock quantity reaches the "no stock" threshold.
*
* @since 3.0
*
* @param WC_Product $product The product whose stock quantity has changed.
*/
do_action( 'woocommerce_no_stock', $product );
} elseif ( $stock_quantity <= $low_stock_amount ) {
/**
* Action fires when a product's stock quantity reaches the "low stock" threshold.
*
* @since 3.0
*
* @param WC_Product $product The product whose stock quantity has changed.
*/
do_action( 'woocommerce_low_stock', $product );
}
}
add_action( 'woocommerce_variation_set_stock', 'wc_trigger_stock_change_actions' );
add_action( 'woocommerce_product_set_stock', 'wc_trigger_stock_change_actions' );
/**
* Increase stock levels for items within an order.
*
@@ -431,8 +485,11 @@ function wc_get_low_stock_amount( WC_Product $product ) {
$low_stock_amount = $product->get_low_stock_amount();
if ( '' === $low_stock_amount && $product->is_type( 'variation' ) ) {
$product = wc_get_product( $product->get_parent_id() );
$low_stock_amount = $product->get_low_stock_amount();
$parent_product = wc_get_product( $product->get_parent_id() );
if ( $parent_product instanceof WC_Product ) {
$low_stock_amount = $parent_product->get_low_stock_amount();
}
}
if ( '' === $low_stock_amount ) {

View File

@@ -31,18 +31,18 @@ function wc_template_redirect() {
if ( is_page( wc_get_page_id( 'checkout' ) ) && wc_get_page_id( 'checkout' ) !== wc_get_page_id( 'cart' ) && WC()->cart->is_empty() && empty( $wp->query_vars['order-pay'] ) && ! isset( $wp->query_vars['order-received'] ) && ! is_customize_preview() && apply_filters( 'woocommerce_checkout_redirect_empty_cart', true ) ) {
wp_safe_redirect( wc_get_cart_url() );
exit;
}
// Logout.
if ( isset( $wp->query_vars['customer-logout'] ) && ! empty( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'customer-logout' ) ) {
wp_safe_redirect( str_replace( '&amp;', '&', wp_logout_url( apply_filters( 'woocommerce_logout_default_redirect_url', wc_get_page_permalink( 'myaccount' ) ) ) ) );
exit;
}
// Redirect to the correct logout endpoint.
if ( isset( $wp->query_vars['customer-logout'] ) && 'true' === $wp->query_vars['customer-logout'] ) {
wp_safe_redirect( esc_url_raw( wc_get_account_endpoint_url( 'customer-logout' ) ) );
// Logout endpoint under My Account page. Logging out requires a valid nonce.
if ( isset( $wp->query_vars['customer-logout'] ) ) {
if ( ! empty( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'customer-logout' ) ) {
wp_logout();
wp_safe_redirect( wc_get_logout_redirect_url() );
exit;
}
/* translators: %s: logout url */
wc_add_notice( sprintf( __( 'Are you sure you want to log out? <a href="%s">Confirm and log out</a>', 'woocommerce' ), wc_logout_url() ) );
wp_safe_redirect( wc_get_page_permalink( 'myaccount' ) );
exit;
}
@@ -240,8 +240,8 @@ function wc_set_loop_prop( $prop, $value = '' ) {
* Set the current visbility for a product in the woocommerce_loop global.
*
* @since 4.4.0
* @param int $product_id Product it to cache visbiility for.
* @param bool $value The poduct visibility value to cache.
* @param int $product_id Product it to cache visibility for.
* @param bool $value The product visibility value to cache.
*/
function wc_set_loop_product_visibility( $product_id, $value ) {
wc_set_loop_prop( "product_visibility_$product_id", $value );
@@ -1387,6 +1387,10 @@ if ( ! function_exists( 'woocommerce_template_loop_add_to_cart' ) ) {
),
);
if ( is_a( $product, 'WC_Product_Simple' ) ) {
$defaults['attributes']['data-success_message'] = $product->add_to_cart_success_message();
}
$args = apply_filters( 'woocommerce_loop_add_to_cart_args', wp_parse_args( $args, $defaults ), $product );
if ( ! empty( $args['attributes']['aria-describedby'] ) ) {
@@ -2854,6 +2858,12 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
}
if ( $args['required'] ) {
// hidden inputs are the only kind of inputs that don't need an `aria-required` attribute.
// checkboxes apply the `custom_attributes` to the label - we need to apply the attribute on the input itself, instead.
if ( ! in_array( $args['type'], array( 'hidden', 'checkbox' ), true ) ) {
$args['custom_attributes']['aria-required'] = 'true';
}
$args['class'][] = 'validate-required';
$required = '&nbsp;<abbr class="required" title="' . esc_attr__( 'required', 'woocommerce' ) . '">*</abbr>';
} else {
@@ -2978,12 +2988,13 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
}
$field .= sprintf(
'<input type="checkbox" name="%1$s" id="%2$s" value="%3$s" class="%4$s" %5$s /> %6$s',
'<input type="checkbox" name="%1$s" id="%2$s" value="%3$s" class="%4$s" %5$s%6$s /> %7$s',
esc_attr( $key ),
esc_attr( $args['id'] ),
esc_attr( $args['checked_value'] ),
esc_attr( 'input-checkbox ' . implode( ' ', $args['input_class'] ) ),
checked( $value, $args['checked_value'], false ),
$args['required'] ? ' aria-required="true"' : '',
wp_kses_post( $args['label'] )
);
@@ -3728,22 +3739,31 @@ function wc_get_price_html_from_text() {
}
/**
* Get logout endpoint.
* Get the redirect URL after logging out. Defaults to the my account page.
*
* @since 9.3.0
* @return string
*/
function wc_get_logout_redirect_url() {
/**
* Filters the logout redirect URL.
*
* @since 2.6.9
* @param string $logout_url Logout URL.
* @return string
*/
return apply_filters( 'woocommerce_logout_default_redirect_url', wc_get_page_permalink( 'myaccount' ) );
}
/**
* Get logout link.
*
* @since 2.6.9
*
* @param string $redirect Redirect URL.
*
* @return string
*/
function wc_logout_url( $redirect = '' ) {
$redirect = $redirect ? $redirect : apply_filters( 'woocommerce_logout_default_redirect_url', wc_get_page_permalink( 'myaccount' ) );
if ( get_option( 'woocommerce_logout_endpoint' ) ) {
return wp_nonce_url( wc_get_endpoint_url( 'customer-logout', '', $redirect ), 'customer-logout' );
}
return wp_logout_url( $redirect );
return wp_logout_url( $redirect ? $redirect : wc_get_logout_redirect_url() );
}
/**

View File

@@ -2779,14 +2779,16 @@ function wc_update_920_add_wc_hooked_blocks_version_option() {
function wc_update_910_remove_obsolete_user_meta() {
global $wpdb;
$deletions = $wpdb->query( "
$deletions = $wpdb->query(
"
DELETE FROM $wpdb->usermeta
WHERE meta_key IN (
'_last_order',
'_order_count',
'_money_spent'
)
" );
"
);
$logger = wc_get_logger();
@@ -2815,3 +2817,38 @@ function wc_update_910_remove_obsolete_user_meta() {
);
}
}
/**
* Add woocommerce_coming_soon option when it is not currently present.
*/
function wc_update_930_add_woocommerce_coming_soon_option() {
add_option( 'woocommerce_coming_soon', 'no' );
}
/**
* Migrate Launch Your Store tour meta keys to the woocommerce_meta user data fields.
*/
function wc_update_930_migrate_user_meta_for_launch_your_store_tour() {
// Rename `woocommerce_launch_your_store_tour_hidden` meta key to `woocommerce_admin_launch_your_store_tour_hidden`.
global $wpdb;
$wpdb->query(
$wpdb->prepare(
"UPDATE {$wpdb->usermeta}
SET meta_key = %s
WHERE meta_key = %s",
'woocommerce_admin_launch_your_store_tour_hidden',
'woocommerce_launch_your_store_tour_hidden'
)
);
// Rename `woocommerce_coming_soon_banner_dismissed` meta key to `woocommerce_admin_coming_soon_banner_dismissed`.
$wpdb->query(
$wpdb->prepare(
"UPDATE {$wpdb->usermeta}
SET meta_key = %s
WHERE meta_key = %s",
'woocommerce_admin_coming_soon_banner_dismissed',
'woocommerce_coming_soon_banner_dismissed'
)
);
}

View File

@@ -55,7 +55,7 @@ class WC_REST_WCCOM_Site_SSR_Controller extends WC_REST_WCCOM_Site_Controller {
}
/**
* Generate SSR data and submit it to WooCommmerce.com.
* Generate SSR data and submit it to WooCommerce.com.
*
* @since 7.8.0
* @param WP_REST_Request $request Full details about the request.