=== KiraBackup ===
Contributors: yasuhirohaga
Tags: backup, restore, migration, database, export
Requires at least: 6.2
Tested up to: 7.1
Stable tag: 1.2.2
Requires PHP: 7.4
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Create a complete WordPress backup — files and database — and restore it on any environment.

== Description ==

**KiraBackup** lets you export your entire WordPress site as a single ZIP package and import it on any WordPress installation. Use it for routine backups, disaster recovery, or site migration.

= Key Features =

* **Selective export** — choose exactly what to include: database, plugins, themes, uploads, must-use plugins.
* **Small steps** — exports and imports advance in steps of about 15 seconds driven by the browser, with a live progress bar. The database is read in slices of about 8 MB, so tables with very large rows do not exhaust PHP memory. Very large sites still take time, and a ZIP with a great many files gets slower to extend as it grows.
* **The site is switched over at the very end** — the database is imported into separate tables, URLs are replaced there, and the restored folders are prepared next to the live ones. Until then nothing on the site changes.
* **A way back from any point** — every step of the switch-over is written into a journal on disk before it is done, and the original folders and database tables are moved aside, not deleted. If the switch-over stops for any reason (time limit, out of memory, the server killing PHP, a power cut), the original site is put back on the next page load; if the database had already been swapped, the switch is finished instead. Before the restore starts, KiraBackup checks the free disk space.
* **Check, then keep or roll back** — after a restore the site as it was before (database and files) is kept for 7 days or until you click **Everything is fine**; **Roll back** puts it back in one click. If the restored site does not load at all, use the recovery address shown before the restore, put an empty file named `kirabackup-recover.txt` into `wp-content` by FTP, or run `wp wpcbr recover --rollback` (it also works with `--skip-plugins --skip-themes`).
* **Any upload size** — the browser sends the ZIP in slices. A slice the server refuses is resent smaller, a dropped connection is retried, and selecting the same file again continues where it stopped. You can also put the ZIP in the imports folder by FTP (it is used where it is and never deleted by KiraBackup) and choose it on the screen, or run `wp wpcbr import <file.zip>`.
* **Two-step import** — inspect the package contents before making any changes to your site.
* **URL replacement** — replaces the source site's URLs with the destination's throughout the database, including inside serialized PHP values and block editor markup.
* **Clear when something goes wrong** — if PHP stops a step (memory or time limit), the screen says which limit and what happened to the site: before the switch-over nothing changed, during it the original site was put back. The uploaded ZIP is kept, so the restore can be started again without uploading; leftovers are removed on the next run. Files that cannot be read are left out of a backup and listed in `skipped-files.txt` instead of failing it.
* **Preflight warnings** — alerts you to low memory, short execution time, or insufficient disk space before you start.

= Typical Use Cases =

* Migrate a site from a local development environment to a live server.
* Move a site to a new hosting provider or domain.
* Create a point-in-time backup before a major update.
* Clone a site for staging or testing.

= Requirements =

* PHP 7.4 or later with the `ZipArchive` extension enabled.
* WordPress 6.2 or later. Single-site installations only (not Multisite).
* Free disk space: during a restore the ZIP, its extracted contents and the database imported next to the current one (checked before the restore starts). The original folders are moved aside, not copied, so keeping them needs no extra space; the original database is kept as extra tables until you confirm the restore.

= Limitations =

