=== Bulk Content Toolkit ===
Contributors: tlloancy
Donate link: https://donorbox.org/wordpress-plugins
Tags: bulk edit, content management, custom fields, quick edit
Requires at least: 5.0
Tested up to: 6.9
Requires PHP: 7.4
Stable Tag: 1.3.0
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html

Add custom meta fields to the WordPress Quick Edit and Bulk Edit panels — no coding required.

== Description ==

Bulk Content Toolkit lets you pick any custom meta field from your post types and expose it directly in the native WordPress **Quick Edit** row and **Bulk Edit** panel.

**How it works**

1. Go to **Settings → Bulk Edit**.
2. Pick a post type.
3. Move the fields you want to edit into the "Selected Fields" column.
4. Save — those fields now appear in Quick Edit and Bulk Edit for that post type.

**Supported field types**

The plugin auto-detects the correct input type (checkbox, select, text, number, datetime, email, URL, color, range, textarea) from your existing data, with a built-in override list for common WooCommerce and LearnPress meta keys.

**Compatibility**

- All registered public post types (posts, pages, WooCommerce products, LearnPress courses, custom types, etc.)
- WordPress 5.0+ / PHP 7.4+

== Installation ==

1. Upload the plugin folder to `/wp-content/plugins/` or install via **Plugins → Add New**.
2. Activate the plugin.
3. Go to **Settings → Bulk Edit** and configure your fields.

== Frequently Asked Questions ==

= Does this plugin work with custom post types? =
Yes, all registered public post types are supported.

= Can I undo bulk actions? =
The plugin does not store undo history. Use the Bulk Edit panel carefully, or combine with a revision/backup plugin.

= Is there a limit to how many fields I can add? =
No hard limit, but adding many fields will add columns to the list view. We recommend selecting only the fields you actively need to bulk-edit.

= How are field types detected? =
On first save, the plugin samples existing meta values and infers the type (checkbox, select, number, etc.). Common WooCommerce and LearnPress fields are hardcoded to the correct type. If a field type is wrong, remove it from the settings and re-add it after adding more data.

== Changelog ==

= 1.3.0 =
**Security**
- Replaced all wp_redirect() with wp_safe_redirect() + exit (5 occurrences).
- Added isset() + wp_unslash() on all $_POST/$_REQUEST accesses before sanitization.
- Removed unauthenticated (nopriv) AJAX hook on get_bulk_edit_statuses — meta values should not be exposed to unauthenticated users.
- Fixed SQL injection risk: $table_name now passed through esc_sql() before interpolation in direct DB queries.
- Added explicit nonce verification on the inline-save (quick edit) path in save_post hook.
- Fixed missing nonce check on bulk edit save — phpcs:disable with justification (WP core verifies the bulk-edit form nonce upstream).

**Bug fixes**
- Fixed fatal PHP error in checkbox type detection: missing $ on $found_values variable name — checkbox auto-detection for 0/1 pairs was completely broken.
- Fixed double-prefix bug on checkbox field key in bulk save: custom_custom_{field} was never found, so unchecking a field in bulk edit had no effect.
- Fixed checkbox bulk edit model: hidden input and checkbox no longer share the same name attribute. The hidden carries the real submitted value (yes/no/indeterminate); the checkbox is visual-only and drives the hidden via JS.
- Fixed select "no change" logic: empty string now correctly skips the field in PHP for all select fields, without depending on a change_field_key that does not exist for selects.
- Fixed date() replaced with gmdate() (8 occurrences) to avoid runtime timezone drift.
- Fixed i18n placeholders in _n() call: %s, %s → %1$s, %2$s, with missing translators comment added.
- Fixed output escaping on $count in bulk action admin notice.

**Improvements**
- Bulk edit mixed state: checkbox now initializes to indeterminate (orange, centered knob) when selected posts have different values. Clicking the switch exits indeterminate and commits a value. Saving without touching it leaves all posts unchanged.
- Bulk edit mixed state: select fields initialize to empty "— No change —" when values differ. Saving without changing the select leaves all posts unchanged.
- Bulk edit mixed state: text/number/datetime/etc fields leave the companion "Change to" select at "— No change —" when values differ, preventing accidental overwrites.
- Quick edit: all field types (text, number, datetime-local, email, url, color, textarea) now pre-populate with the post's current value when the row opens. Previously only checkbox and select were pre-populated.
- Bulk edit JS now uses MutationObserver on #the-list instead of a fragile 500ms setTimeout.
- Column display: checkbox fields now show a dashicon instead of "Yes"/"No" text. Dates use the WP date format from Settings. Long text fields are truncated at 40 characters. URLs display domain only.
- Columns now respect Screen Options: each BCT column can be individually shown/hidden per user via the native WP Screen Options panel (top-right of the list view).
- 14 near-identical HTML generator functions merged into one generic bulkedittoolkit_generate_input_html() — ~200 lines removed.
- All script/style enqueues now pass BULKEDITTOOLKIT_VERSION for proper browser cache busting.
- Settings cache added via wp_cache_get/set (5 min TTL) — the settings table is no longer queried on every page load.
- Direct DB queries now use esc_sql() on table names and $wpdb->prepare() with %s placeholders where applicable.

**Field type system**
- New file includes/utils/field-types.php centralizes two registries: known_types (hardcoded map of well-known meta keys to their correct input type) and blocked_keys (WP/plugin internals that must never appear in the field picker, including _edit_last, _edit_lock, _wp_page_template, etc.).
- Type sampler rewritten: now uses an 80% vote threshold across sampled values instead of first-match. A single outlier value no longer causes an entire field to be mistyped.
- Checkbox detection now requires values to form a known pair (yes/no, 1/0, true/false, on/off) — no longer confused by fields that happen to have two distinct values.
- Field discovery query rewritten: single SQL JOIN with DISTINCT instead of WP_Query loop over all posts — dramatically faster on large catalogs.

**i18n**
- All hardcoded French strings replaced with English base strings wrapped in __() / esc_html_e().
- JS feedback strings (save confirmation, error messages) now passed through wp_localize_script() instead of being hardcoded in French in the JS file.
- All die() direct-access guards updated to standard English: die('No direct access.').
- French code comments translated to English throughout.

= 1.2.9 =
- Bumped: compatibility with WordPress 6.9.

= 1.2.8 =
- Fixed: select tag gathered with wrong method in bulk edit.

= 1.2.7 =
- Fixed: select and switch definitively handled in bulk edit.

= 1.2.5 =
- Fixed: CSS was overstepping its own scope due to a bad condition.

= 1.2.4 =
- Fixed: "No Change" option for select fields in bulk edit now correctly prevents unintended updates.

= 1.2.3 =
- Reverted: new user JS was not an improvement.

= 1.2.2 =
- Improved: clearer bulk edit interface.

= 1.2.1 =
- Fixed: select fields now require at least two values to display.

= 1.2 =
- New: Known Types System for WooCommerce and LearnPress fields.
- New: Reset and Rebuild field types from settings.
- Improved: AJAX handling for Quick Edit.
- Fixed: input type detection edge cases.

= 1.1 =
- Improved: styling for switches and select fields.
- Fixed: rendering of select fields in quick edit.

= 1.0.5 =
- Verified compatibility with WordPress 6.8.1.

= 1.0.0 =
- Initial release.

== Upgrade Notice ==

= 1.3.0 =
Security release. Update immediately. Fixes unsafe redirect, SQL injection risk, missing nonce checks, and a fatal PHP error in field type detection.
