Skip to content

Instantly share code, notes, and snippets.

@damiencarbery
Last active January 8, 2025 16:30
Show Gist options
  • Save damiencarbery/269059efe6d7be230ab3682ffa087031 to your computer and use it in GitHub Desktop.
Save damiencarbery/269059efe6d7be230ab3682ffa087031 to your computer and use it in GitHub Desktop.
WooCommerce - Schedule showing and hiding of products - Add the ability to show and hide a product on specified dates. https://www.damiencarbery.com/2025/01/schedule-showing-and-hiding-of-woocommerce-products/
<?php
/*
Plugin Name: WooCommerce - Schedule showing and hiding of products
Plugin URI: https://www.damiencarbery.com/2025/01/schedule-showing-and-hiding-of-woocommerce-products/
Description: Add the ability to show and hide a product on specified dates.
Author: Damien Carbery
Author URI: https://www.damiencarbery.com
Version: 0.1.20250108
Requires Plugins: woocommerce
*/
class ScheduleHideProducts {
private $show_hide_dates_start_key;
private $show_hide_dates_end_key;
// Returns an instance of this class.
public static function get_instance() {
if ( null == self::$instance ) {
self::$instance = new self;
}
return self::$instance;
}
// Initialize the plugin variables.
public function __construct() {
// The custom meta key for the start and end dates.
$this->show_hide_dates_start_key = '_show_hide_dates_start';
$this->show_hide_dates_end_key = '_show_hide_dates_end';
$this->init();
}
// Set up WordPress specfic actions.
public function init() {
// Declare that this plugin supports WooCommerce HPOS.
// This plugin does not interact with WooCommerce orders so it doesn't have to do anything special to support HPOS.
add_action( 'before_woocommerce_init', function() {
if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
}
} );
// Add the date fields to the Product/General tab.
add_action( 'woocommerce_product_options_pricing', array( $this, 'add_hide_date' ) );
// Save the date fields values if set.
add_action( 'woocommerce_process_product_meta', array( $this, 'save_show_hide_dates' ), 10, 2 );
// Scheduled check for products to show or hide.
add_action( 'woocommerce_scheduled_sales', array( $this, 'scheduled_show_hide_check' ) );
// For debugging.
add_shortcode( 'show_hide', array( $this, 'show_hide_shortcode' ) );
}
public function show_hide_shortcode() {
$product_id = 32;
$visibility_terms = $this->get_visibility_terms( $product_id );
echo '<pre>', var_export( $visibility_terms, true ), '</pre>';
array_push( $visibility_terms, 'exclude-from-catalog', 'exclude-from-search' );
echo '<pre>', var_export( $visibility_terms, true ), '</pre>';
}
// Get the terms without the 'exclude-from-catalog' or 'exclude-from-search' terms.
private function get_visibility_terms( $product_id ) {
// Get the term slugs.
$visibility_terms = wp_list_pluck( wp_get_object_terms( $product_id, 'product_visibility' ), 'slug' );
// Remove 'exclude-from-catalog' or 'exclude-from-search' terms, if present.
$visibility_terms = array_filter( $visibility_terms, function( $x ) { return $x != 'exclude-from-catalog' && $x != 'exclude-from-search'; } );
return $visibility_terms;
}
// Add the date fields to the Product/General tab.
public function add_hide_date() {
$post_id = get_the_ID();
$product_type = WC_Product_Factory::get_product_type( $post_id );
$classname = WC_Product_Factory::get_product_classname( $post_id, $product_type ? $product_type : 'simple' );
$product = new $classname( $post_id );
$show_hide_dates_start_timestamp = $product->get_meta( $this->show_hide_dates_start_key, true, 'edit' );
$show_hide_dates_end_timestamp = $product->get_meta( $this->show_hide_dates_end_key, true, 'edit' );
// Convert the timestamp into Y-m-d format.
$show_hide_dates_start = $show_hide_dates_start_timestamp ? date_i18n( 'Y-m-d', $show_hide_dates_start_timestamp ) : '';
$show_hide_dates_end = $show_hide_dates_end_timestamp ? date_i18n( 'Y-m-d', $show_hide_dates_end_timestamp ) : '';
// Attach DatePicker to the two fields.
echo "
<script>
jQuery( function ( $ ) {
$( '.show_hide_dates_fields' ).each( function () {
$( this )
.find( 'input' )
.datepicker( {
defaultDate: '',
dateFormat: 'yy-mm-dd',
numberOfMonths: 1,
showButtonPanel: true,
} );
} );
$( '#woocommerce-product-data' ).on(
'click',
'.cancel_show_hide_schedule',
function () {
var wrap = $( this ).closest( 'div, table' );
//$( this ).hide(); // Hide the 'Cancel' link.
wrap.find( '.show_hide_dates_fields' ).find( 'input' ).val( '' );
return false;
}
);
} );
</script>
";
echo '
<style>
.woocommerce_options_panel .show_hide_dates_fields .short:first-of-type { margin-bottom: 1em; }
.woocommerce_options_panel .show_hide_dates_fields .short:nth-of-type(2) { clear: left; }
</style>
<p class="form-field show_hide_dates_fields">
<label for="'.$this->show_hide_dates_start_key.'">' . esc_html__( 'Show/hide dates', 'woocommerce' ) . '</label>
<input type="text" class="short" name="'.$this->show_hide_dates_start_key.'" id="'.$this->show_hide_dates_start_key.'" value="' . esc_attr( $show_hide_dates_start ) . '" placeholder="' . esc_html( _x( 'Starting&hellip;', 'placeholder', 'woocommerce' ) ) . ' YYYY-MM-DD" maxlength="10" pattern="' . esc_attr( apply_filters( 'woocommerce_date_input_html_pattern', '[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])' ) ) . '" />
<input type="text" class="short" name="'.$this->show_hide_dates_end_key.'" id="'.$this->show_hide_dates_end_key.'" value="' . esc_attr( $show_hide_dates_end ) . '" placeholder="' . esc_html( _x( 'Ending&hellip;', 'placeholder', 'woocommerce' ) ) . ' YYYY-MM-DD" maxlength="10" pattern="' . esc_attr( apply_filters( 'woocommerce_date_input_html_pattern', '[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])' ) ) . '" />' .
'<a href="#" class="description cancel_show_hide_schedule">' . esc_html__( 'Cancel', 'woocommerce' ) . '</a>' . wc_help_tip( __( 'The product will be shown at 00:00:00 of "Starting" date and hidden at 23:59:59 of "Ending" date.', 'woocommerce' ) ) . '
</p>';
}
// Save the date fields values if set.
public function save_show_hide_dates( $post_id, $post ) {
$product_type = empty( $_POST['product-type'] ) ? WC_Product_Factory::get_product_type( $post_id ) : sanitize_title( wp_unslash( $_POST['product-type'] ) );
$classname = WC_Product_Factory::get_product_classname( $post_id, $product_type ? $product_type : 'simple' );
$product = new $classname( $post_id );
// Handle show/hide dates.
$show_hide_dates_start = '';
$show_hide_dates_end = '';
// Force 'date from' to beginning of day.
if ( isset( $_POST['_show_hide_dates_start'] ) ) {
$show_hide_dates_start = wc_clean( wp_unslash( $_POST['_show_hide_dates_start'] ) );
if ( ! empty( $show_hide_dates_start ) ) {
// ToDo: Maybe use code from set_date_prop() in woocommerce/includes/abstracts/abstract-wc-data.php.
$show_hide_dates_start = new WC_DateTime( date( 'Y-m-d 00:00:00', strtotime( $show_hide_dates_start ) ), new DateTimeZone( 'UTC' ) );
// ToDo: If $show_hide_dates_start is in the future I should hide the product now! (set product_visibility terms)?
}
}
// Force 'date to' to the end of the day.
if ( isset( $_POST['_show_hide_dates_end'] ) ) {
$show_hide_dates_end = wc_clean( wp_unslash( $_POST['_show_hide_dates_end'] ) );
if ( ! empty( $show_hide_dates_end ) ) {
// ToDo: Maybe use code from set_date_prop() in woocommerce/includes/abstracts/abstract-wc-data.php.
$show_hide_dates_end = new WC_DateTime( date( 'Y-m-d 00:00:00', strtotime( $show_hide_dates_end ) ), new DateTimeZone( 'UTC' ) );
}
}
$product->update_meta_data( $this->show_hide_dates_start_key, $show_hide_dates_start ? $show_hide_dates_start->getTimestamp() : $show_hide_dates_start );
$product->update_meta_data( $this->show_hide_dates_end_key, $show_hide_dates_end ? $show_hide_dates_end->getTimestamp() : $show_hide_dates_end );
$product->save();
}
// Returns an array of IDs of products that will be shown soon.
public function get_starting_shows() {
global $wpdb;
return $wpdb->get_col(
$wpdb->prepare(
"SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta
WHERE postmeta.meta_key = '%s'
AND postmeta.meta_value > 0
AND postmeta.meta_value < %s",
$this->show_hide_dates_start_key, time()
)
);
}
// Returns an array of IDs of products that are due to be hidden.
public function get_ending_shows() {
global $wpdb;
return $wpdb->get_col(
$wpdb->prepare(
"SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta
WHERE postmeta.meta_key = '%s'
AND postmeta.meta_value > 0
AND postmeta.meta_value < %s",
$this->show_hide_dates_end_key, time()
)
);
}
// Scheduled check for products to show or hide.
public function scheduled_show_hide_check() {
// Get products to be shown (or published).
$product_ids = $this->get_starting_shows();
if ( $product_ids ) {
$visibility_ids = wc_get_product_visibility_term_ids();
foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id );
if ( $product ) {
$visibility_terms = $this->get_visibility_terms( $product_id );
wp_set_object_terms( $product_id, $visibility_terms, 'product_visibility' );
// Delete the start date so we don't keep changing the product_visibility terms.
$product->delete_meta_data( $this->show_hide_dates_start_key );
$product->save();
}
}
}
// Get products to be hidden (or unpublished).
$product_ids = $this->get_ending_shows();
if ( $product_ids ) {
foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id );
if ( $product ) {
// Get the terms without the 'exclude-from-catalog' or 'exclude-from-search' terms.
$visibility_terms = $this->get_visibility_terms( $product_id );
// Add 'exclude-from-catalog' and 'exclude-from-search' terms so that the product will be hidden.
array_push( $visibility_terms, 'exclude-from-catalog', 'exclude-from-search' );
// Hide product by adding 'exclude-from-catalog' and 'exclude-from-search' terms.
wp_set_object_terms( $product_id, $visibility_terms, 'product_visibility' );
// Delete the end date so we don't keep hiding this product.
$product->delete_meta_data( $this->show_hide_dates_end_key );
$product->save();
}
}
}
}
}
$ScheduleHideProducts = new ScheduleHideProducts();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment