Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions

AddToCartWithOptions::renderprotectedWC 1.0

Render the block.

Метод класса: AddToCartWithOptions{}

Возвращает

Строку|null. Rendered block output.

Использование

// protected - в коде основоного (родительского) или дочернего класса
$result = $this->render( $attributes, $content, $block );
$attributes(массив) (обязательный)
Block attributes.
$content(строка) (обязательный)
Block content.
$block(WP_Block) (обязательный)
Block instance.

Код AddToCartWithOptions::render() WC 10.5.2

protected function render( $attributes, $content, $block ) {
	global $product;

	$product_id = ( is_object( $block ) && property_exists( $block, 'context' ) && is_array( $block->context ) && array_key_exists( 'postId', $block->context ) ) ? $block->context['postId'] : null;

	if ( ! isset( $product_id ) ) {
		return '';
	}

	$previous_product = $product;
	$product          = wc_get_product( $product_id );
	if ( ! $product instanceof \WC_Product ) {
		$product = $previous_product;

		return '';
	}

	// For variations, we display the simple product form.
	$product_type = ProductType::VARIATION === $product->get_type() ? ProductType::SIMPLE : $product->get_type();

	$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );
	$classes            = implode(
		' ',
		array_filter(
			array(
				'wp-block-add-to-cart-with-options wc-block-add-to-cart-with-options',
				esc_attr( $classes_and_styles['classes'] ),
			)
		)
	);

	$template_part_path = $this->get_template_part_path( $product_type );

	if ( is_string( $template_part_path ) && '' !== $template_part_path && file_exists( $template_part_path ) ) {
		$slug                   = $product_type . '-product-add-to-cart-with-options';
		$template_part_contents = '';
		// Determine if we need to load the template part from the DB, the theme or WooCommerce in that order.
		$templates_from_db = BlockTemplateUtils::get_block_templates_from_db( array( $slug ), 'wp_template_part' );

		if ( is_countable( $templates_from_db ) && count( $templates_from_db ) > 0 ) {
			$template_slug_to_load = $templates_from_db[0]->theme;
		} else {
			$theme_has_template_part = BlockTemplateUtils::theme_has_template_part( $slug );
			$template_slug_to_load   = $theme_has_template_part ? get_stylesheet() : BlockTemplateUtils::PLUGIN_SLUG;
		}
		$template_part = get_block_template( $template_slug_to_load . '//' . $slug, 'wp_template_part' );

		if ( $template_part && ! empty( $template_part->content ) ) {
			$template_part_contents = $template_part->content;
		}

		if ( '' === $template_part_contents ) {
			$template_part_contents = file_get_contents( $template_part_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		}

		$default_quantity = $product->get_min_purchase_quantity();

		$product_id = $product->get_id();

		wp_interactivity_state(
			'woocommerce/add-to-cart-with-options',
			array(
				'isFormValid' => function () use ( $product_id ) {
					$product = wc_get_product( $product_id );

					if ( $product instanceof \WC_Product && ( $product->is_type( ProductType::GROUPED ) || $product->has_options() ) ) {
						return false;
					}
					return true;
				},
			)
		);

		wp_interactivity_config(
			'woocommerce/add-to-cart-with-options',
			array(
				'errorMessages' => array(
					'invalidQuantities'                => esc_html__(
						'Please select a valid quantity to add to the cart.',
						'woocommerce'
					),
					'groupedProductAddToCartMissingItems' => esc_html__(
						'Please select some products to add to the cart.',
						'woocommerce'
					),
					'variableProductMissingAttributes' => esc_html__(
						'Please select product attributes before adding to cart.',
						'woocommerce'
					),
					'variableProductOutOfStock'        => sprintf(
						/* translators: %s: product name */
						esc_html__(
							'You cannot add "%s" to the cart because the product is out of stock.',
							'woocommerce'
						),
						$product->get_name()
					),
				),
			)
		);

		wp_interactivity_config(
			'woocommerce',
			array(
				'products' => array(
					$product->get_id() => array(
						'type'              => $product->get_type(),
						'is_in_stock'       => $product->is_in_stock(),
						'sold_individually' => $product->is_sold_individually(),
					),
				),
			)
		);

		$context = array(
			'quantity'         => array( $product->get_id() => $default_quantity ),
			'validationErrors' => array(),
		);

		if ( $product->is_type( ProductType::VARIABLE ) ) {
			$variations_data               = array();
			$context['selectedAttributes'] = array();
			$available_variations          = $product->get_available_variations( 'objects' );
			foreach ( $available_variations as $variation ) {
				// We intentionally set the default quantity to the product's min purchase quantity
				// instead of the variation's min purchase quantity. That's because we use the same
				// input for all variations, so we want quantities to be in sync.
				$context['quantity'][ $variation->get_id() ] = $default_quantity;

				$variation_data = array(
					'attributes'        => $variation->get_variation_attributes(),
					'is_in_stock'       => $variation->is_in_stock(),
					'sold_individually' => $variation->is_sold_individually(),
				);

				$variations_data[ $variation->get_id() ] = $variation_data;
			}

			wp_interactivity_config(
				'woocommerce',
				array(
					'products' => array(
						$product->get_id() => array(
							'variations' => $variations_data,
						),
					),
				)
			);
		} elseif ( $product->is_type( ProductType::VARIATION ) ) {
			$variation_attributes = $product->get_variation_attributes();
			$formatted_attributes = array_map(
				function ( $key, $value ) {
					return [
						'attribute' => $key,
						'value'     => $value,
					];
				},
				array_keys( $variation_attributes ),
				$variation_attributes
			);

			$context['selectedAttributes'] = $formatted_attributes;
		} elseif ( $product->is_type( ProductType::GROUPED ) ) {
			// Add context for purchasable child products.
			$children_product_data = array();
			foreach ( $product->get_children() as $child_product_id ) {
				$child_product = wc_get_product( $child_product_id );
				if ( $child_product && $this->is_child_product_purchasable( $child_product ) ) {
					$child_product_quantity_constraints = Utils::get_product_quantity_constraints( $child_product );

					$children_product_data[ $child_product_id ] = array(
						'min'               => $child_product_quantity_constraints['min'],
						'max'               => $child_product_quantity_constraints['max'],
						'step'              => $child_product_quantity_constraints['step'],
						'type'              => $child_product->get_type(),
						'is_in_stock'       => $child_product->is_in_stock(),
						'sold_individually' => $child_product->is_sold_individually(),
					);
				}
			}

			$context['groupedProductIds'] = array_keys( $children_product_data );
			wp_interactivity_config(
				'woocommerce',
				array(
					'products' => $children_product_data,
				)
			);

			// Add quantity context for purchasable child products.
			$context['quantity'] = array_fill_keys(
				$context['groupedProductIds'],
				0
			);

			// Set default quantity for each child product.
			foreach ( $context['groupedProductIds'] as $child_product_id ) {
				$child_product = wc_get_product( $child_product_id );
				if ( $child_product ) {

					$default_child_quantity = isset( $_POST['quantity'][ $child_product->get_id() ] ) ? wc_stock_amount( wc_clean( wp_unslash( $_POST['quantity'][ $child_product->get_id() ] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing

					$context['quantity'][ $child_product_id ] = $default_child_quantity;

					// Check for any "sold individually" products and set their default quantity to 0.
					if ( $child_product->is_sold_individually() ) {
						$context['quantity'][ $child_product_id ] = 0;
					}
				}
			}
		}

		$hooks_before = '';
		$hooks_after  = '';

		/**
		* Filter to disable the compatibility layer for the blockified templates.
		*
		* This hook allows to disable the compatibility layer for the blockified.
		*
		* @since 7.6.0
		* @param boolean $is_disabled_compatibility_layer Whether the compatibility layer should be disabled.
		*/
		$is_disabled_compatibility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false );

		if ( ! $is_disabled_compatibility_layer && ! Utils::is_not_purchasable_product( $product ) ) {
			ob_start();
			/**
			 * Hook: woocommerce_before_add_to_cart_form.
			 *
			 * @since 10.1.0
			 */
			do_action( 'woocommerce_before_add_to_cart_form' );

			if ( ProductType::SIMPLE === $product_type ) {
				/**
				 * Hook: woocommerce_before_add_to_cart_quantity.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_before_add_to_cart_quantity' );
				/**
				 * Hook: woocommerce_before_add_to_cart_button.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_before_add_to_cart_button' );
			} elseif ( ProductType::EXTERNAL === $product_type ) {
				/**
				 * Hook: woocommerce_before_add_to_cart_button.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_before_add_to_cart_button' );
			} elseif ( ProductType::GROUPED === $product_type ) {
				/**
				 * Hook: woocommerce_before_add_to_cart_button.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_before_add_to_cart_button' );
			} elseif ( ProductType::VARIABLE === $product_type ) {
				/**
				 * Hook: woocommerce_before_variations_form.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_before_variations_form' );
				/**
				 * Hook: woocommerce_after_variations_table.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_after_variations_table' );
				/**
				 * Hook: woocommerce_before_single_variation.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_before_single_variation' );

				// WooCommerce uses `woocommerce_single_variation` to render
				// some UI elements like the Add to Cart button for
				// variations. We need to remove them to avoid those UI
				// elements being duplicate with the blocks.
				// We later add these actions back to avoid affecting other
				// blocks or templates.
				remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation', 10 );
				remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation_add_to_cart_button', 20 );
				/**
				 * Hook: woocommerce_single_variation.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_single_variation' );
				if ( function_exists( 'woocommerce_single_variation' ) ) {
					add_action( 'woocommerce_single_variation', 'woocommerce_single_variation', 10 );
				}
				if ( function_exists( 'woocommerce_single_variation_add_to_cart_button' ) ) {
					add_action( 'woocommerce_single_variation', 'woocommerce_single_variation_add_to_cart_button', 20 );
				}
				/**
				 * Hook: woocommerce_before_add_to_cart_button.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_before_add_to_cart_button' );
				/**
				 * Hook: woocommerce_before_add_to_cart_quantity.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_before_add_to_cart_quantity' );
			}
			$hooks_before = ob_get_clean();

			ob_start();
			if ( ProductType::SIMPLE === $product_type ) {
				/**
				 * Hook: woocommerce_after_add_to_cart_quantity.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_after_add_to_cart_quantity' );
				/**
				 * Hook: woocommerce_after_add_to_cart_button.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_after_add_to_cart_button' );
			} elseif ( ProductType::EXTERNAL === $product_type ) {
				/**
				 * Hook: woocommerce_after_add_to_cart_button.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_after_add_to_cart_button' );
			} elseif ( ProductType::GROUPED === $product_type ) {
				/**
				 * Hook: woocommerce_after_add_to_cart_button.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_after_add_to_cart_button' );
			} elseif ( ProductType::VARIABLE === $product_type ) {
				/**
				 * Hook: woocommerce_after_add_to_cart_quantity.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_after_add_to_cart_quantity' );
				/**
				 * Hook: woocommerce_after_add_to_cart_button.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_after_add_to_cart_button' );
				/**
				 * Hook: woocommerce_after_single_variation.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_after_single_variation' );
				/**
				 * Hook: woocommerce_after_variations_form.
				 *
				 * @since 10.0.0
				 */
				do_action( 'woocommerce_after_variations_form' );
			}

			/**
			 * Hook: woocommerce_after_add_to_cart_form.
			 *
			 * @since 10.1.0
			 */
			do_action( 'woocommerce_after_add_to_cart_form' );

			$hooks_after = ob_get_clean();
		}

		// Because we are printing the template part using do_blocks, context from the outside is lost.
		// This filter is used to add the isDescendantOfAddToCartWithOptions context back.
		add_filter( 'render_block_context', array( $this, 'set_is_descendant_of_add_to_cart_with_options_context' ), 10, 2 );
		$template_part_blocks = do_blocks( $template_part_contents );
		remove_filter( 'render_block_context', array( $this, 'set_is_descendant_of_add_to_cart_with_options_context' ) );

		$wrapper_attributes = array(
			'class'                     => $classes,
			'style'                     => esc_attr( $classes_and_styles['styles'] ),
			'data-wp-interactive'       => 'woocommerce/add-to-cart-with-options',
			'data-wp-class--is-invalid' => '!state.isFormValid',
		);
		$context_directive  = wp_interactivity_data_wp_context( $context );

		$cart_redirect_after_add = get_option( 'woocommerce_cart_redirect_after_add' );
		$form_attributes         = '';
		$legacy_mode             = 'yes' === $cart_redirect_after_add || $this->has_form_elements( $hooks_before ) || $this->has_form_elements( $hooks_after );
		if ( $legacy_mode ) {
			$action_url = home_url( add_query_arg( null, null ) );

			// If an extension is hooking into the form or we need to redirect to the cart,
			// we fall back to a regular HTML form.
			$form_attributes = array(
				'action'  => esc_url(
					/**
					 * Filter the add to cart form action.
					 *
					 * @since 10.0.0
					 * @param string $action_url The add to cart form action URL, defaulting to the current page.
					 * @return string The add to cart form action URL.
					 */
					apply_filters( 'woocommerce_add_to_cart_form_action', $action_url )
				),
				'method'  => 'post',
				'enctype' => 'multipart/form-data',
				'class'   => 'cart',
			);
		} else {
			// Otherwise, we use the Interactivity API.
			$form_attributes = array(
				'data-wp-on--submit' => 'actions.handleSubmit',
			);
		}

		// These hidden inputs are used by extensions or Express Payment methods to gather information of the form state.
		$hidden_input = '';
		if ( ProductType::SIMPLE === $product_type ) {
			$hidden_input = '<input type="hidden" name="add-to-cart" value="' . esc_attr( $product_id ) . '" />';
		} elseif ( ProductType::GROUPED === $product_type ) {
			$hidden_input = '<input type="hidden" name="add-to-cart" value="' . esc_attr( $product_id ) . '" />';
		} elseif ( ProductType::VARIABLE === $product_type ) {
			$hidden_input = '<div class="single_variation_wrap">
				<input type="hidden" name="add-to-cart" value="' . esc_attr( $product_id ) . '" />
				<input type="hidden" name="product_id" value="' . esc_attr( $product_id ) . '" />
				<input type="hidden"
					name="variation_id"
					data-wp-bind--value="woocommerce/product-data::state.variationId"
				/>
			</div>';
		}

		$form_html = sprintf(
			'<form %1$s %2$s>%3$s%4$s%5$s%6$s</form>',
			get_block_wrapper_attributes(
				array_merge(
					$wrapper_attributes,
					$form_attributes,
					array(
						'class' => implode(
							' ',
							array_filter(
								array(
									isset( $wrapper_attributes['class'] ) ? $wrapper_attributes['class'] : '',
									isset( $form_attributes['class'] ) ? $form_attributes['class'] : '',
								)
							)
						),
					)
				)
			),
			$context_directive,
			$hooks_before,
			$template_part_blocks,
			$hooks_after,
			$hidden_input
		);

		ob_start();

		if ( in_array( $product_type, array( ProductType::SIMPLE, ProductType::EXTERNAL, ProductType::VARIABLE, ProductType::GROUPED ), true ) ) {

			$add_to_cart_fn = 'woocommerce_' . $product_type . '_add_to_cart';
			remove_action( 'woocommerce_' . $product_type . '_add_to_cart', $add_to_cart_fn, 30 );

			/**
			 * Trigger the single product add to cart action that prints the markup.
			 *
			 * @since 9.9.0
			 */
			do_action( 'woocommerce_' . $product_type . '_add_to_cart' );
			add_action( 'woocommerce_' . $product_type . '_add_to_cart', $add_to_cart_fn, 30 );
		}

		$form_html = $form_html . ob_get_clean();

		if ( ! $legacy_mode ) {
			$form_html = $this->render_interactivity_notices_region( $form_html );
		}
	} else {
		ob_start();

		/**
		 * Trigger the single product add to cart action that prints the markup.
		 *
		 * @since 9.7.0
		 */
		do_action( 'woocommerce_' . $product_type . '_add_to_cart' );

		$wrapper_attributes = array(
			'class' => $classes,
			'style' => esc_attr( $classes_and_styles['styles'] ),
		);

		$form_html = ob_get_clean();
		$form_html = sprintf( '<div %1$s>%2$s</div>', get_block_wrapper_attributes( $wrapper_attributes ), $form_html );
	}

	$product = $previous_product;

	return $form_html;
}