=== VLabTech Backup & Restore ===
Author:            VLabTech
Author URI:        https://www.vlabtech.com
Version:           2.20
Requires at least: 5.6
Requires PHP:      7.4
License:           GPL-2.0+

== Description ==

VLabTech Backup & Restore is a full-featured WordPress backup and restore plugin.
It creates complete backups of your database and all WordPress files, stores them
securely, and allows you to download and restore them on any WordPress website.

== Installation ==

1. Upload the `vlabtech-backup` folder to `/wp-content/plugins/`.
   OR upload the ZIP file via WordPress Admin > Plugins > Add New > Upload Plugin.
2. Activate the plugin through the 'Plugins' menu in WordPress.
3. Navigate to "VLT Backup" in the admin sidebar.
4. Review the Settings page to configure your backup time, email and keep limit.
5. Click "Create Backup Now" to create your first backup manually.

== Minimum Server Requirements ==

* PHP 7.4+ (PHP 8.0+ recommended)
* PHP ZipArchive extension (enabled on most hosts)
* PHP MySQLi extension
* Writable wp-content/uploads/ directory
* WP-Cron enabled (or a real server cron pointing to wp-cron.php)

== How to Use ==

--- Creating a Backup ---
1. Go to VLT Backup > Dashboard.
2. Click "Create Backup Now".
3. Wait for the process to complete (may take several minutes on large sites).
4. You will see the backup listed in the Stored Backups table.
5. A success email with a secure download link is sent to the configured address.

--- Downloading a Backup ---
1. Go to VLT Backup > Dashboard.
2. In the Stored Backups table, click "⬇ Download" next to any backup.
3. The file will download as a ZIP archive.
4. Store the downloaded file in a secure location (cloud storage, external drive).

--- Restoring on the SAME server ---
1. Go to VLT Backup > Restore.
2. Select the backup from the dropdown list.
3. Check the confirmation checkbox.
4. Click "Restore Selected Backup".
5. Wait for the process to complete.
6. Verify your site is working correctly.

--- Restoring on a DIFFERENT / NEW server ---
1. Install WordPress on the new server.
2. Install and activate VLabTech Backup & Restore.
3. Go to VLT Backup > Restore.
4. Upload the .zip backup file under "Restore by Uploading a Backup File".
5. Check the confirmation checkbox and click "Upload and Restore".
6. IMPORTANT: After restore, edit wp-config.php on the new server with the
   correct database name, username, password, and host for the NEW server.
7. Update your site's URL in the database if the domain has changed:
   - Log into phpMyAdmin or use WP-CLI:
     wp option update siteurl https://newdomain.com
     wp option update home    https://newdomain.com
8. Regenerate your .htaccess by visiting Settings > Permalinks and clicking Save.

== Settings ==

- Automatic Backups: Enable or disable daily automatic backups.
- Daily Backup Time: The time (in your WordPress timezone) to run the daily backup.
- Notification Email: Receives a success email after each backup with a secure
  download link (valid for 7 days).
- Backups to Keep: How many backup files to retain (older ones are auto-deleted).
  Range: 1-30. Default: 1.

== What Is Backed Up ==

✔ Full WordPress database (all tables)
✔ wp-content/ (themes, plugins, uploads, mu-plugins, languages, etc.)
✔ wp-config.php (included in backup but NOT auto-restored on a different server)
✔ .htaccess

NOT backed up (by design):
✘ WordPress core files (wp-admin/, wp-includes/) — these can be re-installed from wordpress.org
✘ Files larger than 512 MB (skipped to prevent memory exhaustion)
✘ The backup directory itself (to prevent recursive backups)

== Security ==

* Only WordPress administrators (manage_options) can access all backup features.
* All form submissions are protected with WordPress nonces.
* Backup files are stored in a protected directory with .htaccess (deny all)
  and an index.php drop-file.
* Download links use cryptographically secure random tokens (32-byte random_bytes).
* Download tokens expire after 7 days.
* Files are served via PHP — never directly from the web.
* All user input is sanitized and validated.
* All output is escaped using WordPress escaping functions.
* ZIP extraction uses zip-slip protection (path traversal prevention).

== Testing Instructions ==

1. Basic functionality test:
   a. Install and activate the plugin.
   b. Go to VLT Backup > Settings and verify the server information table
      shows ZipArchive = Yes.
   c. Click "Create Backup Now" on the Dashboard.
   d. Confirm a ZIP file appears in the Stored Backups table.
   e. Confirm a notification email arrives.
   f. Click Download and verify the ZIP file downloads.

2. Scheduled backup test:
   a. In Settings, set the backup time to 2 minutes from now.
   b. Wait 2-3 minutes and reload the Dashboard.
   c. A new backup should appear (WP-Cron fires on the next page load after
      the scheduled time).
   d. Alternatively, use WP-CLI: wp cron event run vltbk_run_scheduled_backup

3. Restore test (safe test environment):
   a. On a staging site, install the plugin and upload a backup ZIP.
   b. Click Upload and Restore, confirm the checkbox.
   c. After restore, verify the database was imported (check pages, posts, settings).
   d. Verify wp-content files are present.

