=== BackReload – Backup, Restore & Migration ===
Contributors: shiponkarmakar
Tags: backup, restore, migration, ftp, s3
Requires at least: 6.0
Tested up to: 7.0
Requires PHP: 7.4
Stable tag: 1.0.14
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

WordPress backup, restore, and migration. FTP, SFTP, AWS S3, and Wasabi storage. Encrypted, schedulable, and shared-hosting friendly.

== Description ==

BackReload is a backup, restore, and migration plugin for WordPress. It backs up your database, uploads, plugins, themes, and the rest of `wp-content` into split zip archives, encrypts them at rest, and uploads them to one or more remote destinations of your choice. When something goes wrong, you can restore your full site — database, media uploads, plugins, themes, and other content — back onto the same site, or onto a different site with automatic URL and path rewriting.

Direct site-to-site **migration** is full-fidelity: it moves the complete site — database, uploads, other content, and plugins, themes, and mu-plugins — so the destination works immediately after the move. Restore and import are full-fidelity too — they bring back plugins, themes, and mu-plugins along with the database and uploads. A fully-compliant, data-only build (no code ever deployed) is available by defining the BACKRELOAD_DATA_ONLY constant.

= Storage destinations =

* **Local Storage**: files written to `wp-content/uploads/backreload/` inside the site itself.
* **FTP**: any standard FTP server.
* **SFTP**: over SSH, using the PHP ssh2 extension when available.
* **AWS S3**: buckets in any AWS region, with full SigV4 signing.
* **Wasabi**: Wasabi-compatible signing built in.
* **S3-compatible**: works with any provider that speaks the S3 API (Backblaze B2, DigitalOcean Spaces, MinIO, and so on).

You can attach multiple destinations to a single backup. Each archive part is uploaded to every destination in turn, with automatic retries on transient failures.

= Restore and migration =

The Restore wizard lets you pick which content to bring back — database, uploads (media), plugins, themes, mu-plugins, and other `wp-content` files. If the backup came from a different site, BackReload rewrites URLs and absolute paths inside the database as part of the restore, including inside serialized values, so the site comes up correctly on the new domain without any manual search-and-replace. 

Maintenance mode can be automatically engaged for the duration of a restore, then turned back off when the work finishes.

= Site-to-site migration via key =

For moving a site to a new host or domain, BackReload ships a direct site-to-site push. The destination site generates a one-time key URL under **Migrate → Receive**. You paste that URL on the source site under **Migrate → Send**, click **Start migration**, and the source builds a backup and streams it to the destination in encrypted chunks over HTTPS. With auto-restore enabled (default), the destination unpacks and applies the backup automatically. One-click migration.

There's no FTP step, no manual download, no shared cloud storage required. Chunked transport with WAF / mod_security tolerance, cross-tick retry caps, table-prefix fixup, and a live progress UI with phase stepper, throughput, and log feed on the source side.

= Encryption =

Archives can be encrypted at rest with AES-256-GCM using a passphrase you set in Settings. The passphrase is required to restore. The encryption layer is fail-closed: a corrupted or tampered archive refuses to extract rather than producing partial output.

= Scheduling =

Multiple independent schedules are supported. Common patterns: a daily database-only backup, a weekly full backup, and a monthly archive backup, each retaining a different number of historical copies.

= Notifications =

Send backup-completion and failure alerts to email, Slack, Discord, Telegram, Microsoft Teams, Google Chat, Pushover, or any custom webhook. Each channel is opted into success and failure events independently.

= Privacy =

The plugin does NOT send analytics, telemetry, or usage data to any external server. The only outbound network traffic is to the storage destinations you configure and the notification channels you opt into. OAuth flows for Google Drive and OneDrive run only when you explicitly start them.

== External services ==

BackReload does not phone home and has no servers of its own. It only contacts an external service when **you** configure that service as a backup destination or notification channel, or when you start a site-to-site migration. Nothing below is contacted out of the box. For each service, the data sent is your backup/restore content and the API requests needed to store or retrieve it, sent only while the relevant backup, restore, import, or migration is running.

**Google Drive (Google Drive API)** — Used only if you add a Google Drive destination. You provide your own Google OAuth application (Client ID/Secret). BackReload exchanges the OAuth authorization code for access tokens and then uploads, lists, and downloads your backup files in your Drive. Endpoints: accounts.google.com, oauth2.googleapis.com, www.googleapis.com. Terms: https://policies.google.com/terms · Privacy: https://policies.google.com/privacy

