context['postId'] ) ? absint( $block->context['postId'] ) : 0;
if ( ! $post_id ) {
return '';
}
$product = wc_get_product( $post_id );
if ( ! $product instanceof \WC_Product ) {
return '';
}
wp_enqueue_script_module( $this->get_full_block_name() );
$consent = 'I acknowledge that using private APIs means my theme or plugin will inevitably break in the next version of WooCommerce';
BlocksSharedState::load_store_config( $consent );
$items = $this->prefetch_items();
// Seed the shared shopper-lists store the same way the Wishlist
// block does — restUrl + starter nonce + prefetched items. The
// two blocks may both render on the same page (e.g. the merchant
// drops the Wishlist block into a sidebar of single-product); iAPI's
// deep-merge keeps the first writer's payload, so seeding identical
// values here is a no-op when Wishlist already ran.
wp_interactivity_state(
'woocommerce/shopper-lists',
array(
'restUrl' => get_rest_url(),
'nonce' => wp_create_nonce( 'wc_store_api' ),
'lists' => array(
self::LIST_SLUG => array(
'items' => $items,
'isLoading' => false,
),
),
)
);
// Visible labels flow through `wp_interactivity_config` so the
// JS-side getter can pick the right one based on
// `state.isInWishlist`. PHP renders the matching one as the
// initial server-side label.
wp_interactivity_config(
'woocommerce/add-to-wishlist-button',
array(
'addLabel' => $this->get_add_label(),
'savedLabel' => $this->get_saved_label(),
'selectOptionsLabel' => $this->get_select_options_label(),
)
);
$is_variable = $product->is_type( 'variable' );
$initial_is_in_wishlist = $this->is_initial_in_wishlist( $items, $product );
$initial_disabled = $is_variable;
$initial_label = $is_variable
? $this->get_select_options_label()
: ( $initial_is_in_wishlist ? $this->get_saved_label() : $this->get_add_label() );
$wrapper_attributes = array(
'class' => 'wc-block-add-to-wishlist-button',
'data-wp-interactive' => 'woocommerce/add-to-wishlist-button',
'data-wp-context' => (string) wp_json_encode(
array(
'productId' => $product->get_id(),
'isVariableType' => $is_variable,
'isPending' => false,
)
),
);
ob_start();
?>
>
> Items in the schema response shape.
*/
private function prefetch_items(): array {
if ( ! is_user_logged_in() ) {
return array();
}
$request = new \WP_REST_Request( 'GET', '/wc/store/v1/shopper-lists/' . self::LIST_SLUG . '/items' );
$response = rest_do_request( $request );
if ( $response->is_error() ) {
$error = $response->as_error();
$message = $error instanceof \WP_Error ? $error->get_error_message() : 'Unknown error';
wc_get_logger()->debug(
sprintf( 'Add to Wishlist button prefetch failed: %s', $message ),
array(
'source' => 'add-to-wishlist-button',
'data' => array( 'slug' => self::LIST_SLUG ),
)
);
return array();
}
$data = $response->get_data();
if ( ! is_array( $data ) && ! is_object( $data ) ) {
return array();
}
$decoded = json_decode( (string) wp_json_encode( $data ), true );
return is_array( $decoded ) ? $decoded : array();
}
/**
* Whether the current product (or its parent, for a variable parent
* with no selection yet) is already in the prefetched wishlist. For
* variable products the SSR star is always empty — we can't know which
* variation the shopper will pick before JS hydrates.
*
* @param array> $items Schema-shape items.
* @param \WC_Product $product The product being viewed.
*/
private function is_initial_in_wishlist( array $items, \WC_Product $product ): bool {
if ( $product->is_type( 'variable' ) ) {
return false;
}
$product_id = $product->get_id();
foreach ( $items as $item ) {
if ( isset( $item['id'] ) && (int) $item['id'] === $product_id ) {
return true;
}
}
return false;
}
/**
* Visible label when the product is not in the wishlist.
*/
private function get_add_label(): string {
return __( 'Add to wishlist', 'woocommerce' );
}
/**
* Visible label when the product is already in the wishlist.
*/
private function get_saved_label(): string {
return __( 'Saved to wishlist', 'woocommerce' );
}
/**
* Visible label when the shopper still needs to pick variation
* attributes before the wishlist toggle can resolve to a specific
* variation.
*/
private function get_select_options_label(): string {
return __( 'Select options first', 'woocommerce' );
}
/**
* Get the frontend script handle for this block type. Scripts are
* loaded via `viewScriptModule` in block.json.
*
* @param string|null $key The key of the script to get.
* @return null
*/
protected function get_block_type_script( $key = null ) {
return null;
}
/**
* Get the frontend style handle for this block type. Returning null
* lets WP use the `style` array from block.json.
*
* @return null
*/
protected function get_block_type_style() {
return null;
}
/**
* Disable the editor style handle for this block type.
*
* @return null
*/
protected function get_block_type_editor_style() {
return null;
}
}