4. Settings test:
   a. Change "Backups to Keep" to 1.
   b. Create two manual backups.
   c. Verify only the latest backup is retained.

== Important Limitations ==

1. SHARED HOSTING TIME LIMITS: On shared hosting, PHP scripts are often killed
   after 30-60 seconds. Large sites (>500 MB wp-content or large databases) may
   fail to complete. See Large Website Recommendations below.

2. PHP UPLOAD LIMIT: Restoring by file upload is limited by your server's
   upload_max_filesize and post_max_size. The Settings page shows these values.

3. WP-CRON DEPENDENCY: Automatic scheduled backups depend on WP-Cron, which only
   fires when someone visits the website. Low-traffic sites may experience delays.
   Use a real server cron for reliability:
   * * * * * wget -q -O /dev/null https://yourdomain.com/wp-cron.php?doing_wp_cron

4. NO INCREMENTAL BACKUPS: Each backup is a full backup. This is simpler but uses
   more disk space and takes longer for large sites.

5. wp-config.php DATABASE CREDENTIALS: When restoring to a new server, the DB
   credentials in wp-config.php must be updated manually. The plugin does NOT
   modify wp-config.php during restore to avoid breaking the connection.

6. VERY LARGE DATABASES: The PHP-based SQL export/import is suitable for databases
   up to ~500 MB. For larger databases, use a dedicated tool like phpMyAdmin or
   mysqldump outside of WordPress.

7. FILE PERMISSIONS: The plugin needs write access to wp-content/uploads/.
   If you see "could not create directory" errors, check folder permissions (755).

== Large Website Recommendations ==

For sites with > 1 GB of content or > 200 MB databases:

1. INCREASE PHP LIMITS in php.ini or .htaccess:
   php_value max_execution_time 600
   php_value memory_limit 512M
   php_value upload_max_filesize 512M
   php_value post_max_size 512M

2. USE A REAL SERVER CRON instead of WP-Cron for scheduled backups.

3. CONSIDER REMOTE STORAGE: After the backup is created, move it to an external
   location (S3, FTP, Google Drive) to free up server disk space.

4. TEST YOUR BACKUP regularly by doing a test restore on a staging server.

5. SPLIT LARGE SITES: Consider excluding very large media upload directories
   from the backup and backing them up separately with rsync or similar.

== Changelog ==

= 2.20 =
* CRITICAL FIX: restoring a backup could fail with a generic "Server error
  (error) while starting the restore" message on hosts with slow disk/IO
  or strict execution time limits. The cause: ZIP extraction during restore
  was a single, non-resumable operation ($zip->extractTo() for the whole
  archive at once). On a host where this took longer than the server's
  real max_execution_time, PHP would fatal out mid-extraction before any
  response could be sent back to the browser, which only ever saw a bare
  500 error with no detail.
* FIX: ZIP extraction is now chunked and resumable, exactly like every
  other restore step (database import, URL fixing, file restore) — it
  extracts entries in time-boxed batches (bounded by TIME_BUDGET, ~18
  seconds per call) and saves its position, so it can pick up where it
  left off across as many AJAX calls as needed regardless of how slow
  the server's disk/network is. A new "Extracting backup archive (x/y)…"
  progress step is shown during this phase.

= 2.19 =
* FIX: very slow backup file downloads. The download handler only cleared
  one level of PHP output buffering (ob_end_clean() called once). On hosts
  where WordPress core, another plugin, or the server itself had additional
  buffer layers active, the entire backup file was accumulated in memory
  instead of streaming straight to the browser, so large backups either
  took a very long time to start arriving or timed out entirely.
* FIX: the download handler now clears every output buffer level, disables
  gzip/zlib compression (re-compressing an already-compressed ZIP wastes
  time), disables the PHP execution time limit and raises the memory limit
  for the duration of the download, and sends an X-Accel-Buffering: no
  header so reverse proxies (nginx/LiteSpeed in front of Apache, common on
  cPanel) don't buffer the whole response before relaying it.
* IMPROVEMENT: increased the file-read chunk size from 1MB to 8MB, reducing
  the number of fread()/flush() calls needed for large files.
* IMPROVEMENT: switched the download loop to PHP's native readfile(),
  which avoids the per-chunk echo()+flush() overhead of a manual read loop.

= 2.18 =
* CRITICAL FIX (part 2 of the prefix issue): the role-DEFINITIONS option
  ("{prefix}user_roles", e.g. "wp_user_roles") is now also renamed to
  match the destination server's prefix. v2.17 fixed WHICH role a user
  has (capabilities), but the ROLE ITSELF (what "administrator" is
  allowed to do) is defined in this separate option. Without this fix,
  a correctly-restored "administrator" user still had zero actual
  permissions on servers using a different table prefix, causing
  "Sorry, you are not allowed to access this page" even after a 100%
  successful restore.

= 2.17 =
* CRITICAL FIX: after a successful restore to a server using a DIFFERENT
  database table prefix than the backup (e.g. backup used "wp_", this
  server uses a custom prefix), administrators could be locked out with
  "Sorry, you are not allowed to access this page" even though the
  restore completed successfully and the login worked.
