=== APPideas Site Syncinator ===
Contributors:      appideasdotcom
Tags:              multisite, sync, compare, plugins, themes
Requires at least: 6.3
Tested up to:      7.0
Stable tag:        1.4.2
Requires PHP:      7.4
License:           GPLv2 or later
License URI:       https://www.gnu.org/licenses/gpl-2.0.html

Connect two WordPress sites to compare plugin and theme versions and presence, and sync either way between mutually paired sites.

== Description ==

APPideas Site Syncinator connects two WordPress sites and shows, on one screen, how their
installed plugins and themes differ — versions that do not match, and items installed on
one site but missing from the other. Between sites that name each other as peer, it can
also sync those items in either direction.

It adds a single wp-admin screen and nothing else: no public-facing frontend, no dashboard
widgets, and no changes to any other admin page.

Syncing moves plugin and theme files between two sites that you own and that have each been
configured to name the other. Nothing is ever downloaded from APPideas or from any third
party, and no site can install code on another until an administrator on the receiving site
has paired it deliberately. A receiving site downloads only from the single address stored
in its own settings — never from an address named in a request — and verifies the archive's
checksum and contents before anything is installed.

= External services =

This plugin contacts exactly one external address: the peer site URL you enter in its
settings. Nothing else is contacted. No data is sent to APPideas, and there is no analytics,
telemetry, licence check, or update service of any kind.

What this site sends to the peer site you configure:

* To compare — the plugins and themes installed here, with their versions and active state, plus this site's name, address, and connection settings, so the two sites can confirm they are paired with each other.
* To sync — a zip archive of the single plugin or theme being transferred, and the display name of the administrator who started it, so the other site can record who changed it.

Every request is signed with the shared key you generate and install on both sites. The key
itself is never transmitted. Because the peer is a site you choose and run, no terms of use
or privacy policy beyond your own applies to it.

= Comparing =

* Side-by-side Plugins and Themes tabs, colour coded by status, with a differences-only filter.
* Peer data is cached; the panel says how old it is, and a refresh button forces a live re-fetch.
* A comparison never writes anything to either site.

= Syncing =

* Push your copy of a plugin or theme to the peer, or pull the peer's copy.
* The receiving site backs up what it replaces first, and rolls back automatically if the install fails.
* A per-item Restore control puts a backup back. Keep between one and five backups per item, and choose which one to restore.
* A newly installed plugin or theme is left inactive. This plugin itself and single-file plugins are never synced.

= Staying in control =

* Syncing requires both sites to name each other as peer. A shared key alone allows comparison only.
* Optional one-time passcodes require an administrator on each site to approve every sync before it runs.
* Comparison only takes a site out of syncing entirely, while comparison keeps working.
* A Log tab records every sync and restore — who did it, what changed, and the version before and after. Keep entries for anything from an hour to indefinitely, or switch logging off.
* One-way pairing is detected automatically when one site cannot be reached from the other, so a local development site can pull from a public one.

= Multisite =

Network-activate the plugin and the whole network pairs as a single site, identified by the
main site's URL and administered from Network Admin. Subsites get no Syncinator screen by
default, because syncing installs into the network-shared plugins and themes directories.

A network administrator can switch that on, giving every subsite its own peer, key, log and
backups, run by that subsite's own administrator. Because the files are shared network-wide,
each plugin or theme is locked to the first subsite that syncs it, and anything activated
network-wide is never claimable.

= APPideas Site Syncinator PRO adds =

* Host mode — one site compares and syncs against all your other sites at once. Sites arrive as requests to approve, or admit themselves with the host's rotating passcode.
* Multi-column comparison — a column per connected site, with its own sync controls, and columns you can hide and reorder.
* Sync All — bring every differing item into line in a single action.
* Read-only lockdown — freeze what is installed on a site while it keeps serving pulls.

== Installation ==

1. Upload the `appideas-site-syncinator` folder to `/wp-content/plugins/`, or install it from the Plugins screen.
2. Activate the plugin. On a multisite network, network-activate it.
3. Open **Syncinator** in the wp-admin menu — in Network Admin on multisite.

= Pairing two sites =

Both sites need the plugin installed and activated, and both need the same shared key.

1. On site A, open **Syncinator > Settings**.
2. In the **Authentication** card, generate a **Shared key** and copy it — the two buttons sit beside the field.
3. In the **Sites** card, enter site B's address as the **Peer site URL**. In the **Connection** card, switch **Allow connections** on. Save each card.
4. Repeat on site B, pasting in the same shared key and entering site A's address.

The Plugins and Themes tabs then show the comparison. Sync controls appear once each site
names the other: a shared key on its own is enough to compare, never to sync.

If only one of the two sites is reachable from the internet, there is nothing extra to
configure — see the one-way pairing question below.

== Frequently Asked Questions ==

= How is the connection between the two sites secured? =

Each site signs its requests to the other with an HMAC-SHA256 signature over a timestamp, a
single-use nonce, the route, and — where the request carries one — its body, so what gets
installed cannot be altered in flight. Unsigned, mis-signed, expired, replayed, tampered,
and wrong-route requests are all rejected, and the shared key itself is never transmitted.

= Where does the code it installs come from? =

From the other site you paired with, and from nowhere else. Both ends are WordPress installs
you own: one packages its own copy of a plugin or theme, the other installs it. No code is
fetched from APPideas, from a marketplace, or from any third-party server, and the plugin
never installs, activates, deactivates, or updates anything on its own.

Four things have to be true before a transfer can happen. Both sites must hold the same
shared key. Both must name the other as peer — a shared key on its own grants comparison and
nothing more. An administrator must ask for that specific item, by name, in one direction.
And if one-time passcodes are switched on, an administrator on each site must approve it
while it is waiting.

