Releases: mikefreemantn/SourceHub
v2.4.2 - Fix duplicate hook registration
Fixed
- Duplicate hook registration on hub and spokes — Added
static $initializedguards toSourceHub_Hub_Manager::init()andSourceHub_Spoke_Manager::init(), matching the existing pattern inSourceHub_AdminandSourceHub_Validation.
Under certain hosting environments (notably WPEngine MU plugin layers), init() was being invoked more than once per request, causing every action hook — including transition_post_status, post_updated, and sourcehub_delayed_sync — to be registered twice. The downstream effect was:
- Every editorial save triggered 2× syndication requests per spoke
- Slow spokes (e.g., Williamson Source) couldn't dedupe at
receive_posttime before the second request arrived, generating frequentrace_preventedlog entries delayed_sync_replaywarnings on every publish (safety net catching the duplicate delayed sync)- Last-write-wins content corruption when AI rewriting produced non-deterministic variants between the duplicate jobs
The guard is a no-op when init() is called only once (the normal case). When a duplicate call is detected, a single error_log line is written to wp-content/debug.log for confirmation:
SourceHub: Hub_Manager::init() called again in same request - ignoring duplicate registration
Verification After Update
- Confirm Plugins page shows v2.4.2 on hub and all spokes
- Publish one test post —
status_transitionactivity log entries should appear once, not twice - Check
wp-content/debug.logfor the diagnostic line above (if pres3. Checkwp-content/debug.logfore fix is working) - Williamson Source's
race_preventedentries should drop to near-zero over the following hours
v2.4.1 - GitUpdater release detection fix
Why this release exists
v2.4.0 was deployed correctly (tag, release, auto-zipball) but GitUpdater did not detect the update on sites where SourceHub was originally installed from the main branch zip. The plugin header was also missing the Release Asset: true and Update URI directives that tell GitUpdater to track tagged releases instead of branch HEAD.
This release adds those headers and uploads an explicit zip asset to remove any ambiguity around update detection.
Changes
- Added
Release Asset: trueandUpdate URItosourcehub.phpplugin header. - Uploaded
SourceHub-v2.4.1.zipas an explicit release asset (built viagit archivefrom the v2.4.1 tag). - Updated
DEPLOYMENT.md: required headers, branch-install vs release-install gotcha, and a new step to upload the release zip. - No functional plugin changes vs v2.4.0 — all scheduled-post fixes and the spoke View link from v2.4.0 - No functional plugin changes vs v2.4.0 — all scheduled-post fixes and the spoke View link from v2.4.0 - No )- No functional plugin changes vs v2.4.0 — all scheduled-post fixes and the spoke View link from v2.4.0 - No functional plugin changes vs v2.4.0 — all scheduled-post fixes and the spoke View link from v2.4.0 - No )- No functional plugin changes vs v2.4.0 — all scheduled-post fixes and the spoke View link from v2.4.0 - No functionaler that, future tagged releases auto-update.
Verification URLs
v2.4.0 - Scheduled-post fixes & spoke View links
Bug Fixes
Premature publishing of scheduled posts
Multiple defenses against the WordPress core auto-flip behavior that turned future → publish whenever post_date_gmt was already in the past at save time.
- Retry preserves future date —
handle_spoke_retry()no longer stampspost_datewith "now" when the original post is infuturestatus. This prevents a Retry click on a scheduled post from publishing it immediately. - Hub-side stale-future guard — In
prepare_post_data(), when status would be sent asfuturebutpost_date_gmthas already passed (cron lag, late delayed-sync), the hub downgrades topublishand logs astale_future_guard_hubwarning instead of relying on WP's silent auto-flip. - Spoke-side stale-future guard —
update_post_from_data()and the create-path deferred publish increate_post_from_data()now coerce stalefuture→publishexplicitly with a loggedstale_future_guard_spokewarning. Belt-and-suspenders for anything that slips past the hub guard.
Stuck-draft cron false positives
The 3rd-tier safety net introduced in 2.3.0 was firing on posts that were legitimately scheduled for future publish (the hub creates a draft on the spoke immediately at scheduling time, then sends the publish UPDATE only when the future→publish transition fires — potentially days later). The cron now skips any draft whose post_date_gmt is in the future. This eliminates spurious "stuck draft" alerts and the manual-Retry chain they were prompting.
New Features
"View" link on synced spokes
Synced posts now display a direct link to the published spoke article in the SourceHub Syndication meta box.
- Spoke sends the final permalink via
get_permalink()in thesync-completecallback asspoke_post_url. - Hub stores the URL in
_sourcehub_sync_status[connection_id]['spoke_post_url']. - Renders as a "View" button next to the Synced badge (PHP on page load, JS on dynamic AJAX status updates).
- Falls back to constructing
{connection_url}/{post_name}/when called against an older spoke that does not yet send the URL.
Compatibility
- Hub and spokes can be updated independently. Scheduled-post fixes work even with one side updated.
- The "View" link feature requires both hub and spoke on 2.4.0 to use the real permalink; older spokes will use the slug-based fallback URL.
- Watch the activity log for the new actions
stale_future_guard_hubandstale_future_guard_spoke— they indicate the bug was actively firing on real traffic.
v2.3.0 - Stuck-Draft Safety Net & Delayed-Sync Replay
New Features
- 3rd-tier stuck-draft safety net — Spokes now run a cron sweep every 5 minutes looking for SourceHub-created posts that have been sitting in
draftstatus longer than 10 minutes. When found, they POST a report to the hub's new/wp-json/sourcehub/v1/report-stuck-draftendpoint. The hub then logs an ERROR-level activity entry and flips the spoke's_sourcehub_sync_statustofailedwith a descriptive error message, so the stuck post appears in both Activity Logs and Post Logs. from_SourceHubcustom field — every post created via SourceHub on a spoke is now tagged withfrom_SourceHub = truepost meta, making SourceHub posts identifiable by external tooling, custom queries, and admin filters.- Auto-syndicate API — trigger syndication programmatically via post meta fields (
sourcehub_auto_syndicate,sourcehub_auto_syndicate_to_id). SeeAUTO-SYNDICATE-API.mdfor details. - Messaging panel improvements.
Bug Fixes
- Delayed-sync replay safety net — Fixes a race condition where duplicate CREATE callbacks could schedule a second
sourcehub_delayed_syncevent, which would silently skip publishing leaving spoke posts stuck as drafts. The handler now replaysupdate_syndicated_post()as a safety net. Idempotent because spoke-sidefind_existing_post(hub_id + origin_url)resolves the target post before any write — duplicate UPDATEs cannot create duplicate posts. - Timezone bug in stuck-draft age calculation —
_sourcehub_received_atis stored in WP local time but was being compared against UTC, causing age reports to be off by the site'sgmt_offset(e.g. "305 minutes" for a 5-minute-old draft on a UTC-5 site). Now usesget_gmt_from_date()for correct timezone-aware comparison.
Improvements
- Defensive idempotency throughout the sync pipeline — both CREATE and UPDATE flows on the spoke side use the same
find_existing_post(hub_id + origin_url)lookup, eliminating any path that could create duplicate spoke posts. - New
delayed_sync_replaylog action key (replacesdelayed_sync_unexpected_state) for easier filtering of safety-net triggers in Activity Logs. - Cron schedule
sourcehub_every_five_minutesregistered with a clean unschedule on plugin deactivation.
Upgrade Notes
No database migrations or breaking changes. Existing spoke posts created before this release will get the from_SourceHub meta on their next UPDATE through SourceHub. The stuck-draft cron self-registers on the next init after the plugin loads.
v2.2.0 - Message Reactions & Enhancements
🎉 New Features
Message Reactions System
- 6 Emoji Reactions: 👍 👎 ❤️ 😂 🎉 🤔
- Hover-based Picker: Reaction options appear when hovering over messages
- Reaction Display: Shows counts and user names who reacted
- Visual Feedback: Your reactions are highlighted in green
Message Menu (3-Dot Menu)
- Copy Text: Copy message content to clipboard with visual confirmation
- Delete Message: Anyone can delete any message (admin environment)
- Clean UI: Discord-style dropdown menu
🐛 Bug Fixes
- Fixed OG tag preview infinite reload loop
- Fixed OG preview disappearing after a few seconds
- OG data now cached and persists across message refreshes
- Fixed data-message-id attribute for proper element selection
🗄️ Database Changes
- New Table:
wp_sourcehub_message_reactions- Stores reaction_type, user_id, message_id, created_at
- Action Required: Deactivate and reactivate plugin to create the new table
📦 Installation
Download the latest release and install via WordPress admin or use GitUpdater for automatic updates.
⚠️ Impo## ⚠️ Impo## ⚠️ Impo## ⚠️ Impo## ⚠️ Impo## ⚠️ Impo## ⚠er updating to create the reactions table
- Message reactions feature requires the new database table to function
- All users can - All user message (designed f- All users can - All user message (designed f- All users can - All user m0
- Deactivate the plugin
- Reactivate the plugin (creates new database table)
- Test message reactions and menu features
v2.1.0 - Complete Messaging System with Notifications
New Features
- Automatic Link Previews: URLs in messages automatically display Open Graph data with images, titles, and descriptions
- Sound Notifications: Different notification sounds for individual messages (individualmessage.mp3) and group messages (groupmessage.mp3)
- Group Name in Notifications: Group message notifications now display 'Sender in GroupName' format
- Image Thumbnails: Notification popups show image previews when messages contain attachments
- Real-time Badge Updates: Unread message badges clear immediately when messages are read
- Infinite Scroll: Load older messages by scrolling up in conversations
Improvements
- Fixed group creation to work with new database schema
- Added missing AJAX handler for updating group names
- Fixed remove member from group functionality
- Notification clicks now open the correct group or conversation
- Online indicator properly posi- Online indicator properly posi- Online indicator properly posi- Online indicator properly posi- Online indicatle- Online indicator properly posi- Online indicator properly posi- Online indicator properly posi- Online indicator properle - Online indicator properly posi- Online indicator properly posi- Online indicator properly posi- Online indicator properly posi- Online indior- Online indicator properly posi- Online indicator properly posi- Online indicator properly posi- Onldat- Online indicator properly posi- Online indicator properly posi- Online indicator properly posess Heartbeat API for real-time updates
- Implemented proper AJAX handlers for all messaging oper- Implemented proper AJAX handlers for all messaging oper- Implemellation Not- ImplementedT**- Implemented proper AJAX handlers for all messagid reac- Implemented proper AJAX handlers for all messatabase tables.
v2.0.2.8 - Fix Spoke Dashboard Last Sync Display
Fix: Spoke Dashboard Last Sync Display
This release fixes the spoke dashboard showing scheduled post publish date instead of actual sync time.
Problem
- Dashboard query used post_date (scheduled publish date like 5/18/26 @ 1PM) instead of actual sync timestamp
- For scheduled posts, this showed the future publish date instead of when the post was actually synced
- Example: Post synced on 4/17/26 morning but scheduled for 5/18/26 @ 1PM showed 5/18/26 8:00 AM as Last Sync
- Time was also showing in UTC instead of site's local timezone
Solution
- Modified dashboard query to use _sourcehub_last_sync meta field instead of post_date
- Checks last 100 synced posts and finds the most recent _sourcehub_last_sync timestamp
- Falls back to _sourcehub_received_at if _sourcehub_last_sync not found
- Time display already uses site's local timezone via get_date_from_gmt() in time_ago() function
- Result: Dashboard no- Result: Dashboard no- Result: Dashboard no- Result: Dashboard no- Result: Dashboard no- Result: Dashboard no- Result: Dashboard no- R-so- Result: Dashboard no- Result: Dashboard no- Result: Dashboard no- Result: Dashboard no- Result: Dreased posts_per_p- Result: Dashboard no- Result: Dashboard no- Result: Dashboard no- Result: Dashboard no- Result: Dashboard mp- Result: Dashboard no- Result: Dashboard no- Result: Dashboard no- Result: Dashboard no- Result: ync
- Timezone conversion already handled by existing time_ago() function using get_date_from_gmt()
Impact
- Spoke dashboard now accurately shows when last sync occurred
- No longer misleading for scheduled posts
- Displays time in site's local timezone (not UTC)
- More accur- More accur- More accur- More accur- More accur- More accur- More accur- More accur- (fixes dashboard display)
- Hub sites: No changes needed for this fix
Files Changed
- admin/class-sourcehub-admin.php
- sourcehub.php
- CHANGELOG.md
v2.0.2.7 - Fix Duplicate UPDATE Requests from Race Condition Prevention
Critical Fix: Duplicate UPDATE Prevention for Race Conditions
This release fixes the hub sending multiple UPDATE requests when race condition prevention creates one post but multiple jobs report completion.
Problem
- Hub sends multiple simultaneous CREATE requests (due to duplicate delayed sync triggers)
- Spoke race condition prevention creates only ONE post but ALL job IDs send completion callbacks
- Hub receives multiple callbacks with same spoke_post_id but different job IDs
- Hub tracks all job IDs in sync_status and tries to UPDATE each one
- Result: Multiple UPDATE requests sent to same spoke post
- Example: 3 CREATE jobs → 1 post created, 2 race-prevented → 3 callbacks → hub tries to send 3 UPDATEs
Solution
- Deduplicate syndicated_spokes before sending UPDATEs by mapping spoke_post_id to connection_id
- Only send ONE UPDATE per unique spoke post instead of one per - Only send ONE UPDATE per unique spoke post instead of one per - Only send ONE UPDATE per unique spoke post instead of one per - Only send ONE UPDATE per unique spoke post instead UPD- Only send ONE UPDATE per unique spoke post instead of one per - Only send ONE UPDATE per unique spoke post instead of one per - Only send ONete(- Only sect - Only send ONE UPDATE per unique spoke post instead of one per - Only send ONE UPDATE peto- Only send ONE UPDATE per unique spoke post instead of one per - Only send ONE UPDATE per uniqura- Only send ONE UPDATE per unique spoke post instead of one per - Only send OPDA- Only send ONE UPDATE per unique spoke post instead of one per - Oup- Only send ONE UPDATE per unique spoke post instead of oand potential for conflicts
- Maintains all existing race condition prevention benefits
- Backward compatible with older spoke- Backward compatible with older spoke- Backward compatible with older spoke- Backward coATE prevention)
- Spoke sites: Update to v2.0.2.7 (recommended but not required - already sends post IDs correctly)
Files Changed
- includes/class-sourcehub-hub-manager.php
- includes/class-sourcehub-spoke-manager.php
- sourcehub.php
- CHANGELOG.md
v2.0.2.6 - Fix Hub Status Tracking for Stuck Posts
Critical Fix: Accurate Status Tracking
This release fixes the hub showing posts as completed when they are actually stuck in draft on spoke sites.
Problem
- Hub was clearing status transient immediately after sending requests, before callbacks were received
- syndicate_post() cleared transient after sending CREATE requests
- update_syndicated_post() cleared transient after sending UPDATE requests
- If delayed sync failed or UPDATE never fired, hub had no status tracking
- UI would show completed even when spokes were stuck in draft state
- This masked the real issue: delayed sync not firing in poor hosting environments
Solution
- Removed premature delete_transient() calls from both functions
- Transient now only cleared in handle_sync_complete() when all callbacks received
- Hub now accurately shows which spokes are stuck vs completed
- Each spoke tracked independently - one failure does not affect others
Impact
- Hub status accurately reflects actual spoke state (draft vs published)
- Admins can identify which posts need manual intervention
- Reveals underlying issues (delayed sync failures, Action Scheduler problems)
- No impact on successful syncs - they still complete normally
- Each spoke operates independently - partial failures visible
Technical Details
- Modified syndicate_post() in class-sourcehub-hub-manager.php (line 2195-2198)
- Modified update_syndicated_post() in class-sourcehub-hub-manager.php (line 2396-2399)
- Removed delete_transient calls from both functions
- Transient clearing logic remains in handle_sync_complete() (lines 395-398)
- Only clears when all spokes report back (success OR failure)
Deployment
- Hub sites: Update to v2.0.2.6 (fixes status tracking)
- Spoke sites: Can stay on v2.0.2.4 or v2.0.2.5 (no spoke-side changes)
Files Changed
- includes/class-sourcehub-hub-manager.php
- sourcehub.php
- CHANGELOG.md
v2.0.2.5 - Fix Duplicate UPDATE Calls
Critical Fix: Duplicate UPDATE Prevention (Part 2)
This release fixes the remaining duplicate UPDATE issue found in v2.0.2.4 where was calling twice internally within a single execution.
Problem
- v2.0.2.4's delayed sync lock prevented the function from running twice, but didn't prevent duplicate calls WITHIN a single execution
- had multiple code paths that both called
- When flag was set AND existed, both paths would execute
- Result: Two UPDATE requests sent in the same second with different job IDs
Solution
- Modified to only call once when completing drafts
- Other code paths now skip and log warnings instead of calling update again
- Added warning for debugging edge cases
- Result: Only ONE UPDATE request sent per delayed sync execution
Technical Details
- Modified in (lines 3157-3185)
- Changed else-if branches to skip updates and log warnings instead of calling
- Only the path calls now
- Added warning logs for unexpected states
Deployment
- Hub sites: Update to v2.0.2.5 (fixes duplicate UPDATEs)
- Spoke sites: Can stay on v2.0.2.4 (already has hub wake-up fix)