=== itdatex Sync Gate ===
Contributors: itdatex
Tags: staging, sync, migration, database, developer
Requires at least: 6.4
Tested up to: 7.1
Requires PHP: 8.1
Stable tag: 0.11.2
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Granular per-table sync between two paired WordPress sites. Copy individual tables (posts, options, terms) between staging and live.

== Description ==

Sync Gate connects two WordPress installs through an HMAC-signed pair handshake (no cloud broker in between). After pairing, individual database tables can be pulled from or pushed to the other site, with:

* Automatic pre-backup (SQL dump inside the uploads/ directory, HTTP access blocked)
* Serialization-safe URL rewrite (unserialize -> replace -> serialize, so no broken serialized options escape)
* Sensible default exclusions (users, orders, sessions stay local)
* Full audit log (which table, when, how many rows, which backup)

**Audience:** Agencies and developers who need to selectively synchronize WooCommerce shops or content sites between staging and live.

== Features (v0.11.2) ==

* HMAC-signed REST endpoints (no cloud middleman)
* Pairing via pending secret + handshake (15 minute TTL)
* **Table sync** - pull and push individual database tables between paired sites
* **File sync** - pull and push wp-content/uploads/ with diff (size + mtime), backup, dry-run and optional --delete. **Chunked up to 500 MB per file** (5 MB per HTTP chunk, SHA256 integrity check, wp-cron cleanup of expired sessions). **Parallel chunks via curl_multi in both directions** (1..10 configurable, default 3) and **auto-resume on push AND pull** (push session as WP option, pull session as folder-backed store in `uploads/itdatex-sync-gate/pulls/`)
* **Central settings** under Tools -> Sync Gate -> Settings and via WP-CLI `wp sync-gate config`. Configurable: chunk_concurrency (1..10), chunk_size (256 KB..20 MB), session_ttl_hours (1..168)
* Web UI under Tools -> Sync Gate with tabs "Pairs", "Tables", "Files", "Log" and direction radios
* WP-CLI: `list-tables`, `pair-init`, `pair-connect`, `pair-list`, `pull`, `push`, `files pull|push`, `files-resume-list`, `pull-resume-list`, `pull-resume-forget`
* Default exclusions for WooCommerce orders, users, sync-gate's own tables (enforced server-side and client-side)
* Serialized values are handled correctly (including nested)
* Nonce-based replay protection (10 minute TTL) and time window +/- 60s
* Path traversal protection, .php/.phar exclusion, symlink ignore

== Security model ==

Every REST call between paired sites carries four headers:

* `X-Itdatex-SG-Signature` = HMAC-SHA256(secret, method || path || sha256(body) || nonce || timestamp)
* `X-Itdatex-SG-Nonce` (16 byte hex, valid once for 10 minutes)
* `X-Itdatex-SG-Timestamp` (unix seconds, +/- 60s window)
* `X-Itdatex-SG-Remote-URL` (sender URL for pair lookup)

The secret itself is transmitted over HTTPS and stored in plain text in the pair table. Anyone with database access on either side effectively has access to the other; encryption at rest would not add real security here because the decryption key would live in the same WordPress install.

== Installation ==

1. Extract the plugin into `/wp-content/plugins/itdatex-sync-gate/`
2. Activate it in WP Admin under Plugins
3. Activate it on both sites that should be paired
4. Open Tools -> Sync Gate and run the pairing

== Frequently Asked Questions ==

= What happens to my local table before a pull? =

It is written as an SQL dump to `wp-content/uploads/itdatex-sync-gate/backups/<table>-<timestamp>.sql`. Then DROP + CREATE + INSERT with the remote data.

= What about the users / orders tables? =

They are on the default exclusion list. They are not synced unless you pass an explicit `--force` flag.

= Can I push (local -> remote) as well? =

Yes, since v0.2.0. `wp sync-gate push posts --pair-id=1` or via the web UI (choose the "Push" radio).

= Are uploads / media synchronized? =

Yes, since v0.3.0. `wp sync-gate files pull --pair-id=1 --dry-run` shows the diff; without --dry-run the files are transferred. Themes and plugins are excluded (deploy them via Git/Composer). .php, .phar and .htaccess files are never synced for security reasons.

= What happens if the connection drops during a pull? =

The backup has already been written. The sync log carries an "error" status. Manual recovery via `wp db import <backup-file>`.

== Changelog ==

= 0.11.2 =
* wp.org review pass: REST endpoints now expose a real permission_callback that performs the HMAC/pair verification (previously __return_true + in-handler check); admin tab-tables JS moved out of an inline `<script>` block and enqueued via wp_enqueue_script; load_plugin_textdomain() removed (wp.org auto-loads since WP 4.6); readme short description shortened and comparative wording dropped. No behavior change.

= 0.11.1 =
* wp.org compliance pass: settings admin labels are English, plugin header language corrected, defensive wp_unslash+sanitize on all admin $_POST reads, REST permission_callback rationale documented inline. No functional change.

