== Changelog ==

Full release history for MigrateNest. The most recent releases are also listed in readme.txt.

= 1.9.27 =
* Changed: documented, in the description, the one file a restore writes outside this plugin's folder - a temporary must-use plugin that stops WordPress loading half-written plugins mid-restore, removed as soon as the restore finishes.
* Removed: a dead script handler for a Pro-only setting that had no matching control or handler in this package.
* Fixed: Google Drive backups could fail with "Request had insufficient authentication scopes". One-click connect now asks Google only for access to files this plugin creates, and creates its own "MigrateNest Backups" folder to put them in. Sending backups to a folder that already exists is still supported by entering a folder ID and connecting with your own Google Cloud credentials.
* Changed: the S3-compatible endpoint presets (Google Cloud Storage, Backblaze B2, Wasabi, DigitalOcean Spaces) are supplied by the Pro add-on at runtime instead of being hardcoded in the shared admin script. Cloud destinations are a paid feature, so the free package no longer contains any third-party hostname at all.
* Removed: the Staging feature (one-click clone into a subfolder). Creating a staging site meant copying WordPress core and plugin code into a new directory in the site root, which the WordPress.org plugin guidelines do not permit. The Staging screen, its AJAX handlers and its stored options are gone. Existing staging folders created by an earlier version are left on disk untouched - delete them yourself once you no longer need them.

= 1.9.26 =
* Changed: Renamed the plugin to MigrateNest – Site Migration & Backup with the distinctive WordPress.org slug `migratenest`. The existing easywpmigration.com website and every service URL remain unchanged, as do archives, settings, storage paths, and internal compatibility identifiers.

= 1.9.25 =
* Changed: the two <style> blocks that styled this plugin's admin menu entry are now registered through wp_register_style() and wp_add_inline_style() instead of being echoed into admin_head, so WordPress owns the tag and its place in the dependency graph.
* Changed: the fatal-error reporter no longer touches PHP's memory_limit on a request that ended normally. It now confirms a fatal actually occurred before asking for the headroom it needs to report one.
* Changed: the three operations that stream a whole site (an import step, an export step, a staging build) ask for memory through wp_raise_memory_limit(), which only ever raises. The previous hard-coded 1024M could LOWER the limit on a host configured higher, and offered no way to override it; the new `ewpm_archive_memory_limit` filter does.
* Fixed: the uploads, plugins and themes directories are resolved with wp_upload_dir(), WP_PLUGIN_DIR and get_theme_root() rather than assembled from WP_CONTENT_DIR. All three are configurable and need not sit under wp-content, so the size estimates on the export screen were wrong on any site that had moved one.
* Fixed: the per-folder media picker on the export screen resolves the real uploads directory, and stands down cleanly on a site whose uploads have been moved outside wp-content instead of listing paths no archive could carry.
* Changed: removed the load_plugin_textdomain() call. WordPress has loaded plugin translations just in time since 4.6, and this package no longer bundles catalogues of its own.
* Changed: translation catalogues (.po/.mo/.json) are no longer bundled. Translations now come from translate.wordpress.org, which generates every locale and delivers it through the normal update system; the plugin language picker reads those and keeps working.
* Changed: the readme now documents every external service the product can reach — what is sent, when, and under which conditions — with terms and privacy links for each one.
* Changed: the listing shows only screenshots of screens this package actually contains.

= 1.9.24 =
* Changed: Prepared the public identity and translation domain for a distinctive WordPress.org submission while preserving existing archives, settings, storage paths, and internal compatibility identifiers.

= 1.9.23 =
* Fixed: Remote Pull and Sync failed on their first step with a bare HTTP 400. While a restore lock is held the import guard forces WordPress to load only this plugin, and since the free/Pro split the handlers those transfers call live in the Pro add-on — so the guard was unloading the half that owned the request. The guard now keeps the add-on loaded too.
* Fixed: a stale restore lock left the guard forcing the plugin active through a filter, so it could be neither activated nor deactivated from the Plugins screen until the lock file was deleted by hand.

= 1.9.22 =
* Fixed: a remote pull or restore that was rejected reported only its status code, so six identical "HTTP 400" lines gave no clue what to do. The message now names the cause — an unrecognised login session, an expired security token, or whatever the server actually said.

