diff --git a/packages/block-library/src/template-part/edit/index.js b/packages/block-library/src/template-part/edit/index.js
index 6e7507b0a333c7..263e92f570dee9 100644
--- a/packages/block-library/src/template-part/edit/index.js
+++ b/packages/block-library/src/template-part/edit/index.js
@@ -5,6 +5,7 @@ import { useSelect } from '@wordpress/data';
import {
BlockControls,
useBlockProps,
+ __experimentalUseNoRecursiveRenders as useNoRecursiveRenders,
Warning,
store as blockEditorStore,
} from '@wordpress/block-editor';
@@ -34,6 +35,10 @@ export default function TemplatePartEdit( {
} ) {
const templatePartId = theme && slug ? theme + '//' + slug : null;
+ const [ hasAlreadyRendered, RecursionProvider ] = useNoRecursiveRenders(
+ templatePartId
+ );
+
// Set the postId block attribute if it did not exist,
// but wait until the inner blocks have loaded to allow
// new edits to trigger this.
@@ -70,8 +75,7 @@ export default function TemplatePartEdit( {
);
const blockProps = useBlockProps();
- const isPlaceholder = ! slug;
- const isEntityAvailable = ! isPlaceholder && ! isMissing;
+ const isEntityAvailable = slug && ! isMissing;
const TagName = tagName || getTagBasedOnArea( area );
// We don't want to render a missing state if we have any inner blocks.
@@ -91,8 +95,18 @@ export default function TemplatePartEdit( {
);
}
+ if ( hasAlreadyRendered ) {
+ return (
+
+
+ { __( 'Block cannot be rendered inside itself.' ) }
+
+
+ );
+ }
+
return (
- <>
+
}
- >
+
);
}
diff --git a/packages/block-library/src/template-part/index.php b/packages/block-library/src/template-part/index.php
index ff88b452587b0b..229a3c0ebfa7bb 100644
--- a/packages/block-library/src/template-part/index.php
+++ b/packages/block-library/src/template-part/index.php
@@ -13,6 +13,8 @@
* @return string The render.
*/
function render_block_core_template_part( $attributes ) {
+ static $seen_content = array();
+
$content = null;
$area = WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
@@ -60,6 +62,31 @@ function render_block_core_template_part( $attributes ) {
return 'Template Part Not Found';
}
+ if ( in_array( $content, $seen_content, true ) ) {
+ if ( ! is_admin() ) {
+ trigger_error(
+ sprintf(
+ // translators: %s is the user-provided title of the reusable block.
+ __( 'Could not render Template Part with the slug %s: blocks cannot be rendered inside themselves.' ),
+ $attributes['slug']
+ ),
+ E_USER_WARNING
+ );
+ }
+
+ // WP_DEBUG_DISPLAY must only be honored when WP_DEBUG. This precedent
+ // is set in `wp_debug_mode()`.
+ $is_debug = defined( 'WP_DEBUG' ) && WP_DEBUG &&
+ defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY;
+
+ return $is_debug ?
+ // translators: Visible only in the front end, this warning takes the place of a faulty block.
+ __( '[block rendering halted]' ) :
+ '';
+ }
+
+ $seen_content[] = $content;
+
// Run through the actions that are typically taken on the_content.
$content = do_blocks( $content );
$content = wptexturize( $content );