admin管理员组

文章数量:1287598

I am trying to better understand how Wordpress deals with cron jobs so that I can better figure out how to create some custom jobs that send emails based on single occurrence events, like lesson started or lesson webinar started.

In my case, I want to have emails sent to users when I have certain content that becomes available. The availability is based on a date field that I have as custom meta for a custom post type.

So imagine a bunch of courses and lessons. When a lesson opens up, I want that event to trigger sending emails to users with access to the lesson. Likewise, when a lesson's webinar starts, I also want to trigger emails.

Right now, for the case of a lesson drip date, I react to a save_post_membrepressrule hook that passes $rule_id and schedule a cron job to send an email on the drip release date for the lesson. I do that in the hook as

    // save meta for lesson if this rule is 'single_sfwd-lessons' and has fixed drip
function my_save_post_membrepressrule_hook ($rule_id)
{    
    // bunch of checks first
   if ( (isset($_POST['_mepr_rules_type']) AND $_POST['_mepr_rules_type'] == 'single_sfwd-lessons')
        AND ( isset($_POST['_mepr_rules_drip_enabled']) AND $_POST['_mepr_rules_drip_enabled'] == 'on')
        AND ( isset($_POST['_mepr_rules_drip_after_fixed']) AND $_POST['_mepr_rules_drip_after_fixed'] != '')  ) 
   {
      // we have fixed drip date for a given lesson
      $lesid = $_POST['_mepr_rules_content'];
      $course_id = (int) learndash_get_course_id($lesid);

      // now set notification if rule is in future

      $lesdate = $_POST['_mepr_rules_drip_after_fixed'];
      $lesdate = new DateTime($lesdate, new DateTimeZone('UTC'));
      $current_date = new DateTime();
      $current_date->setTimeZone(new DateTimeZone( 'UTC' ));
      
      if ($lesdate > $current_date) 
      {      
          $notifs = get_notifications( 'lesson_started', $course_id, $lesson_id);
          if ( ! $notifs ) {
             return;
          }

          // what memberships are tied to this drip rule?
        
          global $wpdb;
          $memberships = $wpdb->get_results("SELECT access_condition FROM wp_mepr_rule_access_conditions WHERE access_type = 'membership' AND access_operator = 'is' AND rule_id = '$rule_id'");

          foreach ($memberships as $membership) {
              $membership_id = $membership->access_condition
              $wpdb->query("SET time_zone = 'UTC'");
              $activeusers = $wpdb->get_results("SELECT DISTINCT user_id FROM wp_mepr_transactions WHERE status IN('confirmed','complete') AND (expires_at >= NOW() OR expires_at = '0000-00-00 00:00:00') AND product_id = '$membership_id'");

              // likely just one notification post for a given notification type, but use loop just in case
              foreach ($notifs as $notif)
              {
                 foreach ($activeusers as $activeuser) 
                 {
                    if ((int) $activeuser->user_id != 0)
                    {
                       // delete what currently is set for these users for this lesson/membership combo
                    
                       delete_user_meta($activeuser->user_id, 'ld_sent_notification_lesson_started'.$notif->ID . '_' . $lesson_id . '_'. $membership_id);
                    }
                 }
                       
                 // see if we already have cron job to send email for this lesson drip, so we can remove in case we changed the drip date
                 if ( wp_next_scheduled( 'my_custom_crons', [ $notif->ID, 'lesson_started', $membership_id, $course_id, $lesson_id ] ) )             
                 {
                    wp_clear_scheduled_hook( 'my_custom_crons', [ $notif->ID, 'lesson_started', $membership_id, $course_id, $lesson_id ] );
                 }
                 wp_schedule_single_event( $lesdate->getTimeStamp(), 'my_custom_crons', [ $notif->ID, 'lesson_started', $membership_id, $course_id, $lesson_id ] );
              } // end notif loop
           } // end memberships loop
        } // if date in future
    } // if drip rule for lesson
}

So this sets up single event cron in WP named my_custom_crons. I have similar logic for a webinar_started event, as well as webinat_starts_in_onehour event for a given lesson.

To react to this hook, I have