= 1.9.21 =
* Improved: The Free Sync tab now opens a polished feature preview that explains the workflow and links to Pro, while all operational Sync controls remain available only with the paid module.
* Improved: Added clear spacing between the stored-backups panel and the scheduled-backups Pro card.
* Fixed: on a network, restoring across different table prefixes fixed roles only for the site the restore ran on. Every other site keeps its own options table, so their `{prefix}{id}_user_roles` still named the old prefix and those sites' roles stayed broken. Every blog is now covered.
* Added: a regression test for the whole prefix scenario — statement routing, recovering the prefix from a dump that never recorded it, roles surviving the change, subsite capability keys keeping their blog id, and an ordinary same-prefix restore staying untouched.
* Fixed: on a Free-only installation, the dashboard Upgrade controls pointed to the Pro add-on's missing License screen and WordPress refused access. They now open the public pricing page; when the Pro add-on is installed, its local activation screen remains the destination.
* Improved: Free-only Pro feature cards now have a clear premium visual treatment instead of rendering as unstyled text inside the surrounding form.
* Fixed: the Import screen no longer tries to include the Pro verification report from the Free plugin directory when the Pro add-on is disabled.
* Fixed: the Free plugin's Sync tab and direct Sync URL no longer open an unregistered admin page. They now open a local feature preview with a clear pricing link, while an installed Pro add-on replaces it with the working Sync screen.

= 1.9.19 =
* Fixed: after restoring a backup from a site with a different table prefix, accounts came back with no role — which looks like a broken password. WordPress stores the prefix inside the data as well as in table names: usermeta rows keyed `{prefix}capabilities` and `{prefix}user_level`, and the `{prefix}user_roles` option. 1.9.17 moved the tables but left those keys naming the old prefix, so WordPress looked for a capability record that was not there. Those keys are now rewritten too, on both restore paths.

= 1.9.18 =
* Fixed: a full Sync push could leave the receiving site empty. The push uploads an archive and the DESTINATION restores it, so the destination needed the table-prefix fix from 1.9.17 as well — installing it only on the sending site changed nothing. Both restore paths are now prefix-aware, including the fallback used when a restore finishes in one pass.
* Note: for a full Sync push, update BOTH sites. The site doing the restoring is the one that needs this.

= 1.9.17 =
* Fixed: restoring a backup whose site used a different table prefix left the site empty. Operators change the prefix for security, and a backup captures every table in the database — so an archive can also carry a second, unrelated WordPress install. The restore put every table back under its original name, and when that was not this site's prefix, WordPress read the wrong half: the restore reported success and the site came up blank with no explanation. The archive's tables are now placed on this site's prefix, and any other install's tables in the same backup are left alone. The prefix comes from the backup itself, or is read out of the database dump for backups made before it was recorded.

= 1.9.16 =
* Added: on a network, a recovery point can now cover one site instead of only the whole network. The screen says what each choice covers before you make it — a single-site point carries that site’s content and uploads, while network-wide users, roles and the site list are shared by every site and are not rolled back.
* Added: an optional off-site copy of a recovery point. It is a copy, not a hand-off: the local point stays where it is, because rolling back should not depend on the destination being reachable at the moment you need it.

= 1.9.15 =
* Added: a scheduled backup can now cover several sites of a network, not only one site or the whole network. Each chosen site is saved as its own standalone backup, keeps its own retention count, and has its own incremental chain. A run that misses any chosen site is reported as a failure, naming the sites that failed.
* Fixed: retention could delete another scope's backups. A whole-network backup and a single-site backup both begin "scheduled-", so they were counted against the same "keep how many" and could rotate each other away — a site could be pruned to nothing while its keep count still looked satisfied.
* Fixed: incremental backups shared one chain across sites, so one site's incremental could be built on another site's full backup and could not be restored. Each site now keeps its own chain; existing single-scope chains are unaffected.

= 1.9.14 =
* Fixed: on a network, opening a plugin screen from a site URL instead of the Network Admin URL showed "Sorry, you are not allowed to access this page" and stopped there. The forward to Network Admin was attached to a hook that WordPress runs after it has already refused the request, so it never had a chance to act. It now runs at the point of refusal and takes you to the right screen, keeping the address exactly as it was. This is what an OAuth return from Google Drive was hitting.

