| @@ -163,13 +163,9 @@ | ||
| 163 | 163 | public function mxchat_is_pdf_url($url, $response) { |
| 164 | 164 | $content_type = wp_remote_retrieve_header($response, 'content-type'); |
| 165 | 165 | $file_extension = strtolower(pathinfo($url, PATHINFO_EXTENSION)); |
| 166 | 166 | |
| 167 | - // Check Content-Disposition header for .pdf filename (Google Drive sends this) | |
| 168 | - $disposition = wp_remote_retrieve_header($response, 'content-disposition'); | |
| 169 | - $has_pdf_disposition = ! empty($disposition) && stripos($disposition, '.pdf') !== false; | |
| 170 | - | |
| 171 | - return strpos($content_type, 'pdf') !== false || $file_extension === 'pdf' || $has_pdf_disposition; | |
| 167 | + return strpos($content_type, 'pdf') !== false || $file_extension === 'pdf'; | |
| 172 | 168 | } |
| 173 | 169 | |
| 174 | 170 | |
| 175 | 171 | public function mxchat_handle_pdf_for_knowledge_base($pdf_url, $response, $bot_id = 'default') { |
| @@ -863,17 +859,10 @@ | ||
| 863 | 859 | } |
| 864 | 860 | |
| 865 | 861 | // For manual entries (no source_url or mxchat:// prefix), delete the old entry by ID first |
| 866 | 862 | // so submit_content_to_db creates a replacement instead of a duplicate |
| 867 | - // Also treat legacy mxchat.ai source URLs as manual — old bug assigned the site URL to manual entries | |
| 868 | - $is_legacy_manual = !empty($source_url) && strpos($source_url, 'mxchat.ai') !== false && strpos($source_url, 'mxchat://') !== 0; | |
| 869 | - 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) ) { | |
| 870 | 864 | $wpdb->delete( $table, array( 'id' => $entry_id ), array( '%d' ) ); |
| 871 | - // Clear legacy URL so submit_content_to_db generates a unique mxchat:// identifier | |
| 872 | - // instead of reusing the shared URL (which would mass-delete other entries with the same URL) | |
| 873 | - if ( $is_legacy_manual ) { | |
| 874 | - $source_url = ''; | |
| 875 | - } | |
| 876 | 865 | } |
| 877 | 866 | |
| 878 | 867 | // Use the existing submit_content_to_db which handles chunking, Pinecone, and WP DB |
| 879 | 868 | $vector_id = ! empty($source_url) ? md5($source_url) : md5('mxchat_manual_' . $entry_id); |
| @@ -950,22 +939,9 @@ | ||
| 950 | 939 | exit; |
| 951 | 940 | } |
| 952 | 941 | |
| 953 | 942 | $submitted_url = esc_url_raw($_POST['sitemap_url']); |
| 954 | - | |
| 955 | - // Convert Google Drive sharing URLs to direct download URLs | |
| 956 | - if ( strpos($submitted_url, 'drive.google.com') !== false ) { | |
| 957 | - $file_id = ''; | |
| 958 | - if ( preg_match('/[?&]id=([a-zA-Z0-9_-]+)/', $submitted_url, $m) ) { | |
| 959 | - $file_id = $m[1]; | |
| 960 | - } elseif ( preg_match('#/file/d/([a-zA-Z0-9_-]+)#', $submitted_url, $m) ) { | |
| 961 | - $file_id = $m[1]; | |
| 962 | - } | |
| 963 | - if ( ! empty($file_id) ) { | |
| 964 | - $submitted_url = 'https://drive.google.com/uc?export=download&id=' . $file_id; | |
| 965 | - } | |
| 966 | - } | |
| 967 | - | |
| 943 | + | |
| 968 | 944 | // Get bot_id from form submission |
| 969 | 945 | $bot_id = isset($_POST['bot_id']) ? sanitize_key($_POST['bot_id']) : 'default'; |
| 970 | 946 | |
| 971 | 947 | // Get bot-specific options and validate API key |
| @@ -993,18 +969,10 @@ | ||
| 993 | 969 | wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts'))); |
| 994 | 970 | exit; |
| 995 | 971 | } |
| 996 | 972 | |
| 997 | - // Fetch URL — use browser-like headers so servers with bot protection don't block us | |
| 998 | - $response = wp_remote_get($submitted_url, array( | |
| 999 | - 'timeout' => 30, | |
| 1000 | - 'sslverify' => false, | |
| 1001 | - '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', | |
| 1002 | - 'headers' => array( | |
| 1003 | - 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', | |
| 1004 | - 'Accept-Language' => 'en-US,en;q=0.9', | |
| 1005 | - ), | |
| 1006 | - )); | |
| 973 | + // Fetch URL | |
| 974 | + $response = wp_remote_get($submitted_url, array('timeout' => 30)); | |
| 1007 | 975 | |
| 1008 | 976 | if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) { |
| 1009 | 977 | $error_message = is_wp_error($response) ? $response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($response); |
| 1010 | 978 | set_transient('mxchat_admin_notice_error', |
| @@ -2838,12 +2806,11 @@ | ||
| 2838 | 2806 | foreach ($primary_indexes as $path => $source) { |
| 2839 | 2807 | $url = trailingslashit($site_url) . $path; |
| 2840 | 2808 | |
| 2841 | 2809 | $response = wp_remote_head($url, array( |
| 2842 | - 'timeout' => 10, | |
| 2810 | + 'timeout' => 3, // Short timeout | |
| 2843 | 2811 | 'sslverify' => false, |
| 2844 | - 'redirection' => 1, | |
| 2845 | - '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 | |
| 2846 | 2813 | )); |
| 2847 | 2814 | |
| 2848 | 2815 | if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) { |
| 2849 | 2816 | // Found a sitemap index - parse it to get sub-sitemaps |
| @@ -2901,15 +2868,10 @@ | ||
| 2901 | 2868 | private function parse_sitemap_index($url) { |
| 2902 | 2869 | $sub_sitemaps = array(); |
| 2903 | 2870 | |
| 2904 | 2871 | $response = wp_remote_get($url, array( |
| 2905 | - 'timeout' => 30, | |
| 2906 | - 'sslverify' => false, | |
| 2907 | - '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', | |
| 2908 | - 'headers' => array( | |
| 2909 | - 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', | |
| 2910 | - 'Accept-Language' => 'en-US,en;q=0.9', | |
| 2911 | - ), | |
| 2872 | + 'timeout' => 5, | |
| 2873 | + 'sslverify' => false | |
| 2912 | 2874 | )); |
| 2913 | 2875 | |
| 2914 | 2876 | if (is_wp_error($response)) { |
| 2915 | 2877 | return $sub_sitemaps; |
| @@ -2960,15 +2922,10 @@ | ||
| 2960 | 2922 | * Get URL count from a sitemap |
| 2961 | 2923 | */ |
| 2962 | 2924 | private function get_sitemap_url_count($url) { |
| 2963 | 2925 | $response = wp_remote_get($url, array( |
| 2964 | - 'timeout' => 30, | |
| 2965 | - 'sslverify' => false, | |
| 2966 | - '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', | |
| 2967 | - 'headers' => array( | |
| 2968 | - 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', | |
| 2969 | - 'Accept-Language' => 'en-US,en;q=0.9', | |
| 2970 | - ), | |
| 2926 | + 'timeout' => 10, | |
| 2927 | + 'sslverify' => false | |
| 2971 | 2928 | )); |
| 2972 | 2929 | |
| 2973 | 2930 | if (is_wp_error($response)) { |
| 2974 | 2931 | return 0; |
| @@ -2991,11 +2948,10 @@ | ||
| 2991 | 2948 | $sitemaps = array(); |
| 2992 | 2949 | $robots_url = trailingslashit($site_url) . 'robots.txt'; |
| 2993 | 2950 | |
| 2994 | 2951 | $response = wp_remote_get($robots_url, array( |
| 2995 | - 'timeout' => 15, | |
| 2996 | - 'sslverify' => false, | |
| 2997 | - '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 | |
| 2998 | 2954 | )); |
| 2999 | 2955 | |
| 3000 | 2956 | if (is_wp_error($response)) { |
| 3001 | 2957 | return $sitemaps; |
| @@ -7121,37 +7077,11 @@ | ||
| 7121 | 7077 | throw new Exception('Unknown item type: ' . $item_type); |
| 7122 | 7078 | } |
| 7123 | 7079 | |
| 7124 | 7080 | if (is_wp_error($result)) { |
| 7125 | - $error_code = $result->get_error_code(); | |
| 7126 | - // Content errors (empty page, sanitization) are permanent — retrying won't help | |
| 7127 | - $permanent_codes = array('empty_page', 'empty_after_sanitization', 'no_api_key', 'page_not_found'); | |
| 7128 | - if (in_array($error_code, $permanent_codes)) { | |
| 7129 | - // Mark as permanently failed — set attempts = max_attempts so it won't be retried | |
| 7130 | - $current_item = $wpdb->get_row($wpdb->prepare( | |
| 7131 | - "SELECT max_attempts FROM $table_name WHERE id = %d", $item_id | |
| 7132 | - )); | |
| 7133 | - $wpdb->update( | |
| 7134 | - $table_name, | |
| 7135 | - array( | |
| 7136 | - 'status' => 'failed', | |
| 7137 | - 'error_message' => $result->get_error_message(), | |
| 7138 | - 'attempts' => $current_item ? $current_item->max_attempts : 3 | |
| 7139 | - ), | |
| 7140 | - array('id' => $item_id), | |
| 7141 | - array('%s', '%s', '%d'), | |
| 7142 | - array('%d') | |
| 7143 | - ); | |
| 7144 | - wp_send_json_error(array( | |
| 7145 | - 'message' => $result->get_error_message(), | |
| 7146 | - 'permanent_failure' => true, | |
| 7147 | - 'item_id' => $item_id | |
| 7148 | - )); | |
| 7149 | - return; | |
| 7150 | - } | |
| 7151 | 7081 | throw new Exception($result->get_error_message()); |
| 7152 | 7082 | } |
| 7153 | - | |
| 7083 | + | |
| 7154 | 7084 | if ($result === false) { |
| 7155 | 7085 | throw new Exception('Processing returned false - item may be empty or invalid'); |
| 7156 | 7086 | } |
| 7157 | 7087 | |
| @@ -7441,24 +7371,21 @@ | ||
| 7441 | 7371 | return new WP_Error('no_pages', 'PDF has no pages'); |
| 7442 | 7372 | } |
| 7443 | 7373 | |
| 7444 | 7374 | $processed = 0; |
| 7445 | - $skipped_pages = array(); | |
| 7446 | 7375 | |
| 7447 | 7376 | for ($i = 0; $i < $total_pages; $i++) { |
| 7448 | - $page_num = $i + 1; | |
| 7449 | 7377 | $text = $pages[$i]->getText(); |
| 7450 | 7378 | if (empty($text)) { |
| 7451 | - $skipped_pages[] = 'Page ' . $page_num . ': No text could be extracted — page may contain only images, links, or non-standard encoding'; | |
| 7452 | 7379 | continue; |
| 7453 | 7380 | } |
| 7454 | 7381 | |
| 7455 | 7382 | $sanitized = $this->mxchat_sanitize_content_for_api($text); |
| 7456 | 7383 | if (empty($sanitized)) { |
| 7457 | - $skipped_pages[] = 'Page ' . $page_num . ': Text was extracted but contained only special characters, control codes, or unsupported content'; | |
| 7458 | 7384 | continue; |
| 7459 | 7385 | } |
| 7460 | 7386 | |
| 7387 | + $page_num = $i + 1; | |
| 7461 | 7388 | $metadata = array( |
| 7462 | 7389 | 'document_type' => 'pdf', |
| 7463 | 7390 | 'total_pages' => $total_pages, |
| 7464 | 7391 | 'current_page' => $page_num, |
| @@ -7482,12 +7409,8 @@ | ||
| 7482 | 7409 | |
| 7483 | 7410 | // Clean up the temp PDF file |
| 7484 | 7411 | wp_delete_file($pdf_path); |
| 7485 | 7412 | |
| 7486 | - if (!empty($skipped_pages)) { | |
| 7487 | - error_log('MxChat PDF: Skipped ' . count($skipped_pages) . ' of ' . $total_pages . ' pages: ' . implode('; ', $skipped_pages)); | |
| 7488 | - } | |
| 7489 | - | |
| 7490 | 7413 | return $processed > 0 ? true : false; |
| 7491 | 7414 | |
| 7492 | 7415 | } catch (Exception $e) { |
| 7493 | 7416 | if (file_exists($pdf_path)) { |
| @@ -7652,17 +7575,18 @@ | ||
| 7652 | 7575 | return new WP_Error('page_not_found', 'Page ' . $page_number . ' not found in PDF'); |
| 7653 | 7576 | } |
| 7654 | 7577 | |
| 7655 | 7578 | $text = $pages[$page_number - 1]->getText(); |
| 7656 | - | |
| 7579 | + | |
| 7657 | 7580 | if (empty($text)) { |
| 7658 | - 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; | |
| 7659 | 7583 | } |
| 7660 | - | |
| 7584 | + | |
| 7661 | 7585 | $sanitized = $this->mxchat_sanitize_content_for_api($text); |
| 7662 | - | |
| 7586 | + | |
| 7663 | 7587 | if (empty($sanitized)) { |
| 7664 | - 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; | |
| 7665 | 7589 | } |
| 7666 | 7590 | |
| 7667 | 7591 | // Create metadata |
| 7668 | 7592 | $metadata = array( |
| @@ -7766,16 +7690,17 @@ | ||
| 7766 | 7690 | |
| 7767 | 7691 | // Calculate percentage |
| 7768 | 7692 | $percentage = $total > 0 ? round((($completed + $failed) / $total) * 100) : 0; |
| 7769 | 7693 | |
| 7770 | - // Get failed items details (include all failed items, not just those that exhausted retries) | |
| 7694 | + // Get failed items details | |
| 7771 | 7695 | $failed_items = array(); |
| 7772 | 7696 | if ($failed > 0) { |
| 7773 | 7697 | $failed_items = $wpdb->get_results($wpdb->prepare( |
| 7774 | - "SELECT item_type, item_data, error_message, attempts | |
| 7775 | - FROM $table_name | |
| 7776 | - WHERE queue_id = %s | |
| 7698 | + "SELECT item_type, item_data, error_message, attempts | |
| 7699 | + FROM $table_name | |
| 7700 | + WHERE queue_id = %s | |
| 7777 | 7701 | AND status = 'failed' |
| 7702 | + AND attempts >= max_attempts | |
| 7778 | 7703 | ORDER BY id DESC |
| 7779 | 7704 | LIMIT 50", |
| 7780 | 7705 | $queue_id |
| 7781 | 7706 | )); |