WooCommerce MailChimp - Version 2.3.2

Version Description

Download this release

Release Info

Developer anderly
Plugin Icon 128x128 WooCommerce MailChimp
Version 2.3.2
Comparing to
See all releases

Code changes from version 2.3.1 to 2.3.2

Files changed (27) hide show
  1. includes/class-ss-wc-mailchimp-handler.php +14 -42
  2. includes/class-ss-wc-mailchimp-plugin.php +61 -1
  3. includes/lib/action-scheduler/.gitattributes +9 -0
  4. includes/lib/action-scheduler/action-scheduler.php +9 -9
  5. includes/lib/action-scheduler/classes/ActionScheduler.php +9 -0
  6. includes/lib/action-scheduler/classes/ActionScheduler_Abstract_QueueRunner.php +6 -3
  7. includes/lib/action-scheduler/classes/ActionScheduler_AdminHelp.php +47 -0
  8. includes/lib/action-scheduler/classes/ActionScheduler_AdminView.php +6 -1
  9. includes/lib/action-scheduler/classes/ActionScheduler_CronSchedule.php +7 -0
  10. includes/lib/action-scheduler/classes/ActionScheduler_IntervalSchedule.php +1 -3
  11. includes/lib/action-scheduler/classes/ActionScheduler_ListTable.php +11 -4
  12. includes/lib/action-scheduler/classes/ActionScheduler_Logger.php +6 -2
  13. includes/lib/action-scheduler/classes/ActionScheduler_QueueCleaner.php +25 -14
  14. includes/lib/action-scheduler/classes/ActionScheduler_Store.php +31 -1
  15. includes/lib/action-scheduler/classes/ActionScheduler_WPCLI_QueueRunner.php +21 -9
  16. includes/lib/action-scheduler/classes/ActionScheduler_wcSystemStatus.php +147 -0
  17. includes/lib/action-scheduler/classes/ActionScheduler_wpPostStore.php +97 -25
  18. includes/lib/action-scheduler/composer.json +1 -1
  19. includes/lib/action-scheduler/docs/_layouts/default.html +5 -0
  20. includes/lib/action-scheduler/docs/assets/css/style.scss +25 -0
  21. includes/lib/action-scheduler/docs/faq.md +2 -2
  22. includes/lib/action-scheduler/docs/usage.md +2 -2
  23. includes/lib/action-scheduler/license.txt +4 -4
  24. includes/lib/action-scheduler/tests/phpunit/jobstore/ActionScheduler_wpPostStore_Test.php +3 -3
  25. includes/lib/action-scheduler/tests/phpunit/runner/ActionScheduler_QueueRunner_Test.php +54 -1
  26. readme.txt +5 -1
  27. woocommerce-mailchimp.php +1 -1
includes/class-ss-wc-mailchimp-handler.php CHANGED
@@ -125,7 +125,7 @@ if ( ! class_exists( 'SS_WC_MailChimp_Handler' ) ) {
125
  $this->log( sprintf( __( __METHOD__ . '(): Queueing maybe subscribe ($subscribe_customer: %s) for customer (%s) to list %s for order (%s)', 'woocommerce-mailchimp'), $subscribe_customer, $order_billing_email, $list_id, $order_id ) );
126
 
127
  // Queue the subscription.
128
- as_schedule_single_action( time(), 'queue_ss_wc_mailchimp_maybe_subscribe', array( $subscribe_customer, $order_id, $order_billing_first_name, $order_billing_last_name, $order_billing_email, $list_id ), 'sswcmc' );
129
 
130
  }
131
  }
@@ -369,59 +369,31 @@ if ( ! class_exists( 'SS_WC_MailChimp_Handler' ) ) {
369
  * @param string $listid (default: 'false')
370
  * @return void
371
  */
372
- public function maybe_subscribe( $subscribe_customer, $order_id, $first_name, $last_name, $email, $list_id = 'false' ) {
373
 
374
- $this->log( sprintf( __( __METHOD__ . '(): Processing queued maybe_subscribe ($subscribe_customer: %s) for customer (%s) to list %s for order (%s)', 'woocommerce-mailchimp' ), $subscribe_customer, $email, $list_id, $order_id ) );
375
-
376
- if ( ! $email ) {
377
- return; // Email is required.
378
- }
379
 
380
- if ( 'false' == $list_id ) {
381
- $list_id = $this->sswcmc->get_list();
382
- }
383
 
384
- $merge_tags = array(
385
- 'FNAME' => $first_name,
386
- 'LNAME' => $last_name,
387
- );
388
 
389
- $interest_groups = $this->sswcmc->interest_groups();
390
 
391
- if ( ! empty( $interest_groups ) ) {
392
- $interest_groups = array_fill_keys( $interest_groups, true );
393
  }
394
 
395
  // Allow hooking into interest groups.
396
- $interest_groups = apply_filters( 'ss_wc_mailchimp_subscribe_interest_groups', $interest_groups, $order_id, $email );
397
-
398
- $tags = $this->sswcmc->tags();
399
-
400
- $mc_tags = $this->sswcmc->mailchimp()->get_tags( $list_id );
401
-
402
- $tags = array_map( function( $tag ) use ( $mc_tags ) {
403
- return array(
404
- 'name' => $mc_tags[$tag],
405
- 'status' => 'active',
406
- );
407
- }, $tags );
408
 
409
  // Allow hooking into tags.
410
- $tags = apply_filters( 'ss_wc_mailchimp_subscribe_tags', $tags, $order_id, $email );
411
 
412
  // Allow hooking into variables.
413
- $merge_tags = apply_filters( 'ss_wc_mailchimp_subscribe_merge_tags', $merge_tags, $order_id, $email );
414
-
415
- // Set subscription options.
416
- $subscribe_options = array(
417
- 'list_id' => $list_id,
418
- 'email' => $email,
419
- 'merge_tags' => $merge_tags,
420
- 'interest_groups' => $interest_groups,
421
- 'tags' => $tags,
422
- 'email_type' => 'html',
423
- 'double_opt_in' => $this->sswcmc->double_opt_in(),
424
- );
425
 
426
  // Allow hooking into subscription options.
427
  $options = apply_filters( 'ss_wc_mailchimp_subscribe_options', $subscribe_options, $order_id );
125
  $this->log( sprintf( __( __METHOD__ . '(): Queueing maybe subscribe ($subscribe_customer: %s) for customer (%s) to list %s for order (%s)', 'woocommerce-mailchimp'), $subscribe_customer, $order_billing_email, $list_id, $order_id ) );
126
 
127
  // Queue the subscription.
128
+ as_schedule_single_action( time(), 'queue_ss_wc_mailchimp_maybe_subscribe', array( $order_id ), 'sswcmc' );
129
 
130
  }
131
  }
369
  * @param string $listid (default: 'false')
370
  * @return void
371
  */