= 1.9.13 =
* Added: a clear explanation when a plugin screen is opened on a network by someone who is not a Network Administrator. WordPress previously answered with a bare "you are not allowed to access this page", which named neither the plugin nor the reason.
* Changed: the minimum PHP version is now 7.2 (was 7.4).
* Changed: internal restructuring so paid features register themselves rather than being switched on inside the free code. No change to what any plan includes.

= 1.9.12 =
* Changed: Staging sites are now included in Free, with no license required.
* Changed: Sync is now included in every Pro plan, starting with Personal.
* Fixed: a scheduled recovery point could stop with a PHP error. Work that runs on a schedule was calling a WordPress function that only exists on admin screens; the same applied to two other places reached by scheduled runs and by site-to-site sync.
* Fixed: this plugin could interrupt its own restore. While the database was being replaced, its own background work wrote a row the restore was about to write too, and the restore stopped with a duplicate-entry error, leaving the site half-restored. Background work now stands down for the duration.
* Fixed: a backup could be reported as "Not restorable" when it was perfectly fine. The check test-restores into temporary tables, and on sites with long table names (WooCommerce in particular) those names exceeded what MySQL allows, so the test could not run. A name that still cannot fit is now reported as something the check could not verify, never as a broken backup.
* Fixed: an interrupted transfer left a near-empty file in the backups list that looked restorable. Restoring one applied the other site's settings without restoring any content. These are now labelled Incomplete, cannot be restored, and are cleared automatically.
* Fixed: a large transfer could end with "Network error during pull" after a single slow moment. Transfers now retry and resume where they left off, and wait far longer before giving up.
* Fixed: the verification result now explains itself. The badge opens a report showing every check, what passed, and what did not.
* Improved: the Backups list keeps its columns aligned, no longer scrolls sideways, and its row actions are icons with hover labels.

= 1.9.11 =
* Fixed: a restore of a large site could stop partway through with an empty HTTP 400 on every remaining step. While the database is being replaced there is a brief moment when the `active_plugins` row does not exist, and in that moment WordPress ignored the safeguard that keeps this plugin loaded — so the plugin driving the restore stopped loading and its request handler ceased to exist. Reported on a 410 MB pull.
* Fixed: a restore could finish with every plugin on the site switched off, because the option that records them was written in a way that silently did nothing when the row was missing.
* Fixed: on multisite, finishing a restore could network-deactivate every other network-activated plugin. Per-site plugin lists were not affected.
* Fixed: a restore step that ran for more than five minutes could make the safeguard remove itself mid-restore, with the same result. It now tolerates a much longer step and refreshes its heartbeat when a step ends as well as when it begins.
* Fixed: the safeguard now records the real storage path, so a site with a relocated storage folder is protected too.
* Added: feature packaging is controlled from the licence server, so which plan unlocks which feature can change without a plugin update.
* Added: optional free trials.

= 1.9.10 =
Fixes a restore that could stop part-way with an unexplained error, and several faults that quietly lost content when pushing between sites.

**Fixed — restores**
* A restore could stop part-way with an empty "server error", most often near the end. Replacing the database also replaces the login sessions stored in it, so the person running the restore was signed out by their own restore and the remaining steps were rejected — the work was fine, but nobody could finish it. A restore now carries its own authority through to the end. Archives created before this version still hit the old behaviour, because the code that finishes a restore comes from the archive; re-export once on this version and it is gone for good.
* A failed step no longer ends the restore silently. Steps are resumable, so one is retried before giving up, and if it does give up it says what happened and that starting again resumes from where it stopped.
* Verification no longer asks the site for its own homepage with a long wait while the restore request is still open, which could stall or be cut off on servers with few PHP workers.

**Fixed — sync**
* Featured images lost their alt text and caption when pushed. Re-pushing repairs images sent by earlier versions.
* Categories and tags were dropped when the destination did not have the plugin that owns them — a WooCommerce product pushed to a site without WooCommerce lost its categories permanently. They are now kept and appear as soon as that plugin is active.

