PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.1.5
MxChat – AI Chatbot & Content Generation for WordPress v3.1.5
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 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | admin/class-knowledge-manager.php +114 -151 3.2.23.1.5 View file →
@@ -59,8 +59,11 @@
59 59 add_action('wp_ajax_mxchat_paginate_entries', array($this, 'ajax_mxchat_paginate_entries'));
60 60 add_action('wp_ajax_mxchat_get_entry_content', array($this, 'ajax_mxchat_get_entry_content'));
61 61 add_action('wp_ajax_mxchat_save_entry_content', array($this, 'ajax_mxchat_save_entry_content'));
62 62
63 + // Hook for content deletion
64 + add_action('mxchat_delete_content', array($this, 'mxchat_delete_from_pinecone_by_url'), 10, 1);
65 +
63 66 // WordPress post management hooks
64 67 add_action('pre_post_update', array($this, 'mxchat_store_pre_update_status'), 10, 2);
65 68 add_action('post_updated', array($this, 'mxchat_handle_post_update'), 10, 3);
66 69 add_action('before_delete_post', array($this, 'mxchat_handle_post_delete'));
@@ -160,13 +163,9 @@
160 163 public function mxchat_is_pdf_url($url, $response) {
161 164 $content_type = wp_remote_retrieve_header($response, 'content-type');
162 165 $file_extension = strtolower(pathinfo($url, PATHINFO_EXTENSION));
163 166
164 - // Check Content-Disposition header for .pdf filename (Google Drive sends this)
165 - $disposition = wp_remote_retrieve_header($response, 'content-disposition');
166 - $has_pdf_disposition = ! empty($disposition) && stripos($disposition, '.pdf') !== false;
167 -
168 - return strpos($content_type, 'pdf') !== false || $file_extension === 'pdf' || $has_pdf_disposition;
167 + return strpos($content_type, 'pdf') !== false || $file_extension === 'pdf';
169 168 }
170 169
171 170
172 171 public function mxchat_handle_pdf_for_knowledge_base($pdf_url, $response, $bot_id = 'default') {
@@ -860,17 +859,10 @@
860 859 }
861 860
862 861 // For manual entries (no source_url or mxchat:// prefix), delete the old entry by ID first
863 862 // so submit_content_to_db creates a replacement instead of a duplicate
864 - // Also treat legacy mxchat.ai source URLs as manual — old bug assigned the site URL to manual entries
865 - $is_legacy_manual = !empty($source_url) && strpos($source_url, 'mxchat.ai') !== false && strpos($source_url, 'mxchat://') !== 0;
866 - if ( $entry_id > 0 && (empty($source_url) || strpos($source_url, 'mxchat://') === 0 || $is_legacy_manual) ) {
863 + if ( $entry_id > 0 && (empty($source_url) || strpos($source_url, 'mxchat://') === 0) ) {
867 864 $wpdb->delete( $table, array( 'id' => $entry_id ), array( '%d' ) );
868 - // Clear legacy URL so submit_content_to_db generates a unique mxchat:// identifier
869 - // instead of reusing the shared URL (which would mass-delete other entries with the same URL)
870 - if ( $is_legacy_manual ) {
871 - $source_url = '';
872 - }
873 865 }
874 866
875 867 // Use the existing submit_content_to_db which handles chunking, Pinecone, and WP DB
876 868 $vector_id = ! empty($source_url) ? md5($source_url) : md5('mxchat_manual_' . $entry_id);
@@ -947,22 +939,9 @@
947 939 exit;
948 940 }
949 941
950 942 $submitted_url = esc_url_raw($_POST['sitemap_url']);
951 -
952 - // Convert Google Drive sharing URLs to direct download URLs
953 - if ( strpos($submitted_url, 'drive.google.com') !== false ) {
954 - $file_id = '';
955 - if ( preg_match('/[?&]id=([a-zA-Z0-9_-]+)/', $submitted_url, $m) ) {
956 - $file_id = $m[1];
957 - } elseif ( preg_match('#/file/d/([a-zA-Z0-9_-]+)#', $submitted_url, $m) ) {
958 - $file_id = $m[1];
959 - }
960 - if ( ! empty($file_id) ) {
961 - $submitted_url = 'https://drive.google.com/uc?export=download&id=' . $file_id;
962 - }
963 - }
964 -
943 +
965 944 // Get bot_id from form submission
966 945 $bot_id = isset($_POST['bot_id']) ? sanitize_key($_POST['bot_id']) : 'default';
967 946
968 947 // Get bot-specific options and validate API key
@@ -990,18 +969,10 @@
990 969 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
991 970 exit;
992 971 }
993 972
994 - // Fetch URL — use browser-like headers so servers with bot protection don't block us
995 - $response = wp_remote_get($submitted_url, array(
996 - 'timeout' => 30,
997 - 'sslverify' => false,
998 - 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
999 - 'headers' => array(
1000 - 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
1001 - 'Accept-Language' => 'en-US,en;q=0.9',
1002 - ),
1003 - ));
973 + // Fetch URL
974 + $response = wp_remote_get($submitted_url, array('timeout' => 30));
1004 975
1005 976 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
1006 977 $error_message = is_wp_error($response) ? $response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($response);
1007 978 set_transient('mxchat_admin_notice_error',
@@ -2835,12 +2806,11 @@
2835 2806 foreach ($primary_indexes as $path => $source) {
2836 2807 $url = trailingslashit($site_url) . $path;
2837 2808
2838 2809 $response = wp_remote_head($url, array(
2839 - 'timeout' => 10,
2810 + 'timeout' => 3, // Short timeout
2840 2811 'sslverify' => false,
2841 - 'redirection' => 1,
2842 - 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
2812 + 'redirection' => 1
2843 2813 ));
2844 2814
2845 2815 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
2846 2816 // Found a sitemap index - parse it to get sub-sitemaps
@@ -2898,15 +2868,10 @@
2898 2868 private function parse_sitemap_index($url) {
2899 2869 $sub_sitemaps = array();
2900 2870
2901 2871 $response = wp_remote_get($url, array(
2902 - 'timeout' => 30,
2903 - 'sslverify' => false,
2904 - 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
2905 - 'headers' => array(
2906 - 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
2907 - 'Accept-Language' => 'en-US,en;q=0.9',
2908 - ),
2872 + 'timeout' => 5,
2873 + 'sslverify' => false
2909 2874 ));
2910 2875
2911 2876 if (is_wp_error($response)) {
2912 2877 return $sub_sitemaps;
@@ -2957,15 +2922,10 @@
2957 2922 * Get URL count from a sitemap
2958 2923 */
2959 2924 private function get_sitemap_url_count($url) {
2960 2925 $response = wp_remote_get($url, array(
2961 - 'timeout' => 30,
2962 - 'sslverify' => false,
2963 - 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
2964 - 'headers' => array(
2965 - 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
2966 - 'Accept-Language' => 'en-US,en;q=0.9',
2967 - ),
2926 + 'timeout' => 10,
2927 + 'sslverify' => false
2968 2928 ));
2969 2929
2970 2930 if (is_wp_error($response)) {
2971 2931 return 0;
@@ -2988,11 +2948,10 @@
2988 2948 $sitemaps = array();
2989 2949 $robots_url = trailingslashit($site_url) . 'robots.txt';
2990 2950
2991 2951 $response = wp_remote_get($robots_url, array(
2992 - 'timeout' => 15,
2993 - 'sslverify' => false,
2994 - 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
2952 + 'timeout' => 5,
2953 + 'sslverify' => false
2995 2954 ));
2996 2955
2997 2956 if (is_wp_error($response)) {
2998 2957 return $sitemaps;
@@ -5181,33 +5140,40 @@
5181 5140 // If the post was previously published but is now not published, remove from knowledge base
5182 5141 if ($previous_status === 'publish' && $post->post_status !== 'publish') {
5183 5142 // Use the stored URL from when it was published, or fall back to current permalink
5184 5143 $source_url = $previous_url ?: get_permalink($post_id);
5144 +
5145 + if ($source_url) {
5146 + // Check if Pinecone is enabled
5147 + $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
5148 + $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
5185 5149
5186 - if ($source_url) {
5187 - // Chunk-aware deletion (routes to Pinecone or WP DB and removes base + all chunks)
5188 - MxChat_Utils::delete_chunks_for_url($source_url, 'default');
5150 + if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
5151 + // Delete from Pinecone
5152 + $this->mxchat_delete_from_pinecone_by_url($source_url, $pinecone_options);
5153 + } else {
5154 + // Delete from WordPress DB
5155 + global $wpdb;
5156 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
5157 +
5158 + $result = $wpdb->delete(
5159 + $table_name,
5160 + array('source_url' => $source_url),
5161 + array('%s')
5162 + );
5163 + }
5189 5164 }
5190 -
5165 +
5191 5166 // Clean up the transients and exit early
5192 5167 delete_transient($previous_status_key);
5193 5168 delete_transient($previous_url_key);
5194 5169 return;
5195 5170 }
5196 -
5197 - // Slug/permalink rename while still published: delete the old vectors before upserting new ones.
5198 - // Without this, md5(old_url) vectors (base + chunks) would be orphaned under the stale URL.
5199 - if ($post->post_status === 'publish' && !empty($previous_url)) {
5200 - $current_url = get_permalink($post_id);
5201 - if ($current_url && $current_url !== $previous_url) {
5202 - MxChat_Utils::delete_chunks_for_url($previous_url, 'default');
5203 - }
5204 - }
5205 -
5171 +
5206 5172 // Store the current status for next time (if this is an update)
5207 5173 if ($update) {
5208 5174 set_transient($previous_status_key, $post->post_status, DAY_IN_SECONDS);
5209 -
5175 +
5210 5176 // If the post is currently published, also store its URL
5211 5177 if ($post->post_status === 'publish') {
5212 5178 $current_url = get_permalink($post_id);
5213 5179 set_transient($previous_url_key, $current_url, DAY_IN_SECONDS);
@@ -5437,14 +5403,12 @@
5437 5403 if (!$should_sync) {
5438 5404 return;
5439 5405 }
5440 5406
5441 - // Resolve the pre-trash URL. wp_trash_post renames the slug with "__trashed" before firing
5442 - // this hook, so get_permalink() here would return the trashed URL and md5() would miss the
5443 - // real vector IDs stored under the original URL.
5444 - $source_url = $this->mxchat_resolve_pre_trash_url($post_id);
5407 + // Get the URL before post is deleted
5408 + $source_url = get_permalink($post_id);
5445 5409 if (!$source_url) {
5446 - //error_log('MXChat: Failed to resolve source URL for post ' . $post_id);
5410 + //error_log('MXChat: Failed to get permalink for post ' . $post_id);
5447 5411 return;
5448 5412 }
5449 5413
5450 5414 // Use chunk-aware deletion (handles both chunked and non-chunked content)
@@ -5452,36 +5416,56 @@
5452 5416
5453 5417 if (is_wp_error($delete_result)) {
5454 5418 //error_log('MXChat: Chunk-aware deletion failed for URL: ' . $source_url . ' - ' . $delete_result->get_error_message());
5455 5419 }
5420 +}
5456 5421
5457 - delete_transient('mxchat_prev_url_' . $post_id);
5458 - delete_transient('mxchat_prev_status_' . $post_id);
5459 -}
5460 5422
5461 -/**
5462 - * Resolve the source URL for a post being trashed/deleted.
5463 - *
5464 - * Why: wp_trash_post appends "__trashed" to the slug before the wp_trash_post action fires, so
5465 - * get_permalink() returns a URL whose md5() won't match the vector IDs stored in Pinecone or
5466 - * the source_url rows in the WP DB. Prefer the URL captured by mxchat_store_pre_update_status
5467 - * (runs on pre_post_update, before the rename); fall back to stripping the __trashed suffix.
5468 - */
5469 -private function mxchat_resolve_pre_trash_url($post_id) {
5470 - $previous_url = get_transient('mxchat_prev_url_' . $post_id);
5471 - if (!empty($previous_url)) {
5472 - return $previous_url;
5473 - }
5423 + /**
5424 + * Deletes data from Pinecone using a source URL
5425 + */
5426 + public function mxchat_delete_from_pinecone_by_url($source_url, $pinecone_options) {
5427 + $host = $pinecone_options['mxchat_pinecone_host'] ?? '';
5428 + $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
5474 5429
5475 - $current = get_permalink($post_id);
5476 - if (!$current) {
5477 - return '';
5478 - }
5479 - return preg_replace('#__trashed(/?)$#', '$1', $current);
5480 -}
5430 + if (empty($host) || empty($api_key)) {
5431 + //error_log('MXChat: Pinecone deletion failed - missing configuration');
5432 + return false;
5433 + }
5481 5434
5435 + $api_endpoint = "https://{$host}/vectors/delete";
5436 + $vector_id = md5($source_url);
5482 5437
5438 + $request_body = array(
5439 + 'ids' => array($vector_id)
5440 + );
5483 5441
5442 + $response = wp_remote_post($api_endpoint, array(
5443 + 'headers' => array(
5444 + 'Api-Key' => $api_key,
5445 + 'accept' => 'application/json',
5446 + 'content-type' => 'application/json'
5447 + ),
5448 + 'body' => wp_json_encode($request_body),
5449 + 'timeout' => 30
5450 + ));
5451 +
5452 + if (is_wp_error($response)) {
5453 + //error_log('MXChat: Pinecone deletion error - ' . $response->get_error_message());
5454 + return false;
5455 + }
5456 +
5457 + $response_code = wp_remote_retrieve_response_code($response);
5458 + if ($response_code !== 200) {
5459 + //error_log('MXChat: Pinecone deletion failed with status ' . $response_code);
5460 + return false;
5461 + }
5462 +
5463 + return true;
5464 + }
5465 +
5466 +
5467 +
5484 5468 public function mxchat_handle_product_change($post_id, $post, $update) {
5485 5469 if ($post->post_type !== 'product') {
5486 5470 return;
5487 5471 }
@@ -5633,18 +5617,28 @@
5633 5617 if (get_post_type($post_id) !== 'product') {
5634 5618 return;
5635 5619 }
5636 5620
5637 - $source_url = $this->mxchat_resolve_pre_trash_url($post_id);
5638 - if (!$source_url) {
5639 - return;
5640 - }
5621 + $source_url = get_permalink($post_id);
5641 5622
5642 - // Chunk-aware deletion (routes to Pinecone or WP DB and removes base + all chunks)
5643 - MxChat_Utils::delete_chunks_for_url($source_url, 'default');
5623 + // Check if Pinecone is enabled
5624 + $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
5625 + $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
5644 5626
5645 - delete_transient('mxchat_prev_url_' . $post_id);
5646 - delete_transient('mxchat_prev_status_' . $post_id);
5627 + if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
5628 + // Delete from Pinecone
5629 + $this->mxchat_delete_from_pinecone_by_url($source_url, $pinecone_options);
5630 + } else {
5631 + // Delete from WordPress DB
5632 + global $wpdb;
5633 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
5634 +
5635 + $wpdb->delete(
5636 + $table_name,
5637 + array('source_url' => $source_url),
5638 + array('%s')
5639 + );
5640 + }
5647 5641 }
5648 5642
5649 5643 /**
5650 5644 * Handle individual Pinecone content deletion
@@ -7083,37 +7077,11 @@
7083 7077 throw new Exception('Unknown item type: ' . $item_type);
7084 7078 }
7085 7079
7086 7080 if (is_wp_error($result)) {
7087 - $error_code = $result->get_error_code();
7088 - // Content errors (empty page, sanitization) are permanent — retrying won't help
7089 - $permanent_codes = array('empty_page', 'empty_after_sanitization', 'no_api_key', 'page_not_found');
7090 - if (in_array($error_code, $permanent_codes)) {
7091 - // Mark as permanently failed — set attempts = max_attempts so it won't be retried
7092 - $current_item = $wpdb->get_row($wpdb->prepare(
7093 - "SELECT max_attempts FROM $table_name WHERE id = %d", $item_id
7094 - ));
7095 - $wpdb->update(
7096 - $table_name,
7097 - array(
7098 - 'status' => 'failed',
7099 - 'error_message' => $result->get_error_message(),
7100 - 'attempts' => $current_item ? $current_item->max_attempts : 3
7101 - ),
7102 - array('id' => $item_id),
7103 - array('%s', '%s', '%d'),
7104 - array('%d')
7105 - );
7106 - wp_send_json_error(array(
7107 - 'message' => $result->get_error_message(),
7108 - 'permanent_failure' => true,
7109 - 'item_id' => $item_id
7110 - ));
7111 - return;
7112 - }
7113 7081 throw new Exception($result->get_error_message());
7114 7082 }
7115 -
7083 +
7116 7084 if ($result === false) {
7117 7085 throw new Exception('Processing returned false - item may be empty or invalid');
7118 7086 }
7119 7087
@@ -7403,24 +7371,21 @@
7403 7371 return new WP_Error('no_pages', 'PDF has no pages');
7404 7372 }
7405 7373
7406 7374 $processed = 0;
7407 - $skipped_pages = array();
7408 7375
7409 7376 for ($i = 0; $i < $total_pages; $i++) {
7410 - $page_num = $i + 1;
7411 7377 $text = $pages[$i]->getText();
7412 7378 if (empty($text)) {
7413 - $skipped_pages[] = 'Page ' . $page_num . ': No text could be extracted — page may contain only images, links, or non-standard encoding';
7414 7379 continue;
7415 7380 }
7416 7381
7417 7382 $sanitized = $this->mxchat_sanitize_content_for_api($text);
7418 7383 if (empty($sanitized)) {
7419 - $skipped_pages[] = 'Page ' . $page_num . ': Text was extracted but contained only special characters, control codes, or unsupported content';
7420 7384 continue;
7421 7385 }
7422 7386
7387 + $page_num = $i + 1;
7423 7388 $metadata = array(
7424 7389 'document_type' => 'pdf',
7425 7390 'total_pages' => $total_pages,
7426 7391 'current_page' => $page_num,
@@ -7444,12 +7409,8 @@
7444 7409
7445 7410 // Clean up the temp PDF file
7446 7411 wp_delete_file($pdf_path);
7447 7412
7448 - if (!empty($skipped_pages)) {
7449 - error_log('MxChat PDF: Skipped ' . count($skipped_pages) . ' of ' . $total_pages . ' pages: ' . implode('; ', $skipped_pages));
7450 - }
7451 -
7452 7413 return $processed > 0 ? true : false;
7453 7414
7454 7415 } catch (Exception $e) {
7455 7416 if (file_exists($pdf_path)) {
@@ -7614,17 +7575,18 @@
7614 7575 return new WP_Error('page_not_found', 'Page ' . $page_number . ' not found in PDF');
7615 7576 }
7616 7577
7617 7578 $text = $pages[$page_number - 1]->getText();
7618 -
7579 +
7619 7580 if (empty($text)) {
7620 - return new WP_Error('empty_page', 'Page ' . $page_number . ': No text could be extracted — page may contain only images, links, or non-standard encoding');
7581 + // Not an error - just an empty page
7582 + return false;
7621 7583 }
7622 -
7584 +
7623 7585 $sanitized = $this->mxchat_sanitize_content_for_api($text);
7624 -
7586 +
7625 7587 if (empty($sanitized)) {
7626 - return new WP_Error('empty_after_sanitization', 'Page ' . $page_number . ': Text was extracted but contained only special characters, control codes, or unsupported content that was removed during cleanup');
7588 + return false;
7627 7589 }
7628 7590
7629 7591 // Create metadata
7630 7592 $metadata = array(
@@ -7728,16 +7690,17 @@
7728 7690
7729 7691 // Calculate percentage
7730 7692 $percentage = $total > 0 ? round((($completed + $failed) / $total) * 100) : 0;
7731 7693
7732 - // Get failed items details (include all failed items, not just those that exhausted retries)
7694 + // Get failed items details
7733 7695 $failed_items = array();
7734 7696 if ($failed > 0) {
7735 7697 $failed_items = $wpdb->get_results($wpdb->prepare(
7736 - "SELECT item_type, item_data, error_message, attempts
7737 - FROM $table_name
7738 - WHERE queue_id = %s
7698 + "SELECT item_type, item_data, error_message, attempts
7699 + FROM $table_name
7700 + WHERE queue_id = %s
7739 7701 AND status = 'failed'
7702 + AND attempts >= max_attempts
7740 7703 ORDER BY id DESC
7741 7704 LIMIT 50",
7742 7705 $queue_id
7743 7706 ));