plugin updates
This commit is contained in:
@@ -1413,7 +1413,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
|
||||
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
|
||||
// Since WooCommerce 8.7 a succinct '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 );
|
||||
|
||||
@@ -557,7 +557,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
|
||||
}
|
||||
|
||||
// Marketplace promotions.
|
||||
if ( in_array( $screen_id, array( 'woocommerce_page_wc-admin' ), true ) ) {
|
||||
if ( in_array( $screen_id, array( 'edit-shop_coupon', 'woocommerce_page_wc-admin' ), true ) ) {
|
||||
|
||||
$promotions = WC_Admin_Marketplace_Promotions::get_active_promotions();
|
||||
|
||||
|
||||
@@ -0,0 +1,792 @@
|
||||
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName.
|
||||
/**
|
||||
* Brands Admin Page
|
||||
*
|
||||
* Important: For internal use only by the Automattic\WooCommerce\Internal\Brands package.
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 9.4.0
|
||||
*/
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
/**
|
||||
* WC_Brands_Admin class.
|
||||
*/
|
||||
class WC_Brands_Admin {
|
||||
|
||||
/**
|
||||
* Settings array.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $settings_tabs;
|
||||
|
||||
/**
|
||||
* Admin fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $fields = array();
|
||||
|
||||
/**
|
||||
* __construct function.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'scripts' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'styles' ) );
|
||||
add_action( 'product_brand_add_form_fields', array( $this, 'add_thumbnail_field' ) );
|
||||
add_action( 'product_brand_edit_form_fields', array( $this, 'edit_thumbnail_field' ), 10, 1 );
|
||||
add_action( 'created_term', array( $this, 'thumbnail_field_save' ), 10, 1 );
|
||||
add_action( 'edit_term', array( $this, 'thumbnail_field_save' ), 10, 1 );
|
||||
add_action( 'product_brand_pre_add_form', array( $this, 'taxonomy_description' ) );
|
||||
add_filter( 'woocommerce_sortable_taxonomies', array( $this, 'sort_brands' ) );
|
||||
add_filter( 'manage_edit-product_brand_columns', array( $this, 'columns' ) );
|
||||
add_filter( 'manage_product_brand_custom_column', array( $this, 'column' ), 10, 3 );
|
||||
add_filter( 'manage_product_posts_columns', array( $this, 'product_columns' ), 20, 1 );
|
||||
add_filter(
|
||||
'woocommerce_products_admin_list_table_filters',
|
||||
function ( $args ) {
|
||||
$args['product_brand'] = array( $this, 'render_product_brand_filter' );
|
||||
return $args;
|
||||
}
|
||||
);
|
||||
|
||||
$this->settings_tabs = array(
|
||||
'brands' => __( 'Brands', 'woocommerce' ),
|
||||
);
|
||||
|
||||
// Hiding setting for future depreciation. Only users who have touched this settings should see it.
|
||||
$setting_value = get_option( 'wc_brands_show_description' );
|
||||
if ( is_string( $setting_value ) ) {
|
||||
// Add the settings fields to each tab.
|
||||
$this->init_form_fields();
|
||||
add_action( 'woocommerce_get_sections_products', array( $this, 'add_settings_tab' ) );
|
||||
add_action( 'woocommerce_get_settings_products', array( $this, 'add_settings_section' ), null, 2 );
|
||||
}
|
||||
|
||||
add_action( 'woocommerce_update_options_catalog', array( $this, 'save_admin_settings' ) );
|
||||
|
||||
/* 2.1 */
|
||||
add_action( 'woocommerce_update_options_products', array( $this, 'save_admin_settings' ) );
|
||||
|
||||
// Add brands filtering to the coupon creation screens.
|
||||
add_action( 'woocommerce_coupon_options_usage_restriction', array( $this, 'add_coupon_brands_fields' ) );
|
||||
add_action( 'woocommerce_coupon_options_save', array( $this, 'save_coupon_brands' ) );
|
||||
|
||||
// Permalinks.
|
||||
add_filter( 'pre_update_option_woocommerce_permalinks', array( $this, 'validate_product_base' ) );
|
||||
|
||||
add_action( 'current_screen', array( $this, 'add_brand_base_setting' ) );
|
||||
|
||||
// CSV Import/Export Support.
|
||||
// https://github.com/woocommerce/woocommerce/wiki/Product-CSV-Importer-&-Exporter
|
||||
// Import.
|
||||
add_filter( 'woocommerce_csv_product_import_mapping_options', array( $this, 'add_column_to_importer_exporter' ), 10 );
|
||||
add_filter( 'woocommerce_csv_product_import_mapping_default_columns', array( $this, 'add_default_column_mapping' ), 10 );
|
||||
add_filter( 'woocommerce_product_import_inserted_product_object', array( $this, 'process_import' ), 10, 2 );
|
||||
|
||||
// Export.
|
||||
add_filter( 'woocommerce_product_export_column_names', array( $this, 'add_column_to_importer_exporter' ), 10 );
|
||||
add_filter( 'woocommerce_product_export_product_default_columns', array( $this, 'add_column_to_importer_exporter' ), 10 );
|
||||
add_filter( 'woocommerce_product_export_product_column_brand_ids', array( $this, 'get_column_value_brand_ids' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the settings for the new "Brands" subtab.
|
||||
*
|
||||
* @since 9.4.0
|
||||
*
|
||||
* @param array $settings Settings.
|
||||
* @param array $current_section Current section.
|
||||
*/
|
||||
public function add_settings_section( $settings, $current_section ) {
|
||||
if ( 'brands' === $current_section ) {
|
||||
$settings = $this->settings;
|
||||
}
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new "Brands" subtab to the "Products" tab.
|
||||
*
|
||||
* @since 9.4.0
|
||||
* @param array $sections Sections.
|
||||
*/
|
||||
public function add_settings_tab( $sections ) {
|
||||
$sections = array_merge( $sections, $this->settings_tabs );
|
||||
return $sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display coupon filter fields relating to brands.
|
||||
*
|
||||
* @since 9.4.0
|
||||
* @return void
|
||||
*/
|
||||
public function add_coupon_brands_fields() {
|
||||
global $post;
|
||||
// Brands.
|
||||
?>
|
||||
<p class="form-field"><label for="product_brands"><?php esc_html_e( 'Product brands', 'woocommerce' ); ?></label>
|
||||
<select id="product_brands" name="product_brands[]" style="width: 50%;" class="wc-enhanced-select" multiple="multiple" data-placeholder="<?php esc_attr_e( 'Any brand', 'woocommerce' ); ?>">
|
||||
<?php
|
||||
$category_ids = (array) get_post_meta( $post->ID, 'product_brands', true );
|
||||
$categories = get_terms(
|
||||
array(
|
||||
'taxonomy' => 'product_brand',
|
||||
'orderby' => 'name',
|
||||
'hide_empty' => false,
|
||||
)
|
||||
);
|
||||
|
||||
if ( $categories ) {
|
||||
foreach ( $categories as $cat ) {
|
||||
echo '<option value="' . esc_attr( $cat->term_id ) . '"' . selected( in_array( $cat->term_id, $category_ids, true ), true, false ) . '>' . esc_html( $cat->name ) . '</option>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<?php
|
||||
echo wc_help_tip( esc_html__( 'A product must be associated with this brand for the coupon to remain valid or, for "Product Discounts", products with these brands will be discounted.', 'woocommerce' ) );
|
||||
// Exclude Brands.
|
||||
?>
|
||||
<p class="form-field"><label for="exclude_product_brands"><?php esc_html_e( 'Exclude brands', 'woocommerce' ); ?></label>
|
||||
<select id="exclude_product_brands" name="exclude_product_brands[]" style="width: 50%;" class="wc-enhanced-select" multiple="multiple" data-placeholder="<?php esc_attr_e( 'No brands', 'woocommerce' ); ?>">
|
||||
<?php
|
||||
$category_ids = (array) get_post_meta( $post->ID, 'exclude_product_brands', true );
|
||||
$categories = get_terms(
|
||||
array(
|
||||
'taxonomy' => 'product_brand',
|
||||
'orderby' => 'name',
|
||||
'hide_empty' => false,
|
||||
)
|
||||
);
|
||||
|
||||
if ( $categories ) {
|
||||
foreach ( $categories as $cat ) {
|
||||
echo '<option value="' . esc_attr( $cat->term_id ) . '"' . selected( in_array( $cat->term_id, $category_ids, true ), true, false ) . '>' . esc_html( $cat->name ) . '</option>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<?php
|
||||
echo wc_help_tip( esc_html__( 'Product must not be associated with these brands for the coupon to remain valid or, for "Product Discounts", products associated with these brands will not be discounted.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save coupon filter fields relating to brands.
|
||||
*
|
||||
* @since 9.4.0
|
||||
* @param int $post_id Post ID.
|
||||
* @return void
|
||||
*/
|
||||
public function save_coupon_brands( $post_id ) {
|
||||
$product_brands = isset( $_POST['product_brands'] ) ? array_map( 'intval', $_POST['product_brands'] ) : array(); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$exclude_product_brands = isset( $_POST['exclude_product_brands'] ) ? array_map( 'intval', $_POST['exclude_product_brands'] ) : array(); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
|
||||
// Save.
|
||||
update_post_meta( $post_id, 'product_brands', $product_brands );
|
||||
update_post_meta( $post_id, 'exclude_product_brands', $exclude_product_brands );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare form fields to be used in the various tabs.
|
||||
*/
|
||||
public function init_form_fields() {
|
||||
|
||||
/**
|
||||
* Filter Brands settings.
|
||||
*
|
||||
* @since 9.4.0
|
||||
*
|
||||
* @param array $settings Brands settings.
|
||||
*/
|
||||
$this->settings = apply_filters(
|
||||
'woocommerce_brands_settings_fields',
|
||||
array(
|
||||
array(
|
||||
'name' => __( 'Brands Archives', 'woocommerce' ),
|
||||
'type' => 'title',
|
||||
'desc' => '',
|
||||
'id' => 'brands_archives',
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Show description', 'woocommerce' ),
|
||||
'desc' => __( 'Choose to show the brand description on the archive page. Turn this off if you intend to use the description widget instead. Please note: this is only for themes that do not show the description.', 'woocommerce' ),
|
||||
'tip' => '',
|
||||
'id' => 'wc_brands_show_description',
|
||||
'css' => '',
|
||||
'std' => 'yes',
|
||||
'type' => 'checkbox',
|
||||
),
|
||||
array(
|
||||
'type' => 'sectionend',
|
||||
'id' => 'brands_archives',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function scripts() {
|
||||
$screen = get_current_screen();
|
||||
$version = Constants::get_constant( 'WC_VERSION' );
|
||||
$suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min';
|
||||
|
||||
if ( 'edit-product' === $screen->id ) {
|
||||
wp_register_script(
|
||||
'wc-brands-enhanced-select',
|
||||
WC()->plugin_url() . '/assets/js/admin/wc-brands-enhanced-select' . $suffix . '.js',
|
||||
array( 'jquery', 'selectWoo', 'wc-enhanced-select', 'wp-api' ),
|
||||
$version,
|
||||
true
|
||||
);
|
||||
wp_localize_script(
|
||||
'wc-brands-enhanced-select',
|
||||
'wc_brands_enhanced_select_params',
|
||||
array( 'ajax_url' => get_rest_url() . 'brands/search' )
|
||||
);
|
||||
wp_enqueue_script( 'wc-brands-enhanced-select' );
|
||||
}
|
||||
|
||||
if ( in_array( $screen->id, array( 'edit-product_brand' ), true ) ) {
|
||||
wp_enqueue_media();
|
||||
wp_enqueue_style( 'woocommerce_admin_styles' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue styles.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function styles() {
|
||||
$version = Constants::get_constant( 'WC_VERSION' );
|
||||
wp_enqueue_style( 'brands-admin-styles', WC()->plugin_url() . '/assets/css/brands-admin.css', array(), $version );
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin settings function.
|
||||
*/
|
||||
public function admin_settings() {
|
||||
woocommerce_admin_fields( $this->settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save admin settings function.
|
||||
*/
|
||||
public function save_admin_settings() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['section'] ) && 'brands' === $_GET['section'] ) {
|
||||
woocommerce_update_options( $this->settings );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Category thumbnails.
|
||||
*/
|
||||
public function add_thumbnail_field() {
|
||||
global $woocommerce;
|
||||
?>
|
||||
<div class="form-field">
|
||||
<label><?php esc_html_e( 'Thumbnail', 'woocommerce' ); ?></label>
|
||||
<div id="product_cat_thumbnail" style="float:left;margin-right:10px;"><img src="<?php echo esc_url( wc_placeholder_img_src() ); ?>" width="60px" height="60px" /></div>
|
||||
<div style="line-height:60px;">
|
||||
<input type="hidden" id="product_cat_thumbnail_id" name="product_cat_thumbnail_id" />
|
||||
<button type="button" class="upload_image_button button"><?php esc_html_e( 'Upload/Add image', 'woocommerce' ); ?></button>
|
||||
<button type="button" class="remove_image_button button"><?php esc_html_e( 'Remove image', 'woocommerce' ); ?></button>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
|
||||
jQuery(function(){
|
||||
// Only show the "remove image" button when needed
|
||||
if ( ! jQuery('#product_cat_thumbnail_id').val() ) {
|
||||
jQuery('.remove_image_button').hide();
|
||||
}
|
||||
|
||||
// Uploading files
|
||||
var file_frame;
|
||||
|
||||
jQuery(document).on( 'click', '.upload_image_button', function( event ){
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// If the media frame already exists, reopen it.
|
||||
if ( file_frame ) {
|
||||
file_frame.open();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the media frame.
|
||||
file_frame = wp.media.frames.downloadable_file = wp.media({
|
||||
title: '<?php echo esc_js( __( 'Choose an image', 'woocommerce' ) ); ?>',
|
||||
button: {
|
||||
text: '<?php echo esc_js( __( 'Use image', 'woocommerce' ) ); ?>',
|
||||
},
|
||||
multiple: false
|
||||
});
|
||||
|
||||
// When an image is selected, run a callback.
|
||||
file_frame.on( 'select', function() {
|
||||
attachment = file_frame.state().get('selection').first().toJSON();
|
||||
|
||||
jQuery('#product_cat_thumbnail_id').val( attachment.id );
|
||||
jQuery('#product_cat_thumbnail img').attr('src', attachment.url );
|
||||
jQuery('.remove_image_button').show();
|
||||
});
|
||||
|
||||
// Finally, open the modal.
|
||||
file_frame.open();
|
||||
});
|
||||
|
||||
jQuery(document).on( 'click', '.remove_image_button', function( event ){
|
||||
jQuery('#product_cat_thumbnail img').attr('src', '<?php echo esc_js( wc_placeholder_img_src() ); ?>');
|
||||
jQuery('#product_cat_thumbnail_id').val('');
|
||||
jQuery('.remove_image_button').hide();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit thumbnail field row.
|
||||
*
|
||||
* @param WP_Term $term Current taxonomy term object.
|
||||
*/
|
||||
public function edit_thumbnail_field( $term ) {
|
||||
global $woocommerce;
|
||||
|
||||
$image = '';
|
||||
$thumbnail_id = get_term_meta( $term->term_id, 'thumbnail_id', true );
|
||||
if ( $thumbnail_id ) {
|
||||
$image = wp_get_attachment_url( $thumbnail_id );
|
||||
}
|
||||
if ( empty( $image ) ) {
|
||||
$image = wc_placeholder_img_src();
|
||||
}
|
||||
?>
|
||||
<tr class="form-field">
|
||||
<th scope="row" valign="top"><label><?php esc_html_e( 'Thumbnail', 'woocommerce' ); ?></label></th>
|
||||
<td>
|
||||
<div id="product_cat_thumbnail" style="float:left;margin-right:10px;"><img src="<?php echo esc_url( $image ); ?>" width="60px" height="60px" /></div>
|
||||
<div style="line-height:60px;">
|
||||
<input type="hidden" id="product_cat_thumbnail_id" name="product_cat_thumbnail_id" value="<?php echo esc_attr( $thumbnail_id ); ?>" />
|
||||
<button type="button" class="upload_image_button button"><?php esc_html_e( 'Upload/Add image', 'woocommerce' ); ?></button>
|
||||
<button type="button" class="remove_image_button button"><?php esc_html_e( 'Remove image', 'woocommerce' ); ?></button>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
|
||||
jQuery(function(){
|
||||
|
||||
// Only show the "remove image" button when needed
|
||||
if ( ! jQuery('#product_cat_thumbnail_id').val() )
|
||||
jQuery('.remove_image_button').hide();
|
||||
|
||||
// Uploading files
|
||||
var file_frame;
|
||||
|
||||
jQuery(document).on( 'click', '.upload_image_button', function( event ){
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// If the media frame already exists, reopen it.
|
||||
if ( file_frame ) {
|
||||
file_frame.open();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the media frame.
|
||||
file_frame = wp.media.frames.downloadable_file = wp.media({
|
||||
title: '<?php echo esc_js( __( 'Choose an image', 'woocommerce' ) ); ?>',
|
||||
button: {
|
||||
text: '<?php echo esc_js( __( 'Use image', 'woocommerce' ) ); ?>',
|
||||
},
|
||||
multiple: false
|
||||
});
|
||||
|
||||
// When an image is selected, run a callback.
|
||||
file_frame.on( 'select', function() {
|
||||
attachment = file_frame.state().get('selection').first().toJSON();
|
||||
|
||||
jQuery('#product_cat_thumbnail_id').val( attachment.id );
|
||||
jQuery('#product_cat_thumbnail img').attr('src', attachment.url );
|
||||
jQuery('.remove_image_button').show();
|
||||
});
|
||||
|
||||
// Finally, open the modal.
|
||||
file_frame.open();
|
||||
});
|
||||
|
||||
jQuery(document).on( 'click', '.remove_image_button', function( event ){
|
||||
jQuery('#product_cat_thumbnail img').attr('src', '<?php echo esc_js( wc_placeholder_img_src() ); ?>');
|
||||
jQuery('#product_cat_thumbnail_id').val('');
|
||||
jQuery('.remove_image_button').hide();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
<div class="clear"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves thumbnail field.
|
||||
*
|
||||
* @param int $term_id Term ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function thumbnail_field_save( $term_id ) {
|
||||
if ( isset( $_POST['product_cat_thumbnail_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
update_term_meta( $term_id, 'thumbnail_id', absint( $_POST['product_cat_thumbnail_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Description for brand page.
|
||||
*/
|
||||
public function taxonomy_description() {
|
||||
echo wp_kses_post( wpautop( __( 'Brands be added and managed from this screen. You can optionally upload a brand image to display in brand widgets and on brand archives', 'woocommerce' ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort brands function.
|
||||
*
|
||||
* @param array $sortable Sortable array.
|
||||
*/
|
||||
public function sort_brands( $sortable ) {
|
||||
$sortable[] = 'product_brand';
|
||||
return $sortable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add brands column in second-to-last position.
|
||||
*
|
||||
* @since 9.4.0
|
||||
* @param mixed $columns Columns.
|
||||
* @return array
|
||||
*/
|
||||
public function product_columns( $columns ) {
|
||||
if ( empty( $columns ) ) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
$column_index = 'taxonomy-product_brand';
|
||||
$brands_column = $columns[ $column_index ];
|
||||
unset( $columns[ $column_index ] );
|
||||
return array_merge(
|
||||
array_slice( $columns, 0, -2, true ),
|
||||
array( $column_index => $brands_column ),
|
||||
array_slice( $columns, -2, null, true )
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Columns function.
|
||||
*
|
||||
* @param mixed $columns Columns.
|
||||
*/
|
||||
public function columns( $columns ) {
|
||||
if ( empty( $columns ) ) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
$new_columns = array();
|
||||
$new_columns['cb'] = $columns['cb'];
|
||||
$new_columns['thumb'] = __( 'Image', 'woocommerce' );
|
||||
unset( $columns['cb'] );
|
||||
$columns = array_merge( $new_columns, $columns );
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Column function.
|
||||
*
|
||||
* @param mixed $columns Columns.
|
||||
* @param mixed $column Column.
|
||||
* @param mixed $id ID.
|
||||
*/
|
||||
public function column( $columns, $column, $id ) {
|
||||
if ( 'thumb' === $column ) {
|
||||
global $woocommerce;
|
||||
|
||||
$image = '';
|
||||
$thumbnail_id = get_term_meta( $id, 'thumbnail_id', true );
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
$image = wp_get_attachment_url( $thumbnail_id );
|
||||
}
|
||||
if ( empty( $image ) ) {
|
||||
$image = wc_placeholder_img_src();
|
||||
}
|
||||
|
||||
$columns .= '<img src="' . $image . '" alt="Thumbnail" class="wp-post-image" height="48" width="48" />';
|
||||
|
||||
}
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders either dropdown or a search field for brands depending on the threshold value of
|
||||
* woocommerce_product_brand_filter_threshold filter.
|
||||
*/
|
||||
public function render_product_brand_filter() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification
|
||||
$brands_count = (int) wp_count_terms( 'product_brand' );
|
||||
$current_brand_slug = wc_clean( wp_unslash( $_GET['product_brand'] ?? '' ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
|
||||
/**
|
||||
* Filter the brands threshold count.
|
||||
*
|
||||
* @since 9.4.0
|
||||
*
|
||||
* @param int $value Threshold.
|
||||
*/
|
||||
if ( $brands_count <= apply_filters( 'woocommerce_product_brand_filter_threshold', 100 ) ) {
|
||||
wc_product_dropdown_categories(
|
||||
array(
|
||||
'pad_counts' => true,
|
||||
'show_count' => true,
|
||||
'orderby' => 'name',
|
||||
'selected' => $current_brand_slug,
|
||||
'show_option_none' => __( 'Filter by brand', 'woocommerce' ),
|
||||
'option_none_value' => '',
|
||||
'value_field' => 'slug',
|
||||
'taxonomy' => 'product_brand',
|
||||
'name' => 'product_brand',
|
||||
'class' => 'dropdown_product_brand',
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$current_brand = $current_brand_slug ? get_term_by( 'slug', $current_brand_slug, 'product_brand' ) : '';
|
||||
$selected_option = '';
|
||||
if ( $current_brand_slug && $current_brand ) {
|
||||
$selected_option = '<option value="' . esc_attr( $current_brand_slug ) . '" selected="selected">' . esc_html( htmlspecialchars( wp_kses_post( $current_brand->name ) ) ) . '</option>';
|
||||
}
|
||||
$placeholder = esc_attr__( 'Filter by brand', 'woocommerce' );
|
||||
?>
|
||||
<select class="wc-brands-search" name="product_brand" data-placeholder="<?php echo $placeholder; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" data-allow_clear="true">
|
||||
<?php echo $selected_option; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</select>
|
||||
<?php
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification
|
||||
}
|
||||
|
||||
/**
|
||||
* Add brand base permalink setting.
|
||||
*/
|
||||
public function add_brand_base_setting() {
|
||||
$screen = get_current_screen();
|
||||
if ( ! $screen || 'options-permalink' !== $screen->id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_settings_field(
|
||||
'woocommerce_product_brand_slug',
|
||||
__( 'Product brand base', 'woocommerce' ),
|
||||
array( $this, 'product_brand_slug_input' ),
|
||||
'permalink',
|
||||
'optional'
|
||||
);
|
||||
|
||||
$this->save_permalink_settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a slug input box.
|
||||
*/
|
||||
public function product_brand_slug_input() {
|
||||
$permalink = get_option( 'woocommerce_brand_permalink', '' );
|
||||
?>
|
||||
<input name="woocommerce_product_brand_slug" type="text" class="regular-text code" value="<?php echo esc_attr( $permalink ); ?>" placeholder="<?php echo esc_attr_x( 'brand', 'slug', 'woocommerce' ); ?>" />
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save permalnks settings.
|
||||
*
|
||||
* We need to save the options ourselves;
|
||||
* settings api does not trigger save for the permalinks page.
|
||||
*/
|
||||
public function save_permalink_settings() {
|
||||
if ( ! is_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $_POST['permalink_structure'], $_POST['wc-permalinks-nonce'], $_POST['woocommerce_product_brand_slug'] ) && wp_verify_nonce( wp_unslash( $_POST['wc-permalinks-nonce'] ), 'wc-permalinks' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
update_option( 'woocommerce_brand_permalink', wc_sanitize_permalink( trim( wc_clean( wp_unslash( $_POST['woocommerce_product_brand_slug'] ) ) ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the product base.
|
||||
*
|
||||
* Must have an additional slug, not just the brand as the base.
|
||||
*
|
||||
* @param array $value Value.
|
||||
*/
|
||||
public function validate_product_base( $value ) {
|
||||
if ( '/%product_brand%/' === trailingslashit( $value['product_base'] ) ) {
|
||||
$value['product_base'] = '/' . _x( 'product', 'slug', 'woocommerce' ) . $value['product_base'];
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add csv column for importing/exporting.
|
||||
*
|
||||
* @param array $options Mapping options.
|
||||
* @return array $options
|
||||
*/
|
||||
public function add_column_to_importer_exporter( $options ) {
|
||||
$options['brand_ids'] = __( 'Brands', 'woocommerce' );
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default column mapping.
|
||||
*
|
||||
* @param array $mappings Mappings.
|
||||
* @return array $mappings
|
||||
*/
|
||||
public function add_default_column_mapping( $mappings ) {
|
||||
$new_mapping = array( __( 'Brands', 'woocommerce' ) => 'brand_ids' );
|
||||
return array_merge( $mappings, $new_mapping );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add brands to newly imported product.
|
||||
*
|
||||
* @param WC_Product $product Product being imported.
|
||||
* @param array $data Raw CSV data.
|
||||
*/
|
||||
public function process_import( $product, $data ) {
|
||||
if ( empty( $data['brand_ids'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$brand_ids = array_map( 'intval', $this->parse_brands_field( $data['brand_ids'] ) );
|
||||
|
||||
wp_set_object_terms( $product->get_id(), $brand_ids, 'product_brand' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse brands field from a CSV during import.
|
||||
*
|
||||
* Based on WC_Product_CSV_Importer::parse_categories_field()
|
||||
*
|
||||
* @param string $value Field value.
|
||||
* @return array
|
||||
*/
|
||||
public function parse_brands_field( $value ) {
|
||||
|
||||
// Based on WC_Product_Importer::explode_values().
|
||||
$values = str_replace( '\\,', '::separator::', explode( ',', $value ) );
|
||||
$row_terms = array();
|
||||
foreach ( $values as $row_value ) {
|
||||
$row_terms[] = trim( str_replace( '::separator::', ',', $row_value ) );
|
||||
}
|
||||
|
||||
$brands = array();
|
||||
foreach ( $row_terms as $row_term ) {
|
||||
$parent = null;
|
||||
|
||||
// WC Core uses '>', but for some reason it's already escaped at this point.
|
||||
$_terms = array_map( 'trim', explode( '>', $row_term ) );
|
||||
$total = count( $_terms );
|
||||
|
||||
foreach ( $_terms as $index => $_term ) {
|
||||
$term = term_exists( $_term, 'product_brand', $parent );
|
||||
|
||||
if ( is_array( $term ) ) {
|
||||
$term_id = $term['term_id'];
|
||||
} else {
|
||||
$term = wp_insert_term( $_term, 'product_brand', array( 'parent' => intval( $parent ) ) );
|
||||
|
||||
if ( is_wp_error( $term ) ) {
|
||||
break; // We cannot continue if the term cannot be inserted.
|
||||
}
|
||||
|
||||
$term_id = $term['term_id'];
|
||||
}
|
||||
|
||||
// Only requires assign the last category.
|
||||
if ( ( 1 + $index ) === $total ) {
|
||||
$brands[] = $term_id;
|
||||
} else {
|
||||
// Store parent to be able to insert or query brands based in parent ID.
|
||||
$parent = $term_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $brands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get brands column value for csv export.
|
||||
*
|
||||
* @param string $value What will be exported.
|
||||
* @param WC_Product $product Product being exported.
|
||||
* @return string Brands separated by commas and child brands as "parent > child".
|
||||
*/
|
||||
public function get_column_value_brand_ids( $value, $product ) {
|
||||
$brand_ids = wp_parse_id_list( wp_get_post_terms( $product->get_id(), 'product_brand', array( 'fields' => 'ids' ) ) );
|
||||
|
||||
if ( ! count( $brand_ids ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Based on WC_CSV_Exporter::format_term_ids().
|
||||
$formatted_brands = array();
|
||||
foreach ( $brand_ids as $brand_id ) {
|
||||
$formatted_term = array();
|
||||
$ancestor_ids = array_reverse( get_ancestors( $brand_id, 'product_brand' ) );
|
||||
|
||||
foreach ( $ancestor_ids as $ancestor_id ) {
|
||||
$term = get_term( $ancestor_id, 'product_brand' );
|
||||
if ( $term && ! is_wp_error( $term ) ) {
|
||||
$formatted_term[] = $term->name;
|
||||
}
|
||||
}
|
||||
|
||||
$term = get_term( $brand_id, 'product_brand' );
|
||||
|
||||
if ( $term && ! is_wp_error( $term ) ) {
|
||||
$formatted_term[] = $term->name;
|
||||
}
|
||||
|
||||
$formatted_brands[] = implode( ' > ', $formatted_term );
|
||||
}
|
||||
|
||||
// Based on WC_CSV_Exporter::implode_values().
|
||||
$values_to_implode = array();
|
||||
foreach ( $formatted_brands as $brand ) {
|
||||
$brand = (string) is_scalar( $brand ) ? $brand : '';
|
||||
$values_to_implode[] = str_replace( ',', '\\,', $brand );
|
||||
}
|
||||
|
||||
return implode( ', ', $values_to_implode );
|
||||
}
|
||||
}
|
||||
|
||||
$GLOBALS['WC_Brands_Admin'] = new WC_Brands_Admin();
|
||||
@@ -97,6 +97,8 @@ class WC_Admin_Marketplace_Promotions {
|
||||
return array();
|
||||
}
|
||||
|
||||
$promotions = self::merge_promos( $promotions );
|
||||
|
||||
return self::filter_out_inactive_promotions( $promotions );
|
||||
}
|
||||
|
||||
@@ -144,6 +146,7 @@ class WC_Admin_Marketplace_Promotions {
|
||||
}
|
||||
|
||||
$promotions = json_decode( wp_remote_retrieve_body( $raw_promotions ), true );
|
||||
|
||||
if ( ! is_array( $promotions ) ) {
|
||||
$promotions = array();
|
||||
|
||||
@@ -277,6 +280,27 @@ class WC_Admin_Marketplace_Promotions {
|
||||
return $active_promotions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Promos arrive in the array of promotions as an array of arrays with the key 'promos'.
|
||||
* We merge them into the main array.
|
||||
*
|
||||
* @param ?array $promotions Promotions data received from WCCOM.
|
||||
* May have an element with the key 'promos', which contains an array.
|
||||
*
|
||||
* @return array
|
||||
* */
|
||||
private static function merge_promos( ?array $promotions = array() ): array {
|
||||
if (
|
||||
! empty( $promotions['promos'] )
|
||||
&& is_array( $promotions['promos'] )
|
||||
) {
|
||||
$promotions = array_merge( $promotions, $promotions['promos'] );
|
||||
unset( $promotions['promos'] );
|
||||
}
|
||||
|
||||
return $promotions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the `woocommerce_marketplace_menu_items` filter
|
||||
* in `Automattic\WooCommerce\Internal\Admin\Marketplace::get_marketplace_pages`.
|
||||
|
||||
@@ -896,7 +896,8 @@ class WC_Admin_Post_Types {
|
||||
return false;
|
||||
}
|
||||
|
||||
$old_price = (float) $product->{"get_{$price_type}_price"}();
|
||||
$old_price = $product->{"get_{$price_type}_price"}();
|
||||
$old_price = '' === $old_price ? (float) $product->get_regular_price() : (float) $old_price;
|
||||
$price_changed = false;
|
||||
|
||||
$change_price = absint( $request_data[ "change_{$price_type}_price" ] );
|
||||
@@ -906,13 +907,17 @@ class WC_Admin_Post_Types {
|
||||
|
||||
switch ( $change_price ) {
|
||||
case 1:
|
||||
$new_price = $price;
|
||||
if ( empty( $price ) ) {
|
||||
$new_price = $product->get_regular_price();
|
||||
} else {
|
||||
$new_price = $price;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if ( $is_percentage ) {
|
||||
$percent = $price / 100;
|
||||
$new_price = $old_price + ( $old_price * $percent );
|
||||
} else {
|
||||
} elseif ( ! empty( $price ) ) {
|
||||
$new_price = $old_price + $price;
|
||||
}
|
||||
break;
|
||||
@@ -920,7 +925,7 @@ class WC_Admin_Post_Types {
|
||||
if ( $is_percentage ) {
|
||||
$percent = $price / 100;
|
||||
$new_price = max( 0, $old_price - ( $old_price * $percent ) );
|
||||
} else {
|
||||
} elseif ( ! empty( $price ) ) {
|
||||
$new_price = max( 0, $old_price - $price );
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -85,6 +85,7 @@ class WC_Helper_Admin {
|
||||
if ( WC_Helper::is_site_connected() ) {
|
||||
$settings['wccomHelper']['subscription_expired_notice'] = PluginsHelper::get_expired_subscription_notice( false );
|
||||
$settings['wccomHelper']['subscription_expiring_notice'] = PluginsHelper::get_expiring_subscription_notice( false );
|
||||
$settings['wccomHelper']['subscription_missing_notice'] = PluginsHelper::get_missing_subscription_notice();
|
||||
}
|
||||
|
||||
return $settings;
|
||||
|
||||
@@ -40,11 +40,13 @@ class WC_Helper_Updater {
|
||||
}
|
||||
if ( WC_Helper::is_site_connected() ) {
|
||||
add_action( 'load-plugins.php', array( __CLASS__, 'setup_message_for_expired_and_expiring_subscriptions' ), 11 );
|
||||
add_action( 'load-plugins.php', array( __CLASS__, 'setup_message_for_plugins_without_subscription' ), 11 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the hook for modifying default WPCore update notices on the plugins management page.
|
||||
* This is for plugins with expired or expiring subscriptions.
|
||||
*/
|
||||
public static function setup_message_for_expired_and_expiring_subscriptions() {
|
||||
foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) {
|
||||
@@ -52,6 +54,16 @@ class WC_Helper_Updater {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the hook for modifying default WPCore update notices on the plugins management page.
|
||||
* This is for plugins without a subscription.
|
||||
*/
|
||||
public static function setup_message_for_plugins_without_subscription() {
|
||||
foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) {
|
||||
add_action( 'in_plugin_update_message-' . $plugin['_filename'], array( __CLASS__, 'display_notice_for_plugins_without_subscription' ), 10, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in a cron thread, or in a visitor thread if triggered
|
||||
* by _maybe_update_plugins(), or in an auto-update thread.
|
||||
@@ -294,10 +306,11 @@ class WC_Helper_Updater {
|
||||
|
||||
$renew_link = add_query_arg(
|
||||
array(
|
||||
'add-to-cart' => $product_id,
|
||||
'utm_source' => 'pu',
|
||||
'utm_campaign' => 'pu_plugin_screen_renew',
|
||||
),
|
||||
PluginsHelper::WOO_SUBSCRIPTION_PAGE_URL
|
||||
PluginsHelper::WOO_CART_PAGE_URL
|
||||
);
|
||||
|
||||
/* translators: 1: Product regular price */
|
||||
@@ -340,6 +353,52 @@ class WC_Helper_Updater {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on in_plugin_update_message-{file-name}, show a message if plugin is without a subscription.
|
||||
* Only Woo local plugins are passed to this function.
|
||||
*
|
||||
* @see setup_message_for_plugins_without_subscription
|
||||
* @param object $plugin_data An array of plugin metadata.
|
||||
* @param object $response An object of metadata about the available plugin update.
|
||||
*
|
||||
* @return void.
|
||||
*/
|
||||
public static function display_notice_for_plugins_without_subscription( $plugin_data, $response ) {
|
||||
// Extract product ID from the response.
|
||||
$product_id = preg_replace( '/[^0-9]/', '', $response->id );
|
||||
|
||||
if ( WC_Helper::has_product_subscription( $product_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare the expiry notice based on subscription status.
|
||||
$purchase_link = add_query_arg(
|
||||
array(
|
||||
'add-to-cart' => $product_id,
|
||||
'utm_source' => 'pu',
|
||||
'utm_campaign' => 'pu_plugin_screen_purchase',
|
||||
),
|
||||
PluginsHelper::WOO_CART_PAGE_URL,
|
||||
);
|
||||
|
||||
$notice = sprintf(
|
||||
/* translators: 1: URL to My Subscriptions page */
|
||||
__( ' You don\'t have a subscription, <a href="%1$s" class="woocommerce-purchase-subscription">subscribe</a> to update.', 'woocommerce' ),
|
||||
esc_url( $purchase_link ),
|
||||
);
|
||||
|
||||
// Display the expiry notice.
|
||||
echo wp_kses(
|
||||
$notice,
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
'class' => array(),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get update data for all plugins.
|
||||
*
|
||||
|
||||
@@ -371,7 +371,7 @@ class WC_Product_CSV_Importer_Controller {
|
||||
*
|
||||
* @since 3.1.0
|
||||
*/
|
||||
'lines' => apply_filters( 'woocommerce_product_import_batch_size', 30 ),
|
||||
'lines' => apply_filters( 'woocommerce_product_import_batch_size', 1 ),
|
||||
'parse' => true,
|
||||
);
|
||||
|
||||
|
||||
@@ -118,8 +118,14 @@ if ( wc_tax_enabled() ) {
|
||||
<li><strong><?php esc_html_e( 'Coupon(s)', 'woocommerce' ); ?></strong></li>
|
||||
<?php
|
||||
foreach ( $coupons as $item_id => $item ) :
|
||||
$post_id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_title = %s AND post_type = 'shop_coupon' AND post_status = 'publish' LIMIT 1;", $item->get_code() ) ); // phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
$class = $order->is_editable() ? 'code editable' : 'code';
|
||||
$coupon_info = $item->get_meta( 'coupon_info' );
|
||||
if ( $coupon_info ) {
|
||||
$coupon_info = json_decode( $coupon_info, true );
|
||||
$post_id = $coupon_info[0]; //phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
} else {
|
||||
$post_id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_title = %s AND post_type = 'shop_coupon' AND post_status = 'publish' AND post_date < %s LIMIT 1;", $item->get_code(), $order->get_date_created()->format( 'Y-m-d H:i:s' ) ) ); // phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
}
|
||||
$class = $order->is_editable() ? 'code editable' : 'code';
|
||||
?>
|
||||
<li class="<?php echo esc_attr( $class ); ?>">
|
||||
<?php if ( $post_id ) : ?>
|
||||
|
||||
@@ -20,7 +20,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
<strong><?php echo esc_html( wc_attribute_label( $attribute->get_name() ) ); ?></strong>
|
||||
<input type="hidden" name="attribute_names[<?php echo esc_attr( $i ); ?>]" value="<?php echo esc_attr( $attribute->get_name() ); ?>" />
|
||||
<?php else : ?>
|
||||
<input type="text" class="attribute_name" name="attribute_names[<?php echo esc_attr( $i ); ?>]" value="<?php echo esc_attr( $attribute->get_name() ); ?>" placeholder="<?php esc_attr_e( 'f.e. size or color', 'woocommerce' ); ?>" />
|
||||
<input type="text" class="attribute_name" name="attribute_names[<?php echo esc_attr( $i ); ?>]" value="<?php echo esc_attr( $attribute->get_name() ); ?>" placeholder="<?php esc_attr_e( 'e.g. length or weight', 'woocommerce' ); ?>" />
|
||||
<?php endif; ?>
|
||||
<input type="hidden" name="attribute_position[<?php echo esc_attr( $i ); ?>]" class="attribute_position" value="<?php echo esc_attr( $attribute->get_position() ); ?>" />
|
||||
</td>
|
||||
|
||||
@@ -57,6 +57,16 @@ class WC_Settings_Payment_Gateways_React extends WC_Settings_Page {
|
||||
//phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
global $current_section;
|
||||
|
||||
// We don't want to output anything from the action for now. So we buffer it and discard it.
|
||||
ob_start();
|
||||
/**
|
||||
* Fires before the payment gateways settings fields are rendered.
|
||||
*
|
||||
* @since 1.5.7
|
||||
*/
|
||||
do_action( 'woocommerce_admin_field_payment_gateways' );
|
||||
ob_end_clean();
|
||||
|
||||
// Load gateways so we can show any global options they may have.
|
||||
$payment_gateways = WC()->payment_gateways->payment_gateways();
|
||||
|
||||
@@ -91,6 +101,11 @@ class WC_Settings_Payment_Gateways_React extends WC_Settings_Page {
|
||||
global $hide_save_button;
|
||||
$hide_save_button = true;
|
||||
echo '<div id="experimental_wc_settings_payments_' . esc_attr( $section ) . '"></div>';
|
||||
|
||||
// Output the gateways data to the page so the React app can use it.
|
||||
$controller = new WC_REST_Payment_Gateways_Controller();
|
||||
$response = $controller->get_items( new WP_REST_Request( 'GET', '/wc/v3/payment_gateways' ) );
|
||||
echo '<script type="application/json" id="experimental_wc_settings_payments_gateways">' . wp_json_encode( $response->data ) . '</script>';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
<?php // phpcs:disable WordPress.Files.FileName.InvalidClassFileName
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Options;
|
||||
|
||||
//phpcs:disable Squiz.Classes.ClassFileName.NoMatch
|
||||
|
||||
/**
|
||||
* BlockTemplateUtils class used for serving block templates from Woo Blocks.
|
||||
* IMPORTANT: These methods have been duplicated from Gutenberg/lib/full-site-editing/block-templates.php as those functions are not for public usage.
|
||||
*
|
||||
* For internal use only by the Automattic\WooCommerce\Internal\Brands package.
|
||||
*
|
||||
* @version 9.4.0
|
||||
*/
|
||||
class BlockTemplateUtilsDuplicated {
|
||||
|
||||
/**
|
||||
* Directory names for block templates
|
||||
*
|
||||
* Directory names conventions for block templates have changed with Gutenberg 12.1.0,
|
||||
* however, for backwards-compatibility, we also keep the older conventions, prefixed
|
||||
* with `DEPRECATED_`.
|
||||
*
|
||||
* @var array {
|
||||
* @var string DEPRECATED_TEMPLATES Old directory name of the block templates directory.
|
||||
* @var string DEPRECATED_TEMPLATE_PARTS Old directory name of the block template parts directory.
|
||||
* @var string TEMPLATES_DIR_NAME Directory name of the block templates directory.
|
||||
* @var string TEMPLATE_PARTS_DIR_NAME Directory name of the block template parts directory.
|
||||
* }
|
||||
*/
|
||||
protected const DIRECTORY_NAMES = array(
|
||||
'DEPRECATED_TEMPLATES' => 'block-templates',
|
||||
'DEPRECATED_TEMPLATE_PARTS' => 'block-template-parts',
|
||||
'TEMPLATES' => 'templates',
|
||||
'TEMPLATE_PARTS' => 'parts',
|
||||
);
|
||||
|
||||
/**
|
||||
* WooCommerce plugin slug
|
||||
*
|
||||
* This is used to save templates to the DB which are stored against this value in the wp_terms table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected const PLUGIN_SLUG = 'woocommerce/woocommerce';
|
||||
|
||||
/**
|
||||
* Returns an array containing the references of
|
||||
* the passed blocks and their inner blocks.
|
||||
*
|
||||
* @param array $blocks array of blocks.
|
||||
*
|
||||
* @return array block references to the passed blocks and their inner blocks.
|
||||
*/
|
||||
public static function gutenberg_flatten_blocks( &$blocks ) {
|
||||
$all_blocks = array();
|
||||
$queue = array();
|
||||
foreach ( $blocks as &$block ) {
|
||||
$queue[] = &$block;
|
||||
}
|
||||
$queue_count = count( $queue );
|
||||
|
||||
while ( $queue_count > 0 ) {
|
||||
$block = &$queue[0];
|
||||
array_shift( $queue );
|
||||
$all_blocks[] = &$block;
|
||||
|
||||
if ( ! empty( $block['innerBlocks'] ) ) {
|
||||
foreach ( $block['innerBlocks'] as &$inner_block ) {
|
||||
$queue[] = &$inner_block;
|
||||
}
|
||||
}
|
||||
|
||||
$queue_count = count( $queue );
|
||||
}
|
||||
|
||||
return $all_blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses wp_template content and injects the current theme's
|
||||
* stylesheet as a theme attribute into each wp_template_part
|
||||
*
|
||||
* @param string $template_content serialized wp_template content.
|
||||
*
|
||||
* @return string Updated wp_template content.
|
||||
*/
|
||||
public static function gutenberg_inject_theme_attribute_in_content( $template_content ) {
|
||||
$has_updated_content = false;
|
||||
$new_content = '';
|
||||
$template_blocks = parse_blocks( $template_content );
|
||||
|
||||
$blocks = self::gutenberg_flatten_blocks( $template_blocks );
|
||||
foreach ( $blocks as &$block ) {
|
||||
if (
|
||||
'core/template-part' === $block['blockName'] &&
|
||||
! isset( $block['attrs']['theme'] )
|
||||
) {
|
||||
$block['attrs']['theme'] = wp_get_theme()->get_stylesheet();
|
||||
$has_updated_content = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $has_updated_content ) {
|
||||
foreach ( $template_blocks as &$block ) {
|
||||
$new_content .= serialize_block( $block );
|
||||
}
|
||||
|
||||
return $new_content;
|
||||
}
|
||||
|
||||
return $template_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a unified template object based a post Object.
|
||||
*
|
||||
* @param \WP_Post $post Template post.
|
||||
*
|
||||
* @return \WP_Block_Template|\WP_Error Template.
|
||||
*/
|
||||
public static function gutenberg_build_template_result_from_post( $post ) {
|
||||
$terms = get_the_terms( $post, 'wp_theme' );
|
||||
|
||||
if ( is_wp_error( $terms ) ) {
|
||||
return $terms;
|
||||
}
|
||||
|
||||
if ( ! $terms ) {
|
||||
return new \WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$theme = $terms[0]->name;
|
||||
$has_theme_file = true;
|
||||
|
||||
$template = new \WP_Block_Template();
|
||||
$template->wp_id = $post->ID;
|
||||
$template->id = $theme . '//' . $post->post_name;
|
||||
$template->theme = $theme;
|
||||
$template->content = $post->post_content;
|
||||
$template->slug = $post->post_name;
|
||||
$template->source = 'custom';
|
||||
$template->type = $post->post_type;
|
||||
$template->description = $post->post_excerpt;
|
||||
$template->title = $post->post_title;
|
||||
$template->status = $post->post_status;
|
||||
$template->has_theme_file = $has_theme_file;
|
||||
$template->is_custom = false;
|
||||
$template->post_types = array(); // Don't appear in any Edit Post template selector dropdown.
|
||||
|
||||
if ( 'wp_template_part' === $post->post_type ) {
|
||||
$type_terms = get_the_terms( $post, 'wp_template_part_area' );
|
||||
if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) {
|
||||
$template->area = $type_terms[0]->name;
|
||||
}
|
||||
}
|
||||
|
||||
// We are checking 'woocommerce' to maintain legacy templates which are saved to the DB,
|
||||
// prior to updating to use the correct slug.
|
||||
// More information found here: https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5423.
|
||||
if ( self::PLUGIN_SLUG === $theme || 'woocommerce' === strtolower( $theme ) ) {
|
||||
$template->origin = 'plugin';
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a unified template object based on a theme file.
|
||||
*
|
||||
* @param array|object $template_file Theme file.
|
||||
* @param string $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return \WP_Block_Template Template.
|
||||
*/
|
||||
public static function gutenberg_build_template_result_from_file( $template_file, $template_type ) {
|
||||
$template_file = (object) $template_file;
|
||||
|
||||
// If the theme has an archive-products.html template but does not have product taxonomy templates
|
||||
// then we will load in the archive-product.html template from the theme to use for product taxonomies on the frontend.
|
||||
$template_is_from_theme = 'theme' === $template_file->source;
|
||||
$theme_name = wp_get_theme()->get( 'TextDomain' );
|
||||
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
||||
$template_content = file_get_contents( $template_file->path );
|
||||
$template = new \WP_Block_Template();
|
||||
$template->id = $template_is_from_theme ? $theme_name . '//' . $template_file->slug : self::PLUGIN_SLUG . '//' . $template_file->slug;
|
||||
$template->theme = $template_is_from_theme ? $theme_name : self::PLUGIN_SLUG;
|
||||
$template->content = self::gutenberg_inject_theme_attribute_in_content( $template_content );
|
||||
// Plugin was agreed as a valid source value despite existing inline docs at the time of creating: https://github.com/WordPress/gutenberg/issues/36597#issuecomment-976232909.
|
||||
$template->source = $template_file->source ? $template_file->source : 'plugin';
|
||||
$template->slug = $template_file->slug;
|
||||
$template->type = $template_type;
|
||||
$template->title = ! empty( $template_file->title ) ? $template_file->title : self::convert_slug_to_title( $template_file->slug );
|
||||
$template->status = 'publish';
|
||||
$template->has_theme_file = true;
|
||||
$template->origin = $template_file->source;
|
||||
$template->is_custom = false; // Templates loaded from the filesystem aren't custom, ones that have been edited and loaded from the DB are.
|
||||
$template->post_types = array(); // Don't appear in any Edit Post template selector dropdown.
|
||||
$template->area = 'uncategorized';
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new template object so that we can make Woo Blocks default templates available in the current theme should they not have any.
|
||||
*
|
||||
* @param string $template_file Block template file path.
|
||||
* @param string $template_type wp_template or wp_template_part.
|
||||
* @param string $template_slug Block template slug e.g. single-product.
|
||||
* @param bool $template_is_from_theme If the block template file is being loaded from the current theme instead of Woo Blocks.
|
||||
*
|
||||
* @return object Block template object.
|
||||
*/
|
||||
public static function create_new_block_template_object( $template_file, $template_type, $template_slug, $template_is_from_theme = false ) {
|
||||
$theme_name = wp_get_theme()->get( 'TextDomain' );
|
||||
|
||||
$new_template_item = array(
|
||||
'slug' => $template_slug,
|
||||
'id' => $template_is_from_theme ? $theme_name . '//' . $template_slug : self::PLUGIN_SLUG . '//' . $template_slug,
|
||||
'path' => $template_file,
|
||||
'type' => $template_type,
|
||||
'theme' => $template_is_from_theme ? $theme_name : self::PLUGIN_SLUG,
|
||||
// Plugin was agreed as a valid source value despite existing inline docs at the time of creating: https://github.com/WordPress/gutenberg/issues/36597#issuecomment-976232909.
|
||||
'source' => $template_is_from_theme ? 'theme' : 'plugin',
|
||||
'title' => self::convert_slug_to_title( $template_slug ),
|
||||
'description' => '',
|
||||
'post_types' => array(), // Don't appear in any Edit Post template selector dropdown.
|
||||
);
|
||||
|
||||
return (object) $new_template_item;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts template slugs into readable titles.
|
||||
*
|
||||
* @param string $template_slug The templates slug (e.g. single-product).
|
||||
* @return string Human friendly title converted from the slug.
|
||||
*/
|
||||
public static function convert_slug_to_title( $template_slug ) {
|
||||
switch ( $template_slug ) {
|
||||
case 'single-product':
|
||||
return __( 'Single Product', 'woocommerce' );
|
||||
case 'archive-product':
|
||||
return __( 'Product Archive', 'woocommerce' );
|
||||
case 'taxonomy-product_cat':
|
||||
return __( 'Product Category', 'woocommerce' );
|
||||
case 'taxonomy-product_tag':
|
||||
return __( 'Product Tag', 'woocommerce' );
|
||||
default:
|
||||
// Replace all hyphens and underscores with spaces.
|
||||
return ucwords( preg_replace( '/[\-_]/', ' ', $template_slug ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the first matching template part within themes directories
|
||||
*
|
||||
* Since [Gutenberg 12.1.0](https://github.com/WordPress/gutenberg/releases/tag/v12.1.0), the conventions for
|
||||
* block templates and parts directory has changed from `block-templates` and `block-templates-parts`
|
||||
* to `templates` and `parts` respectively.
|
||||
*
|
||||
* This function traverses all possible combinations of directory paths where a template or part
|
||||
* could be located and returns the first one which is readable, prioritizing the new convention
|
||||
* over the deprecated one, but maintaining that one for backwards compatibility.
|
||||
*
|
||||
* @param string $template_slug The slug of the template (i.e. without the file extension).
|
||||
* @param string $template_type Either `wp_template` or `wp_template_part`.
|
||||
*
|
||||
* @return string|null The matched path or `null` if no match was found.
|
||||
*/
|
||||
public static function get_theme_template_path( $template_slug, $template_type = 'wp_template' ) {
|
||||
$template_filename = $template_slug . '.html';
|
||||
$possible_templates_dir = 'wp_template' === $template_type ? array(
|
||||
self::DIRECTORY_NAMES['TEMPLATES'],
|
||||
self::DIRECTORY_NAMES['DEPRECATED_TEMPLATES'],
|
||||
) : array(
|
||||
self::DIRECTORY_NAMES['TEMPLATE_PARTS'],
|
||||
self::DIRECTORY_NAMES['DEPRECATED_TEMPLATE_PARTS'],
|
||||
);
|
||||
|
||||
// Combine the possible root directory names with either the template directory
|
||||
// or the stylesheet directory for child themes.
|
||||
$possible_paths = array_reduce(
|
||||
$possible_templates_dir,
|
||||
function ( $carry, $item ) use ( $template_filename ) {
|
||||
$filepath = DIRECTORY_SEPARATOR . $item . DIRECTORY_SEPARATOR . $template_filename;
|
||||
|
||||
$carry[] = get_template_directory() . $filepath;
|
||||
$carry[] = get_stylesheet_directory() . $filepath;
|
||||
|
||||
return $carry;
|
||||
},
|
||||
array()
|
||||
);
|
||||
|
||||
// Return the first matching.
|
||||
foreach ( $possible_paths as $path ) {
|
||||
if ( is_readable( $path ) ) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the theme has a template. So we know if to load our own in or not.
|
||||
*
|
||||
* @param string $template_name name of the template file without .html extension e.g. 'single-product'.
|
||||
* @return boolean
|
||||
*/
|
||||
public static function theme_has_template( $template_name ) {
|
||||
return (bool) self::get_theme_template_path( $template_name, 'wp_template' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the theme has a template. So we know if to load our own in or not.
|
||||
*
|
||||
* @param string $template_name name of the template file without .html extension e.g. 'single-product'.
|
||||
* @return boolean
|
||||
*/
|
||||
public static function theme_has_template_part( $template_name ) {
|
||||
return (bool) self::get_theme_template_path( $template_name, 'wp_template_part' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if they are using a compatible version of WP, or if not they have a compatible version of the Gutenberg plugin installed.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function supports_block_templates() {
|
||||
if (
|
||||
( ! function_exists( 'wp_is_block_theme' ) || ! wp_is_block_theme() ) &&
|
||||
( ! function_exists( 'gutenberg_supports_block_templates' ) || ! gutenberg_supports_block_templates() )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the blockified templates should be used or not.
|
||||
*
|
||||
* First, we need to make sure WordPress version is higher than 6.1 (lowest that supports Products block).
|
||||
* Then, if the option is not stored on the db, we need to check if the current theme is a block one or not.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function should_use_blockified_product_grid_templates() {
|
||||
$minimum_wp_version = '6.1';
|
||||
|
||||
if ( version_compare( $GLOBALS['wp_version'], $minimum_wp_version, '<' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$use_blockified_templates = wc_string_to_bool( get_option( Options::WC_BLOCK_USE_BLOCKIFIED_PRODUCT_GRID_BLOCK_AS_TEMPLATE ) );
|
||||
|
||||
if ( false === $use_blockified_templates ) {
|
||||
return function_exists( 'wc_current_theme_is_fse_theme' ) && wc_current_theme_is_fse_theme();
|
||||
}
|
||||
|
||||
return $use_blockified_templates;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
//phpcs:disable Squiz.Classes.ClassFileName.NoMatch, Squiz.Classes.ValidClassName.NotCamelCaps
|
||||
/**
|
||||
* Utils for compatibility with WooCommerce Full Site Editor Blocks
|
||||
*
|
||||
* Important: For internal use only by the Automattic\WooCommerce\Internal\Brands package.
|
||||
*
|
||||
* @version 9.4.0
|
||||
*/
|
||||
class WC_Brands_Block_Templates {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'get_block_templates', array( $this, 'get_block_templates' ), 10, 3 );
|
||||
add_filter( 'get_block_file_template', array( $this, 'get_block_file_template' ), 10, 3 );
|
||||
add_filter( 'woocommerce_has_block_template', array( $this, 'has_block_template' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the taxonomy-product_brand template from DB in case a user customized it in FSE
|
||||
*
|
||||
* @return WP_Post|null The taxonomy-product_brand
|
||||
*/
|
||||
private function get_product_brand_template_db() {
|
||||
$posts = get_posts(
|
||||
array(
|
||||
'name' => 'taxonomy-product_brand',
|
||||
'post_type' => 'wp_template',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 1,
|
||||
)
|
||||
);
|
||||
|
||||
if ( count( $posts ) ) {
|
||||
return $posts[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes a bug regarding taxonomies and FSE.
|
||||
* Without this, the system will always load archive-product.php version instead of taxonomy_product_brand.html
|
||||
* it will show a deprecation error if that happens.
|
||||
*
|
||||
* Triggered by woocommerce_has_block_template filter
|
||||
*
|
||||
* @param bool $has_template True if the template is available.
|
||||
* @param string $template_name The name of the template.
|
||||
*
|
||||
* @return bool True if the system is checking archive-product
|
||||
*/
|
||||
public function has_block_template( $has_template, $template_name ) {
|
||||
if ( 'archive-product' === $template_name || 'taxonomy-product_brand' === $template_name ) {
|
||||
$has_template = true;
|
||||
}
|
||||
|
||||
return $has_template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block template for Taxonomy Product Brand. First it attempts to load the last version from DB
|
||||
* Otherwise it loads the file based template.
|
||||
*
|
||||
* @param string $template_type The post_type for the template. Normally wp_template or wp_template_part.
|
||||
*
|
||||
* @return WP_Block_Template The taxonomy-product_brand template.
|
||||
*/
|
||||
private function get_product_brands_template( $template_type ) {
|
||||
$template_db = $this->get_product_brand_template_db();
|
||||
|
||||
if ( $template_db ) {
|
||||
return BlockTemplateUtilsDuplicated::gutenberg_build_template_result_from_post( $template_db );
|
||||
}
|
||||
|
||||
$template_path = BlockTemplateUtilsDuplicated::should_use_blockified_product_grid_templates()
|
||||
? WC()->plugin_path() . '/templates/templates/blockified/taxonomy-product_brand.html'
|
||||
: WC()->plugin_path() . '/templates/templates/taxonomy-product_brand.html';
|
||||
|
||||
$template_file = BlockTemplateUtilsDuplicated::create_new_block_template_object( $template_path, $template_type, 'taxonomy-product_brand', false );
|
||||
|
||||
return BlockTemplateUtilsDuplicated::gutenberg_build_template_result_from_file( $template_file, $template_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check if a template name is woocommerce/taxonomy-product_brand
|
||||
*
|
||||
* Notice depending on the version of WooCommerce this could be:
|
||||
*
|
||||
* woocommerce//taxonomy-product_brand
|
||||
* woocommerce/woocommerce//taxonomy-product_brand
|
||||
*
|
||||
* @param String $id The string to check if contains the template name.
|
||||
*
|
||||
* @return bool True if the template is woocommerce/taxonomy-product_brand
|
||||
*/
|
||||
private function is_taxonomy_product_brand_template( $id ) {
|
||||
return strpos( $id, 'woocommerce//taxonomy-product_brand' ) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block template for Taxonomy Product Brand if requested.
|
||||
* Triggered by get_block_file_template action
|
||||
*
|
||||
* @param WP_Block_Template|null $block_template The current Block Template loaded, if any.
|
||||
* @param string $id The template id normally in the format theme-slug//template-slug.
|
||||
* @param string $template_type The post_type for the template. Normally wp_template or wp_template_part.
|
||||
*
|
||||
* @return WP_Block_Template|null The taxonomy-product_brand template.
|
||||
*/
|
||||
public function get_block_file_template( $block_template, $id, $template_type ) {
|
||||
if ( $this->is_taxonomy_product_brand_template( $id ) && is_null( $block_template ) ) {
|
||||
$block_template = $this->get_product_brands_template( $template_type );
|
||||
}
|
||||
|
||||
return $block_template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Block template in the template query results needed by FSE
|
||||
* Triggered by get_block_templates action
|
||||
*
|
||||
* @param array $query_result The list of templates to render in the query.
|
||||
* @param array $query The current query parameters.
|
||||
* @param string $template_type The post_type for the template. Normally wp_template or wp_template_part.
|
||||
*
|
||||
* @return WP_Block_Template[] Array of the matched Block Templates to render.
|
||||
*/
|
||||
public function get_block_templates( $query_result, $query, $template_type ) {
|
||||
// We don't want to run this if we are looking for template-parts. Like the header.
|
||||
if ( 'wp_template' !== $template_type ) {
|
||||
return $query_result;
|
||||
}
|
||||
|
||||
$post_id = isset( $_REQUEST['postId'] ) ? wc_clean( wp_unslash( $_REQUEST['postId'] ) ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$slugs = $query['slug__in'] ?? array();
|
||||
|
||||
// Only add the template if asking for Product Brands.
|
||||
if (
|
||||
in_array( 'taxonomy-product_brand', $slugs, true ) ||
|
||||
( ! $post_id && ! count( $slugs ) ) ||
|
||||
( ! count( $slugs ) && $this->is_taxonomy_product_brand_template( $post_id ) )
|
||||
) {
|
||||
$query_result[] = $this->get_product_brands_template( $template_type );
|
||||
}
|
||||
|
||||
return $query_result;
|
||||
}
|
||||
}
|
||||
|
||||
new WC_Brands_Block_Templates();
|
||||
@@ -77,6 +77,11 @@ class WC_Autoloader {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the class is already loaded from a merged package, prevent autoloader from loading it as well.
|
||||
if ( \Automattic\WooCommerce\Packages::should_load_class( $class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = $this->get_file_name_from_class( $class );
|
||||
$path = '';
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
//phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
/**
|
||||
* Brand settings manager.
|
||||
*
|
||||
* This class is responsible for setting and getting brand settings for a coupon.
|
||||
*
|
||||
* Important: For internal use only by the Automattic\WooCommerce\Internal\Brands package.
|
||||
*
|
||||
* @version 9.4.0
|
||||
*/
|
||||
class WC_Brands_Brand_Settings_Manager {
|
||||
/**
|
||||
* Brand settings for a coupon.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $brand_settings = array();
|
||||
|
||||
/**
|
||||
* Set brand settings for a coupon.
|
||||
*
|
||||
* @param WC_Coupon $coupon Coupon object.
|
||||
*/
|
||||
public static function set_brand_settings_on_coupon( $coupon ) {
|
||||
$coupon_id = $coupon->get_id();
|
||||
|
||||
// Check if the brand settings are already set for this coupon.
|
||||
if ( isset( self::$brand_settings[ $coupon_id ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$included_brands = get_post_meta( $coupon_id, 'product_brands', true );
|
||||
$included_brands = ! empty( $included_brands ) ? $included_brands : array();
|
||||
|
||||
$excluded_brands = get_post_meta( $coupon_id, 'exclude_product_brands', true );
|
||||
$excluded_brands = ! empty( $excluded_brands ) ? $excluded_brands : array();
|
||||
|
||||
// Store these settings in the static array.
|
||||
self::$brand_settings[ $coupon_id ] = array(
|
||||
'included_brands' => $included_brands,
|
||||
'excluded_brands' => $excluded_brands,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get brand settings for a coupon.
|
||||
*
|
||||
* @param WC_Coupon $coupon Coupon object.
|
||||
* @return array Brand settings (included and excluded brands).
|
||||
*/
|
||||
public static function get_brand_settings_on_coupon( $coupon ) {
|
||||
$coupon_id = $coupon->get_id();
|
||||
|
||||
if ( isset( self::$brand_settings[ $coupon_id ] ) ) {
|
||||
return self::$brand_settings[ $coupon_id ];
|
||||
}
|
||||
|
||||
// Default return value if no settings are found.
|
||||
return array(
|
||||
'included_brands' => array(),
|
||||
'excluded_brands' => array(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
/**
|
||||
* WC_Brands_Coupons class.
|
||||
*
|
||||
* Important: For internal use only by the Automattic\WooCommerce\Internal\Brands package.
|
||||
*
|
||||
* @version 9.4.0
|
||||
*/
|
||||
class WC_Brands_Coupons {
|
||||
|
||||
const E_WC_COUPON_EXCLUDED_BRANDS = 301;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
// Coupon validation and error handling.
|
||||
add_filter( 'woocommerce_coupon_is_valid', array( $this, 'is_coupon_valid' ), 10, 3 );
|
||||
add_filter( 'woocommerce_coupon_is_valid_for_product', array( $this, 'is_valid_for_product' ), 10, 3 );
|
||||
add_filter( 'woocommerce_coupon_error', array( $this, 'brand_exclusion_error' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the coupon based on included and/or excluded product brands.
|
||||
*
|
||||
* If one of the following conditions are met, an exception will be thrown and
|
||||
* displayed as an error notice on the cart page:
|
||||
*
|
||||
* 1) Coupon has a brand requirement but no products in the cart have the brand.
|
||||
* 2) All products in the cart match the brand exclusion rule.
|
||||
* 3) For a cart discount, there is at least one product in cart that matches exclusion rule.
|
||||
*
|
||||
* @throws Exception Throws Exception for invalid coupons.
|
||||
* @param bool $valid Whether the coupon is valid.
|
||||
* @param WC_Coupon $coupon Coupon object.
|
||||
* @param WC_Discounts $discounts Discounts object.
|
||||
* @return bool $valid True if coupon is valid, otherwise Exception will be thrown.
|
||||
*/
|
||||
public function is_coupon_valid( $valid, $coupon, $discounts = null ) {
|
||||
$this->set_brand_settings_on_coupon( $coupon );
|
||||
|
||||
// Only check if coupon has brand restrictions on it.
|
||||
$brand_coupon_settings = WC_Brands_Brand_Settings_Manager::get_brand_settings_on_coupon( $coupon );
|
||||
|
||||
$brand_restrictions = ! empty( $brand_coupon_settings['included_brands'] ) || ! empty( $brand_coupon_settings['excluded_brands'] );
|
||||
if ( ! $brand_restrictions ) {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
$included_brands_match = false;
|
||||
$excluded_brands_matches = 0;
|
||||
|
||||
$items = $discounts->get_items();
|
||||
|
||||
foreach ( $items as $item ) {
|
||||
$product_brands = $this->get_product_brands( $this->get_product_id( $item->product ) );
|
||||
|
||||
if ( ! empty( array_intersect( $product_brands, $brand_coupon_settings['included_brands'] ) ) ) {
|
||||
$included_brands_match = true;
|
||||
}
|
||||
|
||||
if ( ! empty( array_intersect( $product_brands, $brand_coupon_settings['excluded_brands'] ) ) ) {
|
||||
++$excluded_brands_matches;
|
||||
}
|
||||
}
|
||||
|
||||
// 1) Coupon has a brand requirement but no products in the cart have the brand.
|
||||
if ( ! $included_brands_match && ! empty( $brand_coupon_settings['included_brands'] ) ) {
|
||||
throw new Exception( $coupon->get_coupon_error( WC_Coupon::E_WC_COUPON_NOT_APPLICABLE ), WC_Coupon::E_WC_COUPON_NOT_APPLICABLE ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
}
|
||||
|
||||
// 2) All products in the cart match brand exclusion rule.
|
||||
if ( count( $items ) === $excluded_brands_matches ) {
|
||||
throw new Exception( __( 'Sorry, this coupon is not applicable to the brands of selected products.', 'woocommerce' ), self::E_WC_COUPON_EXCLUDED_BRANDS ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
}
|
||||
|
||||
// 3) For a cart discount, there is at least one product in cart that matches exclusion rule.
|
||||
if ( $coupon->is_type( 'fixed_cart' ) && $excluded_brands_matches > 0 ) {
|
||||
throw new Exception( __( 'Sorry, this coupon is not applicable to the brands of selected products.', 'woocommerce' ), self::E_WC_COUPON_EXCLUDED_BRANDS ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a coupon is valid for a product.
|
||||
*
|
||||
* This allows percentage and product discounts to apply to only
|
||||
* the correct products in the cart.
|
||||
*
|
||||
* @param bool $valid Whether the product should get the coupon's discounts.
|
||||
* @param WC_Product $product WC Product Object.
|
||||
* @param WC_Coupon $coupon Coupon object.
|
||||
* @return bool $valid
|
||||
*/
|
||||
public function is_valid_for_product( $valid, $product, $coupon ) {
|
||||
|
||||
if ( ! is_a( $product, 'WC_Product' ) ) {
|
||||
return $valid;
|
||||
}
|
||||
$this->set_brand_settings_on_coupon( $coupon );
|
||||
|
||||
$product_id = $this->get_product_id( $product );
|
||||
$product_brands = $this->get_product_brands( $product_id );
|
||||
|
||||
// Check if coupon has a brand requirement and if this product has that brand attached.
|
||||
$brand_coupon_settings = WC_Brands_Brand_Settings_Manager::get_brand_settings_on_coupon( $coupon );
|
||||
if ( ! empty( $brand_coupon_settings['included_brands'] ) && empty( array_intersect( $product_brands, $brand_coupon_settings['included_brands'] ) ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if coupon has a brand exclusion and if this product has that brand attached.
|
||||
if ( ! empty( $brand_coupon_settings['excluded_brands'] ) && ! empty( array_intersect( $product_brands, $brand_coupon_settings['excluded_brands'] ) ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a custom error message when a cart discount coupon does not validate
|
||||
* because an excluded brand was found in the cart.
|
||||
*
|
||||
* @param string $err The error message.
|
||||
* @param string $err_code The error code.
|
||||
* @return string
|
||||
*/
|
||||
public function brand_exclusion_error( $err, $err_code ) {
|
||||
if ( self::E_WC_COUPON_EXCLUDED_BRANDS !== $err_code ) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
return __( 'Sorry, this coupon is not applicable to the brands of selected products.', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of brands that are assigned to a specific product
|
||||
*
|
||||
* @param int $product_id Product id.
|
||||
* @return array brands
|
||||
*/
|
||||
private function get_product_brands( $product_id ) {
|
||||
return wp_get_post_terms( $product_id, 'product_brand', array( 'fields' => 'ids' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set brand settings as properties on coupon object. These properties are
|
||||
* lists of included product brand IDs and list of excluded brand IDs.
|
||||
*
|
||||
* @param WC_Coupon $coupon Coupon object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function set_brand_settings_on_coupon( $coupon ) {
|
||||
$brand_coupon_settings = WC_Brands_Brand_Settings_Manager::get_brand_settings_on_coupon( $coupon );
|
||||
|
||||
if ( ! empty( $brand_coupon_settings['included_brands'] ) && ! empty( $brand_coupon_settings['excluded_brands'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$included_brands = get_post_meta( $coupon->get_id(), 'product_brands', true );
|
||||
if ( empty( $included_brands ) ) {
|
||||
$included_brands = array();
|
||||
}
|
||||
|
||||
$excluded_brands = get_post_meta( $coupon->get_id(), 'exclude_product_brands', true );
|
||||
if ( empty( $excluded_brands ) ) {
|
||||
$excluded_brands = array();
|
||||
}
|
||||
|
||||
// Store these for later to avoid multiple look-ups.
|
||||
WC_Brands_Brand_Settings_Manager::set_brand_settings_on_coupon( $coupon );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the product (or variant) ID.
|
||||
*
|
||||
* @param WC_Product $product WC Product Object.
|
||||
* @return int Product ID
|
||||
*/
|
||||
private function get_product_id( $product ) {
|
||||
return $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id();
|
||||
}
|
||||
}
|
||||
|
||||
new WC_Brands_Coupons();
|
||||
1070
wp/wp-content/plugins/woocommerce/includes/class-wc-brands.php
Normal file
1070
wp/wp-content/plugins/woocommerce/includes/class-wc-brands.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1132,7 +1132,17 @@ class WC_Checkout {
|
||||
);
|
||||
|
||||
if ( is_wp_error( $customer_id ) ) {
|
||||
throw new Exception( $customer_id->get_error_message() );
|
||||
if ( 'registration-error-email-exists' === $customer_id->get_error_code() ) {
|
||||
/**
|
||||
* Filter the notice shown when a customer tries to register with an existing email address.
|
||||
*
|
||||
* @since 3.3.0
|
||||
* @param string $message The notice.
|
||||
* @param string $email The email address.
|
||||
*/
|
||||
throw new Exception( apply_filters( 'woocommerce_registration_error_email_exists', __( 'An account is already registered with your email address. <a href="#" class="showlogin">Please log in.</a>', 'woocommerce' ), $data['billing_email'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
}
|
||||
throw new Exception( $customer_id->get_error_message() ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
}
|
||||
|
||||
wc_set_customer_auth_cookie( $customer_id );
|
||||
@@ -1357,7 +1367,7 @@ class WC_Checkout {
|
||||
|
||||
if ( is_callable( array( $customer_object, "get_$input" ) ) ) {
|
||||
$value = $customer_object->{"get_$input"}();
|
||||
} elseif ( $customer_object->meta_exists( $input ) ) {
|
||||
} elseif ( is_callable( array( $customer_object, 'meta_exists' ) ) && $customer_object->meta_exists( $input ) ) {
|
||||
$value = $customer_object->get_meta( $input, true );
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class WC_Coupon extends WC_Legacy_Coupon {
|
||||
* Error message.
|
||||
*
|
||||
* This property should not be considered public API, and should not be accessed directly.
|
||||
* It is being added to supress PHP > 8.0 warnings against dynamic property creation, and all access
|
||||
* It is being added to suppress PHP > 8.0 warnings against dynamic property creation, and all access
|
||||
* should be through the getter and setter methods, namely `get_error_message()` and `set_error_message()`.
|
||||
* In the future, the access modifier may be changed back to protected.
|
||||
*
|
||||
|
||||
@@ -986,7 +986,7 @@ class WC_Form_Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// Peform the login.
|
||||
// Perform the login.
|
||||
$user = wp_signon( apply_filters( 'woocommerce_login_credentials', $creds ), is_ssl() );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
|
||||
@@ -633,7 +633,7 @@ class WC_Geo_IP {
|
||||
);
|
||||
|
||||
/**
|
||||
* Contry names.
|
||||
* Country names.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,7 @@ use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Synchro
|
||||
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
|
||||
use Automattic\WooCommerce\Internal\WCCom\ConnectionHelper as WCConnectionHelper;
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
use Automattic\WooCommerce\Utilities\{ OrderUtil, PluginUtil };
|
||||
use Automattic\WooCommerce\Internal\Utilities\PluginInstaller;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
@@ -266,6 +266,10 @@ class WC_Install {
|
||||
'wc_update_930_add_woocommerce_coming_soon_option',
|
||||
'wc_update_930_migrate_user_meta_for_launch_your_store_tour',
|
||||
),
|
||||
'9.4.0' => array(
|
||||
'wc_update_940_add_phone_to_order_address_fts_index',
|
||||
'wc_update_940_remove_help_panel_highlight_shown',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -1283,7 +1287,8 @@ class WC_Install {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( in_array( $legacy_api_plugin, wp_get_active_and_valid_plugins(), true ) ) {
|
||||
$active_valid_plugins = wc_get_container()->get( PluginUtil::class )->get_all_active_valid_plugins();
|
||||
if ( in_array( $legacy_api_plugin, $active_valid_plugins, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ class WC_Post_Data {
|
||||
|
||||
// Meta cache flushing.
|
||||
add_action( 'updated_post_meta', array( __CLASS__, 'flush_object_meta_cache' ), 10, 4 );
|
||||
add_action( 'added_post_meta', array( __CLASS__, 'flush_object_meta_cache' ), 10, 4 );
|
||||
add_action( 'deleted_post_meta', array( __CLASS__, 'flush_object_meta_cache' ), 10, 4 );
|
||||
add_action( 'updated_order_item_meta', array( __CLASS__, 'flush_object_meta_cache' ), 10, 4 );
|
||||
}
|
||||
|
||||
|
||||
@@ -357,7 +357,7 @@ class WC_Privacy_Erasers {
|
||||
* Allow extensions to remove their own personal data for this order.
|
||||
*
|
||||
* @since 3.4.0
|
||||
* @param WC_Order $order A customer object.
|
||||
* @param WC_Order $order Order instance.
|
||||
*/
|
||||
do_action( 'woocommerce_privacy_remove_order_personal_data', $order );
|
||||
}
|
||||
|
||||
@@ -214,9 +214,9 @@ class WC_Structured_Data {
|
||||
$markup['sku'] = $product->get_id();
|
||||
}
|
||||
|
||||
// Add GTIN only if it's a valid number.
|
||||
$gtin = $product->get_global_unique_id();
|
||||
if ( $gtin && is_numeric( $gtin ) ) {
|
||||
// Prepare GTIN and load it if it's valid.
|
||||
$gtin = $this->prepare_gtin( $product->get_global_unique_id() );
|
||||
if ( $this->is_valid_gtin( $gtin ) ) {
|
||||
$markup['gtin'] = $gtin;
|
||||
}
|
||||
|
||||
@@ -577,4 +577,30 @@ class WC_Structured_Data {
|
||||
|
||||
$this->set_data( apply_filters( 'woocommerce_structured_data_order', $markup, $sent_to_admin, $order ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a GTIN is valid.
|
||||
* A valid GTIN is a string containing 8,12,13 or 14 digits.
|
||||
*
|
||||
* @see https://schema.org/gtin
|
||||
* @param string $gtin The GTIN to check.
|
||||
* @return bool True if valid. False otherwise.
|
||||
*/
|
||||
public function is_valid_gtin( $gtin ) {
|
||||
return is_string( $gtin ) && preg_match( '/^(\d{8}|\d{12,14})$/', $gtin );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a GTIN input removing everything except numbers.
|
||||
*
|
||||
* @param string $gtin The GTIN to prepare.
|
||||
* @return string Empty string if no GTIN is provided or the string with the replacements.
|
||||
*/
|
||||
public function prepare_gtin( $gtin ) {
|
||||
if ( ! $gtin || ! is_string( $gtin ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return preg_replace( '/[^0-9]/', '', $gtin );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
use Automattic\WooCommerce\Internal\Utilities\LegacyRestApiStub;
|
||||
use Automattic\WooCommerce\Internal\Utilities\WebhookUtil;
|
||||
use Automattic\WooCommerce\Internal\Admin\Marketplace;
|
||||
use Automattic\WooCommerce\Internal\McStats;
|
||||
use Automattic\WooCommerce\Proxies\LegacyProxy;
|
||||
use Automattic\WooCommerce\Utilities\{LoggingUtil, RestApiUtil, TimeUtil};
|
||||
use Automattic\WooCommerce\Internal\Logging\RemoteLogger;
|
||||
@@ -46,7 +45,7 @@ final class WooCommerce {
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $version = '9.3.3';
|
||||
public $version = '9.4.1';
|
||||
|
||||
/**
|
||||
* WooCommerce Schema version.
|
||||
@@ -384,8 +383,10 @@ final class WooCommerce {
|
||||
unset( $error_copy['message'] );
|
||||
|
||||
$context = array(
|
||||
'source' => 'fatal-errors',
|
||||
'error' => $error_copy,
|
||||
'source' => 'fatal-errors',
|
||||
'error' => $error_copy,
|
||||
// Indicate that this error should be logged remotely if remote logging is enabled.
|
||||
'remote-logging' => true,
|
||||
);
|
||||
|
||||
if ( false !== strpos( $message, 'Stack trace:' ) ) {
|
||||
@@ -407,12 +408,6 @@ final class WooCommerce {
|
||||
$context
|
||||
);
|
||||
|
||||
// Record fatal error stats.
|
||||
$container = wc_get_container();
|
||||
$mc_stats = $container->get( McStats::class );
|
||||
$mc_stats->add( 'error', 'fatal-errors-during-shutdown' );
|
||||
$mc_stats->do_server_side_stats();
|
||||
|
||||
/**
|
||||
* Action triggered when there are errors during shutdown.
|
||||
*
|
||||
@@ -873,7 +868,7 @@ final class WooCommerce {
|
||||
*/
|
||||
$locale = apply_filters( 'plugin_locale', $locale, 'woocommerce' ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingSinceComment
|
||||
|
||||
unload_textdomain( 'woocommerce' );
|
||||
unload_textdomain( 'woocommerce', true );
|
||||
load_textdomain( 'woocommerce', WP_LANG_DIR . '/woocommerce/woocommerce-' . $locale . '.mo' );
|
||||
load_plugin_textdomain( 'woocommerce', false, plugin_basename( dirname( WC_PLUGIN_FILE ) ) . '/i18n/languages' );
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ class WC_CLI_COM_Command {
|
||||
* # force connecting to WCCOM even if site is already connected.
|
||||
* $ wp wc com connect --force
|
||||
*
|
||||
* # Pass password to comman.
|
||||
* # Pass password to command.
|
||||
* $ wp wc com connect --password=PASSWORD
|
||||
*
|
||||
* @param array $args Positional arguments to include when calling the command.
|
||||
|
||||
@@ -145,8 +145,35 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
|
||||
return boolval( $locked );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$result = $wpdb->query( $query );
|
||||
// The insert query can potentially result in a deadlock if there is high concurrency
|
||||
// when trying to insert products, which will result in a false negative for SKU lock
|
||||
// and incorrectly products not being created.
|
||||
// To mitigate this, we will retry the query 3 times before giving up.
|
||||
for ( $attempts = 0; $attempts < 3; $attempts++ ) {
|
||||
if ( $attempts > 1 ) {
|
||||
usleep( 10000 );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$result = $wpdb->query( $query );
|
||||
if ( false !== $result ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( false === $result ) {
|
||||
wc_get_logger()->warning(
|
||||
sprintf(
|
||||
'Failed to obtain SKU lock for product: ID "%d" with SKU "%s" after %d attempts.',
|
||||
$product_id,
|
||||
$sku,
|
||||
$attempts,
|
||||
),
|
||||
array(
|
||||
'error' => $wpdb->last_error,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
@@ -109,10 +109,11 @@ if ( ! class_exists( 'WC_Email_New_Order' ) ) :
|
||||
}
|
||||
|
||||
if ( $this->is_enabled() && $this->get_recipient() ) {
|
||||
$this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() );
|
||||
|
||||
$order->update_meta_data( '_new_order_email_sent', 'true' );
|
||||
$order->save();
|
||||
$email_sent_successfully = $this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() );
|
||||
if ( $email_sent_successfully ) {
|
||||
$order->update_meta_data( '_new_order_email_sent', 'true' );
|
||||
$order->save();
|
||||
}
|
||||
}
|
||||
|
||||
$this->restore_locale();
|
||||
|
||||
@@ -262,7 +262,7 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter {
|
||||
* @since 3.1.0
|
||||
*
|
||||
* @param array $row An associative array with the data of a single row in the CSV file.
|
||||
* @param WC_Product $product The product object correspnding to the current row.
|
||||
* @param WC_Product $product The product object corresponding to the current row.
|
||||
* @param WC_Product_CSV_Exporter $exporter The instance of the CSV exporter.
|
||||
*/
|
||||
return apply_filters( 'woocommerce_product_export_row_data', $row, $product, $this );
|
||||
|
||||
@@ -198,7 +198,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
|
||||
return absint( $original_id );
|
||||
}
|
||||
|
||||
// See if the given ID maps to a valid product allready.
|
||||
// See if the given ID maps to a valid product already.
|
||||
$existing_id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type IN ( 'product', 'product_variation' ) AND ID = %d;", $id ) ); // WPCS: db call ok, cache ok.
|
||||
|
||||
if ( $existing_id ) {
|
||||
|
||||
@@ -91,7 +91,7 @@ interface WC_Queue_Interface {
|
||||
public function cancel_all( $hook, $args = array(), $group = '' );
|
||||
|
||||
/**
|
||||
* Get the date and time for the next scheduled occurence of an action with a given hook
|
||||
* Get the date and time for the next scheduled occurrence of an action with a given hook
|
||||
* (an optionally that matches certain args and group), if any.
|
||||
*
|
||||
* @param string $hook The hook that the job will trigger.
|
||||
|
||||
@@ -7,6 +7,7 @@ if ( ! function_exists( 'wc_admin_get_feature_config' ) ) {
|
||||
'activity-panels' => true,
|
||||
'analytics' => true,
|
||||
'product-block-editor' => true,
|
||||
'product-data-views' => false,
|
||||
'experimental-blocks' => false,
|
||||
'coupons' => true,
|
||||
'core-profiler' => true,
|
||||
@@ -43,6 +44,7 @@ if ( ! function_exists( 'wc_admin_get_feature_config' ) ) {
|
||||
'product-editor-template-system' => false,
|
||||
'blueprint' => false,
|
||||
'reactify-classic-payments-settings' => false,
|
||||
'use-wp-horizon' => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API Brands controller for WC 3.5+
|
||||
*
|
||||
* Handles requests to /products/brands endpoint.
|
||||
*
|
||||
* Important: For internal use only by the Automattic\WooCommerce\Internal\Brands package.
|
||||
*
|
||||
* @package WooCommerce\RestApi
|
||||
* @since 9.4.0
|
||||
*/
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API Brands controller class.
|
||||
*
|
||||
* @package WooCommerce\RestApi
|
||||
* @extends WC_REST_Product_Categories_Controller
|
||||
*/
|
||||
class WC_REST_Product_Brands_V2_Controller extends WC_REST_Product_Categories_V2_Controller {
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'products/brands';
|
||||
|
||||
/**
|
||||
* Taxonomy.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $taxonomy = 'product_brand';
|
||||
}
|
||||
@@ -8,6 +8,9 @@
|
||||
* @since 3.0.0
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
@@ -217,6 +220,11 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
|
||||
__( 'This tool will update your WooCommerce database to the latest version. Please ensure you make sufficient backups before proceeding.', 'woocommerce' )
|
||||
),
|
||||
),
|
||||
'recreate_order_address_fts_index' => array(
|
||||
'name' => __( 'Re-create Order Address FTS index', 'woocommerce' ),
|
||||
'button' => __( 'Recreate index', 'woocommerce' ),
|
||||
'desc' => __( 'This tool will recreate the full text search index for order addresses. If the index does not exist, it will try to create it.', 'woocommerce' ),
|
||||
),
|
||||
);
|
||||
if ( method_exists( 'WC_Install', 'verify_base_tables' ) ) {
|
||||
$tools['verify_db_tables'] = array(
|
||||
@@ -598,6 +606,13 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'recreate_order_address_fts_index':
|
||||
$hpos_controller = wc_get_container()->get( CustomOrdersTableController::class );
|
||||
$results = $hpos_controller->recreate_order_address_fts_index();
|
||||
$ran = $results['status'];
|
||||
$message = $results['message'];
|
||||
break;
|
||||
|
||||
default:
|
||||
$tools = $this->get_tools();
|
||||
if ( isset( $tools[ $tool ]['callback'] ) ) {
|
||||
|
||||
@@ -13,7 +13,7 @@ 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\{ LoggingUtil, OrderUtil };
|
||||
use Automattic\WooCommerce\Utilities\{ LoggingUtil, OrderUtil, PluginUtil };
|
||||
|
||||
/**
|
||||
* System status controller class.
|
||||
@@ -373,7 +373,41 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'plugin' => array(
|
||||
'description' => __( 'Plugin basename. The path to the main plugin file relative to the plugins directory.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'Name of the plugin.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'version' => array(
|
||||
'description' => __( 'Current plugin version.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'version_latest' => array(
|
||||
'description' => __( 'Latest available plugin version.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'url' => array(
|
||||
'description' => __( 'Plugin URL.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'author_name' => array(
|
||||
'description' => __( 'Plugin author name.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'author_url' => array(
|
||||
'description' => __( 'Plugin author URL.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'network_activated' => array(
|
||||
'description' => __( 'Whether the plugin can only be activated network-wide.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'inactive_plugins' => array(
|
||||
@@ -382,7 +416,41 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'plugin' => array(
|
||||
'description' => __( 'Plugin basename. The path to the main plugin file relative to the plugins directory.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'Name of the plugin.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'version' => array(
|
||||
'description' => __( 'Current plugin version.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'version_latest' => array(
|
||||
'description' => __( 'Latest available plugin version.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'url' => array(
|
||||
'description' => __( 'Plugin URL.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'author_name' => array(
|
||||
'description' => __( 'Plugin author name.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'author_url' => array(
|
||||
'description' => __( 'Plugin author URL.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'network_activated' => array(
|
||||
'description' => __( 'Whether the plugin can only be activated network-wide.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'dropins_mu_plugins' => array(
|
||||
@@ -1044,15 +1112,10 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
|
||||
return array();
|
||||
}
|
||||
|
||||
$active_plugins = (array) get_option( 'active_plugins', array() );
|
||||
if ( is_multisite() ) {
|
||||
$network_activated_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
|
||||
$active_plugins = array_merge( $active_plugins, $network_activated_plugins );
|
||||
}
|
||||
$active_valid_plugins = wc_get_container()->get( PluginUtil::class )->get_all_active_valid_plugins();
|
||||
$active_plugins_data = array();
|
||||
|
||||
$active_plugins_data = array();
|
||||
|
||||
foreach ( $active_plugins as $plugin ) {
|
||||
foreach ( $active_valid_plugins as $plugin ) {
|
||||
$data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
|
||||
$active_plugins_data[] = $this->format_plugin_data( $plugin, $data );
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API Brands controller.
|
||||
*
|
||||
* Handles requests to /products/brands endpoint.
|
||||
*
|
||||
* Important: For internal use only by the Automattic\WooCommerce\Internal\Brands package.
|
||||
*
|
||||
* @package WooCommerce\RestApi
|
||||
* @since 9.4.0
|
||||
*/
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
/**
|
||||
* REST API Brands controller class.
|
||||
*
|
||||
* @package WooCommerce\RestApi
|
||||
* @extends WC_REST_Product_Categories_Controller
|
||||
*/
|
||||
class WC_REST_Product_Brands_Controller extends WC_REST_Product_Categories_Controller {
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'products/brands';
|
||||
|
||||
/**
|
||||
* Taxonomy.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $taxonomy = 'product_brand';
|
||||
}
|
||||
@@ -1065,7 +1065,7 @@ class WC_REST_Product_Reviews_Controller extends WC_REST_Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reivew, if the ID is valid.
|
||||
* Get the review, if the ID is valid.
|
||||
*
|
||||
* @since 3.5.0
|
||||
* @param int $id Supplied ID.
|
||||
|
||||
@@ -53,7 +53,7 @@ class WC_REST_Product_Shipping_Classes_Controller extends WC_REST_Product_Shippi
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback fuction for the slug-suggestion endpoint.
|
||||
* Callback function for the slug-suggestion endpoint.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return string The suggested slug.
|
||||
|
||||
@@ -446,7 +446,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V
|
||||
|
||||
if ( is_wp_error( $upload ) ) {
|
||||
/**
|
||||
* Filter to check if it should supress the image upload error, false by default.
|
||||
* Filter to check if it should suppress the image upload error, false by default.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @param bool false If it should suppress.
|
||||
|
||||
@@ -232,7 +232,7 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
||||
}
|
||||
|
||||
if ( wc_product_sku_enabled() ) {
|
||||
// Do a partial match for a sku. Supercedes sku parameter that does exact matching.
|
||||
// Do a partial match for a sku. Supersedes sku parameter that does exact matching.
|
||||
if ( ! empty( $request['search_sku'] ) ) {
|
||||
// Store this for use in the query clause filters.
|
||||
$this->search_sku_in_product_lookup_table = $request['search_sku'];
|
||||
@@ -259,6 +259,18 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $request['global_unique_id'] ) ) {
|
||||
$global_unique_ids = array_map( 'trim', explode( ',', $request['global_unique_id'] ) );
|
||||
$args['meta_query'] = $this->add_meta_query( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
$args,
|
||||
array(
|
||||
'key' => '_global_unique_id',
|
||||
'value' => $global_unique_ids,
|
||||
'compare' => 'IN',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Filter by tax class.
|
||||
if ( ! empty( $request['tax_class'] ) ) {
|
||||
$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
|
||||
|
||||
@@ -641,8 +641,13 @@ class WC_Shortcode_Products {
|
||||
|
||||
do_action( "woocommerce_shortcode_before_{$this->type}_loop", $this->attributes );
|
||||
|
||||
// Fire standard shop loop hooks when paginating results so we can show result counts and so on.
|
||||
if ( wc_string_to_bool( $this->attributes['paginate'] ) ) {
|
||||
/**
|
||||
* Fire the standard shop hooks when paginating so we can display result counts etc.
|
||||
* If the pagination is not enabled, this hook will not be fired.
|
||||
*
|
||||
* @since 3.3.1
|
||||
*/
|
||||
do_action( 'woocommerce_before_shop_loop' );
|
||||
}
|
||||
|
||||
@@ -667,8 +672,13 @@ class WC_Shortcode_Products {
|
||||
$GLOBALS['post'] = $original_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
woocommerce_product_loop_end();
|
||||
|
||||
// Fire standard shop loop hooks when paginating results so we can show result counts and so on.
|
||||
if ( wc_string_to_bool( $this->attributes['paginate'] ) ) {
|
||||
/**
|
||||
* Fire the standard shop hooks when paginating so we can display the pagination.
|
||||
* If the pagination is not enabled, this hook will not be fired.
|
||||
*
|
||||
* @since 3.3.1
|
||||
*/
|
||||
do_action( 'woocommerce_after_shop_loop' );
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
/**
|
||||
* Brands Helper Functions
|
||||
*
|
||||
* Important: For internal use only by the Automattic\WooCommerce\Internal\Brands package.
|
||||
*
|
||||
* @package WooCommerce
|
||||
* @version 9.4.0
|
||||
*/
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
/**
|
||||
* Helper function :: wc_get_brand_thumbnail_url function.
|
||||
*
|
||||
* @param int $brand_id Brand ID.
|
||||
* @param string $size Thumbnail image size.
|
||||
* @return string
|
||||
*/
|
||||
function wc_get_brand_thumbnail_url( $brand_id, $size = 'full' ) {
|
||||
$thumbnail_id = get_term_meta( $brand_id, 'thumbnail_id', true );
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
$thumb_src = wp_get_attachment_image_src( $thumbnail_id, $size );
|
||||
}
|
||||
|
||||
return ! empty( $thumb_src ) ? current( $thumb_src ) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function :: wc_get_brand_thumbnail_image function.
|
||||
*
|
||||
* @since 9.4.0
|
||||
*
|
||||
* @param object $brand Brand term.
|
||||
* @param string $size Thumbnail image size.
|
||||
* @return string
|
||||
*/
|
||||
function wc_get_brand_thumbnail_image( $brand, $size = '' ) {
|
||||
$thumbnail_id = get_term_meta( $brand->term_id, 'thumbnail_id', true );
|
||||
|
||||
if ( '' === $size || 'brand-thumb' === $size ) {
|
||||
/**
|
||||
* Filter the brand's thumbnail size.
|
||||
*
|
||||
* @since 9.4.0
|
||||
*
|
||||
* @param string $size Brand's thumbnail size.
|
||||
*/
|
||||
$size = apply_filters( 'woocommerce_brand_thumbnail_size', 'shop_catalog' );
|
||||
}
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
$image_src = wp_get_attachment_image_src( $thumbnail_id, $size );
|
||||
$image_src = $image_src[0];
|
||||
$dimensions = wc_get_image_size( $size );
|
||||
$image_srcset = function_exists( 'wp_get_attachment_image_srcset' ) ? wp_get_attachment_image_srcset( $thumbnail_id, $size ) : false;
|
||||
$image_sizes = function_exists( 'wp_get_attachment_image_sizes' ) ? wp_get_attachment_image_sizes( $thumbnail_id, $size ) : false;
|
||||
} else {
|
||||
$image_src = wc_placeholder_img_src();
|
||||
$dimensions = wc_get_image_size( $size );
|
||||
$image_srcset = false;
|
||||
$image_sizes = false;
|
||||
}
|
||||
|
||||
// Add responsive image markup if available.
|
||||
if ( $image_srcset && $image_sizes ) {
|
||||
$image = '<img src="' . esc_url( $image_src ) . '" alt="' . esc_attr( $brand->name ) . '" class="brand-thumbnail" width="' . esc_attr( $dimensions['width'] ) . '" height="' . esc_attr( $dimensions['height'] ) . '" srcset="' . esc_attr( $image_srcset ) . '" sizes="' . esc_attr( $image_sizes ) . '" />';
|
||||
} else {
|
||||
$image = '<img src="' . esc_url( $image_src ) . '" alt="' . esc_attr( $brand->name ) . '" class="brand-thumbnail" width="' . esc_attr( $dimensions['width'] ) . '" height="' . esc_attr( $dimensions['height'] ) . '" />';
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves product's brands.
|
||||
*
|
||||
* @param int $post_id Post ID (default: 0).
|
||||
* @param string $sep Seperator (default: ').
|
||||
* @param string $before Before item (default: '').
|
||||
* @param string $after After item (default: '').
|
||||
* @return array List of terms
|
||||
*/
|
||||
function wc_get_brands( $post_id = 0, $sep = ', ', $before = '', $after = '' ) {
|
||||
global $post;
|
||||
|
||||
if ( ! $post_id ) {
|
||||
$post_id = $post->ID;
|
||||
}
|
||||
|
||||
return get_the_term_list( $post_id, 'product_brand', $before, $sep, $after );
|
||||
}
|
||||
|
||||
/**
|
||||
* Polyfills for backwards compatibility with the WooCommerce Brands plugin.
|
||||
*/
|
||||
|
||||
if ( ! function_exists( 'get_brand_thumbnail_url' ) ) {
|
||||
|
||||
/**
|
||||
* Polyfill for get_brand_thumbnail_image.
|
||||
*
|
||||
* @param int $brand_id Brand ID.
|
||||
* @param string $size Thumbnail image size.
|
||||
* @return string
|
||||
*/
|
||||
function get_brand_thumbnail_url( $brand_id, $size = 'full' ) {
|
||||
return wc_get_brand_thumbnail_url( $brand_id, $size );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'get_brand_thumbnail_image' ) ) {
|
||||
|
||||
/**
|
||||
* Polyfill for get_brand_thumbnail_image.
|
||||
*
|
||||
* @param object $brand Brand term.
|
||||
* @param string $size Thumbnail image size.
|
||||
* @return string
|
||||
*/
|
||||
function get_brand_thumbnail_image( $brand, $size = '' ) {
|
||||
return wc_get_brand_thumbnail_image( $brand, $size );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'get_brands' ) ) {
|
||||
|
||||
/**
|
||||
* Polyfill for get_brands.
|
||||
*
|
||||
* @param int $post_id Post ID (default: 0).
|
||||
* @param string $sep Seperator (default: ').
|
||||
* @param string $before Before item (default: '').
|
||||
* @param string $after After item (default: '').
|
||||
* @return array List of terms
|
||||
*/
|
||||
function get_brands( $post_id = 0, $sep = ', ', $before = '', $after = '' ) {
|
||||
return wc_get_brands( $post_id, $sep, $before, $after );
|
||||
}
|
||||
}
|
||||
@@ -1484,7 +1484,11 @@ function wc_transaction_query( $type = 'start', $force = false ) {
|
||||
* @return string Url to cart page
|
||||
*/
|
||||
function wc_get_cart_url() {
|
||||
if ( is_cart() && isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
|
||||
// We don't use is_cart() here because that also checks for a defined constant. We are only interested in the page.
|
||||
$page_id = wc_get_page_id( 'cart' );
|
||||
$is_cart_page = ( $page_id && is_page( $page_id ) ) || wc_post_content_has_shortcode( 'woocommerce_cart' );
|
||||
|
||||
if ( $is_cart_page && isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
|
||||
$protocol = is_ssl() ? 'https' : 'http';
|
||||
$current_url = esc_url_raw( $protocol . '://' . wp_unslash( $_SERVER['HTTP_HOST'] ) . wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
||||
$cart_url = remove_query_arg( array( 'remove_item', 'add-to-cart', 'added-to-cart', 'order_again', '_wpnonce' ), $current_url );
|
||||
@@ -1833,7 +1837,7 @@ function wc_uasort_comparison( $a, $b ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort values based on ascii, usefull for special chars in strings.
|
||||
* Sort values based on ascii, useful for special chars in strings.
|
||||
*
|
||||
* @param string $a First value.
|
||||
* @param string $b Second value.
|
||||
@@ -2524,7 +2528,7 @@ function wc_selected( $value, $options ) {
|
||||
* Retrieves the MySQL server version. Based on $wpdb.
|
||||
*
|
||||
* @since 3.4.1
|
||||
* @return array Vesion information.
|
||||
* @return array Version information.
|
||||
*/
|
||||
function wc_get_server_database_version() {
|
||||
global $wpdb;
|
||||
|
||||
@@ -33,6 +33,36 @@ function wc_page_endpoint_title( $title ) {
|
||||
|
||||
add_filter( 'the_title', 'wc_page_endpoint_title' );
|
||||
|
||||
/**
|
||||
* Replace the title part of the document title.
|
||||
*
|
||||
* @param array $title {
|
||||
* The document title parts.
|
||||
*
|
||||
* @type string $title Title of the viewed page.
|
||||
* @type string $page Optional. Page number if paginated.
|
||||
* @type string $tagline Optional. Site description when on home page.
|
||||
* @type string $site Optional. Site title when not on home page.
|
||||
* }
|
||||
* @return array
|
||||
*/
|
||||
function wc_page_endpoint_document_title_parts( $title ) {
|
||||
global $wp_query;
|
||||
|
||||
if ( ! is_null( $wp_query ) && ! is_admin() && is_main_query() && is_page() && is_wc_endpoint_url() ) {
|
||||
$endpoint = WC()->query->get_current_endpoint();
|
||||
$action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$endpoint_title = WC()->query->get_endpoint_title( $endpoint, $action );
|
||||
$title['title'] = $endpoint_title ? $endpoint_title : $title['title'];
|
||||
|
||||
remove_filter( 'document_title_parts', 'wc_page_endpoint_document_title_parts' );
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
add_filter( 'document_title_parts', 'wc_page_endpoint_document_title_parts' );
|
||||
|
||||
/**
|
||||
* Retrieve page ids - used for myaccount, edit_address, shop, cart, checkout, pay, view_order, terms. returns -1 if no page is found.
|
||||
*
|
||||
|
||||
@@ -410,8 +410,6 @@ function wc_rest_should_load_namespace( string $ns, string $rest_route = '' ): b
|
||||
'wc/private',
|
||||
);
|
||||
|
||||
// We can consider allowing filtering this list in the future.
|
||||
|
||||
$known_namespace_request = false;
|
||||
foreach ( $known_namespaces as $known_namespace ) {
|
||||
if ( str_starts_with( $rest_route, $known_namespace ) ) {
|
||||
@@ -424,5 +422,15 @@ function wc_rest_should_load_namespace( string $ns, string $rest_route = '' ): b
|
||||
return true;
|
||||
}
|
||||
|
||||
return str_starts_with( $rest_route, $ns );
|
||||
/**
|
||||
* Filters whether a namespace should be loaded.
|
||||
*
|
||||
* @param bool $should_load True if the namespace should be loaded, false otherwise.
|
||||
* @param string $ns The namespace to check.
|
||||
* @param string $rest_route The REST route being checked.
|
||||
* @param array $known_namespaces Known namespaces that we know are safe to not load if the request is not for them.
|
||||
*
|
||||
* @since 9.4
|
||||
*/
|
||||
return apply_filters( 'wc_rest_should_load_namespace', str_starts_with( $rest_route, $ns ), $ns, $rest_route, $known_namespaces );
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ function wc_set_loop_prop( $prop, $value = '' ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current visbility for a product in the woocommerce_loop global.
|
||||
* Set the current visibility for a product in the woocommerce_loop global.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @param int $product_id Product it to cache visibility for.
|
||||
@@ -1480,10 +1480,48 @@ if ( ! function_exists( 'woocommerce_result_count' ) ) {
|
||||
if ( ! wc_get_loop_prop( 'is_paginated' ) || ! woocommerce_products_will_display() ) {
|
||||
return;
|
||||
}
|
||||
$args = array(
|
||||
'total' => wc_get_loop_prop( 'total' ),
|
||||
'per_page' => wc_get_loop_prop( 'per_page' ),
|
||||
'current' => wc_get_loop_prop( 'current_page' ),
|
||||
|
||||
/**
|
||||
* Filters the default orderby option.
|
||||
*
|
||||
* @since 1.6.4
|
||||
*
|
||||
* @param string $default_orderby The default orderby option.
|
||||
*/
|
||||
$default_orderby = apply_filters( 'woocommerce_default_catalog_orderby', get_option( 'woocommerce_default_catalog_orderby', '' ) );
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$orderby = isset( $_GET['orderby'] ) ? wc_clean( wp_unslash( $_GET['orderby'] ) ) : $default_orderby;
|
||||
|
||||
// If products follow the default order this doesn't need to be informed.
|
||||
$orderby = 'menu_order' === $orderby ? '' : $orderby;
|
||||
|
||||
$orderby = is_string( $orderby ) ? $orderby : '';
|
||||
|
||||
/**
|
||||
* Filters ordered by messages.
|
||||
*
|
||||
* @since 9.3.0
|
||||
*
|
||||
* @param array $orderedby_messages The list of messages per orderby key.
|
||||
*/
|
||||
$catalog_orderedby_options = apply_filters(
|
||||
'woocommerce_catalog_orderedby',
|
||||
array(
|
||||
'menu_order' => __( 'Default sorting', 'woocommerce' ),
|
||||
'popularity' => __( 'Sorted by popularity', 'woocommerce' ),
|
||||
'rating' => __( 'Sorted by average rating', 'woocommerce' ),
|
||||
'date' => __( 'Sorted by latest', 'woocommerce' ),
|
||||
'price' => __( 'Sorted by price: low to high', 'woocommerce' ),
|
||||
'price-desc' => __( 'Sorted by price: high to low', 'woocommerce' ),
|
||||
)
|
||||
);
|
||||
$orderedby = isset( $catalog_orderedby_options[ $orderby ] ) ? $catalog_orderedby_options[ $orderby ] : '';
|
||||
$orderedby = is_string( $orderedby ) ? $orderedby : '';
|
||||
$args = array(
|
||||
'total' => wc_get_loop_prop( 'total' ),
|
||||
'per_page' => wc_get_loop_prop( 'per_page' ),
|
||||
'current' => wc_get_loop_prop( 'current_page' ),
|
||||
'orderedby' => $orderedby,
|
||||
);
|
||||
|
||||
wc_get_template( 'loop/result-count.php', $args );
|
||||
@@ -1615,25 +1653,39 @@ function wc_get_gallery_image_html( $attachment_id, $main_image = false ) {
|
||||
$thumbnail_srcset = wp_get_attachment_image_srcset( $attachment_id, $thumbnail_size );
|
||||
$full_src = wp_get_attachment_image_src( $attachment_id, $full_size );
|
||||
$alt_text = trim( wp_strip_all_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) );
|
||||
$image = wp_get_attachment_image(
|
||||
|
||||
/**
|
||||
* Filters the attributes for the image markup.
|
||||
*
|
||||
* @since 3.3.2
|
||||
*
|
||||
* @param array $image_attributes Attributes for the image markup.
|
||||
*/
|
||||
$image_params = apply_filters(
|
||||
'woocommerce_gallery_image_html_attachment_image_params',
|
||||
array(
|
||||
'title' => _wp_specialchars( get_post_field( 'post_title', $attachment_id ), ENT_QUOTES, 'UTF-8', true ),
|
||||
'data-caption' => _wp_specialchars( get_post_field( 'post_excerpt', $attachment_id ), ENT_QUOTES, 'UTF-8', true ),
|
||||
'data-src' => esc_url( $full_src[0] ),
|
||||
'data-large_image' => esc_url( $full_src[0] ),
|
||||
'data-large_image_width' => esc_attr( $full_src[1] ),
|
||||
'data-large_image_height' => esc_attr( $full_src[2] ),
|
||||
'class' => esc_attr( $main_image ? 'wp-post-image' : '' ),
|
||||
),
|
||||
$attachment_id,
|
||||
$image_size,
|
||||
$main_image
|
||||
);
|
||||
|
||||
if ( isset( $image_params['title'] ) ) {
|
||||
unset( $image_params['title'] );
|
||||
}
|
||||
|
||||
$image = wp_get_attachment_image(
|
||||
$attachment_id,
|
||||
$image_size,
|
||||
false,
|
||||
apply_filters(
|
||||
'woocommerce_gallery_image_html_attachment_image_params',
|
||||
array(
|
||||
'title' => _wp_specialchars( get_post_field( 'post_title', $attachment_id ), ENT_QUOTES, 'UTF-8', true ),
|
||||
'data-caption' => _wp_specialchars( get_post_field( 'post_excerpt', $attachment_id ), ENT_QUOTES, 'UTF-8', true ),
|
||||
'data-src' => esc_url( $full_src[0] ),
|
||||
'data-large_image' => esc_url( $full_src[0] ),
|
||||
'data-large_image_width' => esc_attr( $full_src[1] ),
|
||||
'data-large_image_height' => esc_attr( $full_src[2] ),
|
||||
'class' => esc_attr( $main_image ? 'wp-post-image' : '' ),
|
||||
),
|
||||
$attachment_id,
|
||||
$image_size,
|
||||
$main_image
|
||||
)
|
||||
$image_params
|
||||
);
|
||||
|
||||
return '<div data-thumb="' . esc_url( $thumbnail_src[0] ) . '" data-thumb-alt="' . esc_attr( $alt_text ) . '" data-thumb-srcset="' . esc_attr( $thumbnail_srcset ) . '" class="woocommerce-product-gallery__image"><a href="' . esc_url( $full_src[0] ) . '">' . $image . '</a></div>';
|
||||
@@ -3791,7 +3843,7 @@ function wc_empty_cart_message() {
|
||||
// This adds the cart-empty classname to the notice to preserve backwards compatibility (for styling purposes etc).
|
||||
$notice = str_replace( 'class="woocommerce-info"', 'class="cart-empty woocommerce-info"', $notice );
|
||||
|
||||
// Return the notice within a consistent wrapper element. This is targetted by some scripts such as cart.js.
|
||||
// Return the notice within a consistent wrapper element. This is targeted by some scripts such as cart.js.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="wc-empty-cart-message">' . $notice . '</div>';
|
||||
}
|
||||
|
||||
@@ -24,12 +24,14 @@ use Automattic\WooCommerce\Database\Migrations\MigrationHelper;
|
||||
use Automattic\WooCommerce\Internal\Admin\Marketing\MarketingSpecs;
|
||||
use Automattic\WooCommerce\Internal\Admin\Notes\WooSubscriptionsNotes;
|
||||
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
|
||||
use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator;
|
||||
use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;
|
||||
use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Register as Download_Directories;
|
||||
use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Synchronize as Download_Directories_Sync;
|
||||
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
|
||||
use Automattic\WooCommerce\Utilities\StringUtil;
|
||||
|
||||
/**
|
||||
@@ -416,7 +418,7 @@ function wc_update_209_brazillian_state() {
|
||||
|
||||
// phpcs:disable WordPress.DB.SlowDBQuery
|
||||
|
||||
// Update brazillian state codes.
|
||||
// Update Brazilian state codes.
|
||||
$wpdb->update(
|
||||
$wpdb->postmeta,
|
||||
array(
|
||||
@@ -2598,7 +2600,7 @@ function wc_update_770_remove_multichannel_marketing_feature_options() {
|
||||
/**
|
||||
* Migrate transaction data which was being incorrectly stored in the postmeta table to HPOS tables.
|
||||
*
|
||||
* @return bool Whether there are pending migration recrods.
|
||||
* @return bool Whether there are pending migration records.
|
||||
*/
|
||||
function wc_update_810_migrate_transactional_metadata_for_hpos() {
|
||||
global $wpdb;
|
||||
@@ -2691,7 +2693,7 @@ function wc_update_890_update_connect_to_woocommerce_note() {
|
||||
* Disables the PayPal Standard gateway for stores that aren't using it.
|
||||
*
|
||||
* PayPal Standard has been deprecated since WooCommerce 5.5, but there are some stores that have it showing up in their
|
||||
* list of available Payment methods even if it's not setup. In WooComerce 8.9 we will disable PayPal Standard for those stores
|
||||
* list of available Payment methods even if it's not setup. In WooCommerce 8.9 we will disable PayPal Standard for those stores
|
||||
* to reduce the amount of new connections to the legacy gateway.
|
||||
*
|
||||
* Shows an admin notice to inform the store owner that PayPal Standard has been disabled and suggests installing PayPal Payments.
|
||||
@@ -2852,3 +2854,69 @@ function wc_update_930_migrate_user_meta_for_launch_your_store_tour() {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recreate FTS index if it already exists, so that phone number can be added to the index.
|
||||
*/
|
||||
function wc_update_940_add_phone_to_order_address_fts_index(): void {
|
||||
$fts_already_exists = get_option( CustomOrdersTableController::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION ) === 'yes';
|
||||
if ( ! $fts_already_exists ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hpos_controller = wc_get_container()->get( CustomOrdersTableController::class );
|
||||
$result = $hpos_controller->recreate_order_address_fts_index();
|
||||
if ( ! $result['status'] ) {
|
||||
if ( class_exists( 'WC_Admin_Settings ' ) ) {
|
||||
WC_Admin_Settings::add_error( $result['message'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove user meta associated with the key 'woocommerce_admin_help_panel_highlight_shown'.
|
||||
*
|
||||
* This key is no longer needed since the help panel spotlight tour has been removed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function wc_update_940_remove_help_panel_highlight_shown() {
|
||||
global $wpdb;
|
||||
|
||||
$meta_key = 'woocommerce_admin_help_panel_highlight_shown';
|
||||
|
||||
$deletions = $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM $wpdb->usermeta WHERE meta_key = %s",
|
||||
$meta_key
|
||||
)
|
||||
);
|
||||
|
||||
// Get the WooCommerce logger to track the results of the deletion.
|
||||
$logger = wc_get_logger();
|
||||
|
||||
if ( null === $logger ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( false === $deletions ) {
|
||||
$logger->notice(
|
||||
'During the update to 9.4.0, WooCommerce attempted to remove user meta with the key "woocommerce_admin_help_panel_highlight_shown", but was unable to do so.',
|
||||
array(
|
||||
'source' => 'wc-updater',
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$logger->info(
|
||||
sprintf(
|
||||
1 === $deletions
|
||||
? 'During the update to 9.4.0, WooCommerce removed %d user meta row associated with the meta key "woocommerce_admin_help_panel_highlight_shown".'
|
||||
: 'During the update to 9.4.0, WooCommerce removed %d user meta rows associated with the meta key "woocommerce_admin_help_panel_highlight_shown".',
|
||||
number_format_i18n( $deletions )
|
||||
),
|
||||
array(
|
||||
'source' => 'wc-updater',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,9 @@ if ( ! function_exists( 'wc_create_new_customer' ) ) {
|
||||
/**
|
||||
* Create a new customer.
|
||||
*
|
||||
* @since 9.4.0 Moved woocommerce_registration_error_email_exists filter to the shortcode checkout class.
|
||||
* @since 9.4.0 Removed handling for generating username/password based on settings--this is consumed at form level. Here, if data is missing it will be generated.
|
||||
*
|
||||
* @param string $email Customer email.
|
||||
* @param string $username Customer username.
|
||||
* @param string $password Customer password.
|
||||
@@ -55,17 +58,24 @@ if ( ! function_exists( 'wc_create_new_customer' ) ) {
|
||||
}
|
||||
|
||||
if ( email_exists( $email ) ) {
|
||||
return new WP_Error( 'registration-error-email-exists', apply_filters( 'woocommerce_registration_error_email_exists', __( 'An account is already registered with your email address. <a href="#" class="showlogin">Please log in.</a>', 'woocommerce' ), $email ) );
|
||||
return new WP_Error(
|
||||
'registration-error-email-exists',
|
||||
sprintf(
|
||||
// Translators: %s Email address.
|
||||
esc_html__( 'An account is already registered with %s. Please log in or use a different email address.', 'woocommerce' ),
|
||||
esc_html( $email )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'yes' === get_option( 'woocommerce_registration_generate_username', 'yes' ) && empty( $username ) ) {
|
||||
if ( empty( $username ) ) {
|
||||
$username = wc_create_new_customer_username( $email, $args );
|
||||
}
|
||||
|
||||
$username = sanitize_user( $username );
|
||||
|
||||
if ( empty( $username ) || ! validate_username( $username ) ) {
|
||||
return new WP_Error( 'registration-error-invalid-username', __( 'Please enter a valid account username.', 'woocommerce' ) );
|
||||
return new WP_Error( 'registration-error-invalid-username', __( 'Please provide a valid account username.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
if ( username_exists( $username ) ) {
|
||||
@@ -74,35 +84,88 @@ if ( ! function_exists( 'wc_create_new_customer' ) ) {
|
||||
|
||||
// Handle password creation.
|
||||
$password_generated = false;
|
||||
if ( 'yes' === get_option( 'woocommerce_registration_generate_password' ) && empty( $password ) ) {
|
||||
|
||||
if ( empty( $password ) ) {
|
||||
$password = wp_generate_password();
|
||||
$password_generated = true;
|
||||
}
|
||||
|
||||
if ( empty( $password ) ) {
|
||||
return new WP_Error( 'registration-error-missing-password', __( 'Please enter an account password.', 'woocommerce' ) );
|
||||
return new WP_Error( 'registration-error-missing-password', __( 'Please create a password for your account.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
// Use WP_Error to handle registration errors.
|
||||
$errors = new WP_Error();
|
||||
|
||||
/**
|
||||
* Fires before a customer account is registered.
|
||||
*
|
||||
* This hook fires before customer accounts are created and passes the form data (username, email) and an array
|
||||
* of errors.
|
||||
*
|
||||
* This could be used to add extra validation logic and append errors to the array.
|
||||
*
|
||||
* @since 7.2.0
|
||||
*
|
||||
* @internal Matches filter name in WooCommerce core.
|
||||
*
|
||||
* @param string $username Customer username.
|
||||
* @param string $user_email Customer email address.
|
||||
* @param \WP_Error $errors Error object.
|
||||
*/
|
||||
do_action( 'woocommerce_register_post', $username, $email, $errors );
|
||||
|
||||
/**
|
||||
* Filters registration errors before a customer account is registered.
|
||||
*
|
||||
* This hook filters registration errors. This can be used to manipulate the array of errors before
|
||||
* they are displayed.
|
||||
*
|
||||
* @since 7.2.0
|
||||
*
|
||||
* @internal Matches filter name in WooCommerce core.
|
||||
*
|
||||
* @param \WP_Error $errors Error object.
|
||||
* @param string $username Customer username.
|
||||
* @param string $user_email Customer email address.
|
||||
* @return \WP_Error
|
||||
*/
|
||||
$errors = apply_filters( 'woocommerce_registration_errors', $errors, $username, $email );
|
||||
|
||||
if ( $errors->get_error_code() ) {
|
||||
if ( is_wp_error( $errors ) && $errors->get_error_code() ) {
|
||||
return $errors;
|
||||
}
|
||||
|
||||
// Merged passed args with sanitized username, email, and password.
|
||||
$customer_data = array_merge(
|
||||
$args,
|
||||
array(
|
||||
'user_login' => $username,
|
||||
'user_pass' => $password,
|
||||
'user_email' => $email,
|
||||
'role' => 'customer',
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters customer data before a customer account is registered.
|
||||
*
|
||||
* This hook filters customer data. It allows user data to be changed, for example, username, password, email,
|
||||
* first name, last name, and role.
|
||||
*
|
||||
* @since 7.2.0
|
||||
*
|
||||
* @param array $customer_data An array of customer (user) data.
|
||||
* @return array
|
||||
*/
|
||||
$new_customer_data = apply_filters(
|
||||
'woocommerce_new_customer_data',
|
||||
array_merge(
|
||||
$args,
|
||||
wp_parse_args(
|
||||
$customer_data,
|
||||
array(
|
||||
'user_login' => $username,
|
||||
'user_pass' => $password,
|
||||
'user_email' => $email,
|
||||
'role' => 'customer',
|
||||
'first_name' => '',
|
||||
'last_name' => '',
|
||||
'source' => 'unknown',
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -113,6 +176,24 @@ if ( ! function_exists( 'wc_create_new_customer' ) ) {
|
||||
return $customer_id;
|
||||
}
|
||||
|
||||
// Set account flag to remind customer to update generated password.
|
||||
if ( $password_generated ) {
|
||||
update_user_option( $customer_id, 'default_password_nag', true, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after a customer account has been registered.
|
||||
*
|
||||
* This hook fires after customer accounts are created and passes the customer data.
|
||||
*
|
||||
* @since 7.2.0
|
||||
*
|
||||
* @internal Matches filter name in WooCommerce core.
|
||||
*
|
||||
* @param integer $customer_id New customer (user) ID.
|
||||
* @param array $new_customer_data Array of customer (user) data.
|
||||
* @param string $password_generated The generated password for the account.
|
||||
*/
|
||||
do_action( 'woocommerce_created_customer', $customer_id, $new_customer_data, $password_generated );
|
||||
|
||||
return $customer_id;
|
||||
@@ -232,7 +313,9 @@ function wc_set_customer_auth_cookie( $customer_id ) {
|
||||
wp_set_auth_cookie( $customer_id, true );
|
||||
|
||||
// Update session.
|
||||
WC()->session->init_session_cookie();
|
||||
if ( is_callable( array( WC()->session, 'init_session_cookie' ) ) ) {
|
||||
WC()->session->init_session_cookie();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,10 +355,10 @@ function wc_update_new_customer_past_orders( $customer_id ) {
|
||||
do_action( 'woocommerce_update_new_customer_past_order', $order_id, $customer );
|
||||
|
||||
if ( $order->get_status() === 'wc-completed' ) {
|
||||
$complete++;
|
||||
++$complete;
|
||||
}
|
||||
|
||||
$linked++;
|
||||
++$linked;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,18 +439,18 @@ function wc_customer_bought_product( $customer_email, $user_id, $product_id ) {
|
||||
}
|
||||
|
||||
if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
|
||||
$statuses = array_map(
|
||||
$statuses = array_map(
|
||||
function ( $status ) {
|
||||
return "wc-$status";
|
||||
},
|
||||
$statuses
|
||||
);
|
||||
$order_table = OrdersTableDataStore::get_orders_table_name();
|
||||
$order_table = OrdersTableDataStore::get_orders_table_name();
|
||||
$user_id_clause = '';
|
||||
if ( $user_id ) {
|
||||
$user_id_clause = 'OR o.customer_id = ' . absint( $user_id );
|
||||
}
|
||||
$sql = "
|
||||
$sql = "
|
||||
SELECT DISTINCT im.meta_value FROM $order_table AS o
|
||||
INNER JOIN {$wpdb->prefix}woocommerce_order_items AS i ON o.id = i.order_id
|
||||
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS im ON i.order_item_id = im.order_item_id
|
||||
@@ -568,17 +651,15 @@ function wc_modify_map_meta_cap( $caps, $cap, $user_id, $args ) {
|
||||
case 'delete_user':
|
||||
if ( ! isset( $args[0] ) || $args[0] === $user_id ) {
|
||||
break;
|
||||
} else {
|
||||
if ( ! wc_current_user_has_role( 'administrator' ) ) {
|
||||
if ( wc_user_has_role( $args[0], 'administrator' ) ) {
|
||||
} elseif ( ! wc_current_user_has_role( 'administrator' ) ) {
|
||||
if ( wc_user_has_role( $args[0], 'administrator' ) ) {
|
||||
$caps[] = 'do_not_allow';
|
||||
} elseif ( wc_current_user_has_role( 'shop_manager' ) ) {
|
||||
// Shop managers can only edit customer info.
|
||||
$userdata = get_userdata( $args[0] );
|
||||
$shop_manager_editable_roles = apply_filters( 'woocommerce_shop_manager_editable_roles', array( 'customer' ) ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
if ( property_exists( $userdata, 'roles' ) && ! empty( $userdata->roles ) && ! array_intersect( $userdata->roles, $shop_manager_editable_roles ) ) {
|
||||
$caps[] = 'do_not_allow';
|
||||
} elseif ( wc_current_user_has_role( 'shop_manager' ) ) {
|
||||
// Shop managers can only edit customer info.
|
||||
$userdata = get_userdata( $args[0] );
|
||||
$shop_manager_editable_roles = apply_filters( 'woocommerce_shop_manager_editable_roles', array( 'customer' ) );
|
||||
if ( property_exists( $userdata, 'roles' ) && ! empty( $userdata->roles ) && ! array_intersect( $userdata->roles, $shop_manager_editable_roles ) ) {
|
||||
$caps[] = 'do_not_allow';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -596,7 +677,7 @@ add_filter( 'map_meta_cap', 'wc_modify_map_meta_cap', 10, 4 );
|
||||
*/
|
||||
function wc_get_customer_download_permissions( $customer_id ) {
|
||||
$data_store = WC_Data_Store::load( 'customer-download' );
|
||||
return apply_filters( 'woocommerce_permission_list', $data_store->get_downloads_for_customer( $customer_id ), $customer_id );
|
||||
return apply_filters( 'woocommerce_permission_list', $data_store->get_downloads_for_customer( $customer_id ), $customer_id ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -655,6 +736,7 @@ function wc_get_customer_available_downloads( $customer_id ) {
|
||||
}
|
||||
|
||||
// Download name will be 'Product Name' for products with a single downloadable file, and 'Product Name - File X' for products with multiple files.
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
$download_name = apply_filters(
|
||||
'woocommerce_downloadable_product_name',
|
||||
$download_file['name'],
|
||||
@@ -688,10 +770,11 @@ function wc_get_customer_available_downloads( $customer_id ) {
|
||||
),
|
||||
);
|
||||
|
||||
$file_number++;
|
||||
++$file_number;
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
return apply_filters( 'woocommerce_customer_available_downloads', $downloads, $customer_id );
|
||||
}
|
||||
|
||||
@@ -817,7 +900,7 @@ add_action( 'profile_update', 'wc_update_profile_last_update_time', 10, 2 );
|
||||
* @param mixed $_meta_value Value of the meta that was changed.
|
||||
*/
|
||||
function wc_meta_update_last_update_time( $meta_id, $user_id, $meta_key, $_meta_value ) {
|
||||
$keys_to_track = apply_filters( 'woocommerce_user_last_update_fields', array( 'first_name', 'last_name' ) );
|
||||
$keys_to_track = apply_filters( 'woocommerce_user_last_update_fields', array( 'first_name', 'last_name' ) ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
|
||||
$update_time = in_array( $meta_key, $keys_to_track, true ) ? true : false;
|
||||
$update_time = 'billing_' === substr( $meta_key, 0, 8 ) ? true : $update_time;
|
||||
@@ -848,7 +931,7 @@ function wc_set_user_last_update_time( $user_id ) {
|
||||
* @return array
|
||||
*/
|
||||
function wc_get_customer_saved_methods_list( $customer_id ) {
|
||||
return apply_filters( 'woocommerce_saved_payment_methods_list', array(), $customer_id );
|
||||
return apply_filters( 'woocommerce_saved_payment_methods_list', array(), $customer_id ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
/**
|
||||
* Brand Description Widget
|
||||
*
|
||||
* When viewing a brand archive, show the current brands description + image
|
||||
*
|
||||
* Important: For internal use only by the Automattic\WooCommerce\Internal\Brands package.
|
||||
*
|
||||
* @package WooCommerce\Widgets
|
||||
* @version 9.4.0
|
||||
*/
|
||||
class WC_Widget_Brand_Description extends WP_Widget {
|
||||
|
||||
/**
|
||||
* Widget class.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $woo_widget_cssclass;
|
||||
|
||||
/**
|
||||
* Widget description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $woo_widget_description;
|
||||
|
||||
/**
|
||||
* Widget idbase.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $woo_widget_idbase;
|
||||
|
||||
/**
|
||||
* Widget name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $woo_widget_name;
|
||||
|
||||
/** Constructor */
|
||||
public function __construct() {
|
||||
|
||||
/* Widget variable settings. */
|
||||
$this->woo_widget_name = __( 'WooCommerce Brand Description', 'woocommerce' );
|
||||
$this->woo_widget_description = __( 'When viewing a brand archive, show the current brands description.', 'woocommerce' );
|
||||
$this->woo_widget_idbase = 'wc_brands_brand_description';
|
||||
$this->woo_widget_cssclass = 'widget_brand_description';
|
||||
|
||||
/* Widget settings. */
|
||||
$widget_ops = array(
|
||||
'classname' => $this->woo_widget_cssclass,
|
||||
'description' => $this->woo_widget_description,
|
||||
);
|
||||
|
||||
/* Create the widget. */
|
||||
parent::__construct( $this->woo_widget_idbase, $this->woo_widget_name, $widget_ops );
|
||||
}
|
||||
|
||||
/**
|
||||
* Echoes the widget content.
|
||||
*
|
||||
* @see WP_Widget
|
||||
*
|
||||
* @param array $args Display arguments including 'before_title', 'after_title',
|
||||
* 'before_widget', and 'after_widget'.
|
||||
* @param array $instance The settings for the particular instance of the widget.
|
||||
*/
|
||||
public function widget( $args, $instance ) {
|
||||
extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
|
||||
|
||||
if ( ! is_tax( 'product_brand' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! get_query_var( 'term' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$thumbnail = '';
|
||||
$term = get_term_by( 'slug', get_query_var( 'term' ), 'product_brand' );
|
||||
|
||||
$thumbnail = wc_get_brand_thumbnail_url( $term->term_id, 'large' );
|
||||
|
||||
echo $before_widget . $before_title . $term->name . $after_title; // phpcs:ignore WordPress.Security.EscapeOutput
|
||||
|
||||
wc_get_template(
|
||||
'widgets/brand-description.php',
|
||||
array(
|
||||
'thumbnail' => $thumbnail,
|
||||
'brand' => $term,
|
||||
),
|
||||
'woocommerce',
|
||||
WC()->plugin_path() . '/templates/brands/'
|
||||
);
|
||||
|
||||
echo $after_widget; // phpcs:ignore WordPress.Security.EscapeOutput
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates widget instance.
|
||||
*
|
||||
* @see WP_Widget->update
|
||||
*
|
||||
* @param array $new_instance New widget instance.
|
||||
* @param array $old_instance Old widget instance.
|
||||
*/
|
||||
public function update( $new_instance, $old_instance ) {
|
||||
$instance['title'] = wp_strip_all_tags( stripslashes( $new_instance['title'] ) );
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the settings update form.
|
||||
*
|
||||
* @param array $instance Current settings.
|
||||
*/
|
||||
public function form( $instance ) {
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'woocommerce' ); ?></label>
|
||||
<input type="text" class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" value="<?php echo isset( $instance['title'] ) ? esc_attr( $instance['title'] ) : ''; ?>" />
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
/**
|
||||
* Layered Navigation Widget for brands WC 2.6 version
|
||||
*
|
||||
* Important: For internal use only by the Automattic\WooCommerce\Internal\Brands package.
|
||||
*
|
||||
* @package WooCommerce\Widgets
|
||||
* @version 9.4.0
|
||||
* @extends WP_Widget
|
||||
*/
|
||||
class WC_Widget_Brand_Nav extends WC_Widget {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
/* Widget variable settings. */
|
||||
$this->widget_cssclass = 'woocommerce widget_brand_nav widget_layered_nav';
|
||||
$this->widget_description = __( 'Shows brands in a widget which lets you narrow down the list of products when viewing products.', 'woocommerce' );
|
||||
$this->widget_id = 'woocommerce_brand_nav';
|
||||
$this->widget_name = __( 'WooCommerce Brand Layered Nav', 'woocommerce' );
|
||||
|
||||
add_filter( 'woocommerce_product_subcategories_args', array( $this, 'filter_out_cats' ) );
|
||||
|
||||
/* Create the widget. */
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out all categories and not display them
|
||||
*
|
||||
* @param array $cat_args Category arguments.
|
||||
*/
|
||||
public function filter_out_cats( $cat_args ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! empty( $_GET['filter_product_brand'] ) ) {
|
||||
return array( 'taxonomy' => '' );
|
||||
}
|
||||
|
||||
return $cat_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the currently viewed taxonomy name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_current_taxonomy() {
|
||||
return is_tax() ? get_queried_object()->taxonomy : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the currently viewed term ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_current_term_id() {
|
||||
return absint( is_tax() ? get_queried_object()->term_id : 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the currently viewed term slug.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_current_term_slug() {
|
||||
return absint( is_tax() ? get_queried_object()->slug : 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Widget function.
|
||||
*
|
||||
* @see WP_Widget
|
||||
*
|
||||
* @param array $args Arguments.
|
||||
* @param array $instance Widget instance.
|
||||
* @return void
|
||||
*/
|
||||
public function widget( $args, $instance ) {
|
||||
$attribute_array = array();
|
||||
$attribute_taxonomies = wc_get_attribute_taxonomies();
|
||||
|
||||
if ( ! empty( $attribute_taxonomies ) ) {
|
||||
foreach ( $attribute_taxonomies as $tax ) {
|
||||
if ( taxonomy_exists( wc_attribute_taxonomy_name( $tax->attribute_name ) ) ) {
|
||||
$attribute_array[ $tax->attribute_name ] = $tax->attribute_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! is_post_type_archive( 'product' ) && ! is_tax( array_merge( is_array( $attribute_array ) ? $attribute_array : array(), array( 'product_cat', 'product_tag' ) ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$_chosen_attributes = WC_Query::get_layered_nav_chosen_attributes();
|
||||
|
||||
$current_term = $attribute_array && is_tax( $attribute_array ) ? get_queried_object()->term_id : '';
|
||||
$current_tax = $attribute_array && is_tax( $attribute_array ) ? get_queried_object()->taxonomy : '';
|
||||
|
||||
/**
|
||||
* Filter the widget's title.
|
||||
*
|
||||
* @since 9.4.0
|
||||
*
|
||||
* @param string $title Widget title
|
||||
* @param array $instance The settings for the particular instance of the widget.
|
||||
* @param string $woo_widget_idbase The widget's id base.
|
||||
*/
|
||||
$title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
|
||||
$taxonomy = 'product_brand';
|
||||
$display_type = isset( $instance['display_type'] ) ? $instance['display_type'] : 'list';
|
||||
|
||||
if ( ! taxonomy_exists( $taxonomy ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get only parent terms. Methods will recursively retrieve children.
|
||||
$terms = get_terms(
|
||||
array(
|
||||
'taxonomy' => $taxonomy,
|
||||
'hide_empty' => true,
|
||||
'parent' => 0,
|
||||
)
|
||||
);
|
||||
|
||||
if ( empty( $terms ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
$this->widget_start( $args, $instance );
|
||||
|
||||
if ( 'dropdown' === $display_type ) {
|
||||
$found = $this->layered_nav_dropdown( $terms, $taxonomy );
|
||||
} else {
|
||||
$found = $this->layered_nav_list( $terms, $taxonomy );
|
||||
}
|
||||
|
||||
$this->widget_end( $args );
|
||||
|
||||
// Force found when option is selected - do not force found on taxonomy attributes.
|
||||
if ( ! is_tax() && is_array( $_chosen_attributes ) && array_key_exists( $taxonomy, $_chosen_attributes ) ) {
|
||||
$found = true;
|
||||
}
|
||||
|
||||
if ( ! $found ) {
|
||||
ob_end_clean();
|
||||
} else {
|
||||
echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update function.
|
||||
*
|
||||
* @see WP_Widget->update
|
||||
*
|
||||
* @param array $new_instance The new settings for the particular instance of the widget.
|
||||
* @param array $old_instance The old settings for the particular instance of the widget.
|
||||
* @return array
|
||||
*/
|
||||
public function update( $new_instance, $old_instance ) {
|
||||
global $woocommerce;
|
||||
|
||||
if ( empty( $new_instance['title'] ) ) {
|
||||
$new_instance['title'] = __( 'Brands', 'woocommerce' );
|
||||
}
|
||||
|
||||
$instance['title'] = wp_strip_all_tags( stripslashes( $new_instance['title'] ) );
|
||||
$instance['display_type'] = stripslashes( $new_instance['display_type'] );
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form function.
|
||||
*
|
||||
* @see WP_Widget->form
|
||||
*
|
||||
* @param array $instance Widget instance.
|
||||
* @return void
|
||||
*/
|
||||
public function form( $instance ) {
|
||||
global $woocommerce;
|
||||
|
||||
if ( ! isset( $instance['display_type'] ) ) {
|
||||
$instance['display_type'] = 'list';
|
||||
}
|
||||
?>
|
||||
<p><label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'woocommerce' ); ?></label>
|
||||
<input type="text" class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" value="<?php echo isset( $instance['title'] ) ? esc_attr( $instance['title'] ) : ''; ?>" />
|
||||
</p>
|
||||
|
||||
<p><label for="<?php echo esc_attr( $this->get_field_id( 'display_type' ) ); ?>"><?php esc_html_e( 'Display Type:', 'woocommerce' ); ?></label>
|
||||
<select id="<?php echo esc_attr( $this->get_field_id( 'display_type' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'display_type' ) ); ?>">
|
||||
<option value="list" <?php selected( $instance['display_type'], 'list' ); ?>><?php esc_html_e( 'List', 'woocommerce' ); ?></option>
|
||||
<option value="dropdown" <?php selected( $instance['display_type'], 'dropdown' ); ?>><?php esc_html_e( 'Dropdown', 'woocommerce' ); ?></option>
|
||||
</select></p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page URL for layered nav items.
|
||||
*
|
||||
* @param string $taxonomy Taxonomy.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_page_base_url( $taxonomy ) {
|
||||
if ( defined( 'SHOP_IS_ON_FRONT' ) ) {
|
||||
$link = home_url();
|
||||
} elseif ( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) ) {
|
||||
$link = get_post_type_archive_link( 'product' );
|
||||
} elseif ( is_product_category() ) {
|
||||
$link = get_term_link( get_query_var( 'product_cat' ), 'product_cat' );
|
||||
} elseif ( is_product_tag() ) {
|
||||
$link = get_term_link( get_query_var( 'product_tag' ), 'product_tag' );
|
||||
} else {
|
||||
$link = get_term_link( get_query_var( 'term' ), get_query_var( 'taxonomy' ) );
|
||||
}
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
// Min/Max.
|
||||
if ( isset( $_GET['min_price'] ) ) {
|
||||
$link = add_query_arg( 'min_price', wc_clean( wp_unslash( $_GET['min_price'] ) ), $link );
|
||||
}
|
||||
|
||||
if ( isset( $_GET['max_price'] ) ) {
|
||||
$link = add_query_arg( 'max_price', wc_clean( wp_unslash( $_GET['max_price'] ) ), $link );
|
||||
}
|
||||
|
||||
// Orderby.
|
||||
if ( isset( $_GET['orderby'] ) ) {
|
||||
$link = add_query_arg( 'orderby', wc_clean( wp_unslash( $_GET['orderby'] ) ), $link );
|
||||
}
|
||||
|
||||
/**
|
||||
* Search Arg.
|
||||
* To support quote characters, first they are decoded from " entities, then URL encoded.
|
||||
*/
|
||||
if ( get_search_query() ) {
|
||||
$link = add_query_arg( 's', rawurlencode( htmlspecialchars_decode( get_search_query() ) ), $link );
|
||||
}
|
||||
|
||||
// Post Type Arg.
|
||||
if ( isset( $_GET['post_type'] ) ) {
|
||||
$link = add_query_arg( 'post_type', wc_clean( wp_unslash( $_GET['post_type'] ) ), $link );
|
||||
}
|
||||
|
||||
// Min Rating Arg.
|
||||
if ( isset( $_GET['min_rating'] ) ) {
|
||||
$link = add_query_arg( 'min_rating', wc_clean( wp_unslash( $_GET['min_rating'] ) ), $link );
|
||||
}
|
||||
|
||||
// All current filters.
|
||||
$_chosen_attributes = WC_Query::get_layered_nav_chosen_attributes();
|
||||
if ( $_chosen_attributes ) {
|
||||
foreach ( $_chosen_attributes as $name => $data ) {
|
||||
if ( $name === $taxonomy ) {
|
||||
continue;
|
||||
}
|
||||
$filter_name = sanitize_title( str_replace( 'pa_', '', $name ) );
|
||||
if ( ! empty( $data['terms'] ) ) {
|
||||
$link = add_query_arg( 'filter_' . $filter_name, implode( ',', $data['terms'] ), $link );
|
||||
}
|
||||
if ( 'or' === $data['query_type'] ) {
|
||||
$link = add_query_arg( 'query_type_' . $filter_name, 'or', $link );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
return esc_url( $link );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently selected attributes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_chosen_attributes() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! empty( $_GET['filter_product_brand'] ) ) {
|
||||
$filter_product_brand = wc_clean( wp_unslash( $_GET['filter_product_brand'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return array_map( 'intval', explode( ',', $filter_product_brand ) );
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dropdown layered nav.
|
||||
*
|
||||
* @param array $terms Terms.
|
||||
* @param string $taxonomy Taxonomy.
|
||||
* @param int $depth Depth.
|
||||
* @return bool Will nav display?
|
||||
*/
|
||||
protected function layered_nav_dropdown( $terms, $taxonomy, $depth = 0 ) {
|
||||
$found = false;
|
||||
|
||||
if ( $taxonomy !== $this->get_current_taxonomy() ) {
|
||||
$term_counts = $this->get_filtered_term_product_counts( wp_list_pluck( $terms, 'term_id' ), $taxonomy, 'or' );
|
||||
$_chosen_attributes = $this->get_chosen_attributes();
|
||||
|
||||
if ( 0 === $depth ) {
|
||||
echo '<select class="wc-brand-dropdown-layered-nav-' . esc_attr( $taxonomy ) . '">';
|
||||
echo '<option value="">' . esc_html__( 'Any Brand', 'woocommerce' ) . '</option>';
|
||||
}
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
// If on a term page, skip that term in widget list.
|
||||
if ( $term->term_id === $this->get_current_term_id() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get count based on current view.
|
||||
$current_values = ! empty( $_chosen_attributes ) ? $_chosen_attributes : array();
|
||||
$option_is_set = in_array( $term->term_id, $current_values, true );
|
||||
$count = isset( $term_counts[ $term->term_id ] ) ? $term_counts[ $term->term_id ] : 0;
|
||||
|
||||
// Only show options with count > 0.
|
||||
if ( 0 < $count ) {
|
||||
$found = true;
|
||||
} elseif ( 0 === $count && ! $option_is_set ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
echo '<option value="' . esc_attr( $term->term_id ) . '" ' . selected( $option_is_set, true, false ) . '>' . esc_html( str_repeat( ' ', 2 * $depth ) . $term->name ) . '</option>';
|
||||
|
||||
$child_terms = get_terms(
|
||||
array(
|
||||
'taxonomy' => $taxonomy,
|
||||
'hide_empty' => true,
|
||||
'parent' => $term->term_id,
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! empty( $child_terms ) ) {
|
||||
$found |= $this->layered_nav_dropdown( $child_terms, $taxonomy, $depth + 1 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( 0 === $depth ) {
|
||||
$link = $this->get_page_base_url( $taxonomy );
|
||||
echo '</select>';
|
||||
|
||||
wc_enqueue_js(
|
||||
"
|
||||
jQuery( '.wc-brand-dropdown-layered-nav-" . esc_js( $taxonomy ) . "' ).change( function() {
|
||||
var slug = jQuery( this ).val();
|
||||
location.href = '" . preg_replace( '%\/page\/[0-9]+%', '', str_replace( array( '&', '%2C' ), array( '&', ',' ), esc_js( add_query_arg( 'filtering', '1', $link ) ) ) ) . '&filter_' . esc_js( $taxonomy ) . "=' + jQuery( '.wc-brand-dropdown-layered-nav-" . esc_js( $taxonomy ) . "' ).val();
|
||||
});
|
||||
"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show list based layered nav.
|
||||
*
|
||||
* @param array $terms Terms.
|
||||
* @param string $taxonomy Taxonomy.
|
||||
* @param int $depth Depth.
|
||||
* @return bool Will nav display?
|
||||
*/
|
||||
protected function layered_nav_list( $terms, $taxonomy, $depth = 0 ) {
|
||||
// List display.
|
||||
echo '<ul class="' . ( 0 === $depth ? '' : 'children ' ) . 'wc-brand-list-layered-nav-' . esc_attr( $taxonomy ) . '">';
|
||||
|
||||
$term_counts = $this->get_filtered_term_product_counts( wp_list_pluck( $terms, 'term_id' ), $taxonomy, 'or' );
|
||||
$_chosen_attributes = $this->get_chosen_attributes();
|
||||
$current_values = ! empty( $_chosen_attributes ) ? $_chosen_attributes : array();
|
||||
$found = false;
|
||||
|
||||
$filter_name = 'filter_' . $taxonomy;
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
$option_is_set = in_array( $term->term_id, $current_values, true );
|
||||
$count = isset( $term_counts[ $term->term_id ] ) ? $term_counts[ $term->term_id ] : 0;
|
||||
|
||||
// skip the term for the current archive.
|
||||
if ( $this->get_current_term_id() === $term->term_id ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only show options with count > 0.
|
||||
if ( 0 < $count ) {
|
||||
$found = true;
|
||||
} elseif ( 0 === $count && ! $option_is_set ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$current_filter = isset( $_GET[ $filter_name ] ) ? explode( ',', wc_clean( wp_unslash( $_GET[ $filter_name ] ) ) ) : array(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$current_filter = array_map( 'intval', $current_filter );
|
||||
|
||||
if ( ! in_array( $term->term_id, $current_filter, true ) ) {
|
||||
$current_filter[] = $term->term_id;
|
||||
}
|
||||
|
||||
$link = $this->get_page_base_url( $taxonomy );
|
||||
|
||||
// Add current filters to URL.
|
||||
foreach ( $current_filter as $key => $value ) {
|
||||
// Exclude query arg for current term archive term.
|
||||
if ( $value === $this->get_current_term_id() ) {
|
||||
unset( $current_filter[ $key ] );
|
||||
}
|
||||
|
||||
// Exclude self so filter can be unset on click.
|
||||
if ( $option_is_set && $value === $term->term_id ) {
|
||||
unset( $current_filter[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $current_filter ) ) {
|
||||
$link = add_query_arg(
|
||||
array(
|
||||
'filtering' => '1',
|
||||
$filter_name => implode( ',', $current_filter ),
|
||||
),
|
||||
$link
|
||||
);
|
||||
}
|
||||
|
||||
echo '<li class="wc-layered-nav-term ' . ( $option_is_set ? 'chosen' : '' ) . '">';
|
||||
|
||||
echo ( $count > 0 || $option_is_set ) ? '<a href="' . esc_url( apply_filters( 'woocommerce_layered_nav_link', $link ) ) . '">' : '<span>'; // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
|
||||
echo esc_html( $term->name );
|
||||
|
||||
echo ( $count > 0 || $option_is_set ) ? '</a> ' : '</span> ';
|
||||
|
||||
echo wp_kses_post( apply_filters( 'woocommerce_layered_nav_count', '<span class="count">(' . absint( $count ) . ')</span>', $count, $term ) );// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
|
||||
$child_terms = get_terms(
|
||||
array(
|
||||
'taxonomy' => $taxonomy,
|
||||
'hide_empty' => true,
|
||||
'parent' => $term->term_id,
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! empty( $child_terms ) ) {
|
||||
$found |= $this->layered_nav_list( $child_terms, $taxonomy, $depth + 1 );
|
||||
}
|
||||
|
||||
echo '</li>';
|
||||
}
|
||||
|
||||
echo '</ul>';
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count products within certain terms, taking the main WP query into consideration.
|
||||
*
|
||||
* @param array $term_ids Term IDs.
|
||||
* @param string $taxonomy Taxonomy.
|
||||
* @param string $query_type Query type.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_filtered_term_product_counts( $term_ids, $taxonomy, $query_type = 'and' ) {
|
||||
global $wpdb;
|
||||
|
||||
$tax_query = WC_Query::get_main_tax_query();
|
||||
$meta_query = WC_Query::get_main_meta_query();
|
||||
|
||||
if ( 'or' === $query_type ) {
|
||||
foreach ( $tax_query as $key => $query ) {
|
||||
if ( is_array( $query ) && $taxonomy === $query['taxonomy'] ) {
|
||||
unset( $tax_query[ $key ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$meta_query = new WP_Meta_Query( $meta_query );
|
||||
$tax_query = new WP_Tax_Query( $tax_query );
|
||||
$meta_query_sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' );
|
||||
$tax_query_sql = $tax_query->get_sql( $wpdb->posts, 'ID' );
|
||||
|
||||
// Generate query.
|
||||
$query = array();
|
||||
$query['select'] = "SELECT COUNT( DISTINCT {$wpdb->posts}.ID ) as term_count, terms.term_id as term_count_id";
|
||||
$query['from'] = "FROM {$wpdb->posts}";
|
||||
$query['join'] = "
|
||||
INNER JOIN {$wpdb->term_relationships} AS term_relationships ON {$wpdb->posts}.ID = term_relationships.object_id
|
||||
INNER JOIN {$wpdb->term_taxonomy} AS term_taxonomy USING( term_taxonomy_id )
|
||||
INNER JOIN {$wpdb->terms} AS terms USING( term_id )
|
||||
" . $tax_query_sql['join'] . $meta_query_sql['join'];
|
||||
$query['where'] = "
|
||||
WHERE {$wpdb->posts}.post_type IN ( 'product' )
|
||||
AND {$wpdb->posts}.post_status = 'publish'
|
||||
" . $tax_query_sql['where'] . $meta_query_sql['where'] . '
|
||||
AND terms.term_id IN (' . implode( ',', array_map( 'absint', $term_ids ) ) . ')
|
||||
';
|
||||
$query['group_by'] = 'GROUP BY terms.term_id';
|
||||
$query = apply_filters( 'woocommerce_get_filtered_term_product_counts_query', $query ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
$query = implode( ' ', $query );
|
||||
|
||||
// We have a query - let's see if cached results of this query already exist.
|
||||
$query_hash = md5( $query );
|
||||
|
||||
$cache = apply_filters( 'woocommerce_layered_nav_count_maybe_cache', true ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
if ( true === $cache ) {
|
||||
$cached_counts = (array) get_transient( 'wc_layered_nav_counts_' . sanitize_title( $taxonomy ) );
|
||||
} else {
|
||||
$cached_counts = array();
|
||||
}
|
||||
|
||||
if ( ! isset( $cached_counts[ $query_hash ] ) ) {
|
||||
$results = $wpdb->get_results( $query, ARRAY_A ); // @codingStandardsIgnoreLine
|
||||
$counts = array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) );
|
||||
$cached_counts[ $query_hash ] = $counts;
|
||||
if ( true === $cache ) {
|
||||
set_transient( 'wc_layered_nav_counts_' . sanitize_title( $taxonomy ), $cached_counts, HOUR_IN_SECONDS );
|
||||
}
|
||||
}
|
||||
|
||||
return array_map( 'absint', (array) $cached_counts[ $query_hash ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1);
|
||||
|
||||
/**
|
||||
* Brand Thumbnails Widget
|
||||
*
|
||||
* Show brand images as thumbnails
|
||||
*
|
||||
* Important: For internal use only by the Automattic\WooCommerce\Internal\Brands package.
|
||||
*
|
||||
* @package WooCommerce\Widgets
|
||||
* @version 9.4.0
|
||||
*/
|
||||
class WC_Widget_Brand_Thumbnails extends WP_Widget {
|
||||
|
||||
/**
|
||||
* Widget CSS class.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $woo_widget_cssclass;
|
||||
|
||||
/**
|
||||
* Widget description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $woo_widget_description;
|
||||
|
||||
/**
|
||||
* Widget id base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $woo_widget_idbase;
|
||||
|
||||
/**
|
||||
* Widget name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $woo_widget_name;
|
||||
|
||||
/** Constructor */
|
||||
public function __construct() {
|
||||
|
||||
/* Widget variable settings. */
|
||||
$this->woo_widget_name = __( 'WooCommerce Brand Thumbnails', 'woocommerce' );
|
||||
$this->woo_widget_description = __( 'Show a grid of brand thumbnails.', 'woocommerce' );
|
||||
$this->woo_widget_idbase = 'wc_brands_brand_thumbnails';
|
||||
$this->woo_widget_cssclass = 'widget_brand_thumbnails';
|
||||
|
||||
/* Widget settings. */
|
||||
$widget_ops = array(
|
||||
'classname' => $this->woo_widget_cssclass,
|
||||
'description' => $this->woo_widget_description,
|
||||
);
|
||||
|
||||
/* Create the widget. */
|
||||
parent::__construct( $this->woo_widget_idbase, $this->woo_widget_name, $widget_ops );
|
||||
}
|
||||
|
||||
/**
|
||||
* Echoes the widget content.
|
||||
*
|
||||
* @see WP_Widget
|
||||
*
|
||||
* @param array $args Display arguments including 'before_title', 'after_title',
|
||||
* 'before_widget', and 'after_widget'.
|
||||
* @param array $instance The settings for the particular instance of the widget.
|
||||
*/
|
||||
public function widget( $args, $instance ) {
|
||||
$instance = wp_parse_args(
|
||||
$instance,
|
||||
array(
|
||||
'title' => '',
|
||||
'columns' => 1,
|
||||
'exclude' => '',
|
||||
'orderby' => 'name',
|
||||
'hide_empty' => 0,
|
||||
'number' => '',
|
||||
)
|
||||
);
|
||||
|
||||
$exclude = array_map( 'intval', explode( ',', $instance['exclude'] ) );
|
||||
$order = 'name' === $instance['orderby'] ? 'asc' : 'desc';
|
||||
|
||||
$brands = get_terms(
|
||||
array(
|
||||
'taxonomy' => 'product_brand',
|
||||
'hide_empty' => $instance['hide_empty'],
|
||||
'orderby' => $instance['orderby'],
|
||||
'exclude' => $exclude,
|
||||
'number' => $instance['number'],
|
||||
'order' => $order,
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! $brands ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the widget's title.
|
||||
*
|
||||
* @since 9.4.0
|
||||
*
|
||||
* @param string $title Widget title
|
||||
* @param array $instance The settings for the particular instance of the widget.
|
||||
* @param string $woo_widget_idbase The widget's id base.
|
||||
*/
|
||||
$title = apply_filters( 'widget_title', $instance['title'], $instance, $this->woo_widget_idbase );
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput
|
||||
if ( '' !== $title ) {
|
||||
echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput
|
||||
}
|
||||
|
||||
wc_get_template(
|
||||
'widgets/brand-thumbnails.php',
|
||||
array(
|
||||
'brands' => $brands,
|
||||
'columns' => (int) $instance['columns'],
|
||||
'fluid_columns' => ! empty( $instance['fluid_columns'] ) ? true : false,
|
||||
),
|
||||
'woocommerce',
|
||||
WC()->plugin_path() . '/templates/brands/'
|
||||
);
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput
|
||||
}
|
||||
|
||||
/**
|
||||
* Update widget instance.
|
||||
*
|
||||
* @param array $new_instance The new settings for the particular instance of the widget.
|
||||
* @param array $old_instance The old settings for the particular instance of the widget.
|
||||
*
|
||||
* @see WP_Widget->update
|
||||
*/
|
||||
public function update( $new_instance, $old_instance ) {
|
||||
$instance['title'] = wp_strip_all_tags( stripslashes( $new_instance['title'] ) );
|
||||
$instance['columns'] = wp_strip_all_tags( stripslashes( $new_instance['columns'] ) );
|
||||
$instance['fluid_columns'] = ! empty( $new_instance['fluid_columns'] ) ? true : false;
|
||||
$instance['orderby'] = wp_strip_all_tags( stripslashes( $new_instance['orderby'] ) );
|
||||
$instance['exclude'] = wp_strip_all_tags( stripslashes( $new_instance['exclude'] ) );
|
||||
$instance['hide_empty'] = wp_strip_all_tags( stripslashes( (string) $new_instance['hide_empty'] ) );
|
||||
$instance['number'] = wp_strip_all_tags( stripslashes( $new_instance['number'] ) );
|
||||
|
||||
if ( ! $instance['columns'] ) {
|
||||
$instance['columns'] = 1;
|
||||
}
|
||||
|
||||
if ( ! $instance['orderby'] ) {
|
||||
$instance['orderby'] = 'name';
|
||||
}
|
||||
|
||||
if ( ! $instance['exclude'] ) {
|
||||
$instance['exclude'] = '';
|
||||
}
|
||||
|
||||
if ( ! $instance['hide_empty'] ) {
|
||||
$instance['hide_empty'] = 0;
|
||||
}
|
||||
|
||||
if ( ! $instance['number'] ) {
|
||||
$instance['number'] = '';
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the settings update form.
|
||||
*
|
||||
* @param array $instance Current settings.
|
||||
*/
|
||||
public function form( $instance ) {
|
||||
if ( ! isset( $instance['hide_empty'] ) ) {
|
||||
$instance['hide_empty'] = 0;
|
||||
}
|
||||
|
||||
if ( ! isset( $instance['orderby'] ) ) {
|
||||
$instance['orderby'] = 'name';
|
||||
}
|
||||
|
||||
if ( empty( $instance['fluid_columns'] ) ) {
|
||||
$instance['fluid_columns'] = false;
|
||||
}
|
||||
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'woocommerce' ); ?></label>
|
||||
<input type="text" class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" value="<?php echo isset( $instance['title'] ) ? esc_attr( $instance['title'] ) : ''; ?>" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( 'columns' ) ); ?>"><?php esc_html_e( 'Columns:', 'woocommerce' ); ?></label>
|
||||
<input type="text" class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'columns' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'columns' ) ); ?>" value="<?php echo isset( $instance['columns'] ) ? esc_attr( $instance['columns'] ) : '1'; ?>" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( 'fluid_columns' ) ); ?>"><?php esc_html_e( 'Fluid columns:', 'woocommerce' ); ?></label>
|
||||
<input type="checkbox" <?php checked( $instance['fluid_columns'] ); ?> id="<?php echo esc_attr( $this->get_field_id( 'fluid_columns' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'fluid_columns' ) ); ?>" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( 'number' ) ); ?>"><?php esc_html_e( 'Number:', 'woocommerce' ); ?></label>
|
||||
<input type="text" class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'number' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'number' ) ); ?>" value="<?php if ( isset ( $instance['number'] ) ) { echo esc_attr( $instance['number'] ); } // phpcs:ignore ?>" placeholder="<?php esc_attr_e( 'All', 'woocommerce' ); ?>" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( 'exclude' ) ); ?>"><?php esc_html_e( 'Exclude:', 'woocommerce' ); ?></label>
|
||||
<input type="text" class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'exclude' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'exclude' ) ); ?>" value="<?php if ( isset ( $instance['exclude'] ) ) { echo esc_attr( $instance['exclude'] ); } // phpcs:ignore ?>" placeholder="<?php esc_attr_e( 'None', 'woocommerce' ); ?>" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( 'hide_empty' ) ); ?>"><?php esc_html_e( 'Hide empty brands:', 'woocommerce' ); ?></label>
|
||||
<select id="<?php echo esc_attr( $this->get_field_id( 'hide_empty' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'hide_empty' ) ); ?>">
|
||||
<option value="1" <?php selected( $instance['hide_empty'], 1 ); ?>><?php esc_html_e( 'Yes', 'woocommerce' ); ?></option>
|
||||
<option value="0" <?php selected( $instance['hide_empty'], 0 ); ?>><?php esc_html_e( 'No', 'woocommerce' ); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( 'orderby' ) ); ?>"><?php esc_html_e( 'Order by:', 'woocommerce' ); ?></label>
|
||||
<select id="<?php echo esc_attr( $this->get_field_id( 'orderby' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'orderby' ) ); ?>">
|
||||
<option value="name" <?php selected( $instance['orderby'], 'name' ); ?>><?php esc_html_e( 'Name', 'woocommerce' ); ?></option>
|
||||
<option value="count" <?php selected( $instance['orderby'], 'count' ); ?>><?php esc_html_e( 'Count', 'woocommerce' ); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user