= 1.9.9 =
Promotes Remote Pull from a generic advanced row into a prominent premium feature card with clear no-download benefit copy, an entitlement-aware Freelancer or Included badge, responsive layout, and a stronger locked-state treatment.

= 1.9.8 =
Adds compact, right-aligned plan badges to locked Sync and Staging navigation items. Free sites see Pro, Personal sites see Freelancer, and eligible Freelancer or Agency sites see clean navigation without badges.

= 1.9.7 =
Adds a purpose-built monochrome Route Loop icon to the compact WordPress admin sidebar, replacing the generic migration glyph while preserving native hover, inactive, and selected-state behaviour.

= 1.9.6 =
Refines the MigrateNest identity with the selected Route Loop symbol: a cleaner open migration path and forward arrow that remains recognizable at small sizes. Updates the plugin header, wordmarks, favicons, and WordPress.org artwork while retaining WordPress's native sidebar glyph.

= 1.9.5 =
Introduces a cleaner, scalable brand system with a compact site-to-site transfer symbol, horizontal and monochrome logo variants, favicons, and coordinated WordPress.org icons and banners. The plugin header now uses the crisp SVG symbol while the compact WordPress sidebar retains its native migration glyph.

= 1.9.4 =
Restores WordPress's native migration glyph in the compact admin sidebar, while retaining the full brand mark in the spacious plugin header and WordPress.org artwork.

= 1.9.3 =
Adds a new MigrateNest brand mark to the WordPress admin menu and plugin header, with optimized WordPress.org directory icons and a safe icon-font fallback.

= 1.9.2 =
Improves the migration experience with an action-first dashboard, clearer Migrate out/Migrate in navigation, a three-step migration journey, safer restore confirmations, transfer speed and time estimates, and a post-restore verification checklist.

= 1.9.1 =
New backups use the `.ewpm` extension, avoiding ambiguity with other migration products. Backups created by MigrateNest and its former WP Site Porter name with the legacy `.wpress` extension remain supported.

= 1.9.0 =
Adds per-site pushes on multisite, three translations, and fixes several faults that could interrupt a restore or leave a site broken afterwards. (Supersedes 1.8.4, which was never released.)

**New**
* Sync: "push the whole site" now honours the site you pick on each end. Choosing a site on a network packs just that site and replaces only the matching site on the destination — the rest of both networks is left alone. Leaving both pickers on the main site still pushes the entire install.
* Translations: Spanish, Spanish (Peru) and German, covering the admin screens. Pick one under Settings; it applies to this plugin only.
* Restoring a database now reports its progress and resumes in slices, so a large database no longer looks frozen near the end and can no longer run out of PHP execution time.

**Fixed — restores**
* Restoring a large site could stop at 99% with an empty error. Updating addresses across every table ran as one uninterruptible request, long enough for a strict host to kill it while the restore itself had actually finished. It now runs in resumable slices and reports which table it is on.
* Verification asked the site for its own homepage with a fifteen-second wait, from inside the request finishing the restore. On a host with few PHP workers that request cannot be answered until the current one ends, so it stalled or was killed. It now waits five seconds, treats being unable to reach itself as a warning rather than a failed migration, and the whole verification stage is time-bounded.
* A restore could stop the plugin running it from loading. Archives frequently do not list MigrateNest among their active plugins, and the protection that keeps only this plugin loading during a restore read that list and concluded it was not needed. It now loads unconditionally while a restore is in progress, and an archived copy of that protection is never restored or exported.

* A restore could kill itself. wp-content is replaced file by file while the import keeps making requests, so a plugin caught half-written was loaded and fatalled, the import stopped with "Server error during import (HTTP 500)", and the site was left down. Only MigrateNest is loaded now while a restore runs.
* A restore could switch MigrateNest off. A site extracted from a network ships without it, so restoring that archive removed the plugin performing the restore and its screens answered "Sorry, you are not allowed to access this page". The plugin now stays active.
* Restoring onto a network no longer wipes that install's own connection key, licence, saved connections or cloud tokens — those are network options and were not being preserved.
* Restoring an archive that cannot be restored here (single site onto a network, or a sub-domain/sub-directory mismatch) is refused when you click, instead of failing partway through. Pulling one site of a remote network is likewise refused before the transfer, not after it.

