mirror of
https://github.com/Heretek-AI/heretek-control-core.git
synced 2026-10-12 02:03:55 +00:00
fix(blocks): fail loudly instead of silently dropping a block on a bad position path
Block_Tree::insert() no-ops when an index path does not resolve and returns the tree unchanged, so every block pack saved the unchanged post and still reported success — the block was lost and the agent believed it landed. Found live while building a 29-block Kadence page: three nested infoboxes vanished with 'ok' for each. New Block_Tree::position_is_valid() (append/prepend always valid; inside/before/after require a path that resolves via at()), enforced in the Kadence (add-block + insert-pattern), Spectra, GenerateBlocks and Blocksy add paths with an invalid_position WP_Error. Core Gutenberg already guarded this. Note for callers: position.path indexes the SEPARATOR-STRIPPED tree (what get-post-blocks returns), not raw parse_blocks output, which counts whitespace entries — that mismatch is exactly what produced the lost blocks. Tests: BlockTreePositionTest (5) incl. the silent-noop behaviour the guard exists to prevent. Suites: pro 2070, public 155. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7ffe5d3f96
commit
e3a3602047
@@ -8,6 +8,7 @@ All notable changes to MCP Tools for Elementor are documented in this file.
|
||||
- **Kadence blocks inserted by AI now auto-repair in the editor.** Kadence blocks use a static JS save() the server cannot run, so blocks inserted through the tools rendered perfectly on the front end but showed "Attempt recovery" on every block in the editor. EMCP now runs that recovery automatically: when you open a post the AI added Kadence blocks to, the editor repairs each of them with its own recovery mechanism (regenerating the markup with Kadence's own save(), content preserved), shows a single notice, and one click on Update stores the clean markup for good. No more clicking "Attempt recovery" block by block. Other static-save block packs can opt in via the `emcp_tools_block_repair_namespaces` filter.
|
||||
|
||||
### Fixed
|
||||
- **Inserting a block at a position that does not exist silently lost the block.** When `add-block` was given a `position.path` that did not resolve to a real block (a stale or miscounted index), the insert quietly did nothing, the post was saved unchanged, and the tool still reported success, so an AI believed the block was added when it was not. Every block pack (Kadence, Spectra, GenerateBlocks, Blocksy) now validates the position first and returns a clear `invalid_position` error naming the problem. The core Gutenberg tools already did this.
|
||||
- **OAuth sign-in failed on subdirectory installs (WordPress in a subfolder).** On a site installed under a path such as `example.com/gpt-build/`, the OAuth discovery document that clients need (`/.well-known/oauth-protected-resource`, RFC 9728) returned 404, so connecting over OAuth from ChatGPT, Claude, or any standard MCP client dead-ended before it could get a token, and no tools appeared. The plugin advertised the correct subfolder URL but did not serve it there, because the request path was matched against the site-root path without accounting for the subdirectory. It now strips the install's home-path prefix before matching, so discovery works on subfolder installs and is unchanged on root installs.
|
||||
|
||||
### Performance
|
||||
|
||||
@@ -146,6 +146,11 @@ class EMCP_Tools_Kadence_Blocks_Integration extends EMCP_Tools_Theme_Integration
|
||||
|
||||
$block = $this->build_block( $name, $attrs );
|
||||
$tree = EMCP_Tools_Block_Tree::from_markup( (string) $post->post_content );
|
||||
// An unresolvable path makes insert() a silent no-op, which would report
|
||||
// success while dropping the block. Fail loudly instead.
|
||||
if ( ! EMCP_Tools_Block_Tree::position_is_valid( $tree, $position ) ) {
|
||||
return new WP_Error( 'invalid_position', __( 'position.path does not resolve to a block in this post. List the blocks in this post first and use a real index path.', 'emcp-tools' ), array( 'status' => 400 ) );
|
||||
}
|
||||
$tree = EMCP_Tools_Block_Tree::insert( $tree, array( $block ), $position );
|
||||
$markup = EMCP_Tools_Block_Tree::to_markup( $tree );
|
||||
|
||||
@@ -225,6 +230,11 @@ class EMCP_Tools_Kadence_Blocks_Integration extends EMCP_Tools_Theme_Integration
|
||||
|
||||
$position = ( isset( $input['position'] ) && is_array( $input['position'] ) ) ? $input['position'] : array( 'mode' => 'append' );
|
||||
$tree = EMCP_Tools_Block_Tree::from_markup( (string) $post->post_content );
|
||||
// An unresolvable path makes insert() a silent no-op, which would report
|
||||
// success while dropping the block. Fail loudly instead.
|
||||
if ( ! EMCP_Tools_Block_Tree::position_is_valid( $tree, $position ) ) {
|
||||
return new WP_Error( 'invalid_position', __( 'position.path does not resolve to a block in this post. List the blocks in this post first and use a real index path.', 'emcp-tools' ), array( 'status' => 400 ) );
|
||||
}
|
||||
$tree = EMCP_Tools_Block_Tree::insert( $tree, $new_blocks, $position );
|
||||
$out = EMCP_Tools_Block_Tree::to_markup( $tree );
|
||||
|
||||
|
||||
@@ -128,6 +128,11 @@ class EMCP_Tools_Spectra_Integration extends EMCP_Tools_Theme_Integration {
|
||||
|
||||
$block = $this->build_block( $name, $attrs );
|
||||
$tree = EMCP_Tools_Block_Tree::from_markup( (string) $post->post_content );
|
||||
// An unresolvable path makes insert() a silent no-op, which would report
|
||||
// success while dropping the block. Fail loudly instead.
|
||||
if ( ! EMCP_Tools_Block_Tree::position_is_valid( $tree, $position ) ) {
|
||||
return new WP_Error( 'invalid_position', __( 'position.path does not resolve to a block in this post. List the blocks in this post first and use a real index path.', 'emcp-tools' ), array( 'status' => 400 ) );
|
||||
}
|
||||
$tree = EMCP_Tools_Block_Tree::insert( $tree, array( $block ), $position );
|
||||
$markup = EMCP_Tools_Block_Tree::to_markup( $tree );
|
||||
|
||||
|
||||
@@ -86,6 +86,33 @@ class EMCP_Tools_Block_Tree {
|
||||
* @param array $position { mode: append|prepend|before|after|inside, path?: int[] }.
|
||||
* @return array New tree.
|
||||
*/
|
||||
/**
|
||||
* Whether a position can actually be applied to this tree.
|
||||
*
|
||||
* `insert()` (like the other editors) no-ops when an index path does not
|
||||
* resolve, returning the tree unchanged — so a caller that saves the result
|
||||
* and reports success silently DROPS the block, and the agent believes it
|
||||
* was added. Callers validate the position with this first and return an
|
||||
* error instead. `append`/`prepend` need no path and are always valid.
|
||||
*
|
||||
* @since 3.12.2
|
||||
*
|
||||
* @param array $blocks Parsed block tree.
|
||||
* @param array $position { mode: append|prepend|before|after|inside, path?: int[] }.
|
||||
* @return bool
|
||||
*/
|
||||
public static function position_is_valid( array $blocks, array $position ): bool {
|
||||
$mode = $position['mode'] ?? 'append';
|
||||
if ( 'append' === $mode || 'prepend' === $mode ) {
|
||||
return true;
|
||||
}
|
||||
$path = $position['path'] ?? array();
|
||||
if ( empty( $path ) || ! is_array( $path ) ) {
|
||||
return false; // inside/before/after are meaningless without a target.
|
||||
}
|
||||
return null !== self::at( $blocks, $path );
|
||||
}
|
||||
|
||||
public static function insert( array $blocks, array $newBlocks, array $position ): array {
|
||||
$mode = $position['mode'] ?? 'append';
|
||||
$path = $position['path'] ?? array();
|
||||
|
||||
+1
-1
Submodule pro updated: 0706357dd4...c72e525db2
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* EMCP_Tools_Block_Tree::position_is_valid() — the guard against silently
|
||||
* dropping a block when an index path does not resolve.
|
||||
*
|
||||
* insert() no-ops on an unresolvable path and returns the tree unchanged, so a
|
||||
* caller that saved the result reported success while losing the block. Every
|
||||
* block-pack add-block now validates the position first. Caught live while
|
||||
* building a full Kadence page: three nested infoboxes vanished and the tool
|
||||
* still said "ok".
|
||||
*
|
||||
* @package EMCP_Tools
|
||||
*/
|
||||
|
||||
require_once dirname( __DIR__ ) . '/includes/class-block-tree.php';
|
||||
|
||||
class BlockTreePositionTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
/** A two-level tree: row (with 2 columns) + a heading. */
|
||||
private function tree(): array {
|
||||
return array(
|
||||
array(
|
||||
'blockName' => 'kadence/rowlayout',
|
||||
'attrs' => array(),
|
||||
'innerBlocks' => array(
|
||||
array( 'blockName' => 'kadence/column', 'attrs' => array(), 'innerBlocks' => array(), 'innerHTML' => '', 'innerContent' => array() ),
|
||||
array( 'blockName' => 'kadence/column', 'attrs' => array(), 'innerBlocks' => array(), 'innerHTML' => '', 'innerContent' => array() ),
|
||||
),
|
||||
'innerHTML' => '',
|
||||
'innerContent' => array( null, null ),
|
||||
),
|
||||
array( 'blockName' => 'kadence/advancedheading', 'attrs' => array(), 'innerBlocks' => array(), 'innerHTML' => '', 'innerContent' => array() ),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_append_and_prepend_need_no_path() {
|
||||
$t = $this->tree();
|
||||
$this->assertTrue( EMCP_Tools_Block_Tree::position_is_valid( $t, array( 'mode' => 'append' ) ) );
|
||||
$this->assertTrue( EMCP_Tools_Block_Tree::position_is_valid( $t, array( 'mode' => 'prepend' ) ) );
|
||||
// Default mode (no mode key) is append.
|
||||
$this->assertTrue( EMCP_Tools_Block_Tree::position_is_valid( $t, array() ) );
|
||||
}
|
||||
|
||||
public function test_valid_top_level_and_nested_paths() {
|
||||
$t = $this->tree();
|
||||
$this->assertTrue( EMCP_Tools_Block_Tree::position_is_valid( $t, array( 'mode' => 'inside', 'path' => array( 0 ) ) ) );
|
||||
$this->assertTrue( EMCP_Tools_Block_Tree::position_is_valid( $t, array( 'mode' => 'inside', 'path' => array( 0, 1 ) ) ) );
|
||||
$this->assertTrue( EMCP_Tools_Block_Tree::position_is_valid( $t, array( 'mode' => 'after', 'path' => array( 1 ) ) ) );
|
||||
}
|
||||
|
||||
public function test_out_of_range_paths_are_invalid() {
|
||||
$t = $this->tree();
|
||||
// The exact live failure: a raw parse_blocks index (separators counted)
|
||||
// pointing past the real tree.
|
||||
$this->assertFalse( EMCP_Tools_Block_Tree::position_is_valid( $t, array( 'mode' => 'inside', 'path' => array( 8, 0 ) ) ) );
|
||||
$this->assertFalse( EMCP_Tools_Block_Tree::position_is_valid( $t, array( 'mode' => 'inside', 'path' => array( 99, 5 ) ) ) );
|
||||
// Second level out of range.
|
||||
$this->assertFalse( EMCP_Tools_Block_Tree::position_is_valid( $t, array( 'mode' => 'inside', 'path' => array( 0, 9 ) ) ) );
|
||||
// A leaf has no children to descend into.
|
||||
$this->assertFalse( EMCP_Tools_Block_Tree::position_is_valid( $t, array( 'mode' => 'inside', 'path' => array( 1, 0 ) ) ) );
|
||||
}
|
||||
|
||||
public function test_inside_before_after_require_a_path() {
|
||||
$t = $this->tree();
|
||||
foreach ( array( 'inside', 'before', 'after' ) as $mode ) {
|
||||
$this->assertFalse( EMCP_Tools_Block_Tree::position_is_valid( $t, array( 'mode' => $mode ) ) );
|
||||
$this->assertFalse( EMCP_Tools_Block_Tree::position_is_valid( $t, array( 'mode' => $mode, 'path' => array() ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/** The behaviour the guard exists to prevent: insert() silently no-ops. */
|
||||
public function test_insert_is_a_silent_noop_on_a_bad_path() {
|
||||
$t = $this->tree();
|
||||
$new = array( array( 'blockName' => 'kadence/infobox', 'attrs' => array(), 'innerBlocks' => array(), 'innerHTML' => '', 'innerContent' => array() ) );
|
||||
$out = EMCP_Tools_Block_Tree::insert( $t, $new, array( 'mode' => 'inside', 'path' => array( 99, 5 ) ) );
|
||||
$this->assertEquals( $t, $out, 'insert() returns the tree unchanged — hence the guard' );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user