Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
99.15% |
116 / 117 |
n/a |
0 / 0 |
CRAP | n/a |
0 / 0 |
||
| 1 | <?php |
| 2 | /** |
| 3 | * AI Answer block render. |
| 4 | * |
| 5 | * Renders the panel scaffold that the Interactivity store hydrates with the |
| 6 | * streaming AI answer. Inserting the block is its own opt-in switch — the |
| 7 | * `jetpack_search_ai_answers_enabled` option governs the overlay, not the block. |
| 8 | * |
| 9 | * The gates below (paid Search plan, host AI opt-out, Jetpack AI master |
| 10 | * switch) emit nothing when they fail, matching how WordAds / Premium |
| 11 | * Content hide on a failed plan check. |
| 12 | * |
| 13 | * @package automattic/jetpack-search |
| 14 | */ |
| 15 | |
| 16 | namespace Automattic\Jetpack\Search; |
| 17 | |
| 18 | if ( ! defined( 'ABSPATH' ) ) { |
| 19 | exit( 0 ); |
| 20 | } |
| 21 | |
| 22 | // Paid-plan gate. AI Answer requires Jetpack Search's paid plan; on a free |
| 23 | // or no-plan site the block contributes nothing to the page (no panel |
| 24 | // scaffold, no `data-wp-interactive` div, no Interactivity hydration). The |
| 25 | // editor surface shows an upgrade prompt instead — see `edit.js`. |
| 26 | if ( ! Search_Blocks::supports_paid_search() ) { |
| 27 | return; |
| 28 | } |
| 29 | |
| 30 | // Host-owner AI opt-out (core's wp_supports_ai() / WP_AI_SUPPORT). The overlay |
| 31 | // already honors it through the plugin's filter gate; the block must agree. |
| 32 | if ( ! AI_Answers::host_allows_ai() ) { |
| 33 | return; |
| 34 | } |
| 35 | |
| 36 | if ( ! AI_Answers::is_master_enabled() ) { |
| 37 | return; |
| 38 | } |
| 39 | |
| 40 | // $attributes is injected by WordPress at block-render time via the |
| 41 | // `render_callback` include scope; static analysis can't see the binding, |
| 42 | // so both phpcs and Phan need a one-line suppression on the next statement. |
| 43 | // @phan-suppress-next-line PhanUndeclaredGlobalVariable |
| 44 | $attrs = (array) $attributes; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable |
| 45 | $heading_raw = trim( (string) ( $attrs['heading'] ?? '' ) ); |
| 46 | $heading = '' === $heading_raw ? __( 'AI answer', 'jetpack-search-pkg' ) : $heading_raw; |
| 47 | $show_citations = ! isset( $attrs['showCitations'] ) || $attrs['showCitations']; |
| 48 | $enable_extended = ! isset( $attrs['enableShowMore'] ) || $attrs['enableShowMore']; |
| 49 | $wrapper_attrs = get_block_wrapper_attributes( |
| 50 | array( 'class' => 'jp-search-answers-panel' ) |
| 51 | ); |
| 52 | ?> |
| 53 | <div |
| 54 | <?php echo wp_kses_data( $wrapper_attrs ); ?> |
| 55 | data-wp-interactive="jetpack-search" |
| 56 | data-wp-init="callbacks.initializeAiAnswer" |
| 57 | data-wp-bind--hidden="state.aiPanelHidden" |
| 58 | aria-live="polite" |
| 59 | hidden |
| 60 | > |
| 61 | <h2 class="jp-search-answers-panel__heading"><?php echo esc_html( $heading ); ?></h2> |
| 62 | |
| 63 | <div |
| 64 | class="jp-search-answers-panel__loading" |
| 65 | data-wp-bind--hidden="!state.aiIsLoading" |
| 66 | hidden |
| 67 | > |
| 68 | <?php esc_html_e( 'Finding an answer', 'jetpack-search-pkg' ); ?> |
| 69 | <span class="jp-search-animated-ellipsis" aria-hidden="true"> |
| 70 | <span></span><span></span><span></span> |
| 71 | </span> |
| 72 | </div> |
| 73 | |
| 74 | <div |
| 75 | class="jp-search-answers-panel__error" |
| 76 | role="alert" |
| 77 | data-wp-bind--hidden="!state.aiIsError" |
| 78 | hidden |
| 79 | > |
| 80 | <p |
| 81 | class="jp-search-answers-panel__error-message" |
| 82 | data-wp-text="state.aiErrorPrimary" |
| 83 | ></p> |
| 84 | <p |
| 85 | class="jp-search-answers-panel__error-detail" |
| 86 | data-wp-bind--hidden="!state.aiHasErrorDetail" |
| 87 | > |
| 88 | <span data-wp-text="state.aiErrorDetail"></span> |
| 89 | <span data-wp-bind--hidden="!state.aiHasErrorCode"> |
| 90 | <br /> |
| 91 | <span data-wp-text="state.aiErrorCodeText"></span> |
| 92 | </span> |
| 93 | </p> |
| 94 | </div> |
| 95 | |
| 96 | <div |
| 97 | class="jp-search-answers-panel__content" |
| 98 | data-wp-bind--hidden="!state.aiHasContent" |
| 99 | hidden |
| 100 | > |
| 101 | <div |
| 102 | class="jp-search-answers-panel__text" |
| 103 | data-wp-watch="callbacks.renderAiAnswerHtml" |
| 104 | ></div> |
| 105 | |
| 106 | <?php if ( $show_citations ) : ?> |
| 107 | <ul |
| 108 | class="jp-search-answers-panel__citations" |
| 109 | data-wp-bind--hidden="!state.aiHasCitations" |
| 110 | hidden |
| 111 | > |
| 112 | <template |
| 113 | data-wp-each--citation="state.aiVisibleCitations" |
| 114 | data-wp-each-key="context.citation.key" |
| 115 | > |
| 116 | <li> |
| 117 | <a |
| 118 | data-wp-bind--href="context.citation.href" |
| 119 | target="_blank" |
| 120 | rel="noopener noreferrer" |
| 121 | > |
| 122 | <span data-wp-text="context.citation.title"></span> |
| 123 | <svg |
| 124 | width="10" |
| 125 | height="10" |
| 126 | viewBox="0 0 10 10" |
| 127 | fill="none" |
| 128 | xmlns="http://www.w3.org/2000/svg" |
| 129 | aria-hidden="true" |
| 130 | class="jp-search-answers-panel__citation-icon" |
| 131 | > |
| 132 | <path d="M1 9L9 1M9 1H5M9 1V5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> |
| 133 | </svg> |
| 134 | </a> |
| 135 | </li> |
| 136 | </template> |
| 137 | </ul> |
| 138 | <?php endif; ?> |
| 139 | </div> |
| 140 | |
| 141 | <p |
| 142 | class="jp-search-answers-panel__loading-hint" |
| 143 | data-wp-bind--hidden="!state.aiExtendedLoadingHintShown" |
| 144 | hidden |
| 145 | > |
| 146 | <span data-wp-text="state.aiExtendedLoadingText"></span> |
| 147 | <span class="jp-search-animated-ellipsis" aria-hidden="true"> |
| 148 | <span></span><span></span><span></span> |
| 149 | </span> |
| 150 | </p> |
| 151 | |
| 152 | <?php if ( $enable_extended ) : ?> |
| 153 | <button |
| 154 | type="button" |
| 155 | class="jp-search-answers-panel__toggle" |
| 156 | data-wp-bind--hidden="!state.aiShowExtendedButton" |
| 157 | data-wp-on--click="actions.showExtendedAiAnswer" |
| 158 | hidden |
| 159 | > |
| 160 | <?php esc_html_e( 'Show more', 'jetpack-search-pkg' ); ?> |
| 161 | <span class="jp-search-answers-panel__toggle-icon" aria-hidden="true"></span> |
| 162 | </button> |
| 163 | <?php endif; ?> |
| 164 | </div> |