admin_url( 'admin-ajax.php' ), 'removeNonce' => wp_create_nonce( 'aaweb_bpafw_remove_term' ), 'removeError' => esc_html__( 'The attribute value could not be removed. Please try again.', 'aaweb-bulk-product-attributes-for-woocommerce' ), ] ); } private function get_current_filters() { return [ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Filters are read-only admin view parameters. 's' => isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Filters are read-only admin view parameters. 'aaweb_keyword' => isset( $_GET['aaweb_keyword'] ) ? sanitize_text_field( wp_unslash( $_GET['aaweb_keyword'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Filters are read-only admin view parameters. 'product_cat' => isset( $_GET['product_cat'] ) ? absint( $_GET['product_cat'] ) : 0, // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Filters are read-only admin view parameters. 'stock_status' => isset( $_GET['stock_status'] ) ? sanitize_text_field( wp_unslash( $_GET['stock_status'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Filters are read-only admin view parameters. 'paged' => isset( $_GET['paged'] ) ? max( 1, absint( $_GET['paged'] ) ) : 1, ]; } private function build_query_args( $filters ) { $args = [ 'post_type' => 'product', 'post_status' => [ 'publish', 'draft', 'pending', 'private' ], 'posts_per_page' => $this->per_page, 'paged' => $filters['paged'], 'orderby' => 'date', 'order' => 'DESC', 'ignore_sticky_posts' => true, 'no_found_rows' => false, 'update_post_meta_cache' => true, 'update_post_term_cache' => true, 'suppress_filters' => false, 'aaweb_keyword' => $filters['aaweb_keyword'], 'aaweb_query' => 1, ]; if ( $filters['s'] !== '' ) { $args['aaweb_title_search'] = $filters['s']; } if ( $filters['product_cat'] > 0 ) { // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Required admin filter for WooCommerce product categories. $args['tax_query'] = [ [ 'taxonomy' => 'product_cat', 'field' => 'term_id', 'terms' => [ $filters['product_cat'] ], ], ]; } if ( $filters['stock_status'] !== '' ) { // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Required admin filter for WooCommerce stock status. $args['meta_query'] = [ [ 'key' => '_stock_status', 'value' => $filters['stock_status'], ], ]; } return $args; } private function get_store_attribute_taxonomies() { $result = []; if ( ! function_exists( 'wc_get_attribute_taxonomies' ) ) { return $result; } $attributes = wc_get_attribute_taxonomies(); if ( empty( $attributes ) ) { return $result; } foreach ( $attributes as $attribute ) { if ( empty( $attribute->attribute_name ) ) { continue; } $taxonomy = wc_attribute_taxonomy_name( $attribute->attribute_name ); if ( ! taxonomy_exists( $taxonomy ) ) { continue; } $label = ! empty( $attribute->attribute_label ) ? $attribute->attribute_label : $attribute->attribute_name; $result[ $taxonomy ] = [ 'id' => (int) $attribute->attribute_id, 'name' => $attribute->attribute_name, 'label' => $label, ]; } return $result; } private function get_attribute_terms( $taxonomy ) { if ( ! taxonomy_exists( $taxonomy ) ) { return []; } $terms = get_terms( [ 'taxonomy' => $taxonomy, 'hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC', ] ); if ( is_wp_error( $terms ) || empty( $terms ) ) { return []; } return $terms; } private function ensure_term_exists( $taxonomy, $term_name ) { if ( ! taxonomy_exists( $taxonomy ) ) { return 0; } $term_name = trim( (string) $term_name ); if ( $term_name === '' ) { return 0; } $term = get_term_by( 'name', $term_name, $taxonomy ); if ( $term && ! is_wp_error( $term ) ) { return (int) $term->term_id; } $created = wp_insert_term( $term_name, $taxonomy ); if ( is_wp_error( $created ) || empty( $created['term_id'] ) ) { return 0; } return (int) $created['term_id']; } private function ensure_product_attribute_meta( $product_id, $taxonomy ) { $product_attributes = get_post_meta( $product_id, '_product_attributes', true ); if ( ! is_array( $product_attributes ) ) { $product_attributes = []; } $existing_position = isset( $product_attributes[ $taxonomy ]['position'] ) ? (int) $product_attributes[ $taxonomy ]['position'] : count( $product_attributes ); $product_attributes[ $taxonomy ] = [ 'name' => $taxonomy, 'value' => '', 'position' => $existing_position, 'is_visible' => 1, 'is_variation' => 0, 'is_taxonomy' => 1, ]; update_post_meta( $product_id, '_product_attributes', $product_attributes ); } private function apply_attribute_terms_to_product( $product_id, $taxonomy, array $term_names, $mode = 'append' ) { $product_id = absint( $product_id ); if ( ! $product_id || get_post_type( $product_id ) !== 'product' ) { return; } if ( ! taxonomy_exists( $taxonomy ) ) { return; } $term_ids = []; foreach ( $term_names as $term_name ) { $term_name = trim( wp_unslash( $term_name ) ); if ( $term_name === '' ) { continue; } $term_id = $this->ensure_term_exists( $taxonomy, $term_name ); if ( $term_id ) { $term_ids[] = $term_id; } } $term_ids = array_values( array_unique( array_map( 'intval', $term_ids ) ) ); if ( empty( $term_ids ) ) { return; } if ( 'replace' === $mode ) { wp_set_object_terms( $product_id, $term_ids, $taxonomy, false ); } else { $current_ids = wp_get_object_terms( $product_id, $taxonomy, [ 'fields' => 'ids' ] ); if ( is_wp_error( $current_ids ) ) { $current_ids = []; } $all_ids = array_values( array_unique( array_map( 'intval', array_merge( $current_ids, $term_ids ) ) ) ); wp_set_object_terms( $product_id, $all_ids, $taxonomy, false ); } $this->ensure_product_attribute_meta( $product_id, $taxonomy ); if ( function_exists( 'wc_delete_product_transients' ) ) { wc_delete_product_transients( $product_id ); } clean_post_cache( $product_id ); } public function handle_remove_term() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( [], 403 ); } check_ajax_referer( 'aaweb_bpafw_remove_term' ); $product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0; $term_id = isset( $_POST['term_id'] ) ? absint( $_POST['term_id'] ) : 0; $taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_key( wp_unslash( $_POST['taxonomy'] ) ) : ''; if ( ! $product_id || ! $term_id || ! $taxonomy || 'product' !== get_post_type( $product_id ) ) { wp_send_json_error( [], 400 ); } if ( ! taxonomy_exists( $taxonomy ) || ! is_object_in_taxonomy( 'product', $taxonomy ) ) { wp_send_json_error( [], 400 ); } $term = get_term( $term_id, $taxonomy ); if ( ! $term || is_wp_error( $term ) ) { wp_send_json_error( [], 404 ); } $result = wp_remove_object_terms( $product_id, $term_id, $taxonomy ); if ( is_wp_error( $result ) ) { wp_send_json_error( [], 500 ); } $remaining_ids = wp_get_object_terms( $product_id, $taxonomy, [ 'fields' => 'ids' ] ); if ( ! is_wp_error( $remaining_ids ) && empty( $remaining_ids ) ) { $product_attributes = get_post_meta( $product_id, '_product_attributes', true ); if ( is_array( $product_attributes ) && isset( $product_attributes[ $taxonomy ] ) ) { unset( $product_attributes[ $taxonomy ] ); update_post_meta( $product_id, '_product_attributes', $product_attributes ); } } if ( function_exists( 'wc_delete_product_transients' ) ) { wc_delete_product_transients( $product_id ); } clean_post_cache( $product_id ); wp_send_json_success(); } public function handle_bulk_apply() { if ( ! is_admin() ) { return; } if ( ! isset( $_POST['aaweb_bulk_apply_submit'] ) ) { return; } if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } check_admin_referer( 'aaweb_bulk_apply_action', 'aaweb_bulk_apply_nonce' ); $product_ids = isset( $_POST['product_ids'] ) ? array_map( 'absint', (array) wp_unslash( $_POST['product_ids'] ) ) : []; $product_ids = array_values( array_filter( $product_ids ) ); $mode = isset( $_POST['aaweb_assign_mode'] ) ? sanitize_text_field( wp_unslash( $_POST['aaweb_assign_mode'] ) ) : 'append'; if ( ! in_array( $mode, [ 'append', 'replace' ], true ) ) { $mode = 'append'; } $allowed_taxonomies = $this->get_store_attribute_taxonomies(); $posted_attributes = isset( $_POST['aaweb_attrs'] ) && is_array( $_POST['aaweb_attrs'] ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nested values are sanitized below after taxonomy validation. ? wp_unslash( $_POST['aaweb_attrs'] ) : []; $sanitized_attributes = []; foreach ( $posted_attributes as $taxonomy => $values ) { $taxonomy = sanitize_key( $taxonomy ); if ( ! isset( $allowed_taxonomies[ $taxonomy ] ) ) { continue; } $values = is_array( $values ) ? array_map( 'sanitize_text_field', $values ) : []; $values = array_values( array_filter( array_map( 'trim', $values ) ) ); if ( ! empty( $values ) ) { $sanitized_attributes[ $taxonomy ] = $values; } } $updated = 0; if ( ! empty( $product_ids ) && ! empty( $sanitized_attributes ) ) { foreach ( $product_ids as $product_id ) { foreach ( $sanitized_attributes as $taxonomy => $term_names ) { $this->apply_attribute_terms_to_product( $product_id, $taxonomy, $term_names, $mode ); } $updated++; } } $redirect_url = add_query_arg( [ 'post_type' => 'product', 'page' => 'aaweb-bulk-product-attributes-for-woocommerce', 's' => isset( $_POST['current_s'] ) ? sanitize_text_field( wp_unslash( $_POST['current_s'] ) ) : '', 'aaweb_keyword' => isset( $_POST['current_aaweb_keyword'] ) ? sanitize_text_field( wp_unslash( $_POST['current_aaweb_keyword'] ) ) : '', 'product_cat' => isset( $_POST['current_product_cat'] ) ? absint( $_POST['current_product_cat'] ) : 0, 'stock_status' => isset( $_POST['current_stock_status'] ) ? sanitize_text_field( wp_unslash( $_POST['current_stock_status'] ) ) : '', 'paged' => isset( $_POST['current_paged'] ) ? max( 1, absint( $_POST['current_paged'] ) ) : 1, 'aaweb_updated' => $updated, ], admin_url( 'edit.php' ) ); wp_safe_redirect( $redirect_url ); exit; } private function get_product_term_names( $product_id, $taxonomy ) { $terms = get_the_terms( $product_id, $taxonomy ); if ( empty( $terms ) || is_wp_error( $terms ) ) { return '—'; } $names = wp_list_pluck( $terms, 'name' ); return implode( ', ', $names ); } private function get_product_term_badges( $product_id, $taxonomy ) { $terms = get_the_terms( $product_id, $taxonomy ); if ( empty( $terms ) || is_wp_error( $terms ) ) { return '—'; } $html = ''; foreach ( $terms as $term ) { $remove_label = sprintf( /* translators: %s: attribute term name. */ __( 'Remove %s', 'aaweb-bulk-product-attributes-for-woocommerce' ), $term->name ); $html .= ''; $html .= '' . esc_html( $term->name ) . ''; $html .= ''; $html .= ''; } return $html ? $html : '—'; } private function get_stock_label( $product ) { if ( ! $product || ! is_a( $product, 'WC_Product' ) ) { return '—'; } $status = $product->get_stock_status(); switch ( $status ) { case 'instock': return esc_html__( 'In stock', 'aaweb-bulk-product-attributes-for-woocommerce' ); case 'outofstock': return esc_html__( 'Out of stock', 'aaweb-bulk-product-attributes-for-woocommerce' ); case 'onbackorder': return esc_html__( 'On backorder', 'aaweb-bulk-product-attributes-for-woocommerce' ); default: return $status ? $status : '—'; } } private function get_stock_class( $product ) { if ( ! $product || ! is_a( $product, 'WC_Product' ) ) { return 'unknown'; } $status = $product->get_stock_status(); return $status ? sanitize_html_class( $status ) : 'unknown'; } private function render_admin_styles() { // Admin styles are enqueued via enqueue_admin_assets(). } private function render_filters_form( $filters ) { ?>
esc_html__( 'All categories', 'aaweb-bulk-product-attributes-for-woocommerce' ), 'taxonomy' => 'product_cat', 'name' => 'product_cat', 'id' => 'product_cat', 'orderby' => 'name', 'selected' => $filters['product_cat'], 'hierarchical' => true, 'hide_empty' => false, 'value_field' => 'term_id', ] ); ?>
get_store_attribute_taxonomies(); ?>

$attribute ) : ?> get_attribute_terms( $taxonomy ); ?>
build_query_args( $filters ); add_filter( 'posts_where', [ $this, 'title_exact_phrase_where_clause' ], 9, 2 ); add_filter( 'posts_join', [ $this, 'keyword_join_sku_meta' ], 10, 2 ); add_filter( 'posts_where', [ $this, 'keyword_where_clause' ], 10, 2 ); add_filter( 'posts_groupby', [ $this, 'keyword_groupby_clause' ], 10, 2 ); $query = new WP_Query( $args ); remove_filter( 'posts_where', [ $this, 'title_exact_phrase_where_clause' ], 9 ); remove_filter( 'posts_join', [ $this, 'keyword_join_sku_meta' ], 10 ); remove_filter( 'posts_where', [ $this, 'keyword_where_clause' ], 10 ); remove_filter( 'posts_groupby', [ $this, 'keyword_groupby_clause' ], 10 ); return $query; } public function title_exact_phrase_where_clause( $where, $query ) { global $wpdb; if ( ! $query->get( 'aaweb_query' ) || ! $query->get( 'aaweb_title_search' ) ) { return $where; } $search = trim( (string) $query->get( 'aaweb_title_search' ) ); if ( '' === $search ) { return $where; } $search = preg_replace( '/\s+/u', ' ', $search ); $like = '%' . $wpdb->esc_like( $search ) . '%'; $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_title LIKE %s", $like ); return $where; } public function keyword_join_sku_meta( $join, $query ) { global $wpdb; if ( ! $query->get( 'aaweb_query' ) || ! $query->get( 'aaweb_keyword' ) ) { return $join; } if ( strpos( $join, 'aaweb_sku_meta' ) === false ) { $join .= " LEFT JOIN {$wpdb->postmeta} AS aaweb_sku_meta ON ({$wpdb->posts}.ID = aaweb_sku_meta.post_id AND aaweb_sku_meta.meta_key = '_sku')"; } return $join; } public function keyword_where_clause( $where, $query ) { global $wpdb; if ( ! $query->get( 'aaweb_query' ) || ! $query->get( 'aaweb_keyword' ) ) { return $where; } $keyword = trim( (string) $query->get( 'aaweb_keyword' ) ); if ( '' === $keyword ) { return $where; } $like = '%' . $wpdb->esc_like( $keyword ) . '%'; $where .= $wpdb->prepare( " AND ( {$wpdb->posts}.post_title LIKE %s OR {$wpdb->posts}.post_excerpt LIKE %s OR {$wpdb->posts}.post_content LIKE %s OR aaweb_sku_meta.meta_value LIKE %s )", $like, $like, $like, $like ); return $where; } public function keyword_groupby_clause( $groupby, $query ) { global $wpdb; if ( ! $query->get( 'aaweb_query' ) || ! $query->get( 'aaweb_keyword' ) ) { return $groupby; } return "{$wpdb->posts}.ID"; } public function render_page() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'You do not have permission to access this page.', 'aaweb-bulk-product-attributes-for-woocommerce' ) ); } $filters = $this->get_current_filters(); $query = $this->run_products_query( $filters ); $attributes = $this->get_store_attribute_taxonomies(); $total = isset( $query->found_posts ) ? (int) $query->found_posts : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin notice after a verified redirect. $updated_notice = isset( $_GET['aaweb_updated'] ) ? absint( $_GET['aaweb_updated'] ) : null; ?>

v

render_filters_form( $filters ); ?>
render_bulk_box(); ?>
0
$attribute ) : ?> have_posts() ) : ?> have_posts() ) : $query->the_post(); ?> $attribute ) : ?>
SKU
—'; ?> get_sku() ?: '—' ); ?> get_price_html() ?: '—' ); ?> get_stock_label( $product ) ); ?> —' ); ?> get_product_term_badges( $product_id, $taxonomy ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Markup is escaped in get_product_term_badges(). ?>
max_num_pages; if ( $total_pages > 1 ) { $base_url = add_query_arg( [ 'post_type' => 'product', 'page' => 'aaweb-bulk-product-attributes-for-woocommerce', 's' => $filters['s'], 'aaweb_keyword' => $filters['aaweb_keyword'], 'product_cat' => $filters['product_cat'], 'stock_status' => $filters['stock_status'], 'paged' => '%#%', ], admin_url( 'edit.php' ) ); echo '
'; echo wp_kses_post( paginate_links( [ 'base' => $base_url, 'format' => '', 'current' => max( 1, $filters['paged'] ), 'total' => $total_pages, 'prev_text' => '«', 'next_text' => '»', ] ) ); echo '
'; } ?>