Changeset 3469618
- Timestamp:
- 02/25/2026 04:39:00 PM (4 weeks ago)
- Location:
- intufind
- Files:
-
- 34 added
- 4 edited
-
tags/1.2.3 (added)
-
tags/1.2.3/admin (added)
-
tags/1.2.3/admin/class-intufind-admin.php (added)
-
tags/1.2.3/admin/class-intufind-components.php (added)
-
tags/1.2.3/admin/css (added)
-
tags/1.2.3/admin/css/intufind-admin.css (added)
-
tags/1.2.3/admin/js (added)
-
tags/1.2.3/admin/js/intufind-admin.js (added)
-
tags/1.2.3/admin/partials (added)
-
tags/1.2.3/admin/partials/chat-display.php (added)
-
tags/1.2.3/admin/partials/recommendations-display.php (added)
-
tags/1.2.3/admin/partials/search-display.php (added)
-
tags/1.2.3/admin/partials/settings-display.php (added)
-
tags/1.2.3/admin/partials/status-display.php (added)
-
tags/1.2.3/admin/partials/sync-display.php (added)
-
tags/1.2.3/includes (added)
-
tags/1.2.3/includes/class-intufind-api.php (added)
-
tags/1.2.3/includes/class-intufind-chat-widget.php (added)
-
tags/1.2.3/includes/class-intufind-content-extractor.php (added)
-
tags/1.2.3/includes/class-intufind-exclusions.php (added)
-
tags/1.2.3/includes/class-intufind-list-columns.php (added)
-
tags/1.2.3/includes/class-intufind-mcp.php (added)
-
tags/1.2.3/includes/class-intufind-plugin.php (added)
-
tags/1.2.3/includes/class-intufind-recommendations-override.php (added)
-
tags/1.2.3/includes/class-intufind-search-override.php (added)
-
tags/1.2.3/includes/class-intufind-search-widget.php (added)
-
tags/1.2.3/includes/class-intufind-shortcodes.php (added)
-
tags/1.2.3/includes/class-intufind-sync-status.php (added)
-
tags/1.2.3/includes/class-intufind-sync.php (added)
-
tags/1.2.3/intufind.php (added)
-
tags/1.2.3/languages (added)
-
tags/1.2.3/languages/intufind.pot (added)
-
tags/1.2.3/readme.txt (added)
-
tags/1.2.3/uninstall.php (added)
-
trunk/includes/class-intufind-api.php (modified) (12 diffs)
-
trunk/includes/class-intufind-sync.php (modified) (9 diffs)
-
trunk/intufind.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
intufind/trunk/includes/class-intufind-api.php
r3462954 r3469618 88 88 } 89 89 90 const MAX_RETRIES = 2; 91 const RETRY_BASE_DELAY = 1; 92 90 93 /** 91 94 * Make an authenticated API request. 95 * 96 * Rate-limit retries are OFF by default. Callers that send idempotent 97 * payloads (bulk upserts, deletes) should pass retries: true explicitly. 92 98 * 93 99 * @param string $method HTTP method (GET, POST, PUT, DELETE). … … 95 101 * @param array $data Request body data (for POST/PUT). 96 102 * @param array $args Additional wp_remote_request args. 103 * Pass 'retries' => true to enable 429 retry with backoff. 97 104 * @return array|WP_Error Response data or WP_Error on failure. 98 105 */ 99 106 public function request( $method, $endpoint, $data = array(), $args = array() ) { 107 $retry_enabled = ! empty( $args['retries'] ); 108 unset( $args['retries'] ); 109 100 110 $api_key = $this->get_api_key(); 101 111 … … 132 142 $request_args = wp_parse_args( $args, $default_args ); 133 143 134 // Make the request. 135 $response = wp_remote_request( $url, $request_args ); 136 137 // Check for WP_Error. 138 if ( is_wp_error( $response ) ) { 139 return $response; 140 } 141 142 // Parse response. 143 $status_code = wp_remote_retrieve_response_code( $response ); 144 $body = wp_remote_retrieve_body( $response ); 145 $data = json_decode( $body, true ); 146 147 // Check for HTTP errors. 148 if ( $status_code >= 400 ) { 144 $max_attempts = $retry_enabled ? self::MAX_RETRIES : 0; 145 $last_error = null; 146 147 for ( $attempt = 0; $attempt <= $max_attempts; $attempt++ ) { 148 if ( $attempt > 0 ) { 149 $delay_ms = (int) ( self::RETRY_BASE_DELAY * pow( 2, $attempt - 1 ) * 1000 ); 150 $jitter = wp_rand( 0, (int) ( $delay_ms * 0.3 ) ); 151 usleep( ( $delay_ms + $jitter ) * 1000 ); 152 } 153 154 $response = wp_remote_request( $url, $request_args ); 155 156 if ( is_wp_error( $response ) ) { 157 return $response; 158 } 159 160 $status_code = wp_remote_retrieve_response_code( $response ); 161 $body = wp_remote_retrieve_body( $response ); 162 $data = json_decode( $body, true ); 163 164 if ( $status_code < 400 ) { 165 return $data; 166 } 167 149 168 $error_default = __( 'API request failed.', 'intufind' ); 150 169 $error_message = isset( $data['error'] ) … … 152 171 : $error_default; 153 172 154 // Provide user-friendly messages for common error codes.155 173 if ( 429 === $status_code ) { 156 $api_error_code = isset( $data['errorCode'] ) ? $data['errorCode'] : ''; 157 $is_limit = 'INDEXING_LIMIT_EXCEEDED' === $api_error_code 158 || stripos( $error_message, 'limit exceeded' ) !== false; 174 $api_error_code = isset( $data['error']['code'] ) ? $data['error']['code'] : ''; 175 $is_limit = 'INDEXING_LIMIT_EXCEEDED' === $api_error_code; 159 176 160 177 if ( $is_limit ) { … … 166 183 } 167 184 168 returnnew WP_Error(185 $last_error = new WP_Error( 169 186 'rate_limited', 170 187 __( 'Too many requests. Please wait a moment and try again.', 'intufind' ), 171 188 array( 'status' => $status_code ) 172 189 ); 190 continue; 173 191 } 174 192 … … 176 194 } 177 195 178 return $ data;196 return $last_error; 179 197 } 180 198 … … 384 402 */ 385 403 public function upsert_posts( $posts ) { 386 return $this->request( 'POST', 'posts/bulk', array( 'entities' => $posts ) );404 return $this->request( 'POST', 'posts/bulk', array( 'entities' => $posts ), array( 'retries' => true ) ); 387 405 } 388 406 … … 394 412 */ 395 413 public function delete_posts( $post_ids ) { 396 return $this->request( 'DELETE', 'posts/bulk', array( 'ids' => $post_ids ) );414 return $this->request( 'DELETE', 'posts/bulk', array( 'ids' => $post_ids ), array( 'retries' => true ) ); 397 415 } 398 416 … … 404 422 */ 405 423 public function upsert_products( $products ) { 406 return $this->request( 'POST', 'products/bulk', array( 'entities' => $products ) );424 return $this->request( 'POST', 'products/bulk', array( 'entities' => $products ), array( 'retries' => true ) ); 407 425 } 408 426 … … 414 432 */ 415 433 public function delete_products( $product_ids ) { 416 return $this->request( 'DELETE', 'products/bulk', array( 'ids' => $product_ids ) );434 return $this->request( 'DELETE', 'products/bulk', array( 'ids' => $product_ids ), array( 'retries' => true ) ); 417 435 } 418 436 … … 683 701 public function upsert_taxonomies( $taxonomies ) { 684 702 // Taxonomy bulk upsert expects a direct array, not wrapped in an object. 685 return $this->request( 'POST', 'taxonomies/bulk', $taxonomies );703 return $this->request( 'POST', 'taxonomies/bulk', $taxonomies, array( 'retries' => true ) ); 686 704 } 687 705 … … 693 711 */ 694 712 public function delete_taxonomies( $taxonomy_ids ) { 695 return $this->request( 'DELETE', 'taxonomies/bulk', array( 'ids' => $taxonomy_ids ) );713 return $this->request( 'DELETE', 'taxonomies/bulk', array( 'ids' => $taxonomy_ids ), array( 'retries' => true ) ); 696 714 } 697 715 -
intufind/trunk/includes/class-intufind-sync.php
r3462954 r3469618 608 608 $processed_ids = array(); 609 609 $skipped_count = 0; 610 $limit_reached = false; 610 611 611 612 foreach ( $items as $item ) { 613 if ( $limit_reached ) { 614 break; 615 } 616 612 617 $post_id = $item['post_id']; 613 618 … … 637 642 // Process in batches. 638 643 if ( count( $documents ) >= self::BULK_BATCH_SIZE ) { 639 $ this->send_bulk_upsert( $post_type, $documents, $processed_ids );644 $limit_reached = $this->send_bulk_upsert( $post_type, $documents, $processed_ids ); 640 645 $documents = array(); 641 646 $processed_ids = array(); … … 643 648 } 644 649 645 // Process remaining .646 if ( ! empty( $documents ) ) {650 // Process remaining (unless limit was already hit). 651 if ( ! empty( $documents ) && ! $limit_reached ) { 647 652 $this->send_bulk_upsert( $post_type, $documents, $processed_ids ); 648 653 } … … 658 663 * @param array $documents Documents to upsert. 659 664 * @param array $processed_ids Post IDs being processed. 660 * @return void665 * @return bool True if indexing limit was reached. 661 666 */ 662 667 private function send_bulk_upsert( $post_type, $documents, $processed_ids ) { 663 668 // Store hashes for status update. 664 669 $hashes = array(); 665 foreach ( $documents as $doc ) {670 foreach ( $documents as &$doc ) { 666 671 $hashes[ $doc['id'] ] = $doc['_hash'] ?? ''; 667 672 unset( $doc['_hash'] ); 668 673 } 674 unset( $doc ); 669 675 670 676 // Send to API. … … 680 686 $error_code = $result->get_error_code(); 681 687 682 // Use a concise per-item message for indexing limits.683 688 if ( 'indexing_limit_exceeded' === $error_code ) { 684 689 $error_message = __( 'Plan indexing limit reached', 'intufind' ); … … 688 693 $this->status->mark_error( $post_id, $error_message ); 689 694 } 690 } else { 691 // Check for individual failures in response. 692 $failed = $this->extract_failed_items( $result ); 693 694 foreach ( $processed_ids as $post_id ) { 695 if ( in_array( (string) $post_id, $failed['ids'], true ) ) { 696 $error_msg = $this->find_failed_item_error( $failed['items'], $post_id ); 697 $this->status->mark_error( $post_id, $error_msg ); 698 } else { 699 $hash = $hashes[ (string) $post_id ] ?? ''; 700 $this->status->mark_synced( $post_id, $hash, 'auto' ); 701 } 702 } 703 } 695 696 return 'indexing_limit_exceeded' === $error_code; 697 } 698 699 $failed = $this->extract_failed_items( $result ); 700 $success_ids = isset( $result['data']['successfulIds'] ) ? $result['data']['successfulIds'] : array(); 701 $success_set = array_flip( array_map( 'strval', $success_ids ) ); 702 $limit_hit = ! empty( $result['data']['indexingLimitReached'] ); 703 704 foreach ( $processed_ids as $post_id ) { 705 if ( in_array( (string) $post_id, $failed['ids'], true ) ) { 706 $error_msg = $this->find_failed_item_error( $failed['items'], $post_id ); 707 $this->status->mark_error( $post_id, $error_msg ); 708 } elseif ( isset( $success_set[ (string) $post_id ] ) ) { 709 $hash = $hashes[ (string) $post_id ] ?? ''; 710 $this->status->mark_synced( $post_id, $hash, 'auto' ); 711 } elseif ( $limit_hit ) { 712 $this->status->mark_error( $post_id, __( 'Plan indexing limit reached', 'intufind' ) ); 713 } 714 } 715 716 return $limit_hit; 704 717 } 705 718 … … 901 914 unset( $doc['_hash'] ); 902 915 } 916 unset( $doc ); 903 917 904 918 // Send to API. … … 918 932 $result['error_messages'][] = $error_message; 919 933 920 // Signal plan limit so the caller can stop sending more batches.921 934 if ( 'indexing_limit_exceeded' === $error_code ) { 922 935 $result['limit_reached'] = true; 923 936 } 924 937 } else { 925 // Check for individual failures in response. 926 $failed = $this->extract_failed_items( $response ); 938 $failed = $this->extract_failed_items( $response ); 939 $success_ids = isset( $response['data']['successfulIds'] ) ? $response['data']['successfulIds'] : array(); 940 $success_set = array_flip( array_map( 'strval', $success_ids ) ); 941 942 $limit_hit = ! empty( $response['data']['indexingLimitReached'] ); 927 943 928 944 foreach ( $batch_ids as $post_id ) { … … 932 948 $result['errors']++; 933 949 $result['error_messages'][] = $error_msg; 934 } else {950 } elseif ( isset( $success_set[ (string) $post_id ] ) ) { 935 951 $hash = $hashes[ (string) $post_id ] ?? ''; 936 952 $this->status->mark_synced( $post_id, $hash, 'manual' ); 937 953 $result['synced']++; 954 } elseif ( $limit_hit ) { 955 $this->status->mark_error( $post_id, __( 'Plan indexing limit reached', 'intufind' ) ); 956 $result['errors']++; 938 957 } 958 } 959 960 if ( $limit_hit ) { 961 $result['limit_reached'] = true; 939 962 } 940 963 } -
intufind/trunk/intufind.php
r3468114 r3469618 4 4 * Plugin URI: https://intufind.com/integrations/wordpress 5 5 * Description: AI-powered search and chat for WordPress. Syncs your content to the cloud for semantic search, intelligent recommendations, and conversational AI. 6 * Version: 1.2. 26 * Version: 1.2.3 7 7 * Requires at least: 6.0 8 8 * Requires PHP: 8.0 … … 26 26 * Plugin constants. 27 27 */ 28 define( 'INTUFIND_VERSION', '1.2. 2' );28 define( 'INTUFIND_VERSION', '1.2.3' ); 29 29 define( 'INTUFIND_PLUGIN_FILE', __FILE__ ); 30 30 define( 'INTUFIND_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); -
intufind/trunk/readme.txt
r3468114 r3469618 5 5 Tested up to: 6.9 6 6 Requires PHP: 8.0 7 Stable tag: 1.2. 27 Stable tag: 1.2.3 8 8 WC tested up to: 9.6 9 9 License: GPLv2 or later … … 215 215 == Changelog == 216 216 217 = 1.2.3 = 218 * Fixed bulk sync failing entirely when batch exceeds plan indexing limit — now processes as many items as will fit 219 * Added retry with exponential backoff for rate-limited API requests during bulk sync 220 * Fixed error code detection for indexing limit responses 221 * Fixed _hash field leaking to API in real-time sync batches 222 217 223 = 1.2.2 = 218 224 * Fixed RSS feed requests and bot-like URL patterns being logged as native search queries … … 286 292 == Upgrade Notice == 287 293 294 = 1.2.3 = 295 Fixes bulk sync failing entirely when plan limit is exceeded — items are now partially processed. Adds retry for rate-limited requests. 296 288 297 = 1.2.2 = 289 298 Filters out RSS feed paths, URLs, and bot-like patterns from native search logging to keep analytics clean.
Note: See TracChangeset
for help on using the changeset viewer.