372
+ public function maybe_subscribe( $order_id ) {
373
 
374
+ // get the ss_wc_mailchimp_opt_in value from the post meta. "order_custom_fields" was removed with WooCommerce 2.1
375
+ $subscribe_customer = get_post_meta( $order_id, 'ss_wc_mailchimp_opt_in', true );
 
 
 
376
 
377
+ // Get the subscribe options
378
+ $subscribe_options = $this->sswcmc->get_subscribe_options_for_order( $order_id );
 
379
 
380
+ $email = $subscribe_options['email'];
381
+ $list_id = $subscribe_options['list_id'];
 
 
382
 
383
+ $this->log( sprintf( __( __METHOD__ . '(): Processing queued maybe_subscribe ($subscribe_customer: %s) for customer (%s) to list %s for order (%s)', 'woocommerce-mailchimp' ), $subscribe_customer, $email, $list_id, $order_id ) );
384
 
385
+ if ( ! $email ) {
386
+ return; // Email is required.
387
  }
388
 
389
  // Allow hooking into interest groups.
390
+ $subscribe_options['interest_groups'] = apply_filters( 'ss_wc_mailchimp_subscribe_interest_groups', $subscribe_options['interest_groups'], $order_id, $email );
 
 
 
 
 
 
 
 
 
 
 
391
 
392
  // Allow hooking into tags.
393
+ $subscribe_options['tags'] = apply_filters( 'ss_wc_mailchimp_subscribe_tags', $subscribe_options['tags'], $order_id, $email );
394
 
395
  // Allow hooking into variables.
396
+ $subscribe_options['merge_tags'] = apply_filters( 'ss_wc_mailchimp_subscribe_merge_tags', $subscribe_options['merge_tags'], $order_id, $email );
 
 
 
 
 
 
 
 
 
 
 
397
 
398
  // Allow hooking into subscription options.
399
  $options = apply_filters( 'ss_wc_mailchimp_subscribe_options', $subscribe_options, $order_id );
includes/class-ss-wc-mailchimp-plugin.php CHANGED
@@ -15,7 +15,7 @@ final class SS_WC_MailChimp_Plugin {
15
  *
16
  * @var string
17
  */
18
- private static $version = '2.3.1';
19
 
20
  /**
21
  * Plugin singleton instance
@@ -248,6 +248,66 @@ final class SS_WC_MailChimp_Plugin {
248
  return $this->settings['tags'];
249
  }
250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  /**
252
  * Whether or not an api key has been set.
253
  *
15
  *
16
  * @var string
17
  */
18
+ private static $version = '2.3.2';
19
 
20
  /**
21
  * Plugin singleton instance
248
  return $this->settings['tags'];
249
  }
250
 
251
+ /**
252
+ * Get the global subscribe options for the passed $order_id
253
+ *
254
+ * @since 2.3.2
255
+ * @access public
256
+ * @param $order_id int The order id.
257
+ */
258
+ public function get_subscribe_options_for_order( $order_id ) {
259
+
260
+ // Get WC order
261
+ $order = wc_get_order( $order_id );
262
+
263
+ $order_id = method_exists( $order, 'get_id' ) ? $order->get_id() : $order->id;
264
+ $email = method_exists( $order, 'get_billing_email' ) ? $order->get_billing_email() : $order->billing_email;
265
+ $first_name = method_exists( $order, 'get_billing_first_name' ) ? $order->get_billing_first_name() : $order->billing_first_name;
266
+ $last_name = method_exists( $order, 'get_billing_last_name' ) ? $order->get_billing_last_name() : $order->billing_last_name;
267
+
268
+ $list_id = $this->get_list();
269
+
270
+ if ( ! $email ) {
271
+ return; // Email is required.
272
+ }
273
+
274
+ $merge_tags = array(
275
+ 'FNAME' => $first_name,
276
+ 'LNAME' => $last_name,
277
+ );
278
+
279
+ $interest_groups = $this->interest_groups();
280
+
281
+ if ( ! empty( $interest_groups ) ) {
282
+ $interest_groups = array_fill_keys( $interest_groups, true );
283
+ }
284
+
285
+ $tags = $this->tags();
286
+
287
+ $mc_tags = $this->mailchimp()->get_tags( $list_id );
288
+
289
+ $tags = array_map( function( $tag ) use ( $mc_tags ) {
290
+ return array(
291
+ 'name' => $mc_tags[$tag],
292
+ 'status' => 'active',
293
+ );
294
+ }, $tags );
295
+
296
+ // Set subscription options.
297
+ $subscribe_options = array(
298
+ 'list_id' => $list_id,
299
+ 'email' => $email,
300
+ 'merge_tags' => $merge_tags,
301
+ 'interest_groups' => $interest_groups,
302
+ 'tags' => $tags,
303
+ 'email_type' => 'html',
304
+ 'double_opt_in' => $this->double_opt_in(),
305
+ );
306
+
307
+ return $subscribe_options;
308
+
309
+ } //end function get_subscribe_options_for_order
310
+
311
  /**
312
  * Whether or not an api key has been set.
313
  *
includes/lib/action-scheduler/.gitattributes ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ docs export-ignore
2
+ tests export-ignore
3
+ codecov.yml export-ignore
4
+ .github export-ignore
5
+ .travis.yml export-ignore
6
+ .gitattributes export-ignore
7
+ .gitignore export-ignore
8
+ phpunit.xml.dist export-ignore
9
+
includes/lib/action-scheduler/action-scheduler.php CHANGED
@@ -1,14 +1,14 @@
1
  <?php
2
  /*
3
  * Plugin Name: Action Scheduler
4
- * Plugin URI: https://github.com/prospress/action-scheduler
5
  * Description: A robust scheduling library for use in WordPress plugins.
6
  * Author: Prospress
7
- * Author URI: http://prospress.com/
8
- * Version: 2.1.1
9
  * License: GPLv3
10
  *
11
- * Copyright 2018 Prospress, Inc. (email : freedoms@prospress.com)
12
  *
13
  * This program is free software: you can redistribute it and/or modify
14
  * it under the terms of the GNU General Public License as published by
@@ -25,21 +25,21 @@
25
  *
26
  */
27
 
28
- if ( ! function_exists( 'action_scheduler_register_2_dot_1_dot_1' ) ) {
29
 
30
  if ( ! class_exists( 'ActionScheduler_Versions' ) ) {
31
  require_once( 'classes/ActionScheduler_Versions.php' );
32
  add_action( 'plugins_loaded', array( 'ActionScheduler_Versions', 'initialize_latest_version' ), 1, 0 );
33
  }
34
 
35
- add_action( 'plugins_loaded', 'action_scheduler_register_2_dot_1_dot_1', 0, 0 );
36
 
37
- function action_scheduler_register_2_dot_1_dot_1() {
38
  $versions = ActionScheduler_Versions::instance();
39
- $versions->register( '2.1.1', 'action_scheduler_initialize_2_dot_1_dot_1' );
40
  }
41
 
42
- function action_scheduler_initialize_2_dot_1_dot_1() {
43
  require_once( 'classes/ActionScheduler.php' );
44
  ActionScheduler::init( __FILE__ );
45
  }
1
  <?php
2
  /*
3
  * Plugin Name: Action Scheduler
4
+ * Plugin URI: https://actionscheduler.org
5
  * Description: A robust scheduling library for use in WordPress plugins.
6
  * Author: Prospress
7
+ * Author URI: https://prospress.com/
8
+ * Version: 2.2.5
9
  * License: GPLv3
10
  *
11
+ * Copyright 2019 Prospress, Inc. (email : freedoms@prospress.com)
12
  *
13
  * This program is free software: you can redistribute it and/or modify
14
  * it under the terms of the GNU General Public License as published by
25
  *
26
  */
27
 
28
+ if ( ! function_exists( 'action_scheduler_register_2_dot_2_dot_5' ) && function_exists( 'add_action' ) ) {
29
 
30
  if ( ! class_exists( 'ActionScheduler_Versions' ) ) {
31
  require_once( 'classes/ActionScheduler_Versions.php' );
32
  add_action( 'plugins_loaded', array( 'ActionScheduler_Versions', 'initialize_latest_version' ), 1, 0 );
33
  }
34
 
35
+ add_action( 'plugins_loaded', 'action_scheduler_register_2_dot_2_dot_5', 0, 0 );
36
 
37
+ function action_scheduler_register_2_dot_2_dot_5() {
38
  $versions = ActionScheduler_Versions::instance();
39
+ $versions->register( '2.2.5', 'action_scheduler_initialize_2_dot_2_dot_5' );
40
  }
41
 
42
+ function action_scheduler_initialize_2_dot_2_dot_5() {
43
  require_once( 'classes/ActionScheduler.php' );
44
  ActionScheduler::init( __FILE__ );
45
  }
includes/lib/action-scheduler/classes/ActionScheduler.php CHANGED
@@ -85,6 +85,11 @@ abstract class ActionScheduler {
85
  self::$plugin_file = $plugin_file;
86
  spl_autoload_register( array( __CLASS__, 'autoload' ) );
87
 
 
 
 
 
 
88
  $store = self::store();
89
  add_action( 'init', array( $store, 'init' ), 1, 0 );
90
 
@@ -97,6 +102,10 @@ abstract class ActionScheduler {
97
  $admin_view = self::admin_view();
98
  add_action( 'init', array( $admin_view, 'init' ), 0, 0 ); // run before $store::init()
99
 
 
 
 
 
100
  require_once( self::plugin_path('functions.php') );
101
 
102
  if ( apply_filters( 'action_scheduler_load_deprecated_functions', true ) ) {
85
  self::$plugin_file = $plugin_file;
86
  spl_autoload_register( array( __CLASS__, 'autoload' ) );
87
 
88
+ /**
89
+ * Fires in the early stages of Action Scheduler init hook.
90
+ */
91
+ do_action( 'action_scheduler_pre_init' );
92
+
93
  $store = self::store();
94
  add_action( 'init', array( $store, 'init' ), 1, 0 );
95
 
102
  $admin_view = self::admin_view();
103
  add_action( 'init', array( $admin_view, 'init' ), 0, 0 ); // run before $store::init()
104
 
105
+ if ( is_admin() ) {
106
+ add_action( 'current_screen', array( 'ActionScheduler_AdminHelp', 'add_help_tabs' ) );
107
+ }
108
+
109
  require_once( self::plugin_path('functions.php') );
110
 
111
  if ( apply_filters( 'action_scheduler_load_deprecated_functions', true ) ) {
includes/lib/action-scheduler/classes/ActionScheduler_Abstract_QueueRunner.php CHANGED
@@ -57,13 +57,16 @@ abstract class ActionScheduler_Abstract_QueueRunner extends ActionScheduler_Abst
57
  $action = $this->store->fetch_action( $action_id );
58
  $this->store->log_execution( $action_id );
59
  $action->execute();
60
- do_action( 'action_scheduler_after_execute', $action_id );
61
  $this->store->mark_complete( $action_id );
62
  } catch ( Exception $e ) {
63
  $this->store->mark_failure( $action_id );
64
  do_action( 'action_scheduler_failed_execution', $action_id, $e );
65
  }
66
- $this->schedule_next_instance( $action );
 
 
 
67
  }
68
 
69
  /**
@@ -86,7 +89,7 @@ abstract class ActionScheduler_Abstract_QueueRunner extends ActionScheduler_Abst
86
  * @author Jeremy Pry
87
  */
88
  protected function run_cleanup() {
89
- $this->cleaner->clean();
90
  }
91
 
92
  /**
57
  $action = $this->store->fetch_action( $action_id );
58
  $this->store->log_execution( $action_id );
59
  $action->execute();
60
+ do_action( 'action_scheduler_after_execute', $action_id, $action );
61
  $this->store->mark_complete( $action_id );
62
  } catch ( Exception $e ) {
63
  $this->store->mark_failure( $action_id );
64
  do_action( 'action_scheduler_failed_execution', $action_id, $e );
65
  }
66
+
67
+ if ( isset( $action ) && is_a( $action, 'ActionScheduler_Action' ) ) {
68
+ $this->schedule_next_instance( $action );
69
+ }
70
  }
71
 
72
  /**
89
  * @author Jeremy Pry
90
  */
91
  protected function run_cleanup() {
92
+ $this->cleaner->clean( 10 * $this->get_time_limit() );
93
  }
94
 
95
  /**
includes/lib/action-scheduler/classes/ActionScheduler_AdminHelp.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Class ActionScheduler_AdminHelp
5
+ * @codeCoverageIgnore
6
+ */
7
+ class ActionScheduler_AdminHelp {
8
+ const SCREEN_ID = 'tools_page_action-scheduler';
9
+
10
+ public function add_help_tabs() {
11
+ $screen = get_current_screen();
12
+
13
+ if ( ! $screen || self::SCREEN_ID != $screen->id ) {
14
+ return;
15
+ }
16
+
17
+ $screen->add_help_tab(
18
+ array(
19
+ 'id' => 'action_scheduler_about',
20
+ 'title' => __( 'About', 'action-scheduler' ),
21
+ 'content' =>
22
+ '<h2>' . __( 'About Action Scheduler', 'action-scheduler' ) . '</h2>' .
23
+ '<p>' .
24
+ __( 'Action Scheduler is a scalable, traceable job queue for background processing large sets of actions. Action Scheduler works by triggering an action hook to run at some time in the future. Scheduled actions can also be scheduled to run on a recurring schedule.', 'action-scheduler' ) .
25
+ '</p>',
26
+ )
27
+ );
28
+
29
+ $screen->add_help_tab(
30
+ array(
31
+ 'id' => 'action_scheduler_columns',
32
+ 'title' => __( 'Columns', 'action-scheduler' ),
33
+ 'content' =>
34
+ '<h2>' . __( 'Scheduled Action Columns', 'action-scheduler' ) . '</h2>' .
35
+ '<ul>' .
36
+ sprintf( '<li><strong>%1$s</strong>: %2$s</li>', __( 'Hook', 'action-scheduler' ), __( 'Name of the action hook that will be triggered.', 'action-scheduler' ) ) .
37
+ sprintf( '<li><strong>%1$s</strong>: %2$s</li>', __( 'Status', 'action-scheduler' ), __( 'Action statuses are Pending, Complete, Canceled, Failed', 'action-scheduler' ) ) .
38
+ sprintf( '<li><strong>%1$s</strong>: %2$s</li>', __( 'Arguments', 'action-scheduler' ), __( 'Optional data array passed to the action hook.', 'action-scheduler' ) ) .
39
+ sprintf( '<li><strong>%1$s</strong>: %2$s</li>', __( 'Group', 'action-scheduler' ), __( 'Optional action group.', 'action-scheduler' ) ) .
40
+ sprintf( '<li><strong>%1$s</strong>: %2$s</li>', __( 'Recurrence', 'action-scheduler' ), __( 'The action\'s schedule frequency.', 'action-scheduler' ) ) .
41
+ sprintf( '<li><strong>%1$s</strong>: %2$s</li>', __( 'Scheduled', 'action-scheduler' ), __( 'The date/time the action is/was scheduled to run.', 'action-scheduler' ) ) .
42
+ sprintf( '<li><strong>%1$s</strong>: %2$s</li>', __( 'Log', 'action-scheduler' ), __( 'Activity log for the action.', 'action-scheduler' ) ) .
43
+ '</ul>',
44
+ )
45
+ );
46
+ }
47
+ }
includes/lib/action-scheduler/classes/ActionScheduler_AdminView.php CHANGED
@@ -9,7 +9,7 @@ class ActionScheduler_AdminView extends ActionScheduler_AdminView_Deprecated {
9
  private static $admin_view = NULL;
10
 
11
  /**
12
- * @return ActionScheduler_QueueRunner
13
  * @codeCoverageIgnore
14
  */
15
  public static function instance() {
@@ -30,6 +30,7 @@ class ActionScheduler_AdminView extends ActionScheduler_AdminView_Deprecated {
30
 
31
  if ( class_exists( 'WooCommerce' ) ) {
32
  add_action( 'woocommerce_admin_status_content_action-scheduler', array( $this, 'render_admin_ui' ) );
 
33
  add_filter( 'woocommerce_admin_status_tabs', array( $this, 'register_system_status_tab' ) );
34
  }
35
 
@@ -37,6 +38,10 @@ class ActionScheduler_AdminView extends ActionScheduler_AdminView_Deprecated {
37
  }
38
  }
39
 
 
 
 
 
40
 
41
  /**
42
  * Registers action-scheduler into WooCommerce > System status.
9
  private static $admin_view = NULL;
10
 
11
  /**
12
+ * @return ActionScheduler_AdminView
13
  * @codeCoverageIgnore
14
  */
15
  public static function instance() {
30
 
31
  if ( class_exists( 'WooCommerce' ) ) {
32
  add_action( 'woocommerce_admin_status_content_action-scheduler', array( $this, 'render_admin_ui' ) );
33
+ add_action( 'woocommerce_system_status_report', array( $this, 'system_status_report' ) );
34
  add_filter( 'woocommerce_admin_status_tabs', array( $this, 'register_system_status_tab' ) );
35
  }
36
 
38
  }
39
  }
40
 
41
+ public function system_status_report() {
42
+ $table = new ActionScheduler_wcSystemStatus( ActionScheduler::store() );
43
+ $table->render();
44
+ }
45
 
46
  /**
47
  * Registers action-scheduler into WooCommerce > System status.
includes/lib/action-scheduler/classes/ActionScheduler_CronSchedule.php CHANGED
@@ -31,6 +31,13 @@ class ActionScheduler_CronSchedule implements ActionScheduler_Schedule {
31
  return true;
32
  }
33
 
 
 
 
 
 
 
 
34
  /**
35
  * For PHP 5.2 compat, since DateTime objects can't be serialized
36
  * @return array
31
  return true;
32
  }
33
 
34
+ /**
35
+ * @return string
36
+ */
37
+ public function get_recurrence() {
38
+ return strval($this->cron);
39
+ }
40
+
41
  /**
42
  * For PHP 5.2 compat, since DateTime objects can't be serialized
43
  * @return array
includes/lib/action-scheduler/classes/ActionScheduler_IntervalSchedule.php CHANGED
@@ -36,9 +36,7 @@ class ActionScheduler_IntervalSchedule implements ActionScheduler_Schedule {
36
  }
37
 
38
  /**
39
- * @param DateTime $after
40
- *
41
- * @return DateTime|null
42
  */
43
  public function interval_in_seconds() {
44
  return $this->interval_in_seconds;
36
  }
37
 
38
  /**
39
+ * @return int
 
 
40
  */
41
  public function interval_in_seconds() {
42
  return $this->interval_in_seconds;
includes/lib/action-scheduler/classes/ActionScheduler_ListTable.php CHANGED
@@ -222,9 +222,16 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
222
  */
223
  protected function get_recurrence( $action ) {
224
  $recurrence = $action->get_schedule();
225
- if ( method_exists( $recurrence, 'interval_in_seconds' ) ) {
226
- return sprintf( __( 'Every %s', 'action-scheduler' ), self::human_interval( $recurrence->interval_in_seconds() ) );
 
 
 
 
 
 
227
  }
 
228
  return __( 'Non-repeating', 'action-scheduler' );
229
  }
230
 
@@ -280,7 +287,7 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
280
  protected function get_log_entry_html( ActionScheduler_LogEntry $log_entry, DateTimezone $timezone ) {
281
  $date = $log_entry->get_date();
282
  $date->setTimezone( $timezone );
283
- return sprintf( '<li><strong>%s</strong><br/>%s</li>', esc_html( $date->format( 'Y-m-d H:i:s e' ) ), esc_html( $log_entry->get_message() ) );
284
  }
285
 
286
  /**
@@ -378,7 +385,7 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
378
 
379
  $next_timestamp = $schedule->next()->getTimestamp();
380
 
381
- $schedule_display_string .= $schedule->next()->format( 'Y-m-d H:i:s e' );
382
  $schedule_display_string .= '<br/>';
383
 
384
  if ( gmdate( 'U' ) > $next_timestamp ) {
222
  */
223
  protected function get_recurrence( $action ) {
224
  $recurrence = $action->get_schedule();
225
+ if ( $recurrence->is_recurring() ) {
226
+ if ( method_exists( $recurrence, 'interval_in_seconds' ) ) {
227
+ return sprintf( __( 'Every %s', 'action-scheduler' ), self::human_interval( $recurrence->interval_in_seconds() ) );
228
+ }
229
+
230
+ if ( method_exists( $recurrence, 'get_recurrence' ) ) {
231
+ return sprintf( __( 'Cron %s', 'action-scheduler' ), $recurrence->get_recurrence() );
232
+ }
233
  }
234
+
235
  return __( 'Non-repeating', 'action-scheduler' );
236
  }
237
 
287
  protected function get_log_entry_html( ActionScheduler_LogEntry $log_entry, DateTimezone $timezone ) {
288
  $date = $log_entry->get_date();
289
  $date->setTimezone( $timezone );
290
+ return sprintf( '<li><strong>%s</strong><br/>%s</li>', esc_html( $date->format( 'Y-m-d H:i:s O' ) ), esc_html( $log_entry->get_message() ) );
291
  }
292
 
293
  /**
385
 
386
  $next_timestamp = $schedule->next()->getTimestamp();
387
 
388
+ $schedule_display_string .= $schedule->next()->format( 'Y-m-d H:i:s O' );
389
  $schedule_display_string .= '<br/>';
390
 
391
  if ( gmdate( 'U' ) > $next_timestamp ) {
includes/lib/action-scheduler/classes/ActionScheduler_Logger.php CHANGED
@@ -55,6 +55,7 @@ abstract class ActionScheduler_Logger {
55
  add_action( 'action_scheduler_unexpected_shutdown', array( $this, 'log_unexpected_shutdown' ), 10, 2 );
56
  add_action( 'action_scheduler_reset_action', array( $this, 'log_reset_action' ), 10, 1 );
57
  add_action( 'action_scheduler_execution_ignored', array( $this, 'log_ignored_action' ), 10, 1 );
 
58
  }
59
 
60
  public function log_stored_action( $action_id ) {
@@ -73,7 +74,7 @@ abstract class ActionScheduler_Logger {
73
  $this->log( $action_id, __( 'action complete', 'action-scheduler' ) );
74
  }
75
 
76
- public function log_failed_action( $action_id, \Exception $exception ) {
77
  $this->log( $action_id, sprintf( __( 'action failed: %s', 'action-scheduler' ), $exception->getMessage() ) );
78
  }
79
 
@@ -94,5 +95,8 @@ abstract class ActionScheduler_Logger {
94
  public function log_ignored_action( $action_id ) {
95
  $this->log( $action_id, __( 'action ignored', 'action-scheduler' ) );
96
  }
 
 
 
 
97
  }
98
-
55
  add_action( 'action_scheduler_unexpected_shutdown', array( $this, 'log_unexpected_shutdown' ), 10, 2 );
56
  add_action( 'action_scheduler_reset_action', array( $this, 'log_reset_action' ), 10, 1 );
57
  add_action( 'action_scheduler_execution_ignored', array( $this, 'log_ignored_action' ), 10, 1 );
58
+ add_action( 'action_scheduler_failed_fetch_action', array( $this, 'log_failed_fetch_action' ), 10, 1 );
59
  }
60
 
61
  public function log_stored_action( $action_id ) {
74
  $this->log( $action_id, __( 'action complete', 'action-scheduler' ) );
75
  }
76
 
77
+ public function log_failed_action( $action_id, Exception $exception ) {
78
  $this->log( $action_id, sprintf( __( 'action failed: %s', 'action-scheduler' ), $exception->getMessage() ) );
79
  }
80
 
95
  public function log_ignored_action( $action_id ) {
96
  $this->log( $action_id, __( 'action ignored', 'action-scheduler' ) );
97
  }
98
+
99
+ public function log_failed_fetch_action( $action_id ) {
100
+ $this->log( $action_id, __( 'There was a failure fetching this action', 'action-scheduler' ) );
101
+ }
102
  }
 
includes/lib/action-scheduler/classes/ActionScheduler_QueueCleaner.php CHANGED
@@ -18,13 +18,6 @@ class ActionScheduler_QueueCleaner {
18
  */
19
  private $month_in_seconds = 2678400;
20
 
21
- /**
22
- * Five minutes in seconds
23
- *
24
- * @var int
25
- */
26
- private $five_minutes = 300;
27
-
28
  /**
29
  * ActionScheduler_QueueCleaner constructor.
30
  *
@@ -77,8 +70,16 @@ class ActionScheduler_QueueCleaner {
77
  }
78
  }
79
 
80
- public function reset_timeouts() {
81
- $timeout = apply_filters( 'action_scheduler_timeout_period', $this->five_minutes );
 
 
 
 
 
 
 
 
82
  if ( $timeout < 0 ) {
83
  return;
84
  }
@@ -97,8 +98,17 @@ class ActionScheduler_QueueCleaner {
97
  }
98
  }
99
 
100
- public function mark_failures() {
101
- $timeout = apply_filters( 'action_scheduler_failure_period', $this->five_minutes );
 
 
 
 
 
 
 
 
 
102
  if ( $timeout < 0 ) {
103
  return;
104
  }
@@ -119,12 +129,13 @@ class ActionScheduler_QueueCleaner {
119
  /**
120
  * Do all of the cleaning actions.
121
  *
 
122
  * @author Jeremy Pry
123
  */
124
- public function clean() {
125
  $this->delete_old_actions();
126
- $this->reset_timeouts();
127
- $this->mark_failures();
128
  }
129
 
130
  /**
18
  */
19
  private $month_in_seconds = 2678400;
20
 
 
 
 
 
 
 
 
21
  /**
22
  * ActionScheduler_QueueCleaner constructor.
23
  *
70
  }
71
  }
72
 
73
+ /**
74
+ * Unclaim pending actions that have not been run within a given time limit.
75
+ *
76
+ * When called by ActionScheduler_Abstract_QueueRunner::run_cleanup(), the time limit passed
77
+ * as a parameter is 10x the time limit used for queue processing.
78
+ *
79
+ * @param int $time_limit The number of seconds to allow a queue to run before unclaiming its pending actions. Default 300 (5 minutes).
80
+ */
81
+ public function reset_timeouts( $time_limit = 300 ) {
82
+ $timeout = apply_filters( 'action_scheduler_timeout_period', $time_limit );
83
  if ( $timeout < 0 ) {
84
  return;
85
  }
98
  }
99
  }
100
 
101
+ /**
102
+ * Mark actions that have been running for more than a given time limit as failed, based on
103
+ * the assumption some uncatachable and unloggable fatal error occurred during processing.
104
+ *
105
+ * When called by ActionScheduler_Abstract_QueueRunner::run_cleanup(), the time limit passed
106
+ * as a parameter is 10x the time limit used for queue processing.
107
+ *
108
+ * @param int $time_limit The number of seconds to allow an action to run before it is considered to have failed. Default 300 (5 minutes).
109
+ */
110
+ public function mark_failures( $time_limit = 300 ) {
111
+ $timeout = apply_filters( 'action_scheduler_failure_period', $time_limit );
112
  if ( $timeout < 0 ) {
113
  return;
114
  }
129
  /**
130
  * Do all of the cleaning actions.
131
  *
132
+ * @param int $time_limit The number of seconds to use as the timeout and failure period. Default 300 (5 minutes).
133
  * @author Jeremy Pry
134
  */
135
+ public function clean( $time_limit = 300 ) {
136
  $this->delete_old_actions();
137
+ $this->reset_timeouts( $time_limit );
138
+ $this->mark_failures( $time_limit );
139
  }
140
 
141
  /**
includes/lib/action-scheduler/classes/ActionScheduler_Store.php CHANGED
@@ -188,13 +188,43 @@ abstract class ActionScheduler_Store {
188
 
189
  public function init() {}
190
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  /**
192
  * @return ActionScheduler_Store
193
  */
194
  public static function instance() {
195
  if ( empty(self::$store) ) {
196
- $class = apply_filters('action_scheduler_store_class', 'ActionScheduler_wpPostStore');
197
  self::$store = new $class();
 
198
  }
199
  return self::$store;
200
  }
188
 
189
  public function init() {}
190
 
191
+ /**
192
+ * Mark an action that failed to fetch correctly as failed.
193
+ *
194
+ * @since 2.2.6
195
+ *
196
+ * @param int $action_id The ID of the action.
197
+ */
198
+ public function mark_failed_fetch_action( $action_id ) {
199
+ self::$store->mark_failure( $action_id );
200
+ }
201
+
202
+ /**
203
+ * Add base hooks
204
+ *
205
+ * @since 2.2.6
206
+ */
207
+ protected static function hook() {
208
+ add_action( 'action_scheduler_failed_fetch_action', array( self::$store, 'mark_failed_fetch_action' ), 20 );
209
+ }
210
+
211
+ /**
212
+ * Remove base hooks
213
+ *
214
+ * @since 2.2.6
215
+ */
216
+ protected static function unhook() {
217
+ remove_action( 'action_scheduler_failed_fetch_action', array( self::$store, 'mark_failed_fetch_action' ), 20 );
218
+ }
219
+
220
  /**
221
  * @return ActionScheduler_Store
222
  */
223
  public static function instance() {
224
  if ( empty(self::$store) ) {
225
+ $class = apply_filters( 'action_scheduler_store_class', 'ActionScheduler_wpPostStore' );
226
  self::$store = new $class();
227
+ self::hook();
228
  }
229
  return self::$store;
230
  }
includes/lib/action-scheduler/classes/ActionScheduler_WPCLI_QueueRunner.php CHANGED
@@ -77,7 +77,7 @@ class ActionScheduler_WPCLI_QueueRunner extends ActionScheduler_Abstract_QueueRu
77
  */
78
  protected function add_hooks() {
79
  add_action( 'action_scheduler_before_execute', array( $this, 'before_execute' ) );
80
- add_action( 'action_scheduler_after_execute', array( $this, 'after_execute' ) );
81
  add_action( 'action_scheduler_failed_execution', array( $this, 'action_failed' ), 10, 2 );
82
  }
83
 
@@ -112,11 +112,7 @@ class ActionScheduler_WPCLI_QueueRunner extends ActionScheduler_Abstract_QueueRu
112
 
113
  $this->process_action( $action_id );
114
  $this->progress_bar->tick();
115
-
116
- // Free up memory after every 50 items
117
- if ( 0 === $this->progress_bar->current() % 50 ) {
118
- $this->stop_the_insanity();
119
- }
120
  }
121
 
122
  $completed = $this->progress_bar->current();
@@ -144,11 +140,16 @@ class ActionScheduler_WPCLI_QueueRunner extends ActionScheduler_Abstract_QueueRu
144
  *
145
  * @author Jeremy Pry
146
  *
147
- * @param $action_id
 
148
  */
149
- public function after_execute( $action_id ) {
 
 
 
 
150
  /* translators: %s refers to the action ID */
151
- WP_CLI::log( sprintf( __( 'Completed processing action %s', 'action-scheduler' ), $action_id ) );
152
  }
153
 
154
  /**
@@ -202,4 +203,15 @@ class ActionScheduler_WPCLI_QueueRunner extends ActionScheduler_Abstract_QueueRu
202
  call_user_func( array( $wp_object_cache, '__remoteset' ) ); // important
203
  }
204
  }
 
 
 
 
 
 
 
 
 
 
 
205
  }
77
  */
78
  protected function add_hooks() {
79
  add_action( 'action_scheduler_before_execute', array( $this, 'before_execute' ) );
80
+ add_action( 'action_scheduler_after_execute', array( $this, 'after_execute' ), 10, 2 );
81
  add_action( 'action_scheduler_failed_execution', array( $this, 'action_failed' ), 10, 2 );
82
  }
83
 
112
 
113
  $this->process_action( $action_id );
114
  $this->progress_bar->tick();
115
+ $this->maybe_stop_the_insanity();
 
 
 
 
116
  }
117
 
118
  $completed = $this->progress_bar->current();
140
  *
141
  * @author Jeremy Pry
142
  *
143
+ * @param int $action_id
144
+ * @param null|ActionScheduler_Action $action The instance of the action. Default to null for backward compatibility.
145
  */
146
+ public function after_execute( $action_id, $action = null ) {
147
+ // backward compatibility
148
+ if ( null === $action ) {
149
+ $action = $this->store->fetch_action( $action_id );
150
+ }
151
  /* translators: %s refers to the action ID */
152
+ WP_CLI::log( sprintf( __( 'Completed processing action %s with hook: %s', 'action-scheduler' ), $action_id, $action->get_hook() ) );
153
  }
154
 
155
  /**
203
  call_user_func( array( $wp_object_cache, '__remoteset' ) ); // important
204
  }
205
  }
206
+
207
+ /**
208
+ * Maybe trigger the stop_the_insanity() method to free up memory.
209
+ */
210
+ protected function maybe_stop_the_insanity() {
211
+ // The value returned by progress_bar->current() might be padded. Remove padding, and convert to int.
212
+ $current_iteration = intval( trim( $this->progress_bar->current() ) );
213
+ if ( 0 === $current_iteration % 50 ) {
214
+ $this->stop_the_insanity();
215
+ }
216
+ }
217
  }
includes/lib/action-scheduler/classes/ActionScheduler_wcSystemStatus.php ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Class ActionScheduler_wcSystemStatus
5
+ */
6
+ class ActionScheduler_wcSystemStatus {
7
+
8
+ /**
9
+ * The active data stores
10
+ *
11
+ * @var ActionScheduler_Store
12
+ */
13
+ protected $store;
14
+
15
+ function __construct( $store ) {
16
+ $this->store = $store;
17
+ }
18
+
19
+ /**
20
+ * Display action data, including number of actions grouped by status and the oldest & newest action in each status.
21
+ *
22
+ * Helpful to identify issues, like a clogged queue.
23
+ */
24
+ public function render() {
25
+ $action_counts = $this->store->action_counts();
26
+ $status_labels = $this->store->get_status_labels();
27
+ $oldest_and_newest = $this->get_oldest_and_newest( array_keys( $status_labels ) );
28
+
29
+ $this->get_template( $status_labels, $action_counts, $oldest_and_newest );
30
+ }
31
+
32
+ /**
33
+ * Get oldest and newest scheduled dates for a given set of statuses.
34
+ *
35
+ * @param array $status_keys Set of statuses to find oldest & newest action for.
36
+ * @return array
37
+ */
38
+ protected function get_oldest_and_newest( $status_keys ) {
39
+
40
+ $oldest_and_newest = array();
41
+
42
+ foreach ( $status_keys as $status ) {
43
+ $oldest_and_newest[ $status ] = array(
44
+ 'oldest' => '&ndash;',
45
+ 'newest' => '&ndash;',
46
+ );
47
+
48
+ if ( 'in-progress' === $status ) {
49
+ continue;
50
+ }
51
+
52
+ $oldest_and_newest[ $status ]['oldest'] = $this->get_action_status_date( $status, 'oldest' );
53
+ $oldest_and_newest[ $status ]['newest'] = $this->get_action_status_date( $status, 'newest' );
54
+ }
55
+
56
+ return $oldest_and_newest;
57
+ }
58
+
59
+ /**
60
+ * Get oldest or newest scheduled date for a given status.
61
+ *
62
+ * @param string $status Action status label/name string.
63
+ * @param string $date_type Oldest or Newest.
64
+ * @return DateTime
65
+ */
66
+ protected function get_action_status_date( $status, $date_type = 'oldest' ) {
67
+
68
+ $order = 'oldest' === $date_type ? 'ASC' : 'DESC';
69
+
70
+ $action = $this->store->query_actions( array(
71
+ 'claimed' => false,
72
+ 'status' => $status,
73
+ 'per_page' => 1,
74
+ 'order' => $order,
75
+ ) );
76
+
77
+ if ( ! empty( $action ) ) {
78
+ $date_object = $this->store->get_date( $action[0] );
79
+ $action_date = $date_object->format( 'Y-m-d H:i:s O' );
80
+ } else {
81
+ $action_date = '&ndash;';
82
+ }
83
+
84
+ return $action_date;
85
+ }
86
+
87
+ /**
88
+ * Get oldest or newest scheduled date for a given status.
89
+ *
90
+ * @param array $status_labels Set of statuses to find oldest & newest action for.
91
+ * @param array $action_counts Number of actions grouped by status.
92
+ * @param array $oldest_and_newest Date of the oldest and newest action with each status.
93
+ */
94
+ protected function get_template( $status_labels, $action_counts, $oldest_and_newest ) {
95
+ ?>
96
+
97
+ <table class="wc_status_table widefat" cellspacing="0">
98
+ <thead>
99
+ <tr>
100
+ <th colspan="5" data-export-label="Action Scheduler"><h2><?php esc_html_e( 'Action Scheduler', 'action-scheduler' ); ?><?php echo wc_help_tip( esc_html__( 'This section shows scheduled action counts.', 'action-scheduler' ) ); ?></h2></th>
101
+ </tr>
102
+ <tr>
103
+ <td><strong><?php esc_html_e( 'Action Status', 'action-scheduler' ); ?></strong></td>
104
+ <td class="help">&nbsp;</td>
105
+ <td><strong><?php esc_html_e( 'Count', 'action-scheduler' ); ?></strong></td>
106
+ <td><strong><?php esc_html_e( 'Oldest Scheduled Date', 'action-scheduler' ); ?></strong></td>
107
+ <td><strong><?php esc_html_e( 'Newest Scheduled Date', 'action-scheduler' ); ?></strong></td>
108
+ </tr>
109
+ </thead>
110
+ <tbody>
111
+ <?php
112
+ foreach ( $action_counts as $status => $count ) {
113
+ // WC uses the 3rd column for export, so we need to display more data in that (hidden when viewed as part of the table) and add an empty 2nd column.
114
+ printf(
115
+ '<tr><td>%1$s</td><td>&nbsp;</td><td>%2$s<span style="display: none;">, Oldest: %3$s, Newest: %4$s</span></td><td>%3$s</td><td>%4$s</td></tr>',
116
+ esc_html( $status_labels[ $status ] ),
117
+ number_format_i18n( $count ),
118
+ $oldest_and_newest[ $status ]['oldest'],
119
+ $oldest_and_newest[ $status ]['newest']
120
+ );
121
+ }
122
+ ?>
123
+ </tbody>
124
+ </table>
125
+
126
+ <?php
127
+ }
128
+
129
+ /**
130
+ * is triggered when invoking inaccessible methods in an object context.
131
+ *
132
+ * @param string $name
133
+ * @param array $arguments
134
+ *
135
+ * @return mixed
136
+ * @link https://php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.methods
137
+ */
138
+ public function __call( $name, $arguments ) {
139
+ switch ( $name ) {
140
+ case 'print':
141
+ _deprecated_function( __CLASS__ . '::print()', '2.2.4', __CLASS__ . '::render()' );
142
+ return call_user_func_array( array( $this, 'render' ), $arguments );
143
+ }
144
+
145
+ return null;
146
+ }
147
+ }
includes/lib/action-scheduler/classes/ActionScheduler_wpPostStore.php CHANGED
@@ -32,7 +32,7 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
32
  $post = array(
33
  'post_type' => self::POST_TYPE,
34
  'post_title' => $action->get_hook(),
35
- 'post_content' => json_encode($action->get_args()),
36
  'post_status' => ( $action->is_finished() ? 'publish' : 'pending' ),
37
  'post_date_gmt' => $this->get_scheduled_date_string( $action, $scheduled_date ),
38
  'post_date' => $this->get_scheduled_date_string_local( $action, $scheduled_date ),
@@ -42,8 +42,23 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
42
 
43
  protected function save_post_array( $post_array ) {
44
  add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
 
 
 
 
 
 
 
 
 
45
  $post_id = wp_insert_post($post_array);
 
 
 
 
 
46
  remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
 
47
 
48
  if ( is_wp_error($post_id) || empty($post_id) ) {
49
  throw new RuntimeException(__('Unable to save action.', 'action-scheduler'));
@@ -61,6 +76,41 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
61
  return $postdata;
62
  }
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  protected function save_post_schedule( $post_id, $schedule ) {
65
  update_post_meta( $post_id, self::SCHEDULE_META_KEY, $schedule );
66
  }
@@ -94,17 +144,21 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
94
 
95
  protected function make_action_from_post( $post ) {
96
  $hook = $post->post_title;
97
- $args = json_decode( $post->post_content, true );
98
 
99
- // Handle args that do not decode properly.
100
- if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $args ) ) {
101
- throw ActionScheduler_InvalidActionException::from_decoding_args( $post->ID );
102
- }
103
 
104
- $schedule = get_post_meta( $post->ID, self::SCHEDULE_META_KEY, true );
105
- if ( empty($schedule) ) {
 
 
 
106
  $schedule = new ActionScheduler_NullSchedule();
 
 
107
  }
 
108
  $group = wp_get_object_terms( $post->ID, self::GROUP_TAXONOMY, array('fields' => 'names') );
109
  $group = empty( $group ) ? '' : reset($group);
110
 
@@ -253,15 +307,16 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
253
  $sql = ( 'count' === $select_or_count ) ? 'SELECT count(p.ID)' : 'SELECT p.ID ';
254
  $sql .= "FROM {$wpdb->posts} p";
255
  $sql_params = array();
256
- if ( ! empty( $query['group'] ) || 'group' === $query['orderby'] ) {
 
 
 
 
257
  $sql .= " INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
258
  $sql .= " INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
259
  $sql .= " INNER JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
260
-
261
- if ( ! empty( $query['group'] ) ) {
262
- $sql .= " AND t.slug=%s";
263
- $sql_params[] = $query['group'];
264
- }
265
  }
266
  $sql .= " WHERE post_type=%s";
267
  $sql_params[] = self::POST_TYPE;
@@ -404,7 +459,9 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
404
  throw new InvalidArgumentException(sprintf(__('Unidentified action %s', 'action-scheduler'), $action_id));
405
  }
406
  do_action( 'action_scheduler_canceled_action', $action_id );
 
407
  wp_trash_post($action_id);
 
408
  }
409
 
410
  public function delete_action( $action_id ) {
@@ -587,16 +644,9 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
587
  'ID' => 'ASC',
588
  ),
589
  'date_query' => array(
590
- 'column' => 'post_date',
591
- array(
592
- 'compare' => '<=',
593
- 'year' => $date->format( 'Y' ),
594
- 'month' => $date->format( 'n' ),
595
- 'day' => $date->format( 'j' ),
596
- 'hour' => $date->format( 'G' ),
597
- 'minute' => $date->format( 'i' ),
598
- 'second' => $date->format( 's' ),
599
- ),
600
  ),
601
  'tax_query' => array(
602
  array(
@@ -685,7 +735,7 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
685
  $status = $this->get_post_column( $action_id, 'post_status' );
686
 
687
  if ( $status === null ) {
688
- throw new \InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
689
  }
690
 
691
  return $this->get_action_status_by_post_status( $status );
@@ -716,11 +766,13 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
716
  throw new InvalidArgumentException(sprintf(__('Unidentified action %s', 'action-scheduler'), $action_id));
717
  }
718
  add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
 
719
  $result = wp_update_post(array(
720
  'ID' => $action_id,
721
  'post_status' => 'publish',
722
  ), TRUE);
723
  remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
 
724
  if ( is_wp_error($result) ) {
725
  throw new RuntimeException($result->get_error_message());
726
  }
@@ -754,4 +806,24 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
754
  $taxonomy_registrar = new ActionScheduler_wpPostStore_TaxonomyRegistrar();
755
  $taxonomy_registrar->register();
756
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
757
  }
32
  $post = array(
33
  'post_type' => self::POST_TYPE,
34
  'post_title' => $action->get_hook(),
35
+ 'post_content' => json_encode($action->get_args(), JSON_UNESCAPED_UNICODE),
36
  'post_status' => ( $action->is_finished() ? 'publish' : 'pending' ),
37
  'post_date_gmt' => $this->get_scheduled_date_string( $action, $scheduled_date ),
38
  'post_date' => $this->get_scheduled_date_string_local( $action, $scheduled_date ),
42
 
43
  protected function save_post_array( $post_array ) {
44
  add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
45
+ add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
46
+
47
+ $has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
48
+
49
+ if ( $has_kses ) {
50
+ // Prevent KSES from corrupting JSON in post_content.
51
+ kses_remove_filters();
52
+ }
53
+
54
  $post_id = wp_insert_post($post_array);
55
+
56
+ if ( $has_kses ) {
57
+ kses_init_filters();
58
+ }
59
+
60
  remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
61
+ remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
62
 
63
  if ( is_wp_error($post_id) || empty($post_id) ) {
64
  throw new RuntimeException(__('Unable to save action.', 'action-scheduler'));
76
  return $postdata;
77
  }
78
 
79
+ /**
80
+ * Create a (probably unique) post name for scheduled actions in a more performant manner than wp_unique_post_slug().
81
+ *
82
+ * When an action's post status is transitioned to something other than 'draft', 'pending' or 'auto-draft, like 'publish'
83
+ * or 'failed' or 'trash', WordPress will find a unique slug (stored in post_name column) using the wp_unique_post_slug()
84
+ * function. This is done to ensure URL uniqueness. The approach taken by wp_unique_post_slug() is to iterate over existing
85
+ * post_name values that match, and append a number 1 greater than the largest. This makes sense when manually creating a
86
+ * post from the Edit Post screen. It becomes a bottleneck when automatically processing thousands of actions, with a
87
+ * database containing thousands of related post_name values.
88
+ *
89
+ * WordPress 5.1 introduces the 'pre_wp_unique_post_slug' filter for plugins to address this issue.
90
+ *
91
+ * We can short-circuit WordPress's wp_unique_post_slug() approach using the 'pre_wp_unique_post_slug' filter. This
92
+ * method is available to be used as a callback on that filter. It provides a more scalable approach to generating a
93
+ * post_name/slug that is probably unique. Because Action Scheduler never actually uses the post_name field, or an
94
+ * action's slug, being probably unique is good enough.
95
+ *
96
+ * For more backstory on this issue, see:
97
+ * - https://github.com/Prospress/action-scheduler/issues/44 and
98
+ * - https://core.trac.wordpress.org/ticket/21112
99
+ *
100
+ * @param string $override_slug Short-circuit return value.
101
+ * @param string $slug The desired slug (post_name).
102
+ * @param int $post_ID Post ID.
103
+ * @param string $post_status The post status.
104
+ * @param string $post_type Post type.
105
+ * @return string
106
+ */
107
+ public function set_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) {
108
+ if ( self::POST_TYPE == $post_type ) {
109
+ $override_slug = uniqid( self::POST_TYPE . '-', true ) . '-' . wp_generate_password( 32, false );
110
+ }
111
+ return $override_slug;
112
+ }
113
+
114
  protected function save_post_schedule( $post_id, $schedule ) {
115
  update_post_meta( $post_id, self::SCHEDULE_META_KEY, $schedule );
116
  }
144
 
145
  protected function make_action_from_post( $post ) {
146
  $hook = $post->post_title;
 
147
 
148
+ try {
149
+ $args = json_decode( $post->post_content, true );
150
+ $this->validate_args( $args, $post->ID );
 
151
 
152
+ $schedule = get_post_meta( $post->ID, self::SCHEDULE_META_KEY, true );
153
+ if ( empty( $schedule ) || ! is_a( $schedule, 'ActionScheduler_Schedule' ) ) {
154
+ throw ActionScheduler_InvalidActionException::from_decoding_args( $post->ID );
155
+ }
156
+ } catch ( ActionScheduler_InvalidActionException $exception ) {
157
  $schedule = new ActionScheduler_NullSchedule();
158
+ $args = array();
159
+ do_action( 'action_scheduler_failed_fetch_action', $post->ID );
160
  }
161
+
162
  $group = wp_get_object_terms( $post->ID, self::GROUP_TAXONOMY, array('fields' => 'names') );
163
  $group = empty( $group ) ? '' : reset($group);
164
 
307
  $sql = ( 'count' === $select_or_count ) ? 'SELECT count(p.ID)' : 'SELECT p.ID ';
308
  $sql .= "FROM {$wpdb->posts} p";
309
  $sql_params = array();
310
+ if ( empty( $query['group'] ) && 'group' === $query['orderby'] ) {
311
+ $sql .= " LEFT JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
312
+ $sql .= " LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
313
+ $sql .= " LEFT JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
314
+ } elseif ( ! empty( $query['group'] ) ) {
315
  $sql .= " INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
316
  $sql .= " INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
317
  $sql .= " INNER JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
318
+ $sql .= " AND t.slug=%s";
319
+ $sql_params[] = $query['group'];
 
 
 
320
  }
321
  $sql .= " WHERE post_type=%s";
322
  $sql_params[] = self::POST_TYPE;
459
  throw new InvalidArgumentException(sprintf(__('Unidentified action %s', 'action-scheduler'), $action_id));
460
  }
461
  do_action( 'action_scheduler_canceled_action', $action_id );
462
+ add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
463
  wp_trash_post($action_id);
464
+ remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
465
  }
466
 
467
  public function delete_action( $action_id ) {
644
  'ID' => 'ASC',
645
  ),
646
  'date_query' => array(
647
+ 'column' => 'post_date_gmt',
648
+ 'before' => $date->format( 'Y-m-d H:i' ),
649
+ 'inclusive' => true,
 
 
 
 
 
 
 
650
  ),
651
  'tax_query' => array(
652
  array(
735
  $status = $this->get_post_column( $action_id, 'post_status' );
736
 
737
  if ( $status === null ) {
738
+ throw new InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
739
  }
740
 
741
  return $this->get_action_status_by_post_status( $status );
766
  throw new InvalidArgumentException(sprintf(__('Unidentified action %s', 'action-scheduler'), $action_id));
767
  }
768
  add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
769
+ add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
770
  $result = wp_update_post(array(
771
  'ID' => $action_id,
772
  'post_status' => 'publish',
773
  ), TRUE);
774
  remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
775
+ remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
776
  if ( is_wp_error($result) ) {
777
  throw new RuntimeException($result->get_error_message());
778
  }
806
  $taxonomy_registrar = new ActionScheduler_wpPostStore_TaxonomyRegistrar();
807
  $taxonomy_registrar->register();
808
  }
809
+
810
+ /**
811
+ * Validate that we could decode action arguments.
812
+ *
813
+ * @param mixed $args The decoded arguments.
814
+ * @param int $action_id The action ID.
815
+ *
816
+ * @throws ActionScheduler_InvalidActionException When the decoded arguments are invalid.
817
+ */
818
+ private function validate_args( $args, $action_id ) {
819
+ // Ensure we have an array of args.
820
+ if ( ! is_array( $args ) ) {
821
+ throw ActionScheduler_InvalidActionException::from_decoding_args( $action_id );
822
+ }
823
+
824
+ // Validate JSON decoding if possible.
825
+ if ( function_exists( 'json_last_error' ) && JSON_ERROR_NONE !== json_last_error() ) {
826
+ throw ActionScheduler_InvalidActionException::from_decoding_args( $action_id );
827
+ }
828
+ }
829
  }
includes/lib/action-scheduler/composer.json CHANGED
@@ -6,6 +6,6 @@
6
  "minimum-stability": "dev",
7
  "require": {},
8
  "require-dev": {
9
- "wp-cli/wp-cli": "^1.3"
10
  }
11
  }
6
  "minimum-stability": "dev",
7
  "require": {},
8
  "require-dev": {
9
+ "wp-cli/wp-cli": "1.5.1"
10
  }
11
  }
includes/lib/action-scheduler/docs/_layouts/default.html CHANGED
@@ -21,6 +21,11 @@
21
  <body>
22
 
23
  <header>
 
 
 
 
 
24
  <div class="container">
25
  <p><a href="/usage/">Usage</a> | <a href="/admin/">Admin</a> | <a href="/wp-cli/">WP-CLI</a> | <a href="/perf/">Background Processing at Scale</a> | <a href="/api/">API</a> | <a href="/faq/">FAQ</a>
26
  <h1><a href="/">action-scheduler</a></h1>
21
  <body>
22
 
23
  <header>
24
+ <a class="github-corner" href="https://github.com/Prospress/action-scheduler/" aria-label="View on GitHub">
25
+ <svg width="80" height="80" viewBox="0 0 250 250" style="fill:#b5e853; color:#151513; position: fixed; top: 0; border: 0; right: 0;" aria-hidden="true">
26
+ <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path>
27
+ </svg>
28
+ </a>
29
  <div class="container">
30
  <p><a href="/usage/">Usage</a> | <a href="/admin/">Admin</a> | <a href="/wp-cli/">WP-CLI</a> | <a href="/perf/">Background Processing at Scale</a> | <a href="/api/">API</a> | <a href="/faq/">FAQ</a>
31
  <h1><a href="/">action-scheduler</a></h1>
includes/lib/action-scheduler/docs/assets/css/style.scss CHANGED
@@ -29,4 +29,29 @@ footer {
29
  .footer-image {
30
  text-align: center;
31
  padding-top: 1em;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
29
  .footer-image {
30
  text-align: center;
31
  padding-top: 1em;
32
+ }
33
+
34
+ .github-corner:hover .octo-arm {
35
+ animation:octocat-wave 560ms ease-in-out
36
+ }
37
+
38
+ @keyframes octocat-wave {
39
+ 0%,100%{
40
+ transform:rotate(0)
41
+ }
42
+ 20%,60%{
43
+ transform:rotate(-25deg)
44
+ }
45
+ 40%,80%{
46
+ transform:rotate(10deg)
47
+ }
48
+ }
49
+
50
+ @media (max-width:500px){
51
+ .github-corner:hover .octo-arm {
52
+ animation:none
53
+ }
54
+ .github-corner .octo-arm {
55
+ animation:octocat-wave 560ms ease-in-out
56
+ }
57
  }
includes/lib/action-scheduler/docs/faq.md CHANGED
@@ -30,7 +30,7 @@ Want to create some other method for initiating Action Scheduler? [Open a new is
30
 
31
  By default, Action Scheduler is designed to work alongside WP-Cron and not change any of its behaviour. This helps avoid unexpectedly overriding WP-Cron on sites installing your plugin, which may have nothing to do with WP-Cron.
32
 
33
- However, we can understand why you might want to replace WP-Cron completely in environments within you control, especially as it gets you the advantages of Action Scheduler. This should be possible without too much code.
34
 
35
  You could use the `'schedule_event'` hook in WordPress to use Action Scheduler for only newly scheduled WP-Cron jobs and map the `$event` param to Action Scheduler API functions.
36
 
@@ -96,6 +96,6 @@ It requires no setup, and won't override any WordPress APIs (unless you want it
96
 
97
  ### How does Action Scheduler work on WordPress Multisite?
98
 
99
- Action Scheduler is designed to manage the scheduled actions on a single site. It has no special handling for running queues across multiple sites in a multisite network. That said, because it's storage and Queue Runner are completely swappable, it would be possible to write multisite handling classes to use with it.
100
 
101
  If you'd like to create a multisite plugin to do this and release it publicly to help others, [open a new issue to let us know](https://github.com/Prospress/action-scheduler/issues/new), we'd love to help you with it.
30
 
31
  By default, Action Scheduler is designed to work alongside WP-Cron and not change any of its behaviour. This helps avoid unexpectedly overriding WP-Cron on sites installing your plugin, which may have nothing to do with WP-Cron.
32
 
33
+ However, we can understand why you might want to replace WP-Cron completely in environments within your control, especially as it gets you the advantages of Action Scheduler. This should be possible without too much code.
34
 
35
  You could use the `'schedule_event'` hook in WordPress to use Action Scheduler for only newly scheduled WP-Cron jobs and map the `$event` param to Action Scheduler API functions.
36
 
96
 
97
  ### How does Action Scheduler work on WordPress Multisite?
98
 
99
+ Action Scheduler is designed to manage the scheduled actions on a single site. It has no special handling for running queues across multiple sites in a multisite network. That said, because its storage and Queue Runner are completely swappable, it would be possible to write multisite handling classes to use with it.
100
 
101
  If you'd like to create a multisite plugin to do this and release it publicly to help others, [open a new issue to let us know](https://github.com/Prospress/action-scheduler/issues/new), we'd love to help you with it.
includes/lib/action-scheduler/docs/usage.md CHANGED
@@ -22,12 +22,12 @@ require_once( plugin_dir_path( __FILE__ ) . '/libraries/action-scheduler/action-
22
  * Schedule an action with the hook 'eg_midnight_log' to run at midnight each day
23
  * so that our callback is run then.
24
  */
25
- function eg_log_action_data() {
26
  if ( false === as_next_scheduled_action( 'eg_midnight_log' ) ) {
27
  as_schedule_recurring_action( strtotime( 'midnight tonight' ), DAY_IN_SECONDS, 'eg_midnight_log' );
28
  }
29
  }
30
- add_action( 'init', 'eg_log_action_data' );
31
 
32
  /**
33
  * A callback to run when the 'eg_midnight_log' scheduled action is run.
22
  * Schedule an action with the hook 'eg_midnight_log' to run at midnight each day
23
  * so that our callback is run then.
24
  */
25
+ function eg_schedule_midnight_log() {
26
  if ( false === as_next_scheduled_action( 'eg_midnight_log' ) ) {
27
  as_schedule_recurring_action( strtotime( 'midnight tonight' ), DAY_IN_SECONDS, 'eg_midnight_log' );
28
  }
29
  }
30
+ add_action( 'init', 'eg_schedule_midnight_log' );
31
 
32
  /**
33
  * A callback to run when the 'eg_midnight_log' scheduled action is run.
includes/lib/action-scheduler/license.txt CHANGED
@@ -1,7 +1,7 @@
1
  GNU GENERAL PUBLIC LICENSE
2
  Version 3, 29 June 2007
3
 
4
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5
  Everyone is permitted to copy and distribute verbatim copies
6
  of this license document, but changing it is not allowed.
7
 
@@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
645
  GNU General Public License for more details.
646
 
647
  You should have received a copy of the GNU General Public License
648
- along with this program. If not, see <http://www.gnu.org/licenses/>.
649
 
650
  Also add information on how to contact you by electronic and paper mail.
651
 
@@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
664
  You should also get your employer (if you work as a programmer) or school,
665
  if any, to sign a "copyright disclaimer" for the program, if necessary.
666
  For more information on this, and how to apply and follow the GNU GPL, see
667
- <http://www.gnu.org/licenses/>.
668
 
669
  The GNU General Public License does not permit incorporating your program
670
  into proprietary programs. If your program is a subroutine library, you
671
  may consider it more useful to permit linking proprietary applications with
672
  the library. If this is what you want to do, use the GNU Lesser General
673
  Public License instead of this License. But first, please read
674
- <http://www.gnu.org/philosophy/why-not-lgpl.html>.
1
  GNU GENERAL PUBLIC LICENSE
2
  Version 3, 29 June 2007
3
 
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
  Everyone is permitted to copy and distribute verbatim copies
6
  of this license document, but changing it is not allowed.
7
 
645
  GNU General Public License for more details.
646
 
647
  You should have received a copy of the GNU General Public License
648
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
649
 
650
  Also add information on how to contact you by electronic and paper mail.
651
 
664
  You should also get your employer (if you work as a programmer) or school,
665
  if any, to sign a "copyright disclaimer" for the program, if necessary.
666
  For more information on this, and how to apply and follow the GNU GPL, see
667
+ <https://www.gnu.org/licenses/>.
668
 
669
  The GNU General Public License does not permit incorporating your program
670
  into proprietary programs. If your program is a subroutine library, you
671
  may consider it more useful to permit linking proprietary applications with
672
  the library. If this is what you want to do, use the GNU Lesser General
673
  Public License instead of this License. But first, please read
674
+ <https://www.gnu.org/licenses/why-not-lgpl.html>.
includes/lib/action-scheduler/tests/phpunit/jobstore/ActionScheduler_wpPostStore_Test.php CHANGED
@@ -42,7 +42,6 @@ class ActionScheduler_wpPostStore_Test extends ActionScheduler_UnitTestCase {
42
  }
43
 
44
  /**
45
- * @expectedException ActionScheduler_InvalidActionException
46
  * @dataProvider provide_bad_args
47
  *
48
  * @param string $content
@@ -55,7 +54,8 @@ class ActionScheduler_wpPostStore_Test extends ActionScheduler_UnitTestCase {
55
  'post_content' => $content,
56
  ) );
57
 
58
- $store->fetch_action( $post_id );
 
59
  }
60
 
61
  public function provide_bad_args() {
@@ -246,7 +246,7 @@ class ActionScheduler_wpPostStore_Test extends ActionScheduler_UnitTestCase {
246
  $now = as_get_datetime_object();
247
  $store->mark_complete( $action_id );
248
 
249
- $this->assertEquals( $store->get_date($action_id)->getTimestamp(), $now->getTimestamp() );
250
 
251
  $next = $action->get_schedule()->next( $now );
252
  $new_action_id = $store->save_action( $action, $next );
42
  }
43
 
44
  /**
 
45
  * @dataProvider provide_bad_args
46
  *
47
  * @param string $content
54
  'post_content' => $content,
55
  ) );
56
 
57
+ $fetched = $store->fetch_action( $post_id );
58
+ $this->assertInstanceOf( 'ActionScheduler_NullSchedule', $fetched->get_schedule() );
59
  }
60
 
61
  public function provide_bad_args() {
246
  $now = as_get_datetime_object();
247
  $store->mark_complete( $action_id );
248
 
249
+ $this->assertEquals( $store->get_date( $action_id )->getTimestamp(), $now->getTimestamp(), '', 1 );
250
 
251
  $next = $action->get_schedule()->next( $now );
252
  $new_action_id = $store->save_action( $action, $next );
includes/lib/action-scheduler/tests/phpunit/runner/ActionScheduler_QueueRunner_Test.php CHANGED
@@ -104,7 +104,7 @@ class ActionScheduler_QueueRunner_Test extends ActionScheduler_UnitTestCase {
104
 
105
 
106
  $this->assertEquals( $random, $new_action->get_hook() );
107
- $this->assertEquals( $schedule->next(as_get_datetime_object())->getTimestamp(), $new_action->get_schedule()->next(as_get_datetime_object())->getTimestamp() );
108
  }
109
 
110
  public function test_hooked_into_wp_cron() {
@@ -206,4 +206,57 @@ class ActionScheduler_QueueRunner_Test extends ActionScheduler_UnitTestCase {
206
  public function return_6() {
207
  return 6;
208
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  }
104
 
105
 
106
  $this->assertEquals( $random, $new_action->get_hook() );
107
+ $this->assertEquals( $schedule->next( as_get_datetime_object() )->getTimestamp(), $new_action->get_schedule()->next( as_get_datetime_object() )->getTimestamp(), '', 1 );
108
  }
109
 
110
  public function test_hooked_into_wp_cron() {
206
  public function return_6() {
207
  return 6;
208
  }
209
+
210
+ public function test_store_fetch_action_failure_schedule_next_instance() {
211
+ $random = md5( rand() );
212
+ $schedule = new ActionScheduler_IntervalSchedule( as_get_datetime_object( '12 hours ago' ), DAY_IN_SECONDS );
213
+ $action = new ActionScheduler_Action( $random, array(), $schedule );
214
+ $action_id = ActionScheduler::store()->save_action( $action );
215
+
216
+ // Set up a mock store that will throw an exception when fetching actions.
217
+ $store = $this
218
+ ->getMockBuilder( 'ActionScheduler_wpPostStore' )
219
+ ->setMethods( array( 'fetch_action' ) )
220
+ ->getMock();
221
+ $store
222
+ ->method( 'fetch_action' )
223
+ ->with( $action_id )
224
+ ->will( $this->throwException( new Exception() ) );
225
+
226
+ // Set up a mock queue runner to verify that schedule_next_instance()
227
+ // isn't called for an undefined $action.
228
+ $runner = $this
229
+ ->getMockBuilder( 'ActionScheduler_QueueRunner' )
230
+ ->setConstructorArgs( array( $store ) )
231
+ ->setMethods( array( 'schedule_next_instance' ) )
232
+ ->getMock();
233
+ $runner
234
+ ->expects( $this->never() )
235
+ ->method( 'schedule_next_instance' );
236
+
237
+ $runner->run();
238
+
239
+ // Set up a mock store that will throw an exception when fetching actions.
240
+ $store2 = $this
241
+ ->getMockBuilder( 'ActionScheduler_wpPostStore' )
242
+ ->setMethods( array( 'fetch_action' ) )
243
+ ->getMock();
244
+ $store2
245
+ ->method( 'fetch_action' )
246
+ ->with( $action_id )
247
+ ->willReturn( null );
248
+
249
+ // Set up a mock queue runner to verify that schedule_next_instance()
250
+ // isn't called for an undefined $action.
251
+ $runner2 = $this
252
+ ->getMockBuilder( 'ActionScheduler_QueueRunner' )
253
+ ->setConstructorArgs( array( $store ) )
254
+ ->setMethods( array( 'schedule_next_instance' ) )
255
+ ->getMock();
256
+ $runner2
257
+ ->expects( $this->never() )
258
+ ->method( 'schedule_next_instance' );
259
+
260
+ $runner2->run();
261
+ }
262
  }
readme.txt CHANGED
@@ -6,7 +6,7 @@ Requires at least: 4.7.0
6
  Tested up to: 5.2.2
7
  WC tested up to: 3.7.0
8
  Requires PHP: 5.6
9
- Stable tag: 2.3.1
10
  License: GPLv3
11
 
12
  Simple and flexible Mailchimp integration for WooCommerce.
@@ -119,6 +119,10 @@ Also, if you enjoy using the software [we'd love it if you could give us a revie
119
 
120
  == Changelog ==
121
 
 
 
 
 
122
  #### 2.3.1 - August 26, 2019
123
  - Fix tag retrieval to return tag IDs.
124
  - Fix migrations.
6
  Tested up to: 5.2.2
7
  WC tested up to: 3.7.0
8
  Requires PHP: 5.6
9
+ Stable tag: 2.3.2
10
  License: GPLv3
11
 
12
  Simple and flexible Mailchimp integration for WooCommerce.
119
 
120
  == Changelog ==
121
 
122
+ #### 2.3.2 - August 27, 2019
123
+ - Upgrade ActionScheduler to 2.2.5
124
+ - Fix encoding of unicode/international characters.
125
+
126
  #### 2.3.1 - August 26, 2019
127
  - Fix tag retrieval to return tag IDs.
128
  - Fix migrations.
woocommerce-mailchimp.php CHANGED
@@ -5,7 +5,7 @@
5
  * Description: WooCommerce Mailchimp provides simple and flexible Mailchimp integration for WooCommerce.
6
  * Author: Saint Systems
7
  * Author URI: https://www.saintsystems.com
8
- * Version: 2.3.1
9
  * WC tested up to: 3.7.0
10
  * Text Domain: woocommerce-mailchimp
11
  * Domain Path: languages
5
  * Description: WooCommerce Mailchimp provides simple and flexible Mailchimp integration for WooCommerce.
6
  * Author: Saint Systems
7
  * Author URI: https://www.saintsystems.com
8
+ * Version: 2.3.2
9
  * WC tested up to: 3.7.0
10
  * Text Domain: woocommerce-mailchimp
11
  * Domain Path: languages