=== WP Bulk Replace ===
Contributors: bsmaha
Tags: bulk edit, attachments, media, pdf, custom fields
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.4.1
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Match a folder of files to your posts, review every pairing, then back up and replace the live files in bulk. Nothing is overwritten without a backup.

== Description ==

Replacing a few hundred PDFs attached to a few hundred pages is a job nobody wants: find the right page, download the old file, upload the new one, repoint the field, repeat. WP Bulk Replace does the matching for you and then makes you check it before anything is written.

It works against any post type and any meta field that holds a file URL, so it is not tied to a particular theme or page builder.

**How it works**

1. **Upload** &mdash; drag files in, or drop them into the staging folder over FTP and scan.
2. **Match** &mdash; pick the post type and the meta field holding the file URL, then run matching. Each filename is scored against every published post of that type. Re-running is safe: rows you set by hand are protected.
3. **Review and resolve** &mdash; correct anything that matched wrongly, filter by confidence or status, and export a CSV if you want a second opinion from someone else.
4. **Back up and replace** &mdash; tick the rows you want and process them. Every replacement is backed up first, and anything processed can be rolled back from the same screen.

**Built to be careful**

* **Every replacement is backed up.** Rollback is one click, per row or in bulk.
* **Conflicts block processing.** If two files match the same post they would overwrite each other, so replacing stops until you resolve it.
* **Creating is separated from replacing.** If a post has no file yet, replacing becomes creating, which needs its own approval.
* **Imports are undoable.** Every reviewer CSV import can be reversed later, not just immediately.
* **Your data stays yours.** Deleting the plugin leaves your batches, history and backups intact unless you explicitly opt in to removal.

**Works with**

PDF, DOC and DOCX files. Reviewer spreadsheets can be CSV or XLSX.

== Installation ==

1. Download `wp-bulk-replace.zip` from https://wpbulkreplace.com/ or from the GitHub releases page, https://github.com/bsmaha/wp-bulk-replace-free/releases.
2. In WordPress, go to Plugins &rarr; Add New &rarr; Upload Plugin, choose the zip and click Install Now. With WP-CLI: `wp plugin install wp-bulk-replace.zip --activate`.
3. Activate the plugin, then go to **Bulk Replace** in the admin menu and create your first batch.

Updates arrive through the normal WordPress Plugins screen, from wpbulkreplace.com.

You will need administrator access, and a way to get files onto the site &mdash; either the browser uploader or FTP/SFTP access to the staging folder.

== Frequently Asked Questions ==

= Will this overwrite files without asking? =

No. Nothing is written until you tick rows and click through the Back up and Replace step, and every file replaced is copied to a backup folder first.

= Can I undo a replacement? =

Yes. Processed rows can be rolled back from the batch screen for as long as their backup exists. Deleting backups is always a deliberate action, never automatic.

= What happens to my data if I delete the plugin? =

By default, nothing is removed. Your batches, history, settings and backups stay in place and are picked up again if you reinstall.

There is a setting to opt in to full removal. Turning it on and then deleting the plugin permanently destroys staged files and backups &mdash; which means processed rows can no longer be rolled back &mdash; along with the plugin's tables and settings. It never touches your Media Library, the replacement files now live on your site, or the meta values on your posts.

Externally pinned staging or backup directories are not deleted automatically during uninstall and must be removed manually if desired.

= Can I keep backups outside the web root? =

Yes, and it is the better place for them. Backups are copies of live documents, so a folder inside `uploads` relies on a deny rule that some servers ignore. Point the plugin somewhere else in `wp-config.php`:

`define('WPBR_BACKUPS_DIR', '/path/outside/webroot/wpbr-backups');`
`define('WPBR_STAGING_DIR', '/path/outside/webroot/wpbr-staging');`

Either constant overrides the matching field on the Settings screen, which then shows the folder read-only. The path must be absolute; the folder is created if it does not exist.

