mirror of
https://github.com/Heretek-AI/heretek-control-core.git
synced 2026-10-12 02:03:55 +00:00
fix(#111): invalidate Elementor 4.2 element cache on data writes
MCP-created pages rendered empty on Elementor 4.2.x: create-page writes empty [] data, an intermediate render caches an empty _elementor_element_cache, and the plugin's raw update_post_meta() write path never invalidated it (Elementor only clears it in Document::save(), which the CLI/proxy fallback bypasses). On a persistent object cache (WP Engine) the stale empty entry survives every content write, so the page serves the cached empty render forever. Add EMCP_Tools_Data::init() — an added/updated_post_meta hook that clears _elementor_element_cache whenever _elementor_data changes, from ANY write path — plus an explicit clear in the save fallback. Also fixes delayed visibility of edits to existing pages. Root-caused + fix live-verified on Elementor 4.2.2. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b5303c1c11
commit
a63ed73c93
@@ -326,6 +326,9 @@ class EMCP_Tools_Bootstrap {
|
||||
99
|
||||
);
|
||||
|
||||
// Invalidate Elementor's rendered-element cache on any _elementor_data
|
||||
// write, so MCP-created/edited pages never serve a stale empty render (#111).
|
||||
EMCP_Tools_Data::init();
|
||||
// Content search index: install-on-init + incremental re-index on save/delete.
|
||||
EMCP_Tools_Search_Index::init();
|
||||
EMCP_Tools_Change_Blobs::init();
|
||||
|
||||
@@ -20,6 +20,42 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
*/
|
||||
class EMCP_Tools_Data {
|
||||
|
||||
/** Elementor 4.2's rendered-element cache meta (Document::CACHE_META_KEY). */
|
||||
const ELEMENT_CACHE_META = '_elementor_element_cache';
|
||||
|
||||
/**
|
||||
* Register global hooks. Invalidates Elementor's rendered-element cache
|
||||
* (`_elementor_element_cache`) whenever a post's `_elementor_data` changes,
|
||||
* from ANY write path — our MCP tools' raw `update_post_meta()` writes, the
|
||||
* editor, or an import.
|
||||
*
|
||||
* Elementor 4.2's Element Cache stores rendered HTML in post meta. Elementor
|
||||
* clears it in `Document::save()`, but our CLI/proxy fallback writes data with
|
||||
* raw `update_post_meta()` and bypasses that. On a persistent object cache
|
||||
* (e.g. WP Engine) a stale/empty entry then survives every content write, so a
|
||||
* page created via MCP — written while its data was still the empty `[]`,
|
||||
* rendered (caching empty), then filled — keeps serving the cached empty
|
||||
* render forever. This restores Elementor's invalidation universally, and also
|
||||
* fixes the delayed visibility of edits to existing pages. See issue #111.
|
||||
*/
|
||||
public static function init(): void {
|
||||
add_action( 'added_post_meta', array( __CLASS__, 'flush_element_cache_on_data_write' ), 10, 3 );
|
||||
add_action( 'updated_post_meta', array( __CLASS__, 'flush_element_cache_on_data_write' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the rendered-element cache when `_elementor_data` is written.
|
||||
*
|
||||
* @param int $meta_id Meta row id (unused).
|
||||
* @param int $post_id Post id.
|
||||
* @param string $meta_key Meta key.
|
||||
*/
|
||||
public static function flush_element_cache_on_data_write( $meta_id, $post_id, $meta_key ): void {
|
||||
if ( '_elementor_data' === $meta_key ) {
|
||||
delete_post_meta( (int) $post_id, self::ELEMENT_CACHE_META );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether Elementor's document manager is available.
|
||||
*
|
||||
@@ -316,6 +352,12 @@ class EMCP_Tools_Data {
|
||||
// Invalidate Elementor CSS cache so it regenerates on next page view.
|
||||
delete_post_meta( $post_id, '_elementor_css' );
|
||||
|
||||
// Invalidate Elementor 4.2's rendered-element cache — the update above
|
||||
// fires the meta hook that clears it, but do it explicitly too so a
|
||||
// re-save of identical data (which skips the hook) still refreshes a
|
||||
// stale/empty cached render. See init() + issue #111.
|
||||
delete_post_meta( $post_id, self::ELEMENT_CACHE_META );
|
||||
|
||||
$upload_dir = wp_get_upload_dir();
|
||||
$css_path = $upload_dir['basedir'] . '/elementor/css/post-' . $post_id . '.css';
|
||||
if ( file_exists( $css_path ) ) {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* Public unit test for EMCP_Tools_Data::flush_element_cache_on_data_write() —
|
||||
* the issue #111 fix that invalidates Elementor 4.2's rendered-element cache
|
||||
* whenever `_elementor_data` is written (so MCP-created pages never serve a
|
||||
* stale/empty cached render). Behaviour is fully live-verified on a real
|
||||
* Elementor 4.2.2 site; this pins the key-matching logic.
|
||||
*
|
||||
* @package EMCP_Tools
|
||||
*/
|
||||
|
||||
require_once dirname( __DIR__ ) . '/includes/class-elementor-data.php';
|
||||
|
||||
class ElementCacheFlushTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
protected function setUp(): void {
|
||||
emcp_test_reset();
|
||||
$GLOBALS['emcp_test']['deleted_meta'] = array();
|
||||
}
|
||||
|
||||
public function test_clears_element_cache_when_elementor_data_written() {
|
||||
EMCP_Tools_Data::flush_element_cache_on_data_write( 5, 42, '_elementor_data' );
|
||||
$this->assertContains( array( 42, '_elementor_element_cache' ), $GLOBALS['emcp_test']['deleted_meta'] );
|
||||
}
|
||||
|
||||
public function test_ignores_other_meta_keys() {
|
||||
EMCP_Tools_Data::flush_element_cache_on_data_write( 5, 42, '_edit_lock' );
|
||||
EMCP_Tools_Data::flush_element_cache_on_data_write( 5, 42, '_elementor_css' );
|
||||
$this->assertSame( array(), $GLOBALS['emcp_test']['deleted_meta'] );
|
||||
}
|
||||
}
|
||||
@@ -153,6 +153,14 @@ function get_post( $post_id ) {
|
||||
return $GLOBALS['emcp_test']['posts'][ (int) $post_id ] ?? null;
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'delete_post_meta' ) ) {
|
||||
// Records deletions in $GLOBALS['emcp_test']['deleted_meta'] as [post_id, key].
|
||||
function delete_post_meta( $post_id, $key, $value = '' ) {
|
||||
$GLOBALS['emcp_test']['deleted_meta'][] = array( (int) $post_id, (string) $key );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function get_permalink( $post = null ): string {
|
||||
return 'http://example.test/?p=' . ( is_object( $post ) ? (int) $post->ID : (int) $post );
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user