PluginProbe
Gutenberg / 9.2.2
Gutenberg v9.2.2
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / template-loader.php

template-loader.php in Gutenberg 9.2.2, at lib/template-loader.php

459 lines 15.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Block template loader functions.
4 *
5 * @package gutenberg
6 */
7
8 /**
9 * Return a list of all overrideable default template types.
10 *
11 * @see get_query_template
12 *
13 * @return string[] List of all overrideable default template types.
14 */
15 function get_template_types() {
16 return array(
17 'index',
18 '404',
19 'archive',
20 'author',
21 'category',
22 'tag',
23 'taxonomy',
24 'date',
25 'embed',
26 'home',
27 'front-page',
28 'privacy-policy',
29 'page',
30 'search',
31 'single',
32 'singular',
33 'attachment',
34 );
35 }
36
37 /**
38 * Adds necessary filters to use 'wp_template' posts instead of theme template files.
39 */
40 function gutenberg_add_template_loader_filters() {
41 foreach ( get_template_types() as $template_type ) {
42 if ( 'embed' === $template_type ) { // Skip 'embed' for now because it is not a regular template type.
43 continue;
44 }
45 add_filter( str_replace( '-', '', $template_type ) . '_template', 'gutenberg_override_query_template', 20, 3 );
46 }
47 }
48 add_action( 'wp_loaded', 'gutenberg_add_template_loader_filters' );
49
50 /**
51 * Get the template hierarchy for a given template type.
52 *
53 * Internally, this filters into the "{$type}_template_hierarchy" hook to record the type-specific template hierarchy.
54 *
55 * @param string $template_type A template type.
56 * @return string[] A list of template candidates, in descending order of priority.
57 */
58 function get_template_hierarchy( $template_type ) {
59 if ( ! in_array( $template_type, get_template_types(), true ) ) {
60 return array();
61 }
62
63 $get_template_function = 'get_' . str_replace( '-', '_', $template_type ) . '_template'; // front-page -> get_front_page_template.
64 $template_hierarchy_filter = str_replace( '-', '', $template_type ) . '_template_hierarchy'; // front-page -> frontpage_template_hierarchy.
65
66 $result = array();
67 $template_hierarchy_filter_function = function( $templates ) use ( &$result ) {
68 $result = $templates;
69 return $templates;
70 };
71
72 add_filter( $template_hierarchy_filter, $template_hierarchy_filter_function, 20, 1 );
73 call_user_func( $get_template_function ); // This invokes template_hierarchy_filter.
74 remove_filter( $template_hierarchy_filter, $template_hierarchy_filter_function, 20 );
75
76 return $result;
77 }
78
79 /**
80 * Filters into the "{$type}_template" hooks to redirect them to the Full Site Editing template canvas.
81 *
82 * Internally, this communicates the block content that needs to be used by the template canvas through a global variable.
83 *
84 * @param string $template Path to the template. See locate_template().
85 * @param string $type Sanitized filename without extension.
86 * @param array $templates A list of template candidates, in descending order of priority.
87 * @return string The path to the Full Site Editing template canvas file.
88 */
89 function gutenberg_override_query_template( $template, $type, array $templates = array() ) {
90 global $_wp_current_template_content;
91
92 $current_template = gutenberg_find_template_post_and_parts( $type, $templates );
93
94 if ( $current_template ) {
95 $_wp_current_template_content = empty( $current_template['template_post']->post_content ) ? __( 'Empty template.', 'gutenberg' ) : $current_template['template_post']->post_content;
96
97 if ( isset( $_GET['_wp-find-template'] ) ) {
98 wp_send_json_success( $current_template['template_post'] );
99 }
100 } else {
101 if ( 'index' === $type ) {
102 if ( isset( $_GET['_wp-find-template'] ) ) {
103 wp_send_json_error( array( 'message' => __( 'No matching template found.', 'gutenberg' ) ) );
104 }
105 } else {
106 return false; // So that the template loader keeps looking for templates.
107 }
108 }
109
110 // Add hooks for template canvas.
111 // Add viewport meta tag.
112 add_action( 'wp_head', 'gutenberg_viewport_meta_tag', 0 );
113
114 // Render title tag with content, regardless of whether theme has title-tag support.
115 remove_action( 'wp_head', '_wp_render_title_tag', 1 ); // Remove conditional title tag rendering...
116 add_action( 'wp_head', 'gutenberg_render_title_tag', 1 ); // ...and make it unconditional.
117
118 // This file will be included instead of the theme's template file.
119 return gutenberg_dir_path() . 'lib/template-canvas.php';
120 }
121
122 /**
123 * Recursively traverses a block tree, creating auto drafts
124 * for any encountered template parts without a fixed post.
125 *
126 * @access private
127 *
128 * @param array $block The root block to start traversing from.
129 * @return int[] A list of template parts IDs for the given block.
130 */
131 function create_auto_draft_for_template_part_block( $block ) {
132 $template_part_ids = array();
133
134 if ( 'core/template-part' === $block['blockName'] && isset( $block['attrs']['slug'] ) ) {
135 if ( isset( $block['attrs']['postId'] ) ) {
136 // Template part is customized.
137 $template_part_id = $block['attrs']['postId'];
138 } else {
139 // A published post might already exist if this template part
140 // was customized elsewhere or if it's part of a customized
141 // template. We also check if an auto-draft was already created
142 // because preloading can make this run twice, so, different code
143 // paths can end up with different posts for the same template part.
144 // E.g. The server could send back post ID 1 to the client, preload,
145 // and create another auto-draft. So, if the client tries to resolve the
146 // post ID from the slug and theme, it won't match with what the server sent.
147 $template_part_query = new WP_Query(
148 array(
149 'post_type' => 'wp_template_part',
150 'post_status' => array( 'publish', 'auto-draft' ),
151 'title' => $block['attrs']['slug'],
152 'meta_key' => 'theme',
153 'meta_value' => $block['attrs']['theme'],
154 'posts_per_page' => 1,
155 'no_found_rows' => true,
156 )
157 );
158 $template_part_post = $template_part_query->have_posts() ? $template_part_query->next_post() : null;
159 if ( $template_part_post && 'auto-draft' !== $template_part_post->post_status ) {
160 $template_part_id = $template_part_post->ID;
161 } else {
162 // Template part is not customized, get it from a file and make an auto-draft for it, unless one already exists
163 // and the underlying file hasn't changed.
164 $template_part_file_path =
165 get_stylesheet_directory() . '/block-template-parts/' . $block['attrs']['slug'] . '.html';
166 if ( ! file_exists( $template_part_file_path ) ) {
167 if ( gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing-demo' ) ) {
168 $template_part_file_path =
169 dirname( __FILE__ ) . '/demo-block-template-parts/' . $block['attrs']['slug'] . '.html';
170 if ( ! file_exists( $template_part_file_path ) ) {
171 $template_part_file_path = false;
172 }
173 } else {
174 $template_part_file_path = false;
175 }
176 }
177
178 if ( $template_part_file_path ) {
179 $file_contents = file_get_contents( $template_part_file_path );
180 if ( $template_part_post && $template_part_post->post_content === $file_contents ) {
181 $template_part_id = $template_part_post->ID;
182 } else {
183 $template_part_id = wp_insert_post(
184 array(
185 'post_content' => $file_contents,
186 'post_title' => $block['attrs']['slug'],
187 'post_status' => 'auto-draft',
188 'post_type' => 'wp_template_part',
189 'post_name' => $block['attrs']['slug'],
190 'meta_input' => array(
191 'theme' => $block['attrs']['theme'],
192 ),
193 )
194 );
195 }
196 }
197 }
198 }
199 $template_part_ids[ $block['attrs']['slug'] ] = $template_part_id;
200 }
201
202 foreach ( $block['innerBlocks'] as $inner_block ) {
203 $template_part_ids = array_merge( $template_part_ids, create_auto_draft_for_template_part_block( $inner_block ) );
204 }
205 return $template_part_ids;
206 }
207
208 /**
209 * Return the correct 'wp_template' post and template part IDs for the current template.
210 *
211 * Accepts an optional $template_hierarchy argument as a hint.
212 *
213 * @param string $template_type The current template type.
214 * @param string[] $template_hierarchy (optional) The current template hierarchy, ordered by priority.
215 * @return null|array {
216 * @type WP_Post|null template_post A template post object, or null if none could be found.
217 * @type int[] A list of template parts IDs for the template.
218 * }
219 */
220 function gutenberg_find_template_post_and_parts( $template_type, $template_hierarchy = array() ) {
221 if ( ! $template_type ) {
222 return null;
223 }
224
225 if ( empty( $template_hierarchy ) ) {
226 if ( 'index' === $template_type ) {
227 $template_hierarchy = get_template_hierarchy( 'index' );
228 } else {
229 $template_hierarchy = array_merge( get_template_hierarchy( $template_type ), get_template_hierarchy( 'index' ) );
230 }
231 }
232
233 $slugs = array_map(
234 'gutenberg_strip_php_suffix',
235 $template_hierarchy
236 );
237
238 // Find most specific 'wp_template' post matching the hierarchy.
239 $template_query = new WP_Query(
240 array(
241 'post_type' => 'wp_template',
242 'post_status' => 'publish',
243 'post_name__in' => $slugs,
244 'orderby' => 'post_name__in',
245 'posts_per_page' => 1,
246 'no_found_rows' => true,
247 )
248 );
249
250 $current_template_post = $template_query->have_posts() ? $template_query->next_post() : null;
251
252 // Build map of template slugs to their priority in the current hierarchy.
253 $slug_priorities = array_flip( $slugs );
254
255 // See if there is a theme block template with higher priority than the resolved template post.
256 $higher_priority_block_template_path = null;
257 $higher_priority_block_template_priority = PHP_INT_MAX;
258 $block_template_files = gutenberg_get_template_paths();
259 foreach ( $block_template_files as $path ) {
260 if ( ! isset( $slug_priorities[ basename( $path, '.html' ) ] ) ) {
261 continue;
262 }
263 $theme_block_template_priority = $slug_priorities[ basename( $path, '.html' ) ];
264 if (
265 $theme_block_template_priority < $higher_priority_block_template_priority &&
266 ( empty( $current_template_post ) || $theme_block_template_priority < $slug_priorities[ $current_template_post->post_name ] )
267 ) {
268 $higher_priority_block_template_path = $path;
269 $higher_priority_block_template_priority = $theme_block_template_priority;
270 }
271 }
272
273 // If there is, use it instead.
274 if ( isset( $higher_priority_block_template_path ) ) {
275 $post_name = basename( $higher_priority_block_template_path, '.html' );
276 $file_contents = file_get_contents( $higher_priority_block_template_path );
277 $current_template_post = array(
278 'post_content' => $file_contents,
279 'post_title' => $post_name,
280 'post_status' => 'auto-draft',
281 'post_type' => 'wp_template',
282 'post_name' => $post_name,
283 );
284 if ( is_admin() || defined( 'REST_REQUEST' ) ) {
285 $template_query = new WP_Query(
286 array(
287 'post_type' => 'wp_template',
288 'post_status' => 'auto-draft',
289 'name' => $post_name,
290 'posts_per_page' => 1,
291 'no_found_rows' => true,
292 )
293 );
294 $current_template_post = $template_query->have_posts() ? $template_query->next_post() : $current_template_post;
295
296 // Only create auto-draft of block template for editing
297 // in admin screens, when necessary, because the underlying
298 // file has changed.
299 if ( is_array( $current_template_post ) || $current_template_post->post_content !== $file_contents ) {
300 if ( ! is_array( $current_template_post ) ) {
301 $current_template_post->post_content = $file_contents;
302 }
303 $current_template_post = get_post(
304 wp_insert_post( $current_template_post )
305 );
306 }
307 } else {
308 $current_template_post = new WP_Post(
309 (object) $current_template_post
310 );
311 }
312 }
313
314 // If we haven't found any template post by here, it means that this theme doesn't even come with a fallback
315 // `index.html` block template. We create one so that people that are trying to access the editor are greeted
316 // with a blank page rather than an error.
317 if ( ! $current_template_post && ( is_admin() || defined( 'REST_REQUEST' ) ) ) {
318 $current_template_post = array(
319 'post_title' => 'index',
320 'post_status' => 'auto-draft',
321 'post_type' => 'wp_template',
322 'post_name' => 'index',
323 );
324 $current_template_post = get_post(
325 wp_insert_post( $current_template_post )
326 );
327 }
328
329 if ( $current_template_post ) {
330 $template_part_ids = array();
331 if ( is_admin() || defined( 'REST_REQUEST' ) ) {
332 foreach ( parse_blocks( $current_template_post->post_content ) as $block ) {
333 $template_part_ids = array_merge( $template_part_ids, create_auto_draft_for_template_part_block( $block ) );
334 }
335 }
336 return array(
337 'template_post' => $current_template_post,
338 'template_part_ids' => $template_part_ids,
339 );
340 }
341 return null;
342 }
343
344 /**
345 * Displays title tag with content, regardless of whether theme has title-tag support.
346 *
347 * @see _wp_render_title_tag()
348 */
349 function gutenberg_render_title_tag() {
350 echo '<title>' . wp_get_document_title() . '</title>' . "\n";
351 }
352
353 /**
354 * Renders the markup for the current template.
355 */
356 function gutenberg_render_the_template() {
357 global $_wp_current_template_content;
358 global $wp_embed;
359
360 if ( ! $_wp_current_template_content ) {
361 echo '<h1>' . esc_html__( 'No matching template found', 'gutenberg' ) . '</h1>';
362 return;
363 }
364
365 $content = $wp_embed->run_shortcode( $_wp_current_template_content );
366 $content = $wp_embed->autoembed( $content );
367 $content = do_blocks( $content );
368 $content = wptexturize( $content );
369 if ( function_exists( 'wp_filter_content_tags' ) ) {
370 $content = wp_filter_content_tags( $content );
371 } else {
372 $content = wp_make_content_images_responsive( $content );
373 }
374 $content = str_replace( ']]>', ']]&gt;', $content );
375
376 // Wrap block template in .wp-site-blocks to allow for specific descendant styles
377 // (e.g. `.wp-site-blocks > *`).
378 echo '<div class="wp-site-blocks">';
379 echo $content; // phpcs:ignore WordPress.Security.EscapeOutput
380 echo '</div>';
381 }
382
383 /**
384 * Renders a 'viewport' meta tag.
385 *
386 * This is hooked into {@see 'wp_head'} to decouple its output from the default template canvas.
387 */
388 function gutenberg_viewport_meta_tag() {
389 echo '<meta name="viewport" content="width=device-width, initial-scale=1" />' . "\n";
390 }
391
392 /**
393 * Strips .php suffix from template file names.
394 *
395 * @access private
396 *
397 * @param string $template_file Template file name.
398 * @return string Template file name without extension.
399 */
400 function gutenberg_strip_php_suffix( $template_file ) {
401 return preg_replace( '/\.php$/', '', $template_file );
402 }
403
404 /**
405 * Extends default editor settings to enable template and template part editing.
406 *
407 * @param array $settings Default editor settings.
408 *
409 * @return array Filtered editor settings.
410 */
411 function gutenberg_template_loader_filter_block_editor_settings( $settings ) {
412 global $post;
413
414 if ( ! $post ) {
415 return $settings;
416 }
417
418 // If this is the Site Editor, auto-drafts for template parts have already been generated
419 // through `filter_rest_wp_template_part_query`, when called via the REST API.
420 if ( isset( $settings['editSiteInitialState'] ) ) {
421 return $settings;
422 }
423
424 // Otherwise, create template part auto-drafts for the edited post.
425 $post = get_post();
426 foreach ( parse_blocks( $post->post_content ) as $block ) {
427 create_auto_draft_for_template_part_block( $block );
428 }
429
430 // TODO: Set editing mode and current template ID for editing modes support.
431 return $settings;
432 }
433 add_filter( 'block_editor_settings', 'gutenberg_template_loader_filter_block_editor_settings' );
434
435 /**
436 * Removes post details from block context when rendering a block template.
437 *
438 * @param array $context Default context.
439 *
440 * @return array Filtered context.
441 */
442 function gutenberg_template_render_without_post_block_context( $context ) {
443 /*
444 * When loading a template or template part directly and not through a page
445 * that resolves it, the top-level post ID and type context get set to that
446 * of the template part. Templates are just the structure of a site, and
447 * they should not be available as post context because blocks like Post
448 * Content would recurse infinitely.
449 */
450 if ( isset( $context['postType'] ) &&
451 ( 'wp_template' === $context['postType'] || 'wp_template_part' === $context['postType'] ) ) {
452 unset( $context['postId'] );
453 unset( $context['postType'] );
454 }
455
456 return $context;
457 }
458 add_filter( 'render_block_context', 'gutenberg_template_render_without_post_block_context' );
459