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 ) { ?>
get_store_attribute_taxonomies(); ?>