Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- 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,