add_action( 'my_custom_crons','my_custom_crons', 99, 5 );
function my_custom_crons( $notif, $notification_type, $membership_id, $course_id, $lesson_id) {

    global $wpdb;
    $wpdb->query("SET time_zone = 'UTC'");
    $activeusers = $wpdb->get_results("SELECT DISTINCT user_id FROM wp_mepr_transactions WHERE status IN('confirmed','complete') AND (expires_at >= NOW() OR expires_at = '0000-00-00 00:00:00') AND product_id = '$membership_id'");
    if ( empty( $activeusers ) ) {
        return;
    }

    // ok, we have users that are active
    foreach ( $activeusers as $activeuser ) {
        $user_id = absint( $activeuser->user_id );
        if ($user_id != 0) {
            // bunch of checking
            // get notification message for notification type (lessons tarted, or webinar started)
            // then send the emails

        }
    }

    // clear the job (but this not needed?)
    wp_clear_scheduled_hook('my_custom_crons', array(
                $notif->ID,
                $notification_type, 
                $course_id,
                $membership_id,
                $lesson_id,
    ));
}

All seems to be working in terms of cron being set up. If I run wp cron event list after adding a Membrepress Drip Rule for a lesson, I see the my_custom_crons job scheduled:

my_custom_crons| 2021-11-03 20:01:00 |16 hours 1 minute| Non-repeating

But what happens if I have multiple lessons and multiple events I want to use for email triggers, all with various dates? As mentioned, I might have lesson_started and lesson_webinar_started event for various lessons. So will my current approach work, or will various events for various lessons all triggered at different times, all using my one cron hook's logic, interfere with one another?

Just not seeing bigger picture of how I should be doing this...

Thanks

I am trying to better understand how Wordpress deals with cron jobs so that I can better figure out how to create some custom jobs that send emails based on single occurrence events, like lesson started or lesson webinar started.

In my case, I want to have emails sent to users when I have certain content that becomes available. The availability is based on a date field that I have as custom meta for a custom post type.

So imagine a bunch of courses and lessons. When a lesson opens up, I want that event to trigger sending emails to users with access to the lesson. Likewise, when a lesson's webinar starts, I also want to trigger emails.

Right now, for the case of a lesson drip date, I react to a save_post_membrepressrule hook that passes $rule_id and schedule a cron job to send an email on the drip release date for the lesson. I do that in the hook as

    // save meta for lesson if this rule is 'single_sfwd-lessons' and has fixed drip
function my_save_post_membrepressrule_hook ($rule_id)
{    
    // bunch of checks first
   if ( (isset($_POST['_mepr_rules_type']) AND $_POST['_mepr_rules_type'] == 'single_sfwd-lessons')
        AND ( isset($_POST['_mepr_rules_drip_enabled']) AND $_POST['_mepr_rules_drip_enabled'] == 'on')
        AND ( isset($_POST['_mepr_rules_drip_after_fixed']) AND $_POST['_mepr_rules_drip_after_fixed'] != '')  ) 
   {
      // we have fixed drip date for a given lesson
      $lesid = $_POST['_mepr_rules_content'];
      $course_id = (int) learndash_get_course_id($lesid);

      // now set notification if rule is in future

      $lesdate = $_POST['_mepr_rules_drip_after_fixed'];
      $lesdate = new DateTime($lesdate, new DateTimeZone('UTC'));
      $current_date = new DateTime();
      $current_date->setTimeZone(new DateTimeZone( 'UTC' ));
      
      if ($lesdate > $current_date) 
      {      
          $notifs = get_notifications( 'lesson_started', $course_id, $lesson_id);
          if ( ! $notifs ) {
             return;
          }

          // what memberships are tied to this drip rule?
        
          global $wpdb;
          $memberships = $wpdb->get_results("SELECT access_condition FROM wp_mepr_rule_access_conditions WHERE access_type = 'membership' AND access_operator = 'is' AND rule_id = '$rule_id'");

          foreach ($memberships as $membership) {
              $membership_id = $membership->access_condition
              $wpdb->query("SET time_zone = 'UTC'");
              $activeusers = $wpdb->get_results("SELECT DISTINCT user_id FROM wp_mepr_transactions WHERE status IN('confirmed','complete') AND (expires_at >= NOW() OR expires_at = '0000-00-00 00:00:00') AND product_id = '$membership_id'");

              // likely just one notification post for a given notification type, but use loop just in case
              foreach ($notifs as $notif)
              {
                 foreach ($activeusers as $activeuser) 
                 {
                    if ((int) $activeuser->user_id != 0)
                    {
                       // delete what currently is set for these users for this lesson/membership combo
                    
                       delete_user_meta($activeuser->user_id, 'ld_sent_notification_lesson_started'.$notif->ID . '_' . $lesson_id . '_'. $membership_id);
                    }
                 }
                       
                 // see if we already have cron job to send email for this lesson drip, so we can remove in case we changed the drip date
                 if ( wp_next_scheduled( 'my_custom_crons', [ $notif->ID, 'lesson_started', $membership_id, $course_id, $lesson_id ] ) )             
                 {
                    wp_clear_scheduled_hook( 'my_custom_crons', [ $notif->ID, 'lesson_started', $membership_id, $course_id, $lesson_id ] );
                 }
                 wp_schedule_single_event( $lesdate->getTimeStamp(), 'my_custom_crons', [ $notif->ID, 'lesson_started', $membership_id, $course_id, $lesson_id ] );
              } // end notif loop
           } // end memberships loop
        } // if date in future
    } // if drip rule for lesson
}

