Plugin Updates

This commit is contained in:
Tony Volpe
2024-04-02 20:23:21 +00:00
parent 96800520e8
commit 94170ec2c4
1514 changed files with 133309 additions and 105985 deletions

View File

@@ -1365,8 +1365,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
} else {
// If we do not have a coupon ID (was it virtual? has it been deleted?) we must create a temporary coupon using what data we have stored during checkout.
$coupon_object = new WC_Coupon();
$coupon_object->set_props( (array) $coupon_item->get_meta( 'coupon_data', true ) );
$coupon_object = $this->get_temporary_coupon( $coupon_item );
$coupon_object->set_code( $coupon_code );
$coupon_object->set_virtual( true );
@@ -1402,6 +1401,35 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$this->calculate_totals( true );
}
/**
* Get a coupon object populated from order line item metadata, to be used when reapplying coupons
* if the original coupon no longer exists.
*
* @since 8.7.0
*
* @param WC_Order_Item_Coupon $coupon_item The order item corresponding to the coupon to reapply.
* @returns WC_Coupon Coupon object populated from order line item metadata, or empty if no such metadata exists (should never happen).
*/
private function get_temporary_coupon( WC_Order_Item_Coupon $coupon_item ): WC_Coupon {
$coupon_object = new WC_Coupon();
// Since WooCommerce 8.7 a succint 'coupon_info' line item meta entry is created
// whenever a coupon is applied to an order. Previously a more verbose 'coupon_data' was created.
$coupon_info = $coupon_item->get_meta( 'coupon_info', true );
if ( $coupon_info ) {
$coupon_object->set_short_info( $coupon_info );
return $coupon_object;
}
$coupon_data = $coupon_item->get_meta( 'coupon_data', true );
if ( $coupon_data ) {
$coupon_object->set_props( (array) $coupon_data );
}
return $coupon_object;
}
/**
* After applying coupons via the WC_Discounts class, update line items.
*
@@ -1461,11 +1489,8 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$coupon_id = wc_get_coupon_id_by_code( $coupon_code );
$coupon = new WC_Coupon( $coupon_id );
// Avoid storing used_by - it's not needed and can get large.
$coupon_data = $coupon->get_data();
unset( $coupon_data['used_by'] );
$coupon_item->add_meta_data( 'coupon_data', $coupon_data );
$coupon_info = $coupon->get_short_info();
$coupon_item->add_meta_data( 'coupon_info', $coupon_info );
} else {
$coupon_item = $this->get_item( $item_id, false );
}

View File

@@ -79,31 +79,19 @@ class WC_Admin_Addons {
* @return array|WP_Error
*/
public static function fetch_featured() {
$transient_name = 'wc_addons_featured';
// Important: WCCOM Extensions API v2.0 is used.
$url = 'https://woocommerce.com/wp-json/wccom-extensions/2.0/featured';
$locale = get_user_locale();
$featured = self::get_locale_data_from_transient( 'wc_addons_featured', $locale );
$featured = self::get_locale_data_from_transient( $transient_name, $locale );
if ( false === $featured ) {
$headers = array();
$auth = WC_Helper_Options::get( 'auth' );
if ( ! empty( $auth['access_token'] ) ) {
$headers['Authorization'] = 'Bearer ' . $auth['access_token'];
}
$parameter_string = '?' . http_build_query( array( 'locale' => get_user_locale() ) );
$country = WC()->countries->get_base_country();
if ( ! empty( $country ) ) {
$parameter_string = $parameter_string . '&' . http_build_query( array( 'country' => $country ) );
}
// Important: WCCOM Extensions API v2.0 is used.
$raw_featured = wp_safe_remote_get(
'https://woocommerce.com/wp-json/wccom-extensions/2.0/featured' . $parameter_string,
array(
'headers' => $headers,
'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
)
$fetch_options = array(
'auth' => true,
'locale' => true,
'country' => true,
);
$raw_featured = self::fetch( $url, $fetch_options );
if ( is_wp_error( $raw_featured ) ) {
do_action( 'woocommerce_page_wc-addons_connection_error', $raw_featured->get_error_message() );
@@ -143,7 +131,7 @@ class WC_Admin_Addons {
}
if ( $featured ) {
self::set_locale_data_in_transient( 'wc_addons_featured', $featured, $locale, DAY_IN_SECONDS );
self::set_locale_data_in_transient( $transient_name, $featured, $locale, DAY_IN_SECONDS );
}
}
@@ -1556,4 +1544,49 @@ class WC_Admin_Addons {
$transient_value[ $locale ] = $value;
return set_transient( $transient, $transient_value, $expiration );
}
/**
* Make wp_safe_remote_get request to Woo.com endpoint.
* Optionally pass user auth token, locale or country.
*
* @param string $url URL to request.
* @param ?array $options Options for the request. For example, to pass auth token, locale and country,
* pass array( 'auth' => true, 'locale' => true, 'country' => true, ).
*
* @return array|WP_Error
*/
public static function fetch( $url, $options = array() ) {
$headers = array();
if ( isset( $options['auth'] ) && $options['auth'] ) {
$auth = WC_Helper_Options::get( 'auth' );
if ( isset( $auth['access_token'] ) && ! empty( $auth['access_token'] ) ) {
$headers['Authorization'] = 'Bearer ' . $auth['access_token'];
}
}
$parameters = array();
if ( isset( $options['locale'] ) && $options['locale'] ) {
$parameters['locale'] = get_user_locale();
}
if ( isset( $options['country'] ) && $options['country'] ) {
$country = WC()->countries->get_base_country();
if ( ! empty( $country ) ) {
$parameters['country'] = $country;
}
}
$query_string = ! empty( $parameters ) ? '?' . http_build_query( $parameters ) : '';
return wp_safe_remote_get(
$url . $query_string,
array(
'headers' => $headers,
'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
)
);
}
}

View File

@@ -554,6 +554,22 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
);
wp_enqueue_script( 'marketplace-suggestions' );
}
// Marketplace promotions.
if ( in_array( $screen_id, array( 'woocommerce_page_wc-admin' ), true ) ) {
$promotions = get_transient( WC_Admin_Marketplace_Promotions::TRANSIENT_NAME );
if ( false === $promotions ) {
return;
}
wp_add_inline_script(
'wc-admin-app',
'window.wcMarketplace = ' . wp_json_encode( array( 'promotions' => $promotions ) ),
'before'
);
}
}
/**

View File

@@ -293,11 +293,11 @@ class WC_Admin_Importers {
'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' => count( $results['imported'] ),
'imported_variations' => count( $results['imported_variations'] ),
'failed' => count( $results['failed'] ),
'updated' => count( $results['updated'] ),
'skipped' => count( $results['skipped'] ),
'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 {
@@ -305,11 +305,11 @@ class WC_Admin_Importers {
array(
'position' => $importer->get_file_position(),
'percentage' => $percent_complete,
'imported' => count( $results['imported'] ),
'imported_variations' => count( $results['imported_variations'] ),
'failed' => count( $results['failed'] ),
'updated' => count( $results['updated'] ),
'skipped' => count( $results['skipped'] ),
'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,
)
);
}

View File

@@ -0,0 +1,288 @@
<?php
/**
* Addons Page
*
* @package WooCommerce\Admin
* @version 2.5.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Admin_Marketplace_Promotions class.
*/
class WC_Admin_Marketplace_Promotions {
const TRANSIENT_NAME = 'woocommerce_marketplace_promotions';
const SCHEDULED_ACTION_HOOK = 'woocommerce_marketplace_fetch_promotions';
/**
* The user's locale, for example en_US.
*
* @var string
*/
public static string $locale;
/**
* On all admin pages, schedule an action to fetch promotions data.
* Add menu badge to WooCommerce Extensions item if the promotions
* API requests one.
*
* @return void
*/
public static function init_marketplace_promotions() {
// Add the callback for our scheduled action.
if ( ! has_action( self::SCHEDULED_ACTION_HOOK, array( __CLASS__, 'fetch_marketplace_promotions' ) ) ) {
add_action( self::SCHEDULED_ACTION_HOOK, array( __CLASS__, 'fetch_marketplace_promotions' ) );
}
if ( self::is_admin_page() ) {
// Schedule the action twice a day, starting now.
if ( false === wp_next_scheduled( self::SCHEDULED_ACTION_HOOK ) ) {
wp_schedule_event( time(), 'twicedaily', self::SCHEDULED_ACTION_HOOK );
}
self::$locale = ( self::$locale ?? get_user_locale() ) ?? 'en_US';
self::maybe_show_bubble_promotions();
}
register_deactivation_hook( WC_PLUGIN_FILE, array( __CLASS__, 'clear_scheduled_event' ) );
}
/**
* Check if the request is for an admin page, and not ajax.
* We may want to add a menu bubble to WooCommerce Extensions
* on any admin page, as the user may view the WooCommerce flyout
* menu.
*
* @return bool
*/
private static function is_admin_page(): bool {
if (
( defined( 'DOING_AJAX' ) && DOING_AJAX )
|| ! is_admin()
) {
return false;
}
return true;
}
/**
* Get promotions to show in the Woo in-app marketplace.
* Only run on selected pages in the main WooCommerce menu in wp-admin.
* Loads promotions in transient with one day life.
*
* @return void
*/
public static function fetch_marketplace_promotions() {
$url = 'https://woo.com/wp-json/wccom-extensions/3.0/promotions';
$promotions = get_transient( self::TRANSIENT_NAME );
if ( false !== $promotions ) {
return;
}
$fetch_options = array(
'auth' => true,
'country' => true,
);
$raw_promotions = WC_Admin_Addons::fetch( $url, $fetch_options );
// phpcs:disable WordPress.NamingConventions.ValidHookName.UseUnderscores
if ( is_wp_error( $raw_promotions ) ) {
/**
* Allows connection error to be handled.
*
* @since 8.7
*/
do_action( 'woocommerce_page_wc-addons_connection_error', $raw_promotions->get_error_message() );
}
$response_code = (int) wp_remote_retrieve_response_code( $raw_promotions );
if ( 200 !== $response_code ) {
/**
* Allows connection error to be handled.
*
* @since 8.7
*/
do_action( 'woocommerce_page_wc-addons_connection_error', $response_code );
}
$promotions = json_decode( wp_remote_retrieve_body( $raw_promotions ), true );
if ( empty( $promotions ) || ! is_array( $promotions ) ) {
/**
* Allows connection error to be handled.
*
* @since 8.7
*/
do_action( 'woocommerce_page_wc-addons_connection_error', 'Empty or malformed response' );
}
// phpcs:enable WordPress.NamingConventions.ValidHookName.UseUnderscores
if ( $promotions ) {
// Filter out any expired promotions.
$promotions = self::get_active_promotions( $promotions );
set_transient( self::TRANSIENT_NAME, $promotions, DAY_IN_SECONDS );
}
}
/**
* If there's an active promotion of the format `menu_bubble`,
* add a filter to show a bubble on the Extensions item in the
* WooCommerce menu.
*
* @return void
* @throws Exception If we are unable to create a DateTime from the date_to_gmt.
*/
private static function maybe_show_bubble_promotions() {
$promotions = get_transient( self::TRANSIENT_NAME );
if ( ! $promotions ) {
return;
}
$bubble_promotions = self::get_promotions_of_format( $promotions, 'menu_bubble' );
if ( empty( $bubble_promotions ) ) {
return;
}
$now_date_time = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
// Let's make absolutely sure the promotion is still active.
foreach ( $bubble_promotions as $promotion ) {
if ( ! isset( $promotion['date_to_gmt'] ) ) {
continue;
}
try {
$date_to_gmt = new DateTime( $promotion['date_to_gmt'], new DateTimeZone( 'UTC' ) );
} catch ( \Exception $ex ) {
continue;
}
if ( $now_date_time < $date_to_gmt ) {
add_filter(
'woocommerce_marketplace_menu_items',
function ( $marketplace_pages ) use ( $promotion ) {
return self::filter_marketplace_menu_items( $marketplace_pages, $promotion );
}
);
break;
}
}
}
/**
* 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.
*
* @return array
*/
private static function get_promotions_of_format( $promotions = array(), $format = '' ): array {
if ( empty( $promotions ) || empty( $format ) ) {
return array();
}
return array_filter(
$promotions,
function( $promotion ) use ( $format ) {
return isset( $promotion['format'] ) && $format === $promotion['format'];
}
);
}
/**
* Find promotions that are still active they have a date range that
* includes the current date.
*
* @param ?array $promotions Data about current promotions.
*
* @return array
*/
private static function get_active_promotions( $promotions = array() ) {
$now_date_time = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
$active_promotions = array();
foreach ( $promotions as $promotion ) {
if ( ! isset( $promotion['date_from_gmt'] ) || ! isset( $promotion['date_to_gmt'] ) ) {
continue;
}
try {
$date_from_gmt = new DateTime( $promotion['date_from_gmt'], new DateTimeZone( 'UTC' ) );
$date_to_gmt = new DateTime( $promotion['date_to_gmt'], new DateTimeZone( 'UTC' ) );
} catch ( \Exception $ex ) {
continue;
}
if ( $now_date_time >= $date_from_gmt && $now_date_time <= $date_to_gmt ) {
$active_promotions[] = $promotion;
}
}
// Sort promotions so the ones starting more recently are at the top.
usort(
$active_promotions,
function ( $a, $b ) {
return $b['date_from_gmt'] <=> $a['date_from_gmt'];
}
);
return $active_promotions;
}
/**
* Callback for the `woocommerce_marketplace_menu_items` filter
* in `Automattic\WooCommerce\Internal\Admin\Marketplace::get_marketplace_pages`.
* At the moment, the Extensions page is the only page in `$menu_items`.
* Adds a bubble to the menu item.
*
* @param array $menu_items Arrays representing items in nav menu.
* @param ?array $promotion Data about a promotion from the Woo.com API.
*
* @return array
*/
public static function filter_marketplace_menu_items( $menu_items, $promotion = array() ) {
if ( ! isset( $promotion['menu_item_id'] ) || ! isset( $promotion['content'] ) ) {
return $menu_items;
}
foreach ( $menu_items as $index => $menu_item ) {
if (
'woocommerce' === $menu_item['parent']
&& $promotion['menu_item_id'] === $menu_item['id']
) {
$bubble_text = $promotion['content'][ self::$locale ] ?? ( $promotion['content']['en_US'] ?? __( 'Sale', 'woocommerce' ) );
$menu_items[ $index ]['title'] = $menu_item['title'] . self::append_bubble( $bubble_text );
break;
}
}
return $menu_items;
}
/**
* Return the markup for a menu item bubble with a given text.
*
* @param string $bubble_text Text of bubble.
*
* @return string
*/
private static function append_bubble( $bubble_text ) {
return ' <span class="awaiting-mod update-plugins remaining-tasks-badge woocommerce-task-list-remaining-tasks-badge">' . esc_html( $bubble_text ) . '</span>';
}
/**
* When WooCommerce is deactivated, clear the scheduled action.
*
* @return void
*/
public static function clear_scheduled_event() {
$timestamp = wp_next_scheduled( self::SCHEDULED_ACTION_HOOK );
wp_unschedule_event( $timestamp, self::SCHEDULED_ACTION_HOOK );
}
}

