Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.00% covered (warning)
75.00%
18 / 24
n/a
0 / 0
CRAP
n/a
0 / 0
1<?php
2/**
3 * Search Results block render.
4 *
5 * Group-like wrapper that emits `$content` (the serialized inner block
6 * markup). Each inner result block handles its own state via the
7 * Interactivity API store; this block contributes only the surrounding
8 * chrome (block-wrapper attrs derived from color/spacing/border/typography
9 * supports).
10 *
11 * Free-plan attribution: the Jetpack colophon must appear on every
12 * free-plan results page. If an author has removed the
13 * `jetpack-search/powered-by` block from the panel (or never had one in
14 * their pattern), this renderer appends a canonical render of it on the
15 * way out so the attribution is structurally non-removable. Paid-plan
16 * sites are unaffected — authors can keep, hide, or delete the block
17 * freely.
18 *
19 * @package automattic/jetpack-search
20 */
21
22namespace Automattic\Jetpack\Search;
23
24// phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
25
26// Author-set post-type scope for this search experience. The constraint
27// shape (`{ include: string[], exclude: string[] }`) round-trips through
28// `Filter_Post_Type::build_constraint()` for slug sanitization + the live
29// searchable-types allowlist, then seeds the `state.staticPostTypes` slot
30// the store reads in `fetchResults()`. One writer per page (a search-
31// results region is singular by design), so no merge — straight overwrite
32// of the slot's deep-merge entry. The block attribute is `postTypeMode`
33// (renamed from the helper's neutral `mode` prop to avoid colliding with
34// any future generic `mode` attribute on this block).
35$scope = Filter_Post_Type::build_constraint(
36    array(
37        'mode'      => ( $attributes['postTypeMode'] ?? 'exclude' ) === 'include' ? 'include' : 'exclude',
38        'postTypes' => $attributes['postTypes'] ?? array(),
39    )
40);
41if ( function_exists( 'wp_interactivity_state' ) && ( ! empty( $scope['include'] ) || ! empty( $scope['exclude'] ) ) ) {
42    wp_interactivity_state( 'jetpack-search', array( 'staticPostTypes' => $scope ) );
43}
44
45// Results-per-page: author override via `resultsPerPage`, falling back live
46// to the site's Reading setting so a later change to `posts_per_page` still
47// takes effect on pages that leave this unset.
48$results_per_page = Helper::resolve_results_per_page( (int) ( $attributes['resultsPerPage'] ?? 0 ) );
49if ( function_exists( 'wp_interactivity_state' ) ) {
50    wp_interactivity_state( 'jetpack-search', array( 'resultsPerPage' => $results_per_page ) );
51}
52
53// Load the WordPress.com Tracks consumer (drains `window._tkq`) so the
54// TrainTracks render/interact events the store pushes actually get sent.
55// Enqueuing here loads it exactly on pages where the Search blocks render,
56// across all blocks experiences. Skipped when tracking is suppressed.
57if ( ! Search_Blocks::is_tracking_disabled() ) {
58    Helper::enqueue_tracks_script();
59}
60
61$panel_content = $content; // @phan-suppress-current-line PhanUndeclaredGlobalVariable -- $content is provided by WP at block render.
62
63if ( Search_Blocks::is_free_plan() && false === strpos( $panel_content, 'wp-block-jetpack-search-powered-by' ) ) {
64    $panel_content .= render_block(
65        array(
66            'blockName' => 'jetpack-search/powered-by',
67            'attrs'     => array(),
68        )
69    );
70}
71?>
72<div <?php echo wp_kses_data( get_block_wrapper_attributes( array( 'class' => 'jetpack-search-search-results' ) ) ); ?>>
73    <?php
74    echo $panel_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Inner block HTML is already escaped by each child block's renderer; auto-injected powered-by output is rendered through render_block() and escaped by its own renderer.
75    ?>
76</div>