| @@ -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 |
| @@ -7055,27 +7049,13 @@ | ||
| 7055 | 7049 | |
| 7056 | 7050 | $result = false; |
| 7057 | 7051 | $error_message = ''; |
| 7058 | 7052 | |
| 7059 | - // Read item directly from DB to get queue_id and preserve special chars in item_data | |
| 7060 | - // (POST round-trip through JS mangles characters like apostrophes in URLs) | |
| 7061 | - $db_item = $wpdb->get_row($wpdb->prepare( | |
| 7062 | - "SELECT queue_id, item_data FROM $table_name WHERE id = %d", | |
| 7063 | - $item_id | |
| 7064 | - )); | |
| 7065 | - $item_queue_id = $db_item ? $db_item->queue_id : ''; | |
| 7066 | - if ($db_item && !empty($db_item->item_data)) { | |
| 7067 | - $db_data = json_decode($db_item->item_data, true); | |
| 7068 | - if (is_array($db_data)) { | |
| 7069 | - $item_data = $db_data; | |
| 7070 | - } | |
| 7071 | - } | |
| 7072 | - | |
| 7073 | 7053 | switch ($item_type) { |
| 7074 | 7054 | case 'url': |
| 7075 | - $result = $this->mxchat_process_queue_url($item_data, $bot_id, $item_queue_id); | |
| 7055 | + $result = $this->mxchat_process_queue_url($item_data, $bot_id); | |
| 7076 | 7056 | break; |
| 7077 | - | |
| 7057 | + | |
| 7078 | 7058 | case 'pdf_page': |
| 7079 | 7059 | $result = $this->mxchat_process_queue_pdf_page($item_data, $bot_id); |
| 7080 | 7060 | break; |
| 7081 | 7061 | |
| @@ -7083,37 +7063,11 @@ | ||
| 7083 | 7063 | throw new Exception('Unknown item type: ' . $item_type); |
| 7084 | 7064 | } |
| 7085 | 7065 | |
| 7086 | 7066 | 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 | 7067 | throw new Exception($result->get_error_message()); |
| 7114 | 7068 | } |
| 7115 | - | |
| 7069 | + | |
| 7116 | 7070 | if ($result === false) { |
| 7117 | 7071 | throw new Exception('Processing returned false - item may be empty or invalid'); |
| 7118 | 7072 | } |
| 7119 | 7073 | |
| @@ -7189,9 +7143,9 @@ | ||
| 7189 | 7143 | |
| 7190 | 7144 | /** |
| 7191 | 7145 | * Process a URL from the queue |
| 7192 | 7146 | */ |
| 7193 | -private function mxchat_process_queue_url($item_data, $bot_id = 'default', $queue_id = '') { | |
| 7147 | +private function mxchat_process_queue_url($item_data, $bot_id = 'default') { | |
| 7194 | 7148 | $url = isset($item_data['url']) ? $item_data['url'] : ''; |
| 7195 | 7149 | |
| 7196 | 7150 | if (empty($url)) { |
| 7197 | 7151 | return new WP_Error('invalid_url', 'URL is empty'); |
| @@ -7239,9 +7193,9 @@ | ||
| 7239 | 7193 | |
| 7240 | 7194 | // Fetch URL content (fallback for non-products or when WooCommerce extraction fails) |
| 7241 | 7195 | $is_likely_pdf = (strtolower(pathinfo(parse_url($url, PHP_URL_PATH) ?: '', PATHINFO_EXTENSION)) === 'pdf'); |
| 7242 | 7196 | $response = wp_remote_get($url, array( |
| 7243 | - 'timeout' => $is_likely_pdf ? 120 : 30, | |
| 7197 | + 'timeout' => $is_likely_pdf ? 60 : 30, | |
| 7244 | 7198 | 'redirection' => 5, |
| 7245 | 7199 | 'user-agent' => 'MxChat/1.0' |
| 7246 | 7200 | )); |
| 7247 | 7201 | |
| @@ -7250,14 +7204,14 @@ | ||
| 7250 | 7204 | } |
| 7251 | 7205 | |
| 7252 | 7206 | $response_code = wp_remote_retrieve_response_code($response); |
| 7253 | 7207 | if ($response_code !== 200) { |
| 7254 | - return new WP_Error('http_error', 'HTTP ' . $response_code . ' error for: ' . $url); | |
| 7208 | + return new WP_Error('http_error', 'HTTP ' . $response_code . ' error'); | |
| 7255 | 7209 | } |
| 7256 | 7210 | |
| 7257 | - // Check if URL is a PDF — expand into per-page queue items using the standard PDF pipeline | |
| 7211 | + // Check if URL is a PDF — process through PDF pipeline instead of HTML | |
| 7258 | 7212 | if ($this->mxchat_is_pdf_url($url, $response)) { |
| 7259 | - return $this->mxchat_expand_pdf_to_queue($url, $response, $bot_id, $queue_id); | |
| 7213 | + return $this->mxchat_process_pdf_url_inline($url, $response, $api_key, $bot_id); | |
| 7260 | 7214 | } |
| 7261 | 7215 | |
| 7262 | 7216 | $html = wp_remote_retrieve_body($response); |
| 7263 | 7217 | |
| @@ -7287,89 +7241,11 @@ | ||
| 7287 | 7241 | return $result; |
| 7288 | 7242 | } |
| 7289 | 7243 | |
| 7290 | 7244 | /** |
| 7291 | - * Expand a PDF URL into per-page queue items using the standard PDF pipeline. | |
| 7292 | - * Called when a sitemap URL turns out to be a PDF — downloads, parses page count, | |
| 7293 | - * and adds pdf_page items to the same queue so they process with full progress tracking. | |
| 7245 | + * Process a PDF URL inline during sitemap queue processing. | |
| 7246 | + * Downloads the PDF, extracts all pages, and submits each to the DB. | |
| 7294 | 7247 | */ |
| 7295 | -private function mxchat_expand_pdf_to_queue($pdf_url, $response, $bot_id = 'default', $queue_id = '') { | |
| 7296 | - set_time_limit(120); // PDFs need extra time for download + parsing | |
| 7297 | - | |
| 7298 | - $upload_dir = wp_upload_dir(); | |
| 7299 | - $pdf_filename = sanitize_file_name('mxchat_kb_' . md5($pdf_url) . '.pdf'); | |
| 7300 | - $pdf_path = trailingslashit($upload_dir['path']) . $pdf_filename; | |
| 7301 | - | |
| 7302 | - $response_body = wp_remote_retrieve_body($response); | |
| 7303 | - if (empty($response_body)) { | |
| 7304 | - return new WP_Error('empty_pdf', 'Empty PDF response for: ' . $pdf_url); | |
| 7305 | - } | |
| 7306 | - | |
| 7307 | - if (!wp_mkdir_p(dirname($pdf_path))) { | |
| 7308 | - return new WP_Error('dir_error', 'Failed to create upload directory'); | |
| 7309 | - } | |
| 7310 | - | |
| 7311 | - file_put_contents($pdf_path, $response_body); | |
| 7312 | - | |
| 7313 | - if (!file_exists($pdf_path)) { | |
| 7314 | - return new WP_Error('save_error', 'Failed to save PDF file'); | |
| 7315 | - } | |
| 7316 | - | |
| 7317 | - try { | |
| 7318 | - $total_pages = $this->mxchat_validate_and_count_pdf_pages($pdf_path); | |
| 7319 | - | |
| 7320 | - if ($total_pages === false || $total_pages < 1) { | |
| 7321 | - wp_delete_file($pdf_path); | |
| 7322 | - return new WP_Error('no_pages', 'PDF has no pages: ' . $pdf_url); | |
| 7323 | - } | |
| 7324 | - | |
| 7325 | - // Build per-page items identical to mxchat_handle_pdf_for_knowledge_base | |
| 7326 | - $pages = array(); | |
| 7327 | - for ($i = 1; $i <= $total_pages; $i++) { | |
| 7328 | - $pages[] = array( | |
| 7329 | - 'pdf_path' => $pdf_path, | |
| 7330 | - 'pdf_url' => $pdf_url, | |
| 7331 | - 'page_number' => $i, | |
| 7332 | - 'total_pages' => $total_pages | |
| 7333 | - ); | |
| 7334 | - } | |
| 7335 | - | |
| 7336 | - // Add pdf_page items to the SAME queue so the JS picks them up automatically | |
| 7337 | - if (!empty($queue_id)) { | |
| 7338 | - $queued_count = $this->mxchat_add_to_queue($queue_id, 'pdf_page', $pages, $bot_id); | |
| 7339 | - } else { | |
| 7340 | - // Fallback: create a new PDF queue (shouldn't happen in sitemap flow) | |
| 7341 | - $new_queue_id = 'pdf_' . md5($pdf_url . time()); | |
| 7342 | - $queued_count = $this->mxchat_add_to_queue($new_queue_id, 'pdf_page', $pages, $bot_id); | |
| 7343 | - $this->mxchat_set_queue_meta($new_queue_id, 'source_url', $pdf_url); | |
| 7344 | - $this->mxchat_set_queue_meta($new_queue_id, 'queue_type', 'pdf'); | |
| 7345 | - $this->mxchat_set_queue_meta($new_queue_id, 'total_items', $total_pages); | |
| 7346 | - $this->mxchat_set_queue_meta($new_queue_id, 'bot_id', $bot_id); | |
| 7347 | - $this->mxchat_set_queue_meta($new_queue_id, 'pdf_path', $pdf_path); | |
| 7348 | - $this->mxchat_set_queue_meta($new_queue_id, 'created_at', current_time('mysql')); | |
| 7349 | - } | |
| 7350 | - | |
| 7351 | - if ($queued_count === 0) { | |
| 7352 | - wp_delete_file($pdf_path); | |
| 7353 | - return new WP_Error('queue_error', 'Failed to add PDF pages to queue'); | |
| 7354 | - } | |
| 7355 | - | |
| 7356 | - // Return true so the original URL item is marked complete | |
| 7357 | - // The new pdf_page items will be processed in subsequent batches | |
| 7358 | - return true; | |
| 7359 | - | |
| 7360 | - } catch (Exception $e) { | |
| 7361 | - if (file_exists($pdf_path)) { | |
| 7362 | - wp_delete_file($pdf_path); | |
| 7363 | - } | |
| 7364 | - return new WP_Error('pdf_parse_error', 'Error parsing PDF: ' . $e->getMessage()); | |
| 7365 | - } | |
| 7366 | -} | |
| 7367 | - | |
| 7368 | -/** | |
| 7369 | - * Legacy: Process a PDF URL inline during sitemap queue processing. | |
| 7370 | - * @deprecated Use mxchat_expand_pdf_to_queue instead — kept for reference only. | |
| 7371 | - */ | |
| 7372 | 7248 | private function mxchat_process_pdf_url_inline($pdf_url, $response, $api_key, $bot_id = 'default') { |
| 7373 | 7249 | set_time_limit(120); // PDFs need more time — downloading + parsing all pages |
| 7374 | 7250 | |
| 7375 | 7251 | $upload_dir = wp_upload_dir(); |
| @@ -7403,24 +7279,21 @@ | ||
| 7403 | 7279 | return new WP_Error('no_pages', 'PDF has no pages'); |
| 7404 | 7280 | } |
| 7405 | 7281 | |
| 7406 | 7282 | $processed = 0; |
| 7407 | - $skipped_pages = array(); | |
| 7408 | 7283 | |
| 7409 | 7284 | for ($i = 0; $i < $total_pages; $i++) { |
| 7410 | - $page_num = $i + 1; | |
| 7411 | 7285 | $text = $pages[$i]->getText(); |
| 7412 | 7286 | 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 | 7287 | continue; |
| 7415 | 7288 | } |
| 7416 | 7289 | |
| 7417 | 7290 | $sanitized = $this->mxchat_sanitize_content_for_api($text); |
| 7418 | 7291 | if (empty($sanitized)) { |
| 7419 | - $skipped_pages[] = 'Page ' . $page_num . ': Text was extracted but contained only special characters, control codes, or unsupported content'; | |
| 7420 | 7292 | continue; |
| 7421 | 7293 | } |
| 7422 | 7294 | |
| 7295 | + $page_num = $i + 1; | |
| 7423 | 7296 | $metadata = array( |
| 7424 | 7297 | 'document_type' => 'pdf', |
| 7425 | 7298 | 'total_pages' => $total_pages, |
| 7426 | 7299 | 'current_page' => $page_num, |
| @@ -7444,12 +7317,8 @@ | ||
| 7444 | 7317 | |
| 7445 | 7318 | // Clean up the temp PDF file |
| 7446 | 7319 | wp_delete_file($pdf_path); |
| 7447 | 7320 | |
| 7448 | - if (!empty($skipped_pages)) { | |
| 7449 | - error_log('MxChat PDF: Skipped ' . count($skipped_pages) . ' of ' . $total_pages . ' pages: ' . implode('; ', $skipped_pages)); | |
| 7450 | - } | |
| 7451 | - | |
| 7452 | 7321 | return $processed > 0 ? true : false; |
| 7453 | 7322 | |
| 7454 | 7323 | } catch (Exception $e) { |
| 7455 | 7324 | if (file_exists($pdf_path)) { |
| @@ -7614,17 +7483,18 @@ | ||
| 7614 | 7483 | return new WP_Error('page_not_found', 'Page ' . $page_number . ' not found in PDF'); |
| 7615 | 7484 | } |
| 7616 | 7485 | |
| 7617 | 7486 | $text = $pages[$page_number - 1]->getText(); |
| 7618 | - | |
| 7487 | + | |
| 7619 | 7488 | 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'); | |
| 7489 | + // Not an error - just an empty page | |
| 7490 | + return false; | |
| 7621 | 7491 | } |
| 7622 | - | |
| 7492 | + | |
| 7623 | 7493 | $sanitized = $this->mxchat_sanitize_content_for_api($text); |
| 7624 | - | |
| 7494 | + | |
| 7625 | 7495 | 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'); | |
| 7496 | + return false; | |
| 7627 | 7497 | } |
| 7628 | 7498 | |
| 7629 | 7499 | // Create metadata |
| 7630 | 7500 | $metadata = array( |
| @@ -7728,16 +7598,17 @@ | ||
| 7728 | 7598 | |
| 7729 | 7599 | // Calculate percentage |
| 7730 | 7600 | $percentage = $total > 0 ? round((($completed + $failed) / $total) * 100) : 0; |
| 7731 | 7601 | |
| 7732 | - // Get failed items details (include all failed items, not just those that exhausted retries) | |
| 7602 | + // Get failed items details | |
| 7733 | 7603 | $failed_items = array(); |
| 7734 | 7604 | if ($failed > 0) { |
| 7735 | 7605 | $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 | |
| 7606 | + "SELECT item_type, item_data, error_message, attempts | |
| 7607 | + FROM $table_name | |
| 7608 | + WHERE queue_id = %s | |
| 7739 | 7609 | AND status = 'failed' |
| 7610 | + AND attempts >= max_attempts | |
| 7740 | 7611 | ORDER BY id DESC |
| 7741 | 7612 | LIMIT 50", |
| 7742 | 7613 | $queue_id |
| 7743 | 7614 | )); |