View File

@@ -283,7 +283,7 @@ class WC_Admin_Meta_Boxes {
* @return string[] Templates array excluding block-based templates.
*/
public function remove_block_templates( $templates ) {
if ( count( $templates ) === 0 || ! wc_current_theme_is_fse_theme() || ( ! function_exists( 'gutenberg_get_block_template' ) && ! function_exists( 'get_block_template' ) ) ) {
if ( count( $templates ) === 0 || ! wc_current_theme_is_fse_theme() ) {
return $templates;
}
@@ -296,9 +296,7 @@ class WC_Admin_Meta_Boxes {
continue;
}
$block_template = function_exists( 'gutenberg_get_block_template' ) ?
gutenberg_get_block_template( $theme . '//' . $template_key ) :
get_block_template( $theme . '//' . $template_key );
$block_template = get_block_template( $theme . '//' . $template_key );
// If the block template has the product post type specified, include it.
if ( $block_template && is_array( $block_template->post_types ) && in_array( 'product', $block_template->post_types ) ) {

View File

@@ -305,6 +305,8 @@ class WC_Admin_Notices {
return;
}
require_once WC_ABSPATH . 'includes/admin/wc-admin-functions.php';
$screen = get_current_screen();
$screen_id = $screen ? $screen->id : '';
$show_on_screens = array(

View File

@@ -192,7 +192,7 @@ class WC_Admin_Webhooks {
$webhook_id = absint( $_GET['delete'] );
if ( $webhook_id ) {
$this->bulk_delete( array( $webhook_id ) );
self::bulk_delete( array( $webhook_id ) );
}
}
}

View File

@@ -39,6 +39,9 @@ class WC_Admin {
if ( isset( $_GET['page'] ) && 'wc-addons' === $_GET['page'] ) {
add_filter( 'admin_body_class', array( 'WC_Admin_Addons', 'filter_admin_body_classes' ) );
}
// Fetch list of promotions from Woo.com for WooCommerce admin UI. We need to fire earlier than admin_init so we can filter menu items.
add_action( 'woocommerce_init', array( 'WC_Admin_Marketplace_Promotions', 'init_marketplace_promotions' ) );
}
/**
@@ -77,6 +80,9 @@ class WC_Admin {
// Marketplace suggestions & related REST API.
include_once __DIR__ . '/marketplace-suggestions/class-wc-marketplace-suggestions.php';
include_once __DIR__ . '/marketplace-suggestions/class-wc-marketplace-updater.php';
// Marketplace promotions.
include_once __DIR__ . '/class-wc-admin-marketplace-promotions.php';
}
/**

View File

@@ -408,7 +408,7 @@ class WC_Helper {
}
$filters = array_fill_keys( array_keys( self::get_filters() ), 0 );
if ( empty( $subscriptions ) ) {
if ( ! is_array( $subscriptions ) || empty( $subscriptions ) ) {
return array();
}
@@ -1461,7 +1461,7 @@ class WC_Helper {
if ( is_readable( $txt ) ) {
$txt = file_get_contents( $txt );
$txt = preg_split( '#\s#', $txt );
if ( count( $txt ) >= 2 ) {
if ( is_array( $txt ) && count( $txt ) >= 2 ) {
$header = sprintf( '%d:%s', $txt[0], $txt[1] );
}
}

View File

@@ -146,8 +146,8 @@ class WC_Tax_Rate_Importer extends WP_Importer {
if ( false !== $handle ) {
$header = fgetcsv( $handle, 0, $this->delimiter );
if ( 10 === count( $header ) ) {
$count = is_countable( $header ) ? count( $header ) : 0;
if ( 10 === $count ) {
$row = fgetcsv( $handle, 0, $this->delimiter );

View File

@@ -80,7 +80,7 @@ if ( ! defined( 'ABSPATH' ) ) {
</thead>
<tbody>
<?php
if ( count( $errors ) ) {
if ( is_array( $errors ) && count( $errors ) ) {
foreach ( $errors as $error ) {
if ( ! is_wp_error( $error ) ) {
continue;

View File

@@ -136,8 +136,7 @@ class WC_Admin_List_Table_Coupons extends WC_Admin_List_Table {
*/
protected function render_products_column() {
$product_ids = $this->object->get_product_ids();
if ( count( $product_ids ) > 0 ) {
if ( is_array( $product_ids ) && count( $product_ids ) > 0 ) {
echo esc_html( implode( ', ', $product_ids ) );
} else {
echo '&ndash;';

View File

@@ -15,8 +15,7 @@ $arrow_img_url = WC_ADMIN_IMAGES_FOLDER_URL . '/product_data/no-variati
?>
<div id="variable_product_options" class="panel wc-metaboxes-wrapper hidden">
<div id="variable_product_options_inner">
<?php if ( ! count( $variation_attributes ) ) : ?>
<?php if ( ! isset( $variation_attributes ) || ! is_array( $variation_attributes ) || count( $variation_attributes ) === 0 ) : ?>
<div class="add-attributes-container">
<div class="add-attributes-message">

View File

@@ -343,7 +343,7 @@ class WC_Report_Sales_By_Category extends WC_Admin_Report {
$index = 0;
foreach ( $chart_data as $data ) {
$color = isset( $this->chart_colours[ $index ] ) ? $this->chart_colours[ $index ] : $this->chart_colours[0];
$width = $this->barwidth / sizeof( $chart_data );
$width = $this->barwidth / count( $chart_data );
$offset = ( $width * $index );
$series = $data['data'];

View File

@@ -178,7 +178,7 @@ class WC_Settings_Tax extends WC_Settings_Page {
public function output_tax_rates() {
global $current_section;
$current_class = $this->get_current_tax_class();
$current_class = self::get_current_tax_class();
$countries = array();
foreach ( WC()->countries->get_allowed_countries() as $value => $label ) {
@@ -320,7 +320,7 @@ class WC_Settings_Tax extends WC_Settings_Page {
// phpcs:disable WordPress.Security.NonceVerification.Missing -- this is called via "do_action('woocommerce_settings_save_'...") in base class, where nonce is verified first.
global $wpdb;
$current_class = sanitize_title( $this->get_current_tax_class() );
$current_class = sanitize_title( self::get_current_tax_class() );
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.NonceVerification.Missing
$posted_countries = wc_clean( wp_unslash( $_POST['tax_rate_country'] ) );

View File

@@ -152,7 +152,7 @@ if ( ! defined( 'ABSPATH' ) ) {
<div>
<button id="btn-back" class="button button-large wc-backbone-modal-back-{{ data.status === 'new' ? 'active' : 'inactive' }}"><?php esc_html_e( 'Back', 'woocommerce' ); ?></button>
<button id="btn-ok" data-status='{{ data.status }}' class="button button-primary button-large">
<div class="wc-backbone-modal-action-{{ data.status === 'new' ? 'active' : 'inactive' }}"><?php esc_html_e( 'Create', 'woocommerce' ); ?></div>
<div class="wc-backbone-modal-action-{{ data.status === 'new' ? 'active' : 'inactive' }}"><?php esc_html_e( 'Create and save', 'woocommerce' ); ?></div>
<div class="wc-backbone-modal-action-{{ data.status === 'existing' ? 'active' : 'inactive' }}"><?php esc_html_e( 'Save', 'woocommerce' ); ?></div>
</button>
</div>

View File

@@ -21,9 +21,14 @@ $dropins_mu_plugins = $report['dropins_mu_plugins'];
$theme = $report['theme'];
$security = $report['security'];
$settings = $report['settings'];
$logging = $report['logging'];
$wp_pages = $report['pages'];
$plugin_updates = new WC_Plugin_Updates();
$untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' ) );
$active_plugins_count = is_countable( $active_plugins ) ? count( $active_plugins ) : 0;
$inactive_plugins_count = is_countable( $inactive_plugins ) ? count( $inactive_plugins ) : 0;
?>
<div class="updated woocommerce-message inline">
<p>
@@ -87,26 +92,6 @@ $untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, Cons
?>
</td>
</tr>
<tr>
<td data-export-label="WC Blocks Version"><?php esc_html_e( 'WooCommerce Blocks package', 'woocommerce' ); ?>:</td>
<td class="help"><?php echo wc_help_tip( esc_html__( 'The WooCommerce Blocks package running on your site.', 'woocommerce' ) ); ?></td>
<td>
<?php
if ( class_exists( '\Automattic\WooCommerce\Blocks\Package' ) ) {
$version = \Automattic\WooCommerce\Blocks\Package::get_version();
$path = \Automattic\WooCommerce\Blocks\Package::get_path(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
} else {
$version = null;
}
if ( ! is_null( $version ) ) {
echo '<mark class="yes"><span class="dashicons dashicons-yes"></span> ' . esc_html( $version ) . ' <code class="private">' . esc_html( $path ) . '</code></mark> ';
} else {
echo '<mark class="error"><span class="dashicons dashicons-warning"></span> ' . esc_html__( 'Unable to detect the Blocks package.', 'woocommerce' ) . '</mark>';
}
?>
</td>
</tr>
<tr>
<td data-export-label="Action Scheduler Version"><?php esc_html_e( 'Action Scheduler package', 'woocommerce' ); ?>:</td>
<td class="help"><?php echo wc_help_tip( esc_html__( 'Action Scheduler package running on your site.', 'woocommerce' ) ); ?></td>
@@ -594,7 +579,7 @@ $untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, Cons
<table class="wc_status_table widefat" cellspacing="0">
<thead>
<tr>
<th colspan="3" data-export-label="Active Plugins (<?php echo count( $active_plugins ); ?>)"><h2><?php esc_html_e( 'Active plugins', 'woocommerce' ); ?> (<?php echo count( $active_plugins ); ?>)</h2></th>
<th colspan="3" data-export-label="Active Plugins (<?php echo esc_attr( $active_plugins_count ); ?>)"><h2><?php esc_html_e( 'Active plugins', 'woocommerce' ); ?> (<?php echo esc_attr( $active_plugins_count ); ?>)</h2></th>
</tr>
</thead>
<tbody>
@@ -604,7 +589,7 @@ $untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, Cons
<table class="wc_status_table widefat" cellspacing="0">
<thead>
<tr>
<th colspan="3" data-export-label="Inactive Plugins (<?php echo count( $inactive_plugins ); ?>)"><h2><?php esc_html_e( 'Inactive plugins', 'woocommerce' ); ?> (<?php echo count( $inactive_plugins ); ?>)</h2></th>
<th colspan="3" data-export-label="Inactive Plugins (<?php echo esc_attr( $inactive_plugins_count ); ?>)"><h2><?php esc_html_e( 'Inactive plugins', 'woocommerce' ); ?> (<?php echo esc_attr( $inactive_plugins_count ); ?>)</h2></th>
</tr>
</thead>
<tbody>
@@ -612,12 +597,13 @@ $untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, Cons
</tbody>
</table>
<?php
if ( 0 < count( $dropins_mu_plugins['dropins'] ) ) :
$dropins_count = is_countable( $dropins_mu_plugins['dropins'] ) ? count( $dropins_mu_plugins['dropins'] ) : 0;
if ( 0 < $dropins_count ) :
?>
<table class="wc_status_table widefat" cellspacing="0">
<thead>
<tr>
<th colspan="3" data-export-label="Dropin Plugins (<?php echo count( $dropins_mu_plugins['dropins'] ); ?>)"><h2><?php esc_html_e( 'Dropin Plugins', 'woocommerce' ); ?> (<?php echo count( $dropins_mu_plugins['dropins'] ); ?>)</h2></th>
<th colspan="3" data-export-label="Dropin Plugins (<?php $dropins_count; ?>)"><h2><?php esc_html_e( 'Dropin Plugins', 'woocommerce' ); ?> (<?php $dropins_count; ?>)</h2></th>
</tr>
</thead>
<tbody>
@@ -636,12 +622,14 @@ if ( 0 < count( $dropins_mu_plugins['dropins'] ) ) :
</table>
<?php
endif;
if ( 0 < count( $dropins_mu_plugins['mu_plugins'] ) ) :
$mu_plugins_count = is_countable( $dropins_mu_plugins['mu_plugins'] ) ? count( $dropins_mu_plugins['mu_plugins'] ) : 0;
if ( 0 < $mu_plugins_count ) :
?>
<table class="wc_status_table widefat" cellspacing="0">
<thead>
<tr>
<th colspan="3" data-export-label="Must Use Plugins (<?php echo count( $dropins_mu_plugins['mu_plugins'] ); ?>)"><h2><?php esc_html_e( 'Must Use Plugins', 'woocommerce' ); ?> (<?php echo count( $dropins_mu_plugins['mu_plugins'] ); ?>)</h2></th>
<th colspan="3" data-export-label="Must Use Plugins (<?php echo esc_attr( $mu_plugins_count ); ?>)"><h2><?php esc_html_e( 'Must Use Plugins', 'woocommerce' ); ?> (<?php echo esc_attr( $mu_plugins_count ); ?>)</h2></th>
</tr>
</thead>
<tbody>
@@ -770,6 +758,55 @@ if ( 0 < count( $dropins_mu_plugins['mu_plugins'] ) ) :
</tbody>
</table>
<table class="wc_status_table widefat" cellspacing="0">
<thead>
<tr>
<th colspan="3" data-export-label="Logging"><h2><?php esc_html_e( 'Logging', 'woocommerce' ); ?></h2></th>
</tr>
</thead>
<tbody>
<tr>
<td data-export-label="Enabled"><?php esc_html_e( 'Enabled', 'woocommerce' ); ?></td>
<td class="help"><?php echo wc_help_tip( esc_html__( 'Is logging enabled?', 'woocommerce' ) ); ?></td>
<td><?php echo $logging['logging_enabled'] ? '<mark class="yes"><span class="dashicons dashicons-yes"></span></mark>' : '<mark class="no">&ndash;</mark>'; ?></td>
</tr>
<tr>
<td data-export-label="Handler"><?php esc_html_e( 'Handler', 'woocommerce' ); ?></td>
<td class="help"><?php echo wc_help_tip( esc_html__( 'How log entries are being stored.', 'woocommerce' ) ); ?></td>
<td><?php echo esc_html( $logging['default_handler'] ); ?></td>
</tr>
<tr>
<td data-export-label="Retention period"><?php esc_html_e( 'Retention period', 'woocommerce' ); ?></td>
<td class="help"><?php echo wc_help_tip( esc_html__( 'How many days log entries will be kept before being auto-deleted.', 'woocommerce' ) ); ?></td>
<td>
<?php
printf(
esc_html(
// translators: %s is a number of days.
_n(
'%s day',
'%s days',
$logging['retention_period_days'],
'woocommerce'
)
),
esc_html( number_format_i18n( $logging['retention_period_days'] ) )
);
?>
</td>
</tr>
<tr>
<td data-export-label="Level threshold"><?php esc_html_e( 'Level threshold', 'woocommerce' ); ?></td>
<td class="help"><?php echo wc_help_tip( esc_html__( 'The minimum severity level of logs that will be stored.', 'woocommerce' ) ); ?></td>
<td><?php echo $logging['level_threshold'] ? esc_html( $logging['level_threshold'] ) : '<mark class="no">&ndash;</mark>'; ?></td>
</tr>
<tr>
<td data-export-label="Log directory size"><?php esc_html_e( 'Log directory size', 'woocommerce' ); ?></td>
<td class="help"><?php echo wc_help_tip( esc_html__( 'The total size of the files in the log directory.', 'woocommerce' ) ); ?></td>
<td><?php echo esc_html( $logging['log_directory_size'] ); ?></td>
</tr>
</tbody>
</table>
<table class="wc_status_table widefat" cellspacing="0">
<thead>
<tr>
@@ -929,7 +966,7 @@ if ( 0 < count( $dropins_mu_plugins['mu_plugins'] ) ) :
<td class="help">&nbsp;</td>
<td>
<?php
$total_overrides = count( $theme['overrides'] );
$total_overrides = is_countable( $theme['overrides'] ) ? count( $theme['overrides'] ) : 0;
for ( $i = 0; $i < $total_overrides; $i++ ) {
$override = $theme['overrides'][ $i ];
if ( $override['core_version'] && ( empty( $override['version'] ) || version_compare( $override['version'], $override['core_version'], '<' ) ) ) {
@@ -944,7 +981,8 @@ if ( 0 < count( $dropins_mu_plugins['mu_plugins'] ) ) :
} else {
echo esc_html( $override['file'] );
}
if ( ( count( $theme['overrides'] ) - 1 ) !== $i ) {
if ( ( $total_overrides - 1 ) !== $i ) {
echo ', ';
}
echo '<br />';

View File

@@ -45,6 +45,10 @@ foreach ( $tools as $action_name => $tool ) {
</p>
</th>
<td class="run-tool">
<?php if ( ! empty( $tool['status_text'] ) ) : ?>
<span class="run-tool-status"><?php echo wp_kses_post( $tool['status_text'] ); ?></span>
<?php endif; ?>
<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<input <?php echo ArrayUtil::is_truthy( $tool, 'disabled' ) ? 'disabled' : ''; ?> type="submit" form="<?php echo 'form_' . $action_name; ?>" class="button button-large" value="<?php echo esc_attr( $tool['button'] ); ?>" />
</td>

View File

@@ -288,6 +288,9 @@ class WC_AJAX {
if ( is_array( $posted_shipping_methods ) ) {
foreach ( $posted_shipping_methods as $i => $value ) {
if ( ! is_string( $value ) ) {
continue;
}
$chosen_shipping_methods[ $i ] = $value;
}
}
@@ -347,6 +350,9 @@ class WC_AJAX {
if ( is_array( $posted_shipping_methods ) ) {
foreach ( $posted_shipping_methods as $i => $value ) {
if ( ! is_string( $value ) ) {
continue;
}
$chosen_shipping_methods[ $i ] = $value;
}
}
@@ -2058,7 +2064,8 @@ class WC_AJAX {
$children = get_terms( $taxonomy, "child_of=$id&menu_order=ASC&hide_empty=0" );
if ( $term && count( $children ) ) {
$children_count = is_countable( $children ) ? count( $children ) : 0;
if ( $term && $children_count ) {
echo 'children';
wp_die();
}

View File

@@ -695,10 +695,8 @@ class WC_Checkout {
)
);
// Avoid storing used_by - it's not needed and can get large.
$coupon_data = $coupon->get_data();
unset( $coupon_data['used_by'] );
$item->add_meta_data( 'coupon_data', $coupon_data );
$coupon_info = $coupon->get_short_info();
$item->add_meta_data( 'coupon_info', $coupon_info );
/**
* Action hook to adjust item before save.
@@ -751,7 +749,7 @@ class WC_Checkout {
);
// phpcs:enable WordPress.Security.NonceVerification.Missing
$skipped = array();
$skipped = array();
$form_was_shown = isset( $_POST['woocommerce-process-checkout-nonce'] ); // phpcs:disable WordPress.Security.NonceVerification.Missing
foreach ( $this->get_checkout_fields() as $fieldset_key => $fieldset ) {
@@ -783,6 +781,9 @@ class WC_Checkout {
$value = wc_sanitize_textarea( $value );
break;
case 'password':
if ( $data['createaccount'] && 'account_password' === $key ) {
$value = wp_slash( $value ); // Passwords are encrypted with slashes on account creation, so we need to slash here too.
}
break;
default:
$value = wc_clean( $value );
@@ -1020,6 +1021,9 @@ class WC_Checkout {
if ( is_array( $data['shipping_method'] ) ) {
foreach ( $data['shipping_method'] as $i => $value ) {
if ( ! is_string( $value ) ) {
continue;
}
$chosen_shipping_methods[ $i ] = $value;
}
}

View File

@@ -287,34 +287,34 @@ class WC_Countries {
* @return array
*/
public function get_allowed_countries() {
if ( 'all' === get_option( 'woocommerce_allowed_countries' ) ) {
return apply_filters( 'woocommerce_countries_allowed_countries', $this->countries );
}
$countries = $this->countries;
$allowed_countries = get_option( 'woocommerce_allowed_countries' );
if ( 'all_except' === get_option( 'woocommerce_allowed_countries' ) ) {
if ( 'all_except' === $allowed_countries ) {
$except_countries = get_option( 'woocommerce_all_except_countries', array() );
if ( ! $except_countries ) {
return $this->countries;
} else {
$all_except_countries = $this->countries;
if ( $except_countries ) {
foreach ( $except_countries as $country ) {
unset( $all_except_countries[ $country ] );
unset( $countries[ $country ] );
}
}
} elseif ( 'specific' === $allowed_countries ) {
$countries = array();
$raw_countries = get_option( 'woocommerce_specific_allowed_countries', array() );
if ( $raw_countries ) {
foreach ( $raw_countries as $country ) {
$countries[ $country ] = $this->countries[ $country ];
}
return apply_filters( 'woocommerce_countries_allowed_countries', $all_except_countries );
}
}
$countries = array();
$raw_countries = get_option( 'woocommerce_specific_allowed_countries', array() );
if ( $raw_countries ) {
foreach ( $raw_countries as $country ) {
$countries[ $country ] = $this->countries[ $country ];
}
}
/**
* Filter the list of allowed selling countries.
*
* @since 3.3.0
* @param array $countries
*/
return apply_filters( 'woocommerce_countries_allowed_countries', $countries );
}
@@ -324,24 +324,34 @@ class WC_Countries {
* @return array
*/
public function get_shipping_countries() {
if ( '' === get_option( 'woocommerce_ship_to_countries' ) ) {
return $this->get_allowed_countries();
// If shipping is disabled, return an empty array.
if ( 'disabled' === get_option( 'woocommerce_ship_to_countries' ) ) {
return array();
}
// Default to selling countries.
$countries = $this->get_allowed_countries();
// All indicates that all countries are allowed, regardless of where you sell to.
if ( 'all' === get_option( 'woocommerce_ship_to_countries' ) ) {
return $this->countries;
}
$countries = $this->countries;
} elseif ( 'specific' === get_option( 'woocommerce_ship_to_countries' ) ) {
$countries = array();
$raw_countries = get_option( 'woocommerce_specific_ship_to_countries', array() );
$countries = array();
$raw_countries = get_option( 'woocommerce_specific_ship_to_countries' );
if ( $raw_countries ) {
foreach ( $raw_countries as $country ) {
$countries[ $country ] = $this->countries[ $country ];
if ( $raw_countries ) {
foreach ( $raw_countries as $country ) {
$countries[ $country ] = $this->countries[ $country ];
}
}
}
/**
* Filter the list of allowed selling countries.
*
* @since 3.3.0
* @param array $countries
*/
return apply_filters( 'woocommerce_countries_shipping_countries', $countries );
}
@@ -859,7 +869,7 @@ class WC_Countries {
),
),
'AL' => array(
'state' => array(
'state' => array(
'label' => __( 'County', 'woocommerce' ),
),
),
@@ -968,7 +978,7 @@ class WC_Countries {
'required' => false,
'hidden' => true,
),
'state' => array(
'state' => array(
'required' => false,
),
),
@@ -1011,7 +1021,7 @@ class WC_Countries {
'postcode' => array(
'required' => false,
),
'state' => array(
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
),
),
@@ -1054,12 +1064,12 @@ class WC_Countries {
),
),
'DO' => array(
'state' => array(
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
'EC' => array(
'state' => array(
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
@@ -1097,11 +1107,11 @@ class WC_Countries {
),
),
'GG' => array(
'state' => array(
'required' => false,
'label' => __( 'Parish', 'woocommerce' ),
),
),
'state' => array(
'required' => false,
'label' => __( 'Parish', 'woocommerce' ),
),
),
'GH' => array(
'postcode' => array(
'required' => false,
@@ -1147,7 +1157,7 @@ class WC_Countries {
),
),
'HN' => array(
'state' => array(
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
),
),
@@ -1350,7 +1360,7 @@ class WC_Countries {
),
),
'NI' => array(
'state' => array(
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
),
),
@@ -1400,7 +1410,7 @@ class WC_Countries {
),
),
'PA' => array(
'state' => array(
'state' => array(
'label' => __( 'Province', 'woocommerce' ),
),
),
@@ -1430,7 +1440,7 @@ class WC_Countries {
),
'PY' => array(
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
'label' => __( 'Department', 'woocommerce' ),
),
),
'RE' => array(
@@ -1497,7 +1507,7 @@ class WC_Countries {
),
),
'SV' => array(
'state' => array(
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
),
),
@@ -1575,7 +1585,7 @@ class WC_Countries {
),
),
'UY' => array(
'state' => array(
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
),
),

View File

@@ -81,6 +81,15 @@ class WC_Coupon extends WC_Legacy_Coupon {
*/
protected $cache_group = 'coupons';
/**
* Sorting.
*
* Used by `get_coupons_from_cart` to sort coupons.
*
* @var int
*/
public $sort = 0;
/**
* Coupon constructor. Loads coupon data.
*
@@ -213,7 +222,7 @@ class WC_Coupon extends WC_Legacy_Coupon {
*
* @since 3.0.0
* @param string $context What the value is for. Valid values are 'view' and 'edit'.
* @return float
* @return string
*/
public function get_amount( $context = 'view' ) {
return wc_format_decimal( $this->get_prop( 'amount', $context ) );
@@ -1097,4 +1106,50 @@ class WC_Coupon extends WC_Legacy_Coupon {
// When using this static method, there is no $this to pass to filter.
return apply_filters( 'woocommerce_coupon_error', $err, $err_code, null );
}
/**
* Get the coupon information that is needed to reapply the coupon to an existing order.
* This information is intended to be stored as a meta value in the order line item corresponding to the coupon
* and should NOT be modified or extended (additional/custom data should go in a separate metadata entry).
*
* The information returned is a JSON-encoded string of an array with the following coupon information:
*
* 0: Id
* 1: Code
* 2: Type, null is equivalent to 'fixed_cart'
* 3: Nominal amount (either a fixed amount or a percent, depending on the coupon type)
* 4: The coupon grants free shipping? (present only if true)
*
* @return string A JSON string with information that allows the coupon to be reapplied to an existing order.
*/
public function get_short_info(): string {
$type = $this->get_discount_type();
$info = array(
$this->get_id(),
$this->get_code(),
'fixed_cart' === $type ? null : $type,
(float) $this->get_prop( 'amount' ),
);
if ( $this->get_free_shipping() ) {
$info[] = true;
}
return wp_json_encode( $info );
}
/**
* Sets the coupon parameters from a reapply information set generated with 'get_short_info'.
*
* @param string $info JSON string with reapply information as returned by 'get_short_info'.
*/
public function set_short_info( string $info ) {
$info = json_decode( $info, true );
$this->set_id( $info[0] ?? 0 );
$this->set_code( $info[1] ?? '' );
$this->set_discount_type( $info[2] ?? 'fixed_cart' );
$this->set_amount( $info[3] ?? 0 );
$this->set_free_shipping( $info[4] ?? false );
}
}

View File

@@ -175,14 +175,6 @@ class WC_Deprecated_Action_Hooks extends WC_Deprecated_Hooks {
do_action( $old_hook, $order_id, $item_id, $item, $item->get_product() );
}
break;
case 'woocommerce_order_update_coupon':
case 'woocommerce_order_update_shipping':
case 'woocommerce_order_update_fee':
case 'woocommerce_order_update_tax':
if ( ! is_a( $item, 'WC_Order_Item_Product' ) ) {
do_action( $old_hook, $order_id, $item_id, $item );
}
break;
default:
do_action_ref_array( $old_hook, $new_callback_args );
break;

View File

@@ -353,12 +353,20 @@ class WC_Form_Handler {
$customer->save();
}
wc_add_notice( __( 'Account details changed successfully.', 'woocommerce' ) );
/**
* Hook: woocommerce_save_account_details.
*
* @since 3.6.0
* @param int $user_id User ID being saved.
*/
do_action( 'woocommerce_save_account_details', $user->ID );
wp_safe_redirect( wc_get_endpoint_url( 'edit-account', '', wc_get_page_permalink( 'myaccount' ) ) );
exit;
// Notices are checked here so that if something created a notice during the save hooks above, the redirect will not happen.
if ( 0 === wc_notice_count( 'error' ) ) {
wc_add_notice( __( 'Account details changed successfully.', 'woocommerce' ) );
wp_safe_redirect( wc_get_endpoint_url( 'edit-account', '', wc_get_page_permalink( 'myaccount' ) ) );
exit;
}
}
}
@@ -901,7 +909,7 @@ class WC_Form_Handler {
$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( wp_unslash( $_REQUEST['quantity'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$variations = array();
$product = wc_get_product( $product_id );
$product = wc_get_product( $product_id );
foreach ( $_REQUEST as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( 'attribute_' !== substr( $key, 0, 10 ) ) {

View File

@@ -1194,7 +1194,7 @@ class WC_Geo_IP {
$this->memory_buffer = fread( $this->filehandle, $s_array['size'] );
}
} else {
$this->log( 'GeoIP API: Can not open ' . $filename, 'error' );
self::log( 'GeoIP API: Can not open ' . $filename, 'error' );
}
}
@@ -1553,7 +1553,7 @@ class WC_Geo_IP {
}
}
$this->log( 'GeoIP API: Error traversing database - perhaps it is corrupt?', 'error' );
self::log( 'GeoIP API: Error traversing database - perhaps it is corrupt?', 'error' );
return false;
}
@@ -1608,7 +1608,7 @@ class WC_Geo_IP {
}
}
$this->log( 'GeoIP API: Error traversing database - perhaps it is corrupt?', 'error' );
self::log( 'GeoIP API: Error traversing database - perhaps it is corrupt?', 'error' );
return false;
}
@@ -1637,7 +1637,7 @@ class WC_Geo_IP {
*/
public function geoip_country_id_by_addr_v6( $addr ) {
if ( ! defined( 'AF_INET6' ) ) {
$this->log( 'GEOIP (geoip_country_id_by_addr_v6): PHP was compiled with --disable-ipv6 option' );
self::log( 'GEOIP (geoip_country_id_by_addr_v6): PHP was compiled with --disable-ipv6 option' );
return false;
}
$ipnum = inet_pton( $addr );

View File

@@ -8,6 +8,7 @@
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Admin\Notes\Notes;
use Automattic\WooCommerce\Internal\TransientFiles\TransientFilesEngine;
use Automattic\WooCommerce\Internal\DataStores\Orders\{ CustomOrdersTableController, DataSynchronizer, OrdersTableDataStore };
use Automattic\WooCommerce\Internal\Features\FeaturesController;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator;
@@ -243,6 +244,9 @@ class WC_Install {
'8.6.0' => array(
'wc_update_860_remove_recommended_marketing_plugins_transient',
),
'8.7.0' => array(
'wc_update_870_prevent_listing_of_transient_files_directory',
),
);
/**
@@ -456,6 +460,11 @@ class WC_Install {
// plugin version update. We base plugin age off of this value.
add_option( 'woocommerce_admin_install_timestamp', time() );
// Force a flush of rewrite rules even if the corresponding hook isn't initialized yet.
if ( ! has_action( 'woocommerce_flush_rewrite_rules' ) ) {
flush_rewrite_rules();
}
/**
* Flush the rewrite rules after install or update.
*
@@ -557,6 +566,7 @@ class WC_Install {
WC()->query->add_endpoints();
WC_API::add_endpoint();
WC_Auth::add_endpoint();
TransientFilesEngine::add_endpoint();
}
/**

View File

@@ -14,6 +14,22 @@ defined( 'ABSPATH' ) || exit;
*/
class WC_Order_Item_Product extends WC_Order_Item {
/**
* Legacy values.
*
* @deprecated 4.4.0 For legacy actions.
* @var array
*/
public $legacy_values;
/**
* Legacy cart item key.
*
* @deprecated 4.4.0 For legacy actions.
* @var string
*/
public $legacy_cart_item_key;
/**
* Order Data array. This is the core order data exposed in APIs since 3.0.0.
*

View File

@@ -52,6 +52,14 @@ class WC_Order_Item extends WC_Data implements ArrayAccess {
*/
protected $object_type = 'order_item';
/**
* Legacy package key.
*
* @deprecated 4.4.0 For legacy actions.
* @var string
*/
public $legacy_package_key;
/**
* Constructor.
*

View File

@@ -29,18 +29,18 @@ class WC_Product_Factory {
return false;
}
$product_type = $this->get_product_type( $product_id );
$product_type = self::get_product_type( $product_id );
// Backwards compatibility.
if ( ! empty( $deprecated ) ) {
wc_deprecated_argument( 'args', '3.0', 'Passing args to the product factory is deprecated. If you need to force a type, construct the product class directly.' );
if ( isset( $deprecated['product_type'] ) ) {
$product_type = $this->get_classname_from_product_type( $deprecated['product_type'] );
$product_type = self::get_classname_from_product_type( $deprecated['product_type'] );
}
}
$classname = $this->get_product_classname( $product_id, $product_type );
$classname = self::get_product_classname( $product_id, $product_type );
try {
return new $classname( $product_id, $deprecated );

View File

@@ -554,7 +554,7 @@ class WC_Query {
*/
private function product_query_post_clauses( $args, $wp_query ) {
$args = $this->price_filter_post_clauses( $args, $wp_query );
$args = $this->filterer->filter_by_attribute_post_clauses( $args, $wp_query, $this->get_layered_nav_chosen_attributes() );
$args = $this->filterer->filter_by_attribute_post_clauses( $args, $wp_query, self::get_layered_nav_chosen_attributes() );
return $args;
}
@@ -790,7 +790,7 @@ class WC_Query {
if ( $main_query && ! $this->filterer->filtering_via_lookup_table_is_active() ) {
// Layered nav filters on terms.
foreach ( $this->get_layered_nav_chosen_attributes() as $taxonomy => $data ) {
foreach ( self::get_layered_nav_chosen_attributes() as $taxonomy => $data ) {
$tax_query[] = array(
'taxonomy' => $taxonomy,
'field' => 'slug',

View File

@@ -263,7 +263,7 @@ class WC_Regenerate_Images {
private static function get_full_size_image_dimensions( $attachment_id ) {
$imagedata = wp_get_attachment_metadata( $attachment_id );
if ( ! $imagedata ) {
if ( ! is_array( $imagedata ) ) {
return array();
}

View File

@@ -71,6 +71,7 @@ class WC_Session_Handler extends WC_Session {
$this->init_session_cookie();
add_action( 'woocommerce_set_cart_cookies', array( $this, 'set_customer_session_cookie' ), 10 );
add_action( 'wp', array( $this, 'maybe_set_customer_session_cookie' ), 99 );
add_action( 'shutdown', array( $this, 'save_data' ), 20 );
add_action( 'wp_logout', array( $this, 'destroy_session' ) );
@@ -137,7 +138,7 @@ class WC_Session_Handler extends WC_Session {
return false;
}
// Session from a different user is not valid. (Although from a guest user will be valid)
// Session from a different user is not valid. (Although from a guest user will be valid).
if ( is_user_logged_in() && ! $this->is_customer_guest( $this->_customer_id ) && strval( get_current_user_id() ) !== $this->_customer_id ) {
return false;
}
@@ -145,6 +146,18 @@ class WC_Session_Handler extends WC_Session {
return true;
}
/**
* Hooks into the wp action to maybe set the session cookie if the user is on a certain page e.g. a checkout endpoint.
*
* Certain gateways may rely on sessions and this ensures a session is present even if the customer does not have a
* cart.
*/
public function maybe_set_customer_session_cookie() {
if ( is_wc_endpoint_url( 'order-pay' ) ) {
$this->set_customer_session_cookie( true );
}
}
/**
* Sets the session cookie on-demand (usually after adding an item to the cart).
*
@@ -345,7 +358,8 @@ class WC_Session_Handler extends WC_Session {
$wpdb->query(
$wpdb->prepare(
"INSERT INTO {$wpdb->prefix}woocommerce_sessions (`session_key`, `session_value`, `session_expiry`) VALUES (%s, %s, %d)
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"INSERT INTO $this->_table (`session_key`, `session_value`, `session_expiry`) VALUES (%s, %s, %d)
ON DUPLICATE KEY UPDATE `session_value` = VALUES(`session_value`), `session_expiry` = VALUES(`session_expiry`)",
$this->_customer_id,
maybe_serialize( $this->_data ),

View File

@@ -241,6 +241,36 @@ class WC_Structured_Data {
'offerCount' => count( $product->get_children() ),
);
}
} elseif ( $product->is_type( 'grouped' ) ) {
if ( $product->is_on_sale() && $product->get_date_on_sale_to() ) {
$price_valid_until = gmdate( 'Y-m-d', $product->get_date_on_sale_to()->getTimestamp() );
}
$tax_display_mode = get_option( 'woocommerce_tax_display_shop' );
$children = array_filter( array_map( 'wc_get_product', $product->get_children() ), 'wc_products_array_filter_visible_grouped' );
$price_function = 'incl' === $tax_display_mode ? 'wc_get_price_including_tax' : 'wc_get_price_excluding_tax';
foreach ( $children as $child ) {
if ( '' !== $child->get_price() ) {
$child_prices[] = $price_function( $child );
}
}
if ( empty( $child_prices ) ) {
$min_price = 0;
} else {
$min_price = min( $child_prices );
}
$markup_offer = array(
'@type' => 'Offer',
'price' => wc_format_decimal( $min_price, wc_get_price_decimals() ),
'priceValidUntil' => $price_valid_until,
'priceSpecification' => array(
'price' => wc_format_decimal( $min_price, wc_get_price_decimals() ),
'priceCurrency' => $currency,
'valueAddedTaxIncluded' => wc_prices_include_tax() ? 'true' : 'false',
),
);
} else {
if ( $product->is_on_sale() && $product->get_date_on_sale_to() ) {
$price_valid_until = gmdate( 'Y-m-d', $product->get_date_on_sale_to()->getTimestamp() );

View File

@@ -625,8 +625,8 @@ class WC_Tracker {
$curr_tokens = preg_split( '/[ :,\-_]+/', $key );
$prev_tokens = preg_split( '/[ :,\-_]+/', $prev );
$len_curr = count( $curr_tokens );
$len_prev = count( $prev_tokens );
$len_curr = is_array( $curr_tokens ) ? count( $curr_tokens ) : 0;
$len_prev = is_array( $prev_tokens ) ? count( $prev_tokens ) : 0;
$index_unique = -1;
// Gather the common tokens.
@@ -640,7 +640,7 @@ class WC_Tracker {
}
// If only one token is different, and those tokens contain digits, then that could be the unique id.
if ( count( $curr_tokens ) - count( $comm_tokens ) <= 1 && count( $comm_tokens ) > 0 && $index_unique > -1 ) {
if ( $len_curr - count( $comm_tokens ) <= 1 && count( $comm_tokens ) > 0 && $index_unique > -1 ) {
$objects[ $key ]->group_key = implode( ' ', $comm_tokens );
$objects[ $prev ]->group_key = implode( ' ', $comm_tokens );
} else {
@@ -1072,8 +1072,9 @@ class WC_Tracker {
* - pickup_locations_count
*/
public static function get_pickup_location_data() {
$pickup_location_enabled = false;
$pickup_locations_count = count( get_option( 'pickup_location_pickup_locations', array() ) );
$pickup_location_enabled = false;
$pickup_location_pickup_locations = get_option( 'pickup_location_pickup_locations', array() );
$pickup_locations_count = is_countable( $pickup_location_pickup_locations ) ? count( $pickup_location_pickup_locations ) : 0;
// Get the available shipping methods.
$shipping_methods = WC()->shipping()->get_shipping_methods();

View File

@@ -37,7 +37,7 @@ final class WooCommerce {
*
* @var string
*/
public $version = '8.6.1';
public $version = '8.7.0';
/**
* WooCommerce Schema version.
@@ -250,6 +250,8 @@ final class WooCommerce {
add_action( 'deactivated_plugin', array( $this, 'deactivated_plugin' ) );
add_action( 'woocommerce_installed', array( $this, 'add_woocommerce_inbox_variant' ) );
add_action( 'woocommerce_updated', array( $this, 'add_woocommerce_inbox_variant' ) );
add_action( 'woocommerce_installed', array( $this, 'add_woocommerce_remote_variant' ) );
add_action( 'woocommerce_updated', array( $this, 'add_woocommerce_remote_variant' ) );
// These classes set up hooks on instantiation.
$container = wc_get_container();
@@ -283,6 +285,9 @@ final class WooCommerce {
* Add woocommerce_inbox_variant for the Remote Inbox Notification.
*
* P2 post can be found at https://wp.me/paJDYF-1uJ.
*
* This will no longer be used. The more flexible add_woocommerce_remote_variant
* below will be used instead.
*/
public function add_woocommerce_inbox_variant() {
$config_name = 'woocommerce_inbox_variant_assignment';
@@ -290,6 +295,18 @@ final class WooCommerce {
update_option( $config_name, wp_rand( 1, 12 ) );
}
}
/**
* Add woocommerce_remote_variant_assignment used to determine cohort
* or group assignment for Remote Spec Engines.
*/
public function add_woocommerce_remote_variant() {
$config_name = 'woocommerce_remote_variant_assignment';
if ( false === get_option( $config_name, false ) ) {
update_option( $config_name, wp_rand( 1, 120 ) );
}
}
/**
* Ensures fatal errors are logged so they can be picked up in the status report.
*
@@ -910,14 +927,15 @@ final class WooCommerce {
/**
* Initialize the customer and cart objects and setup customer saving on shutdown.
*
* Note, wc()->customer is session based. Changes to customer data via this property are not persisted to the database automatically.
*
* @since 3.6.4
* @return void
*/
public function initialize_cart() {
// Cart needs customer info.
if ( is_null( $this->customer ) || ! $this->customer instanceof WC_Customer ) {
$this->customer = new WC_Customer( get_current_user_id(), true );
// Customer should be saved during shutdown.
// Customer session should be saved during shutdown.
add_action( 'shutdown', array( $this->customer, 'save' ), 10 );
}
if ( is_null( $this->cart ) || ! $this->cart instanceof WC_Cart ) {

View File

@@ -6,6 +6,7 @@
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Proxies\LegacyProxy;
if ( ! defined( 'ABSPATH' ) ) {
exit;
@@ -707,6 +708,8 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
* @param WC_Abstract_Order $order Order object.
*/
protected function update_order_meta_from_object( $order ) {
global $wpdb;
if ( is_null( $order->get_meta() ) ) {
return;
}
@@ -734,7 +737,23 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
}
}
}
add_post_meta( $order->get_id(), $meta_data->key, $meta_data->value, false );
if ( is_object( $meta_data->value ) && '__PHP_Incomplete_Class' === get_class( $meta_data->value ) ) {
$meta_value = maybe_serialize( $meta_data->value );
$result = $wpdb->insert(
_get_meta_table( 'post' ),
array(
'post_id' => $order->get_id(),
'meta_key' => $meta_data->key, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'meta_value' => $meta_value, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
),
array( '%d', '%s', '%s' )
);
wp_cache_delete( $order->get_id(), 'post_meta' );
$logger = wc_get_container()->get( LegacyProxy::class )->call_function( 'wc_get_logger' );
$logger->warning( sprintf( 'encountered an order meta value of type __PHP_Incomplete_Class during `update_order_meta_from_object` in order with ID %d: "%s"', $order->get_id(), var_export( $meta_value, true ) ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
} else {
add_post_meta( $order->get_id(), $meta_data->key, $meta_data->value, false );
}
}
// Find remaining meta that was deleted from the order but still present in the associated post.

View File

@@ -12,6 +12,8 @@ if ( ! defined( 'ABSPATH' ) ) {
/**
* WC Customer Data Store which stores the data in session.
*
* Used by the WC_Customer class to store customer data to the session.
*
* @version 3.0.0
*/
class WC_Customer_Data_Store_Session extends WC_Data_Store_WP implements WC_Customer_Data_Store_Interface, WC_Object_Data_Store_Interface {
@@ -24,35 +26,36 @@ class WC_Customer_Data_Store_Session extends WC_Data_Store_WP implements WC_Cust
protected $session_keys = array(
'id',
'date_modified',
'billing_postcode',
'billing_city',
'billing_address_1',
'billing_address',
'billing_address_2',
'billing_state',
'billing_country',
'shipping_postcode',
'shipping_city',
'shipping_address_1',
'shipping_address',
'shipping_address_2',
'shipping_state',
'shipping_country',
'is_vat_exempt',
'calculated_shipping',
'billing_first_name',
'billing_last_name',
'billing_company',
'billing_phone',
'billing_email',
'billing_address',
'billing_address_1',
'billing_address_2',
'billing_city',
'billing_state',
'billing_postcode',
'billing_country',
'shipping_first_name',
'shipping_last_name',
'shipping_company',
'shipping_phone',
'shipping_address',
'shipping_address_1',
'shipping_address_2',
'shipping_city',
'shipping_state',
'shipping_postcode',
'shipping_country',
'is_vat_exempt',
'calculated_shipping',
'meta_data',
);
/**
* Simply update the session.
* Update the session. Note, this does not persist the data to the DB.
*
* @param WC_Customer $customer Customer object.
*/
@@ -61,7 +64,7 @@ class WC_Customer_Data_Store_Session extends WC_Data_Store_WP implements WC_Cust
}
/**
* Simply update the session.
* Update the session. Note, this does not persist the data to the DB.
*
* @param WC_Customer $customer Customer object.
*/
@@ -81,14 +84,36 @@ class WC_Customer_Data_Store_Session extends WC_Data_Store_WP implements WC_Cust
if ( 'billing_' === substr( $session_key, 0, 8 ) ) {
$session_key = str_replace( 'billing_', '', $session_key );
}
$data[ $session_key ] = (string) $customer->{"get_$function_key"}( 'edit' );
if ( 'meta_data' === $session_key ) {
/**
* Filter the allowed session meta data keys.
*
* If the customer object contains any meta data with these keys, it will be stored within the WooCommerce session.
*
* @since 8.7.0
* @param array $allowed_keys The allowed meta data keys.
* @param WC_Customer $customer The customer object.
*/
$allowed_keys = apply_filters( 'woocommerce_customer_allowed_session_meta_keys', array(), $customer );
$session_value = wp_json_encode(
array_filter(
$customer->get_meta_data(),
function( $meta_data ) use ( $allowed_keys ) {
return in_array( $meta_data->key, $allowed_keys, true );
}
)
);
} else {
$session_value = $customer->{"get_$function_key"}( 'edit' );
}
$data[ $session_key ] = (string) $session_value;
}
WC()->session->set( 'customer', $data );
}
/**
* Read customer data from the session unless the user has logged in, in
* which case the stored ID will differ from the actual ID.
* Read customer data from the session unless the user has logged in, in which case the stored ID will differ from
* the actual ID.
*
* @since 3.0.0
* @param WC_Customer $customer Customer object.
@@ -110,8 +135,20 @@ class WC_Customer_Data_Store_Session extends WC_Data_Store_WP implements WC_Cust
if ( 'billing_' === substr( $session_key, 0, 8 ) ) {
$session_key = str_replace( 'billing_', '', $session_key );
}
if ( isset( $data[ $session_key ] ) && is_callable( array( $customer, "set_{$function_key}" ) ) ) {
$customer->{"set_{$function_key}"}( wp_unslash( $data[ $session_key ] ) );
if ( ! empty( $data[ $session_key ] ) && is_callable( array( $customer, "set_{$function_key}" ) ) ) {
if ( 'meta_data' === $session_key ) {
$meta_data_values = json_decode( wp_unslash( $data[ $session_key ] ), true );
if ( $meta_data_values ) {
foreach ( $meta_data_values as $meta_data_value ) {
if ( ! isset( $meta_data_value['key'], $meta_data_value['value'] ) ) {
continue;
}
$customer->add_meta_data( $meta_data_value['key'], $meta_data_value['value'], true );
}
}
} else {
$customer->{"set_{$function_key}"}( wp_unslash( $data[ $session_key ] ) );
}
}
}
}
@@ -120,13 +157,13 @@ class WC_Customer_Data_Store_Session extends WC_Data_Store_WP implements WC_Cust
}
/**
* Load default values if props are unset.
* Set default values for the customer object if props are unset.
*
* @param WC_Customer $customer Customer object.
*/
protected function set_defaults( &$customer ) {
try {
$default = wc_get_customer_default_location();
$default = wc_get_customer_default_location();
$has_shipping_address = $customer->has_shipping_address();
if ( ! $customer->get_billing_country() ) {
@@ -154,7 +191,7 @@ class WC_Customer_Data_Store_Session extends WC_Data_Store_WP implements WC_Cust
}
/**
* Deletes a customer from the database.
* Deletes the customer session.
*
* @since 3.0.0
* @param WC_Customer $customer Customer object.

View File

@@ -89,6 +89,7 @@ if ( ! class_exists( 'WC_Email_New_Order' ) ) :
$order = wc_get_order( $order_id );
}
$email_already_sent = false;
if ( is_a( $order, 'WC_Order' ) ) {
$this->object = $order;
$this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() );

View File

@@ -236,6 +236,7 @@ class WC_Gateway_COD extends WC_Payment_Gateway {
$data_store = WC_Data_Store::load( 'shipping-zone' );
$raw_zones = $data_store->get_zones();
$zones = array();
foreach ( $raw_zones as $raw_zone ) {
$zones[] = new WC_Shipping_Zone( $raw_zone );
@@ -327,7 +328,7 @@ class WC_Gateway_COD extends WC_Payment_Gateway {
* @since 3.4.0
*
* @param array $rate_ids Rate ids to check.
* @return boolean
* @return array
*/
private function get_matching_rates( $rate_ids ) {
// First, match entries in 'method_id:instance_id' format. Then, match entries in 'method_id' format by stripping off the instance ID from the candidates.

View File

@@ -418,11 +418,11 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
$result = WC_Gateway_Paypal_API_Handler::refund_transaction( $order, $amount, $reason );
if ( is_wp_error( $result ) ) {
$this->log( 'Refund Failed: ' . $result->get_error_message(), 'error' );
static::log( 'Refund Failed: ' . $result->get_error_message(), 'error' );
return new WP_Error( 'error', $result->get_error_message() );
}
$this->log( 'Refund Result: ' . wc_print_r( $result, true ) );
static::log( 'Refund Result: ' . wc_print_r( $result, true ) );
switch ( strtolower( $result->ACK ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
case 'success':
@@ -450,13 +450,13 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
$result = WC_Gateway_Paypal_API_Handler::do_capture( $order );
if ( is_wp_error( $result ) ) {
$this->log( 'Capture Failed: ' . $result->get_error_message(), 'error' );
static::log( 'Capture Failed: ' . $result->get_error_message(), 'error' );
/* translators: %s: Paypal gateway error message */
$order->add_order_note( sprintf( __( 'Payment could not be captured: %s', 'woocommerce' ), $result->get_error_message() ) );
return;
}
$this->log( 'Capture Result: ' . wc_print_r( $result, true ) );
static::log( 'Capture Result: ' . wc_print_r( $result, true ) );
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
if ( ! empty( $result->PAYMENTSTATUS ) ) {

View File

@@ -59,22 +59,22 @@ class WC_API_Customers extends WC_API_Resource {
# GET /customers
$routes[ $this->base ] = array(
array( array( $this, 'get_customers' ), WC_API_SERVER::READABLE ),
array( array( $this, 'get_customers' ), WC_API_Server::READABLE ),
);
# GET /customers/count
$routes[ $this->base . '/count' ] = array(
array( array( $this, 'get_customers_count' ), WC_API_SERVER::READABLE ),
array( array( $this, 'get_customers_count' ), WC_API_Server::READABLE ),
);
# GET /customers/<id>
$routes[ $this->base . '/(?P<id>\d+)' ] = array(
array( array( $this, 'get_customer' ), WC_API_SERVER::READABLE ),
array( array( $this, 'get_customer' ), WC_API_Server::READABLE ),
);
# GET /customers/<id>/orders
$routes[ $this->base . '/(?P<id>\d+)/orders' ] = array(
array( array( $this, 'get_customer_orders' ), WC_API_SERVER::READABLE ),
array( array( $this, 'get_customer_orders' ), WC_API_Server::READABLE ),
);
return $routes;

View File

@@ -278,7 +278,7 @@ class WC_API_Server {
// Normalise the endpoints
foreach ( $endpoints as $route => &$handlers ) {
if ( count( $handlers ) <= 2 && isset( $handlers[1] ) && ! is_array( $handlers[1] ) ) {
if ( is_array( $handlers ) && count( $handlers ) <= 2 && isset( $handlers[1] ) && ! is_array( $handlers[1] ) ) {
$handlers = array( $handlers );
}
}

View File

@@ -46,8 +46,8 @@ class WC_API_Coupons extends WC_API_Resource {
# GET/PUT/DELETE /coupons/<id>
$routes[ $this->base . '/(?P<id>\d+)' ] = array(
array( array( $this, 'get_coupon' ), WC_API_Server::READABLE ),
array( array( $this, 'edit_coupon' ), WC_API_SERVER::EDITABLE | WC_API_SERVER::ACCEPT_DATA ),
array( array( $this, 'delete_coupon' ), WC_API_SERVER::DELETABLE ),
array( array( $this, 'edit_coupon' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_coupon' ), WC_API_Server::DELETABLE ),
);
# GET /coupons/code/<code>, note that coupon codes can contain spaces, dashes and underscores

View File

@@ -58,35 +58,35 @@ class WC_API_Customers extends WC_API_Resource {
# GET/POST /customers
$routes[ $this->base ] = array(
array( array( $this, 'get_customers' ), WC_API_SERVER::READABLE ),
array( array( $this, 'create_customer' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'get_customers' ), WC_API_Server::READABLE ),
array( array( $this, 'create_customer' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),
);
# GET /customers/count
$routes[ $this->base . '/count' ] = array(
array( array( $this, 'get_customers_count' ), WC_API_SERVER::READABLE ),
array( array( $this, 'get_customers_count' ), WC_API_Server::READABLE ),
);
# GET/PUT/DELETE /customers/<id>
$routes[ $this->base . '/(?P<id>\d+)' ] = array(
array( array( $this, 'get_customer' ), WC_API_SERVER::READABLE ),
array( array( $this, 'edit_customer' ), WC_API_SERVER::EDITABLE | WC_API_SERVER::ACCEPT_DATA ),
array( array( $this, 'delete_customer' ), WC_API_SERVER::DELETABLE ),
array( array( $this, 'get_customer' ), WC_API_Server::READABLE ),
array( array( $this, 'edit_customer' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_customer' ), WC_API_Server::DELETABLE ),
);
# GET /customers/email/<email>
$routes[ $this->base . '/email/(?P<email>.+)' ] = array(
array( array( $this, 'get_customer_by_email' ), WC_API_SERVER::READABLE ),
array( array( $this, 'get_customer_by_email' ), WC_API_Server::READABLE ),
);
# GET /customers/<id>/orders
$routes[ $this->base . '/(?P<id>\d+)/orders' ] = array(
array( array( $this, 'get_customer_orders' ), WC_API_SERVER::READABLE ),
array( array( $this, 'get_customer_orders' ), WC_API_Server::READABLE ),
);
# GET /customers/<id>/downloads
$routes[ $this->base . '/(?P<id>\d+)/downloads' ] = array(
array( array( $this, 'get_customer_downloads' ), WC_API_SERVER::READABLE ),
array( array( $this, 'get_customer_downloads' ), WC_API_Server::READABLE ),
);
# POST|PUT /customers/bulk

View File

@@ -62,27 +62,27 @@ class WC_API_Orders extends WC_API_Resource {
# GET|POST /orders/<id>/notes
$routes[ $this->base . '/(?P<order_id>\d+)/notes' ] = array(
array( array( $this, 'get_order_notes' ), WC_API_Server::READABLE ),
array( array( $this, 'create_order_note' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'create_order_note' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),
);
# GET|PUT|DELETE /orders/<order_id>/notes/<id>
$routes[ $this->base . '/(?P<order_id>\d+)/notes/(?P<id>\d+)' ] = array(
array( array( $this, 'get_order_note' ), WC_API_Server::READABLE ),
array( array( $this, 'edit_order_note' ), WC_API_SERVER::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_order_note' ), WC_API_SERVER::DELETABLE ),
array( array( $this, 'edit_order_note' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_order_note' ), WC_API_Server::DELETABLE ),
);
# GET|POST /orders/<order_id>/refunds
$routes[ $this->base . '/(?P<order_id>\d+)/refunds' ] = array(
array( array( $this, 'get_order_refunds' ), WC_API_Server::READABLE ),
array( array( $this, 'create_order_refund' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'create_order_refund' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),
);
# GET|PUT|DELETE /orders/<order_id>/refunds/<id>
$routes[ $this->base . '/(?P<order_id>\d+)/refunds/(?P<id>\d+)' ] = array(
array( array( $this, 'get_order_refund' ), WC_API_Server::READABLE ),
array( array( $this, 'edit_order_refund' ), WC_API_SERVER::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_order_refund' ), WC_API_SERVER::DELETABLE ),
array( array( $this, 'edit_order_refund' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_order_refund' ), WC_API_Server::DELETABLE ),
);
# POST|PUT /orders/bulk

View File

@@ -37,7 +37,7 @@ class WC_API_Products extends WC_API_Resource {
# GET/POST /products
$routes[ $this->base ] = array(
array( array( $this, 'get_products' ), WC_API_Server::READABLE ),
array( array( $this, 'create_product' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'create_product' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),
);
# GET /products/count
@@ -75,7 +75,7 @@ class WC_API_Products extends WC_API_Resource {
# GET/POST /products/attributes
$routes[ $this->base . '/attributes' ] = array(
array( array( $this, 'get_product_attributes' ), WC_API_Server::READABLE ),
array( array( $this, 'create_product_attribute' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'create_product_attribute' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),
);
# GET/PUT/DELETE /attributes/<id>

View File

@@ -276,7 +276,7 @@ class WC_API_Server {
// Normalise the endpoints
foreach ( $endpoints as $route => &$handlers ) {
if ( count( $handlers ) <= 2 && isset( $handlers[1] ) && ! is_array( $handlers[1] ) ) {
if ( is_array( $handlers ) && count( $handlers ) <= 2 && isset( $handlers[1] ) && ! is_array( $handlers[1] ) ) {
$handlers = array( $handlers );
}
}

View File

@@ -46,8 +46,8 @@ class WC_API_Coupons extends WC_API_Resource {
# GET/PUT/DELETE /coupons/<id>
$routes[ $this->base . '/(?P<id>\d+)' ] = array(
array( array( $this, 'get_coupon' ), WC_API_Server::READABLE ),
array( array( $this, 'edit_coupon' ), WC_API_SERVER::EDITABLE | WC_API_SERVER::ACCEPT_DATA ),
array( array( $this, 'delete_coupon' ), WC_API_SERVER::DELETABLE ),
array( array( $this, 'edit_coupon' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_coupon' ), WC_API_Server::DELETABLE ),
);
# GET /coupons/code/<code>, note that coupon codes can contain spaces, dashes and underscores

View File

@@ -58,35 +58,35 @@ class WC_API_Customers extends WC_API_Resource {
# GET/POST /customers
$routes[ $this->base ] = array(
array( array( $this, 'get_customers' ), WC_API_SERVER::READABLE ),
array( array( $this, 'create_customer' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'get_customers' ), WC_API_Server::READABLE ),
array( array( $this, 'create_customer' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),
);
# GET /customers/count
$routes[ $this->base . '/count' ] = array(
array( array( $this, 'get_customers_count' ), WC_API_SERVER::READABLE ),
array( array( $this, 'get_customers_count' ), WC_API_Server::READABLE ),
);
# GET/PUT/DELETE /customers/<id>
$routes[ $this->base . '/(?P<id>\d+)' ] = array(
array( array( $this, 'get_customer' ), WC_API_SERVER::READABLE ),
array( array( $this, 'edit_customer' ), WC_API_SERVER::EDITABLE | WC_API_SERVER::ACCEPT_DATA ),
array( array( $this, 'delete_customer' ), WC_API_SERVER::DELETABLE ),
array( array( $this, 'get_customer' ), WC_API_Server::READABLE ),
array( array( $this, 'edit_customer' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_customer' ), WC_API_Server::DELETABLE ),
);
# GET /customers/email/<email>
$routes[ $this->base . '/email/(?P<email>.+)' ] = array(
array( array( $this, 'get_customer_by_email' ), WC_API_SERVER::READABLE ),
array( array( $this, 'get_customer_by_email' ), WC_API_Server::READABLE ),
);
# GET /customers/<id>/orders
$routes[ $this->base . '/(?P<id>\d+)/orders' ] = array(
array( array( $this, 'get_customer_orders' ), WC_API_SERVER::READABLE ),
array( array( $this, 'get_customer_orders' ), WC_API_Server::READABLE ),
);
# GET /customers/<id>/downloads
$routes[ $this->base . '/(?P<id>\d+)/downloads' ] = array(
array( array( $this, 'get_customer_downloads' ), WC_API_SERVER::READABLE ),
array( array( $this, 'get_customer_downloads' ), WC_API_Server::READABLE ),
);
# POST|PUT /customers/bulk

View File

@@ -62,27 +62,27 @@ class WC_API_Orders extends WC_API_Resource {
# GET|POST /orders/<id>/notes
$routes[ $this->base . '/(?P<order_id>\d+)/notes' ] = array(
array( array( $this, 'get_order_notes' ), WC_API_Server::READABLE ),
array( array( $this, 'create_order_note' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'create_order_note' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),
);
# GET|PUT|DELETE /orders/<order_id>/notes/<id>
$routes[ $this->base . '/(?P<order_id>\d+)/notes/(?P<id>\d+)' ] = array(
array( array( $this, 'get_order_note' ), WC_API_Server::READABLE ),
array( array( $this, 'edit_order_note' ), WC_API_SERVER::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_order_note' ), WC_API_SERVER::DELETABLE ),
array( array( $this, 'edit_order_note' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_order_note' ), WC_API_Server::DELETABLE ),
);
# GET|POST /orders/<order_id>/refunds
$routes[ $this->base . '/(?P<order_id>\d+)/refunds' ] = array(
array( array( $this, 'get_order_refunds' ), WC_API_Server::READABLE ),
array( array( $this, 'create_order_refund' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'create_order_refund' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),
);
# GET|PUT|DELETE /orders/<order_id>/refunds/<id>
$routes[ $this->base . '/(?P<order_id>\d+)/refunds/(?P<id>\d+)' ] = array(
array( array( $this, 'get_order_refund' ), WC_API_Server::READABLE ),
array( array( $this, 'edit_order_refund' ), WC_API_SERVER::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_order_refund' ), WC_API_SERVER::DELETABLE ),
array( array( $this, 'edit_order_refund' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_order_refund' ), WC_API_Server::DELETABLE ),
);
# POST|PUT /orders/bulk

View File

@@ -37,7 +37,7 @@ class WC_API_Products extends WC_API_Resource {
# GET/POST /products
$routes[ $this->base ] = array(
array( array( $this, 'get_products' ), WC_API_Server::READABLE ),
array( array( $this, 'create_product' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'create_product' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),
);
# GET /products/count
@@ -104,7 +104,7 @@ class WC_API_Products extends WC_API_Resource {
# GET/POST /products/attributes
$routes[ $this->base . '/attributes' ] = array(
array( array( $this, 'get_product_attributes' ), WC_API_Server::READABLE ),
array( array( $this, 'create_product_attribute' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'create_product_attribute' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),
);
# GET/PUT/DELETE /products/attributes/<id>
@@ -117,7 +117,7 @@ class WC_API_Products extends WC_API_Resource {
# GET/POST /products/attributes/<attribute_id>/terms
$routes[ $this->base . '/attributes/(?P<attribute_id>\d+)/terms' ] = array(
array( array( $this, 'get_product_attribute_terms' ), WC_API_Server::READABLE ),
array( array( $this, 'create_product_attribute_term' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'create_product_attribute_term' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),
);
# GET/PUT/DELETE /products/attributes/<attribute_id>/terms/<id>

View File

@@ -276,7 +276,7 @@ class WC_API_Server {
// Normalise the endpoints
foreach ( $endpoints as $route => &$handlers ) {
if ( count( $handlers ) <= 2 && isset( $handlers[1] ) && ! is_array( $handlers[1] ) ) {
if ( is_array( $handlers ) && count( $handlers ) <= 2 && isset( $handlers[1] ) && ! is_array( $handlers[1] ) ) {
$handlers = array( $handlers );
}
}

View File

@@ -46,8 +46,8 @@ class WC_API_Taxes extends WC_API_Resource {
# GET/PUT/DELETE /taxes/<id>
$routes[ $this->base . '/(?P<id>\d+)' ] = array(
array( array( $this, 'get_tax' ), WC_API_Server::READABLE ),
array( array( $this, 'edit_tax' ), WC_API_SERVER::EDITABLE | WC_API_SERVER::ACCEPT_DATA ),
array( array( $this, 'delete_tax' ), WC_API_SERVER::DELETABLE ),
array( array( $this, 'edit_tax' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),
array( array( $this, 'delete_tax' ), WC_API_Server::DELETABLE ),
);
# GET/POST /taxes/classes
@@ -63,7 +63,7 @@ class WC_API_Taxes extends WC_API_Resource {
# GET /taxes/classes/<slug>
$routes[ $this->base . '/classes/(?P<slug>\w[\w\s\-]*)' ] = array(
array( array( $this, 'delete_tax_class' ), WC_API_SERVER::DELETABLE ),
array( array( $this, 'delete_tax_class' ), WC_API_Server::DELETABLE ),
);
# POST|PUT /taxes/bulk

View File

@@ -121,6 +121,7 @@ final class Experimental_Abtest {
*
* @param string $test_name Name of the A/B test.
* @return mixed|null A/B test variation, or null on failure.
* @throws \Exception If there is an error retrieving the variation and the environment is not production.
*/
public function get_variation( $test_name ) {
// Default to the control variation when users haven't consented to tracking.
@@ -131,7 +132,11 @@ final class Experimental_Abtest {
$variation = $this->fetch_variation( $test_name );
// If there was an error retrieving a variation, conceal the error for the consumer.
// If there was an error retrieving a variation, throw an exception in non-production environments.
if ( is_wp_error( $variation ) ) {
if ( 'production' !== wp_get_environment_type() ) {
throw new \Exception( $variation->get_error_message() );
}
return 'control';
}
@@ -194,7 +199,7 @@ final class Experimental_Abtest {
}
// Make sure test name is a valid one.
if ( ! preg_match( '/^[A-Za-z0-9_]+$/', $test_name ) ) {
if ( ! preg_match( '/^[a-z0-9_]+$/', $test_name ) ) {
return new \WP_Error( 'invalid_test_name', 'Invalid A/B test name.' );
}

View File

@@ -321,9 +321,9 @@ class WC_REST_Orders_V1_Controller extends WC_REST_Posts_Controller {
foreach ( $order->get_items( 'coupon' ) as $coupon_item_id => $coupon_item ) {
$coupon_line = array(
'id' => $coupon_item_id,
'code' => $coupon_item['name'],
'discount' => wc_format_decimal( $coupon_item['discount_amount'], $dp ),
'discount_tax' => wc_format_decimal( $coupon_item['discount_amount_tax'], $dp ),
'code' => $coupon_item->get_name(),
'discount' => wc_format_decimal( $coupon_item->get_discount(), $dp ),
'discount_tax' => wc_format_decimal( $coupon_item->get_discount_tax(), $dp ),
);
$data['coupon_lines'][] = $coupon_line;

View File

@@ -289,6 +289,25 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
);
}
// Add additional applied coupon information.
if ( $item instanceof WC_Order_Item_Coupon ) {
$temp_coupon = new WC_Coupon();
$coupon_info = $item->get_meta( 'coupon_info', true );
if ( $coupon_info ) {
$temp_coupon->set_short_info( $coupon_info );
} else {
$coupon_meta = $item->get_meta( 'coupon_data', true );
if ( $coupon_meta ) {
$temp_coupon->set_props( (array) $coupon_meta );
}
}
$data['discount_type'] = $temp_coupon->get_discount_type();
$data['nominal_amount'] = (float) $temp_coupon->get_amount();
$data['free_shipping'] = $temp_coupon->get_free_shipping();
}
$data['meta_data'] = array_map(
array( $this, 'merge_meta_item_with_formatted_meta_display_attributes' ),
$data['meta_data'],
@@ -1849,29 +1868,47 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'id' => array(
'description' => __( 'Item ID.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'code' => array(
'code' => array(
'description' => __( 'Coupon code.', 'woocommerce' ),
'type' => 'mixed',
'context' => array( 'view', 'edit' ),
),
'discount' => array(
'discount' => array(
'description' => __( 'Discount total.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'discount_tax' => array(
'discount_tax' => array(
'description' => __( 'Discount total tax.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'meta_data' => array(
'discount_type' => array(
'description' => __( 'Discount type.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'nominal_amount' => array(
'description' => __( 'Discount amount as defined in the coupon (absolute value or a percent, depending on the discount type).', 'woocommerce' ),
'type' => 'number',
'context' => array( 'view' ),
'readonly' => true,
),
'free_shipping' => array(
'description' => __( 'Whether the coupon grants free shipping or not.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'meta_data' => array(
'description' => __( 'Meta data.', 'woocommerce' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),

View File

@@ -13,7 +13,8 @@ defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Internal\WCCom\ConnectionHelper;
use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Register as Download_Directories;
use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer as Order_DataSynchronizer;
use Automattic\WooCommerce\Utilities\OrderUtil;
use Automattic\WooCommerce\Utilities\{ LoggingUtil, OrderUtil };
/**
* System status controller class.
*
@@ -632,6 +633,44 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
'type' => 'string',
),
),
'logging' => array(
'description' => __( 'Logging.', 'woocommerce' ),
'type' => 'object',
'context' => array( 'view' ),
'readonly' => true,
'properties' => array(
'logging_enabled' => array(
'description' => __( 'Is logging enabled?', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'view' ),
'readonly' => true,
),
'default_handler' => array(
'description' => __( 'The logging handler class.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'retention_period_days' => array(
'description' => __( 'The number of days log entries are retained.', 'woocommerce' ),
'type' => 'integer',
'context' => array( 'view' ),
'readonly' => true,
),
'level_threshold' => array(
'description' => __( 'Minimum severity level.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'log_directory_size' => array(
'description' => __( 'The size of the log directory.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
),
),
);
@@ -646,7 +685,7 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
*/
public function get_item_mappings() {
return array(
'environment' => $this->get_environment_info(),
'environment' => $this->get_environment_info_per_fields( array( 'environment' ) ),
'database' => $this->get_database_info(),
'active_plugins' => $this->get_active_plugins(),
'inactive_plugins' => $this->get_inactive_plugins(),
@@ -656,6 +695,7 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
'security' => $this->get_security_info(),
'pages' => $this->get_pages(),
'post_type_counts' => $this->get_post_type_counts(),
'logging' => $this->get_logging_info(),
);
}
@@ -704,6 +744,9 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
case 'post_type_counts':
$items['post_type_counts'] = $this->get_post_type_counts();
break;
case 'logging':
$items['logging'] = $this->get_logging_info();
break;
}
}
@@ -1421,6 +1464,21 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
return $pages_output;
}
/**
* Get info about the logging system.
*
* @return array
*/
protected function get_logging_info() {
return array(
'logging_enabled' => LoggingUtil::logging_is_enabled(),
'default_handler' => LoggingUtil::get_default_handler(),
'retention_period_days' => LoggingUtil::get_retention_period(),
'level_threshold' => WC_Log_Levels::get_level_label( strtolower( LoggingUtil::get_level_threshold() ) ),
'log_directory_size' => size_format( LoggingUtil::get_log_directory_size() ),
);
}
/**
* Get any query params needed.
*

View File

@@ -65,6 +65,30 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V
);
}
/**
* Get the downloads for a product variation.
*
* @param WC_Product_Variation $product Product variation instance.
* @param string $context Context of the request: 'view' or 'edit'.
*
* @return array
*/
protected function get_downloads( $product, $context = 'view' ) {
$downloads = array();
if ( $product->is_downloadable() || 'edit' === $context ) {
foreach ( $product->get_downloads() as $file_id => $file ) {
$downloads[] = array(
'id' => $file_id, // MD5 hash.
'name' => $file['name'],
'file' => $file['file'],
);
}
}
return $downloads;
}
/**
* Prepare a single variation output for response.
*
@@ -95,7 +119,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V
'purchasable' => $object->is_purchasable(),
'virtual' => $object->is_virtual(),
'downloadable' => $object->is_downloadable(),
'downloads' => $this->get_downloads( $object ),
'downloads' => $this->get_downloads( $object, $context ),
'download_limit' => '' !== $object->get_download_limit() ? (int) $object->get_download_limit() : -1,
'download_expiry' => '' !== $object->get_download_expiry() ? (int) $object->get_download_expiry() : -1,
'tax_status' => $object->get_tax_status(),

View File

@@ -9,6 +9,7 @@ namespace Automattic\WooCommerce\RestApi;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\RestApi\Utilities\SingletonTrait;
/**
@@ -37,9 +38,14 @@ class Server {
* Register REST API routes.
*/
public function register_rest_routes() {
$container = wc_get_container();
$legacy_proxy = $container->get( LegacyProxy::class );
foreach ( $this->get_rest_namespaces() as $namespace => $controllers ) {
foreach ( $controllers as $controller_name => $controller_class ) {
$this->controllers[ $namespace ][ $controller_name ] = new $controller_class();
$this->controllers[ $namespace ][ $controller_name ] =
$container->has( $controller_class ) ?
$container->get( $controller_class ) :
$legacy_proxy->get_instance_of( $controller_class );
$this->controllers[ $namespace ][ $controller_name ]->register_routes();
}
}

View File

@@ -9,6 +9,7 @@
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;
defined( 'ABSPATH' ) || exit;
@@ -107,6 +108,7 @@ function wc_add_to_cart_message( $products, $show_qty = false, $return = false )
$products = array_fill_keys( array_keys( $products ), 1 );
}
$product_id = null;
foreach ( $products as $product_id => $qty ) {
/* translators: %s: product name */
$titles[] = apply_filters( 'woocommerce_add_to_cart_qty_html', ( $qty > 1 ? absint( $qty ) . ' &times; ' : '' ), $product_id ) . apply_filters( 'woocommerce_add_to_cart_item_name_in_quotes', sprintf( _x( '&ldquo;%s&rdquo;', 'Item name in quotes', 'woocommerce' ), strip_tags( get_the_title( $product_id ) ) ), $product_id );
@@ -393,6 +395,9 @@ function wc_get_chosen_shipping_method_ids() {
$method_ids = array();
$chosen_methods = WC()->session->get( 'chosen_shipping_methods', array() );
foreach ( $chosen_methods as $chosen_method ) {
if ( ! is_string( $chosen_method ) ) {
continue;
}
$chosen_method = explode( ':', $chosen_method );
$method_ids[] = current( $chosen_method );
}
@@ -405,7 +410,7 @@ function wc_get_chosen_shipping_method_ids() {
* @since 3.2.0
* @param int $key Key of package.
* @param array $package Package data array.
* @return string|bool
* @return string|bool Either the chosen method ID or false if nothing is chosen yet.
*/
function wc_get_chosen_shipping_method_for_package( $key, $package ) {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
@@ -421,6 +426,10 @@ function wc_get_chosen_shipping_method_for_package( $key, $package ) {
$method_count = 0;
}
if ( ! isset( $package['rates'] ) || ! is_array( $package['rates'] ) ) {
$package['rates'] = array();
}
// If not set, not available, or available methods have changed, set to the DEFAULT option.
if ( ! $chosen_method || $changed || ! isset( $package['rates'][ $chosen_method ] ) || count( $package['rates'] ) !== $method_count ) {
$chosen_method = wc_get_default_shipping_method_for_package( $key, $package, $chosen_method );
@@ -441,25 +450,55 @@ function wc_get_chosen_shipping_method_for_package( $key, $package ) {
* @since 3.2.0
* @param int $key Key of package.
* @param array $package Package data array.
* @param string $chosen_method Chosen method id.
* @param string $chosen_method Chosen shipping method. e.g. flat_rate:1.
* @return string
*/
function wc_get_default_shipping_method_for_package( $key, $package, $chosen_method ) {
$rate_keys = array_keys( $package['rates'] );
$default = current( $rate_keys );
$coupons = WC()->cart->get_coupons();
foreach ( $coupons as $coupon ) {
if ( $coupon->get_free_shipping() ) {
foreach ( $rate_keys as $rate_key ) {
if ( 0 === stripos( $rate_key, 'free_shipping' ) ) {
$default = $rate_key;
break;
$chosen_method_id = current( explode( ':', $chosen_method ) );
$rate_keys = array_keys( $package['rates'] );
$chosen_method_exists = in_array( $chosen_method, $rate_keys, true );
/**
* If the customer has selected local pickup, keep it selected if it's still in the package. We don't want to auto
* toggle between shipping and pickup even if available shipping methods are changed.
*
* This is important for block based checkout where there is an explicit toggle between shipping and pickup.
*/
$local_pickup_method_ids = LocalPickupUtils::get_local_pickup_method_ids();
$is_local_pickup_chosen = in_array( $chosen_method_id, $local_pickup_method_ids, true );
// Default to the first method in the package. This can be sorted in the backend by the merchant.
$default = current( $rate_keys );
// Default to local pickup if its chosen already.
if ( $chosen_method_exists && $is_local_pickup_chosen ) {
$default = $chosen_method;
} else {
// Check coupons to see if free shipping is available. If it is, we'll use that method as the default.
$coupons = WC()->cart->get_coupons();
foreach ( $coupons as $coupon ) {
if ( $coupon->get_free_shipping() ) {
foreach ( $rate_keys as $rate_key ) {
if ( 0 === stripos( $rate_key, 'free_shipping' ) ) {
$default = $rate_key;
break;
}
}
break;
}
break;
}
}
return apply_filters( 'woocommerce_shipping_chosen_method', $default, $package['rates'], $chosen_method );
/**
* Filters the default shipping method for a package.
*
* @since 3.2.0
* @param string $default Default shipping method.
* @param array $rates Shipping rates.
* @param string $chosen_method Chosen method id.
*/
return (string) apply_filters( 'woocommerce_shipping_chosen_method', $default, $package['rates'], $chosen_method );
}
/**

View File

@@ -232,7 +232,7 @@ function wc_trim_zeros( $price ) {
* @return float
*/
function wc_round_tax_total( $value, $precision = null ) {
$precision = is_null( $precision ) ? wc_get_price_decimals() : intval( $precision );
$precision = is_null( $precision ) ? wc_get_price_decimals() : intval( $precision );
$rounded_tax = NumberUtil::round( $value, $precision, wc_get_tax_rounding_mode() ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.round_modeFound
return apply_filters( 'wc_round_tax_total', $rounded_tax, $value, $precision, WC_TAX_ROUNDING_MODE );
@@ -371,15 +371,17 @@ function wc_format_coupon_code( $value ) {
/**
* Sanitize a coupon code.
*
* Uses sanitize_post_field since coupon codes are stored as
* post_titles - the sanitization and escaping must match.
* Uses sanitize_post_field since coupon codes are stored as post_titles - the sanitization and escaping must match.
*
* Due to the unfiltered_html captability that some (admin) users have, we need to account for slashes.
*
* @since 3.6.0
* @param string $value Coupon code to format.
* @return string
*/
function wc_sanitize_coupon_code( $value ) {
return wp_filter_kses( sanitize_post_field( 'post_title', $value ?? '', 0, 'db' ) );
$value = wp_kses( sanitize_post_field( 'post_title', $value ?? '', 0, 'db' ), 'entities' );
return current_user_can( 'unfiltered_html' ) ? $value : stripslashes( $value );
}
/**

View File

@@ -29,14 +29,16 @@ function wc_notice_count( $notice_type = '' ) {
$notice_count = 0;
$all_notices = WC()->session->get( 'wc_notices', array() );
if ( isset( $all_notices[ $notice_type ] ) ) {
if ( isset( $all_notices[ $notice_type ] ) && is_array( $all_notices[ $notice_type ] ) ) {
$notice_count = count( $all_notices[ $notice_type ] );
} elseif ( empty( $notice_type ) ) {
foreach ( $all_notices as $notices ) {
$notice_count += count( $notices );
if ( is_countable( $notices ) ) {
$notice_count += count( $notices );
}
}
}

View File

@@ -561,7 +561,7 @@ function wc_create_refund( $args = array() ) {
}
// Negative line items.
if ( count( $args['line_items'] ) > 0 ) {
if ( is_array( $args['line_items'] ) && count( $args['line_items'] ) > 0 ) {
$items = $order->get_items( array( 'line_item', 'fee', 'shipping' ) );
foreach ( $items as $item_id => $item ) {

View File

@@ -991,9 +991,7 @@ function wc_get_price_including_tax( $product, $args = array() ) {
$price = '' !== $args['price'] ? max( 0.0, (float) $args['price'] ) : (float) $product->get_price();
$qty = '' !== $args['qty'] ? max( 0.0, (float) $args['qty'] ) : 1;
if ( '' === $price ) {
return '';
} elseif ( empty( $qty ) ) {
if ( empty( $qty ) ) {
return 0.0;
}
@@ -1080,9 +1078,7 @@ function wc_get_price_excluding_tax( $product, $args = array() ) {
$price = '' !== $args['price'] ? max( 0.0, (float) $args['price'] ) : (float) $product->get_price();
$qty = '' !== $args['qty'] ? max( 0.0, (float) $args['qty'] ) : 1;
if ( '' === $price ) {
return '';
} elseif ( empty( $qty ) ) {
if ( empty( $qty ) ) {
return 0.0;
}

View File

@@ -1435,8 +1435,8 @@ if ( ! function_exists( 'woocommerce_get_product_thumbnail' ) ) {
* Get the product thumbnail, or the placeholder if not set.
*
* @param string $size (default: 'woocommerce_thumbnail').
* @param array $attr Image attributes.
* @param bool $placeholder True to return $placeholder if no image is found, or false to return an empty string.
* @param array $attr Image attributes.
* @param bool $placeholder True to return $placeholder if no image is found, or false to return an empty string.
* @return string
*/
function woocommerce_get_product_thumbnail( $size = 'woocommerce_thumbnail', $attr = array(), $placeholder = true ) {
@@ -2830,6 +2830,8 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
'default' => '',
'autofocus' => '',
'priority' => '',
'unchecked_value' => null,
'checked_value' => '1',
);
$args = wp_parse_args( $args, $defaults );
@@ -2956,8 +2958,24 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
break;
case 'checkbox':
$field = '<label class="checkbox ' . implode( ' ', $args['label_class'] ) . '" ' . implode( ' ', $custom_attributes ) . '>
<input type="' . esc_attr( $args['type'] ) . '" class="input-checkbox ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="1" ' . checked( $value, 1, false ) . ' /> ' . $args['label'] . $required . '</label>';
$field = '<label class="checkbox ' . esc_attr( implode( ' ', $args['label_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . '>';
// Output a hidden field so a value is POSTed if the box is not checked.
if ( ! is_null( $args['unchecked_value'] ) ) {
$field .= sprintf( '<input type="hidden" name="%1$s" value="%2$s" />', esc_attr( $key ), esc_attr( $args['unchecked_value'] ) );
}
$field .= sprintf(
'<input type="checkbox" name="%1$s" id="%2$s" value="%3$s" class="%4$s" %5$s /> %6$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 ),
wp_kses_post( $args['label'] )
);
$field .= $required . '</label>';
break;
case 'text':
@@ -3487,7 +3505,7 @@ if ( ! function_exists( 'wc_display_item_downloads' ) ) {
$downloads = is_object( $item ) && $item->is_type( 'line_item' ) ? $item->get_item_downloads() : array();
if ( $downloads ) {
if ( ! empty( $downloads ) ) {
$i = 0;
foreach ( $downloads as $file ) {
$i ++;
@@ -3836,30 +3854,32 @@ function wc_get_formatted_cart_item_data( $cart_item, $flat = false ) {
// Filter item data to allow 3rd parties to add more to the array.
$item_data = apply_filters( 'woocommerce_get_item_data', $item_data, $cart_item );
// Format item data ready to display.
foreach ( $item_data as $key => $data ) {
// Set hidden to true to not display meta on cart.
if ( ! empty( $data['hidden'] ) ) {
unset( $item_data[ $key ] );
continue;
}
$item_data[ $key ]['key'] = ! empty( $data['key'] ) ? $data['key'] : $data['name'];
$item_data[ $key ]['display'] = ! empty( $data['display'] ) ? $data['display'] : $data['value'];
}
// Output flat or in list format.
if ( count( $item_data ) > 0 ) {
ob_start();
if ( $flat ) {
foreach ( $item_data as $data ) {
echo esc_html( $data['key'] ) . ': ' . wp_kses_post( $data['display'] ) . "\n";
if ( is_array( $item_data ) ) {
// Format item data ready to display.
foreach ( $item_data as $key => $data ) {
// Set hidden to true to not display meta on cart.
if ( ! empty( $data['hidden'] ) ) {
unset( $item_data[ $key ] );
continue;
}
} else {
wc_get_template( 'cart/cart-item-data.php', array( 'item_data' => $item_data ) );
$item_data[ $key ]['key'] = ! empty( $data['key'] ) ? $data['key'] : $data['name'];
$item_data[ $key ]['display'] = ! empty( $data['display'] ) ? $data['display'] : $data['value'];
}
return ob_get_clean();
// Output flat or in list format.
if ( count( $item_data ) > 0 ) {
ob_start();
if ( $flat ) {
foreach ( $item_data as $data ) {
echo esc_html( $data['key'] ) . ': ' . wp_kses_post( $data['display'] ) . "\n";
}
} else {
wc_get_template( 'cart/cart-item-data.php', array( 'item_data' => $item_data ) );
}
return ob_get_clean();
}
}
return '';

View File

@@ -2649,3 +2649,21 @@ LIMIT 250
function wc_update_860_remove_recommended_marketing_plugins_transient() {
delete_transient( 'wc_marketing_recommended_plugins' );
}
/**
* Create an .htaccess file and an empty index.html file to prevent listing of the default transient files directory,
* if the directory exists.
*/
function wc_update_870_prevent_listing_of_transient_files_directory() {
global $wp_filesystem;
$default_transient_files_dir = untrailingslashit( wp_upload_dir()['basedir'] ) . '/woocommerce_transient_files';
if ( ! is_dir( $default_transient_files_dir ) ) {
return;
}
require_once ABSPATH . 'wp-admin/includes/file.php';
\WP_Filesystem();
$wp_filesystem->put_contents( $default_transient_files_dir . '/.htaccess', 'deny from all' );
$wp_filesystem->put_contents( $default_transient_files_dir . '/index.html', '' );
}