**Fixed — multisite**
* Adding a site to a network failed on PHP 8 at the first folder in the archive.
* Adding a site to a network rewrote the archive's old address inside every other site on that network. Only the new site is touched now.
* A subsite carrying a stray, unprefixed `wp_user_roles` row could not be exported and re-imported (duplicate key). (Reported and fixed by a contributor testing on a live network.)
* Choosing a site that no longer exists on either end is refused, rather than quietly falling back to the network's main site.
* Pulling a whole network onto a single site is now refused before the transfer starts instead of after it. The restore always blocked this — a network archive carries the network's tables but not the multisite constants, rewrite rules or per-site addresses it needs — but the check only ran once the manifest was read, which is after the entire archive had been downloaded. The refusal now explains the two ways forward: pull one site of the network as a standalone site, or make the destination a network of the same type first.
* A standalone site can now sync into one site of a destination network. The "push into" picker only appeared when the site you were pushing *from* was itself a network, so there was no way to name a site on the far end; supplying it by hand was then refused with a message about networks that did not apply. Picking a site on the destination now works from any install, and the rest of that network is left alone.
* Choosing which site to sync is now its own step on the Sync page, between connecting a destination and choosing what to push. It appears only when one of the two ends is a network, and the step after it is numbered accordingly.
* The chosen site is remembered against the connection, so it survives reloading the page and reconnecting. Previously the picker reset to the destination network's first site each time, which could aim a repeat push somewhere you did not intend.
* Scheduled backups are a network-wide setting again, visible from every site.
* Backups keep the label saying which site they came from.

**Fixed — transfers**
* Uploading a backup failed with HTTP 500 on the second chunk: the chunk size arrived in the browser as text rather than a number, so the uploader asked for the whole remaining file at once. (Diagnosed by a contributor testing on a live network.)
* A UTF-8 byte-order mark emitted by the far site broke the handshake and corrupted transferred bytes; it is now stripped. (Fixed by a contributor testing on a live network.)
* Sites that block `/wp-json/` but allow `?rest_route=` are now detected and used automatically, instead of looking like the plugin is not installed there. (Fixed by a contributor testing on a live network.)
* "Push Everything (Full Site)" failed with "Invalid request signature" at 99%, after the transfer and the restore had both finished. Restoring the destination's database briefly replaced its connection key with the incoming site's, so the last few requests were checked against the wrong key. The destination now keeps using the key it advertised for the length of the transfer.

**Fixed — content sync**
* Featured images lost their alt text and caption when content was pushed. The destination looked for both, but the source never sent them, so every pushed image arrived with the text screen readers announce — and search engines read — silently missing. Re-pushing now also repairs images sent by earlier versions.
* Categories and tags were dropped when the destination did not have the plugin that owns them. Pushing a WooCommerce product to a site without WooCommerce stored the product but discarded its product categories, and they stayed missing after WooCommerce was installed. The terms are now preserved and appear as soon as that plugin is active.

* Pushing a post that uses ACF or Secure Custom Fields now takes the field group with it. Previously only the values travelled, so unless you happened to tick the field group as well, the fields did not appear on the destination at all — the data was there but invisible, which looked like the push had done nothing. The groups a selection depends on are sent first, and the Sync page ticks and locks them so they cannot be left behind.
* A group counts as required both when a selected post stores one of its values and when its location rules attach it to that post's type, so a post with nothing filled in yet still brings its fields along.

**Fixed — licensing**
* Plan limits are enforced: Personal covers scheduling, cloud storage, verification and recovery points; Freelancer adds Sync, Remote Pull, Staging and WP-CLI; Agency adds the multisite tools. Upgrade prompts name the plan a feature needs and its price.
* The in-plugin language picker had no effect, and listed raw locale codes when the site had no outbound network access.

**Fixed — interface**
* Three progress and status messages printed their own placeholders instead of the values, e.g. the Sync page showing "Connected: %1 · %2" in place of the site name and address. The upload and download progress labels were affected the same way.
* The direction row on the Sync page no longer leaves an empty bar behind when there is nothing to show.

= 1.8.3 =
* Cloud connect (Google Drive, Dropbox, OneDrive, Box, pCloud) now opens in a popup window instead of redirecting the whole Settings page.

