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 ); BlocksSharedState::load_placeholder_image( $consent ); // `Add to cart` calls into the shared cart store, which expects // `state.cart.items` and friends. Without this load the cart store // would have no hydrated cart and the action would throw on the // first click. BlocksSharedState::load_cart_state( $consent ); $items = $this->prefetch_items(); // Seed the shared shopper-lists store with the rest URL, the // pre-fetched items, and a starter nonce. The starter nonce is // what the cart store also seeds via `state.nonce` — the JS layer // keeps it fresh by reading the `Nonce` response header on every // subsequent request, so this is just the bootstrap value (and // avoids deadlocking mutations that await `isNonceReady` before // any GET has fired). 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, ), ), ) ); // Only the remove-button aria-label template needs JS-side // interpolation; visible strings (empty state, action label) are // rendered server-side and toggled with directives. wp_interactivity_config( 'woocommerce/wishlist', array( 'removeLabelTemplate' => $this->get_remove_label_template(), ) ); // No `hasShownItems` flag: unlike Saved for Later (which auto- // renders on every cart visit and must avoid flashing an empty // message before a runtime save lands), Wishlist is reached // deliberately — by the My Account endpoint or because a merchant // placed it. Showing the empty message immediately is the right // signal: the visitor came to look at their wishlist, and it's // empty. `data-wp-context---notices` seeds the store-notices // namespace alongside the block's own context on the same wrapper. $wrapper_attributes = array( 'class' => 'wc-block-wishlist', 'data-wp-interactive' => 'woocommerce/wishlist', 'data-wp-context' => (string) wp_json_encode( array( // `stdClass` so it serialises as `{}`, not `[]` — // iAPI's reactive proxy only fires updates on object // writes, not array expandos. 'pendingKeys' => new \stdClass(), ) ), 'data-wp-context---notices' => 'woocommerce/store-notices::' . (string) wp_json_encode( array( 'notices' => array() ) ), ); $list_class = sprintf( 'wc-block-wishlist__list columns-%d', $column_count ); $ul_inner = $this->render_template_markup() . $this->render_items_markup( $items ) . $this->render_empty_markup( $items ); $before_list = $this->render_header_markup( $content ) . ShopperListRenderer::render_interactivity_notices_region( 'wc-block-wishlist__notices' ); return ShopperListRenderer::render_grid_wrapper( $wrapper_attributes, $list_class, $ul_inner, $before_list ); } /** * Prefetch the wishlist items via `rest_do_request()`. Logged-out * users short-circuit to an empty list — the route requires * authentication and we don't want to fire an API call that's only * going to 401. * * @return array> 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'; // Logged at debug level on purpose: prefetch failures are // often transient (network blips, auth refresh races) and // the user-visible behaviour is the empty state — nothing // for ops to act on. wc_get_logger()->debug( sprintf( 'Wishlist prefetch failed: %s', $message ), array( 'source' => 'wishlist', 'data' => array( 'slug' => self::LIST_SLUG ), ) ); return array(); } $data = $response->get_data(); if ( ! is_array( $data ) && ! is_object( $data ) ) { return array(); } // The schema casts `prices` and image entries to stdClass so the // JSON response renders objects, not arrays. Round-trip through // JSON encode/decode to normalise everything to nested arrays so // the SSR markup helpers can treat fields uniformly. $decoded = json_decode( (string) wp_json_encode( $data ), true ); return is_array( $decoded ) ? $decoded : array(); } /** * The `