So this sets up single event cron in WP named my_custom_crons. I have similar logic for a webinar_started event, as well as webinat_starts_in_onehour event for a given lesson.

To react to this hook, I have

add_action( 'my_custom_crons','my_custom_crons', 99, 5 );
function my_custom_crons( $notif, $notification_type, $membership_id, $course_id, $lesson_id) {

    global $wpdb;
    $wpdb->query("SET time_zone = 'UTC'");
    $activeusers = $wpdb->get_results("SELECT DISTINCT user_id FROM wp_mepr_transactions WHERE status IN('confirmed','complete') AND (expires_at >= NOW() OR expires_at = '0000-00-00 00:00:00') AND product_id = '$membership_id'");
    if ( empty( $activeusers ) ) {
        return;
    }

    // ok, we have users that are active
    foreach ( $activeusers as $activeuser ) {
        $user_id = absint( $activeuser->user_id );
        if ($user_id != 0) {
            // bunch of checking
            // get notification message for notification type (lessons tarted, or webinar started)
            // then send the emails

        }
    }

    // clear the job (but this not needed?)
    wp_clear_scheduled_hook('my_custom_crons', array(
                $notif->ID,
                $notification_type, 
                $course_id,
                $membership_id,
                $lesson_id,
    ));
}

All seems to be working in terms of cron being set up. If I run wp cron event list after adding a Membrepress Drip Rule for a lesson, I see the my_custom_crons job scheduled:

my_custom_crons| 2021-11-03 20:01:00 |16 hours 1 minute| Non-repeating

But what happens if I have multiple lessons and multiple events I want to use for email triggers, all with various dates? As mentioned, I might have lesson_started and lesson_webinar_started event for various lessons. So will my current approach work, or will various events for various lessons all triggered at different times, all using my one cron hook's logic, interfere with one another?

Just not seeing bigger picture of how I should be doing this...

Thanks

Share Improve this question edited Nov 3, 2021 at 10:30 Brian asked Nov 2, 2021 at 20:52 BrianBrian 3372 silver badges11 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 2

No, not quite.

When you say this:

wp_schedule_single_event( $sent_on, 'my_custom_cron', [ $notif_id, $notif_name, $membership_id, $lesson_id ] );

You are essentially saying is when the current time passes $sent_on, do:

do_action( 'my_custom_cron', [ $notif_id, $notif_name, $membership_id, $lesson_id ] );

Cron jobs are just a TODO note to fire an action with some arguments in the future. That's all they are, the action works exactly the same as any other action/hook.

So:

But what happens if I have multiple lessons and multiple events I want to use for email triggers, all with various dates?

Then you'll have multiple scheduled cron jobs with different arguments.

It will only send emails for all lessons if you built a function that sends emails for all lessons and added it to that hook. That's an extremely obvious and deliberate thing to do, I think you would know if you had done that due to the special effort needed to fetch all lessons and send emails.

As mentioned, I might have lesson_started and lesson_webinar_started event for various lessons. So will my current approach work, or will various events for various lessons all triggered at different times, all using my one cron hook's logic, interfere with one another?

Did you build them to interfere with eachother? If so yes! If not then no.


If you schedule a cron job 5 times for a hook, that hook will be fired 5 times. If you give it different arguments each time, the hook will fire with different arguments each time. It's a queue. I understand the anxiety and uncertainty around this but a little investigation and common sense reveals it's unfounded. Likewise some quick tests would demonstrate it's not the case.

The one thing that I will point out though is the wp_clear_scheduled_hook call, it doesn't make sense. It's like declaring you will not go to the store on Monday just after you get home from the store. The cron job has already fired, it's too late, and it's a non-repeating one time single cron job so there's nothing to clear in the future.

That entire loop makes no sense, that's not how wp_schedule_single_event works, it would match the first time then there'd be nothing on the follow up iterations of the loop as you've already cleared it. wp_schedule_single_event schedules a single event, not a repeating event.

本文标签: Wordpress cron hookssame callback for completely different action