= 1.8.2 =
* Fix Google Drive (and other OAuth cloud) connect: after auth, Settings now reopens Scheduled Backups on the connected destination panel instead of hiding it.

= 1.8.1 =
* Fix import/restore sessions being lost when AJAX lowercased mixed-case tokens; harden session state writes.

= 1.8.0 =
* Rebrand - the product adopted its previous public name, the `ewpm` prefix, and the https://easywpmigration.com hub. Existing `.wpress` backups from either earlier product identity remain restorable. Settings and backup storage migrate automatically from prior WP Site Porter installs.

= 1.7.0 =
* NEW - Sync now works with WooCommerce, ACF/SCF, Contact Form 7, WPForms, Gravity Forms, Ninja Forms and Formidable (Pro). The Sync scan detects what changed and lets you push it to another site.
* NEW - WooCommerce: push products, product variations, coupons, product images and galleries, and global product attributes. Variations stay attached to their product and their images are recreated on the destination.
* NEW - Plugin data to push: form definitions that live in custom database tables (Gravity Forms, Ninja Forms, Formidable) and WooCommerce attributes are detected by content hash and pushed on demand.
* SAFETY - Definitions and configuration only: form entries/submissions, WooCommerce orders and customer accounts are never read or written, so a push can never overwrite the data your live site has collected.
* FIX - Pushed content now keeps parent/child relationships (WooCommerce variations, hierarchical pages) instead of flattening them to top level.

= 1.6.25 =
* NEW - Backup Assurance (Pro): every scheduled backup is automatically test-restored into a throwaway scratch database to prove it can actually be restored, then the scratch tables are dropped. A "Verified restorable" badge appears on each backup, and you can verify any backup on demand.
* NEW - Backup failure alerts (Pro): get an email (plus optional Slack / Discord / custom webhook) the moment a backup fails or a verification finds a problem, with a backup-health dashboard widget and a watchdog that warns if a scheduled backup silently stops running.
* NEW - Recovery points (Pro): snapshot your site in one click before a risky change and roll back to it just as easily; recovery points are never removed by retention.
* NEW - Incremental backups (Pro): scheduled backups can archive only the files that changed since the last run, with a periodic full to cap the chain, far smaller and faster, so hourly/daily backups stop straining the server. Restoring an incremental automatically rebuilds the full site from its chain.
* NEW - Staging sites (Pro): clone your site into a private, noindexed subfolder in one click to test updates and changes safely, then remove it when you are done.
* Positioning and pricing: Personal $79 / 2 sites, Freelancer $149 / 10 sites, Agency $299 / 50 sites
* Hub / Plugin URI aligned to easywpmigration.com
* Stronger Pro CTAs after free export and import success
* Security: removed a development-only override that allowed OAuth redirects on any host; production redirect-host rules are enforced again
* Privacy: the optional usage heartbeat (Pro, opt-in) no longer transmits your license key (a one-way hash is sent instead)

= 1.6.5 =
* New backup destinations: Dropbox, OneDrive, Box, pCloud, Azure Blob Storage, WebDAV (Nextcloud, ownCloud)
* S3 destination now offers one-click presets for DigitalOcean Spaces, Backblaze B2, Google Cloud Storage and Wasabi
* Cloud drives connect with your own app credentials (no shared broker)

= 1.5.1 =
* Stronger Pro upsells and plan comparison in Settings
* Optional usage heartbeat for install analytics, off unless an administrator turns it on
* Export success CTA and Backups schedule nudge

= 1.5.0 =
* Freemium: Pro licensing via easywpmigration.com (activate/check/deactivate)
* Pro gates: scheduled backups, cloud destinations, Sync, Remote Pull, WP-CLI
* In-plugin upsells and Pro auto-update checks
* OAuth broker and Plugin URI on easywpmigration.com
* External services disclosed for WordPress.org

= 1.4.1 =
* Rebranded to MigrateNest
* New: Connect with Google - direct Google Drive OAuth authentication

= 1.4.0 =
* Scheduled backups, remote destinations, sync history improvements

= 1.3.0 =
* Sync page - full and selective push

= 1.2.0 =
* Pull from another site

= 1.1.0 =
* Redesigned admin interface

= 1.0.0 =
* Initial release