= 0.11.0 =
* SECURITY: server-side table exclusion could be bypassed by client-side --force. From v0.11.0 the server exclusion is absolute, both for pull and push. A compromised pair partner can no longer override the exclusion via "force": true in the body.
* FIX: ChunkStore::append_chunk had a race in load_meta at chunk_concurrency > 1 - parallel chunk writers could read a truncated meta.json and throw "meta.json corrupt". Read-modify-write now runs entirely under flock.
* NEW: tools/e2e/ with a complete cross-site test suite (setup.sh + 6 tests + teardown.sh).

= 0.10.0 =
* Pull auto-resume: `PullSessionStore` persists chunk progress across sync runs (assembly.bin preallocated with ftruncate, received_offsets in meta.json). Aborted pulls resume at the incomplete offsets on the next sync. New wp-cron `itdatex_sg_pull_cleanup` and new WP-CLI commands `pull-resume-list` and `pull-resume-forget`. No server change, no migration.

= 0.9.0 =
* Parallel pull: `FileSyncer::pull_one_chunked` fetches chunks the same way the push already did (`chunk_concurrency` batches over `ParallelHttp`, out-of-order via `fseek`+`fwrite` into a temp file preallocated with `ftruncate`). No server change, no migration.

= 0.8.0 =
* Chunk threshold now follows the chunk_size setting. In v0.7 the threshold was hardcoded to 5 MB, so a 3 MB file was transferred single-shot even when chunk_size=1 MB. Now: file > chunk_size -> chunked. Fallback 5 MB only when the settings service is unreachable.

= 0.7.0 =
* chunk_size (256 KB..20 MB) and session_ttl_hours (1..168) are settings now. chunk_size is pinned per session in meta.json, running uploads are protected against setting changes.

= 0.6.0 =
* Configurable parallelism for chunked file push. New admin tab "Settings" and WP-CLI `wp sync-gate config list|get|set`. Currently only chunk_concurrency (1..10); more settings to follow.

= 0.5.0 =
* Parallel upload (3 in-flight chunks via curl_multi) and auto-resume after connection drop. ChunkStore switched to out-of-order layout. New REST endpoint /files/put-status. WP-CLI files-resume-list + files-resume-forget.

= 0.4.0 =
* Chunking for large files: file sync now supports up to 500 MB per file via 5 MB chunks with SHA256 verification. REST endpoints /files/{put,fetch}-{init,chunk,commit}. Daily wp-cron cleanup for aborted sessions.

= 0.3.0 =
* File sync for wp-content/uploads/: REST /files/manifest + /files/fetch + /files/put, WP-CLI `files pull|push`, admin tab "Files" with dry-run + --delete.

= 0.2.0 =
* Push direction: `wp sync-gate push`, REST /push-target, admin UI pull/push radio, log `push` + `push-in`.

= 0.1.0 =
* Initial release. Pull direction (remote -> local), pairing, backup, URL rewrite, exclusions.

== Upgrade Notice ==

= 0.11.2 =
wp.org review pass. Real REST permission_callback, enqueued admin JS, no functional change. Safe to update.

= 0.11.1 =
Compliance and language pass, no behavior change. Safe to update.

= 0.11.0 =
Contains two fixes for bugs that surfaced in the first real cross-site test: server-side exclusion was client-overridable (security), ChunkStore meta.json was not race-safe under parallel push (consistency). If you rely on server-side user/order exclusions, upgrade to v0.11.0. No DB schema change, no migration.

= 0.10.0 =
Adds pull auto-resume. On first activation a new wp-cron hook `itdatex_sg_pull_cleanup` is registered and the folder `wp-content/uploads/itdatex-sync-gate/pulls/` is created. No DB schema change, no server endpoint change. Peak disk usage during a running pull equals the target file size.

= 0.9.0 =
Adds parallel pull. Peak RAM during pull rises to chunk_concurrency * chunk_size * ~1.33 (default 3 * 5 MB ~= 20 MB). No DB schema change, no migration, no server endpoint change.

= 0.8.0 =
Chunk threshold follows chunk_size setting. If you set chunk_size < 5 MB, all files above that threshold switch to chunked. No DB schema change, no migration.

= 0.7.0 =
Two new settings, no migration needed. Defaults match v0.6 behavior exactly (5 MB chunk_size, 24h session_ttl).

= 0.6.0 =
New option `itdatex_sg_settings`, no migration needed. Existing behavior does not change (default remains concurrency 3).

= 0.5.0 =
Parallel upload + resume. No DB schema change. Aborted v0.4 sessions must be discarded and restarted (session layout incompatible).

= 0.4.0 =
Chunking up to 500 MB. No DB schema change, but a new wp-cron job is registered on activation.

= 0.3.0 =
File sync added. No migration overhead, no DB schema change.

= 0.2.0 =
Push direction added.

= 0.1.0 =
Initial release.