**Microsoft OneDrive (Microsoft Graph API)** — Used only if you add a OneDrive destination. You provide your own Microsoft (Azure AD) application. BackReload exchanges the OAuth code for tokens and uploads/lists/downloads your backup files. Endpoints: login.microsoftonline.com, graph.microsoft.com. Terms: https://www.microsoft.com/servicesagreement/ · Privacy: https://privacy.microsoft.com/privacystatement

**Amazon S3 (AWS)** — Used only if you add an Amazon S3 destination, with credentials you provide. BackReload uploads/downloads/lists backup archives in your bucket via the S3 REST API at `s3.<region>.amazonaws.com`. Terms: https://aws.amazon.com/service-terms/ · Privacy: https://aws.amazon.com/privacy/

**Wasabi** — Used only if you add a Wasabi destination, with credentials you provide. BackReload uploads/downloads backup archives to your bucket via the Wasabi S3 REST API at the regional endpoints `s3.wasabisys.com`, `s3.us-east-2.wasabisys.com`, `s3.us-central-1.wasabisys.com`, `s3.us-west-1.wasabisys.com`, `s3.eu-central-1.wasabisys.com`, `s3.eu-west-1.wasabisys.com`, and `s3.ap-northeast-1.wasabisys.com`. Terms: https://wasabi.com/legal/terms-of-use · Privacy: https://wasabi.com/legal/privacy-policy

**Other S3-compatible storage** — If you add an "S3-compatible" destination (e.g. Backblaze B2, DigitalOcean Spaces, MinIO, or another provider), BackReload connects only to the endpoint and bucket you enter, using credentials you provide, to upload/download your backup archives. The applicable terms and privacy policy are those of the provider you choose.

**FTP / SFTP servers** — If you add an FTP or SFTP destination, BackReload connects only to the host you specify, with credentials you provide, to upload/download your backup archives. The applicable terms are those of that server's operator.

**Site-to-site migration** — Used only when you start a migration. BackReload connects to the other WordPress site you target (the one whose one-time key URL you paste) and streams the backup directly between the two sites over HTTPS. No third-party or BackReload server is involved.

