=== Bulkify — Retailer & Bulk Pricing for WooCommerce ===
Contributors:      cropmarkcreatives
Tags:              woocommerce, bulk-pricing, wholesale, tiered-pricing, retailer
Requires at least: 6.0
Tested up to:      7.0
Stable tag:        1.8.0
Requires PHP:      7.4
License:           GPLv2 or later
License URI:       https://www.gnu.org/licenses/gpl-2.0.html

Tiered bulk and retailer pricing for WooCommerce. Set quantity thresholds with custom prices per product, variation, or a global discount.

== Description ==

**Bulkify** lets you reward customers who buy in bulk by automatically applying a lower "retailer" price once they reach a quantity threshold.

**Key Features**

* **Multiple tiers per product** — set as many quantity/price rows as you need (e.g. 1–4 = regular, 5–9 = £8.00, 10+ = £6.50)
* **Per-variation support** — each variation of a variable product has its own independent tier table
* **Global fallback discount** — set a site-wide percentage discount and minimum quantity that applies to products without per-product tiers
* **Live pricing table** — a clear tier table on the single product page; the active tier row highlights automatically as the customer changes quantity
* **Elementor compatible** — use the `[bulkify_pricing_table]` shortcode anywhere in your Elementor layout, or drop it in any HTML/Shortcode widget
* **Shop/archive display** — compact tier pill row beneath each product card showing all tiers at a glance
* **"You save" column** — shows the percentage saved vs the regular price on each tier row
* **Secure** — nonces, capability checks, `wp_unslash`, `map_deep` sanitization, `wp_kses_post` escaping, and POST key allowlisting on all inputs
* **HPOS compatible** — declared compatible with WooCommerce High-Performance Order Storage
* **Clean uninstall** — removing the plugin deletes all meta and option data from the database

**Shortcode Usage**

Place anywhere (Elementor, Gutenberg, classic editor):

`[bulkify_pricing_table]`

On a non-product page or in a widget, pass the product ID explicitly:

`[bulkify_pricing_table product_id="42"]`

**Customise Colours**

Add to your theme's Additional CSS (Appearance → Customise → Additional CSS):

`
:root {
    --bulkify-accent:       #your-brand-colour;
    --bulkify-accent-text:  #ffffff;
    --bulkify-active-bg:    #edf7ef;
    --bulkify-active-text:  #1a6b30;
    --bulkify-badge-bg:     #your-brand-colour;
}
`

== Installation ==

1. Download the plugin zip file.
2. In your WordPress admin go to **Plugins → Add New → Upload Plugin**.
3. Upload the zip and click **Install Now**, then **Activate**.
4. Go to **WooCommerce → Bulkify** to configure the global settings.
5. On any product edit page open the **Pricing** tab and click **+ Add Tier** to add your quantity/price rows.

**Manual installation**

1. Upload the `bulkify` folder to `/wp-content/plugins/`.
2. Activate the plugin from the **Plugins** screen.

== Frequently Asked Questions ==

= Does this work with variable products? =

Yes. Each variation panel in the product edit screen has its own tier table. The pricing table on the frontend swaps automatically when the customer selects a variation.

= What happens if a product has no tiers set? =

The global fallback discount (WooCommerce → Bulkify) applies if enabled. If that is also disabled, standard WooCommerce pricing is used unchanged.

= Will it work inside my Elementor single product template? =

Yes. Add an HTML or Shortcode widget to your Elementor template and paste `[bulkify_pricing_table]`. The plugin also hooks into `woocommerce_after_add_to_cart_button` as an automatic Elementor fallback.

= Does the retailer price apply at checkout? =

Yes. The tier price is applied during cart recalculation via `woocommerce_before_calculate_totals`, so it appears correctly at checkout, in order emails, and on the order record.

= Is my data removed when I delete the plugin? =

Yes. Deleting the plugin runs `uninstall.php` which removes all `_bulkify_tiers` post meta and the `bulkify_global_settings` option from your database.

= Can I use this on a multisite installation? =

The plugin has not been tested on multisite. It is designed for standard single-site installs.

== Screenshots ==

1. WooCommerce → Bulkify global settings — configure site-wide fallback discount, minimum quantity, price label and badge text.
2. Product edit page — Retailer Pricing Tiers section in the Pricing tab, ready to add tiers.
3. Product edit page — three tiers added (buy 5+, 10+, 20+) each with a custom retailer price.
4. Single product page — full pricing table showing all tiers with quantity ranges and "You save" percentage column.
5. Single product page — customer changes quantity to 11; the matching tier row (10–19) highlights automatically in green.
6. Single product page — quantity set to 20; the 20+ tier row is highlighted showing the best available price.

== Developers ==

Bulkify exposes ten filters so you can customise its behaviour from your theme or another plugin — no core file edits required.

**Pricing filters** (in `Bulkify_Pricing`)

`bulkify_get_tiers` — Modify the saved tier rows for any product or variation.

`add_filter( 'bulkify_get_tiers', function( $tiers, $product_id ) {`
`    // Add a tier programmatically for products in category 12.`
`    if ( has_term( 12, 'product_cat', $product_id ) ) {`
`        $tiers[] = [ 'min_qty' => 50, 'price' => '5.00' ];`
`    }`
`    return $tiers;`
`}, 10, 2 );`

`bulkify_display_tiers` — Modify tiers just before the pricing table or archive pills render. Return an empty array to hide the table entirely.

`bulkify_tier_price` — Override the retailer price applied to a specific cart item.

`add_filter( 'bulkify_tier_price', function( $price, $product_id, $qty, $item ) {`
`    // Apply an extra 5 % for logged-out users.`
`    if ( ! is_user_logged_in() && null !== $price ) {`
`        $price *= 0.95;`
`    }`
`    return $price;`
`}, 10, 4 );`

`bulkify_apply_cart_price` — Return `false` to prevent Bulkify from touching a specific cart item.

`add_filter( 'bulkify_apply_cart_price', function( $apply, $item, $key ) {`
`    // Skip items that have a custom meta flag.`
`    return empty( $item['my_no_bulk'] );`
`}, 10, 3 );`

**Display filters** (in `Bulkify_Display`)

`bulkify_show_pricing_table` — Return `false` to hide the single-product pricing table.

`bulkify_table_html` — Modify the full pricing table HTML string before output.

`add_filter( 'bulkify_table_html', function( $html, $product ) {`
`    return '<div class="my-wrap">' . $html . '</div>';`
`}, 10, 2 );`

`bulkify_show_archive_tiers` — Return `false` to hide tier pills on a specific shop card.

`bulkify_archive_tiers_html` — Modify the archive tier pills HTML before output.

`bulkify_show_badge` — Return `false` to hide the bulk-deal badge on a shop card.

`bulkify_shortcode_output` — Modify the HTML returned by `[bulkify_pricing_table]`.

`add_filter( 'bulkify_shortcode_output', function( $html, $product ) {`
`    return '<h3>' . esc_html__( 'Bulk Deals', 'my-theme' ) . '</h3>' . $html;`
`}, 10, 2 );`

== Changelog ==

= 1.8.0 =
* Developer: added 10 documented filter hooks across Bulkify_Pricing and Bulkify_Display — `bulkify_get_tiers`, `bulkify_display_tiers`, `bulkify_tier_price`, `bulkify_apply_cart_price`, `bulkify_show_pricing_table`, `bulkify_table_html`, `bulkify_show_archive_tiers`, `bulkify_archive_tiers_html`, `bulkify_show_badge`, `bulkify_shortcode_output`.
* Added `== Developers ==` section to readme.txt with usage examples for all hooks.

= 1.7.0 =
* Compliance: shortened plugin header Description to under 140 characters.
* Security: added `wp_unslash()` on nonce field reads before `sanitize_key()` in both save hooks.
* Fix: `render_table_html()` now accepts an explicit `$product` parameter — fixes savings column in shortcode context when `product_id` attribute is used.
* Fix: savings percentage now output via `esc_html( sprintf( ... ) )` — fully PHPCS-traceable, no variable echo.
* Added translator comment on savings percentage string.
* readme.txt: corrected format to WordPress.org specification (short description line, Contributors slug, single-word tags, Stable tag synced).

= 1.6.0 =
* Added `[bulkify_pricing_table]` shortcode for Elementor and block editor compatibility.
* Added secondary hook on `woocommerce_after_add_to_cart_button` as Elementor fallback.
* Added static `$rendered` flag to prevent duplicate table output.
* Archive cards now show all tiers as compact pill rows (was: single lowest-tier hint).
* Added "You save" percentage column to the pricing table.
* Full CSS redesign using CSS custom properties for easy brand customisation.

= 1.5.0 =
* Removed hidden `languages/.gitkeep` file.
* Updated "Tested up to" to WordPress 7.0.
* Added `map_deep( wp_unslash(), 'sanitize_text_field' )` on POST tier arrays.

= 1.4.0 =
* Added GPL-2.0+ license header.
* Removed deprecated `load_plugin_textdomain()` call.
* Added `wp_unslash()` on all `$_POST` reads before sanitization.
* Fixed `EscapeOutput` flag — `wp_kses_post()` called inline on ob buffer.
* Added `readme.txt` and `languages/` folder.

= 1.3.0 =
* Fixed fatal parse error — smart apostrophe in settings description string.

= 1.2.0 =
* Fixed dead `$wrap` variable in `frontend.js buildTable()`.
* Added `data-label` attribute to variable product placeholder div.
* Added `uninstall.php` for clean database removal.

= 1.1.0 =
* Renamed plugin to Bulkify.
* Fixed critical bug — `sanitize_tiers()` array structure (tiers now save correctly).
* Added `current_user_can()` capability checks on all save hooks.
* Added explicit nonce output and verification on product meta saves.
* Added `wp_unslash`/`sanitize_text_field` on all price inputs.
* Added POST key allowlist and `MAX_TIERS = 20` DoS cap.

= 1.0.0 =
* Initial release.

== Upgrade Notice ==

= 1.8.0 =
Developer release: adds 10 filter hooks so themes and plugins can extend Bulkify without editing core files. No behaviour changes for existing stores.

= 1.7.0 =
WordPress.org submission compliance update. Recommended for all users — includes security hardening and a savings column fix for shortcode usage.

= 1.6.0 =
Major update: Elementor shortcode support, all tiers visible on shop cards, redesigned table with brand colour variables.
