Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
96.97% |
96 / 99 |
n/a |
0 / 0 |
CRAP | n/a |
0 / 0 |
||
| 1 | <?php |
| 2 | /** |
| 3 | * Results Sort block render. |
| 4 | * |
| 5 | * WordPress passes `$attributes` in at runtime; VariableAnalysis can't see |
| 6 | * that because this file is include()'d rather than declared as a callback |
| 7 | * parameter, hence the sniff disable. |
| 8 | * |
| 9 | * @package automattic/jetpack-search |
| 10 | */ |
| 11 | |
| 12 | namespace Automattic\Jetpack\Search; |
| 13 | |
| 14 | if ( ! defined( 'ABSPATH' ) ) { |
| 15 | exit( 0 ); |
| 16 | } |
| 17 | |
| 18 | // phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable |
| 19 | |
| 20 | // @phan-suppress-next-line PhanUndeclaredGlobalVariable -- WP always supplies $attributes. |
| 21 | $attrs = (array) $attributes; |
| 22 | $options = Results_Sort::resolve_available_options( $attrs ); |
| 23 | $option_labels = Results_Sort::get_option_labels(); |
| 24 | $default_sort = Results_Sort::normalize_default_sort( $attrs ); |
| 25 | $display_as = Results_Sort::normalize_display_as( $attrs ); |
| 26 | $label = Results_Sort::resolve_label( $attrs ); |
| 27 | |
| 28 | // Determine the effective sort for first paint and hydration. A URL |
| 29 | // `?orderby=...` always wins so deep links keep their meaning -- same |
| 30 | // precedence as the instant-search overlay. When no URL sort is present, |
| 31 | // fall back to the block's `defaultSort` attribute. |
| 32 | $url_sort = Results_Sort::parse_url_sort( $options ); |
| 33 | $effective_sort = $url_sort ?? $default_sort; |
| 34 | if ( ! in_array( $effective_sort, $options, true ) ) { |
| 35 | // `defaultSort` may land outside `availableSortOptions` (e.g. an |
| 36 | // author saved a default, then unchecked it from the list). Rather |
| 37 | // than render a control that can't represent the current state, |
| 38 | // pick the first allowed option so the selection is always a valid |
| 39 | // choice. |
| 40 | $effective_sort = $options[0]; |
| 41 | } |
| 42 | |
| 43 | // `wp_interactivity_data_wp_context()` (used in the radio template) was |
| 44 | // introduced in WP 6.5; calling it on older cores would fatal. Fall back |
| 45 | // to the `select` variant -- its template only emits inert |
| 46 | // `data-wp-bind--*` attributes, which degrade harmlessly without |
| 47 | // Interactivity. |
| 48 | if ( 'radio' === $display_as && ! function_exists( 'wp_interactivity_data_wp_context' ) ) { |
| 49 | $display_as = 'select'; |
| 50 | } |
| 51 | |
| 52 | // Seed the shared Interactivity state with the resolved sort so the JS |
| 53 | // store hydrates against the value the server rendered. Whether this seed |
| 54 | // "wins" depends on theme type: under classic themes the block renders |
| 55 | // during `wp_enqueue_scripts`, after the Search_Blocks state seeder, so |
| 56 | // `wp_interactivity_state()`'s deep-merge layers on top. Under FSE/block |
| 57 | // themes the block template renders before `wp_enqueue_scripts` fires -- |
| 58 | // the Search_Blocks seeder later overwrites this `sortOrder`, falling |
| 59 | // back to `relevance` for any sort key it doesn't recognise. Resolving |
| 60 | // the FSE precedence is tracked separately; this call still does the |
| 61 | // right thing under classic themes and is harmless under FSE. |
| 62 | if ( function_exists( 'wp_interactivity_state' ) ) { |
| 63 | wp_interactivity_state( 'jetpack-search', array( 'sortOrder' => $effective_sort ) ); |
| 64 | } |
| 65 | |
| 66 | $select_id = wp_unique_id( 'jetpack-search-results-sort-' ); |
| 67 | $menu_id = wp_unique_id( 'jetpack-search-results-sort-menu-' ); |
| 68 | ?> |
| 69 | <?php if ( 'popover' === $display_as ) : ?> |
| 70 | <div |
| 71 | <?php echo wp_kses_data( get_block_wrapper_attributes( array( 'class' => 'jetpack-search-results-sort--popover' ) ) ); ?> |
| 72 | data-wp-interactive="jetpack-search" |
| 73 | data-jetpack-search-popover-root |
| 74 | data-wp-on-window--click="actions.onWindowClickClosePopovers" |
| 75 | data-wp-on-window--keydown="actions.onEscapeClosePopovers" |
| 76 | > |
| 77 | <button |
| 78 | type="button" |
| 79 | class="jetpack-search-results-sort__trigger" |
| 80 | aria-haspopup="menu" |
| 81 | aria-expanded="false" |
| 82 | data-wp-bind--aria-expanded="state.isSortPopoverOpen" |
| 83 | disabled |
| 84 | data-wp-bind--disabled="state.isSortTriggerDisabled" |
| 85 | aria-controls="<?php echo esc_attr( $menu_id ); ?>" |
| 86 | data-wp-on--click="actions.toggleSortPopover" |
| 87 | data-wp-on--keydown="actions.onSortTriggerKeydown" |
| 88 | > |
| 89 | <svg class="jetpack-search-results-sort__icon" width="18" height="18" viewBox="0 0 24 24" aria-hidden="true" focusable="false"> |
| 90 | <path fill="currentColor" d="M8 4l-4 4h3v12h2V8h3L8 4zm8 16l4-4h-3V4h-2v12h-3l4 4z"/> |
| 91 | </svg> |
| 92 | <span class="screen-reader-text"><?php esc_html_e( 'Sort results', 'jetpack-search-pkg' ); ?></span> |
| 93 | </button> |
| 94 | <div |
| 95 | id="<?php echo esc_attr( $menu_id ); ?>" |
| 96 | class="jetpack-search-results-sort__menu" |
| 97 | role="menu" |
| 98 | data-wp-bind--hidden="!state.isSortPopoverOpen" |
| 99 | hidden |
| 100 | > |
| 101 | <?php foreach ( $options as $sort_key ) : ?> |
| 102 | <?php $option_label = $option_labels[ $sort_key ] ?? $sort_key; ?> |
| 103 | <button |
| 104 | type="button" |
| 105 | role="menuitemradio" |
| 106 | class="jetpack-search-results-sort__menu-item" |
| 107 | value="<?php echo esc_attr( $sort_key ); ?>" |
| 108 | tabindex="-1" |
| 109 | data-wp-context='<?php echo esc_attr( wp_json_encode( array( 'sortKey' => $sort_key ), JSON_HEX_AMP | JSON_UNESCAPED_SLASHES ) ); ?>' |
| 110 | data-wp-bind--aria-checked="state.isSortOptionSelected" |
| 111 | data-wp-bind--tabindex="state.sortMenuItemTabIndex" |
| 112 | data-wp-on--click="actions.selectSortOrder" |
| 113 | data-wp-on--keydown="actions.onSortMenuKeydown" |
| 114 | data-wp-watch="callbacks.focusSelectedSortMenuItem" |
| 115 | > |
| 116 | <?php echo esc_html( $option_label ); ?> |
| 117 | </button> |
| 118 | <?php endforeach; ?> |
| 119 | </div> |
| 120 | </div> |
| 121 | <?php else : ?> |
| 122 | <div |
| 123 | <?php echo wp_kses_data( get_block_wrapper_attributes() ); ?> |
| 124 | data-wp-interactive="jetpack-search" |
| 125 | > |
| 126 | <?php if ( 'radio' === $display_as ) : ?> |
| 127 | <?php |
| 128 | // Shared `name` groups the radios so the browser enforces single- |
| 129 | // selection semantics across the whole block instance. The wrapper's |
| 130 | // uniquely generated id doubles as the group name -- two results-sort |
| 131 | // blocks on the same page therefore get distinct names and don't |
| 132 | // interfere with each other. |
| 133 | $group_name = $select_id; |
| 134 | ?> |
| 135 | <fieldset class="jetpack-search-results-sort__radio-group"> |
| 136 | <legend><?php echo esc_html( $label ); ?></legend> |
| 137 | <?php foreach ( $options as $sort_key ) : ?> |
| 138 | <?php |
| 139 | $option_label = $option_labels[ $sort_key ] ?? $sort_key; |
| 140 | $radio_id = $select_id . '-' . sanitize_key( $sort_key ); |
| 141 | ?> |
| 142 | <div |
| 143 | class="jetpack-search-results-sort__radio-item" |
| 144 | <?php echo wp_kses_data( wp_interactivity_data_wp_context( array( 'sortKey' => $sort_key ) ) ); ?> |
| 145 | > |
| 146 | <input |
| 147 | type="radio" |
| 148 | id="<?php echo esc_attr( $radio_id ); ?>" |
| 149 | name="<?php echo esc_attr( $group_name ); ?>" |
| 150 | value="<?php echo esc_attr( $sort_key ); ?>" |
| 151 | <?php checked( $effective_sort, $sort_key ); ?> |
| 152 | data-wp-bind--checked="state.isSortOptionSelected" |
| 153 | data-wp-on--change="actions.onSortChange" |
| 154 | /> |
| 155 | <label for="<?php echo esc_attr( $radio_id ); ?>"> |
| 156 | <?php echo esc_html( $option_label ); ?> |
| 157 | </label> |
| 158 | </div> |
| 159 | <?php endforeach; ?> |
| 160 | </fieldset> |
| 161 | <?php else : ?> |
| 162 | <label for="<?php echo esc_attr( $select_id ); ?>"> |
| 163 | <?php echo esc_html( $label ); ?> |
| 164 | </label> |
| 165 | <select |
| 166 | id="<?php echo esc_attr( $select_id ); ?>" |
| 167 | data-wp-bind--value="state.sortOrder" |
| 168 | data-wp-on--change="actions.onSortChange" |
| 169 | > |
| 170 | <?php foreach ( $options as $sort_key ) : ?> |
| 171 | <?php $option_label = $option_labels[ $sort_key ] ?? $sort_key; ?> |
| 172 | <option |
| 173 | value="<?php echo esc_attr( $sort_key ); ?>" |
| 174 | <?php selected( $effective_sort, $sort_key ); ?> |
| 175 | ><?php echo esc_html( $option_label ); ?></option> |
| 176 | <?php endforeach; ?> |
| 177 | </select> |
| 178 | <?php endif; ?> |
| 179 | </div> |
| 180 | <?php endif; ?> |