PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.25
MxChat – AI Chatbot & Content Generation for WordPress v3.2.25
3.2.25 3.2.24 3.2.23 3.2.22 3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 All 156 releases
← All changes | includes/class-mxchat-public.php +82 -2 3.2.23 → 3.2.25 View file →
@@ -11,8 +11,23 @@
11 11 // yields, so an explicit floating placement never doubles up with the
12 12 // Auto-Display instance (plan 95dd1e).
13 13 private static $floating_instance_rendered = false;
14 14
15 + // Bot ids already placed on this page (plan 977cad), floating and inline
16 + // alike. Two copies of one bot share every element id (chat-input-<bot>,
17 + // send-button-<bot>, ...) and chat-script.js looks elements up by id, so
18 + // the second copy's Send reads the FIRST copy's empty box and sends
19 + // nothing. One copy of a bot per page: append_chatbot_to_body() stands
20 + // down for a bot the content already shows, and a second explicit
21 + // placement of the same bot renders an administrator-only note instead.
22 + // Only page renders count: a render in wp-admin, admin-ajax, cron, REST,
23 + // WP-CLI, a feed, or inside wp_head (a plugin building meta tags from the
24 + // content) is not on the page and must not suppress the real one. Note
25 + // that a block theme renders the body BEFORE wp_head (template-canvas
26 + // builds the template first so blocks can enqueue), so "after wp_head"
27 + // is not the test.
28 + private static $rendered_bot_ids = array();
29 +
15 30 public function __construct() {
16 31 // Simply get the options without defining duplicated defaults
17 32 $this->options = get_option('mxchat_options', array());
18 33 add_shortcode('mxchat_chatbot', array($this, 'render_chatbot_shortcode'));
@@ -79,8 +94,17 @@
79 94 // Don't show chatbot if determination is false
80 95 if ($bot_to_show === false) {
81 96 return;
82 97 }
98 +
99 + // The bot is already on this page as an explicit placement (an inline
100 + // shortcode or block in the content). A second, floating copy would share
101 + // its element ids and could be opened but not send; the embedded one wins
102 + // (plan 977cad). A page whose embedded chatbot is a DIFFERENT bot keeps
103 + // both, their ids differ.
104 + if (self::is_bot_on_page($bot_to_show)) {
105 + return;
106 + }
83 107
84 108 // Handle consent for display
85 109 $consent_category = 'marketing';
86 110 $has_consent = true;
@@ -102,8 +126,56 @@
102 126 echo do_shortcode('[mxchat_chatbot floating="yes" has_consent="' . ($has_consent ? 'yes' : 'no') . '"]');
103 127 }
104 128 }
105 129
130 + /**
131 + * Is this render on the page a visitor sees? (plan 977cad) False in
132 + * wp-admin, admin-ajax, cron, REST, WP-CLI, a feed, and inside wp_head.
133 + * Not "after wp_head": block themes render the body content first.
134 + */
135 + private static function is_page_render() {
136 + if (is_admin() || wp_doing_ajax() || wp_doing_cron()
137 + || (defined('REST_REQUEST') && REST_REQUEST)
138 + || (defined('WP_CLI') && WP_CLI)) {
139 + return false;
140 + }
141 + return !doing_action('wp_head') && !is_feed();
142 + }
143 +
144 + private static function record_bot_on_page($bot_id) {
145 + if (!self::is_page_render()) {
146 + return;
147 + }
148 + $bot_id = sanitize_key($bot_id);
149 + if ($bot_id !== '' && !in_array($bot_id, self::$rendered_bot_ids, true)) {
150 + self::$rendered_bot_ids[] = $bot_id;
151 + }
152 + }
153 +
154 + /**
155 + * Has a placement of this bot already rendered in the page body on this
156 + * request? (plan 977cad)
157 + */
158 + public static function is_bot_on_page($bot_id) {
159 + if (!self::is_page_render()) {
160 + return false;
161 + }
162 + return in_array(sanitize_key($bot_id), self::$rendered_bot_ids, true);
163 + }
164 +
165 + /**
166 + * What a second placement of an already-shown bot renders: nothing for
167 + * visitors, one line for a logged-in administrator (plan 977cad).
168 + */
169 + private static function duplicate_placement_notice() {
170 + if (!current_user_can('manage_options')) {
171 + return '';
172 + }
173 + return '<p class="mxchat-placement-notice" data-nosnippet>'
174 + . esc_html__('MxChat: this page already shows this chatbot, so a second copy of the same bot is not displayed. Only administrators see this note.', 'mxchat')
175 + . '</p>';
176 + }
177 +
106 178 private function mxchat_get_user_identifier() {
107 179 return sanitize_text_field($_SERVER['REMOTE_ADDR']);
108 180 }
109 181
@@ -132,9 +204,18 @@
132 204 'bot_id' => '' // Support for multi-bot functionality - empty means auto-detect
133 205 ), $atts);
134 206
135 207 // Determine which bot to use
136 - $bot_id = $this->determine_bot_for_shortcode($attributes['bot_id']);
208 + $bot_id = sanitize_key($this->determine_bot_for_shortcode($attributes['bot_id']));
209 +
210 + // A second placement of a bot already on this page is not rendered
211 + // (plan 977cad, see $rendered_bot_ids): it could be opened but never
212 + // sent. Administrators get a one-line note in its place so the
213 + // duplicate is visible to the person who can remove it.
214 + if (self::is_bot_on_page($bot_id)) {
215 + return self::duplicate_placement_notice();
216 + }
217 + self::record_bot_on_page($bot_id);
137 218
138 219 // A floating shortcode always renders; what it does is suppress the LATER
139 220 // auto-append instance (see append_chatbot_to_body), so a page never gets
140 221 // two floating widgets. This replaced a dead empty branch here that
@@ -146,9 +227,8 @@
146 227 // Non-floating shortcodes (floating="no") should NEVER be blocked by the hide setting
147 228 // This allows embedded chatbots even when floating is hidden
148 229
149 230 $is_floating = $attributes['floating'] === 'yes';
150 - $bot_id = sanitize_key($bot_id); // Sanitize bot ID
151 231
152 232 // Rest of your existing shortcode rendering logic continues unchanged...
153 233 // [All the existing HTML generation code remains the same]
154 234