**Notification channels** — If you enable a notification channel, BackReload sends a short backup-status message (success/failure, site name, basic stats) to the endpoint you configure, only when a backup completes or fails. Channels and their providers: Slack (https://slack.com/terms-of-service · https://slack.com/trust/privacy/privacy-policy), Discord (https://discord.com/terms · https://discord.com/privacy), Telegram (https://telegram.org/tos · https://telegram.org/privacy), Microsoft Teams (https://www.microsoft.com/servicesagreement/ · https://privacy.microsoft.com/privacystatement), Google Chat (https://policies.google.com/terms · https://policies.google.com/privacy), Pushover (https://pushover.net/terms · https://pushover.net/privacy), and any custom webhook URL you provide (terms/privacy of that endpoint's operator). Email notifications are sent through your own WordPress site's mail, not a third party.

== Source code and build process ==

The admin interface is a React + TypeScript application compiled with Vite, and the minified production bundle ships in `assets/dist/`. To keep the plugin fully reviewable, the complete, human-readable source is included in the published package and can also be rebuilt from scratch:

* The unminified front-end source lives in the `src/` directory (included in this plugin).
* Build/tooling config: `package.json`, `vite.config.ts`, `tsconfig.json`, `tailwind.config.js`, `postcss.config.js`.
* PHP library dependencies are declared in `composer.json`.

To rebuild the compiled JavaScript/CSS in `assets/dist/` from source:

1. `npm install`
2. `npm run build`

To install the bundled PHP libraries (phpseclib, used for SFTP):

1. `composer install`

== Installation ==

1. Upload the plugin folder to `/wp-content/plugins/backreload/` or install from the WordPress Plugins screen.
2. Activate **BackReload** in **Plugins → Installed Plugins**.
3. Open the plugin from the WordPress admin sidebar.
4. Visit **Destinations** to attach at least one storage location (Local Storage is created by default).
5. Optional: open **Settings** to enable encryption, configure exclusions, and set the compression level.
6. Optional: open **Schedule** to set up daily, weekly, or monthly automated backups.
7. From the **Dashboard** or **Backups** page, click **Backup now** to create your first backup.

== Frequently Asked Questions ==

= Does this work on shared hosting? =

Yes. The backup engine is tick-based: it does a small amount of work on each cron tick rather than holding a long-running request. This keeps memory and time per request well within shared hosting limits.

= How large can my site be? =

The engine streams files into zip parts and uploads each part as it finishes, so disk and memory use stay bounded regardless of total site size. The largest single zip part is auto-tuned to roughly 80% of your PHP `memory_limit` (capped between 200 MB and 1 GB).

= Can I restore a backup to a different domain? =

Yes. The Restore wizard detects that the source domain doesn't match the destination domain and runs an automatic URL/path rewrite during restore. Serialized values are walked safely.

= My backups are encrypted. What happens if I lose the passphrase? =

You will not be able to restore them. There is no recovery mechanism. That's the entire point of encryption. Store your passphrase in a password manager.

= Can I import a backup from another WordPress site? =

Yes, three ways:
1. Upload the backup parts via **Backups → Import**.
2. Drop the part files into `wp-content/uploads/backreload/backreload-import/` on the server and use **Server folder** import.
3. Configure the same storage destination on both sites and use **Import → From destination** to bring it across (no upload required).

= Does the plugin send data to a third party? =

No. The plugin only contacts storage destinations and notification channels that you explicitly configure. There is no telemetry.

= Does restore bring back my plugins and themes? =

No. BackReload restores your database, uploads (media), and other non-code `wp-content` files, but it never deploys plugin, theme, or mu-plugin code into your live site. Full backups still contain those files for off-site safekeeping, but after a restore you reinstall plugins and themes through the normal WordPress installer. This keeps the plugin from ever writing executable code into your site.

= What happens when I uninstall the plugin? =

Uninstalling deletes all plugin settings, custom database tables, scheduled cron events, and transients. The backup archive files inside `wp-content/uploads/backreload/` are NOT deleted. Those are your data and survive the uninstall in case you want to reinstall later. Delete that folder manually if you want a fully clean removal.

== Screenshots ==

1. Dashboard — backup health, storage used, success rate, next scheduled run, and a live backup-progress panel.
2. Backups list — every backup with its type, size, duration, destination, and status, plus filter, sync, and import controls.
3. Import a backup — bring in a backup by uploading its parts, scanning a server folder, or pulling it from a connected destination.
4. Restore wizard, step 1 — choose which backup to restore.
5. Restore wizard, step 2 — pick the content to bring back: database, uploads (media), and other non-code files.
6. Restore wizard, confirm — review the restore and choose Replace (roll back exactly) or Merge (keep newer files).
7. Site-to-site migration — the destination site generates a one-time key URL to receive a push.
8. Site-to-site migration — the source site pastes the key and streams the backup directly to the destination.
9. Schedule — combine independent hourly/daily/weekly/monthly schedules, each with its own backup type, retention, and destinations.
10. Storage destinations — Local, FTP, SFTP, Google Drive, OneDrive, AWS S3, Wasabi, and S3-compatible providers.
11. Activity log — searchable, downloadable per-backup log of every step.
12. Notifications — email, Slack, Discord, Telegram, Microsoft Teams, Google Chat, Pushover, or a custom webhook.
13. System health check — PHP, WordPress, and the extensions the plugin needs (ZipArchive, OpenSSL, cURL).
14. Maintenance mode — a branded "be right back" page that can auto-enable during a restore.

== Changelog ==

= 1.0.14 =
* The Dashboard now shows a system health score next to the backup success rate, so problems that would stop a backup are visible without opening the System page. It turns amber for warnings and red when a check has failed outright, and clicking it opens the full System report.
* The migration confirmation screen now shows a clear "Migrating from -> to" summary of the source and destination sites before you start, so the direction of the transfer is unmistakable before anything is overwritten.
* Hardened restore against crafted archives: entries with absolute or parent-directory paths are now refused outright and logged, rather than relying on PHP to defuse them. No archive produced by this plugin contains such paths; this protects against a tampered or third-party archive supplied through Import.
* Fixed files being silently left out of a backup on large sites. When a very large folder was being archived across several passes and the host killed the process mid-part, the plugin's record of what it had already archived included files whose archive part was never finished. On the next pass those files were skipped, so they were missing from the finished backup while it still reported success - only discovered when restoring. The record is now trusted only as far as the parts that genuinely completed, and anything beyond that is archived again.
* Fixed the same class of problem when a partly-built component has to be rebuilt: leftover progress notes from the discarded attempt are now cleared, so the rebuild archives everything rather than skipping files whose parts were just deleted.
* Stopping a backup is no longer delayed. A backup being cancelled could be re-queued once by the stall detector before it was finalized, leaving the UI on "Stopping..." longer than necessary.
* A backup that pauses at a time checkpoint is no longer reported in the log as though the host had killed it.
* Fixed a restored site keeping the original site's web address when the backup was picked up from a shared storage destination. Connecting a second WordPress site to the same FTP, SFTP, or cloud destination and restoring a backup found there worked, but if that backup folder had no manifest file - an older backup, or one whose manifest failed to upload - the plugin had no record of which site the backup came from and skipped the URL rewrite entirely. The restored site then kept the original site's address and redirected back to it. The address is now read from the database dump itself when it isn't otherwise known, so the rewrite happens either way.

= 1.0.13 =
* Fixed backups sticking at 5-10% and never moving. The first and largest component (uploads) was archived in a single uninterruptible operation, and its progress was only recorded once that operation finished. On a site whose uploads cannot be packed inside one request, the server would end the request partway, nothing had been recorded, and the next attempt started the same component again from zero - so the backup could never get past its first step no matter how many times it retried. Archiving now stops cleanly when it runs out of time, keeps the parts it has finished, and continues from that point on the next pass. Each pass is guaranteed to archive at least one file, so progress is always made.
* Fixed a healthy backup being marked failed while it was still working. The plugin treats an internal "a backup is running here" marker as proof that work is in progress, and gives up on the backup if that marker goes more than five minutes without being refreshed. But the database dump and the archive build refresh nothing while they run, and on a large site either can take longer than five minutes on its own - a dump of a big postmeta table can run for six or seven. The backup therefore aged out its own marker and was declared dead mid-dump, throwing away every archive already built. Both steps now refresh the marker as they work, so a long dump or a large archive is no longer mistaken for a crash. Genuinely stalled backups are still detected exactly as before.
* Fixed backups stalling on large sites. A backup that needs more than one or two passes would stop partway with "No engine activity for 15+ minutes", usually after the archives were built but before anything was uploaded. The plugin schedules a follow-up pass whenever it pauses at a time checkpoint, but that follow-up was scheduled two seconds into the future while the request meant to run it fired immediately - so it always found nothing to do. The same request was also being rejected by WordPress's own cron lock, because it was sent from inside a cron run that still held it. Both are fixed: the follow-up is scheduled as due immediately and sent once the lock has been released, so passes now chain straight through. Small sites finished in a single pass and were never affected, which is why this only showed up on large ones.
* A backup that has paused cleanly is no longer given up on. If a follow-up pass goes missing (a host that blocks internal requests, or one where WP-Cron only runs on its own schedule), the plugin now re-queues it once instead of immediately marking the backup failed and discarding completed work - which on a large site could be an hour of archiving.
* Added diagnostics for large sites. When a backup or restore stopped on a big site it previously left no trace - the log simply ended - because the host had killed the process outright. The plugin now records which step was running before each heavy operation, so if the process is killed the NEXT attempt reports exactly which step died and how long it ran. Fatal errors such as "Allowed memory size exhausted" are now captured too, along with the host limits and database size needed to interpret them.
* Backups now warn up front when encryption cannot succeed at this site size. AES-GCM encryption needs a whole archive part in memory and refuses parts above a third of the PHP memory limit, which on a large media-heavy site is smaller than the parts the plugin produces. Rather than failing partway through, the log now says so before the work starts and suggests the workaround (raise memory_limit, lower the chunk threshold, or turn encryption off) until streaming encryption ships.
* No change to how backups or restores are performed in this release - the new code only observes and reports.

= 1.0.12 =
* Fixed: scheduled backups ran on UTC instead of the site timezone set in Settings > General. On a site set to Asia/Dhaka (UTC+6), a schedule saved for 3:00 PM actually ran at 9:00 PM. The time you pick is now read as your site's local time, and existing schedules are corrected automatically on update - no need to re-save them.
* Fixed: dates throughout the admin screens (Next 5 runs, backup lists, dashboard) rendered in the browser's timezone rather than the site's, so they disagreed with the site clock for anyone working from a different zone. All dates now display in site time.
* The Time of day field now names your actual site timezone instead of claiming UTC.
* Improved reliability when restoring very large uploads/other archives. The previous build handed the archive an explicit list of every file it contained, which is slower and much heavier on memory on sites with tens of thousands of files, and could make a restore fail. Archives are now extracted wholesale unless something genuinely has to be left out, and those exclusions are applied in batches.
* Backups are now portable between servers with different MySQL settings, and values stored in binary-capable columns are migrated too (genuine binary data such as images is left untouched). Non-finite numbers no longer produce a dump that fails to import.
* The percent-sign repair now also fixes settings stored in WordPress's serialized format (theme options, widgets, and plugin option blocks). Those were the worst affected — the damage left them unreadable to WordPress entirely — and they are now restored along with everything else.
* Restores now repair percent signs damaged by a backup taken with an older version. Those backups replaced every `%` with an internal WordPress token, breaking permalinks, CSS widths and keyframes, and encoded URLs. Restoring such a backup now detects the token and converts it back, including inside serialized data, so an already-affected backup still produces a correct site.
* Fixed percent signs being destroyed in backups. Every `%` in the database was written into the backup file as a long internal WordPress token, so restoring produced corrupted data: permalink structures (`/%postname%/`), CSS percentages and keyframes, and encoded URLs were all replaced by that token — visibly breaking page layouts, menus and links on the restored site. Values are now escaped correctly and percent signs survive a backup/restore intact. IMPORTANT: backups taken with an earlier version already contain this corruption; take a fresh backup after updating.
* A migrated site now always comes up on the address you sent it to. If the destination had previously been cloned or restored from another site, its stored address could still name that other site — and the migration would keep it, leaving the site reachable at one domain while WordPress believed it lived at another. The address the backup was actually pushed to now takes precedence.
* The site can no longer be left stuck in maintenance mode. If a restore stopped before it finished (a server timeout, memory limit, or an error in the restored site), maintenance mode stayed switched on and every visitor kept seeing the "be right back" page. Restore-owned maintenance mode is now released automatically once no restore is running, and is also cleared if the restore hits a fatal error. Maintenance mode you switch on yourself is never touched.
* The address bar and the WordPress admin menu now follow the page you're on inside BackReload. Previously the URL stayed on whichever page you first opened, so WordPress highlighted the wrong menu item and refreshing sent you back to the Dashboard.
* Fixed backups disappearing from the list after a restore. Restoring a database also replays the original site's storage settings, so the plugin started looking for backup files at the ORIGINAL server's path. Finding nothing there, its housekeeping concluded the backups were gone and removed the records — even though the files were still safely on disk. Local Storage locations are now corrected immediately after the database is restored, before any housekeeping runs.
* Fixed the design changing slightly after a restore or migration on Elementor sites (borders, spacing and other styling shifting). The previous build deleted Elementor's compiled CSS so it would rebuild for the new domain, but the rebuild is not identical to what the source produced. The compiled CSS is now kept exactly as generated and only the old site's URLs inside it are rewritten, so the restored site renders identically to the original.
* Fixed "Refusing to clean-wipe a directory outside wp-content / uploads" on Windows (Local by Flywheel, WAMP, XAMPP). Windows paths use backslashes, and the safety check compared them with forward slashes, so a clean restore rejected the site's own wp-content directories. Paths are now normalized before the containment test, on both platforms. The same normalization was applied to the exemption check that protects the plugin's own folder and local backups from being wiped.
* Fixed site-to-site auto-migration failing at the final step on a destination that had itself been migrated before. The destination's Local Storage still held the previous source server's absolute path, so landing the incoming backup failed ("Local Storage path is unusable"). The storage location is now resolved at runtime with the same self-healing fallback used for import.
* Fixed "Storage path is not writable" on import/restore after a migration or host move. The Local Storage destination stored an absolute path, and the migrated database carried the OLD server's path (e.g. a Cloudways /home/<account>/... path) to the new host where it doesn't exist. The plugin now resolves the storage directory at runtime and falls back to this site's real uploads folder (via wp_get_upload_dir), healing the stored path automatically.
* The BackReload admin menu now includes the System page (it was reachable inside the app but missing from the WordPress sidebar submenu).
* Restore and Import now bring back plugins, themes, and mu-plugins too (previously data-only). Uploading or importing a full backup restores the complete site. A data-only build that never deploys code remains available via the BACKRELOAD_DATA_ONLY constant.
* Import now skips plugin/theme parts gracefully instead of failing. Selecting every downloaded part (including plugins-N.zip / themes-N.zip) no longer errors the whole import — the code parts are flagged "not imported" and only the data parts (database, uploads, other content) are brought in. Reinstall plugins/themes through WordPress, or use a site-to-site migration to move them.
* After migrating an Elementor site, stale compiled CSS (built with the old domain) is now cleared so styling — including the header — rebuilds correctly on the destination. This runs only AFTER the dynamic-tag repair from 1.0.6, so the CSS rebuild can't re-trigger the Elementor crash.

= 1.0.6 =
* Site-to-site migration now moves the complete site — plugins, themes, and mu-plugins included — so the destination works immediately after the move (previously the database came across but the plugin/theme files did not). Restoring a backup and importing an archive remain data-only.
* Auto-restore on the destination now runs reliably in the background on sites where WP-Cron is disabled, instead of blocking (and sometimes timing out) the migration's finalize request.
* Added a live restore-progress panel on the destination's Migrate → Receive page, so you can watch an incoming migration restore (download → files → database → URL rewrite) as it happens.
* Fixed a false "migration failed" error where the source's stale-record watchdog marked a long, still-progressing migration as failed.
* Hardened the admin app against optimization/cache plugins that append an HTML comment (e.g. a cache signature) to REST API responses. That trailing text broke JSON parsing and could leave the BackReload screens blank; responses are now salvaged so the UI loads regardless.
* Fixed a critical error ("There has been a critical error on this website") that could appear on the destination after a migration. WordPress drop-ins (object-cache.php, advanced-cache.php, db.php and similar) are tied to the origin server's environment and are no longer carried across — they were loading before the rest of WordPress on a host that couldn't support them and taking down the whole site. Backups skip them and restore ignores any a previous version included; each drop-in's plugin recreates it on activation.
* Migrated sites now always come up on the destination's own domain. Previously the site address could keep the source domain if it was stored in a slightly different form (www vs non-www, a trailing slash, or the opposite scheme) than the search-replace expected; the destination's real siteurl/home are now pinned authoritatively after the URL rewrite.
* Migration is now resilient to caching/optimization plugins on the OTHER site that append an HTML comment to REST responses — the source-to-destination probe, handshake, and finalize calls no longer fail with a confusing "HTTP 200" error. When a destination's REST API is genuinely intercepted (by a "coming soon", security, or cache plugin), the error now explains exactly what to deactivate.
* After a migration restore, the destination's Migrate page no longer shows a phantom "still pushing…" panel or the source site's migration keys. The migration bookkeeping replayed from the source database is now reset so the page reflects the destination itself.
* Added a pre-flight check on the Send page that detects active caching, security/firewall, redirect, and page-builder plugins and advises how to handle them before migrating (including an Elementor "Regenerate CSS" tip).
* URLs stored in encoded form are now migrated too. Page builders keep links inside encoded blobs (Elementor's dynamic-tag settings are URL-encoded JSON, and raw JSON escapes slashes), so a plain search-replace skipped them and those links kept pointing at the old site. The rewrite now also matches the JSON-escaped, URL-encoded, and URL-encoded-JSON shapes of every URL and path.
* Fixed the "There has been a critical error on this website" white screen that could appear on the destination after migrating an Elementor site. Elementor stores dynamic tags whose settings sometimes cannot be parsed; when it rebuilds its CSS (which a move prompts, and add-ons like ElementsKit force on every page load) it passes that unreadable value into code that requires an array and crashes the whole front end. Such tags are already non-functional, so BackReload now normalises them during a migration and the site loads normally. Working tags are left untouched.
* Fixed a critical error on the destination after migrating an Elementor site. BackReload no longer deletes Elementor's compiled CSS after a move — doing so forced Elementor to regenerate CSS on the next visit, which could hit an Elementor-internal crash and white-screen the front end. The compiled CSS is now left in place (as other migration tools do), so the site loads normally after migrating.
* The destination's live restore panel now shows a full activity log and a clear notice that you'll be signed out when the database is restored (sign back in with the source site's login). The migration progress panel also flags when a task stops responding.

= 1.0.3 =
* Fixed site-to-site migrations stalling after the build on sites where WP-Cron is disabled (common with managed hosts and cache plugins). The push now self-continues in the background via a loopback driver — completing even if you close the tab — with the Send page's live status as a fallback.
* The migration Send page now re-attaches to an in-progress migration after a browser reload, so you no longer lose the live progress view while a push is running.
* Fixed Abort not stopping a migration mid-push — the push now honours an abort immediately instead of overwriting it and resuming.
* Fixed database restore failing on migrations between sites with different table prefixes when a plugin table (e.g. All-In-One WP Security's `aiowps_logged_in_users`) confused prefix detection. The source prefix is now detected reliably, so cross-prefix restores complete.

= 1.0.2 =
* Restore is now data-only: BackReload no longer extracts plugins, themes, or mu-plugins into live WordPress paths. Restore, import, and site-to-site migration bring back the database, uploads (media), and other non-code content only. Full backups still capture everything for off-site safekeeping; reinstall plugins and themes through the WordPress installer after restoring.
* Import no longer accepts uploaded plugin, theme, or mu-plugin archive parts.
* Unified all internal identifiers under a single `backreload` prefix (PHP namespace, constants, functions, hooks, options, transients, custom database tables, REST namespace, and the WP-CLI command `wp backreload`).

= 1.0.1 =
* Update the bundled phpseclib library to 3.0.55 (latest stable).

= 1.0.0 =
* Initial release.
* Full-site backup engine with multi-zip splitting and per-category resumption.
* Restore wizard with component picker, source-destination override, and live progress.
* Storage destinations: Local, FTP, SFTP, AWS S3, Wasabi, S3-compatible, Google Drive (OAuth), OneDrive (OAuth).
* Notification channels: email, Slack, Discord, Telegram, Microsoft Teams, Google Chat, Pushover, custom webhook.
* Encryption at rest (AES-256-GCM, PBKDF2 key derivation).
* Site-to-site migration via one-time key: destination generates a key URL, source pastes it and clicks Start. Backup builds, streams direct between the two servers in chunks, and the destination optionally auto-restores. No FTP, no shared cloud storage, no manual download. Works through host upload caps and most WAF / mod_security setups.
* Cross-site migration with URL/path rewrite including inside serialized values. Cross-prefix data fixup ensures admin capabilities survive migrations between sites that use different `$table_prefix` values.
* Cross-server import via shared storage destination (no upload required).
* Sync button to discover backups created by sibling sites on the same destination.
* Maintenance mode with branded "be right back" page that engages automatically during restore.
* Multi-schedule support: combine independent daily/weekly/monthly schedules, each with its own destinations list and retention count.
* Honours the `DISALLOW_FILE_MODS` constant: when file modifications are locked (as on many managed hosts), restoring/importing executable code components (plugins, themes, mu-plugins) is skipped, mirroring WordPress core's own lockout. Database, uploads, and other content still restore.
* WP-CLI command suite: `wp backreload ...`.

== Upgrade Notice ==

= 1.0.14 =
Adds a Dashboard health score and a clear "from -> to" migration summary, and fixes several large-site issues: files silently dropped from a backup, a restored site keeping the original domain when restoring from shared storage, and hardening against tampered import archives.

= 1.0.13 =
Fixes large-site backups sticking at 5-10% and failing before upload. A dump or archive step taking over five minutes was wrongly treated as a crash and its completed work discarded, and backup passes did not chain correctly. Also adds diagnostics so a genuine failure reports which step died instead of ending silently.

= 1.0.12 =
Scheduled backups now run on your site's timezone instead of UTC, and all admin dates display in site time. Existing schedules are corrected automatically. Also fixes percent signs being corrupted in backups - take a fresh backup after updating, as earlier backups already contain that damage.

= 1.0.6 =
Fixes a post-migration "critical error" caused by carrying WordPress cache/database drop-ins across hosts. Also: full site-to-site migration (plugins and themes included), and reliable background auto-restore on WP-Cron-disabled hosts.

= 1.0.3 =
The migration Send page re-attaches to a running migration after a page reload.

= 1.0.2 =
Restore is now data-only (database, uploads, and other content — not plugins, themes, or mu-plugins). All internal names unified under the backreload prefix.

= 1.0.1 =
Updates the bundled phpseclib library to 3.0.55.

= 1.0.0 =
Initial release.
