PluginProbe
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services / 8.8.2
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services v8.8.2
8.8.2 8.8.1 8.8.0 8.7.9 8.7.8 8.7.7 8.7.6 8.7.5 8.7.4 8.7.3 8.7.2 8.7.1 8.7.0 8.6.9 8.6.8 8.6.7 8.6.6 8.6.5 8.6.4 8.6.2 8.6.1 8.6.0 8.5.9 8.5.8 8.5.7 All 538 releases
← All changes | includes/integration/claude/qcld-bot-claude.php +40 -20 8.7.2 → 8.8.2 View file →
@@ -65,10 +65,10 @@
65 65 }
66 66
67 67 public function qcld_claude_settings_option_callback() {
68 68 $nonce = sanitize_text_field( $_POST['nonce'] );
69 - if ( ! wp_verify_nonce( $nonce, 'wp_chatbot' ) ) {
70 - wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Failed in Security check', 'wpchatbot' ) ) );
69 + if ( ! wp_verify_nonce( $nonce, 'wp_chatbot' ) || ! current_user_can( 'manage_options' ) ) {
70 + wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Failed in Security check', 'chatbot' ) ) );
71 71 wp_die();
72 72 } else {
73 73 $claude_api_key = sanitize_text_field( $_POST['claude_api_key'] ?? '' );
74 74 $voyage_api_key = sanitize_text_field( $_POST['voyage_api_key'] ?? '' );
@@ -126,11 +126,19 @@
126 126 $result = wp_remote_post($api_url, $args);
127 127 $result_body = json_decode(wp_remote_retrieve_body($result), true);
128 128
129 129 if( isset($result_body['error']) ) {
130 - wp_send_json( array( 'status' => 'error', 'msg' => esc_html__( $result_body['error']['message'], 'chatbot' ) ) );
130 + wp_send_json( array( 'status' => 'error', 'msg' => esc_html( $result_body['error']['message'] ) ) );
131 131 } elseif ( isset($result_body['content']) && is_array($result_body['content']) ) {
132 - wp_send_json( array( 'status' => 'success', 'msg' => esc_html__( $result_body['content'][0]['text'], 'chatbot' ) ) );
132 + $text_content = '';
133 + foreach ($result_body['content'] as $block) {
134 + if (isset($block['type']) && $block['type'] === 'text' && !empty($block['text'])) {
135 + $text_content .= $block['text'];
136 + }
137 + }
138 + if (!empty($text_content)) {
139 + wp_send_json( array( 'status' => 'success', 'msg' => esc_html( $text_content ) ) );
140 + }
133 141 }
134 142 wp_die();
135 143 }
136 144
@@ -187,8 +195,9 @@
187 195 ];
188 196
189 197 $api_url = 'https://api.anthropic.com/v1/messages';
190 198
199 + // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_init, WordPress.WP.AlternativeFunctions.curl_curl_setopt, WordPress.WP.AlternativeFunctions.curl_curl_exec, WordPress.WP.AlternativeFunctions.curl_curl_errno, WordPress.WP.AlternativeFunctions.curl_curl_error, WordPress.WP.AlternativeFunctions.curl_curl_close -- SSE streaming requires cURL write callback.
191 200 $ch = curl_init( $api_url );
192 201 curl_setopt( $ch, CURLOPT_POST, true );
193 202 curl_setopt( $ch, CURLOPT_HTTPHEADER, [
194 203 'Content-Type: application/json',
@@ -209,10 +218,10 @@
209 218 }
210 219 if ( isset($decoded['type']) && $decoded['type'] === 'content_block_delta' ) {
211 220 $text_delta = $decoded['delta']['text'];
212 221 $payload = json_encode( [ 'choices' => [ [ 'delta' => [ 'content' => $text_delta ] ] ] ] );
213 - echo 'data: ' . $payload . "\n\n";
214 - echo str_repeat( ' ', 1024 );
222 + echo 'data: ' . $payload . "\n\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- SSE streaming raw JSON output
223 + echo str_repeat( ' ', 1024 ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- SSE streaming padding
215 224 flush();
216 225 }
217 226 }
218 227 }
@@ -220,11 +229,13 @@
220 229 } );
221 230
222 231 curl_exec( $ch );
223 232 if ( curl_errno( $ch ) ) {
224 - echo 'data: [ERROR] ' . curl_error( $ch ) . "\n\n"; flush();
233 + echo 'data: [ERROR] ' . esc_html( curl_error( $ch ) ) . "\n\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- curl_error is already escaped above
234 + flush();
225 235 }
226 236 curl_close( $ch );
237 + // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_init, WordPress.WP.AlternativeFunctions.curl_curl_setopt, WordPress.WP.AlternativeFunctions.curl_curl_exec, WordPress.WP.AlternativeFunctions.curl_curl_errno, WordPress.WP.AlternativeFunctions.curl_curl_error, WordPress.WP.AlternativeFunctions.curl_curl_close
227 238 do_action( 'qcld_openai_user_rate_cal', 1 );
228 239 exit;
229 240 }
230 241
@@ -249,9 +260,9 @@
249 260 $relevant_pagelinks = '<br><br><p><em>' . $relevant_post_link[ get_wpbot_locale() ] . '</em><p>' . implode( '</br>', $relevant_pagelink );
250 261 }
251 262 }
252 263
253 - $Parsedown = new Parsedown();
264 + $Parsedown = new Qcld_Parsedown();
254 265
255 266 $claude_model = get_option( 'qcld_claude_model' );
256 267 if ( empty( $claude_model ) || strpos( $claude_model, 'latest' ) !== false ) { $claude_model = 'claude-sonnet-4-6'; }
257 268 $api_url = 'https://api.anthropic.com/v1/messages';
@@ -305,9 +316,10 @@
305 316 $system_content .= "2. Wait for the user's response before asking the next question.\n";
306 317 $system_content .= "3. Once all necessary information is collected for the form, you MUST output a final JSON block summarizing the collected data. The keys inside the \"data\" object MUST be dynamically named based on the specific questions you asked during the form collection (e.g., \"Full Name\", \"Company Size\", \"Email\", etc.). The final JSON block must be wrapped EXACTLY in these delimiters:\n";
307 318 $system_content .= "__AI_FORM_DATA__{ \"form_title\": \"<Form Title>\", \"data\": { \"Question 1\": \"Answer 1\", \"Question 2\": \"Answer 2\" } }__AI_FORM_DATA_END__\n";
308 319 $system_content .= "Do not include any other text after this JSON block once the form is complete.\n";
309 - $system_content .= "4. If the user provides an invalid, irrelevant, or nonsensical answer to your question, DO NOT apologize or state that you lack information. Instead, respond with 'Invalid answer found' and ask the exact same question again.";
320 + $system_content .= "4. If the user provides an invalid, irrelevant, or nonsensical answer to your question, DO NOT apologize or state that you lack information. Instead, politely inform the user in the language of the conversation (the language the user is speaking in, e.g. German if communicating in German) that their answer is invalid or cannot be assigned, and ask the exact same question again in the user's language. NEVER output the English phrase 'Invalid answer found' unless the user is communicating in English.\n";
321 + $system_content .= "5. MULTI-LANGUAGE SUPPORT: Always communicate, ask questions, validate answers, and reply strictly in the user's language (matching the language used by the user, e.g. German, French, Spanish, etc.). Understand and accept valid answers in the user's language.";
310 322 }
311 323
312 324 $contents = $history;
313 325 $last_msg = end($contents);
@@ -350,18 +362,26 @@
350 362 $http_code = wp_remote_retrieve_response_code($result);
351 363 $body = wp_remote_retrieve_body($result);
352 364 $msg = json_decode($body, true);
353 365
354 - if (isset($msg['content']) && is_array($msg['content']) && !empty($msg['content'][0]['text'])) {
355 - $response['status'] = 'success';
356 - $reply_text = $Parsedown->text( $msg['content'][0]['text'] );
357 - if (strpos($reply_text, 'AI_FORM_DATA') !== false) {
358 - $reply_text = Qcld_WPBot_Common_Functions::format_and_save_ai_form_response($reply_text);
359 - $response['message'] = $reply_text;
360 - } else {
361 - $response['message'] = $reply_text . $relevant_pagelinks;
366 + if (isset($msg['content']) && is_array($msg['content'])) {
367 + $text_content = '';
368 + foreach ($msg['content'] as $block) {
369 + if (isset($block['type']) && $block['type'] === 'text' && !empty($block['text'])) {
370 + $text_content .= $block['text'];
371 + }
362 372 }
363 - break;
373 + if (!empty($text_content)) {
374 + $response['status'] = 'success';
375 + $reply_text = $Parsedown->text( $text_content );
376 + if (strpos($reply_text, 'AI_FORM_DATA') !== false) {
377 + $reply_text = Qcld_WPBot_Common_Functions::format_and_save_ai_form_response($reply_text);
378 + $response['message'] = $reply_text;
379 + } else {
380 + $response['message'] = $reply_text . $relevant_pagelinks;
381 + }
382 + break;
383 + }
364 384 }
365 385
366 386 if (($http_code == 503 || $http_code == 429) && $attempt < $max_attempts) {
367 387 $wait_seconds = $attempt * 2;
@@ -398,15 +418,15 @@
398 418 $content = $u_matches[1];
399 419 } else {
400 420 $clean_li = preg_replace('/<div[^>]*class=["\'][^"\']*wp-chatbot-avatar[^"\']*["\'][^>]*>.*?<\/div>/is', '', $li_html);
401 421 $clean_li = preg_replace('/<div[^>]*class=["\'][^"\']*wp-chatbot-agent[^"\']*["\'][^>]*>.*?<\/div>/is', '', $clean_li);
402 - $content = strip_tags($clean_li);
422 + $content = wp_strip_all_tags( $clean_li );
403 423 }
404 424
405 425 $content = preg_replace('/<br\s*\/?>/i', "\n", $content);
406 426 $content = preg_replace('/<div[^>]*class=["\'][^"\']*relevant-links[^"\']*["\'][^>]*>.*?<\/div>/is', '', $content);
407 427 $content = preg_replace('/<span[^>]*class=["\'][^"\']*qcld-chatbot-wildcard[^"\']*["\'][^>]*>.*?<\/span>/is', '', $content);
408 - $content = trim(strip_tags($content));
428 + $content = trim( wp_strip_all_tags( $content ) );
409 429 $content = html_entity_decode($content, ENT_QUOTES, 'UTF-8');
410 430
411 431 if (!empty($content)) {
412 432 $messages[] = [