Simple Abandoned Cart
https://github.com/gregbast1994/msp-abandoned-cart
This plugin simply skims emails at checkout and saves the cart session data. Then we set a cron job to check if the session became an order, if not set another for tomorrow to email the user. Simple abandoned cart functionality.
I've checked the list of cron jobs and can see the 'await_order_for_cart_..' job. I've also dumped the $wp_filter to check if I can find my function connected to a hook anywhere. I've tried making the functions attached to the job static.
The job dumped looks like
[1566572607] => Array
(
[await_order_for_cart_04ff0471d799d19b53034ef078a600d8] => Array
(
[e0f32ed5d8b7b8d89f1d8f832df788f2] => Array
(
[schedule] =>
[args] => Array
(
[hash] => 04ff0471d799d19b53034ef078a600d8
[item_count] => 1
[customer_id] => aeac13344550563f2be4186690b44ef5
)
)
)
)
First setup the first job to check if the session becomes an order.
public function get_data( $email = '' )
{
...
$this->set_wait_for_order_cron();
}
public function set_wait_for_order_cron()
/**
* Creates a unique hook using cart hash, schedules a check on cart
*/
{
if( empty( $this->cart['hash'] ) ) return;
$hook = 'await_order_for_cart_' . $this->cart['hash'];
$this->hooks['before'] = $hook;
$one_hour = time() + 30;
if( ! wp_next_scheduled( $hook ) ){
wp_schedule_single_event( $one_hour, $hook, $this->cart );
add_action( $hook, array( $this, 'check_order_for_cart' ) );
}
}
I do not think the check_order_for_cart ever runs because I never see the 'email_customer_abandoned_cart_...' job.
public static function get_session( $customer_id )
{
$handler = new WC_Session_Handler();
return $handler->get_session( $customer_id );
}
public static function check_order_for_cart( $cart )
{
/**
* Splits up the session cookie, uses the first part to grab the cart session
* If session is false, that means the cart expired or was order.
*/
$session = $this->get_session( $cart['customer_id'] );
if( false !== $session ){
$hook = 'email_customer_abandoned_cart_' . $cart['hash'];
$this->hooks['after'] = $hook;
wp_schedule_single_event( time() + DAY_IN_SECONDS, $hook, $cart );
add_action( $hook, array( $this, 'email_customer_abandoned_cart' ) );
}
// kill action
remove_action( $this->hooks['before'], array( $this, 'check_order_for_cart' ) );
}
Well, I expect that after the first job runs, the second job is created and in the cron queue. I expect when that second job runs that my email is sent and my customer comes back and buys lots of dildo's from me.
Related
I am having difficulty doing this and have checked over previous questions however they do not seem to be working.
So far i have disabled the default wordpress cron by adding below to my wp-config.php:
define('DISABLE_WP_CRON', true);
Then i have attempted to schedule my task to run every 5 mins from within my plugins main php file:
function my_cron_schedules($schedules){
if(!isset($schedules["5min"])){
$schedules["5min"] = array(
'interval' => 5*60,
'display' => __('Once every 5 minutes'));
}
if(!isset($schedules["30min"])){
$schedules["30min"] = array(
'interval' => 30*60,
'display' => __('Once every 30 minutes'));
}
return $schedules;
}
add_filter('cron_schedules','my_cron_schedules');
function schedule_my_cron(){
wp_schedule_event(time(), '5min', 'fivemin_schedule_hook');
}
if(!wp_get_schedule('fivemin_schedule_hook')){
add_action('init', 'schedule_my_cron',10);
}
function fivemin_schedule_hook() {
get_feed();
}
So the above appears to be scheduling my event within database however have 100's of entries when checking cron schedule with:
<?php print_r(get_option('cron')); ?>
I have also made sure to update my crontab with below:
* * * * * wget -q -O - http://wordpress.com/wp-cron.php?doing_wp_cron
However my task does not appear to be running and am concerned about the amount of entries within database for this 5min job.
Each entry looks like below:
Array ( [1524308364] => Array ( [fivemin_schedule_hook] => Array ( [40cd750bba9870f18aada2478b24840a] => Array ( [schedule] => 5min [args] => Array ( ) [interval] => 300 ) ) )
I have tried debugging wp-cron.php by echoing out the $hook when it tries to fire and my hook is shown when i visit wp-cron.php directly. The actual function however just does not seem to fire.
Try this:
Replace this:
function schedule_my_cron(){
wp_schedule_event(time(), '5min', 'fivemin_schedule_hook');
}
if(!wp_get_schedule('fivemin_schedule_hook')){
add_action('init', 'schedule_my_cron',10);
}
..with this:
function schedule_my_cron(){
// Schedules the event if it's NOT already scheduled.
if ( ! wp_next_scheduled ( 'my_5min_event' ) ) {
wp_schedule_event( time(), '5min', 'my_5min_event' );
}
}
// Registers and schedules the my_5min_event cron event.
add_action( 'init', 'schedule_my_cron' );
// Runs fivemin_schedule_hook() function every 5 minutes.
add_action( 'my_5min_event', 'fivemin_schedule_hook' );
//add_action( 'my_5min_event', 'another_function_to_call' );
//add_action( 'my_5min_event', 'another_function_to_call2' );
But a more appropriate/preferred way is to add this in the activation function for your plugin:
wp_schedule_event( time(), '5min', 'my_5min_event' );
Example:
register_activation_hook( __FILE__, 'my_plugin_activation' );
function my_plugin_activation() {
if ( ! wp_next_scheduled ( 'my_5min_event' ) ) {
wp_schedule_event( time(), '5min', 'my_5min_event' );
}
}
..which would be used in place of the following:
function schedule_my_cron(){
// Schedules the event if it's NOT already scheduled.
if ( ! wp_next_scheduled ( 'my_5min_event' ) ) {
wp_schedule_event( time(), '5min', 'my_5min_event' );
}
}
// Registers and schedules the my_5min_event cron event.
add_action( 'init', 'schedule_my_cron' );
And add this somewhere in the deactivation function for your plugin:
wp_clear_scheduled_hook( 'my_5min_event' );
Example:
register_deactivation_hook( __FILE__, 'my_plugin_deactivation' );
function my_plugin_deactivation() {
wp_clear_scheduled_hook( 'my_5min_event' );
}
See https://codex.wordpress.org/Function_Reference/wp_schedule_event#Examples for more details.
I have created a new payment gateway plugin that allows customers to pay later. I will be using it with a woocommerce POS system.
I cannot get the 'customer invoice' email to send automatically - I believe it has something to do with the "pending" status of the order (which it must have in order to allow the customer to pay later). I have analysed many other answers but none have worked for me.
I have gently modified the code from this link (only - changed names and set up as plugin): https://github.com/creativelittledots/woocommerce-pay-later.
It uses a series of functions to trigger the email that I don't fully understand - but it doesn't seem to work: The default status is set to "on- hold" to allow the emails to trigger then the order is set to "pending" to allow the customer to pay for the order.
Here are the relevant snippets:
add_filter( 'woocommerce_default_order_status', array($this, 'default_order_status') );
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
add_filter( 'woocommerce_email_format_string_find', array($this, 'order_status_format_string_find') );
add_filter( 'woocommerce_email_format_string_replace', array($this, 'order_status_format_string_replace'), 10, 2 );
add_action( 'woocommerce_order_status_pending', array($this, 'send_pending_order_emails') );
add_filter( 'woocommerce_valid_order_statuses_for_payment', array($this, 'valid_order_statuses_for_payment' ), 10, 2 );
add_action( 'wp', array($this, 'change_order_to_pending_on_order_received'), 8 );`
public function valid_order_statuses_for_payment($statuses, $order) {
if( $order->is_pay_later ) {
$statuses[] = 'on-hold';
}
return $statuses;
}
/**
* Change the default order status to on-hold so that pending order emails can be triggered
*/
public function default_order_status($default) {
if( ! is_admin() && WC()->session->set( 'chosen_payment_method') == $this->id ) {
$default = 'on-hold';
}
return $default;
}
/**
* Allow Order status to be accessible from emails
*/
public function order_status_format_string_find( $find ) {
$find['order-status'] = '{order_status}';
return $find;
}
/**
* Replace Order status in emails
*/
public function order_status_format_string_replace( $replace, $email ) {
if( $email->object ) {
$replace['order-status'] = wc_get_order_status_name( $email->object->get_status() );
}
return $replace;
}
/**
* Trigger pending order emails and invoice email
*/
public function send_pending_order_emails( $order_id ) {
$emails = new WC_Emails();
$order = wc_get_order( $order_id );
$emails->customer_invoice( $order_id );
$emails->emails['WC_Email_New_Order']->trigger( $order_id );
$order->set_payment_method( $this );
}
/**
* WC Shop As Customer support on Order Received, because the default status is on hold we need to change these orders to pending
*/
public function change_order_to_pending_on_order_received() {
if( class_exists('WC_Shop_As_Customer') && ! empty( $_GET['order_on_behalf'] ) && ! empty( $_GET['key'] ) && ! empty( $_GET['send_invoice'] ) ) {
global $wp;
if ( ! isset( $wp->query_vars['order-received'] ) )
return;
// Bail if we're not shopping-as - don't display the special interface.
if ( ! WC_Shop_As_Customer::get_original_user() )
return;
$order_id = $wp->query_vars['order-received'];
if ( ! empty( $order_id ) ) {
$order = new WC_Order( absint( $order_id) );
$order->update_status( 'pending' );
}
unset( $_GET['send_invoice'] );
}
}
I also read that woocommerce_order_status_pending may not trigger emails so I have tried adding this code (no successes):
add_filter( 'woocommerce_email_actions', 'filter_woocommerce_email_actions' );
function filter_woocommerce_email_actions( $actions ){
$actions[] = 'woocommerce_order_status_pending';
return $actions;
}
I suspect either:
The code to put the order 'on hold' then trigger then back to 'pending' is not set up correctly
woocommerce_order_status_pending hook does not trigger emails
I have a little work around to trigger from the "woocommerce_thankyou" hook in functions.php but I believe that this should be done in the gateway (plus I don't want to rely on woocommerce_thankyou - this email should be based on order creation).
Thanks in advance!!!!!!
I want to know if there is a action hook that can check if the subscription is successfully renewed in woocommerce ? BTW I am using woocommerce subscription plugin. I have created a functionality that records the date of the subscription order and add it to a CSV file, the function is working perfectly for the first purchase I mean when the user purchase a subscription it is recorded successfully in the CSV because I am firing up the function on woocommerce_thankyou action hook, The only issue I am facing is that I can't seem to find a hook which can execute this function on successful subscription renewal. I tried to use woocommerce_subscription_renewal_payment_complete action hook but it didn't worked below is the function that I have created.
/**
* Add subscriptions to csv.
*/
add_action( 'woocommerce_subscription_renewal_payment_complete', 'add_subs_to_csv' );
add_action( 'woocommerce_thankyou', 'add_subs_to_csv' );
function add_subs_to_csv( $order_id ) {
$order = wc_get_order( $order_id );
$items = $order->get_items();
foreach ( $items as $key => $value ) {
$meta_values = $value->get_data();
foreach ( $meta_values as $meta_key => $meta_value ) {
if ( $meta_key == 'product_id' && $meta_value == 875 ) {
$paid_date = explode( " ", get_post_meta( $order_id, '_paid_date', true ) );
$subs_paid_date = date( 'd F, Y', strtotime( $paid_date[0] ) );
wc_add_order_item_meta( $key, 'Delivery Date', $subs_paid_date );
}
}
}
}
Could the wcs_renewal_order_created hook be what you're looking for? The docs say:
WooCommerce Subscriptions stores all details of each subscription
renewal in a standard WooCommerce order, only with a special meta flag
linking it to a subscription.
These orders are always created through the wcs_create_renewal_order()
function, regardless of whether they are created for a scheduled
renewal event, manually via the WooCommerce > Edit Subscription
administration screen, or via the Subscriptions endpoints for the
WooCommerce REST API. Because of this, it’s possible to add, remove or
update the value of anything on that renewal order using this filter.
For example, this can be used to add a discount to specific renewal
orders, like the 12th order each year. It could also be used to add
one-time fee for a certain renewal order, like a special annual extra
fee on a monthly subscription.
So the above hook should trigger after payment, you'd probably just need to check if it was completed status which you could also do in your current hooks:
/**
* After WooCommerce Subscriptions Creates Renewal Order
*
* #param WC_Order Object $order
* #param Integer|WC_Subscription Object $subscription
*
* #return WC_Order $order
*/
function add_subs_to_csv( $order, $subscription ) {
if( 'completed' === $order->get_status() ) {
$items = $order->get_items();
foreach ( $items as $key => $value ) {
$meta_values = $value->get_data();
foreach ( $meta_values as $meta_key => $meta_value ) {
if ( $meta_key == 'product_id' && $meta_value == 875 ) {
$paid_date = explode( " ", get_post_meta( $order_id, '_paid_date', true ) );
$subs_paid_date = date( 'd F, Y', strtotime( $paid_date[0] ) );
wc_add_order_item_meta( $key, 'Delivery Date', $subs_paid_date );
}
}
}
}
return $order
}
add_filter( 'wcs_renewal_order_created', 'add_subs_to_csv', 10, 2 );
I had a issue at Subscription renewal and I got it fixed with below code:
/*
* FIXED : Membership got PAUSED everytime at automatic subscription renewal
*/
function change_membership_status_active( $subscription , $order ) {
global $wpdb;
if( 'completed' === $order->get_status() ) {
$membership = $wpdb->get_row( "SELECT * FROM wp_postmeta WHERE meta_key = '_subscription_id' AND meta_value = $subscription->ID" );
$mem_id = $membership->post_id;
$status = 'wcm-active';
$update_args = array( 'ID' => $mem_id, 'post_status' => $status );
wp_update_post($update_args);
}
}
add_action( 'woocommerce_subscription_renewal_payment_complete', 'change_membership_status_active', 10, 2 );
I am using woocommerce product vendors plugin and in single product page there is sold by . I want to replace this with ;
It is added like
class WC_Product_Vendors_Vendor_Frontend {
public static function init() {
$self = new self();
add_action( 'woocommerce_single_product_summary', array( $self, 'add_sold_by_single' ), 39 );
return true;
}
public function add_sold_by_cart( $values, $cart_item ) {
$sold_by = get_option( 'wcpv_vendor_settings_display_show_by', 'yes' );
if ( 'yes' === $sold_by ) {
$sold_by = WC_Product_Vendors_Utils::get_sold_by_link( $cart_item['data']->id );
$values[] = array(
'name' => apply_filters( 'wcpv_sold_by_text', esc_html__( 'Sold By', 'woocommerce-product-vendors' ) ),
'display' => '<em class="wcpv-sold-by-cart">' . $sold_by['name'] . '</em>',
);
}
return $values;
}
}
WC_Product_Vendors_Vendor_Frontend::init();
I tried to unhook it like
1)
add_action( 'wp_head', 'my_remove_actions', 39 );
function my_remove_actions(){
remove_action( 'woocommerce_single_product_summary', array( 'WC_Product_Vendors_Vendor_Frontend', 'add_sold_by_single' ), 39 );
}
2)
add_action( 'wp_head', 'my_remove_actions', 39 );
function my_remove_actions(){
global $wc_product_vendors_vendor_frontend;
$wc_product_vendors_vendor_frontend = new WC_Product_Vendors_Vendor_Frontend();
remove_action( 'woocommerce_single_product_summary', array( $wc_product_vendors_vendor_frontend, 'add_sold_by_single' ), 39 );
}
3)
class XWC_Product_Vendors_Vendor_Frontend extends WC_Product_Vendors_Vendor_Frontend{
public function __construct(){}
}
add_action( 'wp_head', 'my_remove_actions', 39 );
function my_remove_actions(){
global $xwc_product_vendors_vendor_frontend;
$xwc_product_vendors_vendor_frontend = new XWC_Product_Vendors_Vendor_Frontend();
remove_action( 'woocommerce_single_product_summary', array( $xwc_product_vendors_vendor_frontend, 'add_sold_by_single' ), 39 );
}
4)
class XWC_Product_Vendors_Vendor_Frontend extends WC_Product_Vendors_Vendor_Frontend{
public function __construct() {
remove_action( 'woocommerce_single_product_summary', array( $this, 'add_sold_by_single' ), 39 );
}
}
new XWC_Product_Vendors_Vendor_Frontend();
5)
class XWC_Product_Vendors_Vendor_Frontend extends WC_Product_Vendors_Vendor_Frontend{
public static function init() {
$self = new self();
remove_action( 'woocommerce_single_product_summary', array( $self, 'add_sold_by_single' ), 39 );
}
}
XWC_Product_Vendors_Vendor_Frontend::init();
But could not remove this ! Please guide me to do this.
Update
I got output for
print_r($wp_filter['woocommerce_single_product_summary']);
Array
(
[10] => Array
(
[woocommerce_template_single_price] => Array
(
[function] => woocommerce_template_single_price
[accepted_args] => 1
)
)
[20] => Array
(
[woocommerce_template_single_excerpt] => Array
(
[function] => woocommerce_template_single_excerpt
[accepted_args] => 1
)
)
[40] => Array
(
[woocommerce_template_single_meta] => Array
(
[function] => woocommerce_template_single_meta
[accepted_args] => 1
)
)
[50] => Array
(
[woocommerce_template_single_sharing] => Array
(
[function] => woocommerce_template_single_sharing
[accepted_args] => 1
)
)
[30] => Array
(
[woocommerce_template_single_add_to_cart] => Array
(
[function] => woocommerce_template_single_add_to_cart
[accepted_args] => 1
)
)
[39] => Array
(
[00000000262d19ef000000007f4708faadd_sold_by_single] => Array
(
[function] => Array
(
[0] => WC_Product_Vendors_Vendor_Frontend Object
(
)
[1] => add_sold_by_single
)
[accepted_args] => 1
)
[le_child_add_sold_by_single] => Array
(
[function] => le_child_add_sold_by_single
[accepted_args] => 1
)
)
)
Why are the solutions not working for you?
In order to unregister a callback, you have to get the instance of the object. The problem with this plugin is: it's not using a Singleton properly by giving you the means to grab the instance. Therefore, you can't get the instance. Boo.
Work Around
We need a workaround. Let me explain to help you in the future.
WordPress Core stores all of the registered callbacks with their events' registry. When you do add_action or add_filter, the callback is being stored in this event registry table (which is a large multi-dimensional array). In order to unregister (or remove) a callback that is a method within a specific object (and not a static method), you have to have the instance of the object. That means you need the variable that represents the object you want to target.
Here in this example, you don't have that directly. But WordPress stores the key to the method using it's object hash ID concatenated with the method name. That pattern we can use to fetch the record (element) in the event registry table.
/**
* #EXPLANATION#
* The plugin loads the file using `plugins_loaded` with a priority of 0. Here
* we are doing the same thing but after the file is loaded, meaning after the
* events are registered.
*/
add_action( 'plugins_loaded', 'remove_woocommerce_add_sold_by_single_callback', 1 );
/**
* Unregister the WooCommerce `WC_Product_Vendors_Vendor_Frontend::add_sold_by_single` from the
* event `woocommerce_single_product_summary`.
*
* #EXPLANATION#
* I'm adding comments in the code only to illustrate what is happening and why. Please
* remove the inline comments when using this code. Thank you.
*
* #since 1.0.0
*
* #return void
*/
function remove_woocommerce_add_sold_by_single_callback() {
/**
* #EXPLANATION#
* WordPress keeps an event registry table for the callbacks that
* are pre-registered to each event name. The format of the table is:
*
* $wp_filter[ event name ][ priority number ][ callback name ]
*
* The registry table is a global variable called $wp_filter. You need to include
* that global into this function's scope.
*/
global $wp_filter;
/**
* #EXPLANATION#
* Let's make sure that the event name has a callback registered to the priority
* number of 39. If no, then return early (bail out).
*/
if ( ! isset( $wp_filter['woocommerce_single_product_summary']['39'] ) ) {
return;
}
/**
* #EXPLANATION#
* We will loop through each of the registered callbacks for the priority of 39. Why?
* You can't assume that only that one function is registered at 39. There may be more.
* Therefore, let's loop.
*
* We are grabbing the callback function that is registered. This is a unique key. For
* objects, WordPress uses the PHP construct spl_object_hash() to grab its hash ID. Then it
* concatenates it together with the method's name. You are not going to know what the unique
* ID is. Why? The first 32 characters are the hash ID, which is a memory location and not
* relative to the human readable name of `WC_Product_Vendors_Vendor_Frontend`. Rather, it's
* related to its object.
*/
foreach( $wp_filter['woocommerce_single_product_summary']['39'] as $callback_function => $registration ) {
/**
* #EXPLANATION#
* Give that we don't know what the first 32 characters of the object's hash ID is, we need
* to check if the callback function includes the method we are looking for. This line
* of code says: "Hey, do you contain the method name `add_sold_by_single` starting at
* the 32's character." If yes, then this is the one we want.
*/
if ( strpos( $callback_function, 'add_sold_by_single', 32) !== false) {
/**
* #EXPLANATION#
* Now we have the actual callback function key that is registered in the
* event registry. We can use remove_action to unregister it.
*/
remove_action( 'woocommerce_single_product_summary', $callback_function, 39 );
break;
}
}
}
Essentially what this code is doing is this:
Checks if anything is already registered to the event name and priority number. If no, there's nothing to do. Let's bail out.
We need to get the unique ID, or as WordPress calls it, the $idx. This is the callback's unique key, which is comprised of the hash ID . method name.
Once we have that unique ID, then you can unregister the callback using remove_action. It works for remove_filter too.
Reusable Code
The code above was to illustrate how to do and how it works. But that code is not reusable, since the parameters are hard coded. Instead, you will want to use this code and then call it like this (when in a plugin):
add_action( 'plugins_loaded', 'remove_woocommerce_add_sold_by_single_callback', 1 );
/**
* Unregister the WooCommerce `WC_Product_Vendors_Vendor_Frontend::add_sold_by_single` from the
* event `woocommerce_single_product_summary`.
*
* #since 1.0.0
*
* #return void
*/
function remove_woocommerce_add_sold_by_single_callback() {
do_hard_unregister_object_callback( 'woocommerce_single_product_summary', 39, 'add_sold_by_single');
}
Which Event?
If you are using this code in a plugin (which you should be), use plugins_loaded.
If you put it into your theme, you can't use plugins_loaded because it already fired before the theme loaded. For a theme, you need to use after_setup_theme instead of plugins_loaded.
To Test
To test, let's dump out the registry before and after to see if it did remove it. Do the following:
function remove_woocommerce_add_sold_by_single_callback() {
global $wp_filter;
var_dump( $wp_filter['woocommerce_single_product_summary'][39] );
do_hard_unregister_object_callback( 'woocommerce_single_product_summary', 39, 'add_sold_by_single');
if ( ! isset( $wp_filter['woocommerce_single_product_summary'][39] ) ) {
var_dump( $wp_filter['woocommerce_single_product_summary'] );
} else {
var_dump( $wp_filter['woocommerce_single_product_summary'][39] );
}
}
The results will let us see if it was there to start and gone after.
Try this:
add_action( 'woocommerce_before_single_product', 'my_remove_actions' );
function my_remove_actions(){
remove_action( 'woocommerce_single_product_summary', array( 'WC_Product_Vendors_Vendor_Frontend', 'add_sold_by_single' ), 39 );
}
Show all hooked functions for this filter:
add_action( 'woocommerce_before_single_product', 'lets_have_a_look' );
function lets_have_a_look(){
global $wp_filter;
print_r($wp_filter['woocommerce_single_product_summary']);
}
Or you can just do it by javascript.
add_action( 'woocommerce_archive_description', "move_author_summary_to_image");
/**
* Move the author summary right after the image so we can float them left and right.
*/
function move_author_summary_to_image() {
?>
<script>
$('document').ready (function () {
var summary = $("#isle_body .wcpv-vendor-profile.entry-summary").html();
$("#isle_body .wcpv-vendor-profile.entry-summary").hide();
$(summary).addClass("author_summary").insertAfter($("#isle_body p.wcpv-vendor-logo"));
});
</script>
<?php
}
I have managed to send users an email when a post is published using transition_post_status, but it does not send when the post is updated. I have tried using 'new' with both old_status and new_status but no luck. Any direction is greatly appreciated. My reference thus far is from https://wordpress.stackexchange.com/questions/100644/how-to-auto-send-email-when-publishing-a-custom-post-type?rq=1
add_action('transition_post_status', 'send_media_emails', 10, 3);
function send_media_emails($new_status, $old_status, $post){
if ( 'publish' !== $new_status or 'publish' === $old_status)
return;
$the_media = get_users( array ( 'role' => 'media' ) );
$emails = array ();
foreach($the_media as $media)
$emails[] = $media->user_email;
$body = sprintf('There are new bus cancellations or delays in Huron-Perth <%s>', get_permalink($post));
wp_mail($emails, 'New Bus Cancellation or Delay', $body);
}
//Working but sending double emails now. I tried wrapping it all in the function but that didn't work either. just confused as to where to put it.
function send_media_emails($post_id){
$the_media = get_users( array ( 'role' => 'media' ) );
$emails = array ();
if( ! ( wp_is_post_revision( $post_id) && wp_is_post_autosave( $post_id ) ) ) {
return;
}
if(get_post_status($post_id) == 'draft' or get_post_status($post_id) == 'pending' or get_post_status($post_id) == 'trash'){
return;
}
foreach($the_media as $media){
$emails = $media->user_email;
}
$body = sprintf('There are new bus cancellations or delays in Huron-Perth <%s>', get_permalink($post_id));
wp_mail($emails, 'New Bus Cancellation or Delay', $body);
}
add_action('post_updated', 'send_media_emails');
The transition_post_status hook only fires when the post status changes, e.g. from 'Draft' to 'Publish'.
A better hook would be post_updated. This fires on all updates, so you'll need to filter out updates to drafts and comments in your script.
You might be able to do a 'publish_to_publish' action, but I haven't tested this personally.
transition_post_status will also fire when a post is updated (publish > publish for example).
However, a known bug makes transition_post_status sometimes fire twice: https://github.com/WordPress/gutenberg/issues/15094
There's a solution for the double firing of transition_post_status, which I copy from there:
function ghi15094_my_updater( $new_status, $old_status, $post ) {
// run your code but do NOT count on $_POST data being available here!
}
function ghi15094_transition_action( $new_status, $old_status, $post ) {
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
ghi15094_my_updater( $new_status, $old_status, $post );
set_transient( 'my_updater_flag', 'done', 10 );
} else {
if ( false === get_transient( 'my_updater_flag' ) ) {
ghi15094_my_updater( $new_status, $old_status, $post );
}
}
}
add_action( 'transition_post_status', 'ghi15094_transition_action', 10, 3 );