If you would rather not edit `wp-config.php`, the Settings screen has a Backups location panel. Choose Check locations and it lists the folders it can verify as private (outside your site's web roots, writable, on storage that survives restarts, with room for your backups). You pick one and confirm; existing backups are not moved and stay available for rollback. You can also name a folder for it to consider with `define('WPBR_PRIVATE_ROOT', '/path/outside/webroot');`. "Private" means outside the web roots the plugin can learn; it does not prove that no server maps the folder.

The folder fields on the Settings screen deliberately cannot do this &mdash; they only accept paths inside `wp-content`, because a path typed into the admin should not be able to direct writes anywhere on the server.

= I replaced a file, but my site still shows the old one. =

Usually the replacement worked and a browser, a caching plugin or a CDN is showing a copy it saved earlier. Replacing keeps the file's address on purpose, so nothing that links to it breaks &mdash; and a cache cannot tell the file changed.

Under Bulk Replace &rarr; Settings, "Add a version to links to replaced files" is on by default. It adds a small marker such as `?updated_on=20261014-093015` to the document links (PDF, Word, Excel and similar files) WordPress builds from your Media Library on your public pages, for files this plugin replaced, so browsers fetch the new file. Nothing stored is changed. It does not reach links typed into your content (including the File block), images, audio or video, or pages that a cache has saved a copy of until you clear that cache. If a download or preview feature on your site stops working for a file you replaced, turn the setting off. The "Why am I still seeing the old file after replacing it?" article on the Help Centre walks through checking the replacement and what to clear.

= How does the plugin update, and what does it send? =

The plugin checks https://wpbulkreplace.com for new versions, about twice a day, through the normal WordPress update system. The request contains the plugin's name and version and nothing else &mdash; not your site's address, users or content. No account or license is needed. Before an update is installed, the downloaded package is checked against the SHA-256 checksum in the update manifest, which is fetched over HTTPS from wpbulkreplace.com. This detects a corrupted, truncated or substituted download. It is not a code signature: it confirms the package is the one wpbulkreplace.com published, so it is only as trustworthy as that server. To turn update checks off, add `define('WPBR_DISABLE_UPDATE_CHECKS', true);` to `wp-config.php`.

The only other request the plugin makes is to your own site, to check that its backups folder is not publicly readable.

= Is it on WordPress.org? =

Not currently. WP Bulk Replace is independently developed and distributed from wpbulkreplace.com and GitHub. It is not affiliated with or endorsed by WordPress.org or the WordPress Foundation.

= Does it work with custom post types and custom fields? =

Yes. The post type and meta field are chosen per batch. Multiple meta fields can be given as a fallback chain.

= Does it need a page builder or a particular theme? =

No. It reads and writes post meta directly.

= Can someone else review the matches? =

Yes. Export the batch as a CSV, have them mark it up, and import it back. Each import can be undone.

= Where do I get help, and what should I expect? =

The Help Center at https://help.wpbulkreplace.com first. For a bug you can reproduce, open an issue at https://github.com/bsmaha/wp-bulk-replace-free/issues; for questions and ideas, use https://github.com/bsmaha/wp-bulk-replace-free/discussions. Security problems should be reported privately, as described in SECURITY.md in that repository &mdash; never in a public issue.

The plugin is maintained by one person rather than a team, so a reply usually takes a few working days and is not guaranteed &mdash; anything involving lost files or lost backups jumps the queue.

A report is much easier to answer if it says what you expected, what happened instead, which screen you were on, and your WordPress, PHP and plugin versions. If it is about matching or replacing, switch on Diagnostics under Settings and include that output &mdash; but read it before you post it, because it contains server paths and may contain filenames you would rather not publish.

== Screenshots ==

1. Batches, with progress through each one &mdash; what is untouched, what is mid-review, and what is finished.
2. Review & Resolve. Every file scored against the post it matched, filtered by confidence or by status, with the suggested new filename editable per row.
3. Backup & Replace. Per-row status, the file each one wrote, and a backup kept for every replacement until you delete it.
4. Settings. Where staging and backups live, what counts as a confident match, and exactly what is removed if you ever delete the plugin.

== Changelog ==

= 1.4.1 =
* Fixed: on Backup & Replace, a replacement that was also renamed now says "Backup kept", is counted as Replaced and labelled "Replaced & renamed", and Delete Backups removes its backup. A new attachment that overwrote nothing says "No backup needed".
* New: before you process, Backup & Replace shows the file name each row is expected to be saved as, and says when a name is already in use or planned for another row. Those are saved with a number added; nothing is overwritten. WordPress picks the final number when the file is saved.
* New: a Not verified filter on Backup & Replace, with a count, for processed rows that are not verified yet. It combines with the other filters.
* New: ticked rows are highlighted on Review and on Backup & Replace, including a range selected with Shift-click.

= 1.4.0 =
* New: Review saves only the rows you changed, a few dozen at a time, with progress beside the button. A batch of any size now saves without raising PHP's max_input_vars.
* New: if the connection drops part-way, what was saved stays saved, your other edits stay on the page, and Retry sends only the rest.
* Changed: Refresh Matching and Bulk Rename save your changes first, then run on the server, instead of posting every row.
* Changed: the message for a truncated old-style submission no longer asks you to change a PHP setting.
* New: Show it / Show them on a conflicting match now shows only that group of files, with a banner and a button to see every file again. It also ends by itself once the conflict is fixed and saved.
* New: a trash button on each file row removes just that file from the batch, after one confirmation that names the file. Your other unsaved edits are saved first; your site's media and content are not touched.
* Changed: corrections you make in Review (a note, a status, a file name, a cleared match) are kept when you press Refresh Matching; only automatic suggestions are recalculated.
* Changed: Skip, Delete, Confirm New Attachments and Combine now work on any number of selected files.
* Changed: saving your Review changes adds one line to the Activity Log with how many rows were saved.
* New: on Backup & Replace, click the Staged file or matched-post heading to sort (A to Z, Z to A, original order), and Shift-click a second checkbox to select every shown row in between.
* Fixed: Delete Backups stayed available after the backups had been cleaned up; it now needs a verified row that still has a backup, and says so when it is unavailable.
* Fixed: Roll Back refused a file the plugin had created once its backups had been cleaned up, although that file never needed a backup. The Backup column now reads No backup needed, Backup kept or Backup deleted.
* Changed: the sortable headings show a faint up-down mark before anyone clicks them.
* Fixed: file names made from a title with a long dash (for example "Psychology – Clinical") no longer contain a run of hyphens; a name with repeated hyphens is tidied when its row is saved.

= 1.3.0 =
* Settings are now organised into tabs, with every option you already had kept where it was saved: your settings carry over unchanged.
* The Match step runs on the same matching routine the optional add-ons use (engine services contract 2), so a batch matched for you by an add-on gets exactly the result the Match step would give. Nothing changes in how you match on screen, and rows you set by hand stay protected.
* The Batches list, the batch page and History show "Assistant" or "Connected app" when an optional add-on made a change on someone's behalf. This needs one new column on two tables (database version 3.1); it is added by itself on the first admin page load, and a site that cannot add it simply records no actor and works as before.
* Includes everything in 1.2.0 and 1.1.0.

= 1.2.0 =
* Internal release: nothing changes on screen. Batch creation, file staging, hand matches and the checks made before a write became plain functions that the optional add-ons call (engine services, contract 1).
* Detects the other edition of WP Bulk Replace before loading any code, and refuses to activate beside it with one clear message.

= 1.1.0 =
* Backups can be kept in a private folder outside the web root: a new Backups location panel checks each candidate folder before it is used, and a folder that is readable from the web is never chosen.
* Hosts that keep uploads on cloud or managed storage (for example WordPress VIP) are handled through a storage-adapter contract. WP Bulk Replace never overwrites a file it cannot back up and verify, and it refuses a file no adapter owns.
* Extension API 2: the engine records its intent before a write and concludes from it after an interrupted request. Optional add-ons plug into this contract; none is needed to use WP Bulk Replace.
* Deleting a site or the plugin keeps backups that are the only copy of an original.
* Everything in 1.0.1 is included.

= 1.0.1 =
* New admin design across every screen.
* Backups and staged files are checked for public access with several file types (documents, images, no extension) before anything is replaced; a folder that any of them can read stops the run and says why.
* Protected backups are never deleted or moved by uninstall or by deleting a site while they are the only copy.
* A backup that cannot be confirmed is no longer called a safe original; damaged files can be retried from the batch page.
* Saving variant groups refuses text that is not a list of groups instead of replacing the saved rules with nothing.

= 1.0.0 =
* First public release.
* Updates from wpbulkreplace.com, checked against the SHA-256 in the HTTPS update manifest.

== Upgrade Notice ==

= 1.4.1 =
Backup & Replace now shows the file names it expects to save and keeps backup labels accurate for renamed replacements. No settings to change.

= 1.4.0 =
Large Review batches now save reliably: only changed rows are sent, in small groups, with progress and a Retry. No settings to change.

= 1.3.0 =
Adds tabbed Settings, a shared matching routine for the optional add-ons, and a small database update (3.1) that runs by itself. Your batches, backups and settings carry over.

= 1.2.0 =
Adds the engine services used by optional add-ons and an activation check against the other edition. Your batches, backups and settings carry over.

= 1.1.0 =
Adds private backup folders and the storage-adapter contract used by optional add-ons. Your batches, backups and settings carry over.

= 1.0.1 =
New admin design and stronger checks before backups are written or files replaced. Your batches, backups and settings carry over.

= 1.0.0 =
First public release.