* Cause: WordPress stores user roles/capabilities in usermeta with the
  table prefix baked into the meta_key (e.g. "wp_capabilities"). This is
  now automatically rewritten to match the destination site's actual
  prefix during restore.

= 2.16 =
* CRITICAL FIX: restoring the wp-content/plugins folder no longer
  overwrites VLabTech Backup & Restore's OWN files. Previously, if the
  backup contained a different/older version of this plugin, restoring
  the "plugins" folder would replace the currently-running plugin code
  mid-restore, causing the next step to fail with "server error" because
  the AJAX actions no longer existed in the overwritten files.
* All other plugins ARE still restored normally — only this plugin's own
  folder is skipped (the destination server's installed version is kept,
  which is the correct/expected behaviour).
* Minor: avoided a redundant double-copy of the uploads/ folder.

RECOVERY for users affected by this issue on a previous version:
  1. Re-upload/overwrite wp-content/plugins/vlabtech-backup/ with this
     version's files via FTP or cPanel File Manager.
  2. Reload the Restore page — a "Resume Restore" banner will appear.
  3. Click Resume to continue exactly where the restore left off.

= 2.15 =
* CRITICAL FIX: restore continuation no longer depends on your WordPress
  login session. The instant wp_users/wp_usermeta are swapped during
  restore, your browser's login cookie AND page nonce become invalid by
  design (WordPress ties both to that table) — previously this always
  caused "server error after multiple retries" right around 70%.
* Each restore now gets its own secret continuation token (separate from
  your login), so the remaining steps (file restore, finalising) complete
  automatically even though your session expired.
* If you do see the "session expired" popup during restore, you can
  simply ignore/close it and the restore will keep going on its own.
* The Resume Restore button also carries this token, so resuming after a
  page reload works even before logging back in.

= 2.14 =
* Fixed: after the database swap (around 70%), the restore could appear
  to fail with "server error after multiple retries" because (a) the
  admin session is invalidated by the new user data (this is EXPECTED —
  you must log in again with the ORIGINAL site's admin credentials), and
  (b) the swapped-in database might not list this plugin as active, which
  would silently stop the restore from continuing.
* The plugin now automatically keeps itself active after a database swap.
* New "Resume Restore" banner on the Restore page: if a restore was
  interrupted, log back in and click Resume to continue exactly where it
  left off (no progress lost). A "Discard" option is also available.

= 2.13 =
* CRITICAL FIX: Restore no longer drops existing database tables before
  importing. New data is imported into temporary "vltbk_tmp_*" tables
  first. Only after import AND URL-fixing succeed completely are the
  tables atomically swapped into place (fast RENAME TABLE — no data copy).
* If restore fails at any point before the swap, the live site's database
  is left completely untouched.
* Old tables are preserved with a "_vltbkbak" suffix after a successful
  restore (not deleted) as an additional safety net — delete manually via
  phpMyAdmin once you have verified the restored site.
* Backups now automatically exclude known security-plugin quarantine
  folders (e.g. Guardian Shield) so real malware samples isolated by other
  plugins are never packaged into the backup ZIP.

= 2.12 =
* Restore is now fully CHUNKED — split into many small steps (~18s each)
  instead of one long request. This avoids Cloudflare/proxy timeouts
  (commonly ~100s) and shared-hosting PHP execution limits during large
  restores.
* Database import, URL search-replace, and file restore now resume
  automatically across requests via a saved progress state.
* Automatic retry logic if a single step times out or errors.

= 2.11 =
* Restore now runs via AJAX with a live progress bar (same as backup).
* Built-in fatal-error catcher: PHP memory/time-limit crashes during backup
  or restore now show a clear error message instead of a blank page.
* Restore by file upload now uses AJAX (no more blank admin-post.php page).
* WP_DEBUG is no longer needed to diagnose backup/restore issues — check
  VLT Backup > Logs instead.

= 2.10 =
* Initial public release.
* Full database export via PHP (no mysqldump required).
* ZipArchive-based full wp-content backup.
* Secure token-based download links (7-day expiry).
* WP-Cron daily automatic backups.
* Email notifications with download links.
* Configurable keep limit, backup time, notification email.
* Activity log with colour-coded entries.
* Zip-slip protection on restore.
* .htaccess + index.php protection for backup directory.

== Frequently Asked Questions ==

Q: Does the backup include WordPress core (wp-admin, wp-includes)?
A: No. WordPress core files are identical across all installations and can be
   re-downloaded from wordpress.org. This keeps backup files significantly smaller.

Q: Can I restore to a different domain?
A: Yes, but you will need to update the siteurl and home options in the database
   to reflect the new domain. Use WP-CLI or direct SQL after restoring.

Q: Where are backup files stored?
A: In wp-content/uploads/vlabtech-backups/ — protected from public access.

Q: Can I store backups off-site automatically?
A: This version stores backups locally. Manual download and upload to a cloud
   service is recommended for added protection.

Q: Is this plugin suitable for WordPress Multisite?
A: It has not been tested with Multisite and is not officially supported in that context.

== Author ==

VLabTech — https://www.vlabtech.com
