1 <?php |
1 <?php |
2 /** |
2 /** |
3 * Utilities used to fetch and create templates. |
3 * Utilities used to fetch and create templates and template parts. |
4 * |
4 * |
5 * @package WordPress |
5 * @package WordPress |
6 * @since 5.8.0 |
6 * @since 5.8.0 |
7 */ |
7 */ |
8 |
8 |
|
9 // Define constants for supported wp_template_part_area taxonomy. |
|
10 if ( ! defined( 'WP_TEMPLATE_PART_AREA_HEADER' ) ) { |
|
11 define( 'WP_TEMPLATE_PART_AREA_HEADER', 'header' ); |
|
12 } |
|
13 if ( ! defined( 'WP_TEMPLATE_PART_AREA_FOOTER' ) ) { |
|
14 define( 'WP_TEMPLATE_PART_AREA_FOOTER', 'footer' ); |
|
15 } |
|
16 if ( ! defined( 'WP_TEMPLATE_PART_AREA_SIDEBAR' ) ) { |
|
17 define( 'WP_TEMPLATE_PART_AREA_SIDEBAR', 'sidebar' ); |
|
18 } |
|
19 if ( ! defined( 'WP_TEMPLATE_PART_AREA_UNCATEGORIZED' ) ) { |
|
20 define( 'WP_TEMPLATE_PART_AREA_UNCATEGORIZED', 'uncategorized' ); |
|
21 } |
|
22 |
|
23 /** |
|
24 * For backward compatibility reasons, |
|
25 * block themes might be using block-templates or block-template-parts, |
|
26 * this function ensures we fallback to these folders properly. |
|
27 * |
|
28 * @since 5.9.0 |
|
29 * |
|
30 * @param string $theme_stylesheet The stylesheet. Default is to leverage the main theme root. |
|
31 * |
|
32 * @return string[] { |
|
33 * Folder names used by block themes. |
|
34 * |
|
35 * @type string $wp_template Theme-relative directory name for block templates. |
|
36 * @type string $wp_template_part Theme-relative directory name for block template parts. |
|
37 * } |
|
38 */ |
|
39 function get_block_theme_folders( $theme_stylesheet = null ) { |
|
40 $theme_name = null === $theme_stylesheet ? get_stylesheet() : $theme_stylesheet; |
|
41 $root_dir = get_theme_root( $theme_name ); |
|
42 $theme_dir = "$root_dir/$theme_name"; |
|
43 |
|
44 if ( file_exists( $theme_dir . '/block-templates' ) || file_exists( $theme_dir . '/block-template-parts' ) ) { |
|
45 return array( |
|
46 'wp_template' => 'block-templates', |
|
47 'wp_template_part' => 'block-template-parts', |
|
48 ); |
|
49 } |
|
50 |
|
51 return array( |
|
52 'wp_template' => 'templates', |
|
53 'wp_template_part' => 'parts', |
|
54 ); |
|
55 } |
|
56 |
|
57 /** |
|
58 * Returns a filtered list of allowed area values for template parts. |
|
59 * |
|
60 * @since 5.9.0 |
|
61 * |
|
62 * @return array The supported template part area values. |
|
63 */ |
|
64 function get_allowed_block_template_part_areas() { |
|
65 $default_area_definitions = array( |
|
66 array( |
|
67 'area' => WP_TEMPLATE_PART_AREA_UNCATEGORIZED, |
|
68 'label' => __( 'General' ), |
|
69 'description' => __( |
|
70 'General templates often perform a specific role like displaying post content, and are not tied to any particular area.' |
|
71 ), |
|
72 'icon' => 'layout', |
|
73 'area_tag' => 'div', |
|
74 ), |
|
75 array( |
|
76 'area' => WP_TEMPLATE_PART_AREA_HEADER, |
|
77 'label' => __( 'Header' ), |
|
78 'description' => __( |
|
79 'The Header template defines a page area that typically contains a title, logo, and main navigation.' |
|
80 ), |
|
81 'icon' => 'header', |
|
82 'area_tag' => 'header', |
|
83 ), |
|
84 array( |
|
85 'area' => WP_TEMPLATE_PART_AREA_FOOTER, |
|
86 'label' => __( 'Footer' ), |
|
87 'description' => __( |
|
88 'The Footer template defines a page area that typically contains site credits, social links, or any other combination of blocks.' |
|
89 ), |
|
90 'icon' => 'footer', |
|
91 'area_tag' => 'footer', |
|
92 ), |
|
93 ); |
|
94 |
|
95 /** |
|
96 * Filters the list of allowed template part area values. |
|
97 * |
|
98 * @since 5.9.0 |
|
99 * |
|
100 * @param array $default_area_definitions An array of supported area objects. |
|
101 */ |
|
102 return apply_filters( 'default_wp_template_part_areas', $default_area_definitions ); |
|
103 } |
|
104 |
|
105 |
|
106 /** |
|
107 * Returns a filtered list of default template types, containing their |
|
108 * localized titles and descriptions. |
|
109 * |
|
110 * @since 5.9.0 |
|
111 * |
|
112 * @return array The default template types. |
|
113 */ |
|
114 function get_default_block_template_types() { |
|
115 $default_template_types = array( |
|
116 'index' => array( |
|
117 'title' => _x( 'Index', 'Template name' ), |
|
118 'description' => __( 'Displays posts.' ), |
|
119 ), |
|
120 'home' => array( |
|
121 'title' => _x( 'Home', 'Template name' ), |
|
122 'description' => __( 'Displays posts on the homepage, or on the Posts page if a static homepage is set.' ), |
|
123 ), |
|
124 'front-page' => array( |
|
125 'title' => _x( 'Front Page', 'Template name' ), |
|
126 'description' => __( 'Displays the homepage.' ), |
|
127 ), |
|
128 'singular' => array( |
|
129 'title' => _x( 'Singular', 'Template name' ), |
|
130 'description' => __( 'Displays a single post or page.' ), |
|
131 ), |
|
132 'single' => array( |
|
133 'title' => _x( 'Single Post', 'Template name' ), |
|
134 'description' => __( 'Displays a single post.' ), |
|
135 ), |
|
136 'page' => array( |
|
137 'title' => _x( 'Page', 'Template name' ), |
|
138 'description' => __( 'Displays a single page.' ), |
|
139 ), |
|
140 'archive' => array( |
|
141 'title' => _x( 'Archive', 'Template name' ), |
|
142 'description' => __( 'Displays post categories, tags, and other archives.' ), |
|
143 ), |
|
144 'author' => array( |
|
145 'title' => _x( 'Author', 'Template name' ), |
|
146 'description' => __( 'Displays latest posts written by a single author.' ), |
|
147 ), |
|
148 'category' => array( |
|
149 'title' => _x( 'Category', 'Template name' ), |
|
150 'description' => __( 'Displays latest posts in single post category.' ), |
|
151 ), |
|
152 'taxonomy' => array( |
|
153 'title' => _x( 'Taxonomy', 'Template name' ), |
|
154 'description' => __( 'Displays latest posts from a single post taxonomy.' ), |
|
155 ), |
|
156 'date' => array( |
|
157 'title' => _x( 'Date', 'Template name' ), |
|
158 'description' => __( 'Displays posts from a specific date.' ), |
|
159 ), |
|
160 'tag' => array( |
|
161 'title' => _x( 'Tag', 'Template name' ), |
|
162 'description' => __( 'Displays latest posts with a single post tag.' ), |
|
163 ), |
|
164 'attachment' => array( |
|
165 'title' => __( 'Media' ), |
|
166 'description' => __( 'Displays individual media items or attachments.' ), |
|
167 ), |
|
168 'search' => array( |
|
169 'title' => _x( 'Search', 'Template name' ), |
|
170 'description' => __( 'Displays search results.' ), |
|
171 ), |
|
172 'privacy-policy' => array( |
|
173 'title' => __( 'Privacy Policy' ), |
|
174 'description' => __( 'Displays the privacy policy page.' ), |
|
175 ), |
|
176 '404' => array( |
|
177 'title' => _x( '404', 'Template name' ), |
|
178 'description' => __( 'Displays when no content is found.' ), |
|
179 ), |
|
180 ); |
|
181 |
|
182 /** |
|
183 * Filters the list of template types. |
|
184 * |
|
185 * @since 5.9.0 |
|
186 * |
|
187 * @param array $default_template_types An array of template types, formatted as [ slug => [ title, description ] ]. |
|
188 */ |
|
189 return apply_filters( 'default_template_types', $default_template_types ); |
|
190 } |
|
191 |
|
192 /** |
|
193 * Checks whether the input 'area' is a supported value. |
|
194 * Returns the input if supported, otherwise returns the 'uncategorized' value. |
|
195 * |
|
196 * @since 5.9.0 |
|
197 * @access private |
|
198 * |
|
199 * @param string $type Template part area name. |
|
200 * |
|
201 * @return string Input if supported, else the uncategorized value. |
|
202 */ |
|
203 function _filter_block_template_part_area( $type ) { |
|
204 $allowed_areas = array_map( |
|
205 static function ( $item ) { |
|
206 return $item['area']; |
|
207 }, |
|
208 get_allowed_block_template_part_areas() |
|
209 ); |
|
210 if ( in_array( $type, $allowed_areas, true ) ) { |
|
211 return $type; |
|
212 } |
|
213 |
|
214 $warning_message = sprintf( |
|
215 /* translators: %1$s: Template area type, %2$s: the uncategorized template area value. */ |
|
216 __( '"%1$s" is not a supported wp_template_part area value and has been added as "%2$s".' ), |
|
217 $type, |
|
218 WP_TEMPLATE_PART_AREA_UNCATEGORIZED |
|
219 ); |
|
220 trigger_error( $warning_message, E_USER_NOTICE ); |
|
221 return WP_TEMPLATE_PART_AREA_UNCATEGORIZED; |
|
222 } |
|
223 |
|
224 /** |
|
225 * Finds all nested template part file paths in a theme's directory. |
|
226 * |
|
227 * @since 5.9.0 |
|
228 * @access private |
|
229 * |
|
230 * @param string $base_directory The theme's file path. |
|
231 * @return array A list of paths to all template part files. |
|
232 */ |
|
233 function _get_block_templates_paths( $base_directory ) { |
|
234 $path_list = array(); |
|
235 if ( file_exists( $base_directory ) ) { |
|
236 $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) ); |
|
237 $nested_html_files = new RegexIterator( $nested_files, '/^.+\.html$/i', RecursiveRegexIterator::GET_MATCH ); |
|
238 foreach ( $nested_html_files as $path => $file ) { |
|
239 $path_list[] = $path; |
|
240 } |
|
241 } |
|
242 return $path_list; |
|
243 } |
|
244 |
|
245 /** |
|
246 * Retrieves the template file from the theme for a given slug. |
|
247 * |
|
248 * @since 5.9.0 |
|
249 * @access private |
|
250 * |
|
251 * @param string $template_type 'wp_template' or 'wp_template_part'. |
|
252 * @param string $slug Template slug. |
|
253 * |
|
254 * @return array|null Template. |
|
255 */ |
|
256 function _get_block_template_file( $template_type, $slug ) { |
|
257 if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) { |
|
258 return null; |
|
259 } |
|
260 |
|
261 $themes = array( |
|
262 get_stylesheet() => get_stylesheet_directory(), |
|
263 get_template() => get_template_directory(), |
|
264 ); |
|
265 foreach ( $themes as $theme_slug => $theme_dir ) { |
|
266 $template_base_paths = get_block_theme_folders( $theme_slug ); |
|
267 $file_path = $theme_dir . '/' . $template_base_paths[ $template_type ] . '/' . $slug . '.html'; |
|
268 if ( file_exists( $file_path ) ) { |
|
269 $new_template_item = array( |
|
270 'slug' => $slug, |
|
271 'path' => $file_path, |
|
272 'theme' => $theme_slug, |
|
273 'type' => $template_type, |
|
274 ); |
|
275 |
|
276 if ( 'wp_template_part' === $template_type ) { |
|
277 return _add_block_template_part_area_info( $new_template_item ); |
|
278 } |
|
279 |
|
280 if ( 'wp_template' === $template_type ) { |
|
281 return _add_block_template_info( $new_template_item ); |
|
282 } |
|
283 |
|
284 return $new_template_item; |
|
285 } |
|
286 } |
|
287 |
|
288 return null; |
|
289 } |
|
290 |
|
291 /** |
|
292 * Retrieves the template files from the theme. |
|
293 * |
|
294 * @since 5.9.0 |
|
295 * @access private |
|
296 * |
|
297 * @param string $template_type 'wp_template' or 'wp_template_part'. |
|
298 * |
|
299 * @return array Template. |
|
300 */ |
|
301 function _get_block_templates_files( $template_type ) { |
|
302 if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) { |
|
303 return null; |
|
304 } |
|
305 |
|
306 $themes = array( |
|
307 get_stylesheet() => get_stylesheet_directory(), |
|
308 get_template() => get_template_directory(), |
|
309 ); |
|
310 $template_files = array(); |
|
311 foreach ( $themes as $theme_slug => $theme_dir ) { |
|
312 $template_base_paths = get_block_theme_folders( $theme_slug ); |
|
313 $theme_template_files = _get_block_templates_paths( $theme_dir . '/' . $template_base_paths[ $template_type ] ); |
|
314 foreach ( $theme_template_files as $template_file ) { |
|
315 $template_base_path = $template_base_paths[ $template_type ]; |
|
316 $template_slug = substr( |
|
317 $template_file, |
|
318 // Starting position of slug. |
|
319 strpos( $template_file, $template_base_path . DIRECTORY_SEPARATOR ) + 1 + strlen( $template_base_path ), |
|
320 // Subtract ending '.html'. |
|
321 -5 |
|
322 ); |
|
323 $new_template_item = array( |
|
324 'slug' => $template_slug, |
|
325 'path' => $template_file, |
|
326 'theme' => $theme_slug, |
|
327 'type' => $template_type, |
|
328 ); |
|
329 |
|
330 if ( 'wp_template_part' === $template_type ) { |
|
331 $template_files[] = _add_block_template_part_area_info( $new_template_item ); |
|
332 } |
|
333 |
|
334 if ( 'wp_template' === $template_type ) { |
|
335 $template_files[] = _add_block_template_info( $new_template_item ); |
|
336 } |
|
337 } |
|
338 } |
|
339 |
|
340 return $template_files; |
|
341 } |
|
342 |
|
343 /** |
|
344 * Attempts to add custom template information to the template item. |
|
345 * |
|
346 * @since 5.9.0 |
|
347 * @access private |
|
348 * |
|
349 * @param array $template_item Template to add information to (requires 'slug' field). |
|
350 * @return array Template item. |
|
351 */ |
|
352 function _add_block_template_info( $template_item ) { |
|
353 if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) { |
|
354 return $template_item; |
|
355 } |
|
356 |
|
357 $theme_data = WP_Theme_JSON_Resolver::get_theme_data()->get_custom_templates(); |
|
358 if ( isset( $theme_data[ $template_item['slug'] ] ) ) { |
|
359 $template_item['title'] = $theme_data[ $template_item['slug'] ]['title']; |
|
360 $template_item['postTypes'] = $theme_data[ $template_item['slug'] ]['postTypes']; |
|
361 } |
|
362 |
|
363 return $template_item; |
|
364 } |
|
365 |
|
366 /** |
|
367 * Attempts to add the template part's area information to the input template. |
|
368 * |
|
369 * @since 5.9.0 |
|
370 * @access private |
|
371 * |
|
372 * @param array $template_info Template to add information to (requires 'type' and 'slug' fields). |
|
373 * |
|
374 * @return array Template info. |
|
375 */ |
|
376 function _add_block_template_part_area_info( $template_info ) { |
|
377 if ( WP_Theme_JSON_Resolver::theme_has_support() ) { |
|
378 $theme_data = WP_Theme_JSON_Resolver::get_theme_data()->get_template_parts(); |
|
379 } |
|
380 |
|
381 if ( isset( $theme_data[ $template_info['slug'] ]['area'] ) ) { |
|
382 $template_info['title'] = $theme_data[ $template_info['slug'] ]['title']; |
|
383 $template_info['area'] = _filter_block_template_part_area( $theme_data[ $template_info['slug'] ]['area'] ); |
|
384 } else { |
|
385 $template_info['area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED; |
|
386 } |
|
387 |
|
388 return $template_info; |
|
389 } |
|
390 |
|
391 /** |
|
392 * Returns an array containing the references of |
|
393 * the passed blocks and their inner blocks. |
|
394 * |
|
395 * @since 5.9.0 |
|
396 * @access private |
|
397 * |
|
398 * @param array $blocks array of blocks. |
|
399 * |
|
400 * @return array block references to the passed blocks and their inner blocks. |
|
401 */ |
|
402 function _flatten_blocks( &$blocks ) { |
|
403 $all_blocks = array(); |
|
404 $queue = array(); |
|
405 foreach ( $blocks as &$block ) { |
|
406 $queue[] = &$block; |
|
407 } |
|
408 |
|
409 while ( count( $queue ) > 0 ) { |
|
410 $block = &$queue[0]; |
|
411 array_shift( $queue ); |
|
412 $all_blocks[] = &$block; |
|
413 |
|
414 if ( ! empty( $block['innerBlocks'] ) ) { |
|
415 foreach ( $block['innerBlocks'] as &$inner_block ) { |
|
416 $queue[] = &$inner_block; |
|
417 } |
|
418 } |
|
419 } |
|
420 |
|
421 return $all_blocks; |
|
422 } |
|
423 |
|
424 /** |
|
425 * Parses wp_template content and injects the active theme's |
|
426 * stylesheet as a theme attribute into each wp_template_part |
|
427 * |
|
428 * @since 5.9.0 |
|
429 * @access private |
|
430 * |
|
431 * @param string $template_content serialized wp_template content. |
|
432 * |
|
433 * @return string Updated 'wp_template' content. |
|
434 */ |
|
435 function _inject_theme_attribute_in_block_template_content( $template_content ) { |
|
436 $has_updated_content = false; |
|
437 $new_content = ''; |
|
438 $template_blocks = parse_blocks( $template_content ); |
|
439 |
|
440 $blocks = _flatten_blocks( $template_blocks ); |
|
441 foreach ( $blocks as &$block ) { |
|
442 if ( |
|
443 'core/template-part' === $block['blockName'] && |
|
444 ! isset( $block['attrs']['theme'] ) |
|
445 ) { |
|
446 $block['attrs']['theme'] = wp_get_theme()->get_stylesheet(); |
|
447 $has_updated_content = true; |
|
448 } |
|
449 } |
|
450 |
|
451 if ( $has_updated_content ) { |
|
452 foreach ( $template_blocks as &$block ) { |
|
453 $new_content .= serialize_block( $block ); |
|
454 } |
|
455 |
|
456 return $new_content; |
|
457 } |
|
458 |
|
459 return $template_content; |
|
460 } |
|
461 |
|
462 /** |
|
463 * Parses a block template and removes the theme attribute from each template part. |
|
464 * |
|
465 * @since 5.9.0 |
|
466 * @access private |
|
467 * |
|
468 * @param string $template_content Serialized block template content. |
|
469 * @return string Updated block template content. |
|
470 */ |
|
471 function _remove_theme_attribute_in_block_template_content( $template_content ) { |
|
472 $has_updated_content = false; |
|
473 $new_content = ''; |
|
474 $template_blocks = parse_blocks( $template_content ); |
|
475 |
|
476 $blocks = _flatten_blocks( $template_blocks ); |
|
477 foreach ( $blocks as $key => $block ) { |
|
478 if ( 'core/template-part' === $block['blockName'] && isset( $block['attrs']['theme'] ) ) { |
|
479 unset( $blocks[ $key ]['attrs']['theme'] ); |
|
480 $has_updated_content = true; |
|
481 } |
|
482 } |
|
483 |
|
484 if ( ! $has_updated_content ) { |
|
485 return $template_content; |
|
486 } |
|
487 |
|
488 foreach ( $template_blocks as $block ) { |
|
489 $new_content .= serialize_block( $block ); |
|
490 } |
|
491 |
|
492 return $new_content; |
|
493 } |
|
494 |
|
495 /** |
|
496 * Build a unified template object based on a theme file. |
|
497 * |
|
498 * @since 5.9.0 |
|
499 * @access private |
|
500 * |
|
501 * @param array $template_file Theme file. |
|
502 * @param string $template_type 'wp_template' or 'wp_template_part'. |
|
503 * |
|
504 * @return WP_Block_Template Template. |
|
505 */ |
|
506 function _build_block_template_result_from_file( $template_file, $template_type ) { |
|
507 $default_template_types = get_default_block_template_types(); |
|
508 $template_content = file_get_contents( $template_file['path'] ); |
|
509 $theme = wp_get_theme()->get_stylesheet(); |
|
510 |
|
511 $template = new WP_Block_Template(); |
|
512 $template->id = $theme . '//' . $template_file['slug']; |
|
513 $template->theme = $theme; |
|
514 $template->content = _inject_theme_attribute_in_block_template_content( $template_content ); |
|
515 $template->slug = $template_file['slug']; |
|
516 $template->source = 'theme'; |
|
517 $template->type = $template_type; |
|
518 $template->title = ! empty( $template_file['title'] ) ? $template_file['title'] : $template_file['slug']; |
|
519 $template->status = 'publish'; |
|
520 $template->has_theme_file = true; |
|
521 $template->is_custom = true; |
|
522 |
|
523 if ( 'wp_template' === $template_type && isset( $default_template_types[ $template_file['slug'] ] ) ) { |
|
524 $template->description = $default_template_types[ $template_file['slug'] ]['description']; |
|
525 $template->title = $default_template_types[ $template_file['slug'] ]['title']; |
|
526 $template->is_custom = false; |
|
527 } |
|
528 |
|
529 if ( 'wp_template' === $template_type && isset( $template_file['postTypes'] ) ) { |
|
530 $template->post_types = $template_file['postTypes']; |
|
531 } |
|
532 |
|
533 if ( 'wp_template_part' === $template_type && isset( $template_file['area'] ) ) { |
|
534 $template->area = $template_file['area']; |
|
535 } |
|
536 |
|
537 return $template; |
|
538 } |
|
539 |
9 /** |
540 /** |
10 * Build a unified template object based a post Object. |
541 * Build a unified template object based a post Object. |
11 * |
542 * |
|
543 * @since 5.9.0 |
12 * @access private |
544 * @access private |
13 * @since 5.8.0 |
|
14 * |
545 * |
15 * @param WP_Post $post Template post. |
546 * @param WP_Post $post Template post. |
16 * |
547 * |
17 * @return WP_Block_Template|WP_Error Template. |
548 * @return WP_Block_Template|WP_Error Template. |
18 */ |
549 */ |
19 function _build_template_result_from_post( $post ) { |
550 function _build_block_template_result_from_post( $post ) { |
20 $terms = get_the_terms( $post, 'wp_theme' ); |
551 $default_template_types = get_default_block_template_types(); |
|
552 $terms = get_the_terms( $post, 'wp_theme' ); |
21 |
553 |
22 if ( is_wp_error( $terms ) ) { |
554 if ( is_wp_error( $terms ) ) { |
23 return $terms; |
555 return $terms; |
24 } |
556 } |
25 |
557 |
26 if ( ! $terms ) { |
558 if ( ! $terms ) { |
27 return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) ); |
559 return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) ); |
28 } |
560 } |
29 |
561 |
30 $theme = $terms[0]->name; |
562 $theme = $terms[0]->name; |
|
563 $has_theme_file = wp_get_theme()->get_stylesheet() === $theme && |
|
564 null !== _get_block_template_file( $post->post_type, $post->post_name ); |
|
565 |
|
566 $origin = get_post_meta( $post->ID, 'origin', true ); |
31 |
567 |
32 $template = new WP_Block_Template(); |
568 $template = new WP_Block_Template(); |
33 $template->wp_id = $post->ID; |
569 $template->wp_id = $post->ID; |
34 $template->id = $theme . '//' . $post->post_name; |
570 $template->id = $theme . '//' . $post->post_name; |
35 $template->theme = $theme; |
571 $template->theme = $theme; |
36 $template->content = $post->post_content; |
572 $template->content = $post->post_content; |
37 $template->slug = $post->post_name; |
573 $template->slug = $post->post_name; |
38 $template->source = 'custom'; |
574 $template->source = 'custom'; |
|
575 $template->origin = ! empty( $origin ) ? $origin : null; |
39 $template->type = $post->post_type; |
576 $template->type = $post->post_type; |
40 $template->description = $post->post_excerpt; |
577 $template->description = $post->post_excerpt; |
41 $template->title = $post->post_title; |
578 $template->title = $post->post_title; |
42 $template->status = $post->post_status; |
579 $template->status = $post->post_status; |
43 $template->has_theme_file = false; |
580 $template->has_theme_file = $has_theme_file; |
|
581 $template->is_custom = true; |
|
582 $template->author = $post->post_author; |
|
583 |
|
584 if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) { |
|
585 $template->is_custom = false; |
|
586 } |
|
587 |
|
588 if ( 'wp_template_part' === $post->post_type ) { |
|
589 $type_terms = get_the_terms( $post, 'wp_template_part_area' ); |
|
590 if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) { |
|
591 $template->area = $type_terms[0]->name; |
|
592 } |
|
593 } |
44 |
594 |
45 return $template; |
595 return $template; |
46 } |
596 } |
47 |
597 |
48 /** |
598 /** |
49 * Retrieves a list of unified template objects based on a query. |
599 * Retrieves a list of unified template objects based on a query. |
50 * |
600 * |
51 * @since 5.8.0 |
601 * @since 5.8.0 |
52 * |
602 * |
53 * @param array $query { |
603 * @param array $query { |
54 * Optional. Arguments to retrieve templates. |
604 * Optional. Arguments to retrieve templates. |
55 * |
605 * |
56 * @type array $slug__in List of slugs to include. |
606 * @type array $slug__in List of slugs to include. |
57 * @type int $wp_id Post ID of customized template. |
607 * @type int $wp_id Post ID of customized template. |
|
608 * @type string $area A 'wp_template_part_area' taxonomy value to filter by (for wp_template_part template type only). |
|
609 * @type string $post_type Post type to get the templates for. |
58 * } |
610 * } |
59 * @param string $template_type Optional. The template type (post type). Default 'wp_template'. |
611 * @param string $template_type 'wp_template' or 'wp_template_part'. |
60 * @return WP_Block_Template[] Block template objects. |
612 * |
|
613 * @return array Templates. |
61 */ |
614 */ |
62 function get_block_templates( $query = array(), $template_type = 'wp_template' ) { |
615 function get_block_templates( $query = array(), $template_type = 'wp_template' ) { |
|
616 /** |
|
617 * Filters the block templates array before the query takes place. |
|
618 * |
|
619 * Return a non-null value to bypass the WordPress queries. |
|
620 * |
|
621 * @since 5.9.0 |
|
622 * |
|
623 * @param WP_Block_Template[]|null $block_templates Return an array of block templates to short-circuit the default query, |
|
624 * or null to allow WP to run it's normal queries. |
|
625 * @param array $query { |
|
626 * Optional. Arguments to retrieve templates. |
|
627 * |
|
628 * @type array $slug__in List of slugs to include. |
|
629 * @type int $wp_id Post ID of customized template. |
|
630 * @type string $post_type Post type to get the templates for. |
|
631 * } |
|
632 * @param string $template_type wp_template or wp_template_part. |
|
633 */ |
|
634 $templates = apply_filters( 'pre_get_block_templates', null, $query, $template_type ); |
|
635 if ( ! is_null( $templates ) ) { |
|
636 return $templates; |
|
637 } |
|
638 |
|
639 $post_type = isset( $query['post_type'] ) ? $query['post_type'] : ''; |
63 $wp_query_args = array( |
640 $wp_query_args = array( |
64 'post_status' => array( 'auto-draft', 'draft', 'publish' ), |
641 'post_status' => array( 'auto-draft', 'draft', 'publish' ), |
65 'post_type' => $template_type, |
642 'post_type' => $template_type, |
66 'posts_per_page' => -1, |
643 'posts_per_page' => -1, |
67 'no_found_rows' => true, |
644 'no_found_rows' => true, |
72 'terms' => wp_get_theme()->get_stylesheet(), |
649 'terms' => wp_get_theme()->get_stylesheet(), |
73 ), |
650 ), |
74 ), |
651 ), |
75 ); |
652 ); |
76 |
653 |
|
654 if ( 'wp_template_part' === $template_type && isset( $query['area'] ) ) { |
|
655 $wp_query_args['tax_query'][] = array( |
|
656 'taxonomy' => 'wp_template_part_area', |
|
657 'field' => 'name', |
|
658 'terms' => $query['area'], |
|
659 ); |
|
660 $wp_query_args['tax_query']['relation'] = 'AND'; |
|
661 } |
|
662 |
77 if ( isset( $query['slug__in'] ) ) { |
663 if ( isset( $query['slug__in'] ) ) { |
78 $wp_query_args['post_name__in'] = $query['slug__in']; |
664 $wp_query_args['post_name__in'] = $query['slug__in']; |
79 } |
665 } |
80 |
666 |
81 // This is only needed for the regular templates CPT listing and editor. |
667 // This is only needed for the regular templates/template parts post type listing and editor. |
82 if ( isset( $query['wp_id'] ) ) { |
668 if ( isset( $query['wp_id'] ) ) { |
83 $wp_query_args['p'] = $query['wp_id']; |
669 $wp_query_args['p'] = $query['wp_id']; |
84 } else { |
670 } else { |
85 $wp_query_args['post_status'] = 'publish'; |
671 $wp_query_args['post_status'] = 'publish'; |
86 } |
672 } |
87 |
673 |
88 $template_query = new WP_Query( $wp_query_args ); |
674 $template_query = new WP_Query( $wp_query_args ); |
89 $query_result = array(); |
675 $query_result = array(); |
90 foreach ( $template_query->posts as $post ) { |
676 foreach ( $template_query->posts as $post ) { |
91 $template = _build_template_result_from_post( $post ); |
677 $template = _build_block_template_result_from_post( $post ); |
92 |
678 |
93 if ( ! is_wp_error( $template ) ) { |
679 if ( is_wp_error( $template ) ) { |
94 $query_result[] = $template; |
680 continue; |
95 } |
681 } |
96 } |
682 |
97 |
683 if ( $post_type && ! $template->is_custom ) { |
98 return $query_result; |
684 continue; |
|
685 } |
|
686 |
|
687 $query_result[] = $template; |
|
688 } |
|
689 |
|
690 if ( ! isset( $query['wp_id'] ) ) { |
|
691 $template_files = _get_block_templates_files( $template_type ); |
|
692 foreach ( $template_files as $template_file ) { |
|
693 $template = _build_block_template_result_from_file( $template_file, $template_type ); |
|
694 |
|
695 if ( $post_type && ! $template->is_custom ) { |
|
696 continue; |
|
697 } |
|
698 |
|
699 if ( $post_type && |
|
700 isset( $template->post_types ) && |
|
701 ! in_array( $post_type, $template->post_types, true ) |
|
702 ) { |
|
703 continue; |
|
704 } |
|
705 |
|
706 $is_not_custom = false === array_search( |
|
707 wp_get_theme()->get_stylesheet() . '//' . $template_file['slug'], |
|
708 wp_list_pluck( $query_result, 'id' ), |
|
709 true |
|
710 ); |
|
711 $fits_slug_query = |
|
712 ! isset( $query['slug__in'] ) || in_array( $template_file['slug'], $query['slug__in'], true ); |
|
713 $fits_area_query = |
|
714 ! isset( $query['area'] ) || $template_file['area'] === $query['area']; |
|
715 $should_include = $is_not_custom && $fits_slug_query && $fits_area_query; |
|
716 if ( $should_include ) { |
|
717 $query_result[] = $template; |
|
718 } |
|
719 } |
|
720 } |
|
721 |
|
722 /** |
|
723 * Filters the array of queried block templates array after they've been fetched. |
|
724 * |
|
725 * @since 5.9.0 |
|
726 * |
|
727 * @param WP_Block_Template[] $query_result Array of found block templates. |
|
728 * @param array $query { |
|
729 * Optional. Arguments to retrieve templates. |
|
730 * |
|
731 * @type array $slug__in List of slugs to include. |
|
732 * @type int $wp_id Post ID of customized template. |
|
733 * } |
|
734 * @param string $template_type wp_template or wp_template_part. |
|
735 */ |
|
736 return apply_filters( 'get_block_templates', $query_result, $query, $template_type ); |
99 } |
737 } |
100 |
738 |
101 /** |
739 /** |
102 * Retrieves a single unified template object using its id. |
740 * Retrieves a single unified template object using its id. |
103 * |
741 * |
104 * @since 5.8.0 |
742 * @since 5.8.0 |
105 * |
743 * |
106 * @param string $id Template unique identifier (example: theme_slug//template_slug). |
744 * @param string $id Template unique identifier (example: theme_slug//template_slug). |
107 * @param string $template_type wp_template. |
745 * @param string $template_type Optional. Template type: `'wp_template'` or '`wp_template_part'`. |
|
746 * Default `'wp_template'`. |
108 * |
747 * |
109 * @return WP_Block_Template|null Template. |
748 * @return WP_Block_Template|null Template. |
110 */ |
749 */ |
111 function get_block_template( $id, $template_type = 'wp_template' ) { |
750 function get_block_template( $id, $template_type = 'wp_template' ) { |
|
751 /** |
|
752 *Filters the block template object before the query takes place. |
|
753 * |
|
754 * Return a non-null value to bypass the WordPress queries. |
|
755 * |
|
756 * @since 5.9.0 |
|
757 * |
|
758 * @param WP_Block_Template|null $block_template Return block template object to short-circuit the default query, |
|
759 * or null to allow WP to run its normal queries. |
|
760 * @param string $id Template unique identifier (example: theme_slug//template_slug). |
|
761 * @param string $template_type Template type: `'wp_template'` or '`wp_template_part'`. |
|
762 */ |
|
763 $block_template = apply_filters( 'pre_get_block_template', null, $id, $template_type ); |
|
764 if ( ! is_null( $block_template ) ) { |
|
765 return $block_template; |
|
766 } |
|
767 |
112 $parts = explode( '//', $id, 2 ); |
768 $parts = explode( '//', $id, 2 ); |
113 if ( count( $parts ) < 2 ) { |
769 if ( count( $parts ) < 2 ) { |
114 return null; |
770 return null; |
115 } |
771 } |
116 list( $theme, $slug ) = $parts; |
772 list( $theme, $slug ) = $parts; |
130 ); |
786 ); |
131 $template_query = new WP_Query( $wp_query_args ); |
787 $template_query = new WP_Query( $wp_query_args ); |
132 $posts = $template_query->posts; |
788 $posts = $template_query->posts; |
133 |
789 |
134 if ( count( $posts ) > 0 ) { |
790 if ( count( $posts ) > 0 ) { |
135 $template = _build_template_result_from_post( $posts[0] ); |
791 $template = _build_block_template_result_from_post( $posts[0] ); |
136 |
792 |
137 if ( ! is_wp_error( $template ) ) { |
793 if ( ! is_wp_error( $template ) ) { |
138 return $template; |
794 return $template; |
139 } |
795 } |
140 } |
796 } |
141 |
797 |
142 return null; |
798 $block_template = get_block_file_template( $id, $template_type ); |
143 } |
799 |
|
800 /** |
|
801 * Filters the queried block template object after it's been fetched. |
|
802 * |
|
803 * @since 5.9.0 |
|
804 * |
|
805 * @param WP_Block_Template|null $block_template The found block template, or null if there isn't one. |
|
806 * @param string $id Template unique identifier (example: theme_slug//template_slug). |
|
807 * @param array $template_type Template type: `'wp_template'` or '`wp_template_part'`. |
|
808 */ |
|
809 return apply_filters( 'get_block_template', $block_template, $id, $template_type ); |
|
810 } |
|
811 |
|
812 /** |
|
813 * Retrieves a single unified template object using its id. |
|
814 * |
|
815 * @since 5.9.0 |
|
816 * |
|
817 * @param string $id Template unique identifier (example: theme_slug//template_slug). |
|
818 * @param string $template_type Optional. Template type: `'wp_template'` or '`wp_template_part'`. |
|
819 * Default `'wp_template'`. |
|
820 * @return WP_Block_Template|null The found block template, or null if there isn't one. |
|
821 */ |
|
822 function get_block_file_template( $id, $template_type = 'wp_template' ) { |
|
823 /** |
|
824 * Filters the block templates array before the query takes place. |
|
825 * |
|
826 * Return a non-null value to bypass the WordPress queries. |
|
827 * |
|
828 * @since 5.9.0 |
|
829 * |
|
830 * @param WP_Block_Template|null $block_template Return block template object to short-circuit the default query, |
|
831 * or null to allow WP to run its normal queries. |
|
832 * @param string $id Template unique identifier (example: theme_slug//template_slug). |
|
833 * @param string $template_type Template type: `'wp_template'` or '`wp_template_part'`. |
|
834 */ |
|
835 $block_template = apply_filters( 'pre_get_block_file_template', null, $id, $template_type ); |
|
836 if ( ! is_null( $block_template ) ) { |
|
837 return $block_template; |
|
838 } |
|
839 |
|
840 $parts = explode( '//', $id, 2 ); |
|
841 if ( count( $parts ) < 2 ) { |
|
842 /** This filter is documented in wp-includes/block-template-utils.php */ |
|
843 return apply_filters( 'get_block_file_template', null, $id, $template_type ); |
|
844 } |
|
845 list( $theme, $slug ) = $parts; |
|
846 |
|
847 if ( wp_get_theme()->get_stylesheet() !== $theme ) { |
|
848 /** This filter is documented in wp-includes/block-template-utils.php */ |
|
849 return apply_filters( 'get_block_file_template', null, $id, $template_type ); |
|
850 } |
|
851 |
|
852 $template_file = _get_block_template_file( $template_type, $slug ); |
|
853 if ( null === $template_file ) { |
|
854 /** This filter is documented in wp-includes/block-template-utils.php */ |
|
855 return apply_filters( 'get_block_file_template', null, $id, $template_type ); |
|
856 } |
|
857 |
|
858 $block_template = _build_block_template_result_from_file( $template_file, $template_type ); |
|
859 |
|
860 /** |
|
861 * Filters the array of queried block templates array after they've been fetched. |
|
862 * |
|
863 * @since 5.9.0 |
|
864 * |
|
865 * @param WP_Block_Template|null $block_template The found block template, or null if there is none. |
|
866 * @param string $id Template unique identifier (example: theme_slug//template_slug). |
|
867 * @param string $template_type Template type: `'wp_template'` or '`wp_template_part'`. |
|
868 */ |
|
869 return apply_filters( 'get_block_file_template', $block_template, $id, $template_type ); |
|
870 } |
|
871 |
|
872 /** |
|
873 * Print a template-part. |
|
874 * |
|
875 * @since 5.9.0 |
|
876 * |
|
877 * @param string $part The template-part to print. Use "header" or "footer". |
|
878 */ |
|
879 function block_template_part( $part ) { |
|
880 $template_part = get_block_template( get_stylesheet() . '//' . $part, 'wp_template_part' ); |
|
881 if ( ! $template_part || empty( $template_part->content ) ) { |
|
882 return; |
|
883 } |
|
884 echo do_blocks( $template_part->content ); |
|
885 } |
|
886 |
|
887 /** |
|
888 * Print the header template-part. |
|
889 * |
|
890 * @since 5.9.0 |
|
891 */ |
|
892 function block_header_area() { |
|
893 block_template_part( 'header' ); |
|
894 } |
|
895 |
|
896 /** |
|
897 * Print the footer template-part. |
|
898 * |
|
899 * @since 5.9.0 |
|
900 */ |
|
901 function block_footer_area() { |
|
902 block_template_part( 'footer' ); |
|
903 } |
|
904 |
|
905 /** |
|
906 * Filters theme directories that should be ignored during export. |
|
907 * |
|
908 * @since 6.0.0 |
|
909 * |
|
910 * @param string $path The path of the file in the theme. |
|
911 * @return Bool Whether this file is in an ignored directory. |
|
912 */ |
|
913 function wp_is_theme_directory_ignored( $path ) { |
|
914 $directories_to_ignore = array( '.svn', '.git', '.hg', '.bzr', 'node_modules', 'vendor' ); |
|
915 foreach ( $directories_to_ignore as $directory ) { |
|
916 if ( strpos( $path, $directory ) === 0 ) { |
|
917 return true; |
|
918 } |
|
919 } |
|
920 |
|
921 return false; |
|
922 } |
|
923 |
|
924 /** |
|
925 * Creates an export of the current templates and |
|
926 * template parts from the site editor at the |
|
927 * specified path in a ZIP file. |
|
928 * |
|
929 * @since 5.9.0 |
|
930 * @since 6.0.0 Adds the whole theme to the export archive. |
|
931 * |
|
932 * @return WP_Error|string Path of the ZIP file or error on failure. |
|
933 */ |
|
934 function wp_generate_block_templates_export_file() { |
|
935 if ( ! class_exists( 'ZipArchive' ) ) { |
|
936 return new WP_Error( 'missing_zip_package', __( 'Zip Export not supported.' ) ); |
|
937 } |
|
938 |
|
939 $obscura = wp_generate_password( 12, false, false ); |
|
940 $theme_name = basename( get_stylesheet() ); |
|
941 $filename = get_temp_dir() . $theme_name . $obscura . '.zip'; |
|
942 |
|
943 $zip = new ZipArchive(); |
|
944 if ( true !== $zip->open( $filename, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) { |
|
945 return new WP_Error( 'unable_to_create_zip', __( 'Unable to open export file (archive) for writing.' ) ); |
|
946 } |
|
947 |
|
948 $zip->addEmptyDir( 'templates' ); |
|
949 $zip->addEmptyDir( 'parts' ); |
|
950 |
|
951 // Get path of the theme. |
|
952 $theme_path = wp_normalize_path( get_stylesheet_directory() ); |
|
953 |
|
954 // Create recursive directory iterator. |
|
955 $theme_files = new RecursiveIteratorIterator( |
|
956 new RecursiveDirectoryIterator( $theme_path ), |
|
957 RecursiveIteratorIterator::LEAVES_ONLY |
|
958 ); |
|
959 |
|
960 // Make a copy of the current theme. |
|
961 foreach ( $theme_files as $file ) { |
|
962 // Skip directories as they are added automatically. |
|
963 if ( ! $file->isDir() ) { |
|
964 // Get real and relative path for current file. |
|
965 $file_path = wp_normalize_path( $file ); |
|
966 $relative_path = substr( $file_path, strlen( $theme_path ) + 1 ); |
|
967 |
|
968 if ( ! wp_is_theme_directory_ignored( $relative_path ) ) { |
|
969 $zip->addFile( $file_path, $relative_path ); |
|
970 } |
|
971 } |
|
972 } |
|
973 |
|
974 // Load templates into the zip file. |
|
975 $templates = get_block_templates(); |
|
976 foreach ( $templates as $template ) { |
|
977 $template->content = _remove_theme_attribute_in_block_template_content( $template->content ); |
|
978 |
|
979 $zip->addFromString( |
|
980 'templates/' . $template->slug . '.html', |
|
981 $template->content |
|
982 ); |
|
983 } |
|
984 |
|
985 // Load template parts into the zip file. |
|
986 $template_parts = get_block_templates( array(), 'wp_template_part' ); |
|
987 foreach ( $template_parts as $template_part ) { |
|
988 $zip->addFromString( |
|
989 'parts/' . $template_part->slug . '.html', |
|
990 $template_part->content |
|
991 ); |
|
992 } |
|
993 |
|
994 // Load theme.json into the zip file. |
|
995 $tree = WP_Theme_JSON_Resolver::get_theme_data( array(), array( 'with_supports' => false ) ); |
|
996 // Merge with user data. |
|
997 $tree->merge( WP_Theme_JSON_Resolver::get_user_data() ); |
|
998 |
|
999 $theme_json_raw = $tree->get_data(); |
|
1000 // If a version is defined, add a schema. |
|
1001 if ( $theme_json_raw['version'] ) { |
|
1002 global $wp_version; |
|
1003 $theme_json_version = 'wp/' . substr( $wp_version, 0, 3 ); |
|
1004 $schema = array( '$schema' => 'https://schemas.wp.org/' . $theme_json_version . '/theme.json' ); |
|
1005 $theme_json_raw = array_merge( $schema, $theme_json_raw ); |
|
1006 } |
|
1007 |
|
1008 // Convert to a string. |
|
1009 $theme_json_encoded = wp_json_encode( $theme_json_raw, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); |
|
1010 |
|
1011 // Replace 4 spaces with a tab. |
|
1012 $theme_json_tabbed = preg_replace( '~(?:^|\G)\h{4}~m', "\t", $theme_json_encoded ); |
|
1013 |
|
1014 // Add the theme.json file to the zip. |
|
1015 $zip->addFromString( |
|
1016 'theme.json', |
|
1017 $theme_json_tabbed |
|
1018 ); |
|
1019 |
|
1020 // Save changes to the zip file. |
|
1021 $zip->close(); |
|
1022 |
|
1023 return $filename; |
|
1024 } |