* WordPress itself (wp-admin, wp-includes and the files in the WordPress folder: `.htaccess`, `robots.txt`, `ads.txt`, verification files of search engines and the like) and `wp-config.php` are not backed up or restored. Install WordPress on the new server as usual; `wp-config.php` and `.htaccess` hold that server's own settings, and a copy from another server can stop WordPress from starting — and then no way back would work. Copy any of those files you need by hand. Backups made with older versions may contain them; they are skipped, and the screen says so.
* Translations (`wp-content/languages`) are not included; WordPress downloads them again on the new site (Dashboard → Updates).
* Only this site's database tables (those with its table prefix) are backed up and restored. Another WordPress in the same database — including one whose prefix begins like this one, such as `wp_b2_` next to `wp_` — is left alone. Tables without the site's prefix (made by a few plugins) are not included. Older backups that hold other tables are restored without them, and the screen names them.
* A folder on a different disk than `wp-content` cannot be switched over safely, so it is not restored; restore the other parts, or copy that folder by hand.
* Links and Windows junctions are not followed, and paths the server cannot read (for example over the Windows path-length limit) are skipped; both are listed in `skipped-files.txt` inside the backup.
* On sites that set `DISALLOW_FILE_MODS`, code (plugins, themes, must-use plugins) is not restored and the upload-limit helper is disabled; the database and uploads can still be restored.
* The SQL parser handles standard WordPress data well but may have difficulty with highly customised database schemas.
* URL replacement changes tables row by row, so a table without a primary key (made by a few plugins) keeps its old addresses, as with WP-CLI's `search-replace`. A table whose key holds the address (a plugin's list of URLs) is replaced too, key included. When a new address would make a value longer than its column holds, or the database refuses it (a CHECK constraint — for example a JSON column on MariaDB in a table whose key holds the address), the restore stops and changes nothing; restore without URL replacement and replace the addresses afterwards (for example with `wp search-replace`).
* The database is read table by table while the site keeps running. Each slice of a table continues after the last key read, so a row that is there for the whole backup is saved exactly once. When a table is done, the rows already saved are looked for again: a row that was deleted meanwhile — also one added again under a new key, as WordPress does when an expired transient is set again — is noted as removed, and a row whose other unique values changed is saved again as it is now, so such a name is not restored twice. A row that is added while its part of the table is being read may be in the backup or not. When an older backup holds a row twice under the same key or unique name, the restore keeps the later copy (the newer one); a backup of an older version can still hold an old and a new row that share no unique value (such as a post's field set again), and both are restored. A table with neither a primary key nor a unique key on columns that are never empty (NULL) is read in an order made from the content of each whole row: for a very large table of that kind every slice reads the whole table, and the backup takes longer. Rows that hold exactly the same values are saved as many times as the table holds them, but cannot be told apart, and a row of such a table that is changed while the backup runs counts as removed and added again (it may be saved in its old form, its new form, both or neither).
* Generated columns (computed by the database) are saved without their values and computed again when restored.
* Rows that swap the values of a unique key built on part of a column (a prefix) or on an expression, while the backup checks that table, are not followed: one of them may be restored in its older form, or be missing. Unique keys on whole columns are followed.
* Backups made by 1.2.0 can be restored only by KiraBackup 1.2.0 or later. Complete Backup Restore 1.1 refuses them at the upload ("The backup manifest is invalid"), without changing anything: their database part may remove rows that changed while the backup ran, which 1.1 would apply to the live site. Backups made by 1.1 can still be restored by 1.2.0. If you switch between the two while a backup or restore is under way, neither goes on with the other's: 1.1 says that no job is running (switch back to KiraBackup to finish it), and KiraBackup stops a backup or restore that 1.1 began, without changing the site, and removes the unfinished archive of such a backup. Each keeps its own restore point (the copy of the site from before its last restore) under its own names: a restore, "Roll back" or "Discard" in 1.1 leaves KiraBackup's untouched, and "Everything is fine" in KiraBackup never removes 1.1's. A backup made with 1.1 while a KiraBackup restore waits for your check also holds KiraBackup's copy of the site, so it is larger. If you then restore that backup in 1.1, 1.1 changes every "%" in the values it puts back, so KiraBackup's "Roll back" refuses without changing anything until "Roll back" in 1.1 has undone that restore.

== Installation ==

1. In your WordPress admin, go to **Plugins → Add New → Upload Plugin**.
2. Select `kirabackup.zip` and click **Install Now**.
3. Activate the plugin.
4. Go to **KiraBackup** in the admin menu to create your first backup.

Alternatively, upload the `kirabackup` folder to `wp-content/plugins/` via FTP and activate it from the Plugins screen.

== Frequently Asked Questions ==

= Will migrating to a different domain break my site? =

No. Enable the **URL Replacement** option during import and enter the destination site URL. The plugin will replace all occurrences of the source URL throughout the database, including inside serialized PHP values used by many themes and plugins.

= Why are WordPress itself and wp-config.php not restored? =

The new server already runs WordPress, and WordPress itself always comes from WordPress.org. `wp-config.php` and `.htaccess` belong to the server: its database connection, paths and PHP settings. While those files would be half replaced — or if a copy from another server did not fit — WordPress could not start at all, and then neither the automatic recovery nor any other way back could run. Your content, users, settings, plugins, themes and media are all in the parts that are restored; translation files (`wp-content/languages`) are not — WordPress downloads them again. If you need a custom setting from the old `wp-config.php`, a rule from `.htaccess` or a file such as `robots.txt` or `ads.txt`, copy it by hand.

= What happens if the import is interrupted, or it breaks my site? =

Until the switch-over nothing on the site changes: the database goes into separate tables and the files are prepared next to the live ones. If the browser tab closes, open KiraBackup again — it offers to continue or to cancel and clean up. If PHP stops a step, the screen shows why; the uploaded ZIP is kept, so you can start the restore again without uploading.

The switch-over itself is recorded step by step in a journal in `wp-content/wpcbr-tmp/` (inside a folder with a random name, so nobody can download it or the extracted database). Only folders are moved, so even when the journal itself cannot be written (a full disk), KiraBackup stops and puts everything back by looking at what is actually there. If the switch-over stops part-way, the next page load puts the original site back (or finishes the switch when the database had already been swapped), and the KiraBackup screen tells you what happened. A small must-use plugin (`!kirabackup-recovery.php`, with one "!" more at the start than any other must-use plugin of the site or of the backup, so that it loads first) does this, so it works even when a restored plugin, must-use plugin or theme breaks the site; it is removed when the restore is confirmed or rolled back.

After a successful restore, check the site, then click **Everything is fine** (the copy of the old site is deleted) or **Roll back** (database and files go back). Without a choice the old copy is deleted after 7 days — unless a person has to decide first (a roll-back was refused, or the restored site lacks much of what it should have); then it is kept until you choose, and the screen says why. If the restored site does not open at all:

* open the recovery address shown before the restore (`…/?wpcbr-recover=<key>`), or
* put an empty file named `kirabackup-recover.txt` into `wp-content` by FTP, then open the login page (`…/wp-login.php`) — it rolls back there. Any other page only accepts it and leaves the work to WP-Cron, which needs the site to be able to call itself (blocked by Basic authentication or some firewalls), or
* run `wp wpcbr recover --rollback --skip-plugins --skip-themes`.

If even WordPress cannot start, the file `HOW-TO-RECOVER.txt` in the random folder inside `wp-content/wpcbr-tmp/` lists every step to undo by FTP and phpMyAdmin.

Rolling back puts the whole site back as it was before the restore: posts, comments, orders, media and settings added after the restore are removed too. KiraBackup asks before it does that.

= Are WordPress user accounts and passwords migrated? =

Yes, when you include the database in your export. WordPress stores user accounts (login names, hashed passwords, roles) in the users and usermeta tables, which are exported and restored. Passwords are stored as hashes and remain valid after migration. Restoring the database replaces the users of this site, so after the restore you log in with the accounts of the backup.

= What happens if the upload is too large? =

Nothing special to do: the browser always sends the ZIP in slices of a few megabytes. If the server, a proxy or a firewall refuses a slice, it is resent smaller from the same position; if the connection drops, it is retried, and selecting the same file again continues the upload. When even small slices are refused, put the ZIP by FTP into the imports folder named on the Restore tab (inside the backups folder) and choose it there, or run `wp wpcbr import <file.zip>`.

= Where are backups stored? =

In a folder inside `wp-content` with a random name (shown on the Settings tab), and each backup file has a random part in its name. On Apache and IIS the folder also blocks direct downloads (`.htaccess`, `web.config`); nginx does not read those files, so there the random names are the protection. To keep backups outside the public web folder entirely, add `define( 'WPCBR_BACKUP_DIR', '/path/outside/public_html/kirabackup' );` to wp-config.php.

= What does the "PHP Upload Limit" button change? =

It is optional. It writes `upload_max_filesize`, `post_max_size`, `max_execution_time` and `max_input_time` into a marked block in the `.htaccess` of the site's root folder — where WordPress keeps its own rules (Apache with mod_php) — and into the WordPress folder's `.user.ini` (PHP-FPM/CGI). It never touches nginx configuration, and it is disabled when `DISALLOW_FILE_MODS` is set. To undo it, remove that block and those lines. Uploads work without it.

= What is deleted when I delete the plugin? =

Its settings and job records. Your backups and the restore point are kept, unless you tick **Also delete the stored backups and the restore point** on the Settings tab before deleting the plugin. A restore that is waiting for **Everything is fine** keeps its recovery helper, so it can still be rolled back; with that option ticked, the copy of the old site is deleted too.

= Does it work on Multisite? =

No. On a Multisite network the plugin stays inactive, because a restore would overwrite the tables and files shared by every site.

= I used "Complete Backup Restore" before. How do I switch? =

KiraBackup is the same plugin under a new name. Deactivate Complete Backup Restore, activate KiraBackup, then delete the old plugin. Your stored backups and restore point carry over, and backups made with the old version can be restored.

== Screenshots ==

1. Export screen — select which components to include in the backup ZIP.
2. Import screen — upload a package and review its contents before restoring.
3. Import preview — confirm source URL, WordPress version, and included targets.
4. Stored backups list — download or delete previously created packages.

== Changelog ==

= 1.2.2 =
* Fixed: a backup that lists an empty folder — such as mu-plugins left empty by an earlier restore on a site that had none — could not be restored with its default parts ("The package is missing files for: mu-plugins"). A part the backup holds nothing of is no longer offered — the Restore tab says so — and that folder of the site is left as it is. Empty folders inside a part are restored as before.
* Improved: when PHP runs out of memory in the middle of a restore's switch-over, KiraBackup asks WordPress for the memory to put the site back (`wp_raise_memory_limit()`): `WP_MAX_MEMORY_LIMIT` or 32 MB above the limit that ran out, whichever is higher, where the server lets PHP change the limit.
* Improved: on Windows servers, the checks whether the backups folder, `.htaccess` and the WordPress folder can be written use WordPress's own test (`wp_is_writable()`).
* Maintenance: the choices sent from the backup and restore forms are cleaned as they arrive, and the code explains each place where it reads the database or files directly.

= 1.2.1 =
* Changed: every database query is prepared with placeholders — table and column names too (`%i`), also when a restore imports the backup's `database.sql`. Requires WordPress 6.2 or later. A statement in `database.sql` that neither KiraBackup nor mysqldump writes (an expression among the values, `ON DUPLICATE KEY UPDATE`, …) is now refused instead of run.
* Changed: WordPress's folders are looked up in one place; the PHP Upload Limit helper writes its `.htaccess` block into the site's root `.htaccess` (`get_home_path()`, where WordPress keeps its rules).
* Fixed: values of BIT columns changed on backup → restore (BIT(1) 0 came back as 1, BIT(8) 7 as 0x37). They are now saved as hex values, and backups made with earlier versions are read back as the numbers they hold.
* Fixed: a table whose name holds a space (a plugin's table may) is restored again.
* Fixed: the FAQ and the Settings tab name the values the PHP Upload Limit button writes (`max_input_time`, not `memory_limit`).

= 1.2.0 =
* Changed: the plugin is now named KiraBackup (slug `kirabackup`). Settings, restore points and stored backups from Complete Backup Restore carry over; deactivate the old plugin before activating KiraBackup.
* Fixed: values containing "%" (permalink structure, URL-encoded slugs, serialized data) were damaged by backup → restore. Backups made with older versions are repaired while they are restored.
* Fixed: a restore no longer switches the site over until the very end. URLs, table prefixes and plugin activation are fixed on the imported tables first; only then are folders moved into place and the database swapped.
* Added: restore journal. Every switch-over step is recorded before it is done and the original files and tables are kept, so a restore stopped at any point (time limit, out of memory, killed process) is put back automatically — or finished, when the database was already swapped. After a restore: **Everything is fine** / **Roll back** (database and files), kept for 7 days; ways back when wp-admin does not open: a recovery address, a file put in by FTP, and `wp wpcbr recover`.
* Changed: WordPress itself (wp-admin, wp-includes, root files such as .htaccess) and wp-config.php are no longer backed up or restored — while they are half replaced, or if a copy from another server does not fit, WordPress cannot start and no way back works. Older backups that contain them are restored without them.
* Security: the restore's journal and extracted files live in a randomly named folder, so servers that ignore .htaccess (nginx) cannot serve them.
* Fixed: a resumed database import no longer inserts rows twice; a roll-back keeps the backups folder name and KiraBackup's activation.
* Fixed: URL replacement changes every value exactly once — also in tables whose primary key holds the address, after a request was killed part-way, and when a second request takes over a step that stood still. A table that cannot be read or a value the database refuses stops the restore instead of leaving old addresses behind.
* Fixed: when PHP stops a step, the job is marked failed at once and the screen offers to continue or cancel, instead of waiting up to 15 minutes. A job left open by a closed tab is offered on the next visit.
* Fixed: a restore or roll-back no longer reports "Another backup or restore is already running" (job records are no longer stored inside backups).
* Fixed: only this site's database tables (those with its table prefix) are backed up and restored. Another WordPress in the same database — also one whose prefix begins like this one — is no longer rolled back with it. Older backups that hold other tables are restored without them, and the screen names them.
* Fixed: **Roll back** first checks that the kept copy of the site is complete. If a part of it is missing (deleted to free space, by a cleanup tool…), nothing is changed and the screen names what is missing — before, the current files were removed.
* Fixed: **Roll back** finishes when a plugin that the restore had set aside was installed again in the meantime; what was added after the restore goes with it and is named.
* Fixed: a copy of the site made while a restore waited for confirmation (a staging copy on the same server) no longer rolls back or confirms the original site.
* Fixed: **Everything is fine** and the automatic confirmation after 7 days delete the kept copy only when the restored site is complete, and the automatic one does it in a background request instead of a visitor's.
* Fixed: the recovery helper also loads before must-use plugins whose names begin with "!"; a restore does not start while an old `kirabackup-recover.txt` is waiting in wp-content.
* Improved: uploads are sent in slices that are resent smaller when refused (413, upload limit, firewall), retried after a dropped connection, and resumed when the same file is selected again. ZIPs placed in the imports folder by FTP can be restored; new `wp wpcbr import` and `wp wpcbr backup` commands.
* Fixed: a backup of a site in use no longer misses a row, or saves one twice (a backup that could not be restored), when the site deletes or adds rows while the backup runs; each slice continues after the last key it saved. Rows whose key changed only in letter case, width, kana marks or trailing spaces, and rows that took each other's unique names while the backup ran, are restored once, as they are now; tables keyed by long text back up on servers with a small max_allowed_packet.
* Fixed: a site with a table that has generated columns can be restored (the backup saved their values, which MySQL refuses). Backups made before are restored too.
* Improved: the database is dumped in slices of about 8 MB; unreadable files, links and junctions are skipped and listed instead of failing the backup.
* Security: the backups folder gets a random name (or set WPCBR_BACKUP_DIR outside the web root); the plugin stays inactive on Multisite; code is not restored when DISALLOW_FILE_MODS is set.
* Changed: admin CSS and JavaScript are loaded as separate files only on the plugin screen; bundled translation files were removed (translations come from translate.wordpress.org).
* Added: optional deletion of backups and the restore point when the plugin is deleted (off by default).

= 1.1.2 =
* Added: single-file backups are automatically uploaded in browser-sized chunks when the destination server has a low request limit.
* Improved: automatic retry with progressively smaller chunks after an HTTP 413 response.
* Improved: clearer upload UI with selected file size, chunking notice, percentage progress, and updated Japanese translations.
* Fixed: idempotent chunk retries, concurrent job execution, large-table URL replacement, serialized data handling, empty BLOB exports, and non-destructive Plugins/Uploads restores.

= 1.1.1 =
* Fixed: a chunked backup could fail with a "Failed to open the ZIP archive" error and produce no package; backups are now created reliably.
* Fixed: restoring the Uploads or Plugins directory is now non-destructive — files are copied first and only then are extraneous files removed, so a failed or interrupted file restore no longer deletes your existing media or other plugins.
* Fixed: URL replacement now also updates escaped-slash URLs (e.g. inside block-editor content), so images and internal links are no longer left pointing at the old domain after a migration.
* Fixed: URL replacement no longer corrupts serialized values that do not contain a URL, and never blanks a value on broken serialized data.
* Improved: URL replacement processes each table in batches to avoid running out of memory on very large tables.
* Fixed: empty binary/BLOB column values no longer produce invalid SQL that aborted a database restore.
* Hardened: data from an imported package is never unserialized into arbitrary PHP objects (object-injection safe).
* Added: a re-entrancy guard so a slow request re-sent by the browser cannot run the same export/import job twice.
* Fixed: after a failed import the progress overlay can now be dismissed and the import retried, instead of staying on screen.
* Minor: backup file names now include a random suffix; the SQL mode is set on every import batch.
* Improved: exports remain a single ZIP, while oversized ZIPs are automatically divided into safe browser-upload chunks and reassembled during import.
* Improved: the import screen now shows the selected file, size, automatic chunking notice, and percentage progress; Japanese translations were updated.
* Fixed: retrying a chunk after a lost response is now idempotent and no longer fails when the destination chunk already exists.

= 1.1.0 =
* Chunked export and import driven by the browser with a live progress bar, so large sites no longer hit PHP timeouts or memory limits.
* Safe database restore: tables are rebuilt in shadow tables and swapped in atomically at the very end. An interrupted import never breaks the running site or your login, and the previous database is kept as a one-click restore point.
* Roll back the last restore from the admin page, or with WP-CLI (`wp wpcbr rollback` / `status` / `cleanup`) when you cannot reach wp-admin.
* Optional split packages: split a backup into smaller parts that are uploaded one at a time and reassembled on import, so a low upload limit on the destination is not a problem.
* Atomic file restore for boot-critical directories (active theme, mu-plugins): the new copy is built alongside the live one and swapped in, so a failed copy can never leave a half-replaced theme that white-screens the site.
* Database is imported before any file is changed, so a failed database import leaves every file untouched (no new files against an old database).
* Cross–table-prefix migrations now also remap the prefix-dependent user capability/role keys (`<prefix>capabilities`, `<prefix>user_roles`), so migrated users keep their roles instead of being locked out of wp-admin. Composite primary keys are handled correctly during URL replacement.
* Safer "PHP Upload Limit" helper: it no longer edits the web server (nginx) config (which could take the site down), raises the execution/input time alongside the size so large uploads don't time out, and preserves unrelated .user.ini directives.

= 1.0.0 =
* Initial release.

== Upgrade Notice ==

= 1.2.2 =
Fixes restoring a backup that lists an empty folder (such as mu-plugins left empty by an earlier restore), and gives more room to put the site back after PHP runs out of memory during a restore.

= 1.2.1 =
Database queries are all prepared with placeholders; WordPress 6.2 or later is required.

= 1.2.0 =
Renamed to KiraBackup. If you used Complete Backup Restore, deactivate it first, then activate KiraBackup; your backups and restore point carry over. Backups made by 1.2.0 cannot be restored by 1.1 (it refuses them); restore them with 1.2.0 or later.

= 1.1.2 =
Adds automatic large-ZIP upload while keeping backups as a single portable file, plus restore reliability and security fixes.

= 1.1.1 =
Fixes a bug that could stop backups from being generated, makes Uploads/Plugins restores non-destructive, and improves URL replacement for domain migrations. Recommended for all users.

= 1.1.0 =
Adds chunked processing for large sites, atomic database restore with one-click/WP-CLI rollback, and optional split packages for low upload limits.

= 1.0.0 =
Initial release.
