public function test_unschedule() { $time = time(); $hook = md5(rand()); $action_id = wc_schedule_single_action($time, $hook); wc_unschedule_action($hook); $next = wc_next_scheduled_action($hook); $this->assertFalse($next); $store = ActionScheduler::store(); $action = $store->fetch_action($action_id); $this->assertNull($action->get_schedule()->next()); $this->assertEmpty($action->get_hook()); }
/** * Update subscription WP-Cron tasks to Action Scheduler. * * @since 2.0 */ public static function upgrade_hooks($number_hooks_to_upgrade) { $counter = 0; $cron = _get_cron_array(); foreach ($cron as $timestamp => $actions) { foreach ($actions as $hook => $details) { if ('scheduled_subscription_payment' == $hook || 'scheduled_subscription_expiration' == $hook || 'scheduled_subscription_end_of_prepaid_term' == $hook || 'scheduled_subscription_trial_end' == $hook || 'paypal_check_subscription_payment' == $hook) { foreach ($details as $hook_key => $values) { if (!wc_next_scheduled_action($hook, $values['args'])) { wc_schedule_single_action($timestamp, $hook, $values['args']); unset($cron[$timestamp][$hook][$hook_key]); $counter++; } if ($counter >= $number_hooks_to_upgrade) { break; } } // If there are no other jobs scheduled for this hook at this timestamp, remove the entire hook if (0 == count($cron[$timestamp][$hook])) { unset($cron[$timestamp][$hook]); } if ($counter >= $number_hooks_to_upgrade) { break; } } } // If there are no actions schedued for this timestamp, remove the entire schedule if (0 == count($cron[$timestamp])) { unset($cron[$timestamp]); } if ($counter >= $number_hooks_to_upgrade) { break; } } // Set the cron with the removed schedule _set_cron_array($cron); return $counter; }
/** * Migrate the trial expiration, next payment and expiration/end dates to a new subscription. * * @since 2.0 */ private static function migrate_dates($new_subscription, $old_subscription) { global $wpdb; $dates_to_update = array(); // old hook => new hook $date_keys = array('trial_end' => array('old_subscription_key' => 'trial_expiry_date', 'old_scheduled_hook' => 'scheduled_subscription_trial_end'), 'end' => array('old_subscription_key' => 'expiry_date', 'old_scheduled_hook' => 'scheduled_subscription_expiration'), 'end_date' => array('old_subscription_key' => '_subscription_end_date', 'old_scheduled_hook' => ''), 'next_payment' => array('old_subscription_key' => '', 'old_scheduled_hook' => 'scheduled_subscription_payment'), 'end_of_prepaid_term' => array('old_subscription_key' => '', 'old_scheduled_hook' => 'scheduled_subscription_end_of_prepaid_term')); $old_hook_args = array('user_id' => $old_subscription['user_id'], 'subscription_key' => $old_subscription['subscription_key']); foreach ($date_keys as $new_key => $old_keys) { // First check if there is a date stored on the subscription, and if so, use that if (!empty($old_keys['old_subscription_key']) && (isset($old_subscription[$old_keys['old_subscription_key']]) && 0 !== $old_subscription[$old_keys['old_subscription_key']])) { $dates_to_update[$new_key] = $old_subscription[$old_keys['old_subscription_key']]; } elseif (!empty($old_keys['old_scheduled_hook'])) { // Now check if there is a scheduled date, this is for next payment and end of prepaid term dates $next_scheduled = wc_next_scheduled_action($old_keys['old_scheduled_hook'], $old_hook_args); if ($next_scheduled > 0) { if ('end_of_prepaid_term' == $new_key) { wc_schedule_single_action($next_scheduled, 'woocommerce_scheduled_subscription_end_of_prepaid_term', array('subscription_id' => $new_subscription->id)); } else { $dates_to_update[$new_key] = date('Y-m-d H:i:s', $next_scheduled); } } } } // Trash all the hooks in one go to save write requests $wpdb->update($wpdb->posts, array('post_status' => 'trash'), array('post_type' => ActionScheduler_wpPostStore::POST_TYPE, 'post_content' => json_encode($old_hook_args)), array('%s', '%s')); $dates_to_update['start'] = $new_subscription->post->post_date_gmt; // v2.0 enforces new rules for dates when they are being set, so we need to massage the old data to conform to these new rules foreach ($dates_to_update as $date_type => $date) { if (0 == $date) { continue; } switch ($date_type) { case 'end': if (array_key_exists('next_payment', $dates_to_update) && $date <= $dates_to_update['next_payment']) { $dates_to_update[$date_type] = $date; } case 'next_payment': if (array_key_exists('trial_end', $dates_to_update) && $date < $dates_to_update['trial_end']) { $dates_to_update[$date_type] = $date; } case 'trial_end': if (array_key_exists('start', $dates_to_update) && $date <= $dates_to_update['start']) { $dates_to_update[$date_type] = $date; } } } try { if (!empty($dates_to_update)) { $new_subscription->update_dates($dates_to_update); } WCS_Upgrade_Logger::add(sprintf('For subscription %d: updated dates = %s', $new_subscription->id, str_replace(array('{', '}', '"'), '', json_encode($dates_to_update)))); } catch (Exception $e) { WCS_Upgrade_Logger::add(sprintf('For subscription %d: unable to update dates, exception "%s"', $new_subscription->id, $e->getMessage())); } }
public function test_filtering_of_get_comments() { $post_id = $this->factory->post->create_object(array('post_title' => __FUNCTION__)); $comment_id = $this->factory->comment->create_object(array('comment_post_ID' => $post_id, 'comment_author' => __CLASS__, 'comment_content' => __FUNCTION__)); // Verify that we're getting the expected comment before we add logging comments $comments = get_comments(); $this->assertCount(1, $comments); $this->assertEquals($comment_id, $comments[0]->comment_ID); $action_id = wc_schedule_single_action(time(), 'a hook'); $logger = ActionScheduler::logger(); $message = 'Logging that something happened'; $log_id = $logger->log($action_id, $message); // Verify that logging comments are excluded from general comment queries $comments = get_comments(); $this->assertCount(1, $comments); $this->assertEquals($comment_id, $comments[0]->comment_ID); // Verify that logging comments are returned when asking for them specifically $comments = get_comments(array('type' => ActionScheduler_wpCommentLogger::TYPE)); // Expecting two: one when the action is created, another when we added our custom log $this->assertCount(2, $comments); $this->assertContains($log_id, wp_list_pluck($comments, 'comment_ID')); }
/** * When a subscription's status is updated, maybe schedule an event * * @param object $subscription An instance of a WC_Subscription object * @param string $date_type Can be 'start', 'trial_end', 'next_payment', 'last_payment', 'end', 'end_of_prepaid_term' or a custom date type * @param string $datetime A MySQL formated date/time string in the GMT/UTC timezone. */ public function update_status($subscription, $new_status, $old_status) { $action_args = array('subscription_id' => $subscription->id); switch ($new_status) { case 'active': foreach ($this->action_hooks as $action_hook => $date_type) { $next_scheduled = wc_next_scheduled_action($action_hook, $action_args); $event_time = $subscription->get_time($date_type); // Maybe clear the existing schedule for this hook if (false !== $next_scheduled && $next_scheduled != $event_time) { wc_unschedule_action($action_hook, $action_args); } if (0 != $event_time && $event_time > current_time('timestamp', true) && $next_scheduled != $event_time) { wc_schedule_single_action($event_time, $action_hook, $action_args); } } break; case 'pending-cancel': $end_time = $subscription->get_time('end'); // This will have been set to the correct date already // Now that we have the current times, clear the scheduled hooks foreach ($this->action_hooks as $action_hook => $date_type) { wc_unschedule_action($action_hook, $action_args); } $next_scheduled = wc_next_scheduled_action('woocommerce_scheduled_subscription_end_of_prepaid_term', $action_args); if (false !== $next_scheduled && $next_scheduled != $end_time) { wc_unschedule_action('woocommerce_scheduled_subscription_end_of_prepaid_term', $action_args); } // The end date was set in WC_Subscriptions::update_dates() to the appropriate value, so we can schedule our action for that time if ($end_time > current_time('timestamp', true) && $next_scheduled != $end_time) { wc_schedule_single_action($end_time, 'woocommerce_scheduled_subscription_end_of_prepaid_term', $action_args); } break; case 'on-hold': case 'cancelled': case 'switched': case 'expired': case 'trash': foreach ($this->action_hooks as $action_hook => $date_type) { wc_unschedule_action($action_hook, $action_args); } wc_unschedule_action('woocommerce_scheduled_subscription_end_of_prepaid_term', $action_args); break; } }
/** * Updates the trial expiration date as scheduled in WP-Cron and in the subscription details array. * * @param string $subscription_key A subscription key of the form created by @see self::get_subscription_key() * @param int $user_id (optional) The ID of the user who owns the subscription. Although this parameter is optional, if you have the User ID you should pass it to improve performance. * @param (optional) $next_payment string | int The date and time the next payment is due, either as MySQL formatted datetime string or a Unix timestamp. If empty, @see self::calculate_next_payment_date() will be called. * @return mixed If the trial expiration does not get set, returns false, otherwise it will return a MySQL datetime formatted string for the new date when the trial will expire * @since 1.2.4 */ public static function set_trial_expiration_date($subscription_key, $user_id = '', $trial_expiration_date = '') { $is_set = false; if (empty($user_id)) { $user_id = self::get_user_id_from_subscription_key($subscription_key); } if (empty($trial_expiration_date)) { $trial_expiration_date = self::calculate_trial_expiration_date($subscription_key, $user_id, 'timestamp'); } elseif (is_string($trial_expiration_date)) { $trial_expiration_date = strtotime($trial_expiration_date); } // Update the date stored on the subscription $date_string = $trial_expiration_date != 0 ? date('Y-m-d H:i:s', $trial_expiration_date) : 0; self::update_users_subscriptions($user_id, array($subscription_key => array('trial_expiry_date' => $date_string))); $hook_args = array('user_id' => (int) $user_id, 'subscription_key' => $subscription_key); // Clear the existing schedule for this hook wc_unschedule_action('scheduled_subscription_trial_end', $hook_args); if ($trial_expiration_date != 0 && $trial_expiration_date > gmdate('U')) { wc_schedule_single_action($trial_expiration_date, 'scheduled_subscription_trial_end', array('user_id' => (int) $user_id, 'subscription_key' => $subscription_key)); $is_set = true; } return apply_filters('woocommerce_subscriptions_set_trial_expiration_date', $is_set, $trial_expiration_date, $subscription_key, $user_id); }
/** * AJAX handler for toggling an email queue's status */ public static function toggle_queue_status() { global $wpdb; $id = $_POST['id']; $status = $wpdb->get_var($wpdb->prepare("SELECT status FROM {$wpdb->prefix}followup_email_orders WHERE id = %d", $id)); $resp = array('ack' => 'OK'); if ($status == 0) { // activate $wpdb->update($wpdb->prefix . 'followup_email_orders', array('status' => 1), array('id' => $id)); // re-create the task $param = array('email_order_id' => $id); $send_time = $wpdb->get_var($wpdb->prepare("SELECT send_on FROM {$wpdb->prefix}followup_email_orders WHERE id = %d", $id)); wc_schedule_single_action($send_time, 'sfn_followup_emails', $param, 'fue'); $resp['new_status'] = __('Queued', 'follow_up_emails'); $resp['new_action'] = __('Do not send', 'follow_up_emails'); } else { // deactivate $wpdb->update($wpdb->prefix . 'followup_email_orders', array('status' => 0), array('id' => $id)); // if using action-scheduler, delete the task $param = array('email_order_id' => $id); wc_unschedule_action('sfn_followup_emails', $param, 'fue'); $resp['new_status'] = __('Suspended', 'follow_up_emails'); $resp['new_action'] = __('Re-enable', 'follow_up_emails'); } die(json_encode($resp)); }
/** * In typical PayPal style, there are a couple of important limitations we need to work around: * * 1. PayPal does not support subscriptions with a $0 recurring total. As a result, we treat it * as a normal purchase and then handle the subscription renewals here. * * 2. PayPal make no guarantee about when a recurring payment will be charged. This creates issues for * suspending a subscription until the payment is processed. Specifically, if PayPal processed a payment * *before* it was due, we can't suspend the subscription when it is due because it will remain suspended * until the next payment. As a result, subscriptions for PayPal are not suspended. However, if there was * an issue with the subscription sign-up or payment that was not correctly reported to the store, then the * subscription would remain active. No renewal order would be generated, because no payments are completed, * so physical subscriptions would not be affected, however, subscriptions to digital goods would be affected. * * @since 1.4.3 */ public static function scheduled_subscription_payment($amount_to_charge, $order, $product_id) { $hook_args = array('subscription_key' => WC_Subscriptions_Manager::get_subscription_key($order->id, $product_id)); $one_day_from_now = gmdate('U') + 60 * 60 * 24; wc_schedule_single_action($one_day_from_now, 'paypal_check_subscription_payment', $hook_args); }
/** * Schedule unsent emails to use action-scheduler * * @param int $pos * @param int $length * * @return bool|int */ public static function action_scheduler_import($pos = 0, $length = 0) { $wpdb = Follow_Up_Emails::instance()->wpdb; if ($length > 0) { $rows = $wpdb->get_results("SELECT id, send_on FROM {$wpdb->prefix}followup_email_orders WHERE is_sent = 0 ORDER BY id ASC LIMIT {$pos}, {$length}"); } else { $rows = $wpdb->get_results("SELECT id, send_on FROM {$wpdb->prefix}followup_email_orders WHERE is_sent = 0 ORDER BY id ASC"); } if (!$rows) { return false; } foreach ($rows as $row) { $data = array('email_order_id' => $row->id); $job_id = wc_schedule_single_action($row->send_on, 'sfn_followup_emails', $data, 'fue'); $pos++; } return $pos; }
/** * Return the scheduled date/time * @param array $item * @return string */ public function get_date_value($item) { $scheduler = Follow_Up_Emails::instance()->scheduler; $param = $scheduler->get_scheduler_parameters($item['id']); $send_on = wc_next_scheduled_action('sfn_followup_emails', $param, 'fue'); if (false === $send_on) { // attempt to schedule the email again $send_on = strtotime(get_gmt_from_date(date('Y-m-d H:i:s', $item['send_on']))); wc_schedule_single_action($send_on, 'sfn_followup_emails', $param, 'fue'); } return get_date_from_gmt(date('Y-m-d H:i:s', $send_on), get_option('date_format') . ' ' . get_option('time_format')); }
/** * Move scheduled subscription hooks out of wp-cron and into the new Action Scheduler. * * Also set all existing subscriptions to "sold individually" to maintain previous behavior * for existing subscription products before the subscription quantities feature was enabled.. * * @since 1.5 */ public static function ajax_upgrade() { global $wpdb; @set_time_limit(600); @ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT)); set_transient('wc_subscriptions_is_upgrading', 'true', 60 * 2); if ('really_old_version' == $_POST['upgrade_step']) { $database_updates = ''; if ('0' != self::$active_version && version_compare(self::$active_version, '1.2', '<')) { self::upgrade_database_to_1_2(); self::generate_renewal_orders(); update_option(WC_Subscriptions_Admin::$option_prefix . '_active_version', '1.2'); $database_updates = '1.2, '; } // Add Variable Subscription product type term if ('0' != self::$active_version && version_compare(self::$active_version, '1.3', '<')) { self::upgrade_database_to_1_3(); update_option(WC_Subscriptions_Admin::$option_prefix . '_active_version', '1.3'); $database_updates .= '1.3 & '; } // Moving subscription meta out of user meta and into item meta if ('0' != self::$active_version && version_compare(self::$active_version, '1.4', '<')) { self::upgrade_database_to_1_4(); update_option(WC_Subscriptions_Admin::$option_prefix . '_active_version', '1.4'); $database_updates .= '1.4.'; } $results = array('message' => sprintf(__('Database updated to version %s', 'woocommerce-subscriptions'), $database_updates)); } elseif ('products' == $_POST['upgrade_step']) { // Set status to 'sold individually' for all existing subscriptions that haven't already been updated $sql = "SELECT DISTINCT ID FROM {$wpdb->posts} as posts\n\t\t\t\tJOIN {$wpdb->postmeta} as postmeta\n\t\t\t\t\tON posts.ID = postmeta.post_id\n\t\t\t\t\tAND (postmeta.meta_key LIKE '_subscription%')\n\t\t\t\tJOIN {$wpdb->postmeta} AS soldindividually\n\t\t\t\t\tON posts.ID = soldindividually.post_id\n\t\t\t\t\tAND ( soldindividually.meta_key LIKE '_sold_individually' AND soldindividually.meta_value != 'yes' )\n\t\t\t\tWHERE posts.post_type = 'product'"; $subscription_product_ids = $wpdb->get_results($sql); foreach ($subscription_product_ids as $product_id) { update_post_meta($product_id->ID, '_sold_individually', 'yes'); } $results = array('message' => sprintf(__('Marked %s subscription products as "sold individually".', 'woocommerce-subscriptions'), count($subscription_product_ids))); } else { $counter = 0; $before_cron_update = microtime(true); // update all of the current Subscription cron tasks to the new Action Scheduler $cron = _get_cron_array(); foreach ($cron as $timestamp => $actions) { foreach ($actions as $hook => $details) { if ($hook == 'scheduled_subscription_payment' || $hook == 'scheduled_subscription_expiration' || $hook == 'scheduled_subscription_end_of_prepaid_term' || $hook == 'scheduled_subscription_trial_end' || $hook == 'paypal_check_subscription_payment') { foreach ($details as $hook_key => $values) { if (!wc_next_scheduled_action($hook, $values['args'])) { wc_schedule_single_action($timestamp, $hook, $values['args']); unset($cron[$timestamp][$hook][$hook_key]); $counter++; } if ($counter >= self::$upgrade_limit) { break; } } // If there are no other jobs scheduled for this hook at this timestamp, remove the entire hook if (0 == count($cron[$timestamp][$hook])) { unset($cron[$timestamp][$hook]); } if ($counter >= self::$upgrade_limit) { break; } } } // If there are no actions schedued for this timestamp, remove the entire schedule if (0 == count($cron[$timestamp])) { unset($cron[$timestamp]); } if ($counter >= self::$upgrade_limit) { break; } } // Set the cron with the removed schedule _set_cron_array($cron); $results = array('upgraded_count' => $counter, 'message' => sprintf(__('Migrated %s subscription related hooks to the new scheduler (in {execution_time} seconds).', 'woocommerce-subscriptions'), $counter)); } if (isset($counter) && $counter < self::$upgrade_limit) { self::upgrade_complete(); } delete_transient('wc_subscriptions_is_upgrading'); header('Content-Type: application/json; charset=utf-8'); echo json_encode($results); exit; }
/** * Schedule a hook to automatically clear the log after 8 weeks */ public static function schedule_cleanup() { $time_to_cleanup = gmdate('U') + self::$weeks_until_cleanup * WEEK_IN_SECONDS; self::add(sprintf('Upgrade complete. Scheduling log cleanup for %s GMT/UTC', date('Y-m-d H:i:s', $time_to_cleanup))); wc_schedule_single_action($time_to_cleanup, 'woocommerce_subscriptions_clear_upgrade_log'); }