PluginProbe
Gutenberg / 8.9.2
Gutenberg v8.9.2
24.0.0 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 All 403 releases
← All changes | lib/template-loader.php +268 -89 7.4.0 → 8.9.2 View file →
@@ -5,23 +5,16 @@
5 5 * @package gutenberg
6 6 */
7 7
8 8 /**
9 - * Adds necessary filters to use 'wp_template' posts instead of theme template files.
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.
10 14 */
11 -function gutenberg_add_template_loader_filters() {
12 - if ( ! post_type_exists( 'wp_template' ) ) {
13 - return;
14 - }
15 -
16 - /**
17 - * Array of all overrideable default template types.
18 - *
19 - * @see get_query_template
20 - *
21 - * @var array
22 - */
23 - $template_types = array(
15 +function get_template_types() {
16 + return array(
24 17 'index',
25 18 '404',
26 19 'archive',
27 20 'author',
@@ -28,12 +21,12 @@
28 21 'category',
29 22 'tag',
30 23 'taxonomy',
31 24 'date',
32 - // Skip 'embed' for now because it is not a regular template type.
25 + 'embed',
33 26 'home',
34 - 'frontpage',
35 - 'privacypolicy',
27 + 'front-page',
28 + 'privacy-policy',
36 29 'page',
37 30 'search',
38 31 'single',
39 32 'singular',
@@ -38,39 +31,93 @@
38 31 'single',
39 32 'singular',
40 33 'attachment',
41 34 );
42 - foreach ( $template_types as $template_type ) {
43 - add_filter( $template_type . '_template', 'gutenberg_override_query_template', 20, 3 );
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 );
44 46 }
45 -
46 - add_filter( 'template_include', 'gutenberg_find_template', 20 );
47 47 }
48 48 add_action( 'wp_loaded', 'gutenberg_add_template_loader_filters' );
49 49
50 50 /**
51 - * Filters into the "{$type}_template" hooks to record the current template hierarchy.
51 + * Get the template hierarchy for a given template type.
52 52 *
53 - * The method returns an empty result for every template so that a 'wp_template' post
54 - * is used instead.
53 + * Internally, this filters into the "{$type}_template_hierarchy" hook to record the type-specific template hierarchy.
55 54 *
56 - * @see gutenberg_find_template
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.
57 81 *
82 + * Internally, this communicates the block content that needs to be used by the template canvas through a global variable.
83 + *
58 84 * @param string $template Path to the template. See locate_template().
59 85 * @param string $type Sanitized filename without extension.
60 86 * @param array $templates A list of template candidates, in descending order of priority.
61 - * @return string Empty string to ensure template file is considered not found.
87 + * @return string The path to the Full Site Editing template canvas file.
62 88 */
63 89 function gutenberg_override_query_template( $template, $type, array $templates = array() ) {
64 - global $_wp_current_template_hierarchy;
90 + global $_wp_current_template_content;
65 91
66 - if ( ! is_array( $_wp_current_template_hierarchy ) ) {
67 - $_wp_current_template_hierarchy = $templates;
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 + }
68 100 } else {
69 - $_wp_current_template_hierarchy = array_merge( $_wp_current_template_hierarchy, $templates );
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 + }
70 108 }
71 109
72 - return '';
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';
73 120 }
74 121
75 122 /**
76 123 * Recursively traverses a block tree, creating auto drafts
@@ -78,54 +125,115 @@
78 125 *
79 126 * @access private
80 127 *
81 128 * @param array $block The root block to start traversing from.
129 + * @return int[] A list of template parts IDs for the given block.
82 130 */
83 131 function create_auto_draft_for_template_part_block( $block ) {
84 - if ( 'core/template-part' === $block['blockName'] && ! isset( $block['attrs']['id'] ) ) {
85 - $template_part_file_path =
86 - get_stylesheet_directory() . '/block-template-parts/' . $block['attrs']['slug'] . '.html';
87 - if ( ! file_exists( $template_part_file_path ) ) {
88 - return;
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 + 'name' => $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 + }
89 198 }
90 - wp_insert_post(
91 - array(
92 - 'post_content' => file_get_contents( $template_part_file_path ),
93 - 'post_title' => ucfirst( $block['attrs']['slug'] ),
94 - 'post_status' => 'auto-draft',
95 - 'post_type' => 'wp_template_part',
96 - 'post_name' => $block['attrs']['slug'],
97 - 'meta_input' => array(
98 - 'theme' => $block['attrs']['theme'],
99 - ),
100 - )
101 - );
199 + $template_part_ids[ $block['attrs']['slug'] ] = $template_part_id;
102 200 }
103 201
104 202 foreach ( $block['innerBlocks'] as $inner_block ) {
105 - create_auto_draft_for_template_part_block( $inner_block );
203 + $template_part_ids = array_merge( $template_part_ids, create_auto_draft_for_template_part_block( $inner_block ) );
106 204 }
205 + return $template_part_ids;
107 206 }
108 207
109 208 /**
110 - * Find the correct 'wp_template' post for the current hierarchy and return the path
111 - * to the canvas file that will render it.
209 + * Return the correct 'wp_template' post and template part IDs for the current template.
112 210 *
113 - * @param string $template_file Original template file. Will be overridden.
114 - * @return string Path to the canvas file to include.
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 + * }
115 219 */
116 -function gutenberg_find_template( $template_file ) {
117 - global $_wp_current_template_id, $_wp_current_template_content, $_wp_current_template_hierarchy;
220 +function gutenberg_find_template_post_and_parts( $template_type, $template_hierarchy = array() ) {
221 + if ( ! $template_type ) {
222 + return null;
223 + }
118 224
119 - // Bail if no relevant template hierarchy was determined, or if the template file
120 - // was overridden another way.
121 - if ( ! $_wp_current_template_hierarchy || $template_file ) {
122 - return $template_file;
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 + }
123 231 }
124 232
125 233 $slugs = array_map(
126 234 'gutenberg_strip_php_suffix',
127 - $_wp_current_template_hierarchy
235 + $template_hierarchy
128 236 );
129 237
130 238 // Find most specific 'wp_template' post matching the hierarchy.
131 239 $template_query = new WP_Query(
@@ -134,13 +242,13 @@
134 242 'post_status' => 'publish',
135 243 'post_name__in' => $slugs,
136 244 'orderby' => 'post_name__in',
137 245 'posts_per_page' => 1,
246 + 'no_found_rows' => true,
138 247 )
139 248 );
140 249
141 - $template_posts = $template_query->get_posts();
142 - $current_template_post = array_shift( $template_posts );
250 + $current_template_post = $template_query->have_posts() ? $template_query->next_post() : null;
143 251
144 252 // Build map of template slugs to their priority in the current hierarchy.
145 253 $slug_priorities = array_flip( $slugs );
146 254
@@ -146,14 +254,19 @@
146 254
147 255 // See if there is a theme block template with higher priority than the resolved template post.
148 256 $higher_priority_block_template_path = null;
149 257 $higher_priority_block_template_priority = PHP_INT_MAX;
150 - $block_template_files = glob( get_stylesheet_directory() . '/block-templates/*.html' ) ?: array();
258 + $block_template_files = glob( get_stylesheet_directory() . '/block-templates/*.html' );
259 + $block_template_files = is_array( $block_template_files ) ? $block_template_files : array();
151 260 if ( is_child_theme() ) {
152 - $block_template_files = array_merge( $block_template_files, glob( get_template_directory() . '/block-templates/*.html' ) ?: array() );
261 + $child_block_template_files = glob( get_template_directory() . '/block-templates/*.html' );
262 + $child_block_template_files = is_array( $child_block_template_files ) ? $child_block_template_files : array();
263 + $block_template_files = array_merge( $block_template_files, $child_block_template_files );
153 264 }
154 265 if ( gutenberg_is_experiment_enabled( 'gutenberg-full-site-editing-demo' ) ) {
155 - $block_template_files = array_merge( $block_template_files, glob( dirname( __FILE__ ) . '/demo-block-templates/*.html' ) ?: array() );
266 + $demo_block_template_files = glob( dirname( __FILE__ ) . '/demo-block-templates/*.html' );
267 + $demo_block_template_files = is_array( $demo_block_template_files ) ? $demo_block_template_files : array();
268 + $block_template_files = array_merge( $block_template_files, $demo_block_template_files );
156 269 }
157 270 foreach ( $block_template_files as $path ) {
158 271 if ( ! isset( $slug_priorities[ basename( $path, '.html' ) ] ) ) {
159 272 continue;
@@ -170,22 +283,39 @@
170 283
171 284 // If there is, use it instead.
172 285 if ( isset( $higher_priority_block_template_path ) ) {
173 286 $post_name = basename( $higher_priority_block_template_path, '.html' );
287 + $file_contents = file_get_contents( $higher_priority_block_template_path );
174 288 $current_template_post = array(
175 - 'post_content' => file_get_contents( $higher_priority_block_template_path ),
176 - 'post_title' => ucfirst( $post_name ),
289 + 'post_content' => $file_contents,
290 + 'post_title' => $post_name,
177 291 'post_status' => 'auto-draft',
178 292 'post_type' => 'wp_template',
179 293 'post_name' => $post_name,
180 294 );
181 - if ( is_admin() ) {
295 + if ( is_admin() || defined( 'REST_REQUEST' ) ) {
296 + $template_query = new WP_Query(
297 + array(
298 + 'post_type' => 'wp_template',
299 + 'post_status' => 'auto-draft',
300 + 'name' => $post_name,
301 + 'posts_per_page' => 1,
302 + 'no_found_rows' => true,
303 + )
304 + );
305 + $current_template_post = $template_query->have_posts() ? $template_query->next_post() : $current_template_post;
306 +
182 307 // Only create auto-draft of block template for editing
183 - // in admin screens, similarly to how we do it for new
184 - // posts in the editor.
185 - $current_template_post = get_post(
186 - wp_insert_post( $current_template_post )
187 - );
308 + // in admin screens, when necessary, because the underlying
309 + // file has changed.
310 + if ( is_array( $current_template_post ) || $current_template_post->post_content !== $file_contents ) {
311 + if ( ! is_array( $current_template_post ) ) {
312 + $current_template_post->post_content = $file_contents;
313 + }
314 + $current_template_post = get_post(
315 + wp_insert_post( $current_template_post )
316 + );
317 + }
188 318 } else {
189 319 $current_template_post = new WP_Post(
190 320 (object) $current_template_post
191 321 );
@@ -191,25 +321,36 @@
191 321 );
192 322 }
193 323 }
194 324
325 + // If we haven't found any template post by here, it means that this theme doesn't even come with a fallback
326 + // `index.html` block template. We create one so that people that are trying to access the editor are greeted
327 + // with a blank page rather than an error.
328 + if ( ! $current_template_post && ( is_admin() || defined( 'REST_REQUEST' ) ) ) {
329 + $current_template_post = array(
330 + 'post_title' => 'index',
331 + 'post_status' => 'auto-draft',
332 + 'post_type' => 'wp_template',
333 + 'post_name' => 'index',
334 + );
335 + $current_template_post = get_post(
336 + wp_insert_post( $current_template_post )
337 + );
338 + }
339 +
195 340 if ( $current_template_post ) {
196 - if ( is_admin() ) {
341 + $template_part_ids = array();
342 + if ( is_admin() || defined( 'REST_REQUEST' ) ) {
197 343 foreach ( parse_blocks( $current_template_post->post_content ) as $block ) {
198 - create_auto_draft_for_template_part_block( $block );
344 + $template_part_ids = array_merge( $template_part_ids, create_auto_draft_for_template_part_block( $block ) );
199 345 }
200 346 }
201 - $_wp_current_template_id = $current_template_post->ID;
202 - $_wp_current_template_content = $current_template_post->post_content;
347 + return array(
348 + 'template_post' => $current_template_post,
349 + 'template_part_ids' => $template_part_ids,
350 + );
203 351 }
204 -
205 - // Add extra hooks for template canvas.
206 - add_action( 'wp_head', 'gutenberg_viewport_meta_tag', 0 );
207 - remove_action( 'wp_head', '_wp_render_title_tag', 1 );
208 - add_action( 'wp_head', 'gutenberg_render_title_tag', 1 );
209 -
210 - // This file will be included instead of the theme's template file.
211 - return gutenberg_dir_path() . 'lib/template-canvas.php';
352 + return null;
212 353 }
213 354
214 355 /**
215 356 * Displays title tag with content, regardless of whether theme has title-tag support.
@@ -235,9 +376,13 @@
235 376 $content = $wp_embed->run_shortcode( $_wp_current_template_content );
236 377 $content = $wp_embed->autoembed( $content );
237 378 $content = do_blocks( $content );
238 379 $content = wptexturize( $content );
239 - $content = wp_make_content_images_responsive( $content );
380 + if ( function_exists( 'wp_filter_content_tags' ) ) {
381 + $content = wp_filter_content_tags( $content );
382 + } else {
383 + $content = wp_make_content_images_responsive( $content );
384 + }
240 385 $content = str_replace( ']]>', ']]>', $content );
241 386
242 387 // Wrap block template in .wp-site-blocks to allow for specific descendant styles
243 388 // (e.g. `.wp-site-blocks > *`).
@@ -274,14 +419,23 @@
274 419 *
275 420 * @return array Filtered editor settings.
276 421 */
277 422 function gutenberg_template_loader_filter_block_editor_settings( $settings ) {
278 - if ( ! post_type_exists( 'wp_template' ) || ! post_type_exists( 'wp_template_part' ) ) {
423 + global $post;
424 +
425 + if ( ! $post ) {
279 426 return $settings;
280 427 }
281 428
282 - // Create template part auto-drafts for the edited post.
283 - foreach ( parse_blocks( get_post()->post_content ) as $block ) {
429 + // If this is the Site Editor, auto-drafts for template parts have already been generated
430 + // through `filter_rest_wp_template_part_query`, when called via the REST API.
431 + if ( isset( $settings['editSiteInitialState'] ) ) {
432 + return $settings;
433 + }
434 +
435 + // Otherwise, create template part auto-drafts for the edited post.
436 + $post = get_post();
437 + foreach ( parse_blocks( $post->post_content ) as $block ) {
284 438 create_auto_draft_for_template_part_block( $block );
285 439 }
286 440
287 441 // TODO: Set editing mode and current template ID for editing modes support.
@@ -287,4 +441,29 @@
287 441 // TODO: Set editing mode and current template ID for editing modes support.
288 442 return $settings;
289 443 }
290 444 add_filter( 'block_editor_settings', 'gutenberg_template_loader_filter_block_editor_settings' );
445 +
446 +/**
447 + * Removes post details from block context when rendering a block template.
448 + *
449 + * @param array $context Default context.
450 + *
451 + * @return array Filtered context.
452 + */
453 +function gutenberg_template_render_without_post_block_context( $context ) {
454 + /*
455 + * When loading a template or template part directly and not through a page
456 + * that resolves it, the top-level post ID and type context get set to that
457 + * of the template part. Templates are just the structure of a site, and
458 + * they should not be available as post context because blocks like Post
459 + * Content would recurse infinitely.
460 + */
461 + if ( isset( $context['postType'] ) &&
462 + ( 'wp_template' === $context['postType'] || 'wp_template_part' === $context['postType'] ) ) {
463 + unset( $context['postId'] );
464 + unset( $context['postType'] );
465 + }
466 +
467 + return $context;
468 +}
469 +add_filter( 'render_block_context', 'gutenberg_template_render_without_post_block_context' );