mirror of
https://github.com/Heretek-AI/heretek-control-core.git
synced 2026-10-12 02:03:55 +00:00
fix(themer): a dynamic value on a core Elementor widget killed the front end
Reported hours after 3.13.0 shipped: putting a dynamic value on a standard
Heading produced "There has been a critical error on this website".
Elementor's tag registry keys by get_name() but stores only the class, and
rebuilds the tag later from that class and the element data alone:
$this->tags_info[ $tag->get_name() ] = [ 'class' => get_class( $tag ), … ];
return new $tag_class( [ 'settings' => …, 'id' => … ] );
3.13.0 registered one parameterised class under seventeen names, taking the
source key as its first constructor argument. Registration and the picker both
looked correct, because those use the stored instance. Rendering does not: it
rebuilds, the tag name is gone by then, and the data array landed in the key
parameter, so every render threw a TypeError.
Each source is now its own concrete class with the key in its class identity,
which is how Elementor Pro's own tags are built. The base is abstract so it can
never be registered, and registration skips (and in debug logs) any catalogued
source with no class rather than registering something unrebuildable.
Nothing stored is damaged: affected pages render correctly on update with
nothing to redo. The block editor was never affected, its bindings use our own
callback rather than Elementor's registry, verified while fixing this.
Verified live on Elementor 4.2.2: all 17 tags rebuild, each has its own class,
and a real HTTP request to a page with a dynamic Heading returns 200 with the
resolved value instead of a critical error.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
307ab778c0
commit
ea14a0da87
@@ -2,6 +2,15 @@
|
||||
|
||||
All notable changes to MCP Tools for Elementor are documented in this file.
|
||||
|
||||
## [3.13.1]
|
||||
|
||||
Fixes a critical error on the front end when a dynamic value is used on a standard Elementor widget. If you are on 3.13.0 and using dynamic data, please update.
|
||||
|
||||
### Fixed
|
||||
- **A page died with "There has been a critical error on this website" when a normal Elementor widget, such as a Heading, was given a dynamic value.** The dynamic picker offered every source and the editor looked right, so the problem only appeared once a visitor loaded the page.
|
||||
Elementor stores a dynamic source by class name and rebuilds it when the page renders. 3.13.0 registered all seventeen sources as one shared class that was told which source it was when it was created, and that instruction was lost by the time Elementor rebuilt it, so rendering failed outright. Each source is now its own class, which is how Elementor's own dynamic sources are built, so a rebuilt source always knows what it is. No page content or setting was damaged: affected pages render correctly again as soon as you update, with nothing to redo.
|
||||
The block editor was never affected. Its bindings use a different mechanism and worked throughout.
|
||||
|
||||
## [3.13.0]
|
||||
|
||||
Adds nine widgets and dynamic data to EMCP Themer, and rewrites how raw SQL is checked. Please update.
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
* Plugin Name: EMCP Tools
|
||||
* Plugin URI: https://github.com/msrbuilds/elementor-mcp
|
||||
* Description: Extends the WordPress MCP Adapter to expose Elementor data, widgets, and page design tools as MCP tools for AI agents.
|
||||
* Version: 3.13.0
|
||||
* Version: 3.13.1
|
||||
* Requires at least: 6.9
|
||||
* Tested up to: 6.9
|
||||
* Requires PHP: 8.1
|
||||
@@ -161,7 +161,7 @@ if ( EMCP_Tools_Migration::is_legacy_plugin_active() ) {
|
||||
}
|
||||
|
||||
// Plugin constants.
|
||||
define( 'EMCP_TOOLS_VERSION', '3.13.0' );
|
||||
define( 'EMCP_TOOLS_VERSION', '3.13.1' );
|
||||
define( 'EMCP_TOOLS_DIR', plugin_dir_path( __FILE__ ) );
|
||||
define( 'EMCP_TOOLS_URL', plugin_dir_url( __FILE__ ) );
|
||||
define( 'EMCP_TOOLS_BASENAME', plugin_basename( __FILE__ ) );
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* The concrete Elementor tag: one class parameterised by source key, rather
|
||||
* than a dozen near-identical subclasses.
|
||||
* The Elementor dynamic tag classes, one concrete class per source.
|
||||
*
|
||||
* Required only from inside elementor/dynamic_tags/register, where the
|
||||
* Elementor base class is guaranteed to exist.
|
||||
* ONE CLASS PER SOURCE IS NOT OPTIONAL, and a shared parameterised class does
|
||||
* not work. Elementor's registry keys by `get_name()` but stores only the class
|
||||
* name, and re-instantiates it later with the element data alone:
|
||||
*
|
||||
* $this->tags_info[ $tag->get_name() ] = [ 'class' => get_class( $tag ), ... ];
|
||||
* ...
|
||||
* return new $tag_class( [ 'settings' => …, 'id' => … ] );
|
||||
*
|
||||
* So the tag NAME is gone by the time the object is rebuilt. Registering the
|
||||
* same class under seventeen names made every one of them rebuild as the same
|
||||
* class with no way to know which source it was, and because that constructor
|
||||
* took the key first, the front end fataled with a TypeError the moment a
|
||||
* widget carrying a dynamic value rendered. The key has to live in the class
|
||||
* identity, which is how Elementor Pro's own tags are built too.
|
||||
*
|
||||
* These extend an Elementor base class, so the file is required only from
|
||||
* inside the `elementor/dynamic_tags/register` callback.
|
||||
*
|
||||
* @package EMCP_Tools
|
||||
* @since 3.13.0
|
||||
@@ -17,39 +31,47 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
if ( ! class_exists( 'EMCP_Tools_Themer_Elementor_Tag' ) && class_exists( '\Elementor\Core\DynamicTags\Tag' ) ) {
|
||||
|
||||
/**
|
||||
* Shared behaviour. Never registered directly: it has no source of its own.
|
||||
*
|
||||
* @since 3.13.0
|
||||
*/
|
||||
class EMCP_Tools_Themer_Elementor_Tag extends \Elementor\Core\DynamicTags\Tag {
|
||||
abstract class EMCP_Tools_Themer_Elementor_Tag extends \Elementor\Core\DynamicTags\Tag {
|
||||
|
||||
/**
|
||||
* Source key this instance renders.
|
||||
* The catalog key this class renders.
|
||||
*
|
||||
* @var string
|
||||
* Declared per subclass rather than passed in, because Elementor rebuilds
|
||||
* a tag from its class name alone.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private $source_key;
|
||||
abstract protected function source_key(): string;
|
||||
|
||||
/**
|
||||
* @param string $key Source key.
|
||||
* @param array $data Elementor element data.
|
||||
* Class name for a source key, or '' when the source has no tag class.
|
||||
*
|
||||
* @since 3.13.0
|
||||
* @param string $key Source key.
|
||||
* @return string
|
||||
*/
|
||||
public function __construct( string $key = 'post-title', array $data = array() ) {
|
||||
$this->source_key = $key;
|
||||
parent::__construct( $data );
|
||||
public static function class_for( string $key ): string {
|
||||
$class = 'EMCP_Tools_Themer_Elementor_Tag_' . str_replace( '-', '_', ucwords( $key, '-' ) );
|
||||
return class_exists( $class ) ? $class : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return EMCP_Tools_Themer_Elementor_Tags::tag_name( $this->source_key );
|
||||
return EMCP_Tools_Themer_Elementor_Tags::tag_name( $this->source_key() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
$def = EMCP_Tools_Themer_Dynamic_Catalog::get( $this->source_key );
|
||||
return $def ? $def['label'] : $this->source_key;
|
||||
$def = EMCP_Tools_Themer_Dynamic_Catalog::get( $this->source_key() );
|
||||
return $def ? $def['label'] : $this->source_key();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,14 +85,14 @@ if ( ! class_exists( 'EMCP_Tools_Themer_Elementor_Tag' ) && class_exists( '\Elem
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_categories() {
|
||||
$def = EMCP_Tools_Themer_Dynamic_Catalog::get( $this->source_key );
|
||||
$def = EMCP_Tools_Themer_Dynamic_Catalog::get( $this->source_key() );
|
||||
return EMCP_Tools_Themer_Elementor_Tags::categories_for( $def ? $def['type'] : 'html' );
|
||||
}
|
||||
|
||||
/** Echo the resolved value. */
|
||||
public function render() {
|
||||
$settings = method_exists( $this, 'get_settings' ) ? (array) $this->get_settings() : array();
|
||||
$v = EMCP_Tools_Themer_Dynamic::value( $this->source_key, $settings );
|
||||
$v = EMCP_Tools_Themer_Dynamic::value( $this->source_key(), $settings );
|
||||
|
||||
if ( 'image' === $v['type'] ) {
|
||||
// An image tag fills a URL control, so the URL is what it emits.
|
||||
@@ -86,4 +108,134 @@ if ( ! class_exists( 'EMCP_Tools_Themer_Elementor_Tag' ) && class_exists( '\Elem
|
||||
echo esc_html( (string) $v['value'] );
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Free sources.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Post_Title extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'post-title';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Post_Excerpt extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'post-excerpt';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Post_Url extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'post-url';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Post_Date extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'post-date';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Post_Id extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'post-id';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Featured_Image extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'featured-image';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Archive_Title extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'archive-title';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Site_Title extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'site-title';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Site_Logo extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'site-logo';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Description extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'description';
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Pro sources. The classes are free-tier plumbing that only names a key;
|
||||
// every value these resolve to comes from the Pro overlay, and the keys are
|
||||
// absent from the catalog without a licence, so these are never registered
|
||||
// on a free site.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Custom_Field extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'custom-field';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Author_Name extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'author-name';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Author_Bio extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'author-bio';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Author_Url extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'author-url';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Author_Avatar extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'author-avatar';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Term_Name extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'term-name';
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.13.0 */
|
||||
class EMCP_Tools_Themer_Elementor_Tag_Term_Url extends EMCP_Tools_Themer_Elementor_Tag {
|
||||
protected function source_key(): string {
|
||||
return 'term-url';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,20 @@ class EMCP_Tools_Themer_Elementor_Tags {
|
||||
return;
|
||||
}
|
||||
foreach ( self::taggable_keys() as $key ) {
|
||||
$manager->register( new EMCP_Tools_Themer_Elementor_Tag( $key ) );
|
||||
// Each source needs its OWN class: Elementor stores only the class
|
||||
// name and rebuilds the tag from it, so a shared class cannot tell
|
||||
// which source it was and the front end fatals on render.
|
||||
$class = EMCP_Tools_Themer_Elementor_Tag::class_for( $key );
|
||||
if ( '' === $class ) {
|
||||
// A catalogued source with no tag class. Skip rather than
|
||||
// register something that cannot be rebuilt.
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
error_log( '[EMCP Tools] dynamic source "' . $key . '" has no Elementor tag class; not registered.' );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$manager->register( new $class() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
Submodule pro updated: 162e934dbb...bb91167516
+5
-1
@@ -3,7 +3,7 @@ Contributors: mianshahzadraza
|
||||
Tags: elementor, mcp, ai, page-builder, automation
|
||||
Requires at least: 6.9
|
||||
Tested up to: 7.0
|
||||
Stable tag: 3.13.0
|
||||
Stable tag: 3.13.1
|
||||
Requires PHP: 8.1
|
||||
License: GPLv2 or later
|
||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
@@ -176,6 +176,10 @@ On shared LiteSpeed hosting (e.g. Hostinger) this is usually the host caching/bu
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 3.13.1 =
|
||||
Fixes a critical error on the front end when a dynamic value is used on a standard Elementor widget. If you are on 3.13.0 and using dynamic data, please update.
|
||||
* Fixed: a page showed "There has been a critical error on this website" when a normal Elementor widget such as a Heading was given a dynamic value. Elementor rebuilds a dynamic source from its class when the page renders, and 3.13.0 used one shared class for all seventeen sources, so the rebuilt source no longer knew which one it was. Each source is now its own class. Nothing was damaged: affected pages render again as soon as you update, with nothing to redo. The block editor was never affected.
|
||||
|
||||
= 3.13.0 =
|
||||
Adds nine widgets and dynamic data to EMCP Themer, and rewrites how raw SQL is checked. Please update.
|
||||
* Added: nine new Themer widgets, so a Single, Archive, Search or 404 template can actually be finished: Author Box, Post Navigation, Post Comments, Search Form, Sitemap, Post Info, Archive Posts, Featured Image and Post Excerpt. All free. Author Box and Sitemap also ship as blocks; comments, search and post navigation already exist as core blocks so they are not duplicated.
|
||||
|
||||
Reference in New Issue
Block a user