=== Bodholdt Backup for Google Drive ===
Contributors: bodholdtlabs
Tags: backup, google drive, restore, cloud
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 6.53.2
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Automated WordPress backups to Google Drive: full + incremental, scheduled, with one-click restore. Fully functional and free.

== Description ==

Bodholdt Backup for Google Drive is a complete, fully functional WordPress backup plugin that uploads your site (database and files) directly to your own Google Drive. There is no account to create and no license key to enter: the full backup engine, incremental backups, OAuth-secured Drive sync, scheduled or manual runs, configurable retention, and one-click full restore all work as soon as you connect your Drive. Nothing in this plugin is switched off until you pay, and it ships with no license client. Updates arrive in your dashboard.

= Everything included =

* **Full + incremental backups.** Manifest-based incremental backups only transfer changed files. Database is always fully exported.
* **Google Drive OAuth 2.0.** Connect once via your own Google Cloud project. Tokens encrypted at rest.
* **Scheduled or manual.** Hourly, daily, or weekly. Timezone-aware via WordPress cron. Or run on demand from the dashboard.
* **One-click full restore, and selective restore.** Download and restore any backup, or pick just the database, plugins, themes, uploads, or core. A pre-restore safety snapshot is taken automatically.
* **Configurable retention.** Choose how many recent backups to keep; older ones are auto-pruned from Google Drive to make room for newer ones.
* **Backup logs.** Paginated history with status filters and per-failure troubleshooting hints.
* **Email notifications.** A report after every backup attempt, successful or failed, to any address you choose. A failed run carries a short diagnosis with it, so the email leads with the fix rather than the raw error. A separate warning arrives if backups quietly stop running altogether.
* **Cloud browser.** View, download, and manage every backup in Drive directly from the WordPress dashboard.
* **Custom exclusions.** Skip specific directories or file patterns from backups.
* **Stale-backup watchdog.** A daily check warns you in the dashboard if your backups have quietly stopped running.
* **Multisite-compatible.** Network admin menu, `manage_network_options` capability gate.


= There is no paid edition of this plugin =

Earlier builds carried a small card at the top of the plugin's admin screens pointing at a paid edition. That edition has been retired and the card is gone with it, so this build now contains no upsell of any kind: no card, no notice, no license client, no license key field, no tier checks, and no upgrade prompt attached to any control. Nothing here stops working, degrades, or expires, because there is nothing to buy.

Slack and Discord messages and a scheduled backup-health digest are in a separate plugin of ours. They have never been part of this one, this build does not check for them, and nothing here changes depending on whether you have them. Email notifications are a different thing and they are in this plugin, for successful and failed backups alike.

= Privacy =

This copy of the plugin sends us no telemetry, no analytics, no tracking and no usage statistics, and none of your site content, database content or backup data ever reaches us. No license key is held and none is sent.

What leaves your server for Google is your backup archive and the requests that list, download and prune it, because Google Drive is the destination you configured. Those requests use the default user agent WordPress itself sets, which names your WordPress version and your site address, so Google sees your address on every call, in the same way WordPress.org sees it when WordPress checks for updates.

You downloaded this copy from bodholdtlabs.com rather than installing it from WordPress.org, so it also asks one address of ours whether a newer version of the plugin exists. That request carries the plugin version it is running and this site's address in its user agent, and nothing else: no request body, no cookies, no license key, no account. A good answer is cached for twelve hours, so the check runs no more than about twice a day, and a failure is cached for one hour, so while our server is unreachable it can ask up to once an hour. There is no settings switch for it. The one line PHP filter `bodholdt_gdrive_update_check_enabled` turns it off.

Some of what this plugin asks for never leaves your own site at all: it pings your own `wp-cron.php` so that a backup or a restore starts at once, and it asks your own site for one of its own pages to see whether the site is answering, both after a restore and when it is working out whether backups can start on their own. Every one of them is listed under External services below. This plugin is GPL-2.0-or-later.

= Security =

To report a security problem, email support@bodholdtlabs.com with "Security" in the
subject line. The full policy is at https://bodholdtlabs.com/security/

What this plugin does to protect you:
* OAuth credentials and tokens encrypted at rest (Sodium preferred, HMAC-authenticated AES-256-CBC fallback).
* All admin forms nonce-protected.
* Capability checks on every admin operation.
* Randomized backup-staging directory outside the web root (closed a 2026-05 SQL-dump exposure risk).
* OAuth 2.0 state parameter validation.
* Backup-filename allowlist on retention enforcement, so user-dropped files in your backup folder will never be auto-deleted.
* Path-traversal protection on zip extraction (Windows drive letters, backslashes, system files).
* Owner-token operation leases prevent concurrent backup and restore runs, including while a restore replaces WordPress options.

