Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
16.00% covered (danger)
16.00%
12 / 75
25.00% covered (danger)
25.00%
2 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Inline_Search_Correction
16.00% covered (danger)
16.00%
12 / 75
25.00% covered (danger)
25.00%
2 / 8
257.08
0.00% covered (danger)
0.00%
0 / 1
 setup_corrected_query_hooks
40.00% covered (danger)
40.00%
2 / 5
0.00% covered (danger)
0.00%
0 / 1
4.94
 enqueue_styles
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 register_corrected_query_script
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
6
 register_corrected_query_style
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 maybe_use_corrected_query
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 get_title_selectors
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 get_corrected_query_message
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 get_search_result
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Inline Search Correction: Handles search query correction display
4 *
5 * @package automattic/jetpack-search
6 */
7
8namespace Automattic\Jetpack\Search;
9
10use Automattic\Jetpack\Assets;
11
12/**
13 * Class for handling search correction display
14 *
15 * @since 0.48.0
16 */
17class Inline_Search_Correction {
18    /**
19     * Setup hooks for displaying corrected query notice.
20     *
21     * @param \WP_Query $query The current query.
22     */
23    public function setup_corrected_query_hooks( $query ) {
24        if ( ! $query->is_search() || ! $query->is_main_query() ) {
25            return;
26        }
27
28        add_filter( 'get_search_query', array( $this, 'maybe_use_corrected_query' ) );
29        add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ) );
30        add_action( 'wp_footer', array( $this, 'register_corrected_query_script' ) );
31    }
32
33    /**
34     * Enqueue theme-specific styles for the search correction.
35     * This is hooked to wp_enqueue_scripts to ensure styles load properly in the head.
36     *
37     * @since 0.48.0
38     */
39    public function enqueue_styles() {
40        if ( '' === $this->get_corrected_query_message() ) {
41            return;
42        }
43
44        $handle = 'jetpack-search-inline-corrected-query';
45        $this->register_corrected_query_style( $handle );
46    }
47
48    /**
49     * Register and configure the JavaScript for displaying the corrected query notice.
50     *
51     * Passes a plain-text message (not HTML). The front-end builds the notice with
52     * textContent so user-controlled query text is never parsed as HTML.
53     * Avoids wp_localize_script(), which html_entity_decodes values (core #25280).
54     *
55     * @since 0.48.0
56     */
57    public function register_corrected_query_script() {
58        $message = $this->get_corrected_query_message();
59        if ( '' === $message ) {
60            return;
61        }
62
63        $handle = 'jetpack-search-inline-corrected-query';
64
65        Assets::register_script(
66            $handle,
67            'build/inline-search/jp-search-inline.js',
68            Package::get_installed_path() . '/src',
69            array(
70                'in_footer'  => true,
71                'textdomain' => 'jetpack-search-pkg',
72                'enqueue'    => true,
73            )
74        );
75
76        // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
77        wp_add_inline_script(
78            $handle,
79            'window.JetpackSearchCorrectedQuery=' . wp_json_encode(
80                array(
81                    'message'   => $message,
82                    'selectors' => $this->get_title_selectors(),
83                ),
84                JSON_HEX_TAG | JSON_HEX_AMP | JSON_UNESCAPED_SLASHES
85            ) . ';',
86            'before'
87        );
88    }
89
90    /**
91     * Register and enqueue theme-specific styles for corrected query.
92     *
93     * @since 0.48.0
94     * @param string $handle The script handle to use for the stylesheet.
95     */
96    private function register_corrected_query_style( $handle ) {
97        $css_path      = 'build/inline-search/';
98        $css_file      = 'corrected-query.css';
99        $full_css_path = $css_path . $css_file;
100        $package_path  = Package::get_installed_path();
101        $css_full_path = $package_path . '/' . $full_css_path;
102
103        // Verify the CSS file exists before trying to enqueue it
104        if ( ! file_exists( $css_full_path ) ) {
105            return;
106        }
107
108        // We need to use plugins_url for reliable URL generation
109        $file_url = plugins_url(
110            $full_css_path,
111            $package_path . '/package.json'
112        );
113
114        // Use the file's modification time for more precise cache busting
115        $file_version = file_exists( $css_full_path ) ? filemtime( $css_full_path ) : Package::VERSION;
116
117        wp_enqueue_style(
118            $handle,
119            $file_url,
120            array(),
121            $file_version // Use file modification time for cache busting
122        );
123    }
124
125    /**
126     * Replaces the search query with the corrected query in the title.
127     *
128     * @param string $query The original search query.
129     * @return string The corrected query if available, otherwise the original query.
130     */
131    public function maybe_use_corrected_query( $query ) {
132        $search_result = $this->get_search_result();
133        if ( is_array( $search_result ) && ! empty( $search_result['corrected_query'] ) && ! empty( $search_result['results'] ) ) {
134            return $search_result['corrected_query'];
135        }
136
137        return $query;
138    }
139
140    /**
141     * Get selectors where corrected query notice will be displayed.
142     *
143     * @since 0.48.0
144     * @return array CSS selectors for search title elements.
145     */
146    private function get_title_selectors() {
147        $default_selectors = array(
148            '.wp-block-query-title',
149            '.page-title',
150            '.archive-title',
151            '.entry-title',
152            '.nv-page-title',
153            '.page-subheading',
154        );
155
156        /**
157         * Filter the selectors where corrected query notice appears.
158         *
159         * @since 0.48.0
160         * @param array $default_selectors CSS selectors for search title elements.
161         */
162        return apply_filters( 'jetpack_search_title_selectors', $default_selectors );
163    }
164
165    /**
166     * Generate the plain-text message for the corrected query notice.
167     *
168     * Returns text only — never HTML. The front-end must insert this via
169     * textContent (or equivalent), not insertAdjacentHTML / innerHTML.
170     *
171     * @return string The notice message, or empty string if none.
172     */
173    public function get_corrected_query_message() {
174        global $wp_query;
175        $original_query = $wp_query->get( 's' );
176        $search_result  = $this->get_search_result();
177
178        if ( ! is_array( $search_result ) || empty( $search_result['corrected_query'] ) || empty( $search_result['results'] ) ) {
179            return '';
180        }
181
182        return sprintf(
183            /* translators: %s: Original search term the user entered */
184            __( 'No results for "%s"', 'jetpack-search-pkg' ),
185            $original_query
186        );
187    }
188
189    /**
190     * Get the search result from the Inline_Search instance.
191     *
192     * @return array|\WP_Error|null The search result or null if not available.
193     */
194    private function get_search_result() {
195        $inline_search = Inline_Search::instance();
196        return $inline_search->get_search_result();
197    }
198}