The receiving site fetches the archive only from the peer address in its own settings, checks
its SHA-256 against the one in the signed request, and refuses to install unless every file
in the archive sits under the expected folder. It backs up what it is replacing first and
rolls back automatically if the install fails. A newly installed plugin or theme is left
inactive.

= Does it change anything on either site? =

Comparison never writes anything. Syncing installs the chosen plugin or theme on the
receiving site, and only ever because an administrator asked for it — that site backs up the
copy it is replacing first, rolls back automatically if the install fails, and keeps the
backup for a one-click Restore.

= Both sites have the same key, but no sync controls appear. Why? =

Syncing also requires the pairing to be mutual: each site's configured peer URL must name
the other. A shared key alone grants comparison and nothing more.

= One of my sites is not reachable from the internet. Can I still use it? =

Yes. The plugin detects this and switches to one-way pairing: the private site compares and
pulls from the public one, and pushing is refused at both ends. The public site shows the
private site's request to pair this way, and an administrator there accepts it once.

= How do I stop a site from being changed? =

Switch on **Comparison only** in its Settings. That site keeps comparing but refuses every
sync in both directions, and its sync controls disappear.

= Are there server requirements beyond WordPress itself? =

Both sites must be able to zip and unzip, using either the PHP `zip` extension or the PclZip
library WordPress bundles in core. A site that can do neither says so, and comparison keeps
working regardless.

= How many backups are kept? =

One per item by default, and you can raise it to five in Settings. Each one is a full copy of
the installed directory, so more of them means more disk used — the Tools card reports the
total and can purge the lot. When several are held, Restore lets you choose which to put back.

== Screenshots ==

1. Installed plugins on both sites side by side, colour coded by status, with a differences-only filter and per-item sync controls.
2. The same comparison for themes.
3. Settings, grouped into cards that save separately — connection mode, shared key, one-time passcodes, cache lifetime, and how many backups to keep.
4. The activity log: every sync and restore, who set it off, and the versions either side.
5. What APPideas Site Syncinator PRO adds.

== Changelog ==

= 1.4.2 =
* One-time passcodes: an optional handshake requiring an administrator on each site to approve every sync before it runs.
* New **Log** tab recording every sync and restore — who, what, and the version before and after. Keep entries for anything from an hour to indefinitely, or switch logging off.
* **Comparison only** mode takes a site out of syncing entirely while comparison keeps working.
* Multisite support: network-activate once and the whole network pairs as a single site, administered from Network Admin.
* One-way pairing for a site that is not publicly reachable, detected automatically, with that site pulling only.
* How long peer data stays fresh is now a setting, including switching caching off.
* Settings are grouped into cards, each with its own Save and Cancel in place of one screen-wide button.
* New **Tools** card: see what the pre-sync backups are using on disk and purge every one of them from one place.
* Keep up to five pre-sync backups per item and choose which one a Restore puts back.
* A network administrator can give each subsite its own Syncinator, with its own peer, key, log and backups.
* The Syncinator screen is now the plugin's alone — notices other plugins would inject into it are removed.
* A connection waiting on the other site's administrator re-checks itself, so approval appears without reloading the page.
* PRO adds host mode with a column per connected site, Sync All, connection approval with a rotating passcode, and read-only lockdown.

= 1.1.0 =
* Plugin and theme syncing between mutually paired sites — push your version to the peer, or pull the peer's.
* Syncing requires both sites to name each other; a shared key alone still allows comparison only.
* The receiving site backs up what it replaces, rolls back automatically if the install fails, and offers a one-click Restore.
* Signed, checksum-verified transfers pinned to the configured peer URL, with zip contents validated before install.
* Packaging now works without the PHP `zip` extension, using the PclZip library WordPress bundles.

= 1.0.2 =
* Close initial testing gaps, bugs found and fixed.

= 1.0.1 =
* Fully functioning and good looking sync status.

= 1.0.0 =
* Initial plugin functionality.

= 0.1.0 =
* Plugin and theme comparison between two paired sites, with colour-coded differences, a differences-only filter, and manual refresh.
* Settings panel: enable/disable toggle, peer site URL, and a generated shared key.
* HMAC-SHA256 signed peer channel with timestamp, replay, and route binding checks.

== Upgrade Notice ==

= 1.4.2 =
Adds one-time passcodes, an activity log, comparison-only mode, multisite support, and
one-way pairing for a site that is not publicly reachable. Settings are grouped into cards
that save separately, and you choose how many backups to keep, which one to restore, and
how long log entries live.

== Test Analysis ==

| Suite                   | Metric             | v 1.4.2 | v 1.4.0  |
|-------------------------|--------------------|---------|----------|
| PHP (PHPUnit)           | Line coverage      | 89.26 % | 89.39 %  |
| PHP (PHPUnit)           | Tests              | 913     | 883      |
| JavaScript (Jest)       | Statement coverage | 98.21 % | 98.17 %  |
| JavaScript (Jest)       | Branch coverage    | 88.02 % | 87.82 %  |
| JavaScript (Jest)       | Tests              | 685     | 669      |
| End-to-end (Playwright) | Tests              | 82      | 81       |

Version 1.4.2 lints clean — zero errors, zero warnings, and zero suppressions — against
WordPress Coding Standards 3.4.0 on PHP_CodeSniffer 3.13.5, PHPCompatibilityWP 2.1.8
checked from PHP 7.4 upward, and @wordpress/eslint-plugin 22.22.0 on ESLint 8.57.1. Plugin
Check 2.1.0 reports no errors and no warnings against the shipped files, across every
check category.

