Skip to content

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 $days before 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.
  • null means 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_price filter.

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).