== External services ==

This plugin connects to the following third-party services. By installing and connecting the plugin you should be aware of what is sent, when, and to whom.

= Google Drive API and Google OAuth 2.0 =

What it is and what it's used for: Google Drive is the cloud storage destination for your backups. The plugin uses Google's OAuth 2.0 service to obtain permission to access your own Google Drive, and the Google Drive API to upload, list, download, and delete your backup archives. Storing and retrieving your backups in your Google Drive cannot be done locally on your WordPress server. That is the whole purpose of the plugin.

What data is sent and when:
* During connection (one time, when you click "Connect to Google Drive"): your browser is sent to Google's consent screen, and the plugin exchanges an authorization code for access/refresh tokens with `https://oauth2.googleapis.com/token`. If you disconnect, the plugin asks Google to revoke the token at `https://oauth2.googleapis.com/revoke`.
* During each backup: the contents of your backup archive (a ZIP of the WordPress files and a database export you selected to back up) are uploaded to `https://www.googleapis.com/upload/drive/v3/files`. The request carries your Google access token for authorization.
* During listing, restore, and retention pruning: the plugin queries, downloads, reads metadata for, and deletes backup files in your Drive via `https://www.googleapis.com/drive/v3/files`. Only files this plugin created in its own backup folder are managed.

The backup data is sent to Google only because you configured the plugin to back up to your own Google Drive account. No backup data is ever sent to Bodholdt Labs. Every one of these requests uses the default user agent WordPress itself sets, which names your WordPress version and your site address, so Google sees your address on each call.

* Google Terms of Service: https://policies.google.com/terms
* Google Privacy Policy: https://policies.google.com/privacy
* Google API Services User Data Policy: https://developers.google.com/terms/api-services-user-data-policy

= Bodholdt Labs update check =

What it is and what it is used for: this copy of the plugin was downloaded from bodholdtlabs.com rather than installed from WordPress.org, so nothing else would ever tell it that a newer version exists. It asks `https://bodholdtlabs.com/wp-json/bodholdt-licensing/v1/update-check/bodholdt-google-drive-backup-free` whether there is one, and if there is, WordPress offers it on the Plugins screen like any other update.

What data is sent and when: the version this plugin is running and this site's address, both in the request's user agent, exactly as a WordPress.org install sends them. There is no request body and there are no cookies, and no license key, no account, no site content and no backup data. A good answer is cached for twelve hours, so the check runs no more than about twice a day; a failure is cached for one hour instead, so while our server is unreachable it can ask up to once an hour until it gets an answer.

How to turn it off: there is no settings screen for this. Returning false from the `bodholdt_gdrive_update_check_enabled` filter, in a theme's functions.php or an mu-plugin, stops the check entirely. The filter is read before the cache, so it takes effect at once rather than waiting for a cached answer to expire.

If you accept an update: WordPress downloads the new version from bodholdtlabs.com, the same way it would download one from WordPress.org. We count that download, as a running total for the plugin and one row recording the file, the version and the time, with no address and no identifier in it. Our web server logs the request the way any web server logs any request.

* Terms of Service: https://bodholdtlabs.com/terms-of-service/
* Privacy Policy: https://bodholdtlabs.com/privacy-policy/

= Requests that never leave your own site =

These go to your own WordPress site and to nobody else. They are not third-party services, and they are listed here because they are HTTP requests and this section is meant to be complete: every request this plugin makes to your own server is one of the kinds below.

* **Waking WordPress cron.** When you start a backup or a restore by hand, the plugin posts to your own site's `wp-cron.php`, the same address WordPress itself uses when it wakes cron, so that the work begins at once instead of waiting for the next visitor. It sends no data with the request and never reads the reply.
* **The check after a restore.** When a restore finishes, the plugin asks your own site for its front page and its admin screen and reads only the HTTP status line, so that it can tell you whether the site came back up. Both addresses are built from the host you are already browsing, not from the restored settings, because after a migration those name the site the backup came from. It follows no redirects, sends no cookies, and keeps nothing. It deliberately does not verify the TLS certificate on those two requests, so that a self-signed certificate on a staging site is not reported as a dead site. The `bodholdt_gdrive_restore_health_check_urls` filter changes or removes the addresses it asks.
* **The check before a backup starts.** The plugin makes that same request to your own admin screen on two other occasions: on the Dashboard, until your first backup has succeeded, so the setup check can tell you in advance whether your site is able to start a backup on its own, and each time you start a backup by hand, so that a site which cannot reach itself is told so plainly instead of watching a progress bar that never moves. It asks the same admin address, it is changed by the same filter, it reads only the HTTP status line, and the answer is cached for an hour, so it runs at most once an hour.

== Installation ==

1. Install the ZIP you downloaded from bodholdtlabs.com: go to **Plugins → Add New → Upload Plugin**, choose the file, and install it. You can also unzip it into `/wp-content/plugins/` yourself. This copy checks bodholdtlabs.com for new versions and offers them in your dashboard, so once it is in place you can leave it there and take updates as they arrive.
2. Activate the plugin through the **Plugins** menu.
3. Open **Bodholdt G-Drive** in your admin sidebar.
4. Follow the guided 4-step Setup Wizard, or open **Settings** to configure manually.
5. Create a Google Cloud project, enable the Drive API, and generate OAuth credentials. The in-plugin guide walks you through this in about 15 minutes, and no billing is required.
6. Paste your Client ID and Client Secret, then click **Authorize Google Access**.
7. Configure your backup schedule.
8. Click **Start New Backup** on the **Dashboard** to run your first backup.

== Frequently Asked Questions ==

= Do I need a paid Google account? =

No. Google gives every account 15 GB of free Drive storage. Backups range from ~50 MB (small blog) to a few GB (large WooCommerce site). The Dashboard shows a size estimate before each backup.

= Do I need a license key or account? =

No. This plugin is free and fully functional. There is no license key to enter and no account to create, and every capability it ships with (full and incremental backups, scheduling, configurable retention, full restore, and selective restore) works out of the box. There is no paid edition of it to buy. The one thing this copy asks us is whether a newer version has been released, so your dashboard can offer it to you, and that check is described under External services above.

= Does the plugin create database tables? =

Yes, five. It creates `bodholdt_logs` for the backup history shown on the Logs tab, and the optional Night Watch Arcade that this copy includes creates four more (`bod_arcade_progress`, `bod_arcade_achievements`, `bod_arcade_events` and `bod_arcade_celebrations`), each behind your WordPress table prefix. Between them they hold backup run history, points, unlocked achievements, a scoring log and pending celebration cards, with no personal or visitor data in any of them. None of those rows are ever sent to Bodholdt Labs and none to any third party. They leave your site in one way and one way only: a database backup exports every table in your database, so they travel inside `database.sql` to your own Google Drive along with everything else, on every backup that includes the database.


= What is a Google Cloud project and why do I need one? =

To use the Google Drive API on your own behalf, Google requires you to create a free Cloud project and generate OAuth credentials. This keeps your Drive access under your own control. Bodholdt never sees your Google credentials. The plugin includes a step-by-step guide for this; it takes about 15 minutes and is free.

= What is incremental backup mode? =

Incremental mode only backs up files that changed since your last full backup. This makes subsequent backups much faster and smaller. The database is always fully exported regardless of mode. You can force a full backup at any time.

= Will backups slow down my site? =

Backups run in the background via WordPress cron. Visitors browsing your site don't notice them.

= What if a backup fails? =

The plugin emails you with the error and a suggested fix. The Logs tab on the dashboard shows every backup with diagnosis hints (expired tokens → reconnect; quota exceeded → expand Drive storage; etc.). Each chunked upload retries up to 3 times before reporting failure.

= Can I restore to a different server? =

Yes, with a working WordPress installation on the destination. This edition does not ship a standalone webroot migration installer, so restore always runs inside WordPress: install WordPress on the new server, install and connect this plugin to the same Google Drive account, then restore the backup from the **Your Backups** tab.

= Will the plugin delete files I manually put in the backup folder? =

No. The retention enforcer uses an ID manifest written at upload time, plus a backup-filename allowlist. Files you manually drop into the backup folder are invisible to the enforcer and stay forever.

= Does this work with WordPress Multisite? =

Yes. Network admin menu placement and `manage_network_options` capability checks throughout. **As of v6.9.0, multisite backups are *complete* by default.** One backup per network captures every blog plus the shared user/sitemeta/blogs tables. Restoring recreates the full multisite layout.

Network admins can switch to per-site mode in Settings for an isolated site slice. A primary-site per-site backup contains its database and, when selected, only primary-site uploads; it never contains WordPress core, themes, plugins, the `wp-content/uploads/sites` subsite tree, or network-shared tables. A backup started from a non-primary site must be configured as Database only or it stops before upload. File restore from older per-site archives without this signed boundary is refused, while database-only restore remains available. Per-site archives cannot restore a complete multisite network.

As of v6.12.0, the retention setting and the retention manifest are stored network-wide, so retention is enforced consistently no matter which blog a backup runs under.

= What's in a backup zip? =

Every backup contains:

* `database.sql` is the SQL dump (mode-aware: single-site, per-blog, or network)
* `bodholdt-manifest.json` describes mode, blog topology, plugin/wp versions, and tables dumped. The restore engine reads this first.
* Selected WordPress files based on your "What to Back Up" setting. In primary per-site mode this is limited to primary uploads, with `wp-content/uploads/sites` preserved and excluded; shared code is never included.

The SQL dump and signed manifest are stored inside the authenticated archive. No executable webroot installer is included.

= Will my must-use plugins, languages, and drop-ins be backed up and restored? =

Yes (drop-ins since v6.15.0; mu-plugins / languages since v6.14.2). The backup zip includes `wp-content/mu-plugins/`, `wp-content/languages/`, and the four standard WordPress drop-ins (`object-cache.php`, `advanced-cache.php`, `db.php`, `maintenance.php`) whenever they're present at the source, and the restore engine writes them back to the destination. Earlier versions had blind spots: v6.0.0 to v6.14.0 silently omitted mu-plugins from every backup (fixed in v6.14.1); v6.0.0 to v6.14.1 silently dropped mu-plugins / languages / drop-ins on restore even when they were in the zip (restore side fixed in v6.14.2); v6.0.0 to v6.14.2 silently omitted drop-ins from the backup zip itself (fixed in v6.15.0; the restore-side `is_file()` guard added in v6.14.2 had masked this backup-side gap by degrading quietly). If you're restoring from an older backup, the missing pieces may need to be re-installed manually after the restore completes. Once you take a fresh backup on v6.15.0 or later, the full set rides through end-to-end.

= Will my `wp-content/object-cache.php` drop-in be backed up and restored? =

Yes (since v6.15.0). Drop-ins are single files at the root of `wp-content/` rather than directories, so they need explicit handling that the general dirs scanner can't provide. v6.15.0 adds an unconditional drop-ins block (symmetric to v6.14.1's always-include treatment of mu-plugins) covering the four standard WordPress drop-in names: `object-cache.php` (persistent object cache: Redis Object Cache, Memcached drop-ins), `advanced-cache.php` (full-page cache: W3 Total Cache, WP Super Cache, etc.), `db.php` (database abstraction layer: HyperDB, LudicrousDB), and `maintenance.php` (custom maintenance-mode page). Installs without any of these drop-ins degrade quietly to "nothing to include", guarded with `is_file()` checks at both ends of the backup/restore round-trip. If you're restoring from a backup taken on v6.14.2 or earlier and your source site had drop-ins, the drop-ins will be missing post-restore and you'll need to re-install them manually (re-activate Redis Object Cache from its admin panel, for example).

= I have both your Google Drive and OneDrive backup plugins installed. Will I lose one during a restore? =

No (since v6.14.2). Older versions unconditionally skipped the sibling backup plugin during restore, so a customer running both plugins as belt-and-suspenders backup would lose the one they weren't restoring with and have to reinstall it. v6.14.2 changes the skip to be conditional: the sibling is now skipped only if it's currently network-active (i.e., running and could collide with the restore). Installed-but-inactive siblings are restored normally. The plugin doing the restore is still always skipped (self-skip is necessary, because a plugin can't safely overwrite itself mid-execution).

= After I restore to a fresh staging site, my Network Admin link 404s. Why? =

Network Admin in a multisite-subdomains install is reachable only via the *main site's* hostname (the row at `wp_blogs.blog_id = 1`). After a restore, that's whatever your *original* network's main site was, not necessarily the hostname you configured on your staging server. So if your original network's main site was `example.com` and you've restored onto `staging.example.com`, your Network Admin lives at `https://example.com/wp-admin/network/…`, not at `https://staging.example.com/wp-admin/network/…`. To move Network Admin onto a new hostname, update `siteurl` / `home` for blog 1, `wp_blogs.domain` for blog 1, `wp_site.domain`, and `DOMAIN_CURRENT_SITE` in `wp-config.php`. That is the standard WordPress "migrate multisite to a new domain" procedure. This is WordPress default behavior, not a plugin bug.

= My staging site (Local by WP Engine, etc.) shows a TLS cert error on one of my multisite hostnames after restore. =

Local generates exactly one TLS cert per site, covering only that site's primary domain. If your network has multiple hostnames (e.g., `example.com` for one blog and `app.example.com` for another), Local won't have a cert for the secondary hostname after a restore, and your browser will refuse to load it with `ERR_CERT_COMMON_NAME_INVALID`. If your browser has ever visited the real production version of that hostname over HTTPS, HSTS may make the cert-warning click-through impossible. Easiest workaround for verification-only purposes: skip the browser and use `wp-cli` (`wp site list`, `wp option get blogname --url=…`) to confirm the restore worked. For full browser access: add the secondary hostname to Local's site config so Local re-issues the cert with both hostnames in SAN, then trust the new cert. Production hosts won't hit this, because they have real TLS certs covering whatever hostnames you've configured DNS for.

= Backups (or restores) silently fail with "Fetching Download link" disappearing. What's wrong? =

This is the §4.5.13 symptom: the staging directory the plugin uses to assemble the backup zip (or unpack the restore zip) isn't writable. Pre-v6.15.0 the plugin used `/var/lib/bodholdt-staging/gdrive/` exclusively, which only exists on operator-prepared hosts. On any shared host without root access (most managed WordPress hosts, every local development environment, every WP.org reviewer install), the `mkdir()` call returned `false` and the next `fopen()` failed silently. The AJAX progress UI would simply stop advancing without an error.

**The plugin resolves the staging directory in two steps, in this order:**

1. `/var/lib/bodholdt-staging/gdrive/<suffix>/` if it is writable. This is the preferred location, because it sits outside the web document root.
2. A private `bodholdt-staging-gdrive-<suffix>/` directory inside your system temp folder, if `/var/lib/` is not writable. It is ephemeral, and it is also outside the web document root.

Each fallback is logged with `error_log()`, so your PHP error log tells you which path was used. If neither location is usable, the plugin stops with a clear message instead of failing silently:

    No secure backup staging directory is available. Configure BODHOLDT_GDRIVE_DIR to a private
    writable path outside the web root, or ask your host to enable a writable system
    temp directory.

If you would rather choose the location yourself, add this to your `wp-config.php`, above the `/* That's all, stop editing! */` line:

    define( 'BODHOLDT_GDRIVE_DIR', '/var/lib/bodholdt-staging/gdrive/' );

The path must be absolute, and it must sit outside your web root. A path inside the WordPress install, including anything under `wp-content/uploads`, is rejected by the plugin's security check, and every backup and restore will stop with the message above. If you do not have a writable directory outside the document root, ask your host for one.

= Is the plugin GPL? =

Yes. The plugin source is GPL-2.0-or-later and is freely modifiable.

== Screenshots ==

1. **Dashboard**: Health summary, last-backup status, size estimation, big green Start-Backup button.
2. **Settings → Google Drive**: OAuth credentials, schedule selector, backup-mode toggle (Complete, or Fast for changed files only), retention controls (how many recent backups to keep).
3. **Your Backups** (cloud browser): a list of every backup in Drive with its date and size, with download and restore on each one, verify where the archive can be checked.
4. **Restore panel**: choose exactly what to bring back, then confirm. Restoring is not one click and it is not undoable: a safety copy of your database is saved first, and the panel says so plainly.
5. **Logs**: paginated history of every run, filtered by counted chips for complete, error and warning, with troubleshooting notes on the rows that need them.
6. **Setup Wizard**: Guided 4-step first-run walkthrough: connect Google Drive → configure schedule → first backup.

== Changelog ==

= 6.53.2 (2026-09-05) =
* The readme fix in 6.53.1 landed in the wrong file. These plugins ship one edition, the free one, and its readme is built from readme-wporg.txt. That file still carried every sentence the 6.53.1 entry said had gone: a verify button on every backup row, a one-click full restore, and log status pills reading success, error and running. So the note describing the correction shipped without the correction. The Screenshots section you are reading now matches what the plugin actually shows.

= 6.53.1 (2026-09-05) =
* The Screenshots list in this readme described a few screens that are no longer here. The Logs page has counted chips for complete, error and warning rather than a filter and sort pair, and there was never a "running" or "success" status in the log itself. The backup list shows verify only where an archive can actually be checked. And the restore section now says the two things that matter most: you choose exactly what comes back, and there is no undo. Nothing about the plugin changed in this release. It only stops describing itself wrongly.

Recent releases are listed here. The complete history for every version is at https://bodholdtlabs.com/changelog/

= 6.53.0 =
* **The check that asks your own site whether it came back up is now careful about who it will trust.** After a restore, and again before your first backup, this plugin asks your own site for one page and reads nothing but the answer code. That request goes to the very address you are being served from, so it has always accepted that address's certificate without checking it, which is what lets the check work on a staging site with a self signed certificate. The list of addresses it asks for can be redirected by a filter, and until now anything the filter supplied was accepted the same way. From this release only the address you are being served from right now is accepted unchecked. Every other address is verified exactly like every other request this plugin makes.
* **This still cannot change the outcome of a restore.** It never rolls anything back and it never turns a finished restore into a failed one. When it cannot get an answer, for any reason at all, it says nothing rather than guessing. A certificate your server cannot verify is now one of those reasons.
* **One thing to know if you use that filter.** If you point the check at an address over https whose certificate does not validate on your server, you will get no verdict instead of a wrong one. Point it at an http address, or at a name the certificate is valid for.
* **Held in place by tests.** All four answers this check can give, healthy, server error, maintenance and no answer, are now pinned for an address on your own site and for one the filter supplied, so this cannot quietly drift back.

= 6.52.4 =
* **The list of requests that never leave your own site was one short.** That section said it was complete and named two: cron starting a backup, and the check after a restore. There is a third. Before your first backup, and again each time you start a backup by hand, the plugin asks your own admin screen whether your site can answer itself, which is how the setup check knows to warn you that backups may not start on their own. It has worked that way since 6.50.8, it never leaves your server, and nothing about it has changed. It is now described alongside the other two.
* **The wording no longer depends on counting.** "Two more requests" is a promise that has to be rechecked by hand every time anything is added, which is exactly how it went out of date. The Privacy section and the External services section now describe every request this plugin makes to your own site, which stays true whatever the number is.
* **The note for 6.52.3 counted them too, and no longer does.**
* **Waking cron is described more accurately.** It happens when you start a restore by hand as well as a backup.
* **Nothing in the plugin changed.** This release is readme wording only.

= 6.52.3 =
* **The privacy wording is now generated from one description of what this copy sends, and where.** The Privacy section, the External services section and the FAQ answer about the log table were each written at a different time and had drifted apart from one another. All three now come from the same statement, so a claim cannot be true in one of them and false in another three screens down. Nothing in the plugin changed, and nothing about what it sends changed.
* **The Privacy section no longer contradicts itself.** It said nothing about your site or its contents ever reaches us, and then, two sentences later, that the update check sends this site's address. Your address is something about your site, so the first half of that was simply wrong. It now names what never goes to us and what does, in the same breath.
* **The answer about the log table was wrong, and is now right.** It said the backup history rows are never sent anywhere. They are never sent to us and never to anyone else, but a database backup exports every table in your database, so those rows do travel inside `database.sql` to your own Google Drive like every other table. The same answer now also lists the four arcade tables this copy creates, which it had never mentioned at all.
* **The update check is described honestly.** "About twice a day" was only the twelve hour cache on a good answer; a failure is cached for one hour, so while our server is unreachable the check can ask up to once an hour. "You can switch it off" meant a one line PHP filter and not a setting, and the section now says so. It also now says what happens when you accept an update, and that we count the download.
* **Two things that were never written down now are.** The requests this plugin makes to Google carry the user agent WordPress itself sets, which names your WordPress version and your site address, so Google sees your address on every backup call. And some requests go to your own site and nowhere else: cron starting a backup, and the check after a restore that asks your own site whether it came back up. Both are described under External services.
* **The install step no longer offers a plugin directory listing that does not exist.** It tells you to install the file you downloaded from bodholdtlabs.com and keep it, because this is the copy that offers you new versions in your dashboard.

= 6.52.2 =
* **A correction to the release notes, not to the plugin.** The note for 6.52.1 was written about the WordPress.org copy of this plugin, and it was wrong about the copy you are holding. It said nothing here had changed, and that this copy updates from WordPress.org. Neither was ever true of it. The corrected note is below, and nothing in the plugin itself changed here.

= 6.52.1 =
* **This copy now says in its own header where it updates from.** You downloaded this plugin from bodholdtlabs.com, and it installs into the same folder name a WordPress.org listing would use. WordPress matches an installed plugin to WordPress.org by that folder name, so if these plugins are ever listed there, WordPress could have replaced this copy with the WordPress.org build without anybody asking, and that build carries no update channel of its own. This copy now states in its own header that it updates from bodholdtlabs.com, which is WordPress's own way of saying leave this one where it is. It goes on updating from bodholdtlabs.com, the way it has since 6.52.0, and the check it makes is described under External services above.

= 6.52.0 =
* **The card advertising a paid edition is gone.** A small card sat at the top of every screen of this plugin, pointing at a paid edition of it. That edition has been retired, so the card was advertising something nobody can buy. It has been removed and nothing replaced it: there is now no upsell of any kind anywhere in this plugin.
* **The description now says what is actually true.** One edition, free, no tiers, nothing switched off: full and incremental backups, scheduling, retention, full restore, selective restore, multisite, unlimited sites, and email notifications after every backup whether it succeeded or failed. Nothing on that list will ever move behind a payment.

= 6.51.2 =
* **A backup that cannot reach Google Drive now ends as a failure, and tells you.** If your Google connection had expired or been withdrawn, the backup got as far as signing in and then simply stopped. Nothing was recorded as a failure, no "Backup Failed" email arrived, and the dashboard was left saying "Authenticating with Google..." at 10 per cent until that status aged out an hour later. The run now ends the way every other failure ends, so the log entry, the email, and anything else listening all happen once, and the message names the expired connection and the control that fixes it. A connection you removed on purpose, and a site that has never connected, stay quiet exactly as before, and the dashboard no longer claims a backup is running after one of those.
* **A failed backup now carries a one line diagnosis with it.** The plugin already works out a short cure for the failures it recognises, such as "Re-authenticate Google Drive in Settings.", and until now only the paid Slack and Discord alerts could see it. That sentence now travels with the failure itself, so anything listening for a failed backup can lead with the cure instead of the raw error text. When there is no honest short answer, such as a brief outage at Google's end, the sentence is deliberately left empty and the full message is always there behind it. What the paid alerts send is unchanged.

= 6.51.1 =
* **Free build packaging cleanup.** No functional changes in either edition.

= 6.51.0 =
* **Developers can now listen for what the plugin does.** The backup engine announces five moments as ordinary WordPress actions: `bodholdt_gdrive_backup_success`, `bodholdt_gdrive_backup_failure`, `bodholdt_gdrive_restore_initiated`, `bodholdt_gdrive_watchdog_stale`, and `bodholdt_gdrive_preflight_blocked`. Each one hands over a single array of plain facts, such as the archive name and size. A sixth action, `bodholdt_gdrive_settings_sections`, lets other code add a section to the end of the Settings screen. If you do not write code against WordPress, nothing changes for you.
* **Tidier packaging for the free edition.** The free download no longer carries fragments of paid-only code it could never run. This is housekeeping in how the package is put together. No feature was added or removed in either edition.

= 6.50.13 =
* **The setup wizard now checks what you pasted, and says what looks wrong.** A Google client ID ends in .apps.googleusercontent.com and a client secret starts with GOCSPX-, and they sit next to each other on the Credentials page. If one looks wrong, or if the two look swapped, you are told which field to look at instead of finding out later from a refused connection. It never stops you saving.
* **A connection that dies after a week now tells you why.** Google's "Testing" publishing status expires access after 7 days, and the failure used to look identical to someone revoking access by hand. The plugin now remembers when you connected, and if the connection is refused within 8 days it names the likely cause and the exact control to fix it, rather than sending you round the same loop to the same result.

= 6.50.12 =
* **Plainer writing.** A few messages used long dashes where a full stop or a separator reads more easily. Nothing functional changed.

= 6.50.11 =
* **A correction to the setup check added in 6.50.8.** It said a private staging folder was ready after looking at only one of the two places a backup has to be able to write. A backup also needs somewhere to put a short-lived lock file, in your system temp folder, and on a host where that is not available the check reported everything was fine while every backup, restore, and settings change quietly refused to start. It now answers about both, so a site where nothing can run is told so.

= 6.50.10 =
* **A correction to the plugin description. Nothing about the plugin changed.** The Screenshots section called the backup list sortable, which it has never been, and said each row carried a delete action, which it does not. It now names the buttons that are really there: download, verify, and restore.

= 6.50.9 =
* **A new install now points you at setup instead of leaving you to find it.** Activating the plugin added a menu item near the bottom of the sidebar and said nothing else. The bolded "Set up" link on the Plugins list helped anyone who happened to look at that row, but it is on one screen and it disappears the moment you click anything, and bulk activation and WP-CLI never really show it at all. The next admin screen you open now offers one clear door into the setup wizard, once. It never appears if you have already connected your account or finished with the wizard, it is not shown on the plugin's own screens where the wizard opens by itself, and dismissing it retires it for good.

= 6.50.8 =
* **Your first backup now tells you when it cannot start.** WordPress runs backups on a scheduled task, and on some hosts that scheduler is switched off or blocked. When that happened, the backup screen showed a spinner and the word Initializing for a full hour before giving up, with nothing to explain why. Until your first backup succeeds, the dashboard now runs a short setup check and says in plain language whether your site can reach itself and whether there is a private folder to build the backup in. If a backup you started has not moved after ninety seconds, the screen stops spinning and tells you what to look at. A check we cannot complete is reported as exactly that, because on a hardened host an unanswerable test is normal and does not mean anything is wrong.
* **A missing staging folder is now visible on screen.** If there is nowhere safe outside your web root to build a backup, the plugin stops backups and restores rather than write somewhere unsafe. That is the right call, but it happened in silence. It now says so on your dashboard.

= 6.50.7 =
* **The setup wizard now tells you the truth about how long connecting Google Drive takes.** It promised no technical experience and under five minutes, then asked you to create a Google Cloud project, enable the Drive API, configure a consent screen and generate OAuth credentials. That is real work. The wizard and the install guide now say to plan on about 15 minutes, once, and that we walk you through every click. Nothing about the setup itself changed.


= 6.50.5 =
* **Packaging only, no functional change.** This release exists so the code we build from and the package you receive are byte for byte the same. 6.50.4 shipped moments earlier with one comment line different in a shared file. Nothing you can see or use has changed.

== Upgrade Notice ==

= 6.40.0 =
Critical if you exclude paths. Backups with a whole area excluded, such as uploads or themes, could not be restored at all. They restore now, including backups you already have. Broad exclusions are disclosed instead of silent, and a missing area names itself. No fresh backup is needed.

= 6.39.0 =
Critical. Restores complete again on sites with a populated excluded folder, and Verify and Trust accepts genuine backups again, both proven on real backups. Tamper checking now works on container hosting, and a stopped run names the file that changed. No fresh backup is needed.

= 6.38.0 =
Recommended. Restoring a backup from before 6.36.0 no longer deletes folders that backup never contained. A restore no longer stalls on a special file appearing while it runs, and names what it skips. A backup with a whole-site exclusion now stops with a clear message. No fresh backup is needed.

= 6.37.0 =
Recommended. A file restore no longer stops when your site holds a special file such as a socket or a named pipe. It is skipped and reported, and any remaining stop names the exact file. Exclusion lines that would silently do nothing are now refused when you save. No fresh backup is needed.

= 6.36.0 =
Strongly recommended. Stray wp-*.php files at your WordPress root, some of which hold database credentials, are no longer backed up. Also fixes an exact restore deleting the contents of a folder excluded by pattern, and exclusion lines with stray spaces that matched nothing at all.

= 6.34.0 =
Strongly recommended. Fixes backups that could never be restored when both Bodholdt backup plugins are installed, missing database columns on MySQL 8, backups stopping on a symbolic link, and an interrupted restore leaving the site stuck offline. Take a fresh backup after updating.

= 6.15.0 =
Strongly recommended. Staging-dir fallback fixes silent backup failures on managed hosts and Local. Completes drop-in disaster recovery.

= 6.14.2 =
Strongly recommended. Restore now includes mu-plugins, languages, and WordPress drop-ins for a complete one-click site recovery.

= 6.14.1 =
Critical for must-use plugins: backups now always include wp-content/mu-plugins/. Take a fresh backup after upgrading. Recommended for everyone.

= 6.14.0 =
Friendlier 4-step setup wizard (Welcome → Connect → Schedule → Run First Backup). No breaking changes; recommended for everyone.

= 6.13.0 =
First-run polish: Plugins-list "Set up" link and clearer OAuth setup-guide copy. No breaking changes; recommended for everyone.

= 6.12.1 =
Hotfix: "Could not calculate estimate" dashboard error. Recommended for everyone on 6.12.0.

= 6.12.0 =
Multisite fix: network-wide retention manifest with self-healing desync repair. Auto-migrates on upgrade. Strongly recommended for multisite installs.

= 6.11.0 =
Polish ship. Cross-engine collation portability so backups restore cleanly on MySQL 5.7/8.x and older MariaDB. Progress bar now resumes on page reload mid-backup. Default-exclusion patterns added for old dev artifacts. No breaking changes; recommended for everyone.

= 6.10.0 =
Critical multisite restore-completeness fixes for symlink-deployed installs and views that JOIN wp_users. Strongly recommended for multisite + symlink deploys.

= 6.9.0 =
Multisite installs now back up the entire network (every blog + shared tables) by default. Single-site installs unchanged. Restore engine refuses incompatible mode combos to prevent orphan tables. Recommended update for everyone.

= 6.6.0 =
Three hard-locks removed, so the admin pages, manual backups, and scheduled backups are always available. Recommended update for everyone.

= 6.1.1 =
Critical: closes a bug where user-dropped files in `Bodholdt Backups/<site>` could be auto-deleted by retention. Recommended for all users.
