Hooks and integration
The public function, actions, filters and price history table.
The plugin keeps a price history of every product and variation and shows the EU "Lowest price in the last 30 days" label while a product is on sale. This page is the contract for other plugins that want to read that history, keep their own, or show their own label. Everything named here is public and stays backwards compatible within 1.x.
All names use the bcrs_cc_ prefix. The plugin defines no global classes or functions without it, hooks in at default priorities, and never removes another plugin's hooks.
Reading the lowest price
$lowest = function_exists( 'bcrs_cc_get_lowest_price' ) ? bcrs_cc_get_lowest_price( $product_id, 30 ) : null;
bcrs_cc_get_lowest_price( int $product_id, int $days = 30 ): ?float
- The lowest active price (what customers paid) in the
$daysbefore the product's current price took effect. The current price never counts. An earlier, cheaper price inside the window does, including an earlier sale or campaign, and so does the price in effect when the window opened. - Pass a variation ID for variable products. A variable product's own ID has no history.
- The price is as stored, as entered in the shop (with or without tax, per WooCommerce's settings). Use
wc_get_price_to_display()to show it. nullmeans unknown: the history does not reach back before the current price (for example, a product that was already on sale when the plugin was activated), or WooCommerce is inactive.- The result passes through the
bcrs_cc_lowest_pricefilter.
Actions
bcrs_cc_price_recorded
Fires after each recorded change.
add_action( 'bcrs_cc_price_recorded', function ( int $product_id, array $row ) {
// $row: id, product_id, regular_price, sale_price, price (floats or null when empty),
// campaign_id (0 unless a campaign or quick change made the change),
// source ("save" or "backfill"), recorded_at (UTC, "Y-m-d H:i:s").
}, 10, 2 );
A row is recorded on every product save (woocommerce_after_product_object_save) whose regular, sale or active price differs from the last row. This covers the product editor, quick and bulk edit, CSV import, the REST API, WooCommerce's scheduled sales starting and ending, and the plugin's own campaigns and quick changes. It does not cover direct SQL writes that bypass WooCommerce.
Filters
bcrs_cc_price_history_enabled
bool, default true. Return false to stop recording, when your plugin keeps the history. The label then no longer uses the built-in history either, so also supply the value through bcrs_cc_lowest_price, or the label will not show.
add_filter( 'bcrs_cc_price_history_enabled', '__return_false' );
bcrs_cc_lowest_price
apply_filters( 'bcrs_cc_lowest_price', ?float $lowest, int $product_id, int $days )
Supplies or corrects the lowest price that the label and bcrs_cc_get_lowest_price() use. Return a number, or null for unknown.
bcrs_cc_lowest_price_display
apply_filters( 'bcrs_cc_lowest_price_display', string $label, WC_Product $product, float $lowest, string $context )
The label HTML appended after the price. $context is product (the product page's own product, or one of its variations) or list (shop, category, related products, blocks). Return '' to hide it, or different HTML to replace it.
// A plugin that shows its own notice hides this one.
add_filter( 'bcrs_cc_lowest_price_display', '__return_empty_string' );
bcrs_cc_lowest_price_days
int, default 30. How many days the label looks back.
bcrs_cc_price_history_retention_days
int, default 365, at least 60. How long rows are kept. A daily Action Scheduler action (bcrs_cc_price_prune, group campaign-center) deletes older rows, keeping for each product the last row before the cutoff: the price in effect when the kept history begins.
Other filters
| Filter | What it changes |
|---|---|
Pro bcrs_cc_report_order_statuses |
The order statuses the sales report counts (default Processing and Completed) |
bcrs_cc_batch_max_items |
Products per background batch (default 500) |
bcrs_cc_batch_time_limit |
Seconds per background batch (default about 10) |
Pro bcrs_cc_notification_message |
A campaign notification before it is sent (subject, lines, link); return false to send nothing |
Pro bcrs_cc_notification_channels |
Where notifications go (email, slack); add an object implementing BrainCraftRs\CampaignCenter\Notifications\Channel for another service |
Pro bcrs_cc_missed_run_grace_seconds |
How overdue a start or end is before "did not run on time" is sent (default 900) |
bcrs_cc_cron_stale_seconds |
Seconds without a scheduled-task run before the Scheduled tasks card and the notification settings warn (default 3600, at least 600) |
The action bcrs_cc_campaign_event fires with ( $event, $campaign_id, $context ) when a campaign is started, ended, reverted or did_not_run.
Storefront Pro
The sale badge, the countdown and the Campaign products block are described in Sale badge, countdown and campaign products.
bcrs_cc_sale_badge_text
Filter. Receives the badge wording as escaped HTML, the WC_Product, and the sale (campaign_id, name, end_at as a UTC DateTimeImmutable or null). Return different HTML to change it, or an empty string to keep the theme's own badge for that product.
bcrs_cc_countdown_html
Filter. Receives the countdown HTML, the WC_Product, the sale, and the context (product for the product page's own product, list elsewhere). Return an empty string to hide it. Your own markup keeps ticking if it has a data-end attribute (Unix time) and a .bcrs-cc-countdown__time child.
bcrs_cc_storefront_campaign_products
Filter. Receives the product IDs the Campaign products block or shortcode lists (most recently put on sale first) and its attributes (campaign, limit, columns, orderby, order). Return a different list to change it.
Never shown twice
The label is added through woocommerce_get_price_html at priority 20, after most themes and plugins. It is skipped when the price HTML already contains the label, or contains the word omnibus (case-insensitive), meaning another plugin already added an Omnibus notice there. A plugin that wants to be detected this way only needs omnibus in a class name of its notice; otherwise use bcrs_cc_lowest_price_display.
Storage
Table {$wpdb->prefix}bcrs_cc_price_history:
| Column | Type | Notes |
|---|---|---|
id |
bigint unsigned, auto increment | |
product_id |
bigint unsigned | Product or variation ID |
regular_price |
decimal(19,4) NULL | NULL when empty |
sale_price |
decimal(19,4) NULL | NULL when empty |
price |
decimal(19,4) NULL | Active price (_price) |
campaign_id |
bigint unsigned, default 0 | Campaign or quick change ID, 0 for any other change |
source |
varchar(20) | save or backfill |
recorded_at |
datetime | UTC |
Indexes: product_time (product_id, recorded_at) and recorded_at. On activation and on upgrade, products and variations with no history get one backfill row with their current prices. Read the table directly only if the function and filters above do not cover your case: its columns may grow, but the ones listed will not change meaning within 1.x. Deleting a product deletes its rows.
Settings
The Lowest price settings are stored in bcrs_cc_lowest_price_mode (auto, the default, is on when the store's base country is in the EU; yes; no), bcrs_cc_lowest_price_where (product or everywhere) and bcrs_cc_lowest_price_text (empty for the translated default; {price} and {days} placeholders).