Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
File Manager
/
wp-content
/
plugins
/
woocommerce
/
src
/
Blocks
/
BlockTypes
/
AddToCartWithOptions
:
VariationSelectorAttribute.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php declare(strict_types=1); namespace Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions; use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock; use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait; use Automattic\WooCommerce\Internal\ProductAttributes\VisualAttributeTermMeta; use WP_Block; /** * Block type for Variation Selector in the Add to Cart + Options block. */ class VariationSelectorAttribute extends AbstractBlock { use EnableBlockJsonAssetsTrait; /** * Block name. * * @var string */ protected $block_name = 'add-to-cart-with-options-variation-selector-attribute'; /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. * @return void */ protected function enqueue_data( array $attributes = array() ): void { parent::enqueue_data( $attributes ); if ( is_admin() ) { $this->asset_data_registry->add( 'experimentalVisualAttributes', array_key_exists( 'wc-visual', wc_get_attribute_types() ) ); } } /** * Render the block. * * @param array $attributes Block attributes. * @param string $content Block content. * @param WP_Block $block Block instance. * @return string Rendered block output. */ protected function render( $attributes, $content, $block ) { global $product; if ( ! $product instanceof \WC_Product_Variable ) { return ''; } $content = ''; $product_attributes = $product->get_variation_attributes(); $available_values_by_attribute = $this->get_available_variation_values_by_attribute_slug(); foreach ( $product_attributes as $product_attribute_name => $product_attribute_terms ) { $content .= $this->render_attribute_row( $product_attribute_name, $product_attribute_terms, $block, $attributes, $available_values_by_attribute ); } return $content; } /** * Get attribute row HTML. * * @param string $attribute_name Product Attribute Name. * @param array $product_attribute_terms Product Attribute Terms. * @param WP_Block $block The Block. * @param array $attributes Template block attributes (displayStyle, autoselect, etc.). * @param array $available_values_by_attribute Variation values keyed by attribute slug. * @return string Row HTML */ private function render_attribute_row( string $attribute_name, array $product_attribute_terms, WP_Block $block, array $attributes, array $available_values_by_attribute ): string { $inner_blocks = $block->parsed_block['innerBlocks'] ?? array(); if ( empty( $inner_blocks ) ) { return ''; } $attribute_slug = wc_variation_attribute_name( $attribute_name ); $attribute_terms = $this->get_filtered_attribute_terms( $attribute_name, $product_attribute_terms, $available_values_by_attribute[ $attribute_slug ] ?? array() ); if ( empty( $attribute_terms ) ) { return ''; } $default_selected = $this->get_default_selected_attribute( $attribute_slug, $attribute_terms ); $variation_items = $this->build_variation_selectable_items( $attribute_name, $attribute_slug, $attribute_terms, $default_selected ); $attribute_label = wc_attribute_label( $attribute_name ); $attribute_id = 'wc_product_attribute_' . uniqid(); $context = array( 'woocommerce/attributeId' => $attribute_id, 'woocommerce/attributeName' => $attribute_name, 'woocommerce/attributeTerms' => $attribute_terms, 'woocommerce/selectableItems' => array( 'items' => $variation_items, 'selectionMode' => 'single', 'storeNamespace' => 'woocommerce/add-to-cart-with-options', 'groupLabel' => $attribute_label, ), ); $inner_html = ''; foreach ( $inner_blocks as $inner_block ) { $inner_block = $this->replace_legacy_attribute_options_block( $inner_block, $attributes ); $inner_html .= ( new WP_Block( $inner_block, $context ) )->render(); } $interactive_context = array( 'name' => $attribute_label, 'variationAttributeOptions' => $variation_items, 'selectedValue' => $default_selected, 'autoselect' => $attributes['autoselect'] ?? false, 'disabledAttributesAction' => $attributes['disabledAttributesAction'] ?? 'disable', ); $interactive_attributes = array( 'data-wp-interactive' => 'woocommerce/add-to-cart-with-options', 'data-wp-init' => 'callbacks.setDefaultSelectedAttribute', ); // Hidden input for legacy form POST submissions (page refresh). Chips and // dropdown UI elements do not include name="attribute_*" fields. $hidden_attribute_input = sprintf( '<input type="hidden" name="%1$s" value="%2$s" data-wp-bind--value="context.selectedValue" />', esc_attr( $attribute_slug ), esc_attr( $default_selected ?? '' ) ); return sprintf( '<div %s %s>%s%s</div>', get_block_wrapper_attributes( $interactive_attributes ), wp_interactivity_data_wp_context( $interactive_context ), $inner_html, $hidden_attribute_input ); } /** * Replace legacy Attribute Options block and apply its settings to the parent attributes. * * @param array $inner_block The inner block to replace. * @param array $attributes Parent block attributes, updated when attributes in the legacy Attribute Options block are found. * @return array The replaced inner block. */ private function replace_legacy_attribute_options_block( array $inner_block, array &$attributes ): array { if ( 'woocommerce/add-to-cart-with-options-variation-selector-attribute-options' === $inner_block['blockName'] ) { $legacy_attrs = $inner_block['attrs'] ?? array(); if ( array_key_exists( 'autoselect', $legacy_attrs ) && true === $legacy_attrs['autoselect'] ) { $attributes['autoselect'] = true; } if ( array_key_exists( 'disabledAttributesAction', $legacy_attrs ) && 'hide' === $legacy_attrs['disabledAttributesAction'] ) { $attributes['disabledAttributesAction'] = 'hide'; } if ( array_key_exists( 'optionStyle', $legacy_attrs ) && 'dropdown' === $legacy_attrs['optionStyle'] ) { $attributes['displayStyle'] = 'woocommerce/dropdown'; } return array( 'blockName' => 'woocommerce/dropdown' === $attributes['displayStyle'] ? 'woocommerce/dropdown' : 'woocommerce/product-filter-chips', 'attrs' => array(), 'innerBlocks' => array(), 'innerHTML' => '', 'innerContent' => array(), ); } elseif ( isset( $inner_block['innerBlocks'] ) && is_array( $inner_block['innerBlocks'] ) && ! empty( $inner_block['innerBlocks'] ) ) { foreach ( $inner_block['innerBlocks'] as $key => $child_inner_block ) { $inner_block['innerBlocks'][ $key ] = $this->replace_legacy_attribute_options_block( $child_inner_block, $attributes ); } } return $inner_block; } /** * Build filtered attribute term options for the variation selector. * * @param string $attribute_name Product attribute name. * @param array $product_attribute_terms Custom attribute terms when not a taxonomy. * @param array $available_values Available variation values for this attribute (value => true). * @return array Attribute terms, or empty string when none match. */ private function get_filtered_attribute_terms( string $attribute_name, array $product_attribute_terms, array $available_values ) { global $product; $selected_attribute = $product->get_variation_default_attribute( $attribute_name ); $terms = taxonomy_exists( $attribute_name ) ? wc_get_product_terms( $product->get_id(), $attribute_name, array( 'fields' => 'all' ) ) : $product_attribute_terms; $attribute_terms = array(); if ( ! empty( $available_values ) ) { $allows_any_value = isset( $available_values[''] ); foreach ( $terms as $term ) { $option = $this->map_term_to_option( $term, $attribute_name, $product, $selected_attribute ); if ( ! isset( $option['value'] ) ) { continue; } if ( $allows_any_value || isset( $available_values[ $option['value'] ] ) ) { $attribute_terms[] = $option; } } } return $attribute_terms; } /** * Build a lookup of attribute values used by available variations. * * @return array Map of attribute slug to set of values (keys are values). */ private function get_available_variation_values_by_attribute_slug(): array { global $product; $product_variations = $product->get_available_variations( 'objects' ); $available_by_slug = array(); foreach ( $product_variations as $variation ) { foreach ( $variation->get_variation_attributes() as $attribute_slug => $value ) { $available_by_slug[ $attribute_slug ][ $value ] = true; } } return $available_by_slug; } /** * Get the default selected attribute. * * @param string $attribute_slug The attribute's slug. * @param array $attribute_terms The attribute's terms. * @return string|null The default selected attribute. */ private function get_default_selected_attribute( string $attribute_slug, array $attribute_terms ): ?string { if ( isset( $_GET[ $attribute_slug ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $raw = wp_unslash( $_GET[ $attribute_slug ] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( is_string( $raw ) ) { $attribute_slug_from_request = sanitize_title( $raw ); foreach ( $attribute_terms as $attribute_term ) { if ( sanitize_title( $attribute_term['value'] ) === $attribute_slug_from_request ) { return $attribute_term['value']; } } } } else { foreach ( $attribute_terms as $attribute_term ) { if ( $attribute_term['isSelected'] ) { return $attribute_term['value']; } } } return null; } /** * Build selectable items for the inner block protocol and client context. * * @param string $attribute_name Product attribute name. * @param string $attribute_slug Attribute slug. * @param array $attribute_terms Terms from context. * @param string|null $default_selected Default selected attribute value. * @return array */ private function build_variation_selectable_items( string $attribute_name, string $attribute_slug, array $attribute_terms, ?string $default_selected ): array { $id_prefix = sanitize_title( $attribute_slug ); $items = array(); $term_visuals = VisualAttributeTermMeta::is_visual_attribute_taxonomy( $attribute_name ) ? VisualAttributeTermMeta::get_term_visuals( wp_list_pluck( $attribute_terms, 'term_id' ) ) : array(); foreach ( $attribute_terms as $attribute_term ) { if ( ! is_array( $attribute_term ) || ! isset( $attribute_term['value'], $attribute_term['label'] ) ) { continue; } $value = (string) $attribute_term['value']; $slug = sanitize_title( $value ); $item = array( 'id' => $id_prefix . '-' . $slug, 'label' => (string) $attribute_term['label'], 'value' => $value, 'ariaLabel' => (string) $attribute_term['label'], 'selected' => $default_selected === $value, ); if ( isset( $attribute_term['term_id'], $term_visuals[ $attribute_term['term_id'] ] ) ) { $item['visual'] = $term_visuals[ $attribute_term['term_id'] ]; } $items[] = $item; } return $items; } /** * Map a taxonomy term or custom attribute option to the variation row option shape. * * @param \WP_Term|string $term Term object for taxonomies, option string for custom attributes. * @param string $attribute_name Name of the attribute. * @param \WC_Product $product Product object. * @param string $selected_attribute Default selected attribute value. * @return array */ private function map_term_to_option( $term, string $attribute_name, \WC_Product $product, string $selected_attribute ): array { if ( $term instanceof \WP_Term ) { $value = $term->slug; $label = $term->name; $filter_item = $term; } elseif ( is_string( $term ) ) { $value = $term; $label = $term; $filter_item = null; } else { return array(); } $option = array( 'value' => $value, /** * Filter the variation option name. * * @since 9.7.0 * * @param string $option_label The option label. * @param \WP_Term|string|null $item Term object for taxonomies, option string for custom attributes. * @param string $attribute_name Name of the attribute. * @param \WC_Product $product Product object. */ 'label' => apply_filters( 'woocommerce_variation_option_name', $label, $filter_item, $attribute_name, $product ), 'isSelected' => $selected_attribute === $value, ); if ( $term instanceof \WP_Term ) { $option['term_id'] = $term->term_id; } return $option; } }