Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- /**
- * Scheduled currency price sync for WooCommerce products.
- *
- * Runs on a recurring WP-Cron schedule (default: once per day) and updates
- * the price of every product — simple, variable (per variation), external,
- * grouped, etc. — that carries both the '_origin_product_currency' and
- * '_origin_product_price' meta keys, converting the stored origin price
- * into the shop's current currency via WOOGC_Currency_Converter::convert().
- *
- * Products/variations missing EITHER meta key are skipped and the loop
- * continues to the next one.
- *
- * USAGE:
- * Paste this into a custom/mu-plugin file, or into your theme's
- * functions.php. Requires the plugin that provides WOOGC_Currency_Converter
- * to be active.
- */
- if ( ! defined( 'ABSPATH' ) ) {
- exit;
- }
- define( 'CUSTOM_PRICE_SYNC_HOOK', 'custom_price_sync_cron_hook' );
- add_filter( 'woogc/ps/synchronize_product/origin_product/meta_data', 'custom_woogc_add_site_currency', 10, 1 );
- function custom_woogc_add_site_currency( $args )
- {
- $product = wc_get_product( $args[ 'origin_product_id' ] );
- $args['product_meta']['_origin_product_currency'][] = get_option( 'woocommerce_currency' );
- $args['product_meta']['_origin_product_price'][] = $product->get_price();
- return $args;
- }
- /**
- *
- * Change the interval via the 'custom_price_sync_interval_seconds' filter,
- * e.g. add_filter( 'custom_price_sync_interval_seconds', fn() => HOUR_IN_SECONDS * 6 );
- *
- * NOTE: if you change this after the event is already scheduled, run
- * custom_price_sync_unschedule_event() once (or deactivate/reactivate)
- * so WordPress picks up the new interval — WP doesn't reschedule an
- * already-queued event automatically.
- */
- add_filter( 'cron_schedules', 'custom_price_sync_register_interval' );
- function custom_price_sync_register_interval( $schedules ) {
- $schedules['custom_price_sync_interval'] = array(
- 'interval' => apply_filters( 'custom_price_sync_interval_seconds', DAY_IN_SECONDS ),
- 'display' => __( 'Custom Price Sync Interval', 'custom-price-sync' ),
- );
- return $schedules;
- }
- /**
- * Schedule the event if it isn't already scheduled.
- */
- add_action( 'init', 'custom_price_sync_schedule_event' );
- function custom_price_sync_schedule_event() {
- if ( ! wp_next_scheduled( CUSTOM_PRICE_SYNC_HOOK ) ) {
- wp_schedule_event( time(), 'custom_price_sync_interval', CUSTOM_PRICE_SYNC_HOOK );
- }
- }
- /**
- * Unschedule helper — call this from a plugin deactivation hook if you turn
- * this into its own plugin file:
- * register_deactivation_hook( __FILE__, 'custom_price_sync_unschedule_event' );
- */
- function custom_price_sync_unschedule_event() {
- $timestamp = wp_next_scheduled( CUSTOM_PRICE_SYNC_HOOK );
- if ( $timestamp ) {
- wp_unschedule_event( $timestamp, CUSTOM_PRICE_SYNC_HOOK );
- }
- }
- /**
- * The cron callback: loops through every product in batches and updates
- * prices for any product/variation carrying the origin currency/price meta.
- */
- add_action( CUSTOM_PRICE_SYNC_HOOK, 'custom_price_sync_run' );
- function custom_price_sync_run() {
- if ( ! class_exists( 'WOOGC_Currency_Converter' ) ) {
- custom_price_sync_log( 'WOOGC_Currency_Converter class not found. Aborting sync.' );
- return;
- }
- $batch_size = apply_filters( 'custom_price_sync_batch_size', 300 );
- $paged = 1;
- do {
- $query = new WP_Query( array(
- 'post_type' => 'product',
- 'post_status' => 'publish',
- 'posts_per_page' => $batch_size,
- 'paged' => $paged,
- 'fields' => 'ids',
- 'orderby' => 'ID',
- 'order' => 'ASC',
- 'no_found_rows' => true,
- 'update_post_meta_cache' => false,
- 'update_post_term_cache' => false,
- ) );
- if ( empty( $query->posts ) ) {
- break;
- }
- foreach ( $query->posts as $product_id ) {
- $product = wc_get_product( $product_id );
- if ( ! $product ) {
- continue;
- }
- if ( $product->is_type( 'variable' ) ) {
- $updated_any_variation = false;
- foreach ( $product->get_children() as $variation_id ) {
- $variation = wc_get_product( $variation_id );
- if ( ! $variation ) {
- continue;
- }
- if ( custom_price_sync_maybe_update_product( $variation ) ) {
- $updated_any_variation = true;
- }
- }
- // Refresh the parent's cached price range after variations change.
- if ( $updated_any_variation && class_exists( 'WC_Product_Variable' ) ) {
- WC_Product_Variable::sync( $product_id );
- wc_delete_product_transients( $product_id );
- }
- } else {
- // Simple, external, grouped, etc.
- custom_price_sync_maybe_update_product( $product );
- }
- }
- $paged++;
- } while ( true );
- custom_price_sync_log( 'Price sync run completed.' );
- }
- /**
- * Updates a single product/variation's price from its origin currency/price
- * meta, if both metas exist. Returns true if the price was updated.
- *
- * @param WC_Product $product
- * @return bool
- */
- function custom_price_sync_maybe_update_product( $product ) {
- $product_id = $product->get_id();
- // Skip products that don't have BOTH meta keys set (this is the correct
- // existence check — get_post_meta()/get_meta() return '' both when a
- // meta is missing AND when it's set to an empty string).
- if ( ! metadata_exists( 'post', $product_id, '_origin_product_currency' )
- || ! metadata_exists( 'post', $product_id, '_origin_product_price' ) ) {
- return false;
- }
- $origin_currency = $product->get_meta( '_origin_product_currency', true );
- $origin_price = $product->get_meta( '_origin_product_price', true );
- if ( '' === $origin_currency || '' === $origin_price || ! is_numeric( $origin_price ) ) {
- custom_price_sync_log( sprintf( 'Product #%d has invalid origin meta, skipping.', $product_id ) );
- return false;
- }
- $shop_currency = get_option( 'woocommerce_currency' );
- // Nothing to convert if origin currency already matches the shop currency.
- if ( $origin_currency === $shop_currency ) {
- $new_price = floatval( $origin_price );
- } else {
- $new_price = WOOGC_Currency_Converter::convert(
- floatval( $origin_price ),
- $origin_currency,
- $shop_currency
- );
- if ( false === $new_price ) {
- custom_price_sync_log( sprintf(
- 'Currency conversion failed for product #%d (%s -> %s), skipping.',
- $product_id,
- $origin_currency,
- $shop_currency
- ) );
- return false;
- }
- }
- $new_price = wc_format_decimal( $new_price, wc_get_price_decimals() );
- $old_regular_price = $product->get_regular_price();
- $old_sale_price = $product->get_sale_price();
- $product->set_regular_price( $new_price );
- // If there's no active sale price, keep the active price in sync too.
- // (If a sale price exists, we leave it as-is rather than guessing a new one.)
- if ( '' === $old_sale_price ) {
- $product->set_price( $new_price );
- }
- $product->save();
- custom_price_sync_log( sprintf(
- 'Product #%d price updated: %s %s -> %s %s (regular price %s -> %s).',
- $product_id,
- $origin_price,
- $origin_currency,
- $new_price,
- $shop_currency,
- $old_regular_price,
- $new_price
- ) );
- return true;
- }
- /**
- * Simple logging helper — writes to WooCommerce > Status > Logs
- * (source: custom-price-sync). Disable with:
- * add_filter( 'custom_price_sync_enable_logging', '__return_false' );
- */
- function custom_price_sync_log( $message ) {
- if ( ! apply_filters( 'custom_price_sync_enable_logging', true ) ) {
- return;
- }
- if ( function_exists( 'wc_get_logger' ) ) {
- wc_get_logger()->info( $message, array( 'source' => 'custom-price-sync' ) );
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment