WP_REST_Server::READABLE, 'callback' => [ $this, 'search' ], 'permission_callback' => static fn(): bool => is_user_logged_in(), 'args' => [ 'q' => [ 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field', ], ], ] ); } /** * Answer one search. * * @param WP_REST_Request $request The request. * * @internal Hooked callback. */ public function search( WP_REST_Request $request ): WP_REST_Response|WP_Error { $mode = FormConfig::get()['product_pick']; $q = trim( (string) $request->get_param( 'q' ) ); if ( FormConfig::PRODUCT_PURCHASED === $mode ) { return rest_ensure_response( $this->purchased( get_current_user_id(), $q ) ); } if ( FormConfig::PRODUCT_ALL === $mode && mb_strlen( $q ) >= self::MIN_CHARS ) { return rest_ensure_response( $this->catalogue( $q ) ); } return rest_ensure_response( [] ); } /** * The customer's purchased products whose title contains the query. * * @param int $userId Customer. * @param string $q Typed text; empty lists all. * * @return list */ private function purchased( int $userId, string $q ): array { $out = []; foreach ( Purchases::productIds( $userId ) as $id ) { $row = $this->row( $id ); if ( null === $row ) { continue; } if ( '' !== $q && false === mb_stripos( $row['title'], $q ) ) { continue; } $out[] = $row; if ( count( $out ) >= self::LIMIT ) { break; } } return $out; } /** * Published, catalogue-visible products matching the query. * * @param string $q Typed text. * * @return list */ private function catalogue( string $q ): array { $key = 'rh_tm_ps_' . md5( mb_strtolower( $q ) ); $cached = get_transient( $key ); if ( is_array( $cached ) ) { return $cached; } $ids = get_posts( [ 'post_type' => 'product', 'post_status' => 'publish', 's' => $q, 'posts_per_page' => self::LIMIT * 2, 'fields' => 'ids', 'no_found_rows' => true, 'update_post_term_cache' => false, ] ); $out = []; foreach ( $ids as $id ) { $row = $this->row( (int) $id ); if ( null !== $row ) { $out[] = $row; } if ( count( $out ) >= self::LIMIT ) { break; } } set_transient( $key, $out, 10 * MINUTE_IN_SECONDS ); return $out; } /** * One picker row, or null for a product that should not be offered. * * Hidden-from-catalogue and unpublished products are left out: a * testimonial tagged to one would show on a page no shopper reaches. * * @param int $id Product ID. * * @return array{id:int, title:string, image:string}|null */ private function row( int $id ): ?array { $product = function_exists( 'wc_get_product' ) ? wc_get_product( $id ) : null; if ( ! $product || 'publish' !== $product->get_status() || 'hidden' === $product->get_catalog_visibility() ) { return null; } $image = (string) wp_get_attachment_image_url( (int) $product->get_image_id(), 'thumbnail' ); return [ 'id' => $id, 'title' => wp_strip_all_tags( $product->get_name() ), 'image' => $image, ]; } }