PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.1-beta
16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-search / src / search-blocks / class-theme-chrome-slug-resolver.php
jetpack / jetpack_vendor / automattic / jetpack-search / src / search-blocks Last commit date
blocks 1 month ago patterns 2 months ago templates 2 months ago class-custom-taxonomy-slot-mapping.php 2 months ago class-filter-post-type.php 2 months ago class-overlay-template.php 2 months ago class-product-overlay-template.php 2 months ago class-product-search-template.php 2 months ago class-search-blocks.php 3 weeks ago class-search-template.php 2 months ago class-singleton-template-cpt.php 1 month ago class-theme-chrome-slug-resolver.php 3 months ago class-wc-block-helpers.php 2 months ago
class-theme-chrome-slug-resolver.php
253 lines
1 <?php
2 /**
3 * Resolves the header/footer template-part slugs the active theme uses for
4 * its search results so the bundled Jetpack Search template can mirror them.
5 *
6 * @package automattic/jetpack-search
7 */
8
9 namespace Automattic\Jetpack\Search;
10
11 /**
12 * Resolves chrome (header/footer) slugs for the bundled search templates,
13 * cached in a site option, invalidated on theme switch + relevant Site
14 * Editor saves.
15 *
16 * Resolution chain (per slot): theme's `search.html` → `index.html` →
17 * hardcoded `header`/`footer` defaults. The 237-theme wordpress.org
18 * survey behind SEARCH-217 found this two-rung chain covers every theme
19 * that ships parts at non-default slugs (the long tail of `wp:pattern`-
20 * wrap themes already works with the defaults because they still ship
21 * `parts/header.html` / `parts/footer.html`).
22 */
23 class Theme_Chrome_Slug_Resolver {
24
25 const OPTION_NAME = 'jetpack_search_resolved_chrome_slugs';
26
27 const DEFAULTS = array(
28 'header' => 'header',
29 'footer' => 'footer',
30 );
31
32 /**
33 * Hook invalidation actions. Idempotent — safe to call from init.
34 *
35 * `save_post_wp_template_part` is broader than the wp_template hook
36 * (no narrowing by slug or theme): part edits are rare and even an
37 * over-eager invalidation is cheap (one extra `compute()` next request).
38 */
39 public static function register_hooks() {
40 add_action( 'switch_theme', array( static::class, 'invalidate' ) );
41 add_action( 'save_post_wp_template', array( static::class, 'maybe_invalidate_on_template_save' ), 10, 2 );
42 add_action( 'save_post_wp_template_part', array( static::class, 'invalidate' ) );
43 }
44
45 /**
46 * Resolved chrome slugs for the active theme.
47 *
48 * @return array{header:string,footer:string}
49 */
50 public static function resolve(): array {
51 $stylesheet = (string) get_stylesheet();
52 if ( static::is_preview() ) {
53 // Preview themes: resolve fresh, never read or write the cache.
54 return static::compute();
55 }
56 $cached = static::read_cache( $stylesheet );
57 if ( null !== $cached ) {
58 return $cached;
59 }
60 $computed = static::compute();
61 static::write_cache( $stylesheet, $computed );
62 return $computed;
63 }
64
65 /**
66 * Clear the cache. Hooked to `switch_theme` and template-part saves.
67 */
68 public static function invalidate() {
69 delete_option( self::OPTION_NAME );
70 }
71
72 /**
73 * `save_post_wp_template` handler — only invalidate when the saved
74 * template is one we read from (`search` or `index`) on the active
75 * theme. Cuts noise from unrelated template edits.
76 *
77 * @param int $post_id Post ID.
78 * @param \WP_Post $post Post object.
79 */
80 public static function maybe_invalidate_on_template_save( $post_id, $post ) {
81 if ( ! $post instanceof \WP_Post ) {
82 return;
83 }
84 if ( 'search' !== $post->post_name && 'index' !== $post->post_name ) {
85 return;
86 }
87 $terms = wp_get_post_terms( $post_id, 'wp_theme', array( 'fields' => 'names' ) );
88 if ( is_wp_error( $terms ) || ! in_array( (string) get_stylesheet(), (array) $terms, true ) ) {
89 return;
90 }
91 static::invalidate();
92 }
93
94 /**
95 * Pull the first and last top-level `core/template-part` slugs out of
96 * template markup. Slugs outside `[a-zA-Z0-9_-]` are rejected so the
97 * JSON round-trip in the bundled-template substitution can't break.
98 * A single top-level template-part is treated as header-only. Only
99 * top-level blocks are walked — parts nested inside `wp:group`
100 * containers are skipped on purpose (a theme that buries its chrome
101 * inside a wrapper falls back to the resolver's later rungs).
102 *
103 * @param string $template_content Block markup.
104 * @return array{header:?string,footer:?string}
105 */
106 public static function extract_from_template_content( string $template_content ): array {
107 $header = null;
108 $footer = null;
109 $count = 0;
110 if ( '' === $template_content || ! function_exists( 'parse_blocks' ) ) {
111 return array(
112 'header' => $header,
113 'footer' => $footer,
114 );
115 }
116 foreach ( parse_blocks( $template_content ) as $block ) {
117 if ( 'core/template-part' !== ( $block['blockName'] ?? '' ) ) {
118 continue;
119 }
120 $slug = $block['attrs']['slug'] ?? null;
121 if ( ! is_string( $slug ) || '' === $slug || ! preg_match( '/^[a-zA-Z0-9_-]+$/', $slug ) ) {
122 continue;
123 }
124 if ( null === $header ) {
125 $header = $slug;
126 }
127 $footer = $slug;
128 ++$count;
129 }
130 if ( $count < 2 ) {
131 $footer = null;
132 }
133 return array(
134 'header' => $header,
135 'footer' => $footer,
136 );
137 }
138
139 /**
140 * Run the resolution chain. Doesn't touch the cache (caller decides).
141 * The active stylesheet is implicit via `get_active_theme_template_content()`.
142 *
143 * @return array{header:string,footer:string}
144 */
145 protected static function compute(): array {
146 $found = array(
147 'header' => null,
148 'footer' => null,
149 );
150 foreach ( array( 'search', 'index' ) as $template_name ) {
151 if ( null !== $found['header'] && null !== $found['footer'] ) {
152 break;
153 }
154 $content = static::get_active_theme_template_content( $template_name );
155 if ( null === $content ) {
156 continue;
157 }
158 $extracted = static::extract_from_template_content( $content );
159 $found['header'] = $found['header'] ?? $extracted['header'];
160 $found['footer'] = $found['footer'] ?? $extracted['footer'];
161 }
162 return array(
163 'header' => $found['header'] ?? self::DEFAULTS['header'],
164 'footer' => $found['footer'] ?? self::DEFAULTS['footer'],
165 );
166 }
167
168 /**
169 * Read the option-backed cache. Returns null on miss or when the
170 * stored stylesheet doesn't match the active one (handles the rare
171 * case where `switch_theme` fired without clearing the option).
172 *
173 * @param string $stylesheet Active stylesheet.
174 * @return array{header:string,footer:string}|null
175 */
176 protected static function read_cache( string $stylesheet ): ?array {
177 $raw = get_option( self::OPTION_NAME );
178 if ( ! is_array( $raw ) || ( $raw['stylesheet'] ?? null ) !== $stylesheet ) {
179 return null;
180 }
181 $header = $raw['header'] ?? null;
182 $footer = $raw['footer'] ?? null;
183 if ( ! is_string( $header ) || ! is_string( $footer ) ) {
184 return null;
185 }
186 return array(
187 'header' => $header,
188 'footer' => $footer,
189 );
190 }
191
192 /**
193 * Persist the resolved slugs to the option-backed cache.
194 *
195 * `autoload=false`: the option changes only on theme switches /
196 * search.html edits, so we don't want it in the `alloptions` payload
197 * fetched on every request. The first `get_option()` per request
198 * issues its own DB query (or hits the object cache on sites that
199 * have one) instead.
200 *
201 * Cache-cold race: two concurrent requests can both compute and write
202 * with last-writer-wins semantics. Same input → same output, so
203 * correctness isn't affected.
204 *
205 * @param string $stylesheet Active stylesheet.
206 * @param array{header:string,footer:string} $slugs Resolved slugs.
207 */
208 protected static function write_cache( string $stylesheet, array $slugs ) {
209 update_option(
210 self::OPTION_NAME,
211 array(
212 'stylesheet' => $stylesheet,
213 'header' => $slugs['header'],
214 'footer' => $slugs['footer'],
215 ),
216 false
217 );
218 }
219
220 /**
221 * Whether the current request is a theme preview (Customizer or
222 * Site Editor theme preview). Cache reads and writes are skipped in
223 * this state so a preview never pollutes the production-theme cache.
224 *
225 * @return bool
226 */
227 protected static function is_preview(): bool {
228 if ( function_exists( 'is_customize_preview' ) && is_customize_preview() ) {
229 return true;
230 }
231 // Site Editor theme preview surfaces as `?wp_theme_preview=<theme-slug>`.
232 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only preview detection.
233 return ! empty( $_GET['wp_theme_preview'] );
234 }
235
236 /**
237 * Resolved markup for an active-theme template. Overridable seam.
238 *
239 * @param string $template_name Bare template slug (no `theme//` prefix).
240 * @return string|null
241 */
242 protected static function get_active_theme_template_content( string $template_name ): ?string {
243 if ( ! function_exists( 'get_block_template' ) ) {
244 return null;
245 }
246 $tmpl = get_block_template( (string) get_stylesheet() . '//' . $template_name, 'wp_template' );
247 if ( ! $tmpl || empty( $tmpl->content ) ) {
248 return null;
249 }
250 return (string) $tmpl->content;
251 }
252 }
253