PluginProbe
Gutenberg / 14.7.0
Gutenberg v14.7.0
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
gutenberg / lib / compat / wordpress-6.1 / block-template-utils.php

block-template-utils.php in Gutenberg 14.7.0, at lib/compat/wordpress-6.1/block-template-utils.php

626 lines 20.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Temporary compatibility shims for features present in Gutenberg.
4 * This file should be removed when WordPress 6.1.0 becomes the lowest
5 * supported version by this plugin.
6 *
7 * @package gutenberg
8 */
9
10 /**
11 * Updates the list of default template types, containing their
12 * localized titles and descriptions.
13 *
14 * We will only need to update `get_default_block_template_types` function.
15 *
16 * @param array $default_template_types The default template types.
17 *
18 * @return array The default template types.
19 */
20 function gutenberg_get_default_block_template_types( $default_template_types ) {
21 if ( isset( $default_template_types['single'] ) ) {
22 $default_template_types['single'] = array(
23 'title' => _x( 'Single', 'Template name', 'gutenberg' ),
24 'description' => __( 'The default template for displaying any single post or attachment.', 'gutenberg' ),
25 );
26 }
27 if ( isset( $default_template_types['category'] ) ) {
28 $default_template_types['category'] = array(
29 'title' => _x( 'Category', 'Template name', 'gutenberg' ),
30 'description' => __( 'Displays latest posts from a single post category.', 'gutenberg' ),
31 );
32 }
33 return $default_template_types;
34 }
35 add_filter( 'default_template_types', 'gutenberg_get_default_block_template_types', 10 );
36
37
38 /**
39 * Retrieves a list of unified template objects based on a query.
40 *
41 * @param array $query {
42 * Optional. Arguments to retrieve templates.
43 *
44 * @type array $slug__in List of slugs to include.
45 * @type int $wp_id Post ID of customized template.
46 * @type string $area A 'wp_template_part_area' taxonomy value to filter by (for wp_template_part template type only).
47 * @type string $post_type Post type to get the templates for.
48 * }
49 * @param array $template_type wp_template or wp_template_part.
50 *
51 * @return array Templates.
52 */
53 function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_template' ) {
54 /**
55 * Filters the block templates array before the query takes place.
56 *
57 * Return a non-null value to bypass the WordPress queries.
58 *
59 * @since 10.8
60 *
61 * @param Gutenberg_Block_Template[]|null $block_templates Return an array of block templates to short-circuit the default query,
62 * or null to allow WP to run it's normal queries.
63 * @param array $query {
64 * Optional. Arguments to retrieve templates.
65 *
66 * @type array $slug__in List of slugs to include.
67 * @type int $wp_id Post ID of customized template.
68 * @type string $post_type Post type to get the templates for.
69 * }
70 * @param array $template_type wp_template or wp_template_part.
71 */
72 $templates = apply_filters( 'pre_get_block_templates', null, $query, $template_type );
73 if ( ! is_null( $templates ) ) {
74 return $templates;
75 }
76
77 $post_type = isset( $query['post_type'] ) ? $query['post_type'] : '';
78 $wp_query_args = array(
79 'post_status' => array( 'auto-draft', 'draft', 'publish' ),
80 'post_type' => $template_type,
81 'posts_per_page' => -1,
82 'no_found_rows' => true,
83 'tax_query' => array(
84 array(
85 'taxonomy' => 'wp_theme',
86 'field' => 'name',
87 'terms' => get_stylesheet(),
88 ),
89 ),
90 );
91
92 if ( 'wp_template_part' === $template_type && isset( $query['area'] ) ) {
93 $wp_query_args['tax_query'][] = array(
94 'taxonomy' => 'wp_template_part_area',
95 'field' => 'name',
96 'terms' => $query['area'],
97 );
98 $wp_query_args['tax_query']['relation'] = 'AND';
99 }
100
101 if ( isset( $query['slug__in'] ) ) {
102 $wp_query_args['post_name__in'] = $query['slug__in'];
103 }
104
105 // This is only needed for the regular templates/template parts CPT listing and editor.
106 if ( isset( $query['wp_id'] ) ) {
107 $wp_query_args['p'] = $query['wp_id'];
108 } else {
109 $wp_query_args['post_status'] = 'publish';
110 }
111
112 $template_query = new WP_Query( $wp_query_args );
113 $query_result = array();
114 foreach ( $template_query->posts as $post ) {
115 $template = gutenberg_build_block_template_result_from_post( $post );
116 if ( is_wp_error( $template ) ) {
117 continue;
118 }
119
120 if ( $post_type && ! $template->is_custom ) {
121 continue;
122 }
123
124 if ( $post_type &&
125 isset( $template->post_types ) &&
126 ! in_array( $post_type, $template->post_types, true )
127 ) {
128 continue;
129 }
130
131 $query_result[] = $template;
132 }
133 if ( ! isset( $query['wp_id'] ) ) {
134 $template_files = _get_block_templates_files( $template_type );
135 foreach ( $template_files as $template_file ) {
136 $template = _build_block_template_result_from_file( $template_file, $template_type );
137
138 if ( $post_type && ! $template->is_custom ) {
139 continue;
140 }
141
142 if ( $post_type &&
143 isset( $template->post_types ) &&
144 ! in_array( $post_type, $template->post_types, true )
145 ) {
146 continue;
147 }
148
149 $is_not_custom = false === array_search(
150 wp_get_theme()->get_stylesheet() . '//' . $template_file['slug'],
151 array_column( $query_result, 'id' ),
152 true
153 );
154 $fits_slug_query =
155 ! isset( $query['slug__in'] ) || in_array( $template_file['slug'], $query['slug__in'], true );
156 $fits_area_query =
157 ! isset( $query['area'] ) || $template_file['area'] === $query['area'];
158 $should_include = $is_not_custom && $fits_slug_query && $fits_area_query;
159 if ( $should_include ) {
160 $query_result[] = $template;
161 }
162 }
163 }
164 /**
165 * Filters the array of queried block templates array after they've been fetched.
166 *
167 * @since 10.8
168 *
169 * @param Gutenberg_Block_Template[] $query_result Array of found block templates.
170 * @param array $query {
171 * Optional. Arguments to retrieve templates.
172 *
173 * @type array $slug__in List of slugs to include.
174 * @type int $wp_id Post ID of customized template.
175 * }
176 * @param array $template_type wp_template or wp_template_part.
177 */
178 return apply_filters( 'get_block_templates', $query_result, $query, $template_type );
179 }
180
181 /**
182 * Retrieves a single unified template object using its id.
183 *
184 * @param string $id Template unique identifier (example: theme_slug//template_slug).
185 * @param array $template_type wp_template or wp_template_part.
186 *
187 * @return Gutenberg_Block_Template|null Template.
188 */
189 function gutenberg_get_block_template( $id, $template_type = 'wp_template' ) {
190 /**
191 * Filters the block template object before the query takes place.
192 *
193 * Return a non-null value to bypass the WordPress queries.
194 *
195 * @since 10.8
196 *
197 * @param Gutenberg_Block_Template|null $block_template Return block template object to short-circuit the default query,
198 * or null to allow WP to run it's normal queries.
199 * @param string $id Template unique identifier (example: theme_slug//template_slug).
200 * @param array $template_type wp_template or wp_template_part.
201 */
202 $block_template = apply_filters( 'pre_get_block_template', null, $id, $template_type );
203 if ( ! is_null( $block_template ) ) {
204 return $block_template;
205 }
206
207 $parts = explode( '//', $id, 2 );
208 if ( count( $parts ) < 2 ) {
209 return null;
210 }
211 list( $theme, $slug ) = $parts;
212 $wp_query_args = array(
213 'post_name__in' => array( $slug ),
214 'post_type' => $template_type,
215 'post_status' => array( 'auto-draft', 'draft', 'publish', 'trash' ),
216 'posts_per_page' => 1,
217 'no_found_rows' => true,
218 'tax_query' => array(
219 array(
220 'taxonomy' => 'wp_theme',
221 'field' => 'name',
222 'terms' => $theme,
223 ),
224 ),
225 );
226 $template_query = new WP_Query( $wp_query_args );
227 $posts = $template_query->posts;
228
229 if ( count( $posts ) > 0 ) {
230 $template = gutenberg_build_block_template_result_from_post( $posts[0] );
231
232 if ( ! is_wp_error( $template ) ) {
233 return $template;
234 }
235 }
236
237 $block_template = get_block_file_template( $id, $template_type );
238
239 /**
240 * Filters the queried block template object after it's been fetched.
241 *
242 * @since 10.8
243 *
244 * @param Gutenberg_Block_Template|null $block_template The found block template, or null if there isn't one.
245 * @param string $id Template unique identifier (example: theme_slug//template_slug).
246 * @param array $template_type wp_template or wp_template_part.
247 */
248 return apply_filters( 'get_block_template', $block_template, $id, $template_type );
249 }
250
251 /**
252 * Builds the title and description of a post-specific template based on the underlying referenced post.
253 * Mutates the underlying template object.
254 *
255 * @since 6.1.0
256 * @access private
257 * @internal
258 *
259 * @param string $post_type Post type e.g.: page, post, product.
260 * @param string $slug Slug of the post e.g.: a-story-about-shoes.
261 * @param WP_Block_Template $template Template to mutate adding the description and title computed.
262 * @return boolean Returns true if the referenced post was found and false otherwise.
263 */
264 function _gutenberg_build_title_and_description_for_single_post_type_block_template( $post_type, $slug, WP_Block_Template $template ) {
265 $post_type_object = get_post_type_object( $post_type );
266
267 $default_args = array(
268 'post_type' => $post_type,
269 'post_status' => 'publish',
270 'posts_per_page' => 1,
271 'update_post_meta_cache' => false,
272 'update_post_term_cache' => false,
273 'ignore_sticky_posts' => true,
274 'no_found_rows' => true,
275 );
276
277 $args = array(
278 'name' => $slug,
279 );
280 $args = wp_parse_args( $args, $default_args );
281
282 $posts_query = new WP_Query( $args );
283
284 if ( empty( $posts_query->posts ) ) {
285 $template->title = sprintf(
286 /* translators: Custom template title in the Site Editor referencing a post that was not found. 1: Post type singular name, 2: Post type slug. */
287 __( 'Not found: %1$s (%2$s)', 'gutenberg' ),
288 $post_type_object->labels->singular_name,
289 $slug
290 );
291
292 return false;
293 }
294
295 $post_title = $posts_query->posts[0]->post_title;
296
297 $template->title = sprintf(
298 /* translators: Custom template title in the Site Editor. 1: Post type singular name, 2: Post title. */
299 __( '%1$s: %2$s', 'gutenberg' ),
300 $post_type_object->labels->singular_name,
301 $post_title
302 );
303
304 $template->description = sprintf(
305 /* translators: Custom template description in the Site Editor. %s: Post title. */
306 __( 'Template for %s', 'gutenberg' ),
307 $post_title
308 );
309
310 $args = array(
311 'title' => $post_title,
312 );
313 $args = wp_parse_args( $args, $default_args );
314
315 $posts_with_same_title_query = new WP_Query( $args );
316
317 if ( count( $posts_with_same_title_query->posts ) > 1 ) {
318 $template->title = sprintf(
319 /* translators: Custom template title in the Site Editor. 1: Template title, 2: Post type slug. */
320 __( '%1$s (%2$s)', 'gutenberg' ),
321 $template->title,
322 $slug
323 );
324 }
325
326 return true;
327 }
328
329 /**
330 * Builds the title and description of a taxonomy-specific template based on the underlying entity referenced.
331 * Mutates the underlying template object.
332 *
333 * @access private
334 * @internal
335 *
336 * @param string $taxonomy Identifier of the taxonomy, e.g.: category.
337 * @param string $slug Slug of the term, e.g.: shoes.
338 * @param WP_Block_Template $template Template to mutate adding the description and title computed.
339 *
340 * @return boolean True if the term referenced was found and false otherwise.
341 */
342 function _gutenberg_build_title_and_description_for_taxonomy_block_template( $taxonomy, $slug, WP_Block_Template $template ) {
343 $taxonomy_object = get_taxonomy( $taxonomy );
344
345 $default_args = array(
346 'taxonomy' => $taxonomy,
347 'hide_empty' => false,
348 'update_term_meta_cache' => false,
349 );
350
351 $term_query = new WP_Term_Query();
352
353 $args = array(
354 'number' => 1,
355 'slug' => $slug,
356 );
357 $args = wp_parse_args( $args, $default_args );
358
359 $terms_query = $term_query->query( $args );
360
361 if ( empty( $terms_query ) ) {
362 $template->title = sprintf(
363 /* translators: Custom template title in the Site Editor, referencing a taxonomy term that was not found. 1: Taxonomy singular name, 2: Term slug. */
364 __( 'Not found: %1$s (%2$s)', 'gutenberg' ),
365 $taxonomy_object->labels->singular_name,
366 $slug
367 );
368 return false;
369 }
370
371 $term_title = $terms_query[0]->name;
372
373 $template->title = sprintf(
374 /* translators: Custom template title in the Site Editor. 1: Taxonomy singular name, 2: Term title. */
375 __( '%1$s: %2$s', 'gutenberg' ),
376 $taxonomy_object->labels->singular_name,
377 $term_title
378 );
379
380 $template->description = sprintf(
381 /* translators: Custom template description in the Site Editor. %s: Term title. */
382 __( 'Template for %s', 'gutenberg' ),
383 $term_title
384 );
385
386 $term_query = new WP_Term_Query();
387
388 $args = array(
389 'number' => 2,
390 'name' => $term_title,
391 );
392 $args = wp_parse_args( $args, $default_args );
393
394 $terms_with_same_title_query = $term_query->query( $args );
395
396 if ( count( $terms_with_same_title_query ) > 1 ) {
397 $template->title = sprintf(
398 /* translators: Custom template title in the Site Editor. 1: Template title, 2: Term slug. */
399 __( '%1$s (%2$s)', 'gutenberg' ),
400 $template->title,
401 $slug
402 );
403 }
404
405 return true;
406 }
407
408 /**
409 * Build a unified template object based a post Object.
410 *
411 * @param WP_Post $post Template post.
412 *
413 * @return Gutenberg_Block_Template|WP_Error Template.
414 */
415 function gutenberg_build_block_template_result_from_post( $post ) {
416 $default_template_types = get_default_block_template_types();
417 $terms = get_the_terms( $post, 'wp_theme' );
418
419 if ( is_wp_error( $terms ) ) {
420 return $terms;
421 }
422
423 if ( ! $terms ) {
424 return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.', 'gutenberg' ) );
425 }
426
427 $origin = get_post_meta( $post->ID, 'origin', true );
428 $is_wp_suggestion = get_post_meta( $post->ID, 'is_wp_suggestion', true );
429
430 $theme = $terms[0]->name;
431 $template_file = _get_block_template_file( $post->post_type, $post->post_name );
432 $has_theme_file = get_stylesheet() === $theme && null !== $template_file;
433
434 $template = new WP_Block_Template();
435 $template->wp_id = $post->ID;
436 $template->id = $theme . '//' . $post->post_name;
437 $template->theme = $theme;
438 $template->content = $post->post_content;
439 $template->slug = $post->post_name;
440 $template->source = 'custom';
441 $template->origin = ! empty( $origin ) ? $origin : null;
442 $template->type = $post->post_type;
443 $template->description = $post->post_excerpt;
444 $template->title = $post->post_title;
445 $template->status = $post->post_status;
446 $template->has_theme_file = $has_theme_file;
447 $template->is_custom = empty( $is_wp_suggestion );
448 $template->author = $post->post_author;
449
450 // We keep this check for existent templates that are part of the template hierarchy.
451 if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) {
452 $template->is_custom = false;
453 }
454
455 if ( 'wp_template' === $post->post_type && $has_theme_file && isset( $template_file['postTypes'] ) ) {
456 $template->post_types = $template_file['postTypes'];
457 }
458
459 if ( 'wp_template_part' === $post->post_type ) {
460 $type_terms = get_the_terms( $post, 'wp_template_part_area' );
461 if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) {
462 $template->area = $type_terms[0]->name;
463 }
464 }
465 // If it is a block template without description and without title or with title equal to the slug.
466 if ( 'wp_template' === $post->post_type && empty( $template->description ) && ( empty( $template->title ) || $template->title === $template->slug ) ) {
467 $matches = array();
468 // If it is a block template for a single author, page, post, tag, category, custom post type or custom taxonomy.
469 if ( preg_match( '/(author|page|single|tag|category|taxonomy)-(.+)/', $template->slug, $matches ) ) {
470 $type = $matches[1];
471 $slug_remaining = $matches[2];
472 switch ( $type ) {
473 case 'author':
474 $nice_name = $slug_remaining;
475 $users = get_users(
476 array(
477 'capability' => 'edit_posts',
478 'search' => $nice_name,
479 'search_columns' => array( 'user_nicename' ),
480 'fields' => 'display_name',
481 )
482 );
483
484 if ( empty( $users ) ) {
485 $template->title = sprintf(
486 // translators: Represents the title of a user's custom template in the Site Editor referencing a deleted author, where %s is the author's nicename, e.g. "Deleted author: jane-doe".
487 __( 'Deleted author: %s', 'gutenberg' ),
488 $nice_name
489 );
490 } else {
491 $author_name = $users[0];
492
493 $template->title = sprintf(
494 // translators: Represents the title of a user's custom template in the Site Editor, where %s is the author's name, e.g. "Author: Jane Doe".
495 __( 'Author: %s', 'gutenberg' ),
496 $author_name
497 );
498 $template->description = sprintf(
499 // translators: Represents the description of a user's custom template in the Site Editor, e.g. "Template for Author: Jane Doe".
500 __( 'Template for %1$s', 'gutenberg' ),
501 $author_name
502 );
503
504 $users_with_same_name = get_users(
505 array(
506 'capability' => 'edit_posts',
507 'search' => $author_name,
508 'search_columns' => array( 'display_name' ),
509 'fields' => 'display_name',
510 )
511 );
512 if ( count( $users_with_same_name ) > 1 ) {
513 $template->title = sprintf(
514 // translators: Represents the title of a user's custom template in the Site Editor, where %1$s is the template title of an author template and %2$s is the nicename of the author, e.g. "Author: Jane Doe (jane-doe)".
515 __( '%1$s (%2$s)', 'gutenberg' ),
516 $template->title,
517 $nice_name
518 );
519 }
520 }
521 break;
522 case 'page':
523 _gutenberg_build_title_and_description_for_single_post_type_block_template( 'page', $slug_remaining, $template );
524 break;
525 case 'single':
526 $post_types = get_post_types();
527 foreach ( $post_types as $post_type ) {
528 $post_type_length = strlen( $post_type ) + 1;
529 // If $slug_remaining starts with $post_type followed by a hyphen.
530 if ( 0 === strncmp( $slug_remaining, $post_type . '-', $post_type_length ) ) {
531 $slug = substr( $slug_remaining, $post_type_length, strlen( $slug_remaining ) );
532 $found = _gutenberg_build_title_and_description_for_single_post_type_block_template( $post_type, $slug, $template );
533 if ( $found ) {
534 break;
535 }
536 }
537 }
538 break;
539 case 'tag':
540 _gutenberg_build_title_and_description_for_taxonomy_block_template( 'post_tag', $slug_remaining, $template );
541 break;
542 case 'category':
543 _gutenberg_build_title_and_description_for_taxonomy_block_template( 'category', $slug_remaining, $template );
544 break;
545 case 'taxonomy':
546 $taxonomies = get_taxonomies();
547 foreach ( $taxonomies as $taxonomy ) {
548 $taxonomy_length = strlen( $taxonomy ) + 1;
549 // If $slug_remaining starts with $taxonomy followed by a hyphen.
550 if ( 0 === strncmp( $slug_remaining, $taxonomy . '-', $taxonomy_length ) ) {
551 $slug = substr( $slug_remaining, $taxonomy_length, strlen( $slug_remaining ) );
552 $found = _gutenberg_build_title_and_description_for_taxonomy_block_template( $taxonomy, $slug, $template );
553 if ( $found ) {
554 break;
555 }
556 }
557 }
558 break;
559 }
560 }
561 }
562 return $template;
563 }
564
565 if ( ! function_exists( 'get_template_hierarchy' ) ) {
566 /**
567 * Helper function to get the Template Hierarchy for a given slug.
568 * We need to Handle special cases here like `front-page`, `singular` and `archive` templates.
569 *
570 * Noting that we always add `index` as the last fallback template.
571 *
572 * @param string $slug The template slug to be created.
573 * @param boolean $is_custom Indicates if a template is custom or part of the template hierarchy.
574 * @param string $template_prefix The template prefix for the created template. This is used to extract the main template type ex. in `taxonomy-books` we extract the `taxonomy`.
575 *
576 * @return array<string> The template hierarchy.
577 */
578 function get_template_hierarchy( $slug, $is_custom = false, $template_prefix = '' ) {
579 if ( 'index' === $slug ) {
580 return array( 'index' );
581 }
582 if ( $is_custom ) {
583 return array( 'page', 'singular', 'index' );
584 }
585 if ( 'front-page' === $slug ) {
586 return array( 'front-page', 'home', 'index' );
587 }
588 $template_hierarchy = array( $slug );
589 // Most default templates don't have `$template_prefix` assigned.
590 if ( $template_prefix ) {
591 list($type) = explode( '-', $template_prefix );
592 // We need these checks because we always add the `$slug` above.
593 if ( ! in_array( $template_prefix, array( $slug, $type ), true ) ) {
594 $template_hierarchy[] = $template_prefix;
595 }
596 if ( $slug !== $type ) {
597 $template_hierarchy[] = $type;
598 }
599 }
600 // Handle `archive` template.
601 if (
602 str_starts_with( $slug, 'author' ) ||
603 str_starts_with( $slug, 'taxonomy' ) ||
604 str_starts_with( $slug, 'category' ) ||
605 str_starts_with( $slug, 'tag' ) ||
606 'date' === $slug
607 ) {
608 $template_hierarchy[] = 'archive';
609 }
610 // Handle `single` template.
611 if ( 'attachment' === $slug ) {
612 $template_hierarchy[] = 'single';
613 }
614 // Handle `singular` template.
615 if (
616 str_starts_with( $slug, 'single' ) ||
617 str_starts_with( $slug, 'page' ) ||
618 'attachment' === $slug
619 ) {
620 $template_hierarchy[] = 'singular';
621 }
622 $template_hierarchy[] = 'index';
623 return $template_hierarchy;
624 }
625 }
626