Plugin Directory

Changeset 3463885


Ignore:
Timestamp:
02/17/2026 10:57:15 PM (6 weeks ago)
Author:
winrid
Message:

3.18.0 Improve re-sync reliability and duplicate prevention

Location:
fastcomments
Files:
40 added
5 edited

Legend:

Unmodified
Added
Removed
  • fastcomments/trunk/README.txt

    r3462221 r3463885  
    44Requires at least: 4.6
    55Tested up to: 6.9.2
    6 Stable tag: 3.17.0
     6Stable tag: 3.18.0
    77Requires PHP: 5.2.5
    88License: GPLv2 or later
     
    8484== Changelog ==
    8585
     86= 3.18.0 =
     87* Improved sync reliability: "Download FC to WP" now sends WP IDs back to FastComments, eliminating slow table scans on future syncs.
     88* Fixed duplicate comments when re-syncing after plugin reinstall.
     89* Sync mapping failures are now queued and automatically retried on the next cron tick.
     90* Improved error logging for failed comment inserts during sync.
     91
    8692= 3.17.0 =
    8793* Admin theme update.
  • fastcomments/trunk/core/FastCommentsIntegrationCore.php

    r3436960 r3463885  
    194194        // This removes the weird logic where each time a command is finished processing, we advance the fastcomments_stream_last_fetch_timestamp.
    195195        // The reason this logic is weird is the two things are relatively far from each other, potentially being bug prone.
     196        $this->retryPendingIdMappings();
    196197        $token = $this->getSettingValue('fastcomments_token');
    197198        if ($token) {
     
    423424    }
    424425
     426    /**
     427     * Sends WP ID mappings back to FC so meta.wpId is set for O(1) lookups on future syncs.
     428     * On failure, queues mappings for retry on next tick.
     429     */
     430    public function sendCommentIdMappings($mappings) {
     431        if (count($mappings) === 0) {
     432            return;
     433        }
     434        $token = $this->getSettingValue('fastcomments_token');
     435        $patch_url = "$this->baseUrl/comment-ids?token=$token";
     436        $patch_response = $this->makeHTTPRequest('PATCH', $patch_url, json_encode(array('mappings' => $mappings)));
     437        if ($patch_response->responseStatusCode !== 200) {
     438            $this->log('warn', "Failed to send WP ID mappings back to FC (HTTP $patch_response->responseStatusCode), queuing for retry");
     439            $this->queueMappingsForRetry($mappings);
     440        }
     441    }
     442
     443    private function queueMappingsForRetry($mappings) {
     444        $existing = $this->getSettingValue('fastcomments_pending_id_mappings');
     445        if ($existing) {
     446            $existing = json_decode($existing, true);
     447            if (!is_array($existing)) {
     448                $existing = array();
     449            }
     450        } else {
     451            $existing = array();
     452        }
     453        foreach ($mappings as $mapping) {
     454            $existing[] = $mapping;
     455        }
     456        // Cap at 5000 to prevent unbounded growth
     457        if (count($existing) > 5000) {
     458            $existing = array_slice($existing, -5000);
     459        }
     460        $this->setSettingValue('fastcomments_pending_id_mappings', json_encode($existing));
     461    }
     462
     463    /**
     464     * Retries any queued ID mappings that failed to send previously.
     465     */
     466    public function retryPendingIdMappings() {
     467        $pending = $this->getSettingValue('fastcomments_pending_id_mappings');
     468        if (!$pending) {
     469            return;
     470        }
     471        $mappings = json_decode($pending, true);
     472        if (!is_array($mappings) || count($mappings) === 0) {
     473            $this->setSettingValue('fastcomments_pending_id_mappings', null);
     474            return;
     475        }
     476        $this->log('debug', "Retrying " . count($mappings) . " pending ID mappings");
     477        $token = $this->getSettingValue('fastcomments_token');
     478        $patch_url = "$this->baseUrl/comment-ids?token=$token";
     479        // Send in batches of 500 (endpoint limit)
     480        foreach (array_chunk($mappings, 500) as $batch) {
     481            $patch_response = $this->makeHTTPRequest('PATCH', $patch_url, json_encode(array('mappings' => $batch)));
     482            if ($patch_response->responseStatusCode !== 200) {
     483                $this->log('warn', "Retry of pending ID mappings failed (HTTP $patch_response->responseStatusCode), will try again next tick");
     484                return;
     485            }
     486        }
     487        $this->setSettingValue('fastcomments_pending_id_mappings', null);
     488        $this->log('debug', "All pending ID mappings sent successfully");
     489    }
     490
    425491    private function setSetupDone() {
    426492        /**
  • fastcomments/trunk/core/FastCommentsWordPressIntegration.php

    r3436960 r3463885  
    8787        delete_option('fastcomments_comment_sent_count');
    8888        delete_option('fastcomments_log_level');
     89        delete_option('fastcomments_pending_id_mappings');
    8990
    9091        $timestamp = wp_next_scheduled('fastcomments_cron');
     
    192193    }
    193194
     195    /**
     196     * Records a FC→WP mapping after inserting a comment during sync.
     197     * Stores both the mapping table entry and the comment_meta.
     198     */
     199    public function recordSyncMapping($fcId, $wpId) {
     200        $this->addCommentIDMapEntry($fcId, $wpId);
     201        update_comment_meta($wpId, 'fastcomments_id', $fcId);
     202    }
     203
    194204    private function addCommentIDMapEntry($fcId, $wpId) {
    195205        $this->log('debug', "addCommentIDMapEntry $fcId -> $wpId");
     
    235245    }
    236246
     247    /**
     248     * Fallback lookup: find a WP comment via externalId or meta.wpId from the FC comment.
     249     * Both are WP primary key lookups via get_comment() — O(1), not a table scan.
     250     */
     251    private function getWPCommentIdFromFCComment($fc_comment) {
     252        // Check meta.wpId first (set when WP sends IDs back after "Download FC → WP" sync — most recent)
     253        if (isset($fc_comment->meta) && isset($fc_comment->meta->wpId) && is_numeric($fc_comment->meta->wpId)) {
     254            $wp_comment = get_comment((int)$fc_comment->meta->wpId);
     255            if ($wp_comment) {
     256                $this->log('debug', "Found WP comment {$fc_comment->meta->wpId} for FC ID {$fc_comment->_id} via meta.wpId");
     257                return (int)$fc_comment->meta->wpId;
     258            }
     259        }
     260        // Fall back to externalId (set when comments were originally uploaded from WP → FC)
     261        if (isset($fc_comment->externalId) && is_numeric($fc_comment->externalId)) {
     262            $wp_comment = get_comment((int)$fc_comment->externalId);
     263            if ($wp_comment) {
     264                $this->log('debug', "Found WP comment {$fc_comment->externalId} for FC ID {$fc_comment->_id} via externalId");
     265                return (int)$fc_comment->externalId;
     266            }
     267        }
     268        return null;
     269    }
     270
    237271    public function makeHTTPRequest($method, $url, $body) {
    238272        // Use longer timeout for POST requests (comment uploads can take time with large batches)
     
    324358
    325359        $wp_id = $this->getWPCommentId($fc_comment->_id);
    326         $wp_parent_id = isset($fc_comment->parentId) && $fc_comment->parentId ? $this->getWPCommentId($fc_comment->parentId) : 0;
     360
     361        // Fallback: use externalId or meta.wpId from the FC comment (O(1) primary key lookups)
     362        if ($wp_id === null) {
     363            $wp_id = $this->getWPCommentIdFromFCComment($fc_comment);
     364            if ($wp_id !== null) {
     365                // Re-populate the mapping table so future lookups use the fast path
     366                $this->addCommentIDMapEntry($fc_comment->_id, $wp_id);
     367            }
     368        }
     369
     370        $parent_wp_id = isset($fc_comment->parentId) && $fc_comment->parentId ? $this->getWPCommentId($fc_comment->parentId) : null;
     371        $wp_parent_id = $parent_wp_id ? $parent_wp_id : 0;
    327372
    328373        $wp_comment['comment_ID'] = is_numeric($wp_id) ? $wp_id : null;
     
    388433    public function handleEvents($events) {
    389434        $this->log('debug', "BEGIN handleEvents");
     435        $mappings = array();
    390436        foreach ($events as $event) {
    391437            try {
     
    421467                            $comment_id_or_false = wp_insert_comment($new_wp_comment);
    422468                            if ($comment_id_or_false) {
    423                                 $this->addCommentIDMapEntry($fcId, $comment_id_or_false);
    424                                 update_comment_meta($comment_id_or_false, 'fastcomments_id', $eventData->comment->_id);
     469                                $this->recordSyncMapping($fcId, $comment_id_or_false);
     470                                $mappings[] = array('fcId' => $fcId, 'wpId' => $comment_id_or_false);
    425471                                $this->log('debug', "Saved $fcId (wp id $comment_id_or_false)");
    426472                            } else {
     
    493539            }
    494540        }
     541        $this->sendCommentIdMappings($mappings);
    495542        $this->log('debug', "END handleEvents");
    496543    }
  • fastcomments/trunk/fastcomments-wordpress-plugin.php

    r3462221 r3463885  
    44Plugin URI: https://fastcomments.com
    55Description: A live, fast, privacy-focused commenting system with advanced spam prevention capabilities.
    6 Version: 3.17.0
     6Version: 3.18.0
    77Author: winrid @ FastComments
    88License: GPL-2.0+
     
    1414}
    1515
    16 $FASTCOMMENTS_VERSION = 3.170;
     16$FASTCOMMENTS_VERSION = 3.180;
    1717
    1818require_once plugin_dir_path(__FILE__) . 'admin/fastcomments-admin.php';
  • fastcomments/trunk/public/fastcomments-public.php

    r2768101 r3463885  
    9696            $count = count($get_comments_response->comments);
    9797            if ($count > 0) {
     98                $mappings = array();
    9899                foreach ($get_comments_response->comments as $comment) {
    99100                    $wp_comment = $fastcomments->fc_to_wp_comment($comment, true);
    100                     if (!wp_update_comment($wp_comment)) {
    101                         wp_insert_comment($wp_comment);
     101                    if (is_numeric($wp_comment['comment_ID'])) {
     102                        // Existing comment found - update it
     103                        wp_update_comment($wp_comment);
     104                        update_comment_meta($wp_comment['comment_ID'], 'fastcomments_id', $comment->_id);
     105                    } else {
     106                        // No existing mapping found - insert new comment
     107                        unset($wp_comment['comment_ID']);
     108                        $new_id = wp_insert_comment($wp_comment);
     109                        if ($new_id > 0) {
     110                            $fastcomments->recordSyncMapping($comment->_id, $new_id);
     111                            $mappings[] = array('fcId' => $comment->_id, 'wpId' => $new_id);
     112                        } else {
     113                            $fastcomments->log('error', "Failed to insert WP comment for FC ID $comment->_id");
     114                        }
    102115                    }
    103116                }
     117                $fastcomments->sendCommentIdMappings($mappings);
    104118                return new WP_REST_Response(array('status' => 'success', 'hasMore' => $get_comments_response->hasMore, 'totalCount' => $includeCount ? $get_comments_response->totalCount : null, 'count' => $count), 200);
    105119            } else {
Note: See TracChangeset for help on using the changeset viewer.