jetpack
/
jetpack_vendor
/
automattic
/
jetpack-search
/
src
/
search-blocks
/
blocks
/
search-input
/
render.php
render.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-beta, at jetpack_vendor/automattic/jetpack-search/src/search-blocks/blocks/search-input/render.php
| 1 | <?php |
| 2 | /** |
| 3 | * Search Input block render. |
| 4 | * |
| 5 | * WordPress passes $attributes, $content, $block to render.php at runtime; |
| 6 | * VariableAnalysis can't see that, so the sniff is disabled here. |
| 7 | * |
| 8 | * @package automattic/jetpack-search |
| 9 | */ |
| 10 | |
| 11 | namespace Automattic\Jetpack\Search; |
| 12 | |
| 13 | if ( ! defined( 'ABSPATH' ) ) { |
| 14 | exit( 0 ); |
| 15 | } |
| 16 | |
| 17 | // phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable |
| 18 | |
| 19 | // Trim so a whitespace-only placeholder still falls back to the translated |
| 20 | // default — block.json's static default can't be localized, so the empty/ |
| 21 | // whitespace case resolves here. |
| 22 | $placeholder = trim( (string) ( $attributes['placeholder'] ?? '' ) ); |
| 23 | if ( '' === $placeholder ) { |
| 24 | $placeholder = __( 'Search…', 'jetpack-search-pkg' ); |
| 25 | } |
| 26 | $show_icon = (bool) ( $attributes['showIcon'] ?? true ); |
| 27 | $submit_only = ! empty( $attributes['submitOnly'] ); |
| 28 | // Sanitize the saved `suggestionTypes` array down to a known-good subset of |
| 29 | // the enum, in the canonical render order, deduplicated. Mirrors the |
| 30 | // enum in `block.json::attributes.suggestionTypes.items.enum` and the |
| 31 | // `TYPE_ORDER` constant in `suggestion-rows.js`. Authors can save anything |
| 32 | // the editor lets them — and a future block-version migration could leave |
| 33 | // stale strings here — so the front end stays defensive. |
| 34 | $allowed_suggestion_types = array( 'query', 'taxonomy', 'post' ); |
| 35 | $raw_suggestion_types = $attributes['suggestionTypes'] ?? $allowed_suggestion_types; |
| 36 | if ( ! is_array( $raw_suggestion_types ) ) { |
| 37 | $raw_suggestion_types = $allowed_suggestion_types; |
| 38 | } |
| 39 | $suggestion_types = array_values( |
| 40 | array_intersect( $allowed_suggestion_types, array_map( 'strval', $raw_suggestion_types ) ) |
| 41 | ); |
| 42 | // `enableSuggestions` is the author's master kill switch; an empty |
| 43 | // `suggestionTypes` array collapses to the same outcome (no dropdown). The |
| 44 | // combined gate keeps the rest of the render path branch-free — no listbox, |
| 45 | // no combobox attributes, no `data-wp-context` seeded when either says off. |
| 46 | $enable_suggestions = ! empty( $attributes['enableSuggestions'] ) && ! empty( $suggestion_types ); |
| 47 | // Read the URL-derived query through the shared helper so the SSR |
| 48 | // `value=` matches the Interactivity store's seeded `searchQuery`. |
| 49 | // The helper picks `s` vs `q` based on `is_search()` and applies the |
| 50 | // same sanitize_text_field + trim WP would have applied to `s`. |
| 51 | $initial_query = Search_Blocks::parse_url_search_query(); |
| 52 | $input_id = wp_unique_id( 'jetpack-search-input-' ); |
| 53 | // A separate id pair is generated for the listbox + status region so the |
| 54 | // input's `aria-controls` / `aria-activedescendant` can resolve to stable |
| 55 | // DOM ids. Only generated (and only emitted) when suggestions are on, to |
| 56 | // keep the default DOM untouched for authors who haven't opted in. |
| 57 | $listbox_id = $enable_suggestions ? wp_unique_id( 'jetpack-search-suggestions-' ) : ''; |
| 58 | // `data-wp-context` seeds the per-instance Interactivity state for the |
| 59 | // suggestions dropdown. Keeping it per-block (rather than on the shared |
| 60 | // `jetpack-search` store) means a header + sidebar Search Input on the |
| 61 | // same page never share a dropdown's `showSuggestions` / `activeIndex` — |
| 62 | // each instance owns its own UI state while still pulling the query from |
| 63 | // the shared `state.searchQuery`. Post-type scope lives on the parent |
| 64 | // `search-results` block, not here — inputs are entry points, not |
| 65 | // boundaries. |
| 66 | $context = $enable_suggestions |
| 67 | ? array( |
| 68 | 'showSuggestions' => false, |
| 69 | 'activeIndex' => -1, |
| 70 | 'activeOptionId' => '', |
| 71 | 'rows' => array(), |
| 72 | 'listboxId' => $listbox_id, |
| 73 | // `suggestionTypes` is seeded so the view bundle can filter |
| 74 | // rows client-side without re-reading the block attribute via |
| 75 | // a roundtrip. Per-instance because two Search Input blocks |
| 76 | // on the same page could pick different shapes. |
| 77 | 'suggestionTypes' => $suggestion_types, |
| 78 | ) |
| 79 | : array(); |
| 80 | $emit_context = ! empty( $context ); |
| 81 | $context_json = $emit_context |
| 82 | ? wp_json_encode( $context, JSON_HEX_AMP | JSON_UNESCAPED_SLASHES ) |
| 83 | : ''; |
| 84 | |
| 85 | // Mirrors `render_block_core_search()`'s width handling: both halves of the |
| 86 | // (value, unit) pair must be present, then emit `width: <n><unit>;` on the |
| 87 | // wrapper. The inside-wrapper's `display:flex` + `min-width:0` on the field |
| 88 | // shrinks the visible text area in step; icon + clear stay at natural width. |
| 89 | // `widthUnit` is allowlisted against the same units the editor offers — |
| 90 | // `block.json` only types it as a free-form string, so a REST write or a |
| 91 | // future migration can't smuggle an arbitrary unit into the inline style. |
| 92 | $wrapper_extra_attrs = array(); |
| 93 | $allowed_width_units = array( 'px', '%' ); |
| 94 | $raw_width_unit = (string) ( $attributes['widthUnit'] ?? '' ); |
| 95 | $width_unit = in_array( $raw_width_unit, $allowed_width_units, true ) ? $raw_width_unit : ''; |
| 96 | $has_width = isset( $attributes['width'] ) && '' !== $attributes['width'] && '' !== $width_unit; |
| 97 | if ( $has_width ) { |
| 98 | $wrapper_extra_attrs['style'] = sprintf( |
| 99 | 'width:%d%s;', |
| 100 | (int) $attributes['width'], |
| 101 | $width_unit |
| 102 | ); |
| 103 | } |
| 104 | ?> |
| 105 | <div |
| 106 | <?php echo wp_kses_data( get_block_wrapper_attributes( $wrapper_extra_attrs ) ); ?> |
| 107 | data-wp-interactive="jetpack-search" |
| 108 | <?php if ( $emit_context ) : ?> |
| 109 | data-wp-context='<?php echo esc_attr( $context_json ); ?>' |
| 110 | <?php endif; ?> |
| 111 | > |
| 112 | <label class="jetpack-search-input__label screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"> |
| 113 | <?php esc_html_e( 'Search', 'jetpack-search-pkg' ); ?> |
| 114 | </label> |
| 115 | <div class="jetpack-search-input__inside-wrapper"> |
| 116 | <?php if ( $show_icon ) : ?> |
| 117 | <svg class="jetpack-search-input__icon" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> |
| 118 | <path d="M13 5c-3.3 0-6 2.7-6 6 0 1.4.5 2.7 1.3 3.7l-3.8 3.8 1.1 1.1 3.8-3.8c1 .8 2.3 1.3 3.7 1.3 3.3 0 6-2.7 6-6s-2.7-6-6-6zm0 10.5c-2.5 0-4.5-2-4.5-4.5s2-4.5 4.5-4.5 4.5 2 4.5 4.5-2 4.5-4.5 4.5z" /> |
| 119 | </svg> |
| 120 | <?php endif; ?> |
| 121 | <input |
| 122 | id="<?php echo esc_attr( $input_id ); ?>" |
| 123 | type="search" |
| 124 | class="jetpack-search-input__field" |
| 125 | placeholder="<?php echo esc_attr( $placeholder ); ?>" |
| 126 | value="<?php echo esc_attr( $initial_query ); ?>" |
| 127 | <?php if ( $submit_only ) : ?> |
| 128 | data-submit-only="true" |
| 129 | <?php endif; ?> |
| 130 | <?php if ( $enable_suggestions ) : ?> |
| 131 | role="combobox" |
| 132 | aria-autocomplete="list" |
| 133 | aria-haspopup="listbox" |
| 134 | aria-controls="<?php echo esc_attr( $listbox_id ); ?>" |
| 135 | data-suggestions-enabled="true" |
| 136 | data-wp-bind--aria-expanded="context.showSuggestions" |
| 137 | data-wp-bind--aria-activedescendant="context.activeOptionId" |
| 138 | data-wp-on--focus="actions.onSearchFocus" |
| 139 | data-wp-on--blur="actions.onSearchBlur" |
| 140 | <?php endif; ?> |
| 141 | data-wp-bind--value="state.searchQuery" |
| 142 | data-wp-on--input="actions.onSearchInput" |
| 143 | data-wp-on--keydown="actions.onSearchKeydown" |
| 144 | data-wp-init="callbacks.initFocusInputIfHasQuery" |
| 145 | /> |
| 146 | <button |
| 147 | type="button" |
| 148 | class="jetpack-search-input__clear" |
| 149 | data-wp-bind--hidden="!state.searchQuery" |
| 150 | data-wp-on--click="actions.clearSearch" |
| 151 | > |
| 152 | <?php |
| 153 | /* translators: Button is used to clear the search input query. */ |
| 154 | echo esc_html__( 'clear', 'jetpack-search-pkg' ); |
| 155 | ?> |
| 156 | </button> |
| 157 | <?php if ( $enable_suggestions ) : ?> |
| 158 | <ul |
| 159 | id="<?php echo esc_attr( $listbox_id ); ?>" |
| 160 | class="jetpack-search-input__suggestions" |
| 161 | role="listbox" |
| 162 | aria-label="<?php echo esc_attr__( 'Search suggestions', 'jetpack-search-pkg' ); ?>" |
| 163 | data-wp-bind--hidden="!context.showSuggestions" |
| 164 | hidden |
| 165 | > |
| 166 | <template |
| 167 | data-wp-each--row="context.rows" |
| 168 | data-wp-each-key="context.row.key" |
| 169 | > |
| 170 | <li |
| 171 | class="jetpack-search-input__suggestions-label" |
| 172 | role="presentation" |
| 173 | data-wp-bind--hidden="!context.row.isHeader" |
| 174 | data-wp-text="context.row.label" |
| 175 | ></li> |
| 176 | <li |
| 177 | class="jetpack-search-input__suggestions-option" |
| 178 | role="option" |
| 179 | data-wp-bind--hidden="context.row.isHeader" |
| 180 | data-wp-bind--id="context.row.optionId" |
| 181 | data-wp-bind--aria-selected="state.isRowActive" |
| 182 | data-wp-class--is-active="state.isRowActive" |
| 183 | data-wp-on--mousedown="actions.onSuggestionMousedown" |
| 184 | data-wp-on--click="actions.onSuggestionClick" |
| 185 | tabindex="-1" |
| 186 | > |
| 187 | <span data-wp-text="context.row.text"></span> |
| 188 | </li> |
| 189 | </template> |
| 190 | </ul> |
| 191 | <?php endif; ?> |
| 192 | </div> |
| 193 | </div> |
| 194 |