-
-
Save damiencarbery/dfc45a9ceb52fa336749b363a6e7dd51 to your computer and use it in GitHub Desktop.
<?php | |
/* | |
Plugin Name: Defer WooCommerce emails for a few minutes (use Action Scheduler) | |
Plugin URI: https://www.damiencarbery.com/2020/04/defer-woocommerce-emails-for-a-few-minutes/ | |
Description: Defer WooCommerce emails for a specified time after the normal delivery time. Use Action Scheduler instead of WP Cron. Call do_action() instead of remove_action() and calling trigger. | |
Author: Damien Carbery | |
Author URI: https://www.damiencarbery.com | |
Version: 0.8 | |
*/ | |
class DeferSendingWooCommerceEmails { | |
private static $instance; | |
private $default_defer_time; | |
// An associative array to match $email_id with email class, to allow for the deferring of different emails. | |
private $email_id_to_defer; | |
// Returns an instance of this class. | |
public static function get_instance() { | |
if ( null == self::$instance ) { | |
self::$instance = new DeferSendingWooCommerceEmails(); | |
} | |
return self::$instance; | |
} | |
// Initialize the plugin variables. | |
public function __construct() { | |
$this->default_defer_time = 600; // Defer for 600 seconds (10 minutes). | |
// Can add other email IDs and their defer times. | |
// You can uncomment the add_action() with 'order_status_changed' function to find out the before and after status. | |
// This is also the list of emails that will be deferred. | |
$this->email_id_to_defer = array( | |
'woocommerce_new_customer_note' => $this->default_defer_time, | |
'woocommerce_order_status_completed' => $this->default_defer_time, | |
// Probably don't want to defer these emails but they are shown | |
// here as a demo of a different defer time. | |
//'woocommerce_order_status_pending_to_on-hold' => $this->default_defer_time, // New order | |
//'woocommerce_order_status_pending_to_processing' => $this->default_defer_time, | |
// Additional transitition-to-email class info from @djm56 | |
//'woocommerce_order_status_on-hold' => $this->default_defer_time, // Order on hold. | |
//'woocommerce_order_status_pending_to_on-hold' => $this->default_defer_time, // Order on hold. | |
//'woocommerce_order_status_cancelled_to_on-hold' => $this->default_defer_time, // Order on hold. | |
//'woocommerce_order_status_pending_to_on-hold' => $this->default_defer_time, // Order on hold. | |
); | |
$this->init(); | |
} | |
// Set up WordPress specfic actions. | |
public function init() { | |
// Set all WooCommerce emails to be deferred. | |
add_filter( 'woocommerce_defer_transactional_emails', '__return_true' ); | |
// Allow most emails to be sent as normal but prevent emails listed in $this->email_id_to_defer. Schedule them for a time in the future. | |
add_filter( 'woocommerce_allow_send_queued_transactional_email', array( $this, 'whether_send_queued_wc_email' ), 10, 3 ); | |
// This is the scheduled function that will send the email. | |
add_action( 'send_deferred_woocommerce_email', array( $this, 'send_deferred_woocommerce_email' ), 10, 2 ); | |
// DEBUG: Add the order modification time and current time to prove that the email was intentionally delayed. | |
add_action( 'woocommerce_email_order_details', array( $this, 'add_defer_length_info_to_order_email' ), 5, 4 ); | |
// Uncomment this to log the before and after statuses so you know what ones | |
// to add to the $this->email_id_to_defer array. | |
//add_action('woocommerce_order_status_changed', array( $this, 'order_status_changed' ), 10, 4); | |
} | |
// Log the before and after status to discover which one to add to $this->email_id_to_defer array. | |
public function order_status_changed( $id, $from, $to, $order ) { | |
error_log( 'Order ID: ' . $id ); | |
error_log( 'Status from: ' . $from ); | |
error_log( 'Status to: ' . $to ); | |
} | |
private function get_email_defer_time( $filter ) { | |
if ( array_key_exists( $filter, $this->email_id_to_defer ) ) { | |
return $this->email_id_to_defer[ $filter ]; | |
} | |
return $this->default_defer_time; | |
} | |
public function whether_send_queued_wc_email( $true, $filter, $args ) { | |
//error_log( 'woocommerce_allow_send_queued_transactional_email $filter: ' . var_export( $filter, true ) ); | |
//error_log( 'woocommerce_allow_send_queued_transactional_email order_number: ' . var_export( $args[ 0 ], true ) ); | |
if ( array_key_exists( $filter, $this->email_id_to_defer ) ) { | |
// TODO: Consider verifying that $args[0] is a valid order number. | |
//$order = wc_get_order( $args[ 0 ] ); | |
//$action_args = array( 'filter' => $filter, 'args' => $args ); | |
// Call Action Scheduler instead of WP Cron. Args will be filter and $args. | |
$event_id = as_schedule_single_action( time() + $this->get_email_defer_time( $filter ), 'send_deferred_woocommerce_email', array( $filter, $args ) ); | |
//$order_num = $args[ 0 ]; | |
//error_log( sprintf( 'woocommerce_allow_send_queued_transactional_email: Defer a %s email for order: %s for %d seconds (event ID: %d).', $filter, $order_num, $this->get_email_defer_time( $filter ), $event_id ) ); | |
return false; | |
} | |
//error_log( 'woocommerce_allow_send_queued_transactional_email: Ok to send email.' ); | |
return $true; | |
} | |
// Send the deferred email for order $order_id. | |
public function send_deferred_woocommerce_email( $filter, $args = array() ) { | |
//error_log( 'send_deferred_woocommerce_email for order: ' . $order_id ); | |
// Use same code as WC_Emails::send_queued_transactional_email() in woocommerce/includes/class-wc-emails.php | |
WC_Emails::instance(); | |
// Ensure gateways are loaded in case they need to insert data into the emails. | |
WC()->payment_gateways(); | |
WC()->shipping(); | |
do_action_ref_array( $filter . '_notification', $args ); | |
} | |
// This is an experimental function to add the date/time the order was modified and | |
// the date/time the email was sent into email - to demonstrate that the deferring code worked. | |
public function add_defer_length_info_to_order_email( $order, $sent_to_admin, $plain_text, $email ) { | |
if ( 'customer_completed_order' == $email->id ) { | |
if ( $plain_text ) { | |
printf( '%sThe order was modified at %s and email sent at %s.', "\n", $order->get_date_modified(), current_time( 'mysql' ) ); | |
} | |
else { | |
printf( '<p>The order was modified at <strong>%s</strong> and email sent at <strong>%s</strong>.</p>', $order->get_date_modified(), current_time( 'mysql' ) ); | |
} | |
} | |
} | |
} | |
$DeferSendingWooCommerceEmails = new DeferSendingWooCommerceEmails(); |
@djm56 - Thanks for the additional code. I have updated the gist to include these. I'm delighted that you found it useful - it took me a long while to figure it all out.
Thanks for sharing your gist! This helped me a ton!
Has anyone experienced "random" execution success of this script? Sometimes event is scheduled and sometimes not. Any ideas?
Has anyone experienced "random" execution success of this script? Sometimes event is scheduled and sometimes not. Any ideas?
I suggest uncommenting the error_log calls so that you can see whether the script is running as expected.
To see whether the event is scheduled I use Advanced Cron Manager.
Has anyone experienced "random" execution success of this script? Sometimes event is scheduled and sometimes not. Any ideas?
Maybe I should look into using Action Scheduler. WooCommerce uses it extensively and it can log events.
Will this work for registration notification?
Will this work for registration notification?
If that plugin uses the WooCommerce email system then it could be defer those emails by adding the email ID to the$this->email_id_to_defer
array.
Where could I download that plugin to experiment?
I have solved mine the problem! I commented out the hooks for debugging, because these actions are missing in the desired email. Message ID is"woocommerce_created_customer".
I have solved mine the problem! I commented out the hooks for debugging, because these actions are missing in the desired email. Message ID is"woocommerce_created_customer".
Did you add 'woocommerce_created_customer
' to the$this->email_id_to_defer
array?
Thanks for this code I was desperately looking for something like this you saved my life. I needed it for on-hold mails. Here is the code I used so it pulls the correct email.
'woocommerce_order_status_on-hold' => array('WC_Email_Customer_On_Hold_Order', $this->default_defer_time), 'woocommerce_order_status_pending_to_on-hold' => array('WC_Email_Customer_On_Hold_Order', $this->default_defer_time), // New order 'woocommerce_order_status_cancelled_to_on-hold' => array('WC_Email_Customer_On_Hold_Order', $this->default_defer_time), // New order 'woocommerce_order_status_pending_to_on-hold' => array('WC_Email_Customer_On_Hold_Order', $this->default_defer_time), // New order