6, 'category' => 3, 'course' => 4, 'lesson' => 4, 'post' => 6, 'page' => 2 ]; private const LIMITS_ALL = [ 'product' => 4, 'category' => 2, 'course' => 3, 'lesson' => 3, 'post' => 3, 'page' => 2 ]; private const TYPES = [ 'learning' => [ 'rooster_course' => 'course', 'rooster_lesson' => 'lesson' ], 'blog' => [ 'post' => 'post' ], 'all' => [ 'rooster_course' => 'course', 'rooster_lesson' => 'lesson', 'post' => 'post', 'page' => 'page' ], ]; public static function boot(): void { add_action( 'rest_api_init', [ self::class, 'routes' ] ); } public static function routes(): void { register_rest_route( self::NS, self::ROUTE, [ 'methods' => 'GET', 'callback' => [ self::class, 'answer' ], 'permission_callback' => '__return_true', 'args' => [ 'q' => [ 'type' => 'string', 'required' => true, 'minLength' => 2, 'maxLength' => 80 ], 'scope' => [ 'type' => 'string', 'enum' => [ 'product', 'learning', 'blog', 'all' ], 'default' => 'product' ], ], ] ); } public static function answer( \WP_REST_Request $request ): \WP_REST_Response { $q = trim( preg_replace( '/\s+/u', ' ', wp_strip_all_tags( (string) $request['q'] ) ) ?? '' ); $scope = (string) $request['scope']; if ( 'product' === $scope && ! self::has_products() ) { $scope = 'all'; } $lang = (string) ( apply_filters( 'wpml_current_language', null ) ?? ( function_exists( 'pll_current_language' ) ? pll_current_language() : '' ) ); $shared = ! is_user_logged_in(); $key = md5( $q . '|' . $scope . '|' . $lang ); $items = $shared ? wp_cache_get( $key, 'rooster_suggest' ) : false; if ( ! is_array( $items ) ) { $items = mb_strlen( $q ) < 2 ? [] : self::items( $q, $scope ); if ( $shared ) { wp_cache_set( $key, $items, 'rooster_suggest', 5 * MINUTE_IN_SECONDS ); } } $types = match ( $scope ) { 'product' => 'product', 'learning' => array_values( array_filter( array_keys( self::TYPES['learning'] ), 'post_type_exists' ) ), 'blog' => 'post', default => '', }; $all = add_query_arg( array_filter( [ 's' => rawurlencode( $q ), 'post_type' => $types ] ), home_url( '/' ) ); $response = new \WP_REST_Response( [ 'q' => $q, 'scope' => $scope, 'items' => $items, 'all' => $all ] ); $response->header( 'Cache-Control', $shared ? 'public, max-age=300' : 'private, no-store' ); return $response; } public static function items( string $q, string $scope ): array { $items = []; $limits = 'all' === $scope ? self::LIMITS_ALL : self::LIMITS; if ( in_array( $scope, [ 'product', 'all' ], true ) && self::has_products() ) { foreach ( self::posts( 'product', $q, $limits['product'], self::product_query_args() ) as $id ) { $product = wc_get_product( $id ); if ( ! $product ) { continue; } $items[] = self::item( 'product', $id ) + self::price( (string) $product->get_price_html() ); } $terms = get_terms( [ 'taxonomy' => 'product_cat', 'name__like' => $q, 'number' => $limits['category'], 'hide_empty' => true, 'orderby' => 'count', 'order' => 'DESC' ] ); foreach ( is_array( $terms ) ? $terms : [] as $term ) { $link = get_term_link( $term ); if ( is_string( $link ) ) { $item = [ 'type' => 'category', 'title' => self::text( $term->name ), 'url' => $link, 'count' => (int) $term->count ]; $thumb = (int) get_term_meta( $term->term_id, 'thumbnail_id', true ); $image = $thumb ? wp_get_attachment_image_src( $thumb, 'woocommerce_gallery_thumbnail' ) : false; $items[] = $image ? $item + [ 'image' => (string) $image[0] ] : $item; } } } foreach ( self::TYPES[ $scope ] ?? [] as $post_type => $type ) { if ( ! post_type_exists( $post_type ) ) { continue; } foreach ( self::posts( $post_type, $q, $limits[ $type ], 'page' === $type ? self::shop_pages() : [] ) as $id ) { $items[] = self::item( $type, $id ); } } return array_values( array_filter( (array) apply_filters( 'rooster/suggest/items', $items, $q, $scope ), static fn ( $item ) => is_array( $item ) && ! empty( $item['title'] ) && ! empty( $item['url'] ) ) ); } private static function shop_pages(): array { $ids = function_exists( 'wc_get_page_id' ) ? array_filter( array_map( 'wc_get_page_id', [ 'cart', 'checkout', 'myaccount' ] ), static fn ( $id ) => $id > 0 ) : []; return $ids ? [ 'post__not_in' => array_values( $ids ) ] : []; } private static function has_products(): bool { return function_exists( 'wc_get_product' ) && post_type_exists( 'product' ); } private static function product_query_args(): array { $hidden = function_exists( 'wc_get_product_visibility_term_ids' ) ? wc_get_product_visibility_term_ids() : []; $ids = array_filter( [ $hidden['exclude-from-search'] ?? 0, 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ? ( $hidden['outofstock'] ?? 0 ) : 0 ] ); return $ids ? [ 'tax_query' => [ [ 'taxonomy' => 'product_visibility', 'field' => 'term_taxonomy_id', 'terms' => array_values( $ids ), 'operator' => 'NOT IN' ] ] ] : []; } private static function posts( string $post_type, string $q, int $limit, array $extra = [] ): array { $query = new \WP_Query( $extra + [ 'post_type' => $post_type, 'post_status' => 'publish', 's' => $q, 'posts_per_page' => $limit, 'fields' => 'ids', 'no_found_rows' => true, 'ignore_sticky_posts' => true, 'update_post_term_cache' => false, 'suppress_filters' => false, ] ); return array_map( 'intval', $query->posts ); } private static function item( string $type, int $id ): array { $item = [ 'type' => $type, 'title' => self::text( get_the_title( $id ) ), 'url' => (string) get_permalink( $id ) ]; $thumb = get_post_thumbnail_id( $id ); $image = $thumb ? wp_get_attachment_image_src( (int) $thumb, 'product' === $type && has_image_size( 'woocommerce_gallery_thumbnail' ) ? 'woocommerce_gallery_thumbnail' : 'thumbnail' ) : false; if ( $image ) { $item['image'] = (string) $image[0]; } return $item; } public static function price( string $html ): array { $html = (string) preg_replace( '#.*?#s', '', $html ); $out = []; if ( preg_match( '#]*>(.*?)#s', $html, $m ) ) { $out['was'] = self::text( $m[1] ); $html = str_replace( $m[0], '', $html ); } return [ 'price' => self::text( $html ) ] + $out; } private static function text( string $html ): string { $text = html_entity_decode( wp_strip_all_tags( $html ), ENT_QUOTES | ENT_HTML5, 'UTF-8' ); return trim( (string) preg_replace( '/\s+/u', ' ', $text ) ); } }