Email Subscribers & Newsletters - Version 4.9.1

Version Description

  • New: Added {{POSTIMAGE-URL}} keyword for Post Notification
  • Update: Action Scheduler Library to 3.3.0
  • Fix: Amazon SES batch sending issue [PRO]

=

Download this release

Release Info

Developer Icegram
Plugin Icon 128x128 Email Subscribers & Newsletters
Version 4.9.1
Comparing to
See all releases

Code changes from version 4.9.0 to 4.9.1

Files changed (78) hide show
  1. email-subscribers.php +2 -2
  2. lite/includes/classes/class-es-handle-post-notification.php +10 -1
  3. lite/includes/libraries/action-scheduler/action-scheduler.php +25 -13
  4. lite/includes/libraries/action-scheduler/changelog.txt +32 -0
  5. lite/includes/libraries/action-scheduler/classes/ActionScheduler_ActionClaim.php +3 -3
  6. lite/includes/libraries/action-scheduler/classes/ActionScheduler_ActionFactory.php +30 -30
  7. lite/includes/libraries/action-scheduler/classes/ActionScheduler_AdminView.php +3 -4
  8. lite/includes/libraries/action-scheduler/classes/ActionScheduler_Compatibility.php +14 -4
  9. lite/includes/libraries/action-scheduler/classes/ActionScheduler_DataController.php +2 -2
  10. lite/includes/libraries/action-scheduler/classes/ActionScheduler_FatalErrorMonitor.php +10 -10
  11. lite/includes/libraries/action-scheduler/classes/ActionScheduler_ListTable.php +56 -30
  12. lite/includes/libraries/action-scheduler/classes/ActionScheduler_LogEntry.php +5 -5
  13. lite/includes/libraries/action-scheduler/classes/ActionScheduler_NullLogEntry.php +1 -1
  14. lite/includes/libraries/action-scheduler/classes/ActionScheduler_OptionLock.php +0 -1
  15. lite/includes/libraries/action-scheduler/classes/ActionScheduler_QueueCleaner.php +26 -29
  16. lite/includes/libraries/action-scheduler/classes/ActionScheduler_QueueRunner.php +6 -7
  17. lite/includes/libraries/action-scheduler/classes/ActionScheduler_Versions.php +13 -13
  18. lite/includes/libraries/action-scheduler/classes/ActionScheduler_WPCommentCleaner.php +3 -15
  19. lite/includes/libraries/action-scheduler/classes/ActionScheduler_wcSystemStatus.php +21 -9
  20. lite/includes/libraries/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_QueueRunner.php +1 -1
  21. lite/includes/libraries/action-scheduler/classes/WP_CLI/Migration_Command.php +61 -103
  22. lite/includes/libraries/action-scheduler/classes/WP_CLI/ProgressBar.php +1 -1
  23. lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler.php +14 -17
  24. lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Abstract_ListTable.php +136 -58
  25. lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php +2 -2
  26. lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Abstract_RecurringSchedule.php +9 -13
  27. lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schedule.php +2 -3
  28. lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schema.php +47 -10
  29. lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Lock.php +2 -2
  30. lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Logger.php +9 -10
  31. lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Store.php +99 -25
  32. lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_TimezoneHelper.php +9 -9
  33. lite/includes/libraries/action-scheduler/classes/actions/ActionScheduler_Action.php +9 -9
  34. lite/includes/libraries/action-scheduler/classes/actions/ActionScheduler_CanceledAction.php +3 -3
  35. lite/includes/libraries/action-scheduler/classes/actions/ActionScheduler_FinishedAction.php +2 -2
  36. lite/includes/libraries/action-scheduler/classes/actions/ActionScheduler_NullAction.php +2 -2
  37. lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_DBLogger.php +13 -9
  38. lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_DBStore.php +132 -116
  39. lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_HybridStore.php +17 -18
  40. lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_wpCommentLogger.php +36 -44
  41. lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore.php +351 -154
  42. lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_PostStatusRegistrar.php +0 -1
  43. lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_PostTypeRegistrar.php +23 -24
  44. lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_TaxonomyRegistrar.php +6 -7
  45. lite/includes/libraries/action-scheduler/classes/migration/ActionMigrator.php +2 -2
  46. lite/includes/libraries/action-scheduler/classes/migration/ActionScheduler_DBStoreMigrator.php +5 -5
  47. lite/includes/libraries/action-scheduler/classes/migration/BatchFetcher.php +15 -20
  48. lite/includes/libraries/action-scheduler/classes/migration/Controller.php +24 -4
  49. lite/includes/libraries/action-scheduler/classes/migration/DryRun_LogMigrator.php +1 -1
  50. lite/includes/libraries/action-scheduler/classes/migration/Runner.php +8 -11
  51. lite/includes/libraries/action-scheduler/classes/migration/Scheduler.php +2 -2
  52. lite/includes/libraries/action-scheduler/classes/schedules/ActionScheduler_CanceledSchedule.php +1 -1
  53. lite/includes/libraries/action-scheduler/classes/schedules/ActionScheduler_CronSchedule.php +8 -11
  54. lite/includes/libraries/action-scheduler/classes/schedules/ActionScheduler_IntervalSchedule.php +6 -9
  55. lite/includes/libraries/action-scheduler/classes/schedules/ActionScheduler_NullSchedule.php +0 -1
  56. lite/includes/libraries/action-scheduler/classes/schedules/ActionScheduler_Schedule.php +2 -2
  57. lite/includes/libraries/action-scheduler/classes/schedules/ActionScheduler_SimpleSchedule.php +4 -7
  58. lite/includes/libraries/action-scheduler/classes/schema/ActionScheduler_LoggerSchema.php +51 -7
  59. lite/includes/libraries/action-scheduler/classes/schema/ActionScheduler_StoreSchema.php +58 -9
  60. lite/includes/libraries/action-scheduler/deprecated/ActionScheduler_AdminView_Deprecated.php +5 -5
  61. lite/includes/libraries/action-scheduler/deprecated/ActionScheduler_Schedule_Deprecated.php +2 -2
  62. lite/includes/libraries/action-scheduler/deprecated/ActionScheduler_Store_Deprecated.php +0 -1
  63. lite/includes/libraries/action-scheduler/deprecated/functions.php +24 -24
  64. lite/includes/libraries/action-scheduler/functions.php +94 -50
  65. lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression.php +302 -291
  66. lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_AbstractField.php +83 -78
  67. lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_DayOfMonthField.php +78 -74
  68. lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_DayOfWeekField.php +93 -90
  69. lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_FieldFactory.php +44 -43
  70. lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_FieldInterface.php +29 -29
  71. lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_HoursField.php +36 -33
  72. lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_MinutesField.php +28 -25
  73. lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_MonthField.php +43 -50
  74. lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_YearField.php +32 -29
  75. lite/includes/libraries/action-scheduler/readme.txt +76 -0
  76. lite/languages/email-subscribers.pot +2 -2
  77. lite/public/partials/class-es-shortcode.php +1 -1
  78. readme.txt +11 -3
email-subscribers.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: Email Subscribers & Newsletters
4
  * Plugin URI: https://www.icegram.com/
5
  * Description: Add subscription forms on website, send HTML newsletters & automatically notify subscribers about new blog posts once it is published.
6
- * Version: 4.9.0
7
  * Author: Icegram
8
  * Author URI: https://www.icegram.com/
9
  * Requires at least: 3.9
@@ -187,7 +187,7 @@ if ( 'premium' === $ig_es_plan ) {
187
  /* ***************************** Initial Compatibility Work (End) ******************* */
188
 
189
  if ( ! defined( 'ES_PLUGIN_VERSION' ) ) {
190
- define( 'ES_PLUGIN_VERSION', '4.9.0' );
191
  }
192
 
193
  // Plugin Folder Path.
3
  * Plugin Name: Email Subscribers & Newsletters
4
  * Plugin URI: https://www.icegram.com/
5
  * Description: Add subscription forms on website, send HTML newsletters & automatically notify subscribers about new blog posts once it is published.
6
+ * Version: 4.9.1
7
  * Author: Icegram
8
  * Author URI: https://www.icegram.com/
9
  * Requires at least: 3.9
187
  /* ***************************** Initial Compatibility Work (End) ******************* */
188
 
189
  if ( ! defined( 'ES_PLUGIN_VERSION' ) ) {
190
+ define( 'ES_PLUGIN_VERSION', '4.9.1' );
191
  }
192
 
193
  // Plugin Folder Path.
lite/includes/classes/class-es-handle-post-notification.php CHANGED
@@ -176,6 +176,7 @@ class ES_Handle_Post_Notification {
176
  // Size of {{POSTIMAGE}}
177
  $post_thumbnail = '';
178
  $post_thumbnail_link = '';
 
179
  if ( ( function_exists( 'has_post_thumbnail' ) ) && ( has_post_thumbnail( $post_id ) ) ) {
180
  $es_post_image_size = get_option( 'ig_es_post_image_size', 'full' );
181
  switch ( $es_post_image_size ) {
@@ -196,8 +197,16 @@ class ES_Handle_Post_Notification {
196
  $post_thumbnail_link = "<a href='" . $post_link . "' target='_blank'>" . $post_thumbnail . '</a>';
197
  }
198
 
199
- $es_templ_body = str_replace( '{{POSTIMAGE}}', $post_thumbnail_link, $es_templ_body );
200
 
 
 
 
 
 
 
 
 
201
  // Get post description
202
  $post_description_length = 50;
203
  $post_description = $post->post_content;
176
  // Size of {{POSTIMAGE}}
177
  $post_thumbnail = '';
178
  $post_thumbnail_link = '';
179
+ $post_thumbnail_url = '';
180
  if ( ( function_exists( 'has_post_thumbnail' ) ) && ( has_post_thumbnail( $post_id ) ) ) {
181
  $es_post_image_size = get_option( 'ig_es_post_image_size', 'full' );
182
  switch ( $es_post_image_size ) {
197
  $post_thumbnail_link = "<a href='" . $post_link . "' target='_blank'>" . $post_thumbnail . '</a>';
198
  }
199
 
200
+ $es_templ_body = str_replace( '{{POSTIMAGE}}', $post_thumbnail_link, $es_templ_body );
201
 
202
+ $post_thumbnail_id = get_post_thumbnail_id( $post_id );
203
+
204
+ if ( ! empty( $post_thumbnail_id ) ) {
205
+ $post_thumbnail_url = wp_get_attachment_url( $post_thumbnail_id );
206
+ }
207
+
208
+ $es_templ_body = str_replace( '{{POSTIMAGE-URL}}', $post_thumbnail_url, $es_templ_body );
209
+
210
  // Get post description
211
  $post_description_length = 50;
212
  $post_description = $post->post_content;
lite/includes/libraries/action-scheduler/action-scheduler.php CHANGED
@@ -1,11 +1,11 @@
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: Automattic
7
  * Author URI: https://automattic.com/
8
- * Version: 3.1.6
9
  * License: GPLv3
10
  *
11
  * Copyright 2019 Automattic, Inc. (https://automattic.com/contact/)
@@ -23,30 +23,42 @@
23
  * You should have received a copy of the GNU General Public License
24
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
25
  *
 
26
  */
27
 
28
- if ( ! function_exists( 'action_scheduler_register_3_dot_1_dot_6' ) ) {
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_3_dot_1_dot_6', 0, 0 );
36
 
37
- function action_scheduler_register_3_dot_1_dot_6() {
 
 
 
38
  $versions = ActionScheduler_Versions::instance();
39
- $versions->register( '3.1.6', 'action_scheduler_initialize_3_dot_1_dot_6' );
40
  }
41
 
42
- function action_scheduler_initialize_3_dot_1_dot_6() {
43
- require_once 'classes/abstracts/ActionScheduler.php';
44
- ActionScheduler::init( __FILE__ );
 
 
 
 
 
 
 
 
45
  }
46
 
47
  // Support usage in themes - load this version if no plugin has loaded a version yet.
48
- if ( did_action( 'plugins_loaded' ) && ! class_exists( 'ActionScheduler' ) ) {
49
- action_scheduler_initialize_3_dot_1_dot_6();
50
  do_action( 'action_scheduler_pre_theme_init' );
51
  ActionScheduler_Versions::initialize_latest_version();
52
  }
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: Automattic
7
  * Author URI: https://automattic.com/
8
+ * Version: 3.3.0
9
  * License: GPLv3
10
  *
11
  * Copyright 2019 Automattic, Inc. (https://automattic.com/contact/)
23
  * You should have received a copy of the GNU General Public License
24
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
25
  *
26
+ * @package ActionScheduler
27
  */
28
 
29
+ if ( ! function_exists( 'action_scheduler_register_3_dot_3_dot_0' ) && function_exists( 'add_action' ) ) {
30
 
31
+ if ( ! class_exists( 'ActionScheduler_Versions', false ) ) {
32
+ require_once __DIR__ . '/classes/ActionScheduler_Versions.php';
33
  add_action( 'plugins_loaded', array( 'ActionScheduler_Versions', 'initialize_latest_version' ), 1, 0 );
34
  }
35
 
36
+ add_action( 'plugins_loaded', 'action_scheduler_register_3_dot_3_dot_0', 0, 0 );
37
 
38
+ /**
39
+ * Registers this version of Action Scheduler.
40
+ */
41
+ function action_scheduler_register_3_dot_3_dot_0() {
42
  $versions = ActionScheduler_Versions::instance();
43
+ $versions->register( '3.3.0', 'action_scheduler_initialize_3_dot_3_dot_0' );
44
  }
45
 
46
+ /**
47
+ * Initializes this version of Action Scheduler.
48
+ */
49
+ function action_scheduler_initialize_3_dot_3_dot_0() {
50
+ // A final safety check is required even here, because historic versions of Action Scheduler
51
+ // followed a different pattern (in some unusual cases, we could reach this point and the
52
+ // ActionScheduler class is already defined—so we need to guard against that).
53
+ if ( ! class_exists( 'ActionScheduler', false ) ) {
54
+ require_once __DIR__ . '/classes/abstracts/ActionScheduler.php';
55
+ ActionScheduler::init( __FILE__ );
56
+ }
57
  }
58
 
59
  // Support usage in themes - load this version if no plugin has loaded a version yet.
60
+ if ( did_action( 'plugins_loaded' ) && ! doing_action( 'plugins_loaded' ) && ! class_exists( 'ActionScheduler', false ) ) {
61
+ action_scheduler_initialize_3_dot_3_dot_0();
62
  do_action( 'action_scheduler_pre_theme_init' );
63
  ActionScheduler_Versions::initialize_latest_version();
64
  }
lite/includes/libraries/action-scheduler/changelog.txt ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *** Changelog ***
2
+
3
+ = 3.3.0 - 2021-09-15 =
4
+ * Enhancement - Adds as_has_scheduled_action() to provide a performant way to test for existing actions. #645
5
+ * Fix - Improves compatibility with environments where NO_ZERO_DATE is enabled. #519
6
+ * Fix - Adds safety checks to guard against errors when our database tables cannot be created. #645
7
+ * Dev - Now supports queries that use multiple statuses. #649
8
+ * Dev - Minimum requirements for WordPress and PHP bumped (to 5.2 and 5.6 respectively). #723
9
+
10
+ = 3.2.1 - 2021-06-21 =
11
+ * Fix - Add extra safety/account for different versions of AS and different loading patterns. #714
12
+ * Fix - Handle hidden columns (Tools → Scheduled Actions) | #600.
13
+
14
+ = 3.2.0 - 2021-06-03 =
15
+ * Fix - Add "no ordering" option to as_next_scheduled_action().
16
+ * Fix - Add secondary scheduled date checks when claiming actions (DBStore) | #634.
17
+ * Fix - Add secondary scheduled date checks when claiming actions (wpPostStore) | #634.
18
+ * Fix - Adds a new index to the action table, reducing the potential for deadlocks (props: @glagonikas).
19
+ * Fix - Fix unit tests infrastructure and adapt tests to PHP 8.
20
+ * Fix - Identify in-use data store.
21
+ * Fix - Improve test_migration_is_scheduled.
22
+ * Fix - PHP notice on list table.
23
+ * Fix - Speed up clean up and batch selects.
24
+ * Fix - Update pending dependencies.
25
+ * Fix - [PHP 8.0] Only pass action arg values through to do_action_ref_array().
26
+ * Fix - [PHP 8] Set the PHP version to 7.1 in composer.json for PHP 8 compatibility.
27
+ * Fix - add is_initialized() to docs.
28
+ * Fix - fix file permissions.
29
+ * Fix - fixes #664 by replacing __ with esc_html__.
30
+
31
+ = 3.1.6 - 2020-05-12 =
32
+ * Change log starts.
lite/includes/libraries/action-scheduler/classes/ActionScheduler_ActionClaim.php CHANGED
@@ -4,11 +4,11 @@
4
  * Class ActionScheduler_ActionClaim
5
  */
6
  class ActionScheduler_ActionClaim {
7
- private $id = '';
8
  private $action_ids = array();
9
 
10
  public function __construct( $id, array $action_ids ) {
11
- $this->id = $id;
12
  $this->action_ids = $action_ids;
13
  }
14
 
@@ -20,4 +20,4 @@ class ActionScheduler_ActionClaim {
20
  return $this->action_ids;
21
  }
22
  }
23
-
4
  * Class ActionScheduler_ActionClaim
5
  */
6
  class ActionScheduler_ActionClaim {
7
+ private $id = '';
8
  private $action_ids = array();
9
 
10
  public function __construct( $id, array $action_ids ) {
11
+ $this->id = $id;
12
  $this->action_ids = $action_ids;
13
  }
14
 
20
  return $this->action_ids;
21
  }
22
  }
23
+
lite/includes/libraries/action-scheduler/classes/ActionScheduler_ActionFactory.php CHANGED
@@ -6,27 +6,27 @@
6
  class ActionScheduler_ActionFactory {
7
 
8
  /**
9
- * @param string $status The action's status in the data store
10
- * @param string $hook The hook to trigger when this action runs
11
- * @param array $args Args to pass to callbacks when the hook is triggered
12
  * @param ActionScheduler_Schedule $schedule The action's schedule
13
- * @param string $group A group to put the action in
14
  *
15
  * @return ActionScheduler_Action An instance of the stored action
16
  */
17
  public function get_stored_action( $status, $hook, array $args = array(), ActionScheduler_Schedule $schedule = null, $group = '' ) {
18
 
19
  switch ( $status ) {
20
- case ActionScheduler_Store::STATUS_PENDING:
21
  $action_class = 'ActionScheduler_Action';
22
  break;
23
- case ActionScheduler_Store::STATUS_CANCELED:
24
  $action_class = 'ActionScheduler_CanceledAction';
25
  if ( ! is_null( $schedule ) && ! is_a( $schedule, 'ActionScheduler_CanceledSchedule' ) && ! is_a( $schedule, 'ActionScheduler_NullSchedule' ) ) {
26
  $schedule = new ActionScheduler_CanceledSchedule( $schedule->get_date() );
27
  }
28
  break;
29
- default:
30
  $action_class = 'ActionScheduler_FinishedAction';
31
  break;
32
  }
@@ -58,29 +58,29 @@ class ActionScheduler_ActionFactory {
58
  * claimed by both the existing WP Cron and WP CLI runners, as well as a new async request runner.
59
  *
60
  * @param string $hook The hook to trigger when this action runs
61
- * @param array $args Args to pass when the hook is triggered
62
  * @param string $group A group to put the action in
63
  *
64
  * @return int The ID of the stored action
65
  */
66
  public function async( $hook, $args = array(), $group = '' ) {
67
  $schedule = new ActionScheduler_NullSchedule();
68
- $action = new ActionScheduler_Action( $hook, $args, $schedule, $group );
69
  return $this->store( $action );
70
  }
71
 
72
  /**
73
  * @param string $hook The hook to trigger when this action runs
74
- * @param array $args Args to pass when the hook is triggered
75
- * @param int $when Unix timestamp when the action will run
76
  * @param string $group A group to put the action in
77
  *
78
  * @return int The ID of the stored action
79
  */
80
  public function single( $hook, $args = array(), $when = null, $group = '' ) {
81
- $date = as_get_datetime_object( $when );
82
  $schedule = new ActionScheduler_SimpleSchedule( $date );
83
- $action = new ActionScheduler_Action( $hook, $args, $schedule, $group );
84
  return $this->store( $action );
85
  }
86
 
@@ -88,20 +88,20 @@ class ActionScheduler_ActionFactory {
88
  * Create the first instance of an action recurring on a given interval.
89
  *
90
  * @param string $hook The hook to trigger when this action runs
91
- * @param array $args Args to pass when the hook is triggered
92
- * @param int $first Unix timestamp for the first run
93
- * @param int $interval Seconds between runs
94
  * @param string $group A group to put the action in
95
  *
96
  * @return int The ID of the stored action
97
  */
98
  public function recurring( $hook, $args = array(), $first = null, $interval = null, $group = '' ) {
99
- if ( empty( $interval ) ) {
100
  return $this->single( $hook, $args, $first, $group );
101
  }
102
- $date = as_get_datetime_object( $first );
103
  $schedule = new ActionScheduler_IntervalSchedule( $date, $interval );
104
- $action = new ActionScheduler_Action( $hook, $args, $schedule, $group );
105
  return $this->store( $action );
106
  }
107
 
@@ -109,23 +109,23 @@ class ActionScheduler_ActionFactory {
109
  * Create the first instance of an action recurring on a Cron schedule.
110
  *
111
  * @param string $hook The hook to trigger when this action runs
112
- * @param array $args Args to pass when the hook is triggered
113
- * @param int $base_timestamp The first instance of the action will be scheduled
114
- * to run at a time calculated after this timestamp matching the cron
115
- * expression. This can be used to delay the first instance of the action.
116
- * @param int $schedule A cron definition string
117
  * @param string $group A group to put the action in
118
  *
119
  * @return int The ID of the stored action
120
  */
121
  public function cron( $hook, $args = array(), $base_timestamp = null, $schedule = null, $group = '' ) {
122
- if ( empty( $schedule ) ) {
123
  return $this->single( $hook, $args, $base_timestamp, $group );
124
  }
125
- $date = as_get_datetime_object( $base_timestamp );
126
- $cron = CronExpression::factory( $schedule );
127
  $schedule = new ActionScheduler_CronSchedule( $date, $cron );
128
- $action = new ActionScheduler_Action( $hook, $args, $schedule, $group );
129
  return $this->store( $action );
130
  }
131
 
@@ -162,8 +162,8 @@ class ActionScheduler_ActionFactory {
162
  }
163
 
164
  $schedule_class = get_class( $schedule );
165
- $new_schedule = new $schedule( $next, $schedule->get_recurrence(), $schedule->get_first_date() );
166
- $new_action = new ActionScheduler_Action( $action->get_hook(), $action->get_args(), $new_schedule, $action->get_group() );
167
  return $this->store( $new_action );
168
  }
169
 
6
  class ActionScheduler_ActionFactory {
7
 
8
  /**
9
+ * @param string $status The action's status in the data store
10
+ * @param string $hook The hook to trigger when this action runs
11
+ * @param array $args Args to pass to callbacks when the hook is triggered
12
  * @param ActionScheduler_Schedule $schedule The action's schedule
13
+ * @param string $group A group to put the action in
14
  *
15
  * @return ActionScheduler_Action An instance of the stored action
16
  */
17
  public function get_stored_action( $status, $hook, array $args = array(), ActionScheduler_Schedule $schedule = null, $group = '' ) {
18
 
19
  switch ( $status ) {
20
+ case ActionScheduler_Store::STATUS_PENDING :
21
  $action_class = 'ActionScheduler_Action';
22
  break;
23
+ case ActionScheduler_Store::STATUS_CANCELED :
24
  $action_class = 'ActionScheduler_CanceledAction';
25
  if ( ! is_null( $schedule ) && ! is_a( $schedule, 'ActionScheduler_CanceledSchedule' ) && ! is_a( $schedule, 'ActionScheduler_NullSchedule' ) ) {
26
  $schedule = new ActionScheduler_CanceledSchedule( $schedule->get_date() );
27
  }
28
  break;
29
+ default :
30
  $action_class = 'ActionScheduler_FinishedAction';
31
  break;
32
  }
58
  * claimed by both the existing WP Cron and WP CLI runners, as well as a new async request runner.
59
  *
60
  * @param string $hook The hook to trigger when this action runs
61
+ * @param array $args Args to pass when the hook is triggered
62
  * @param string $group A group to put the action in
63
  *
64
  * @return int The ID of the stored action
65
  */
66
  public function async( $hook, $args = array(), $group = '' ) {
67
  $schedule = new ActionScheduler_NullSchedule();
68
+ $action = new ActionScheduler_Action( $hook, $args, $schedule, $group );
69
  return $this->store( $action );
70
  }
71
 
72
  /**
73
  * @param string $hook The hook to trigger when this action runs
74
+ * @param array $args Args to pass when the hook is triggered
75
+ * @param int $when Unix timestamp when the action will run
76
  * @param string $group A group to put the action in
77
  *
78
  * @return int The ID of the stored action
79
  */
80
  public function single( $hook, $args = array(), $when = null, $group = '' ) {
81
+ $date = as_get_datetime_object( $when );
82
  $schedule = new ActionScheduler_SimpleSchedule( $date );
83
+ $action = new ActionScheduler_Action( $hook, $args, $schedule, $group );
84
  return $this->store( $action );
85
  }
86
 
88
  * Create the first instance of an action recurring on a given interval.
89
  *
90
  * @param string $hook The hook to trigger when this action runs
91
+ * @param array $args Args to pass when the hook is triggered
92
+ * @param int $first Unix timestamp for the first run
93
+ * @param int $interval Seconds between runs
94
  * @param string $group A group to put the action in
95
  *
96
  * @return int The ID of the stored action
97
  */
98
  public function recurring( $hook, $args = array(), $first = null, $interval = null, $group = '' ) {
99
+ if ( empty($interval) ) {
100
  return $this->single( $hook, $args, $first, $group );
101
  }
102
+ $date = as_get_datetime_object( $first );
103
  $schedule = new ActionScheduler_IntervalSchedule( $date, $interval );
104
+ $action = new ActionScheduler_Action( $hook, $args, $schedule, $group );
105
  return $this->store( $action );
106
  }
107
 
109
  * Create the first instance of an action recurring on a Cron schedule.
110
  *
111
  * @param string $hook The hook to trigger when this action runs
112
+ * @param array $args Args to pass when the hook is triggered
113
+ * @param int $base_timestamp The first instance of the action will be scheduled
114
+ * to run at a time calculated after this timestamp matching the cron
115
+ * expression. This can be used to delay the first instance of the action.
116
+ * @param int $schedule A cron definition string
117
  * @param string $group A group to put the action in
118
  *
119
  * @return int The ID of the stored action
120
  */
121
  public function cron( $hook, $args = array(), $base_timestamp = null, $schedule = null, $group = '' ) {
122
+ if ( empty($schedule) ) {
123
  return $this->single( $hook, $args, $base_timestamp, $group );
124
  }
125
+ $date = as_get_datetime_object( $base_timestamp );
126
+ $cron = CronExpression::factory( $schedule );
127
  $schedule = new ActionScheduler_CronSchedule( $date, $cron );
128
+ $action = new ActionScheduler_Action( $hook, $args, $schedule, $group );
129
  return $this->store( $action );
130
  }
131
 
162
  }
163
 
164
  $schedule_class = get_class( $schedule );
165
+ $new_schedule = new $schedule( $next, $schedule->get_recurrence(), $schedule->get_first_date() );
166
+ $new_action = new ActionScheduler_Action( $action->get_hook(), $action->get_args(), $new_schedule, $action->get_group() );
167
  return $this->store( $new_action );
168
  }
169
 
lite/includes/libraries/action-scheduler/classes/ActionScheduler_AdminView.php CHANGED
@@ -2,12 +2,11 @@
2
 
3
  /**
4
  * Class ActionScheduler_AdminView
5
- *
6
  * @codeCoverageIgnore
7
  */
8
  class ActionScheduler_AdminView extends ActionScheduler_AdminView_Deprecated {
9
 
10
- private static $admin_view = null;
11
 
12
  private static $screen_id = 'tools_page_action-scheduler';
13
 
@@ -21,7 +20,7 @@ class ActionScheduler_AdminView extends ActionScheduler_AdminView_Deprecated {
21
  public static function instance() {
22
 
23
  if ( empty( self::$admin_view ) ) {
24
- $class = apply_filters( 'action_scheduler_admin_view_class', 'ActionScheduler_AdminView' );
25
  self::$admin_view = new $class();
26
  }
27
 
@@ -79,7 +78,7 @@ class ActionScheduler_AdminView extends ActionScheduler_AdminView_Deprecated {
79
  'action-scheduler',
80
  array( $this, 'render_admin_ui' )
81
  );
82
- add_action( 'load-' . $hook_suffix, array( $this, 'process_admin_ui' ) );
83
  }
84
 
85
  /**
2
 
3
  /**
4
  * Class ActionScheduler_AdminView
 
5
  * @codeCoverageIgnore
6
  */
7
  class ActionScheduler_AdminView extends ActionScheduler_AdminView_Deprecated {
8
 
9
+ private static $admin_view = NULL;
10
 
11
  private static $screen_id = 'tools_page_action-scheduler';
12
 
20
  public static function instance() {
21
 
22
  if ( empty( self::$admin_view ) ) {
23
+ $class = apply_filters('action_scheduler_admin_view_class', 'ActionScheduler_AdminView');
24
  self::$admin_view = new $class();
25
  }
26
 
78
  'action-scheduler',
79
  array( $this, 'render_admin_ui' )
80
  );
81
+ add_action( 'load-' . $hook_suffix , array( $this, 'process_admin_ui' ) );
82
  }
83
 
84
  /**
lite/includes/libraries/action-scheduler/classes/ActionScheduler_Compatibility.php CHANGED
@@ -83,17 +83,27 @@ class ActionScheduler_Compatibility {
83
  *
84
  * Only allows raising the existing limit and prevents lowering it. Wrapper for wc_set_time_limit(), when available.
85
  *
86
- * @param int The time limit in seconds.
87
  */
88
  public static function raise_time_limit( $limit = 0 ) {
89
- if ( $limit < ini_get( 'max_execution_time' ) ) {
 
 
 
 
 
 
 
 
 
 
90
  return;
91
  }
92
 
93
  if ( function_exists( 'wc_set_time_limit' ) ) {
94
  wc_set_time_limit( $limit );
95
- } elseif ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) ) {
96
- @set_time_limit( $limit );
97
  }
98
  }
99
  }
83
  *
84
  * Only allows raising the existing limit and prevents lowering it. Wrapper for wc_set_time_limit(), when available.
85
  *
86
+ * @param int $limit The time limit in seconds.
87
  */
88
  public static function raise_time_limit( $limit = 0 ) {
89
+ $limit = (int) $limit;
90
+ $max_execution_time = (int) ini_get( 'max_execution_time' );
91
+
92
+ /*
93
+ * If the max execution time is already unlimited (zero), or if it exceeds or is equal to the proposed
94
+ * limit, there is no reason for us to make further changes (we never want to lower it).
95
+ */
96
+ if (
97
+ 0 === $max_execution_time
98
+ || ( $max_execution_time >= $limit && $limit !== 0 )
99
+ ) {
100
  return;
101
  }
102
 
103
  if ( function_exists( 'wc_set_time_limit' ) ) {
104
  wc_set_time_limit( $limit );
105
+ } elseif ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) { // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.safe_modeDeprecatedRemoved
106
+ @set_time_limit( $limit ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
107
  }
108
  }
109
  }
lite/includes/libraries/action-scheduler/classes/ActionScheduler_DataController.php CHANGED
@@ -18,10 +18,10 @@ class ActionScheduler_DataController {
18
  const DATASTORE_CLASS = 'ActionScheduler_DBStore';
19
 
20
  /** Logger data store class name. */
21
- const LOGGER_CLASS = 'ActionScheduler_DBLogger';
22
 
23
  /** Migration status option name. */
24
- const STATUS_FLAG = 'action_scheduler_migration_status';
25
 
26
  /** Migration status option value. */
27
  const STATUS_COMPLETE = 'complete';
18
  const DATASTORE_CLASS = 'ActionScheduler_DBStore';
19
 
20
  /** Logger data store class name. */
21
+ const LOGGER_CLASS = 'ActionScheduler_DBLogger';
22
 
23
  /** Migration status option name. */
24
+ const STATUS_FLAG = 'action_scheduler_migration_status';
25
 
26
  /** Migration status option value. */
27
  const STATUS_COMPLETE = 'complete';
lite/includes/libraries/action-scheduler/classes/ActionScheduler_FatalErrorMonitor.php CHANGED
@@ -5,9 +5,9 @@
5
  */
6
  class ActionScheduler_FatalErrorMonitor {
7
  /** @var ActionScheduler_ActionClaim */
8
- private $claim = null;
9
  /** @var ActionScheduler_Store */
10
- private $store = null;
11
  private $action_id = 0;
12
 
13
  public function __construct( ActionScheduler_Store $store ) {
@@ -18,19 +18,19 @@ class ActionScheduler_FatalErrorMonitor {
18
  $this->claim = $claim;
19
  add_action( 'shutdown', array( $this, 'handle_unexpected_shutdown' ) );
20
  add_action( 'action_scheduler_before_execute', array( $this, 'track_current_action' ), 0, 1 );
21
- add_action( 'action_scheduler_after_execute', array( $this, 'untrack_action' ), 0, 0 );
22
- add_action( 'action_scheduler_execution_ignored', array( $this, 'untrack_action' ), 0, 0 );
23
- add_action( 'action_scheduler_failed_execution', array( $this, 'untrack_action' ), 0, 0 );
24
  }
25
 
26
  public function detach() {
27
- $this->claim = null;
28
  $this->untrack_action();
29
  remove_action( 'shutdown', array( $this, 'handle_unexpected_shutdown' ) );
30
  remove_action( 'action_scheduler_before_execute', array( $this, 'track_current_action' ), 0 );
31
- remove_action( 'action_scheduler_after_execute', array( $this, 'untrack_action' ), 0 );
32
- remove_action( 'action_scheduler_execution_ignored', array( $this, 'untrack_action' ), 0 );
33
- remove_action( 'action_scheduler_failed_execution', array( $this, 'untrack_action' ), 0 );
34
  }
35
 
36
  public function track_current_action( $action_id ) {
@@ -44,7 +44,7 @@ class ActionScheduler_FatalErrorMonitor {
44
  public function handle_unexpected_shutdown() {
45
  if ( $error = error_get_last() ) {
46
  if ( in_array( $error['type'], array( E_ERROR, E_PARSE, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR ) ) ) {
47
- if ( ! empty( $this->action_id ) ) {
48
  $this->store->mark_failure( $this->action_id );
49
  do_action( 'action_scheduler_unexpected_shutdown', $this->action_id, $error );
50
  }
5
  */
6
  class ActionScheduler_FatalErrorMonitor {
7
  /** @var ActionScheduler_ActionClaim */
8
+ private $claim = NULL;
9
  /** @var ActionScheduler_Store */
10
+ private $store = NULL;
11
  private $action_id = 0;
12
 
13
  public function __construct( ActionScheduler_Store $store ) {
18
  $this->claim = $claim;
19
  add_action( 'shutdown', array( $this, 'handle_unexpected_shutdown' ) );
20
  add_action( 'action_scheduler_before_execute', array( $this, 'track_current_action' ), 0, 1 );
21
+ add_action( 'action_scheduler_after_execute', array( $this, 'untrack_action' ), 0, 0 );
22
+ add_action( 'action_scheduler_execution_ignored', array( $this, 'untrack_action' ), 0, 0 );
23
+ add_action( 'action_scheduler_failed_execution', array( $this, 'untrack_action' ), 0, 0 );
24
  }
25
 
26
  public function detach() {
27
+ $this->claim = NULL;
28
  $this->untrack_action();
29
  remove_action( 'shutdown', array( $this, 'handle_unexpected_shutdown' ) );
30
  remove_action( 'action_scheduler_before_execute', array( $this, 'track_current_action' ), 0 );
31
+ remove_action( 'action_scheduler_after_execute', array( $this, 'untrack_action' ), 0 );
32
+ remove_action( 'action_scheduler_execution_ignored', array( $this, 'untrack_action' ), 0 );
33
+ remove_action( 'action_scheduler_failed_execution', array( $this, 'untrack_action' ), 0 );
34
  }
35
 
36
  public function track_current_action( $action_id ) {
44
  public function handle_unexpected_shutdown() {
45
  if ( $error = error_get_last() ) {
46
  if ( in_array( $error['type'], array( E_ERROR, E_PARSE, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR ) ) ) {
47
+ if ( !empty($this->action_id) ) {
48
  $this->store->mark_failure( $this->action_id );
49
  do_action( 'action_scheduler_unexpected_shutdown', $this->action_id, $error );
50
  }
lite/includes/libraries/action-scheduler/classes/ActionScheduler_ListTable.php CHANGED
@@ -2,7 +2,6 @@
2
 
3
  /**
4
  * Implements the admin view of the actions.
5
- *
6
  * @codeCoverageIgnore
7
  */
8
  class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
@@ -77,8 +76,8 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
77
  /**
78
  * Sets the current data store object into `store->action` and initialises the object.
79
  *
80
- * @param ActionScheduler_Store $store
81
- * @param ActionScheduler_Logger $logger
82
  * @param ActionScheduler_QueueRunner $runner
83
  */
84
  public function __construct( ActionScheduler_Store $store, ActionScheduler_Logger $logger, ActionScheduler_QueueRunner $runner ) {
@@ -126,9 +125,9 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
126
 
127
  $this->row_actions = array(
128
  'hook' => array(
129
- 'run' => array(
130
- 'name' => __( 'Run', 'action-scheduler' ),
131
- 'desc' => __( 'Process the action now as if it were run as part of a queue', 'action-scheduler' ),
132
  ),
133
  'cancel' => array(
134
  'name' => __( 'Cancel', 'action-scheduler' ),
@@ -183,8 +182,29 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
183
  'ajax' => false,
184
  )
185
  );
 
 
 
 
 
 
 
 
 
 
186
  }
187
 
 
 
 
 
 
 
 
 
 
 
 
188
  /**
189
  * Convert an interval of seconds into a two part human friendly string.
190
  *
@@ -214,7 +234,7 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
214
  if ( ! empty( $output ) ) {
215
  $output .= ' ';
216
  }
217
- $output .= sprintf( _n( self::$time_periods[ $time_period_index ]['names'][0], self::$time_periods[ $time_period_index ]['names'][1], $periods_in_interval, 'action-scheduler' ), $periods_in_interval );
218
  $seconds_remaining -= $periods_in_interval * self::$time_periods[ $time_period_index ]['seconds'];
219
  $periods_included++;
220
  }
@@ -292,7 +312,7 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
292
  * Prints the logs entries inline. We do so to avoid loading Javascript and other hacks to show it in a modal.
293
  *
294
  * @param ActionScheduler_LogEntry $log_entry
295
- * @param DateTimezone $timezone
296
  * @return string
297
  */
298
  protected function get_log_entry_html( ActionScheduler_LogEntry $log_entry, DateTimezone $timezone ) {
@@ -310,7 +330,7 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
310
  * @return string
311
  */
312
  protected function maybe_render_actions( $row, $column_name ) {
313
- if ( 'pending' === strtolower( $row['status_name'] ) ) {
314
  return parent::maybe_render_actions( $row, $column_name );
315
  }
316
 
@@ -341,7 +361,7 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
341
  if ( ! in_array( $wpdb->prefix . $table_name, $found_tables ) ) {
342
  $this->admin_notices[] = array(
343
  'class' => 'error',
344
- 'message' => __( 'It appears one or more database tables were missing. Attempting to re-create the missing table(s).', 'action-scheduler' ),
345
  );
346
  $this->recreate_tables();
347
  parent::display_admin_notices();
@@ -372,7 +392,7 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
372
 
373
  // No lock set or lock expired
374
  if ( false === $async_request_lock_expiration || $async_request_lock_expiration < time() ) {
375
- $in_progress_url = add_query_arg( 'status', 'in-progress', remove_query_arg( 'status' ) );
376
  /* translators: %s: process URL */
377
  $async_request_message = sprintf( __( 'A new queue has begun processing. <a href="%s">View actions in-progress &raquo;</a>', 'action-scheduler' ), esc_url( $in_progress_url ) );
378
  } else {
@@ -391,20 +411,20 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
391
  if ( is_array( $notification ) ) {
392
  delete_transient( 'action_scheduler_admin_notice' );
393
 
394
- $action = $this->store->fetch_action( $notification['action_id'] );
395
  $action_hook_html = '<strong><code>' . $action->get_hook() . '</code></strong>';
396
  if ( 1 == $notification['success'] ) {
397
  $class = 'updated';
398
  switch ( $notification['row_action_type'] ) {
399
- case 'run':
400
  /* translators: %s: action HTML */
401
  $action_message_html = sprintf( __( 'Successfully executed action: %s', 'action-scheduler' ), $action_hook_html );
402
  break;
403
- case 'cancel':
404
  /* translators: %s: action HTML */
405
  $action_message_html = sprintf( __( 'Successfully canceled action: %s', 'action-scheduler' ), $action_hook_html );
406
  break;
407
- default:
408
  /* translators: %s: action HTML */
409
  $action_message_html = sprintf( __( 'Successfully processed change for action: %s', 'action-scheduler' ), $action_hook_html );
410
  break;
@@ -473,7 +493,7 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
473
  * Deletes actions based on their ID. This is the handler for the bulk delete. It assumes the data
474
  * properly validated by the callee and it will delete the actions without any extra validation.
475
  *
476
- * @param array $ids
477
  * @param string $ids_sql Inherited and unused
478
  */
479
  protected function bulk_delete( array $ids, $ids_sql ) {
@@ -523,23 +543,23 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
523
  /**
524
  * Implements the logic behind processing an action once an action link is clicked on the list table.
525
  *
526
- * @param int $action_id
527
  * @param string $row_action_type The type of action to perform on the action.
528
  */
529
  protected function process_row_action( $action_id, $row_action_type ) {
530
  try {
531
  switch ( $row_action_type ) {
532
- case 'run':
533
  $this->runner->process_action( $action_id, 'Admin List Table' );
534
  break;
535
- case 'cancel':
536
  $this->store->cancel_action( $action_id );
537
  break;
538
  }
539
- $success = 1;
540
  $error_message = '';
541
  } catch ( Exception $e ) {
542
- $success = 0;
543
  $error_message = $e->getMessage();
544
  }
545
 
@@ -552,8 +572,9 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
552
  public function prepare_items() {
553
  $this->prepare_column_headers();
554
 
555
- $per_page = $this->get_items_per_page( $this->package . '_items_per_page', $this->items_per_page );
556
- $query = array(
 
557
  'per_page' => $per_page,
558
  'offset' => $this->get_items_offset(),
559
  'status' => $this->get_request_status(),
@@ -591,13 +612,11 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
591
  );
592
  }
593
 
594
- $this->set_pagination_args(
595
- array(
596
- 'total_items' => $total_items,
597
- 'per_page' => $per_page,
598
- 'total_pages' => ceil( $total_items / $per_page ),
599
- )
600
- );
601
  }
602
 
603
  /**
@@ -614,4 +633,11 @@ class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
614
  protected function get_search_box_button_text() {
615
  return __( 'Search hook, args and claim ID', 'action-scheduler' );
616
  }
 
 
 
 
 
 
 
617
  }
2
 
3
  /**
4
  * Implements the admin view of the actions.
 
5
  * @codeCoverageIgnore
6
  */
7
  class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
76
  /**
77
  * Sets the current data store object into `store->action` and initialises the object.
78
  *
79
+ * @param ActionScheduler_Store $store
80
+ * @param ActionScheduler_Logger $logger
81
  * @param ActionScheduler_QueueRunner $runner
82
  */
83
  public function __construct( ActionScheduler_Store $store, ActionScheduler_Logger $logger, ActionScheduler_QueueRunner $runner ) {
125
 
126
  $this->row_actions = array(
127
  'hook' => array(
128
+ 'run' => array(
129
+ 'name' => __( 'Run', 'action-scheduler' ),
130
+ 'desc' => __( 'Process the action now as if it were run as part of a queue', 'action-scheduler' ),
131
  ),
132
  'cancel' => array(
133
  'name' => __( 'Cancel', 'action-scheduler' ),
182
  'ajax' => false,
183
  )
184
  );
185
+
186
+ add_screen_option(
187
+ 'per_page',
188
+ array(
189
+ 'default' => $this->items_per_page,
190
+ )
191
+ );
192
+
193
+ add_filter( 'set_screen_option_' . $this->get_per_page_option_name(), array( $this, 'set_items_per_page_option' ), 10, 3 );
194
+ set_screen_options();
195
  }
196
 
197
+ /**
198
+ * Handles setting the items_per_page option for this screen.
199
+ *
200
+ * @param mixed $status Default false (to skip saving the current option).
201
+ * @param string $option Screen option name.
202
+ * @param int $value Screen option value.
203
+ * @return int
204
+ */
205
+ public function set_items_per_page_option( $status, $option, $value ) {
206
+ return $value;
207
+ }
208
  /**
209
  * Convert an interval of seconds into a two part human friendly string.
210
  *
234
  if ( ! empty( $output ) ) {
235
  $output .= ' ';
236
  }
237
+ $output .= sprintf( _n( self::$time_periods[ $time_period_index ]['names'][0], self::$time_periods[ $time_period_index ]['names'][1], $periods_in_interval, 'action-scheduler' ), $periods_in_interval );
238
  $seconds_remaining -= $periods_in_interval * self::$time_periods[ $time_period_index ]['seconds'];
239
  $periods_included++;
240
  }
312
  * Prints the logs entries inline. We do so to avoid loading Javascript and other hacks to show it in a modal.
313
  *
314
  * @param ActionScheduler_LogEntry $log_entry
315
+ * @param DateTimezone $timezone
316
  * @return string
317
  */
318
  protected function get_log_entry_html( ActionScheduler_LogEntry $log_entry, DateTimezone $timezone ) {
330
  * @return string
331
  */
332
  protected function maybe_render_actions( $row, $column_name ) {
333
+ if ( 'pending' === strtolower( $row[ 'status_name' ] ) ) {
334
  return parent::maybe_render_actions( $row, $column_name );
335
  }
336
 
361
  if ( ! in_array( $wpdb->prefix . $table_name, $found_tables ) ) {
362
  $this->admin_notices[] = array(
363
  'class' => 'error',
364
+ 'message' => __( 'It appears one or more database tables were missing. Attempting to re-create the missing table(s).' , 'action-scheduler' ),
365
  );
366
  $this->recreate_tables();
367
  parent::display_admin_notices();
392
 
393
  // No lock set or lock expired
394
  if ( false === $async_request_lock_expiration || $async_request_lock_expiration < time() ) {
395
+ $in_progress_url = add_query_arg( 'status', 'in-progress', remove_query_arg( 'status' ) );
396
  /* translators: %s: process URL */
397
  $async_request_message = sprintf( __( 'A new queue has begun processing. <a href="%s">View actions in-progress &raquo;</a>', 'action-scheduler' ), esc_url( $in_progress_url ) );
398
  } else {
411
  if ( is_array( $notification ) ) {
412
  delete_transient( 'action_scheduler_admin_notice' );
413
 
414
+ $action = $this->store->fetch_action( $notification['action_id'] );
415
  $action_hook_html = '<strong><code>' . $action->get_hook() . '</code></strong>';
416
  if ( 1 == $notification['success'] ) {
417
  $class = 'updated';
418
  switch ( $notification['row_action_type'] ) {
419
+ case 'run' :
420
  /* translators: %s: action HTML */
421
  $action_message_html = sprintf( __( 'Successfully executed action: %s', 'action-scheduler' ), $action_hook_html );
422
  break;
423
+ case 'cancel' :
424
  /* translators: %s: action HTML */
425
  $action_message_html = sprintf( __( 'Successfully canceled action: %s', 'action-scheduler' ), $action_hook_html );
426
  break;
427
+ default :
428
  /* translators: %s: action HTML */
429
  $action_message_html = sprintf( __( 'Successfully processed change for action: %s', 'action-scheduler' ), $action_hook_html );
430
  break;
493
  * Deletes actions based on their ID. This is the handler for the bulk delete. It assumes the data
494
  * properly validated by the callee and it will delete the actions without any extra validation.
495
  *
496
+ * @param array $ids
497
  * @param string $ids_sql Inherited and unused
498
  */
499
  protected function bulk_delete( array $ids, $ids_sql ) {
543
  /**
544
  * Implements the logic behind processing an action once an action link is clicked on the list table.
545
  *
546
+ * @param int $action_id
547
  * @param string $row_action_type The type of action to perform on the action.
548
  */
549
  protected function process_row_action( $action_id, $row_action_type ) {
550
  try {
551
  switch ( $row_action_type ) {
552
+ case 'run' :
553
  $this->runner->process_action( $action_id, 'Admin List Table' );
554
  break;
555
+ case 'cancel' :
556
  $this->store->cancel_action( $action_id );
557
  break;
558
  }
559
+ $success = 1;
560
  $error_message = '';
561
  } catch ( Exception $e ) {
562
+ $success = 0;
563
  $error_message = $e->getMessage();
564
  }
565
 
572
  public function prepare_items() {
573
  $this->prepare_column_headers();
574
 
575
+ $per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
576
+
577
+ $query = array(
578
  'per_page' => $per_page,
579
  'offset' => $this->get_items_offset(),
580
  'status' => $this->get_request_status(),
612
  );
613
  }
614
 
615
+ $this->set_pagination_args( array(
616
+ 'total_items' => $total_items,
617
+ 'per_page' => $per_page,
618
+ 'total_pages' => ceil( $total_items / $per_page ),
619
+ ) );
 
 
620
  }
621
 
622
  /**
633
  protected function get_search_box_button_text() {
634
  return __( 'Search hook, args and claim ID', 'action-scheduler' );
635
  }
636
+
637
+ /**
638
+ * {@inheritDoc}
639
+ */
640
+ protected function get_per_page_option_name() {
641
+ return str_replace( '-', '_', $this->screen->id ) . '_per_page';
642
+ }
643
  }
lite/includes/libraries/action-scheduler/classes/ActionScheduler_LogEntry.php CHANGED
@@ -8,12 +8,12 @@ class ActionScheduler_LogEntry {
8
  /**
9
  * @var int $action_id
10
  */
11
- protected $action_id = '';
12
 
13
  /**
14
  * @var string $message
15
  */
16
- protected $message = '';
17
 
18
  /**
19
  * @var Datetime $date
@@ -23,8 +23,8 @@ class ActionScheduler_LogEntry {
23
  /**
24
  * Constructor
25
  *
26
- * @param mixed $action_id Action ID
27
- * @param string $message Message
28
  * @param Datetime $date Datetime object with the time when this log entry was created. If this parameter is
29
  * not provided a new Datetime object (with current time) will be created.
30
  */
@@ -44,7 +44,7 @@ class ActionScheduler_LogEntry {
44
 
45
  $this->action_id = $action_id;
46
  $this->message = $message;
47
- $this->date = $date ? $date : new Datetime();
48
  }
49
 
50
  /**
8
  /**
9
  * @var int $action_id
10
  */
11
+ protected $action_id = '';
12
 
13
  /**
14
  * @var string $message
15
  */
16
+ protected $message = '';
17
 
18
  /**
19
  * @var Datetime $date
23
  /**
24
  * Constructor
25
  *
26
+ * @param mixed $action_id Action ID
27
+ * @param string $message Message
28
  * @param Datetime $date Datetime object with the time when this log entry was created. If this parameter is
29
  * not provided a new Datetime object (with current time) will be created.
30
  */
44
 
45
  $this->action_id = $action_id;
46
  $this->message = $message;
47
+ $this->date = $date ? $date : new Datetime;
48
  }
49
 
50
  /**
lite/includes/libraries/action-scheduler/classes/ActionScheduler_NullLogEntry.php CHANGED
@@ -8,4 +8,4 @@ class ActionScheduler_NullLogEntry extends ActionScheduler_LogEntry {
8
  // nothing to see here
9
  }
10
  }
11
-
8
  // nothing to see here
9
  }
10
  }
11
+
lite/includes/libraries/action-scheduler/classes/ActionScheduler_OptionLock.php CHANGED
@@ -5,7 +5,6 @@
5
  * for up-to a given duration.
6
  *
7
  * Class ActionScheduler_OptionLock
8
- *
9
  * @since 3.0.0
10
  */
11
  class ActionScheduler_OptionLock extends ActionScheduler_Lock {
5
  * for up-to a given duration.
6
  *
7
  * Class ActionScheduler_OptionLock
 
8
  * @since 3.0.0
9
  */
10
  class ActionScheduler_OptionLock extends ActionScheduler_Lock {
lite/includes/libraries/action-scheduler/classes/ActionScheduler_QueueCleaner.php CHANGED
@@ -25,13 +25,13 @@ class ActionScheduler_QueueCleaner {
25
  * @param int $batch_size The batch size.
26
  */
27
  public function __construct( ActionScheduler_Store $store = null, $batch_size = 20 ) {
28
- $this->store = $store ? $store : ActionScheduler_Store::instance();
29
  $this->batch_size = $batch_size;
30
  }
31
 
32
  public function delete_old_actions() {
33
  $lifespan = apply_filters( 'action_scheduler_retention_period', $this->month_in_seconds );
34
- $cutoff = as_get_datetime_object( $lifespan . ' seconds ago' );
35
 
36
  $statuses_to_purge = array(
37
  ActionScheduler_Store::STATUS_COMPLETE,
@@ -39,14 +39,13 @@ class ActionScheduler_QueueCleaner {
39
  );
40
 
41
  foreach ( $statuses_to_purge as $status ) {
42
- $actions_to_delete = $this->store->query_actions(
43
- array(
44
- 'status' => $status,
45
- 'modified' => $cutoff,
46
- 'modified_compare' => '<=',
47
- 'per_page' => $this->get_batch_size(),
48
- )
49
- );
50
 
51
  foreach ( $actions_to_delete as $action_id ) {
52
  try {
@@ -85,16 +84,15 @@ class ActionScheduler_QueueCleaner {
85
  if ( $timeout < 0 ) {
86
  return;
87
  }
88
- $cutoff = as_get_datetime_object( $timeout . ' seconds ago' );
89
- $actions_to_reset = $this->store->query_actions(
90
- array(
91
- 'status' => ActionScheduler_Store::STATUS_PENDING,
92
- 'modified' => $cutoff,
93
- 'modified_compare' => '<=',
94
- 'claimed' => true,
95
- 'per_page' => $this->get_batch_size(),
96
- )
97
- );
98
 
99
  foreach ( $actions_to_reset as $action_id ) {
100
  $this->store->unclaim_action( $action_id );
@@ -116,15 +114,14 @@ class ActionScheduler_QueueCleaner {
116
  if ( $timeout < 0 ) {
117
  return;
118
  }
119
- $cutoff = as_get_datetime_object( $timeout . ' seconds ago' );
120
- $actions_to_reset = $this->store->query_actions(
121
- array(
122
- 'status' => ActionScheduler_Store::STATUS_RUNNING,
123
- 'modified' => $cutoff,
124
- 'modified_compare' => '<=',
125
- 'per_page' => $this->get_batch_size(),
126
- )
127
- );
128
 
129
  foreach ( $actions_to_reset as $action_id ) {
130
  $this->store->mark_failure( $action_id );
25
  * @param int $batch_size The batch size.
26
  */
27
  public function __construct( ActionScheduler_Store $store = null, $batch_size = 20 ) {
28
+ $this->store = $store ? $store : ActionScheduler_Store::instance();
29
  $this->batch_size = $batch_size;
30
  }
31
 
32
  public function delete_old_actions() {
33
  $lifespan = apply_filters( 'action_scheduler_retention_period', $this->month_in_seconds );
34
+ $cutoff = as_get_datetime_object($lifespan.' seconds ago');
35
 
36
  $statuses_to_purge = array(
37
  ActionScheduler_Store::STATUS_COMPLETE,
39
  );
40
 
41
  foreach ( $statuses_to_purge as $status ) {
42
+ $actions_to_delete = $this->store->query_actions( array(
43
+ 'status' => $status,
44
+ 'modified' => $cutoff,
45
+ 'modified_compare' => '<=',
46
+ 'per_page' => $this->get_batch_size(),
47
+ 'orderby' => 'none',
48
+ ) );
 
49
 
50
  foreach ( $actions_to_delete as $action_id ) {
51
  try {
84
  if ( $timeout < 0 ) {
85
  return;
86
  }
87
+ $cutoff = as_get_datetime_object($timeout.' seconds ago');
88
+ $actions_to_reset = $this->store->query_actions( array(
89
+ 'status' => ActionScheduler_Store::STATUS_PENDING,
90
+ 'modified' => $cutoff,
91
+ 'modified_compare' => '<=',
92
+ 'claimed' => true,
93
+ 'per_page' => $this->get_batch_size(),
94
+ 'orderby' => 'none',
95
+ ) );
 
96
 
97
  foreach ( $actions_to_reset as $action_id ) {
98
  $this->store->unclaim_action( $action_id );
114
  if ( $timeout < 0 ) {
115
  return;
116
  }
117
+ $cutoff = as_get_datetime_object($timeout.' seconds ago');
118
+ $actions_to_reset = $this->store->query_actions( array(
119
+ 'status' => ActionScheduler_Store::STATUS_RUNNING,
120
+ 'modified' => $cutoff,
121
+ 'modified_compare' => '<=',
122
+ 'per_page' => $this->get_batch_size(),
123
+ 'orderby' => 'none',
124
+ ) );
 
125
 
126
  foreach ( $actions_to_reset as $action_id ) {
127
  $this->store->mark_failure( $action_id );
lite/includes/libraries/action-scheduler/classes/ActionScheduler_QueueRunner.php CHANGED
@@ -19,8 +19,8 @@ class ActionScheduler_QueueRunner extends ActionScheduler_Abstract_QueueRunner {
19
  * @codeCoverageIgnore
20
  */
21
  public static function instance() {
22
- if ( empty( self::$runner ) ) {
23
- $class = apply_filters( 'action_scheduler_queue_runner_class', 'ActionScheduler_QueueRunner' );
24
  self::$runner = new $class();
25
  }
26
  return self::$runner;
@@ -114,7 +114,6 @@ class ActionScheduler_QueueRunner extends ActionScheduler_Abstract_QueueRunner {
114
  * that was the only context in which this method was run, and the self::WP_CRON_HOOK hook had no context
115
  * passed along with it. New code calling this method directly, or by triggering the self::WP_CRON_HOOK,
116
  * should set a context as the first parameter. For an example of this, refer to the code seen in
117
- *
118
  * @see ActionScheduler_AsyncRequest_QueueRunner::handle()
119
  *
120
  * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
@@ -145,14 +144,14 @@ class ActionScheduler_QueueRunner extends ActionScheduler_Abstract_QueueRunner {
145
  * Actions are processed by claiming a set of pending actions then processing each one until either the batch
146
  * size is completed, or memory or time limits are reached, defined by @see $this->batch_limits_exceeded().
147
  *
148
- * @param int $size The maximum number of actions to process in the batch.
149
  * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
150
  * Generally, this should be capitalised and not localised as it's a proper noun.
151
  * @return int The number of actions processed.
152
  */
153
  protected function do_batch( $size = 100, $context = '' ) {
154
- $claim = $this->store->stake_claim( $size );
155
- $this->monitor->attach( $claim );
156
  $processed_actions = 0;
157
 
158
  foreach ( $claim->get_actions() as $action_id ) {
@@ -167,7 +166,7 @@ class ActionScheduler_QueueRunner extends ActionScheduler_Abstract_QueueRunner {
167
  break;
168
  }
169
  }
170
- $this->store->release_claim( $claim );
171
  $this->monitor->detach();
172
  $this->clear_caches();
173
  return $processed_actions;
19
  * @codeCoverageIgnore
20
  */
21
  public static function instance() {
22
+ if ( empty(self::$runner) ) {
23
+ $class = apply_filters('action_scheduler_queue_runner_class', 'ActionScheduler_QueueRunner');
24
  self::$runner = new $class();
25
  }
26
  return self::$runner;
114
  * that was the only context in which this method was run, and the self::WP_CRON_HOOK hook had no context
115
  * passed along with it. New code calling this method directly, or by triggering the self::WP_CRON_HOOK,
116
  * should set a context as the first parameter. For an example of this, refer to the code seen in
 
117
  * @see ActionScheduler_AsyncRequest_QueueRunner::handle()
118
  *
119
  * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
144
  * Actions are processed by claiming a set of pending actions then processing each one until either the batch
145
  * size is completed, or memory or time limits are reached, defined by @see $this->batch_limits_exceeded().
146
  *
147
+ * @param int $size The maximum number of actions to process in the batch.
148
  * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
149
  * Generally, this should be capitalised and not localised as it's a proper noun.
150
  * @return int The number of actions processed.
151
  */
152
  protected function do_batch( $size = 100, $context = '' ) {
153
+ $claim = $this->store->stake_claim($size);
154
+ $this->monitor->attach($claim);
155
  $processed_actions = 0;
156
 
157
  foreach ( $claim->get_actions() as $action_id ) {
166
  break;
167
  }
168
  }
169
+ $this->store->release_claim($claim);
170
  $this->monitor->detach();
171
  $this->clear_caches();
172
  return $processed_actions;
lite/includes/libraries/action-scheduler/classes/ActionScheduler_Versions.php CHANGED
@@ -7,16 +7,16 @@ class ActionScheduler_Versions {
7
  /**
8
  * @var ActionScheduler_Versions
9
  */
10
- private static $instance = null;
11
 
12
  private $versions = array();
13
 
14
  public function register( $version_string, $initialization_callback ) {
15
- if ( isset( $this->versions[ $version_string ] ) ) {
16
- return false;
17
  }
18
- $this->versions[ $version_string ] = $initialization_callback;
19
- return true;
20
  }
21
 
22
  public function get_versions() {
@@ -24,20 +24,20 @@ class ActionScheduler_Versions {
24
  }
25
 
26
  public function latest_version() {
27
- $keys = array_keys( $this->versions );
28
- if ( empty( $keys ) ) {
29
  return false;
30
  }
31
  uasort( $keys, 'version_compare' );
32
- return end( $keys );
33
  }
34
 
35
  public function latest_version_callback() {
36
  $latest = $this->latest_version();
37
- if ( empty( $latest ) || ! isset( $this->versions[ $latest ] ) ) {
38
  return '__return_null';
39
  }
40
- return $this->versions[ $latest ];
41
  }
42
 
43
  /**
@@ -45,7 +45,7 @@ class ActionScheduler_Versions {
45
  * @codeCoverageIgnore
46
  */
47
  public static function instance() {
48
- if ( empty( self::$instance ) ) {
49
  self::$instance = new self();
50
  }
51
  return self::$instance;
@@ -56,7 +56,7 @@ class ActionScheduler_Versions {
56
  */
57
  public static function initialize_latest_version() {
58
  $self = self::instance();
59
- call_user_func( $self->latest_version_callback() );
60
  }
61
  }
62
-
7
  /**
8
  * @var ActionScheduler_Versions
9
  */
10
+ private static $instance = NULL;
11
 
12
  private $versions = array();
13
 
14
  public function register( $version_string, $initialization_callback ) {
15
+ if ( isset($this->versions[$version_string]) ) {
16
+ return FALSE;
17
  }
18
+ $this->versions[$version_string] = $initialization_callback;
19
+ return TRUE;
20
  }
21
 
22
  public function get_versions() {
24
  }
25
 
26
  public function latest_version() {
27
+ $keys = array_keys($this->versions);
28
+ if ( empty($keys) ) {
29
  return false;
30
  }
31
  uasort( $keys, 'version_compare' );
32
+ return end($keys);
33
  }
34
 
35
  public function latest_version_callback() {
36
  $latest = $this->latest_version();
37
+ if ( empty($latest) || !isset($this->versions[$latest]) ) {
38
  return '__return_null';
39
  }
40
+ return $this->versions[$latest];
41
  }
42
 
43
  /**
45
  * @codeCoverageIgnore
46
  */
47
  public static function instance() {
48
+ if ( empty(self::$instance) ) {
49
  self::$instance = new self();
50
  }
51
  return self::$instance;
56
  */
57
  public static function initialize_latest_version() {
58
  $self = self::instance();
59
+ call_user_func($self->latest_version_callback());
60
  }
61
  }
62
+
lite/includes/libraries/action-scheduler/classes/ActionScheduler_WPCommentCleaner.php CHANGED
@@ -66,13 +66,7 @@ class ActionScheduler_WPCommentCleaner {
66
  * Attached to the migration complete hook 'action_scheduler/migration_complete'.
67
  */
68
  public static function maybe_schedule_cleanup() {
69
- if ( (bool) get_comments(
70
- array(
71
- 'type' => ActionScheduler_wpCommentLogger::TYPE,
72
- 'number' => 1,
73
- 'fields' => 'ids',
74
- )
75
- ) ) {
76
  update_option( self::$has_logs_option_key, 'yes' );
77
 
78
  if ( ! as_next_scheduled_action( self::$cleanup_hook ) ) {
@@ -86,13 +80,7 @@ class ActionScheduler_WPCommentCleaner {
86
  */
87
  public static function delete_all_action_comments() {
88
  global $wpdb;
89
- $wpdb->delete(
90
- $wpdb->comments,
91
- array(
92
- 'comment_type' => ActionScheduler_wpCommentLogger::TYPE,
93
- 'comment_agent' => ActionScheduler_wpCommentLogger::AGENT,
94
- )
95
- );
96
  delete_option( self::$has_logs_option_key );
97
  }
98
 
@@ -102,7 +90,7 @@ class ActionScheduler_WPCommentCleaner {
102
  public static function register_admin_notice() {
103
  add_action( 'admin_notices', array( __CLASS__, 'print_admin_notice' ) );
104
  }
105
-
106
  /**
107
  * Prints details about the orphaned action logs and includes information on where to learn more.
108
  */
66
  * Attached to the migration complete hook 'action_scheduler/migration_complete'.
67
  */
68
  public static function maybe_schedule_cleanup() {
69
+ if ( (bool) get_comments( array( 'type' => ActionScheduler_wpCommentLogger::TYPE, 'number' => 1, 'fields' => 'ids' ) ) ) {
 
 
 
 
 
 
70
  update_option( self::$has_logs_option_key, 'yes' );
71
 
72
  if ( ! as_next_scheduled_action( self::$cleanup_hook ) ) {
80
  */
81
  public static function delete_all_action_comments() {
82
  global $wpdb;
83
+ $wpdb->delete( $wpdb->comments, array( 'comment_type' => ActionScheduler_wpCommentLogger::TYPE, 'comment_agent' => ActionScheduler_wpCommentLogger::AGENT ) );
 
 
 
 
 
 
84
  delete_option( self::$has_logs_option_key );
85
  }
86
 
90
  public static function register_admin_notice() {
91
  add_action( 'admin_notices', array( __CLASS__, 'print_admin_notice' ) );
92
  }
93
+
94
  /**
95
  * Prints details about the orphaned action logs and includes information on where to learn more.
96
  */
lite/includes/libraries/action-scheduler/classes/ActionScheduler_wcSystemStatus.php CHANGED
@@ -12,7 +12,14 @@ class ActionScheduler_wcSystemStatus {
12
  */
13
  protected $store;
14
 
15
- function __construct( $store ) {
 
 
 
 
 
 
 
16
  $this->store = $store;
17
  }
18
 
@@ -94,18 +101,23 @@ class ActionScheduler_wcSystemStatus {
94
  * @param array $oldest_and_newest Date of the oldest and newest action with each status.
95
  */
96
  protected function get_template( $status_labels, $action_counts, $oldest_and_newest ) {
97
- $as_version = ActionScheduler_Versions::instance()->latest_version();
 
98
  ?>
99
 
100
  <table class="wc_status_table widefat" cellspacing="0">
101
  <thead>
102
  <tr>
103
- <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>
104
  </tr>
105
  <tr>
106
  <td colspan="2" data-export-label="Version"><?php esc_html_e( 'Version:', 'action-scheduler' ); ?></td>
107
  <td colspan="3"><?php echo esc_html( $as_version ); ?></td>
108
  </tr>
 
 
 
 
109
  <tr>
110
  <td><strong><?php esc_html_e( 'Action Status', 'action-scheduler' ); ?></strong></td>
111
  <td class="help">&nbsp;</td>
@@ -121,9 +133,9 @@ class ActionScheduler_wcSystemStatus {
121
  printf(
122
  '<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>',
123
  esc_html( $status_labels[ $status ] ),
124
- number_format_i18n( $count ),
125
- $oldest_and_newest[ $status ]['oldest'],
126
- $oldest_and_newest[ $status ]['newest']
127
  );
128
  }
129
  ?>
@@ -134,10 +146,10 @@ class ActionScheduler_wcSystemStatus {
134
  }
135
 
136
  /**
137
- * is triggered when invoking inaccessible methods in an object context.
138
  *
139
- * @param string $name
140
- * @param array $arguments
141
  *
142
  * @return mixed
143
  * @link https://php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.methods
12
  */
13
  protected $store;
14
 
15
+ /**
16
+ * Constructor method for ActionScheduler_wcSystemStatus.
17
+ *
18
+ * @param ActionScheduler_Store $store Active store object.
19
+ *
20
+ * @return void
21
+ */
22
+ public function __construct( $store ) {
23
  $this->store = $store;
24
  }
25
 
101
  * @param array $oldest_and_newest Date of the oldest and newest action with each status.
102
  */
103
  protected function get_template( $status_labels, $action_counts, $oldest_and_newest ) {
104
+ $as_version = ActionScheduler_Versions::instance()->latest_version();
105
+ $as_datastore = get_class( ActionScheduler_Store::instance() );
106
  ?>
107
 
108
  <table class="wc_status_table widefat" cellspacing="0">
109
  <thead>
110
  <tr>
111
+ <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 details of Action Scheduler.', 'action-scheduler' ) ); ?></h2></th>
112
  </tr>
113
  <tr>
114
  <td colspan="2" data-export-label="Version"><?php esc_html_e( 'Version:', 'action-scheduler' ); ?></td>
115
  <td colspan="3"><?php echo esc_html( $as_version ); ?></td>
116
  </tr>
117
+ <tr>
118
+ <td colspan="2" data-export-label="Data store"><?php esc_html_e( 'Data store:', 'action-scheduler' ); ?></td>
119
+ <td colspan="3"><?php echo esc_html( $as_datastore ); ?></td>
120
+ </tr>
121
  <tr>
122
  <td><strong><?php esc_html_e( 'Action Status', 'action-scheduler' ); ?></strong></td>
123
  <td class="help">&nbsp;</td>
133
  printf(
134
  '<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>',
135
  esc_html( $status_labels[ $status ] ),
136
+ esc_html( number_format_i18n( $count ) ),
137
+ esc_html( $oldest_and_newest[ $status ]['oldest'] ),
138
+ esc_html( $oldest_and_newest[ $status ]['newest'] )
139
  );
140
  }
141
  ?>
146
  }
147
 
148
  /**
149
+ * Is triggered when invoking inaccessible methods in an object context.
150
  *
151
+ * @param string $name Name of method called.
152
+ * @param array $arguments Parameters to invoke the method with.
153
  *
154
  * @return mixed
155
  * @link https://php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.methods
lite/includes/libraries/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_QueueRunner.php CHANGED
@@ -143,7 +143,7 @@ class ActionScheduler_WPCLI_QueueRunner extends ActionScheduler_Abstract_QueueRu
143
  *
144
  * @author Jeremy Pry
145
  *
146
- * @param int $action_id
147
  * @param null|ActionScheduler_Action $action The instance of the action. Default to null for backward compatibility.
148
  */
149
  public function after_execute( $action_id, $action = null ) {
143
  *
144
  * @author Jeremy Pry
145
  *
146
+ * @param int $action_id
147
  * @param null|ActionScheduler_Action $action The instance of the action. Default to null for backward compatibility.
148
  */
149
  public function after_execute( $action_id, $action = null ) {
lite/includes/libraries/action-scheduler/classes/WP_CLI/Migration_Command.php CHANGED
@@ -32,42 +32,38 @@ class Migration_Command extends WP_CLI_Command {
32
  return;
33
  }
34
 
35
- WP_CLI::add_command(
36
- 'action-scheduler migrate',
37
- array( $this, 'migrate' ),
38
- array(
39
- 'shortdesc' => 'Migrates actions to the DB tables store',
40
- 'synopsis' => array(
41
- array(
42
- 'type' => 'assoc',
43
- 'name' => 'batch-size',
44
- 'optional' => true,
45
- 'default' => 100,
46
- 'description' => 'The number of actions to process in each batch',
47
- ),
48
- array(
49
- 'type' => 'assoc',
50
- 'name' => 'free-memory-on',
51
- 'optional' => true,
52
- 'default' => 50,
53
- 'description' => 'The number of actions to process between freeing memory. 0 disables freeing memory',
54
- ),
55
- array(
56
- 'type' => 'assoc',
57
- 'name' => 'pause',
58
- 'optional' => true,
59
- 'default' => 0,
60
- 'description' => 'The number of seconds to pause when freeing memory',
61
- ),
62
- array(
63
- 'type' => 'flag',
64
- 'name' => 'dry-run',
65
- 'optional' => true,
66
- 'description' => 'Reports on the actions that would have been migrated, but does not change any data',
67
- ),
68
- ),
69
- )
70
- );
71
  }
72
 
73
  /**
@@ -85,14 +81,14 @@ class Migration_Command extends WP_CLI_Command {
85
  $runner = new Runner( $config );
86
  $runner->init_destination();
87
 
88
- $batch_size = isset( $assoc_args['batch-size'] ) ? (int) $assoc_args['batch-size'] : 100;
89
- $free_on = isset( $assoc_args['free-memory-on'] ) ? (int) $assoc_args['free-memory-on'] : 50;
90
- $sleep = isset( $assoc_args['pause'] ) ? (int) $assoc_args['pause'] : 0;
91
  \ActionScheduler_DataController::set_free_ticks( $free_on );
92
  \ActionScheduler_DataController::set_sleep_time( $sleep );
93
 
94
  do {
95
- $actions_processed = $runner->run( $batch_size );
96
  $this->total_processed += $actions_processed;
97
  } while ( $actions_processed > 0 );
98
 
@@ -113,15 +109,12 @@ class Migration_Command extends WP_CLI_Command {
113
  * @return ActionScheduler\Migration\Config
114
  */
115
  private function get_migration_config( $args ) {
116
- $args = wp_parse_args(
117
- $args,
118
- array(
119
- 'dry-run' => false,
120
- )
121
- );
122
 
123
  $config = Controller::instance()->get_migration_config_object();
124
- $config->set_dry_run( ! empty( $args['dry-run'] ) );
125
 
126
  return $config;
127
  }
@@ -130,61 +123,26 @@ class Migration_Command extends WP_CLI_Command {
130
  * Hook command line logging into migration actions.
131
  */
132
  private function init_logging() {
133
- add_action(
134
- 'action_scheduler/migrate_action_dry_run',
135
- function ( $action_id ) {
136
- WP_CLI::debug( sprintf( 'Dry-run: migrated action %d', $action_id ) );
137
- },
138
- 10,
139
- 1
140
- );
141
- add_action(
142
- 'action_scheduler/no_action_to_migrate',
143
- function ( $action_id ) {
144
- WP_CLI::debug( sprintf( 'No action found to migrate for ID %d', $action_id ) );
145
- },
146
- 10,
147
- 1
148
- );
149
- add_action(
150
- 'action_scheduler/migrate_action_failed',
151
- function ( $action_id ) {
152
- WP_CLI::warning( sprintf( 'Failed migrating action with ID %d', $action_id ) );
153
- },
154
- 10,
155
- 1
156
- );
157
- add_action(
158
- 'action_scheduler/migrate_action_incomplete',
159
- function ( $source_id, $destination_id ) {
160
- WP_CLI::warning( sprintf( 'Unable to remove source action with ID %d after migrating to new ID %d', $source_id, $destination_id ) );
161
- },
162
- 10,
163
- 2
164
- );
165
- add_action(
166
- 'action_scheduler/migrated_action',
167
- function ( $source_id, $destination_id ) {
168
- WP_CLI::debug( sprintf( 'Migrated source action with ID %d to new store with ID %d', $source_id, $destination_id ) );
169
- },
170
- 10,
171
- 2
172
- );
173
- add_action(
174
- 'action_scheduler/migration_batch_starting',
175
- function ( $batch ) {
176
- WP_CLI::debug( 'Beginning migration of batch: ' . print_r( $batch, true ) );
177
- },
178
- 10,
179
- 1
180
- );
181
- add_action(
182
- 'action_scheduler/migration_batch_complete',
183
- function ( $batch ) {
184
- WP_CLI::log( sprintf( 'Completed migration of %d actions', count( $batch ) ) );
185
- },
186
- 10,
187
- 1
188
- );
189
  }
190
  }
32
  return;
33
  }
34
 
35
+ WP_CLI::add_command( 'action-scheduler migrate', [ $this, 'migrate' ], [
36
+ 'shortdesc' => 'Migrates actions to the DB tables store',
37
+ 'synopsis' => [
38
+ [
39
+ 'type' => 'assoc',
40
+ 'name' => 'batch-size',
41
+ 'optional' => true,
42
+ 'default' => 100,
43
+ 'description' => 'The number of actions to process in each batch',
44
+ ],
45
+ [
46
+ 'type' => 'assoc',
47
+ 'name' => 'free-memory-on',
48
+ 'optional' => true,
49
+ 'default' => 50,
50
+ 'description' => 'The number of actions to process between freeing memory. 0 disables freeing memory',
51
+ ],
52
+ [
53
+ 'type' => 'assoc',
54
+ 'name' => 'pause',
55
+ 'optional' => true,
56
+ 'default' => 0,
57
+ 'description' => 'The number of seconds to pause when freeing memory',
58
+ ],
59
+ [
60
+ 'type' => 'flag',
61
+ 'name' => 'dry-run',
62
+ 'optional' => true,
63
+ 'description' => 'Reports on the actions that would have been migrated, but does not change any data',
64
+ ],
65
+ ],
66
+ ] );
 
 
 
 
67
  }
68
 
69
  /**
81
  $runner = new Runner( $config );
82
  $runner->init_destination();
83
 
84
+ $batch_size = isset( $assoc_args[ 'batch-size' ] ) ? (int) $assoc_args[ 'batch-size' ] : 100;
85
+ $free_on = isset( $assoc_args[ 'free-memory-on' ] ) ? (int) $assoc_args[ 'free-memory-on' ] : 50;
86
+ $sleep = isset( $assoc_args[ 'pause' ] ) ? (int) $assoc_args[ 'pause' ] : 0;
87
  \ActionScheduler_DataController::set_free_ticks( $free_on );
88
  \ActionScheduler_DataController::set_sleep_time( $sleep );
89
 
90
  do {
91
+ $actions_processed = $runner->run( $batch_size );
92
  $this->total_processed += $actions_processed;
93
  } while ( $actions_processed > 0 );
94
 
109
  * @return ActionScheduler\Migration\Config
110
  */
111
  private function get_migration_config( $args ) {
112
+ $args = wp_parse_args( $args, [
113
+ 'dry-run' => false,
114
+ ] );
 
 
 
115
 
116
  $config = Controller::instance()->get_migration_config_object();
117
+ $config->set_dry_run( ! empty( $args[ 'dry-run' ] ) );
118
 
119
  return $config;
120
  }
123
  * Hook command line logging into migration actions.
124
  */
125
  private function init_logging() {
126
+ add_action( 'action_scheduler/migrate_action_dry_run', function ( $action_id ) {
127
+ WP_CLI::debug( sprintf( 'Dry-run: migrated action %d', $action_id ) );
128
+ }, 10, 1 );
129
+ add_action( 'action_scheduler/no_action_to_migrate', function ( $action_id ) {
130
+ WP_CLI::debug( sprintf( 'No action found to migrate for ID %d', $action_id ) );
131
+ }, 10, 1 );
132
+ add_action( 'action_scheduler/migrate_action_failed', function ( $action_id ) {
133
+ WP_CLI::warning( sprintf( 'Failed migrating action with ID %d', $action_id ) );
134
+ }, 10, 1 );
135
+ add_action( 'action_scheduler/migrate_action_incomplete', function ( $source_id, $destination_id ) {
136
+ WP_CLI::warning( sprintf( 'Unable to remove source action with ID %d after migrating to new ID %d', $source_id, $destination_id ) );
137
+ }, 10, 2 );
138
+ add_action( 'action_scheduler/migrated_action', function ( $source_id, $destination_id ) {
139
+ WP_CLI::debug( sprintf( 'Migrated source action with ID %d to new store with ID %d', $source_id, $destination_id ) );
140
+ }, 10, 2 );
141
+ add_action( 'action_scheduler/migration_batch_starting', function ( $batch ) {
142
+ WP_CLI::debug( 'Beginning migration of batch: ' . print_r( $batch, true ) );
143
+ }, 10, 1 );
144
+ add_action( 'action_scheduler/migration_batch_complete', function ( $batch ) {
145
+ WP_CLI::log( sprintf( 'Completed migration of %d actions', count( $batch ) ) );
146
+ }, 10, 1 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  }
148
  }
lite/includes/libraries/action-scheduler/classes/WP_CLI/ProgressBar.php CHANGED
@@ -38,7 +38,7 @@ class ProgressBar {
38
  * @param string $message Text to display before the progress bar.
39
  * @param integer $count Total number of ticks to be performed.
40
  * @param integer $interval Optional. The interval in milliseconds between updates. Default 100.
41
- *
42
  * @throws Exception When this is not run within WP CLI
43
  */
44
  public function __construct( $message, $count, $interval = 100 ) {
38
  * @param string $message Text to display before the progress bar.
39
  * @param integer $count Total number of ticks to be performed.
40
  * @param integer $interval Optional. The interval in milliseconds between updates. Default 100.
41
+ *
42
  * @throws Exception When this is not run within WP CLI
43
  */
44
  public function __construct( $message, $count, $interval = 100 ) {
lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler.php CHANGED
@@ -5,18 +5,17 @@ use Action_Scheduler\Migration\Controller;
5
 
6
  /**
7
  * Class ActionScheduler
8
- *
9
  * @codeCoverageIgnore
10
  */
11
  abstract class ActionScheduler {
12
  private static $plugin_file = '';
13
  /** @var ActionScheduler_ActionFactory */
14
- private static $factory = null;
15
  /** @var bool */
16
  private static $data_store_initialized = false;
17
 
18
  public static function factory() {
19
- if ( ! isset( self::$factory ) ) {
20
  self::$factory = new ActionScheduler_ActionFactory();
21
  }
22
  return self::$factory;
@@ -44,29 +43,27 @@ abstract class ActionScheduler {
44
 
45
  /**
46
  * Get the absolute system path to the plugin directory, or a file therein
47
- *
48
  * @static
49
  * @param string $path
50
  * @return string
51
  */
52
  public static function plugin_path( $path ) {
53
- $base = dirname( self::$plugin_file );
54
  if ( $path ) {
55
- return trailingslashit( $base ) . $path;
56
  } else {
57
- return untrailingslashit( $base );
58
  }
59
  }
60
 
61
  /**
62
  * Get the absolute URL to the plugin directory, or a file therein
63
- *
64
  * @static
65
  * @param string $path
66
  * @return string
67
  */
68
  public static function plugin_url( $path ) {
69
- return plugins_url( $path, self::$plugin_file );
70
  }
71
 
72
  public static function autoload( $class ) {
@@ -94,7 +91,7 @@ abstract class ActionScheduler {
94
  $dir = $classes_dir . 'schema' . $d;
95
  } elseif ( strpos( $class, 'ActionScheduler' ) === 0 ) {
96
  $segments = explode( '_', $class );
97
- $type = isset( $segments[1] ) ? $segments[1] : '';
98
 
99
  switch ( $type ) {
100
  case 'WPCLI':
@@ -122,7 +119,7 @@ abstract class ActionScheduler {
122
  }
123
 
124
  if ( file_exists( "{$dir}{$class}.php" ) ) {
125
- include "{$dir}{$class}.php";
126
  return;
127
  }
128
  }
@@ -142,7 +139,7 @@ abstract class ActionScheduler {
142
  */
143
  do_action( 'action_scheduler_pre_init' );
144
 
145
- require_once self::plugin_path( 'functions.php' );
146
  ActionScheduler_DataController::init();
147
 
148
  $store = self::store();
@@ -164,7 +161,7 @@ abstract class ActionScheduler {
164
  }
165
 
166
  if ( apply_filters( 'action_scheduler_load_deprecated_functions', true ) ) {
167
- require_once self::plugin_path( 'deprecated/functions.php' );
168
  }
169
 
170
  if ( defined( 'WP_CLI' ) && WP_CLI ) {
@@ -251,7 +248,7 @@ abstract class ActionScheduler {
251
  );
252
 
253
  $segments = explode( '_', $class );
254
- $segment = isset( $segments[1] ) ? $segments[1] : $class;
255
 
256
  return isset( $migration_segments[ $segment ] ) && $migration_segments[ $segment ];
257
  }
@@ -273,17 +270,17 @@ abstract class ActionScheduler {
273
  );
274
 
275
  $segments = explode( '_', $class );
276
- $segment = isset( $segments[1] ) ? $segments[1] : $class;
277
 
278
  return isset( $cli_segments[ $segment ] ) && $cli_segments[ $segment ];
279
  }
280
 
281
  final public function __clone() {
282
- trigger_error( 'Singleton. No cloning allowed!', E_USER_ERROR );
283
  }
284
 
285
  final public function __wakeup() {
286
- trigger_error( 'Singleton. No serialization allowed!', E_USER_ERROR );
287
  }
288
 
289
  final private function __construct() {}
5
 
6
  /**
7
  * Class ActionScheduler
 
8
  * @codeCoverageIgnore
9
  */
10
  abstract class ActionScheduler {
11
  private static $plugin_file = '';
12
  /** @var ActionScheduler_ActionFactory */
13
+ private static $factory = NULL;
14
  /** @var bool */
15
  private static $data_store_initialized = false;
16
 
17
  public static function factory() {
18
+ if ( !isset(self::$factory) ) {
19
  self::$factory = new ActionScheduler_ActionFactory();
20
  }
21
  return self::$factory;
43
 
44
  /**
45
  * Get the absolute system path to the plugin directory, or a file therein
 
46
  * @static
47
  * @param string $path
48
  * @return string
49
  */
50
  public static function plugin_path( $path ) {
51
+ $base = dirname(self::$plugin_file);
52
  if ( $path ) {
53
+ return trailingslashit($base).$path;
54
  } else {
55
+ return untrailingslashit($base);
56
  }
57
  }
58
 
59
  /**
60
  * Get the absolute URL to the plugin directory, or a file therein
 
61
  * @static
62
  * @param string $path
63
  * @return string
64
  */
65
  public static function plugin_url( $path ) {
66
+ return plugins_url($path, self::$plugin_file);
67
  }
68
 
69
  public static function autoload( $class ) {
91
  $dir = $classes_dir . 'schema' . $d;
92
  } elseif ( strpos( $class, 'ActionScheduler' ) === 0 ) {
93
  $segments = explode( '_', $class );
94
+ $type = isset( $segments[ 1 ] ) ? $segments[ 1 ] : '';
95
 
96
  switch ( $type ) {
97
  case 'WPCLI':
119
  }
120
 
121
  if ( file_exists( "{$dir}{$class}.php" ) ) {
122
+ include( "{$dir}{$class}.php" );
123
  return;
124
  }
125
  }
139
  */
140
  do_action( 'action_scheduler_pre_init' );
141
 
142
+ require_once( self::plugin_path( 'functions.php' ) );
143
  ActionScheduler_DataController::init();
144
 
145
  $store = self::store();
161
  }
162
 
163
  if ( apply_filters( 'action_scheduler_load_deprecated_functions', true ) ) {
164
+ require_once( self::plugin_path( 'deprecated/functions.php' ) );
165
  }
166
 
167
  if ( defined( 'WP_CLI' ) && WP_CLI ) {
248
  );
249
 
250
  $segments = explode( '_', $class );
251
+ $segment = isset( $segments[ 1 ] ) ? $segments[ 1 ] : $class;
252
 
253
  return isset( $migration_segments[ $segment ] ) && $migration_segments[ $segment ];
254
  }
270
  );
271
 
272
  $segments = explode( '_', $class );
273
+ $segment = isset( $segments[ 1 ] ) ? $segments[ 1 ] : $class;
274
 
275
  return isset( $cli_segments[ $segment ] ) && $cli_segments[ $segment ];
276
  }
277
 
278
  final public function __clone() {
279
+ trigger_error("Singleton. No cloning allowed!", E_USER_ERROR);
280
  }
281
 
282
  final public function __wakeup() {
283
+ trigger_error("Singleton. No serialization allowed!", E_USER_ERROR);
284
  }
285
 
286
  final private function __construct() {}
lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Abstract_ListTable.php CHANGED
@@ -25,22 +25,30 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
25
 
26
  /**
27
  * The table name
 
 
28
  */
29
  protected $table_name;
30
 
31
  /**
32
  * Package name, used to get options from WP_List_Table::get_items_per_page.
 
 
33
  */
34
  protected $package;
35
 
36
  /**
37
  * How many items do we render per page?
 
 
38
  */
39
  protected $items_per_page = 10;
40
 
41
  /**
42
  * Enables search in this table listing. If this array
43
  * is empty it means the listing is not searchable.
 
 
44
  */
45
  protected $search_by = array();
46
 
@@ -48,6 +56,8 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
48
  * Columns to show in the table listing. It is a key => value pair. The
49
  * key must much the table column name and the value is the label, which is
50
  * automatically translated.
 
 
51
  */
52
  protected $columns = array();
53
 
@@ -58,34 +68,51 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
58
  * The array of actions are key => value, where key is the method name
59
  * (with the prefix row_action_<key>) and the value is the label
60
  * and title.
 
 
61
  */
62
  protected $row_actions = array();
63
 
64
  /**
65
  * The Primary key of our table
 
 
66
  */
67
  protected $ID = 'ID';
68
 
69
  /**
70
  * Enables sorting, it expects an array
71
  * of columns (the column names are the values)
 
 
72
  */
73
  protected $sort_by = array();
74
 
 
 
 
 
 
75
  protected $filter_by = array();
76
 
77
  /**
78
- * @var array The status name => count combinations for this table's items. Used to display status filters.
 
 
79
  */
80
  protected $status_counts = array();
81
 
82
  /**
83
- * @var array Notices to display when loading the table. Array of arrays of form array( 'class' => {updated|error}, 'message' => 'This is the notice text display.' ).
 
 
84
  */
85
  protected $admin_notices = array();
86
 
87
  /**
88
- * @var string Localised string displayed in the <h1> element above the able.
 
 
89
  */
90
  protected $table_header;
91
 
@@ -99,6 +126,8 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
99
  * validations and afterwards will execute the bulk method, with two arguments. The first argument
100
  * is the array with primary keys, the second argument is a string with a list of the primary keys,
101
  * escaped and ready to use (with `IN`).
 
 
102
  */
103
  protected $bulk_actions = array();
104
 
@@ -106,7 +135,11 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
106
  * Makes translation easier, it basically just wraps
107
  * `_x` with some default (the package name).
108
  *
109
- * @deprecated 3.0.0
 
 
 
 
110
  */
111
  protected function translate( $text, $context = '' ) {
112
  return $text;
@@ -116,6 +149,10 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
116
  * Reads `$this->bulk_actions` and returns an array that WP_List_Table understands. It
117
  * also validates that the bulk method handler exists. It throws an exception because
118
  * this is a library meant for developers and missing a bulk method is a development-time error.
 
 
 
 
119
  */
120
  protected function get_bulk_actions() {
121
  $actions = array();
@@ -149,21 +186,28 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
149
  $method = 'bulk_' . $action;
150
  if ( array_key_exists( $action, $this->bulk_actions ) && is_callable( array( $this, $method ) ) && ! empty( $_GET['ID'] ) && is_array( $_GET['ID'] ) ) {
151
  $ids_sql = '(' . implode( ',', array_fill( 0, count( $_GET['ID'] ), '%s' ) ) . ')';
152
- $this->$method( $_GET['ID'], $wpdb->prepare( $ids_sql, $_GET['ID'] ) );
 
153
  }
154
 
155
- wp_redirect(
156
- remove_query_arg(
157
- array( '_wp_http_referer', '_wpnonce', 'ID', 'action', 'action2' ),
158
- wp_unslash( $_SERVER['REQUEST_URI'] )
159
- )
160
- );
161
- exit;
 
 
162
  }
163
 
164
  /**
165
  * Default code for deleting entries.
166
  * validated already by process_bulk_action()
 
 
 
 
167
  */
168
  protected function bulk_delete( array $ids, $ids_sql ) {
169
  $store = ActionScheduler::store();
@@ -179,7 +223,7 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
179
  protected function prepare_column_headers() {
180
  $this->_column_headers = array(
181
  $this->get_columns(),
182
- array(),
183
  $this->get_sortable_columns(),
184
  );
185
  }
@@ -219,7 +263,7 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
219
  protected function get_items_query_limit() {
220
  global $wpdb;
221
 
222
- $per_page = $this->get_items_per_page( $this->package . '_items_per_page', $this->items_per_page );
223
  return $wpdb->prepare( 'LIMIT %d', $per_page );
224
  }
225
 
@@ -229,7 +273,7 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
229
  * @return int
230
  */
231
  protected function get_items_offset() {
232
- $per_page = $this->get_items_per_page( $this->package . '_items_per_page', $this->items_per_page );
233
  $current_page = $this->get_pagenum();
234
  if ( 1 < $current_page ) {
235
  $offset = $per_page * ( $current_page - 1 );
@@ -278,8 +322,8 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
278
 
279
  $valid_sortable_columns = array_values( $this->sort_by );
280
 
281
- if ( ! empty( $_GET['orderby'] ) && in_array( $_GET['orderby'], $valid_sortable_columns ) ) {
282
- $orderby = sanitize_text_field( $_GET['orderby'] );
283
  } else {
284
  $orderby = $valid_sortable_columns[0];
285
  }
@@ -294,7 +338,7 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
294
  */
295
  protected function get_request_order() {
296
 
297
- if ( ! empty( $_GET['order'] ) && 'desc' === strtolower( $_GET['order'] ) ) {
298
  $order = 'DESC';
299
  } else {
300
  $order = 'ASC';
@@ -309,7 +353,7 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
309
  * @return string
310
  */
311
  protected function get_request_status() {
312
- $status = ( ! empty( $_GET['status'] ) ) ? $_GET['status'] : '';
313
  return $status;
314
  }
315
 
@@ -319,7 +363,7 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
319
  * @return string
320
  */
321
  protected function get_request_search_query() {
322
- $search_query = ( ! empty( $_GET['s'] ) ) ? $_GET['s'] : '';
323
  return $search_query;
324
  }
325
 
@@ -331,7 +375,7 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
331
  */
332
  protected function get_table_columns() {
333
  $columns = array_keys( $this->columns );
334
- if ( ! in_array( $this->ID, $columns ) ) {
335
  $columns[] = $this->ID;
336
  }
337
 
@@ -345,21 +389,22 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
345
  * If the current request does not have any search or if this list table does not support
346
  * that feature it will return an empty string.
347
  *
348
- * TODO:
349
- * - Improve search doing LIKE by word rather than by phrases.
350
- *
351
  * @return string
352
  */
353
  protected function get_items_query_search() {
354
  global $wpdb;
355
 
356
- if ( empty( $_GET['s'] ) || empty( $this->search_by ) ) {
357
  return '';
358
  }
359
 
 
 
360
  $filter = array();
361
  foreach ( $this->search_by as $column ) {
362
- $filter[] = $wpdb->prepare( '`' . $column . '` like "%%s%"', $wpdb->esc_like( $_GET['s'] ) );
 
 
363
  }
364
  return implode( ' OR ', $filter );
365
  }
@@ -371,18 +416,18 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
371
  protected function get_items_query_filters() {
372
  global $wpdb;
373
 
374
- if ( ! $this->filter_by || empty( $_GET['filter_by'] ) || ! is_array( $_GET['filter_by'] ) ) {
375
  return '';
376
  }
377
 
378
  $filter = array();
379
 
380
  foreach ( $this->filter_by as $column => $options ) {
381
- if ( empty( $_GET['filter_by'][ $column ] ) || empty( $options[ $_GET['filter_by'][ $column ] ] ) ) {
382
  continue;
383
  }
384
 
385
- $filter[] = $wpdb->prepare( "`$column` = %s", $_GET['filter_by'][ $column ] );
386
  }
387
 
388
  return implode( ' AND ', $filter );
@@ -405,9 +450,9 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
405
 
406
  $this->process_row_actions();
407
 
408
- if ( ! empty( $_REQUEST['_wp_http_referer'] ) ) {
409
  // _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter
410
- wp_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
411
  exit;
412
  }
413
 
@@ -432,11 +477,11 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
432
 
433
  $sql = "SELECT $columns FROM {$this->table_name} {$where} {$order} {$limit} {$offset}";
434
 
435
- $this->set_items( $wpdb->get_results( $sql, ARRAY_A ) );
436
 
437
  $query_count = "SELECT COUNT({$this->ID}) FROM {$this->table_name} {$where}";
438
- $total_items = $wpdb->get_var( $query_count );
439
- $per_page = $this->get_items_per_page( $this->package . '_items_per_page', $this->items_per_page );
440
  $this->set_pagination_args(
441
  array(
442
  'total_items' => $total_items,
@@ -446,6 +491,11 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
446
  );
447
  }
448
 
 
 
 
 
 
449
  public function extra_tablenav( $which ) {
450
  if ( ! $this->filter_by || 'top' !== $which ) {
451
  return;
@@ -454,7 +504,7 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
454
  echo '<div class="alignleft actions">';
455
 
456
  foreach ( $this->filter_by as $id => $options ) {
457
- $default = ! empty( $_GET['filter_by'][ $id ] ) ? $_GET['filter_by'][ $id ] : '';
458
  if ( empty( $options[ $default ] ) ) {
459
  $default = '';
460
  }
@@ -462,7 +512,7 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
462
  echo '<select name="filter_by[' . esc_attr( $id ) . ']" class="first" id="filter-by-' . esc_attr( $id ) . '">';
463
 
464
  foreach ( $options as $value => $label ) {
465
- echo '<option value="' . esc_attr( $value ) . '" ' . esc_html( $value == $default ? 'selected' : '' ) . '>'
466
  . esc_html( $label )
467
  . '</option>';
468
  }
@@ -477,6 +527,8 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
477
  /**
478
  * Set the data for displaying. It will attempt to unserialize (There is a chance that some columns
479
  * are serialized). This can be override in child classes for futher data transformation.
 
 
480
  */
481
  protected function set_items( array $items ) {
482
  $this->items = array();
@@ -489,6 +541,8 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
489
  * Renders the checkbox for each row, this is the first column and it is named ID regardless
490
  * of how the primary key is named (to keep the code simpler). The bulk actions will do the proper
491
  * name transformation though using `$this->ID`.
 
 
492
  */
493
  public function column_cb( $row ) {
494
  return '<input name="ID[]" type="checkbox" value="' . esc_attr( $row[ $this->ID ] ) . '" />';
@@ -500,9 +554,9 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
500
  * This method renders the action menu, it reads the definition from the $row_actions property,
501
  * and it checks that the row action method exists before rendering it.
502
  *
503
- * @param array $row Row to render
504
- * @param $column_name Current row
505
- * @return
506
  */
507
  protected function maybe_render_actions( $row, $column_name ) {
508
  if ( empty( $this->row_actions[ $column_name ] ) ) {
@@ -539,31 +593,46 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
539
  return $actions;
540
  }
541
 
 
 
 
 
 
542
  protected function process_row_actions() {
543
  $parameters = array( 'row_action', 'row_id', 'nonce' );
544
  foreach ( $parameters as $parameter ) {
545
- if ( empty( $_REQUEST[ $parameter ] ) ) {
546
  return;
547
  }
548
  }
549
 
550
- $method = 'row_action_' . $_REQUEST['row_action'];
 
 
 
551
 
552
- if ( $_REQUEST['nonce'] === wp_create_nonce( $_REQUEST['row_action'] . '::' . $_REQUEST['row_id'] ) && method_exists( $this, $method ) ) {
553
- $this->$method( $_REQUEST['row_id'] );
554
  }
555
 
556
- wp_redirect(
557
- remove_query_arg(
558
- array( 'row_id', 'row_action', 'nonce' ),
559
- wp_unslash( $_SERVER['REQUEST_URI'] )
560
- )
561
- );
562
- exit;
 
 
563
  }
564
 
565
  /**
566
  * Default column formatting, it will escape everythig for security.
 
 
 
 
 
567
  */
568
  public function column_default( $item, $column_name ) {
569
  $column_html = esc_html( $item[ $column_name ] );
@@ -588,7 +657,7 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
588
  */
589
  protected function display_admin_notices() {
590
  foreach ( $this->admin_notices as $notice ) {
591
- echo '<div id="message" class="' . $notice['class'] . '">';
592
  echo ' <p>' . wp_kses_post( $notice['message'] ) . '</p>';
593
  echo '</div>';
594
  }
@@ -602,7 +671,7 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
602
  $status_list_items = array();
603
  $request_status = $this->get_request_status();
604
 
605
- // Helper to set 'all' filter when not set on status counts passed in
606
  if ( ! isset( $this->status_counts['all'] ) ) {
607
  $this->status_counts = array( 'all' => array_sum( $this->status_counts ) ) + $this->status_counts;
608
  }
@@ -626,7 +695,7 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
626
 
627
  if ( $status_list_items ) {
628
  echo '<ul class="subsubsub">';
629
- echo implode( " | \n", $status_list_items );
630
  echo '</ul>';
631
  }
632
  }
@@ -638,14 +707,14 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
638
  */
639
  protected function display_table() {
640
  echo '<form id="' . esc_attr( $this->_args['plural'] ) . '-filter" method="get">';
641
- foreach ( $_GET as $key => $value ) {
642
- if ( '_' === $key[0] || 'paged' === $key ) {
643
  continue;
644
  }
645
  echo '<input type="hidden" name="' . esc_attr( $key ) . '" value="' . esc_attr( $value ) . '" />';
646
  }
647
  if ( ! empty( $this->search_by ) ) {
648
- echo $this->search_box( $this->get_search_box_button_text(), 'plugin' ); // WPCS: XSS OK
649
  }
650
  parent::display();
651
  echo '</form>';
@@ -658,9 +727,9 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
658
  $this->process_bulk_action();
659
  $this->process_row_actions();
660
 
661
- if ( ! empty( $_REQUEST['_wp_http_referer'] ) ) {
662
  // _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter
663
- wp_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
664
  exit;
665
  }
666
  }
@@ -685,4 +754,13 @@ abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
685
  protected function get_search_box_placeholder() {
686
  return esc_html__( 'Search', 'action-scheduler' );
687
  }
 
 
 
 
 
 
 
 
 
688
  }
25
 
26
  /**
27
  * The table name
28
+ *
29
+ * @var string
30
  */
31
  protected $table_name;
32
 
33
  /**
34
  * Package name, used to get options from WP_List_Table::get_items_per_page.
35
+ *
36
+ * @var string
37
  */
38
  protected $package;
39
 
40
  /**
41
  * How many items do we render per page?
42
+ *
43
+ * @var int
44
  */
45
  protected $items_per_page = 10;
46
 
47
  /**
48
  * Enables search in this table listing. If this array
49
  * is empty it means the listing is not searchable.
50
+ *
51
+ * @var array
52
  */
53
  protected $search_by = array();
54
 
56
  * Columns to show in the table listing. It is a key => value pair. The
57
  * key must much the table column name and the value is the label, which is
58
  * automatically translated.
59
+ *
60
+ * @var array
61
  */
62
  protected $columns = array();
63
 
68
  * The array of actions are key => value, where key is the method name
69
  * (with the prefix row_action_<key>) and the value is the label
70
  * and title.
71
+ *
72
+ * @var array
73
  */
74
  protected $row_actions = array();
75
 
76
  /**
77
  * The Primary key of our table
78
+ *
79
+ * @var string
80
  */
81
  protected $ID = 'ID';
82
 
83
  /**
84
  * Enables sorting, it expects an array
85
  * of columns (the column names are the values)
86
+ *
87
+ * @var array
88
  */
89
  protected $sort_by = array();
90
 
91
+ /**
92
+ * The default sort order
93
+ *
94
+ * @var string
95
+ */
96
  protected $filter_by = array();
97
 
98
  /**
99
+ * The status name => count combinations for this table's items. Used to display status filters.
100
+ *
101
+ * @var array
102
  */
103
  protected $status_counts = array();
104
 
105
  /**
106
+ * Notices to display when loading the table. Array of arrays of form array( 'class' => {updated|error}, 'message' => 'This is the notice text display.' ).
107
+ *
108
+ * @var array
109
  */
110
  protected $admin_notices = array();
111
 
112
  /**
113
+ * Localised string displayed in the <h1> element above the able.
114
+ *
115
+ * @var string
116
  */
117
  protected $table_header;
118
 
126
  * validations and afterwards will execute the bulk method, with two arguments. The first argument
127
  * is the array with primary keys, the second argument is a string with a list of the primary keys,
128
  * escaped and ready to use (with `IN`).
129
+ *
130
+ * @var array
131
  */
132
  protected $bulk_actions = array();
133
 
135
  * Makes translation easier, it basically just wraps
136
  * `_x` with some default (the package name).
137
  *
138
+ * @param string $text The new text to translate.
139
+ * @param string $context The context of the text.
140
+ * @return string|void The translated text.
141
+ *
142
+ * @deprecated 3.0.0 Use `_x()` instead.
143
  */
144
  protected function translate( $text, $context = '' ) {
145
  return $text;
149
  * Reads `$this->bulk_actions` and returns an array that WP_List_Table understands. It
150
  * also validates that the bulk method handler exists. It throws an exception because
151
  * this is a library meant for developers and missing a bulk method is a development-time error.
152
+ *
153
+ * @return array
154
+ *
155
+ * @throws RuntimeException Throws RuntimeException when the bulk action does not have a callback method.
156
  */
157
  protected function get_bulk_actions() {
158
  $actions = array();
186
  $method = 'bulk_' . $action;
187
  if ( array_key_exists( $action, $this->bulk_actions ) && is_callable( array( $this, $method ) ) && ! empty( $_GET['ID'] ) && is_array( $_GET['ID'] ) ) {
188
  $ids_sql = '(' . implode( ',', array_fill( 0, count( $_GET['ID'] ), '%s' ) ) . ')';
189
+ $id = array_map( 'absint', $_GET['ID'] );
190
+ $this->$method( $id, $wpdb->prepare( $ids_sql, $id ) ); //phpcs:ignore WordPress.DB.PreparedSQL
191
  }
192
 
193
+ if ( isset( $_SERVER['REQUEST_URI'] ) ) {
194
+ wp_safe_redirect(
195
+ remove_query_arg(
196
+ array( '_wp_http_referer', '_wpnonce', 'ID', 'action', 'action2' ),
197
+ esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) )
198
+ )
199
+ );
200
+ exit;
201
+ }
202
  }
203
 
204
  /**
205
  * Default code for deleting entries.
206
  * validated already by process_bulk_action()
207
+ *
208
+ * @param array $ids ids of the items to delete.
209
+ * @param string $ids_sql the sql for the ids.
210
+ * @return void
211
  */
212
  protected function bulk_delete( array $ids, $ids_sql ) {
213
  $store = ActionScheduler::store();
223
  protected function prepare_column_headers() {
224
  $this->_column_headers = array(
225
  $this->get_columns(),
226
+ get_hidden_columns( $this->screen ),
227
  $this->get_sortable_columns(),
228
  );
229
  }
263
  protected function get_items_query_limit() {
264
  global $wpdb;
265
 
266
+ $per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
267
  return $wpdb->prepare( 'LIMIT %d', $per_page );
268
  }
269
 
273
  * @return int
274
  */
275
  protected function get_items_offset() {
276
+ $per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
277
  $current_page = $this->get_pagenum();
278
  if ( 1 < $current_page ) {
279
  $offset = $per_page * ( $current_page - 1 );
322
 
323
  $valid_sortable_columns = array_values( $this->sort_by );
324
 
325
+ if ( ! empty( $_GET['orderby'] ) && in_array( $_GET['orderby'], $valid_sortable_columns, true ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
326
+ $orderby = sanitize_text_field( wp_unslash( $_GET['orderby'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
327
  } else {
328
  $orderby = $valid_sortable_columns[0];
329
  }
338
  */
339
  protected function get_request_order() {
340
 
341
+ if ( ! empty( $_GET['order'] ) && 'desc' === strtolower( sanitize_text_field( wp_unslash( $_GET['order'] ) ) ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
342
  $order = 'DESC';
343
  } else {
344
  $order = 'ASC';
353
  * @return string
354
  */
355
  protected function get_request_status() {
356
+ $status = ( ! empty( $_GET['status'] ) ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
357
  return $status;
358
  }
359
 
363
  * @return string
364
  */
365
  protected function get_request_search_query() {
366
+ $search_query = ( ! empty( $_GET['s'] ) ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
367
  return $search_query;
368
  }
369
 
375
  */
376
  protected function get_table_columns() {
377
  $columns = array_keys( $this->columns );
378
+ if ( ! in_array( $this->ID, $columns, true ) ) {
379
  $columns[] = $this->ID;
380
  }
381
 
389
  * If the current request does not have any search or if this list table does not support
390
  * that feature it will return an empty string.
391
  *
 
 
 
392
  * @return string
393
  */
394
  protected function get_items_query_search() {
395
  global $wpdb;
396
 
397
+ if ( empty( $_GET['s'] ) || empty( $this->search_by ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
398
  return '';
399
  }
400
 
401
+ $search_string = sanitize_text_field( wp_unslash( $_GET['s'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
402
+
403
  $filter = array();
404
  foreach ( $this->search_by as $column ) {
405
+ $wild = '%';
406
+ $sql_like = $wild . $wpdb->esc_like( $search_string ) . $wild;
407
+ $filter[] = $wpdb->prepare( '`' . $column . '` LIKE %s', $sql_like ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.DB.PreparedSQL.NotPrepared
408
  }
409
  return implode( ' OR ', $filter );
410
  }
416
  protected function get_items_query_filters() {
417
  global $wpdb;
418
 
419
+ if ( ! $this->filter_by || empty( $_GET['filter_by'] ) || ! is_array( $_GET['filter_by'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
420
  return '';
421
  }
422
 
423
  $filter = array();
424
 
425
  foreach ( $this->filter_by as $column => $options ) {
426
+ if ( empty( $_GET['filter_by'][ $column ] ) || empty( $options[ $_GET['filter_by'][ $column ] ] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
427
  continue;
428
  }
429
 
430
+ $filter[] = $wpdb->prepare( "`$column` = %s", sanitize_text_field( wp_unslash( $_GET['filter_by'][ $column ] ) ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
431
  }
432
 
433
  return implode( ' AND ', $filter );
450
 
451
  $this->process_row_actions();
452
 
453
+ if ( ! empty( $_REQUEST['_wp_http_referer'] && ! empty( $_SERVER['REQUEST_URI'] ) ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
454
  // _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter
455
+ wp_safe_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) );
456
  exit;
457
  }
458
 
477
 
478
  $sql = "SELECT $columns FROM {$this->table_name} {$where} {$order} {$limit} {$offset}";
479
 
480
+ $this->set_items( $wpdb->get_results( $sql, ARRAY_A ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
481
 
482
  $query_count = "SELECT COUNT({$this->ID}) FROM {$this->table_name} {$where}";
483
+ $total_items = $wpdb->get_var( $query_count ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
484
+ $per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
485
  $this->set_pagination_args(
486
  array(
487
  'total_items' => $total_items,
491
  );
492
  }
493
 
494
+ /**
495
+ * Display the table.
496
+ *
497
+ * @param string $which The name of the table.
498
+ */
499
  public function extra_tablenav( $which ) {
500
  if ( ! $this->filter_by || 'top' !== $which ) {
501
  return;
504
  echo '<div class="alignleft actions">';
505
 
506
  foreach ( $this->filter_by as $id => $options ) {
507
+ $default = ! empty( $_GET['filter_by'][ $id ] ) ? sanitize_text_field( wp_unslash( $_GET['filter_by'][ $id ] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
508
  if ( empty( $options[ $default ] ) ) {
509
  $default = '';
510
  }
512
  echo '<select name="filter_by[' . esc_attr( $id ) . ']" class="first" id="filter-by-' . esc_attr( $id ) . '">';
513
 
514
  foreach ( $options as $value => $label ) {
515
+ echo '<option value="' . esc_attr( $value ) . '" ' . esc_html( $value === $default ? 'selected' : '' ) . '>'
516
  . esc_html( $label )
517
  . '</option>';
518
  }
527
  /**
528
  * Set the data for displaying. It will attempt to unserialize (There is a chance that some columns
529
  * are serialized). This can be override in child classes for futher data transformation.
530
+ *
531
+ * @param array $items Items array.
532
  */
533
  protected function set_items( array $items ) {
534
  $this->items = array();
541
  * Renders the checkbox for each row, this is the first column and it is named ID regardless
542
  * of how the primary key is named (to keep the code simpler). The bulk actions will do the proper
543
  * name transformation though using `$this->ID`.
544
+ *
545
+ * @param array $row The row to render.
546
  */
547
  public function column_cb( $row ) {
548
  return '<input name="ID[]" type="checkbox" value="' . esc_attr( $row[ $this->ID ] ) . '" />';
554
  * This method renders the action menu, it reads the definition from the $row_actions property,
555
  * and it checks that the row action method exists before rendering it.
556
  *
557
+ * @param array $row Row to be rendered.
558
+ * @param string $column_name Column name.
559
+ * @return string
560
  */
561
  protected function maybe_render_actions( $row, $column_name ) {
562
  if ( empty( $this->row_actions[ $column_name ] ) ) {
593
  return $actions;
594
  }
595
 
596
+ /**
597
+ * Process the bulk actions.
598
+ *
599
+ * @return void
600
+ */
601
  protected function process_row_actions() {
602
  $parameters = array( 'row_action', 'row_id', 'nonce' );
603
  foreach ( $parameters as $parameter ) {
604
+ if ( empty( $_REQUEST[ $parameter ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
605
  return;
606
  }
607
  }
608
 
609
+ $action = sanitize_text_field( wp_unslash( $_REQUEST['row_action'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
610
+ $row_id = sanitize_text_field( wp_unslash( $_REQUEST['row_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
611
+ $nonce = sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
612
+ $method = 'row_action_' . $action; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
613
 
614
+ if ( wp_verify_nonce( $nonce, $action . '::' . $row_id ) && method_exists( $this, $method ) ) {
615
+ $this->$method( sanitize_text_field( wp_unslash( $row_id ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
616
  }
617
 
618
+ if ( isset( $_SERVER['REQUEST_URI'] ) ) {
619
+ wp_safe_redirect(
620
+ remove_query_arg(
621
+ array( 'row_id', 'row_action', 'nonce' ),
622
+ esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) )
623
+ )
624
+ );
625
+ exit;
626
+ }
627
  }
628
 
629
  /**
630
  * Default column formatting, it will escape everythig for security.
631
+ *
632
+ * @param array $item The item array.
633
+ * @param string $column_name Column name to display.
634
+ *
635
+ * @return string
636
  */
637
  public function column_default( $item, $column_name ) {
638
  $column_html = esc_html( $item[ $column_name ] );
657
  */
658
  protected function display_admin_notices() {
659
  foreach ( $this->admin_notices as $notice ) {
660
+ echo '<div id="message" class="' . esc_attr( $notice['class'] ) . '">';
661
  echo ' <p>' . wp_kses_post( $notice['message'] ) . '</p>';
662
  echo '</div>';
663
  }
671
  $status_list_items = array();
672
  $request_status = $this->get_request_status();
673
 
674
+ // Helper to set 'all' filter when not set on status counts passed in.
675
  if ( ! isset( $this->status_counts['all'] ) ) {
676
  $this->status_counts = array( 'all' => array_sum( $this->status_counts ) ) + $this->status_counts;
677
  }
695
 
696
  if ( $status_list_items ) {
697
  echo '<ul class="subsubsub">';
698
+ echo implode( " | \n", $status_list_items ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
699
  echo '</ul>';
700
  }
701
  }
707
  */
708
  protected function display_table() {
709
  echo '<form id="' . esc_attr( $this->_args['plural'] ) . '-filter" method="get">';
710
+ foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
711
+ if ( '_' === $key[0] || 'paged' === $key || 'ID' === $key ) {
712
  continue;
713
  }
714
  echo '<input type="hidden" name="' . esc_attr( $key ) . '" value="' . esc_attr( $value ) . '" />';
715
  }
716
  if ( ! empty( $this->search_by ) ) {
717
+ echo $this->search_box( $this->get_search_box_button_text(), 'plugin' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
718
  }
719
  parent::display();
720
  echo '</form>';
727
  $this->process_bulk_action();
728
  $this->process_row_actions();
729
 
730
+ if ( ! empty( $_REQUEST['_wp_http_referer'] ) && ! empty( $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
731
  // _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter
732
+ wp_safe_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) );
733
  exit;
734
  }
735
  }
754
  protected function get_search_box_placeholder() {
755
  return esc_html__( 'Search', 'action-scheduler' );
756
  }
757
+
758
+ /**
759
+ * Gets the screen per_page option name.
760
+ *
761
+ * @return string
762
+ */
763
+ protected function get_per_page_option_name() {
764
+ return $this->package . '_items_per_page';
765
+ }
766
  }
lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php CHANGED
@@ -43,7 +43,7 @@ abstract class ActionScheduler_Abstract_QueueRunner extends ActionScheduler_Abst
43
  /**
44
  * Process an individual action.
45
  *
46
- * @param int $action_id The action ID to process.
47
  * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
48
  * Generally, this should be capitalised and not localised as it's a proper noun.
49
  */
@@ -83,7 +83,7 @@ abstract class ActionScheduler_Abstract_QueueRunner extends ActionScheduler_Abst
83
  * Schedule the next instance of the action if necessary.
84
  *
85
  * @param ActionScheduler_Action $action
86
- * @param int $action_id
87
  */
88
  protected function schedule_next_instance( ActionScheduler_Action $action, $action_id ) {
89
  try {
43
  /**
44
  * Process an individual action.
45
  *
46
+ * @param int $action_id The action ID to process.
47
  * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
48
  * Generally, this should be capitalised and not localised as it's a proper noun.
49
  */
83
  * Schedule the next instance of the action if necessary.
84
  *
85
  * @param ActionScheduler_Action $action
86
+ * @param int $action_id
87
  */
88
  protected function schedule_next_instance( ActionScheduler_Action $action, $action_id ) {
89
  try {
lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Abstract_RecurringSchedule.php CHANGED
@@ -15,14 +15,14 @@ abstract class ActionScheduler_Abstract_RecurringSchedule extends ActionSchedule
15
  *
16
  * @var DateTime
17
  */
18
- private $first_date = null;
19
 
20
  /**
21
  * Timestamp equivalent of @see $this->first_date
22
  *
23
  * @var int
24
  */
25
- protected $first_timestamp = null;
26
 
27
  /**
28
  * The recurrance between each time an action is run using this schedule.
@@ -35,8 +35,8 @@ abstract class ActionScheduler_Abstract_RecurringSchedule extends ActionSchedule
35
  protected $recurrence;
36
 
37
  /**
38
- * @param DateTime $date The date & time to run the action.
39
- * @param mixed $recurrence The data used to determine the schedule's recurrance.
40
  * @param DateTime|null $first (Optional) The date & time the first instance of this interval schedule ran. Default null, meaning this is the first instance.
41
  */
42
  public function __construct( DateTime $date, $recurrence, DateTime $first = null ) {
@@ -70,19 +70,15 @@ abstract class ActionScheduler_Abstract_RecurringSchedule extends ActionSchedule
70
 
71
  /**
72
  * For PHP 5.2 compat, since DateTime objects can't be serialized
73
- *
74
  * @return array
75
  */
76
  public function __sleep() {
77
- $sleep_params = parent::__sleep();
78
  $this->first_timestamp = $this->first_date->getTimestamp();
79
- return array_merge(
80
- $sleep_params,
81
- array(
82
- 'first_timestamp',
83
- 'recurrence',
84
- )
85
- );
86
  }
87
 
88
  /**
15
  *
16
  * @var DateTime
17
  */
18
+ private $first_date = NULL;
19
 
20
  /**
21
  * Timestamp equivalent of @see $this->first_date
22
  *
23
  * @var int
24
  */
25
+ protected $first_timestamp = NULL;
26
 
27
  /**
28
  * The recurrance between each time an action is run using this schedule.
35
  protected $recurrence;
36
 
37
  /**
38
+ * @param DateTime $date The date & time to run the action.
39
+ * @param mixed $recurrence The data used to determine the schedule's recurrance.
40
  * @param DateTime|null $first (Optional) The date & time the first instance of this interval schedule ran. Default null, meaning this is the first instance.
41
  */
42
  public function __construct( DateTime $date, $recurrence, DateTime $first = null ) {
70
 
71
  /**
72
  * For PHP 5.2 compat, since DateTime objects can't be serialized
 
73
  * @return array
74
  */
75
  public function __sleep() {
76
+ $sleep_params = parent::__sleep();
77
  $this->first_timestamp = $this->first_date->getTimestamp();
78
+ return array_merge( $sleep_params, array(
79
+ 'first_timestamp',
80
+ 'recurrence'
81
+ ) );
 
 
 
82
  }
83
 
84
  /**
lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schedule.php CHANGED
@@ -10,14 +10,14 @@ abstract class ActionScheduler_Abstract_Schedule extends ActionScheduler_Schedul
10
  *
11
  * @var DateTime
12
  */
13
- private $scheduled_date = null;
14
 
15
  /**
16
  * Timestamp equivalent of @see $this->scheduled_date
17
  *
18
  * @var int
19
  */
20
- protected $scheduled_timestamp = null;
21
 
22
  /**
23
  * @param DateTime $date The date & time to run the action.
@@ -67,7 +67,6 @@ abstract class ActionScheduler_Abstract_Schedule extends ActionScheduler_Schedul
67
 
68
  /**
69
  * For PHP 5.2 compat, since DateTime objects can't be serialized
70
- *
71
  * @return array
72
  */
73
  public function __sleep() {
10
  *
11
  * @var DateTime
12
  */
13
+ private $scheduled_date = NULL;
14
 
15
  /**
16
  * Timestamp equivalent of @see $this->scheduled_date
17
  *
18
  * @var int
19
  */
20
+ protected $scheduled_timestamp = NULL;
21
 
22
  /**
23
  * @param DateTime $date The date & time to run the action.
67
 
68
  /**
69
  * For PHP 5.2 compat, since DateTime objects can't be serialized
 
70
  * @return array
71
  */
72
  public function __sleep() {
lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schema.php CHANGED
@@ -17,10 +17,21 @@ abstract class ActionScheduler_Abstract_Schema {
17
  */
18
  protected $schema_version = 1;
19
 
 
 
 
 
 
20
  /**
21
  * @var array Names of tables that will be registered by this class.
22
  */
23
- protected $tables = array();
 
 
 
 
 
 
24
 
25
  /**
26
  * Register tables with WordPress, and create them if needed.
@@ -42,6 +53,13 @@ abstract class ActionScheduler_Abstract_Schema {
42
  // create the tables
43
  if ( $this->schema_update_required() || $force_update ) {
44
  foreach ( $this->tables as $table ) {
 
 
 
 
 
 
 
45
  $this->update_table( $table );
46
  }
47
  $this->mark_schema_update_complete();
@@ -63,29 +81,29 @@ abstract class ActionScheduler_Abstract_Schema {
63
  * @return bool
64
  */
65
  private function schema_update_required() {
66
- $option_name = 'schema-' . static::class;
67
- $version_found_in_db = get_option( $option_name, 0 );
68
 
69
  // Check for schema option stored by the Action Scheduler Custom Tables plugin in case site has migrated from that plugin with an older schema
70
- if ( 0 === $version_found_in_db ) {
71
 
72
  $plugin_option_name = 'schema-';
73
 
74
  switch ( static::class ) {
75
- case 'ActionScheduler_StoreSchema':
76
  $plugin_option_name .= 'Action_Scheduler\Custom_Tables\DB_Store_Table_Maker';
77
  break;
78
- case 'ActionScheduler_LoggerSchema':
79
  $plugin_option_name .= 'Action_Scheduler\Custom_Tables\DB_Logger_Table_Maker';
80
  break;
81
  }
82
 
83
- $version_found_in_db = get_option( $plugin_option_name, 0 );
84
 
85
  delete_option( $plugin_option_name );
86
  }
87
 
88
- return version_compare( $version_found_in_db, $this->schema_version, '<' );
89
  }
90
 
91
  /**
@@ -111,7 +129,7 @@ abstract class ActionScheduler_Abstract_Schema {
111
  * @return void
112
  */
113
  private function update_table( $table ) {
114
- require_once ABSPATH . 'wp-admin/includes/upgrade.php';
115
  $definition = $this->get_table_definition( $table );
116
  if ( $definition ) {
117
  $updated = dbDelta( $definition );
@@ -130,6 +148,25 @@ abstract class ActionScheduler_Abstract_Schema {
130
  * table prefix for the current blog
131
  */
132
  protected function get_full_table_name( $table ) {
133
- return $GLOBALS['wpdb']->prefix . $table;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  }
135
  }
17
  */
18
  protected $schema_version = 1;
19
 
20
+ /**
21
+ * @var string Schema version stored in database.
22
+ */
23
+ protected $db_version;
24
+
25
  /**
26
  * @var array Names of tables that will be registered by this class.
27
  */
28
+ protected $tables = [];
29
+
30
+ /**
31
+ * Can optionally be used by concrete classes to carry out additional initialization work
32
+ * as needed.
33
+ */
34
+ public function init() {}
35
 
36
  /**
37
  * Register tables with WordPress, and create them if needed.
53
  // create the tables
54
  if ( $this->schema_update_required() || $force_update ) {
55
  foreach ( $this->tables as $table ) {
56
+ /**
57
+ * Allow custom processing before updating a table schema.
58
+ *
59
+ * @param string $table Name of table being updated.
60
+ * @param string $db_version Existing version of the table being updated.
61
+ */
62
+ do_action( 'action_scheduler_before_schema_update', $table, $this->db_version );
63
  $this->update_table( $table );
64
  }
65
  $this->mark_schema_update_complete();
81
  * @return bool
82
  */
83
  private function schema_update_required() {
84
+ $option_name = 'schema-' . static::class;
85
+ $this->db_version = get_option( $option_name, 0 );
86
 
87
  // Check for schema option stored by the Action Scheduler Custom Tables plugin in case site has migrated from that plugin with an older schema
88
+ if ( 0 === $this->db_version ) {
89
 
90
  $plugin_option_name = 'schema-';
91
 
92
  switch ( static::class ) {
93
+ case 'ActionScheduler_StoreSchema' :
94
  $plugin_option_name .= 'Action_Scheduler\Custom_Tables\DB_Store_Table_Maker';
95
  break;
96
+ case 'ActionScheduler_LoggerSchema' :
97
  $plugin_option_name .= 'Action_Scheduler\Custom_Tables\DB_Logger_Table_Maker';
98
  break;
99
  }
100
 
101
+ $this->db_version = get_option( $plugin_option_name, 0 );
102
 
103
  delete_option( $plugin_option_name );
104
  }
105
 
106
+ return version_compare( $this->db_version, $this->schema_version, '<' );
107
  }
108
 
109
  /**
129
  * @return void
130
  */
131
  private function update_table( $table ) {
132
+ require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
133
  $definition = $this->get_table_definition( $table );
134
  if ( $definition ) {
135
  $updated = dbDelta( $definition );
148
  * table prefix for the current blog
149
  */
150
  protected function get_full_table_name( $table ) {
151
+ return $GLOBALS[ 'wpdb' ]->prefix . $table;
152
+ }
153
+
154
+ /**
155
+ * Confirms that all of the tables registered by this schema class have been created.
156
+ *
157
+ * @return bool
158
+ */
159
+ public function tables_exist() {
160
+ global $wpdb;
161
+
162
+ $existing_tables = $wpdb->get_col( 'SHOW TABLES' );
163
+ $expected_tables = array_map(
164
+ function ( $table_name ) use ( $wpdb ) {
165
+ return $wpdb->prefix . $table_name;
166
+ },
167
+ $this->tables
168
+ );
169
+
170
+ return count( array_intersect( $existing_tables, $expected_tables ) ) === count( $expected_tables );
171
  }
172
  }
lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Lock.php CHANGED
@@ -8,7 +8,7 @@
8
  abstract class ActionScheduler_Lock {
9
 
10
  /** @var ActionScheduler_Lock */
11
- private static $locker = null;
12
 
13
  /** @var int */
14
  protected static $lock_duration = MINUTE_IN_SECONDS;
@@ -54,7 +54,7 @@ abstract class ActionScheduler_Lock {
54
  */
55
  public static function instance() {
56
  if ( empty( self::$locker ) ) {
57
- $class = apply_filters( 'action_scheduler_lock_class', 'ActionScheduler_OptionLock' );
58
  self::$locker = new $class();
59
  }
60
  return self::$locker;
8
  abstract class ActionScheduler_Lock {
9
 
10
  /** @var ActionScheduler_Lock */
11
+ private static $locker = NULL;
12
 
13
  /** @var int */
14
  protected static $lock_duration = MINUTE_IN_SECONDS;
54
  */
55
  public static function instance() {
56
  if ( empty( self::$locker ) ) {
57
+ $class = apply_filters( 'action_scheduler_lock_class', 'ActionScheduler_OptionLock' );
58
  self::$locker = new $class();
59
  }
60
  return self::$locker;
lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Logger.php CHANGED
@@ -2,31 +2,30 @@
2
 
3
  /**
4
  * Class ActionScheduler_Logger
5
- *
6
  * @codeCoverageIgnore
7
  */
8
  abstract class ActionScheduler_Logger {
9
- private static $logger = null;
10
 
11
  /**
12
  * @return ActionScheduler_Logger
13
  */
14
  public static function instance() {
15
- if ( empty( self::$logger ) ) {
16
- $class = apply_filters( 'action_scheduler_logger_class', 'ActionScheduler_wpCommentLogger' );
17
  self::$logger = new $class();
18
  }
19
  return self::$logger;
20
  }
21
 
22
  /**
23
- * @param string $action_id
24
- * @param string $message
25
  * @param DateTime $date
26
  *
27
  * @return string The log entry ID
28
  */
29
- abstract public function log( $action_id, $message, DateTime $date = null );
30
 
31
  /**
32
  * @param string $entry_id
@@ -87,7 +86,7 @@ abstract class ActionScheduler_Logger {
87
  $this->log( $action_id, $message );
88
  }
89
 
90
- public function log_completed_action( $action_id, $action = null, $context = '' ) {
91
  if ( ! empty( $context ) ) {
92
  /* translators: %s: context */
93
  $message = sprintf( __( 'action complete via %s', 'action-scheduler' ), $context );
@@ -135,12 +134,12 @@ abstract class ActionScheduler_Logger {
135
  }
136
 
137
  /**
138
- * @param string $action_id
139
  * @param Exception|NULL $exception The exception which occured when fetching the action. NULL by default for backward compatibility.
140
  *
141
  * @return ActionScheduler_LogEntry[]
142
  */
143
- public function log_failed_fetch_action( $action_id, Exception $exception = null ) {
144
 
145
  if ( ! is_null( $exception ) ) {
146
  /* translators: %s: exception message */
2
 
3
  /**
4
  * Class ActionScheduler_Logger
 
5
  * @codeCoverageIgnore
6
  */
7
  abstract class ActionScheduler_Logger {
8
+ private static $logger = NULL;
9
 
10
  /**
11
  * @return ActionScheduler_Logger
12
  */
13
  public static function instance() {
14
+ if ( empty(self::$logger) ) {
15
+ $class = apply_filters('action_scheduler_logger_class', 'ActionScheduler_wpCommentLogger');
16
  self::$logger = new $class();
17
  }
18
  return self::$logger;
19
  }
20
 
21
  /**
22
+ * @param string $action_id
23
+ * @param string $message
24
  * @param DateTime $date
25
  *
26
  * @return string The log entry ID
27
  */
28
+ abstract public function log( $action_id, $message, DateTime $date = NULL );
29
 
30
  /**
31
  * @param string $entry_id
86
  $this->log( $action_id, $message );
87
  }
88
 
89
+ public function log_completed_action( $action_id, $action = NULL, $context = '' ) {
90
  if ( ! empty( $context ) ) {
91
  /* translators: %s: context */
92
  $message = sprintf( __( 'action complete via %s', 'action-scheduler' ), $context );
134
  }
135
 
136
  /**
137
+ * @param string $action_id
138
  * @param Exception|NULL $exception The exception which occured when fetching the action. NULL by default for backward compatibility.
139
  *
140
  * @return ActionScheduler_LogEntry[]
141
  */
142
+ public function log_failed_fetch_action( $action_id, Exception $exception = NULL ) {
143
 
144
  if ( ! is_null( $exception ) ) {
145
  /* translators: %s: exception message */
lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_Store.php CHANGED
@@ -2,7 +2,6 @@
2
 
3
  /**
4
  * Class ActionScheduler_Store
5
- *
6
  * @codeCoverageIgnore
7
  */
8
  abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated {
@@ -14,20 +13,20 @@ abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated {
14
  const DEFAULT_CLASS = 'ActionScheduler_wpPostStore';
15
 
16
  /** @var ActionScheduler_Store */
17
- private static $store = null;
18
 
19
  /** @var int */
20
  protected static $max_args_length = 191;
21
 
22
  /**
23
  * @param ActionScheduler_Action $action
24
- * @param DateTime $scheduled_date Optional Date of the first instance
25
- * to store. Otherwise uses the first date of the action's
26
- * schedule.
27
  *
28
  * @return int The action ID
29
  */
30
- abstract public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = null );
31
 
32
  /**
33
  * @param string $action_id
@@ -37,20 +36,94 @@ abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated {
37
  abstract public function fetch_action( $action_id );
38
 
39
  /**
40
- * @param string $hook Hook name/slug.
41
- * @param array $params Hook arguments.
42
- * @return string ID of the next action matching the criteria.
 
 
 
 
 
43
  */
44
- abstract public function find_action( $hook, $params = array() );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  /**
47
- * @param array $query Query parameters.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  * @param string $query_type Whether to select or count the results. Default, select.
49
  *
50
- * @return array|int The IDs of or count of actions matching the query.
51
  */
52
  abstract public function query_actions( $query = array(), $query_type = 'select' );
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  /**
55
  * Get a count of all actions in the store, grouped by status
56
  *
@@ -140,7 +213,7 @@ abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated {
140
  * @return string
141
  */
142
  protected function validate_sql_comparator( $comparison_operator ) {
143
- if ( in_array( $comparison_operator, array( '!=', '>', '>=', '<', '<=', '=' ) ) ) {
144
  return $comparison_operator;
145
  }
146
  return '=';
@@ -150,10 +223,10 @@ abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated {
150
  * Get the time MySQL formated date/time string for an action's (next) scheduled date.
151
  *
152
  * @param ActionScheduler_Action $action
153
- * @param DateTime $scheduled_date (optional)
154
  * @return string
155
  */
156
- protected function get_scheduled_date_string( ActionScheduler_Action $action, DateTime $scheduled_date = null ) {
157
  $next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date;
158
  if ( ! $next ) {
159
  return '0000-00-00 00:00:00';
@@ -167,10 +240,10 @@ abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated {
167
  * Get the time MySQL formated date/time string for an action's (next) scheduled date.
168
  *
169
  * @param ActionScheduler_Action $action
170
- * @param DateTime $scheduled_date (optional)
171
  * @return string
172
  */
173
- protected function get_scheduled_date_string_local( ActionScheduler_Action $action, DateTime $scheduled_date = null ) {
174
  $next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date;
175
  if ( ! $next ) {
176
  return '0000-00-00 00:00:00';
@@ -246,6 +319,7 @@ abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated {
246
  'hook' => $hook,
247
  'status' => self::STATUS_PENDING,
248
  'per_page' => 1000,
 
249
  )
250
  );
251
 
@@ -270,6 +344,7 @@ abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated {
270
  'group' => $group,
271
  'status' => self::STATUS_PENDING,
272
  'per_page' => 1000,
 
273
  )
274
  );
275
 
@@ -311,16 +386,15 @@ abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated {
311
  * Check if there are any pending scheduled actions due to run.
312
  *
313
  * @param ActionScheduler_Action $action
314
- * @param DateTime $scheduled_date (optional)
315
  * @return string
316
  */
317
  public function has_pending_actions_due() {
318
- $pending_actions = $this->query_actions(
319
- array(
320
- 'date' => as_get_datetime_object(),
321
- 'status' => self::STATUS_PENDING,
322
- )
323
- );
324
 
325
  return ! empty( $pending_actions );
326
  }
@@ -340,7 +414,7 @@ abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated {
340
  */
341
  public static function instance() {
342
  if ( empty( self::$store ) ) {
343
- $class = apply_filters( 'action_scheduler_store_class', self::DEFAULT_CLASS );
344
  self::$store = new $class();
345
  }
346
  return self::$store;
2
 
3
  /**
4
  * Class ActionScheduler_Store
 
5
  * @codeCoverageIgnore
6
  */
7
  abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated {
13
  const DEFAULT_CLASS = 'ActionScheduler_wpPostStore';
14
 
15
  /** @var ActionScheduler_Store */
16
+ private static $store = NULL;
17
 
18
  /** @var int */
19
  protected static $max_args_length = 191;
20
 
21
  /**
22
  * @param ActionScheduler_Action $action
23
+ * @param DateTime $scheduled_date Optional Date of the first instance
24
+ * to store. Otherwise uses the first date of the action's
25
+ * schedule.
26
  *
27
  * @return int The action ID
28
  */
29
+ abstract public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = NULL );
30
 
31
  /**
32
  * @param string $action_id
36
  abstract public function fetch_action( $action_id );
37
 
38
  /**
39
+ * Find an action.
40
+ *
41
+ * Note: the query ordering changes based on the passed 'status' value.
42
+ *
43
+ * @param string $hook Action hook.
44
+ * @param array $params Parameters of the action to find.
45
+ *
46
+ * @return string|null ID of the next action matching the criteria or NULL if not found.
47
  */
48
+ public function find_action( $hook, $params = array() ) {
49
+ $params = wp_parse_args(
50
+ $params,
51
+ array(
52
+ 'args' => null,
53
+ 'status' => self::STATUS_PENDING,
54
+ 'group' => '',
55
+ )
56
+ );
57
+
58
+ // These params are fixed for this method.
59
+ $params['hook'] = $hook;
60
+ $params['orderby'] = 'date';
61
+ $params['per_page'] = 1;
62
+
63
+ if ( ! empty( $params['status'] ) ) {
64
+ if ( self::STATUS_PENDING === $params['status'] ) {
65
+ $params['order'] = 'ASC'; // Find the next action that matches.
66
+ } else {
67
+ $params['order'] = 'DESC'; // Find the most recent action that matches.
68
+ }
69
+ }
70
+
71
+ $results = $this->query_actions( $params );
72
+
73
+ return empty( $results ) ? null : $results[0];
74
+ }
75
 
76
  /**
77
+ * Query for action count or list of action IDs.
78
+ *
79
+ * @since x.x.x $query['status'] accepts array of statuses instead of a single status.
80
+ *
81
+ * @param array $query {
82
+ * Query filtering options.
83
+ *
84
+ * @type string $hook The name of the actions. Optional.
85
+ * @type string|array $status The status or statuses of the actions. Optional.
86
+ * @type array $args The args array of the actions. Optional.
87
+ * @type DateTime $date The scheduled date of the action. Used in UTC timezone. Optional.
88
+ * @type string $date_compare Operator for selecting by $date param. Accepted values are '!=', '>', '>=', '<', '<=', '='. Defaults to '<='.
89
+ * @type DateTime $modified The last modified date of the action. Used in UTC timezone. Optional.
90
+ * @type string $modified_compare Operator for comparing $modified param. Accepted values are '!=', '>', '>=', '<', '<=', '='. Defaults to '<='.
91
+ * @type string $group The group the action belongs to. Optional.
92
+ * @type bool|int $claimed TRUE to find claimed actions, FALSE to find unclaimed actions, an int to find a specific claim ID. Optional.
93
+ * @type int $per_page Number of results to return. Defaults to 5.
94
+ * @type int $offset The query pagination offset. Defaults to 0.
95
+ * @type int $orderby Accepted values are 'hook', 'group', 'modified', 'date' or 'none'. Defaults to 'date'.
96
+ * @type string $order Accepted values are 'ASC' or 'DESC'. Defaults to 'ASC'.
97
+ * }
98
  * @param string $query_type Whether to select or count the results. Default, select.
99
  *
100
+ * @return string|array|null The IDs of actions matching the query. Null on failure.
101
  */
102
  abstract public function query_actions( $query = array(), $query_type = 'select' );
103
 
104
+ /**
105
+ * Run query to get a single action ID.
106
+ *
107
+ * @since x.x.x
108
+ *
109
+ * @see ActionScheduler_Store::query_actions for $query arg usage but 'per_page' and 'offset' can't be used.
110
+ *
111
+ * @param array $query Query parameters.
112
+ *
113
+ * @return int|null
114
+ */
115
+ public function query_action( $query ) {
116
+ $query['per_page'] = 1;
117
+ $query['offset'] = 0;
118
+ $results = $this->query_actions( $query );
119
+
120
+ if ( empty( $results ) ) {
121
+ return null;
122
+ } else {
123
+ return (int) $results[0];
124
+ }
125
+ }
126
+
127
  /**
128
  * Get a count of all actions in the store, grouped by status
129
  *
213
  * @return string
214
  */
215
  protected function validate_sql_comparator( $comparison_operator ) {
216
+ if ( in_array( $comparison_operator, array('!=', '>', '>=', '<', '<=', '=') ) ) {
217
  return $comparison_operator;
218
  }
219
  return '=';
223
  * Get the time MySQL formated date/time string for an action's (next) scheduled date.
224
  *
225
  * @param ActionScheduler_Action $action
226
+ * @param DateTime $scheduled_date (optional)
227
  * @return string
228
  */
229
+ protected function get_scheduled_date_string( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
230
  $next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date;
231
  if ( ! $next ) {
232
  return '0000-00-00 00:00:00';
240
  * Get the time MySQL formated date/time string for an action's (next) scheduled date.
241
  *
242
  * @param ActionScheduler_Action $action
243
+ * @param DateTime $scheduled_date (optional)
244
  * @return string
245
  */
246
+ protected function get_scheduled_date_string_local( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
247
  $next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date;
248
  if ( ! $next ) {
249
  return '0000-00-00 00:00:00';
319
  'hook' => $hook,
320
  'status' => self::STATUS_PENDING,
321
  'per_page' => 1000,
322
+ 'orderby' => 'action_id',
323
  )
324
  );
325
 
344
  'group' => $group,
345
  'status' => self::STATUS_PENDING,
346
  'per_page' => 1000,
347
+ 'orderby' => 'action_id',
348
  )
349
  );
350
 
386
  * Check if there are any pending scheduled actions due to run.
387
  *
388
  * @param ActionScheduler_Action $action
389
+ * @param DateTime $scheduled_date (optional)
390
  * @return string
391
  */
392
  public function has_pending_actions_due() {
393
+ $pending_actions = $this->query_actions( array(
394
+ 'date' => as_get_datetime_object(),
395
+ 'status' => ActionScheduler_Store::STATUS_PENDING,
396
+ 'orderby' => 'none',
397
+ ) );
 
398
 
399
  return ! empty( $pending_actions );
400
  }
414
  */
415
  public static function instance() {
416
  if ( empty( self::$store ) ) {
417
+ $class = apply_filters( 'action_scheduler_store_class', self::DEFAULT_CLASS );
418
  self::$store = new $class();
419
  }
420
  return self::$store;
lite/includes/libraries/action-scheduler/classes/abstracts/ActionScheduler_TimezoneHelper.php CHANGED
@@ -4,7 +4,7 @@
4
  * Class ActionScheduler_TimezoneHelper
5
  */
6
  abstract class ActionScheduler_TimezoneHelper {
7
- private static $local_timezone = null;
8
 
9
  /**
10
  * Set a DateTime's timezone to the WordPress site's timezone, or a UTC offset
@@ -99,21 +99,21 @@ abstract class ActionScheduler_TimezoneHelper {
99
  /**
100
  * @deprecated 2.1.0
101
  */
102
- public static function get_local_timezone( $reset = false ) {
103
  _deprecated_function( __FUNCTION__, '2.1.0', 'ActionScheduler_TimezoneHelper::set_local_timezone()' );
104
  if ( $reset ) {
105
- self::$local_timezone = null;
106
  }
107
- if ( ! isset( self::$local_timezone ) ) {
108
- $tzstring = get_option( 'timezone_string' );
109
 
110
- if ( empty( $tzstring ) ) {
111
- $gmt_offset = get_option( 'gmt_offset' );
112
  if ( $gmt_offset == 0 ) {
113
  $tzstring = 'UTC';
114
  } else {
115
  $gmt_offset *= HOUR_IN_SECONDS;
116
- $tzstring = timezone_name_from_abbr( '', $gmt_offset, 1 );
117
 
118
  // If there's no timezone string, try again with no DST.
119
  if ( false === $tzstring ) {
@@ -145,7 +145,7 @@ abstract class ActionScheduler_TimezoneHelper {
145
  }
146
  }
147
 
148
- self::$local_timezone = new DateTimeZone( $tzstring );
149
  }
150
  return self::$local_timezone;
151
  }
4
  * Class ActionScheduler_TimezoneHelper
5
  */
6
  abstract class ActionScheduler_TimezoneHelper {
7
+ private static $local_timezone = NULL;
8
 
9
  /**
10
  * Set a DateTime's timezone to the WordPress site's timezone, or a UTC offset
99
  /**
100
  * @deprecated 2.1.0
101
  */
102
+ public static function get_local_timezone( $reset = FALSE ) {
103
  _deprecated_function( __FUNCTION__, '2.1.0', 'ActionScheduler_TimezoneHelper::set_local_timezone()' );
104
  if ( $reset ) {
105
+ self::$local_timezone = NULL;
106
  }
107
+ if ( !isset(self::$local_timezone) ) {
108
+ $tzstring = get_option('timezone_string');
109
 
110
+ if ( empty($tzstring) ) {
111
+ $gmt_offset = get_option('gmt_offset');
112
  if ( $gmt_offset == 0 ) {
113
  $tzstring = 'UTC';
114
  } else {
115
  $gmt_offset *= HOUR_IN_SECONDS;
116
+ $tzstring = timezone_name_from_abbr( '', $gmt_offset, 1 );
117
 
118
  // If there's no timezone string, try again with no DST.
119
  if ( false === $tzstring ) {
145
  }
146
  }
147
 
148
+ self::$local_timezone = new DateTimeZone($tzstring);
149
  }
150
  return self::$local_timezone;
151
  }
lite/includes/libraries/action-scheduler/classes/actions/ActionScheduler_Action.php CHANGED
@@ -7,19 +7,19 @@ class ActionScheduler_Action {
7
  protected $hook = '';
8
  protected $args = array();
9
  /** @var ActionScheduler_Schedule */
10
- protected $schedule = null;
11
- protected $group = '';
12
 
13
- public function __construct( $hook, array $args = array(), ActionScheduler_Schedule $schedule = null, $group = '' ) {
14
  $schedule = empty( $schedule ) ? new ActionScheduler_NullSchedule() : $schedule;
15
- $this->set_hook( $hook );
16
- $this->set_schedule( $schedule );
17
- $this->set_args( $args );
18
- $this->set_group( $group );
19
  }
20
 
21
  public function execute() {
22
- return do_action_ref_array( $this->get_hook(), $this->get_args() );
23
  }
24
 
25
  /**
@@ -70,6 +70,6 @@ class ActionScheduler_Action {
70
  * @return bool If the action has been finished
71
  */
72
  public function is_finished() {
73
- return false;
74
  }
75
  }
7
  protected $hook = '';
8
  protected $args = array();
9
  /** @var ActionScheduler_Schedule */
10
+ protected $schedule = NULL;
11
+ protected $group = '';
12
 
13
+ public function __construct( $hook, array $args = array(), ActionScheduler_Schedule $schedule = NULL, $group = '' ) {
14
  $schedule = empty( $schedule ) ? new ActionScheduler_NullSchedule() : $schedule;
15
+ $this->set_hook($hook);
16
+ $this->set_schedule($schedule);
17
+ $this->set_args($args);
18
+ $this->set_group($group);
19
  }
20
 
21
  public function execute() {
22
+ return do_action_ref_array( $this->get_hook(), array_values( $this->get_args() ) );
23
  }
24
 
25
  /**
70
  * @return bool If the action has been finished
71
  */
72
  public function is_finished() {
73
+ return FALSE;
74
  }
75
  }
lite/includes/libraries/action-scheduler/classes/actions/ActionScheduler_CanceledAction.php CHANGED
@@ -9,10 +9,10 @@
9
  class ActionScheduler_CanceledAction extends ActionScheduler_FinishedAction {
10
 
11
  /**
12
- * @param string $hook
13
- * @param array $args
14
  * @param ActionScheduler_Schedule $schedule
15
- * @param string $group
16
  */
17
  public function __construct( $hook, array $args = array(), ActionScheduler_Schedule $schedule = null, $group = '' ) {
18
  parent::__construct( $hook, $args, $schedule, $group );
9
  class ActionScheduler_CanceledAction extends ActionScheduler_FinishedAction {
10
 
11
  /**
12
+ * @param string $hook
13
+ * @param array $args
14
  * @param ActionScheduler_Schedule $schedule
15
+ * @param string $group
16
  */
17
  public function __construct( $hook, array $args = array(), ActionScheduler_Schedule $schedule = null, $group = '' ) {
18
  parent::__construct( $hook, $args, $schedule, $group );
lite/includes/libraries/action-scheduler/classes/actions/ActionScheduler_FinishedAction.php CHANGED
@@ -10,7 +10,7 @@ class ActionScheduler_FinishedAction extends ActionScheduler_Action {
10
  }
11
 
12
  public function is_finished() {
13
- return true;
14
  }
15
  }
16
-
10
  }
11
 
12
  public function is_finished() {
13
+ return TRUE;
14
  }
15
  }
16
+
lite/includes/libraries/action-scheduler/classes/actions/ActionScheduler_NullAction.php CHANGED
@@ -5,7 +5,7 @@
5
  */
6
  class ActionScheduler_NullAction extends ActionScheduler_Action {
7
 
8
- public function __construct( $hook = '', array $args = array(), ActionScheduler_Schedule $schedule = null ) {
9
  $this->set_schedule( new ActionScheduler_NullSchedule() );
10
  }
11
 
@@ -13,4 +13,4 @@ class ActionScheduler_NullAction extends ActionScheduler_Action {
13
  // don't execute
14
  }
15
  }
16
-
5
  */
6
  class ActionScheduler_NullAction extends ActionScheduler_Action {
7
 
8
+ public function __construct( $hook = '', array $args = array(), ActionScheduler_Schedule $schedule = NULL ) {
9
  $this->set_schedule( new ActionScheduler_NullSchedule() );
10
  }
11
 
13
  // don't execute
14
  }
15
  }
16
+
lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_DBLogger.php CHANGED
@@ -29,7 +29,7 @@ class ActionScheduler_DBLogger extends ActionScheduler_Logger {
29
  ActionScheduler_TimezoneHelper::set_local_timezone( $date );
30
  $date_local = $date->format( 'Y-m-d H:i:s' );
31
 
32
- /** @var \wpdb $wpdb */
33
  global $wpdb;
34
  $wpdb->insert(
35
  $wpdb->actionscheduler_logs,
@@ -53,7 +53,7 @@ class ActionScheduler_DBLogger extends ActionScheduler_Logger {
53
  * @return ActionScheduler_LogEntry
54
  */
55
  public function get_entry( $entry_id ) {
56
- /** @var \wpdb $wpdb */
57
  global $wpdb;
58
  $entry = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE log_id=%d", $entry_id ) );
59
 
@@ -72,7 +72,11 @@ class ActionScheduler_DBLogger extends ActionScheduler_Logger {
72
  return new ActionScheduler_NullLogEntry();
73
  }
74
 
75
- $date = as_get_datetime_object( $record->log_date_gmt );
 
 
 
 
76
 
77
  return new ActionScheduler_LogEntry( $record->action_id, $record->message, $date );
78
  }
@@ -85,7 +89,7 @@ class ActionScheduler_DBLogger extends ActionScheduler_Logger {
85
  * @return ActionScheduler_LogEntry[]
86
  */
87
  public function get_logs( $action_id ) {
88
- /** @var \wpdb $wpdb */
89
  global $wpdb;
90
 
91
  $records = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE action_id=%d", $action_id ) );
@@ -99,8 +103,8 @@ class ActionScheduler_DBLogger extends ActionScheduler_Logger {
99
  * @codeCoverageIgnore
100
  */
101
  public function init() {
102
-
103
  $table_maker = new ActionScheduler_LoggerSchema();
 
104
  $table_maker->register_tables();
105
 
106
  parent::init();
@@ -114,7 +118,7 @@ class ActionScheduler_DBLogger extends ActionScheduler_Logger {
114
  * @param int $action_id Action ID.
115
  */
116
  public function clear_deleted_action_logs( $action_id ) {
117
- /** @var \wpdb $wpdb */
118
  global $wpdb;
119
  $wpdb->delete( $wpdb->actionscheduler_logs, array( 'action_id' => $action_id ), array( '%d' ) );
120
  }
@@ -129,7 +133,7 @@ class ActionScheduler_DBLogger extends ActionScheduler_Logger {
129
  return;
130
  }
131
 
132
- /** @var \wpdb $wpdb */
133
  global $wpdb;
134
  $date = as_get_datetime_object();
135
  $date_gmt = $date->format( 'Y-m-d H:i:s' );
@@ -141,10 +145,10 @@ class ActionScheduler_DBLogger extends ActionScheduler_Logger {
141
  $value_rows = array();
142
 
143
  foreach ( $action_ids as $action_id ) {
144
- $value_rows[] = $wpdb->prepare( $format, $action_id );
145
  }
146
  $sql_query .= implode( ',', $value_rows );
147
 
148
- $wpdb->query( $sql_query );
149
  }
150
  }
29
  ActionScheduler_TimezoneHelper::set_local_timezone( $date );
30
  $date_local = $date->format( 'Y-m-d H:i:s' );
31
 
32
+ /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort
33
  global $wpdb;
34
  $wpdb->insert(
35
  $wpdb->actionscheduler_logs,
53
  * @return ActionScheduler_LogEntry
54
  */
55
  public function get_entry( $entry_id ) {
56
+ /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort
57
  global $wpdb;
58
  $entry = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE log_id=%d", $entry_id ) );
59
 
72
  return new ActionScheduler_NullLogEntry();
73
  }
74
 
75
+ if ( is_null( $record->log_date_gmt ) ) {
76
+ $date = as_get_datetime_object( ActionScheduler_StoreSchema::DEFAULT_DATE );
77
+ } else {
78
+ $date = as_get_datetime_object( $record->log_date_gmt );
79
+ }
80
 
81
  return new ActionScheduler_LogEntry( $record->action_id, $record->message, $date );
82
  }
89
  * @return ActionScheduler_LogEntry[]
90
  */
91
  public function get_logs( $action_id ) {
92
+ /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort
93
  global $wpdb;
94
 
95
  $records = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE action_id=%d", $action_id ) );
103
  * @codeCoverageIgnore
104
  */
105
  public function init() {
 
106
  $table_maker = new ActionScheduler_LoggerSchema();
107
+ $table_maker->init();
108
  $table_maker->register_tables();
109
 
110
  parent::init();
118
  * @param int $action_id Action ID.
119
  */
120
  public function clear_deleted_action_logs( $action_id ) {
121
+ /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort
122
  global $wpdb;
123
  $wpdb->delete( $wpdb->actionscheduler_logs, array( 'action_id' => $action_id ), array( '%d' ) );
124
  }
133
  return;
134
  }
135
 
136
+ /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort
137
  global $wpdb;
138
  $date = as_get_datetime_object();
139
  $date_gmt = $date->format( 'Y-m-d H:i:s' );
145
  $value_rows = array();
146
 
147
  foreach ( $action_ids as $action_id ) {
148
+ $value_rows[] = $wpdb->prepare( $format, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
149
  }
150
  $sql_query .= implode( ',', $value_rows );
151
 
152
+ $wpdb->query( $sql_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
153
  }
154
  }
lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_DBStore.php CHANGED
@@ -9,6 +9,16 @@
9
  */
10
  class ActionScheduler_DBStore extends ActionScheduler_Store {
11
 
 
 
 
 
 
 
 
 
 
 
12
  /** @var int */
13
  protected static $max_args_length = 8000;
14
 
@@ -22,6 +32,7 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
22
  */
23
  public function init() {
24
  $table_maker = new ActionScheduler_StoreSchema();
 
25
  $table_maker->register_tables();
26
  }
27
 
@@ -29,9 +40,10 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
29
  * Save an action.
30
  *
31
  * @param ActionScheduler_Action $action Action object.
32
- * @param DateTime $date Optional schedule date. Default null.
33
  *
34
  * @return int Action ID.
 
35
  */
36
  public function save_action( ActionScheduler_Action $action, \DateTime $date = null ) {
37
  try {
@@ -45,7 +57,7 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
45
  'status' => ( $action->is_finished() ? self::STATUS_COMPLETE : self::STATUS_PENDING ),
46
  'scheduled_date_gmt' => $this->get_scheduled_date_string( $action, $date ),
47
  'scheduled_date_local' => $this->get_scheduled_date_string_local( $action, $date ),
48
- 'schedule' => serialize( $action->get_schedule() ),
49
  'group_id' => $this->get_group_id( $action->get_group() ),
50
  );
51
  $args = wp_json_encode( $action->get_args() );
@@ -61,9 +73,9 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
61
  $action_id = $wpdb->insert_id;
62
 
63
  if ( is_wp_error( $action_id ) ) {
64
- throw new RuntimeException( $action_id->get_error_message() );
65
  } elseif ( empty( $action_id ) ) {
66
- throw new RuntimeException( $wpdb->last_error ? $wpdb->last_error : __( 'Database error.', 'action-scheduler' ) );
67
  }
68
 
69
  do_action( 'action_scheduler_stored_action', $action_id );
@@ -161,6 +173,19 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
161
  unset( $data->extended_args );
162
  }
163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  try {
165
  $action = $this->make_action_from_db_record( $data );
166
  } catch ( ActionScheduler_InvalidActionException $exception ) {
@@ -191,7 +216,7 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
191
 
192
  $hook = $data->hook;
193
  $args = json_decode( $data->args, true );
194
- $schedule = unserialize( $data->schedule );
195
 
196
  $this->validate_args( $args, $data->action_id );
197
  $this->validate_schedule( $schedule, $data->action_id );
@@ -204,71 +229,20 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
204
  return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group );
205
  }
206
 
207
- /**
208
- * Find an action.
209
- *
210
- * @param string $hook Action hook.
211
- * @param array $params Parameters of the action to find.
212
- *
213
- * @return string|null ID of the next action matching the criteria or NULL if not found.
214
- */
215
- public function find_action( $hook, $params = array() ) {
216
- $params = wp_parse_args(
217
- $params,
218
- array(
219
- 'args' => null,
220
- 'status' => self::STATUS_PENDING,
221
- 'group' => '',
222
- )
223
- );
224
-
225
- /** @var wpdb $wpdb */
226
- global $wpdb;
227
- $query = "SELECT a.action_id FROM {$wpdb->actionscheduler_actions} a";
228
- $args = array();
229
- if ( ! empty( $params['group'] ) ) {
230
- $query .= " INNER JOIN {$wpdb->actionscheduler_groups} g ON g.group_id=a.group_id AND g.slug=%s";
231
- $args[] = $params['group'];
232
- }
233
- $query .= ' WHERE a.hook=%s';
234
- $args[] = $hook;
235
- if ( ! is_null( $params['args'] ) ) {
236
- $query .= ' AND a.args=%s';
237
- $args[] = $this->get_args_for_query( $params['args'] );
238
- }
239
-
240
- $order = 'ASC';
241
- if ( ! empty( $params['status'] ) ) {
242
- $query .= ' AND a.status=%s';
243
- $args[] = $params['status'];
244
-
245
- if ( self::STATUS_PENDING == $params['status'] ) {
246
- $order = 'ASC'; // Find the next action that matches.
247
- } else {
248
- $order = 'DESC'; // Find the most recent action that matches.
249
- }
250
- }
251
-
252
- $query .= " ORDER BY scheduled_date_gmt $order LIMIT 1";
253
-
254
- $query = $wpdb->prepare( $query, $args );
255
-
256
- $id = $wpdb->get_var( $query );
257
-
258
- return $id;
259
- }
260
-
261
  /**
262
  * Returns the SQL statement to query (or count) actions.
263
  *
 
 
264
  * @param array $query Filtering options.
265
  * @param string $select_or_count Whether the SQL should select and return the IDs or just the row count.
266
  *
267
  * @return string SQL statement already properly escaped.
 
268
  */
269
  protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) {
270
 
271
- if ( ! in_array( $select_or_count, array( 'select', 'count' ) ) ) {
272
  throw new InvalidArgumentException( __( 'Invalid value for select or count parameter. Cannot query actions.', 'action-scheduler' ) );
273
  }
274
 
@@ -318,8 +292,10 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
318
  }
319
 
320
  if ( $query['status'] ) {
321
- $sql .= ' AND a.status=%s';
322
- $sql_params[] = $query['status'];
 
 
323
  }
324
 
325
  if ( $query['date'] instanceof \DateTime ) {
@@ -340,9 +316,9 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
340
  $sql_params[] = $date_string;
341
  }
342
 
343
- if ( $query['claimed'] === true ) {
344
  $sql .= ' AND a.claim_id != 0';
345
- } elseif ( $query['claimed'] === false ) {
346
  $sql .= ' AND a.claim_id = 0';
347
  } elseif ( ! is_null( $query['claimed'] ) ) {
348
  $sql .= ' AND a.claim_id = %d';
@@ -365,27 +341,32 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
365
  }
366
 
367
  if ( 'select' === $select_or_count ) {
 
 
 
 
 
368
  switch ( $query['orderby'] ) {
369
  case 'hook':
370
- $orderby = 'a.hook';
371
  break;
372
  case 'group':
373
- $orderby = 'g.slug';
374
  break;
375
  case 'modified':
376
- $orderby = 'a.last_attempt_gmt';
 
 
 
 
 
377
  break;
378
  case 'date':
379
  default:
380
- $orderby = 'a.scheduled_date_gmt';
381
  break;
382
  }
383
- if ( strtoupper( $query['order'] ) == 'ASC' ) {
384
- $order = 'ASC';
385
- } else {
386
- $order = 'DESC';
387
- }
388
- $sql .= " ORDER BY $orderby $order";
389
  if ( $query['per_page'] > 0 ) {
390
  $sql .= ' LIMIT %d, %d';
391
  $sql_params[] = $query['offset'];
@@ -394,19 +375,23 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
394
  }
395
 
396
  if ( ! empty( $sql_params ) ) {
397
- $sql = $wpdb->prepare( $sql, $sql_params );
398
  }
399
 
400
  return $sql;
401
  }
402
 
403
  /**
404
- * Query for action count of list of action IDs.
405
  *
406
- * @param array $query Query parameters.
407
- * @param string $query_type Whether to select or count the results. Default, select.
408
  *
409
- * @return null|string|array The IDs of actions matching the query
 
 
 
 
 
410
  */
411
  public function query_actions( $query = array(), $query_type = 'select' ) {
412
  /** @var wpdb $wpdb */
@@ -414,7 +399,7 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
414
 
415
  $sql = $this->get_query_actions_sql( $query, $query_type );
416
 
417
- return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql );
418
  }
419
 
420
  /**
@@ -432,8 +417,8 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
432
  $actions_count_by_status = array();
433
  $action_stati_and_labels = $this->get_status_labels();
434
 
435
- foreach ( $wpdb->get_results( $sql ) as $action_data ) {
436
- // Ignore any actions with invalid status
437
  if ( array_key_exists( $action_data->status, $action_stati_and_labels ) ) {
438
  $actions_count_by_status[ $action_data->status ] = $action_data->count;
439
  }
@@ -448,6 +433,7 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
448
  * @param int $action_id Action ID.
449
  *
450
  * @return void
 
451
  */
452
  public function cancel_action( $action_id ) {
453
  /** @var \wpdb $wpdb */
@@ -507,7 +493,7 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
507
  }
508
 
509
  // Don't cancel actions that are already canceled.
510
- if ( isset( $query_args['status'] ) && $query_args['status'] == self::STATUS_CANCELED ) {
511
  return;
512
  }
513
 
@@ -517,6 +503,7 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
517
  array(
518
  'per_page' => 1000,
519
  'status' => self::STATUS_PENDING,
 
520
  )
521
  );
522
 
@@ -532,8 +519,8 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
532
  array_unshift( $parameters, self::STATUS_CANCELED );
533
 
534
  $wpdb->query(
535
- $wpdb->prepare( // wpcs: PreparedSQLPlaceholders replacement count ok.
536
- "UPDATE {$wpdb->actionscheduler_actions} SET status = %s WHERE action_id IN {$query_in}",
537
  $parameters
538
  )
539
  );
@@ -546,13 +533,14 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
546
  * Delete an action.
547
  *
548
  * @param int $action_id Action ID.
 
549
  */
550
  public function delete_action( $action_id ) {
551
  /** @var \wpdb $wpdb */
552
  global $wpdb;
553
  $deleted = $wpdb->delete( $wpdb->actionscheduler_actions, array( 'action_id' => $action_id ), array( '%d' ) );
554
  if ( empty( $deleted ) ) {
555
- throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
556
  }
557
  do_action( 'action_scheduler_deleted_action', $action_id );
558
  }
@@ -562,7 +550,6 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
562
  *
563
  * @param string $action_id Action ID.
564
  *
565
- * @throws \InvalidArgumentException
566
  * @return \DateTime The local date the action is scheduled to run, or the date that it ran.
567
  */
568
  public function get_date( $action_id ) {
@@ -576,7 +563,7 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
576
  *
577
  * @param int $action_id Action ID.
578
  *
579
- * @throws \InvalidArgumentException
580
  * @return \DateTime The GMT date the action is scheduled to run, or the date that it ran.
581
  */
582
  protected function get_date_gmt( $action_id ) {
@@ -584,9 +571,9 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
584
  global $wpdb;
585
  $record = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d", $action_id ) );
586
  if ( empty( $record ) ) {
587
- throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
588
  }
589
- if ( $record->status == self::STATUS_PENDING ) {
590
  return as_get_datetime_object( $record->scheduled_date_gmt );
591
  } else {
592
  return as_get_datetime_object( $record->last_attempt_gmt );
@@ -598,13 +585,18 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
598
  *
599
  * @param int $max_actions Maximum number of action to include in claim.
600
  * @param \DateTime $before_date Jobs must be schedule before this date. Defaults to now.
 
 
601
  *
602
  * @return ActionScheduler_ActionClaim
603
  */
604
  public function stake_claim( $max_actions = 10, \DateTime $before_date = null, $hooks = array(), $group = '' ) {
605
  $claim_id = $this->generate_claim_id();
 
 
606
  $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group );
607
- $action_ids = $this->find_actions_by_claim_id( $claim_id );
 
608
 
609
  return new ActionScheduler_ActionClaim( $claim_id, $action_ids );
610
  }
@@ -629,9 +621,12 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
629
  * @param string $claim_id Claim Id.
630
  * @param int $limit Number of action to include in claim.
631
  * @param \DateTime $before_date Should use UTC timezone.
 
 
632
  *
633
  * @return int The number of actions that were claimed.
634
- * @throws \RuntimeException
 
635
  */
636
  protected function claim_actions( $claim_id, $limit, \DateTime $before_date = null, $hooks = array(), $group = '' ) {
637
  /** @var \wpdb $wpdb */
@@ -640,7 +635,7 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
640
  $now = as_get_datetime_object();
641
  $date = is_null( $before_date ) ? $now : clone $before_date;
642
 
643
- // can't use $wpdb->update() because of the <= condition
644
  $update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s";
645
  $params = array(
646
  $claim_id,
@@ -662,7 +657,7 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
662
 
663
  $group_id = $this->get_group_id( $group, false );
664
 
665
- // throw exception if no matching group found, this matches ActionScheduler_wpPostStore's behaviour
666
  if ( empty( $group_id ) ) {
667
  /* translators: %s: group name */
668
  throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) );
@@ -672,13 +667,19 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
672
  $params[] = $group_id;
673
  }
674
 
675
- $order = 'ORDER BY attempts ASC, scheduled_date_gmt ASC, action_id ASC LIMIT %d';
 
 
 
 
 
 
 
676
  $params[] = $limit;
677
 
678
- $sql = $wpdb->prepare( "{$update} {$where} {$order}", $params );
679
-
680
- $rows_affected = $wpdb->query( $sql );
681
- if ( $rows_affected === false ) {
682
  throw new \RuntimeException( __( 'Unable to claim actions. Database error.', 'action-scheduler' ) );
683
  }
684
 
@@ -694,9 +695,9 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
694
  global $wpdb;
695
 
696
  $sql = "SELECT COUNT(DISTINCT claim_id) FROM {$wpdb->actionscheduler_actions} WHERE claim_id != 0 AND status IN ( %s, %s)";
697
- $sql = $wpdb->prepare( $sql, array( self::STATUS_PENDING, self::STATUS_RUNNING ) );
698
 
699
- return (int) $wpdb->get_var( $sql );
700
  }
701
 
702
  /**
@@ -710,28 +711,39 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
710
  global $wpdb;
711
 
712
  $sql = "SELECT claim_id FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d";
713
- $sql = $wpdb->prepare( $sql, $action_id );
714
 
715
- return (int) $wpdb->get_var( $sql );
716
  }
717
 
718
  /**
719
  * Retrieve the action IDs of action in a claim.
720
  *
721
- * @param string $claim_id Claim ID.
722
- *
723
  * @return int[]
724
  */
725
  public function find_actions_by_claim_id( $claim_id ) {
726
  /** @var \wpdb $wpdb */
727
  global $wpdb;
728
 
729
- $sql = "SELECT action_id FROM {$wpdb->actionscheduler_actions} WHERE claim_id=%d";
730
- $sql = $wpdb->prepare( $sql, $claim_id );
 
731
 
732
- $action_ids = $wpdb->get_col( $sql );
 
 
 
 
 
 
 
 
 
 
 
733
 
734
- return array_map( 'intval', $action_ids );
735
  }
736
 
737
  /**
@@ -769,6 +781,7 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
769
  * Mark an action as failed.
770
  *
771
  * @param int $action_id Action ID.
 
772
  */
773
  public function mark_failure( $action_id ) {
774
  /** @var \wpdb $wpdb */
@@ -781,7 +794,7 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
781
  array( '%d' )
782
  );
783
  if ( empty( $updated ) ) {
784
- throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
785
  }
786
  }
787
 
@@ -797,8 +810,8 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
797
  global $wpdb;
798
 
799
  $sql = "UPDATE {$wpdb->actionscheduler_actions} SET attempts = attempts+1, status=%s, last_attempt_gmt = %s, last_attempt_local = %s WHERE action_id = %d";
800
- $sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id );
801
- $wpdb->query( $sql );
802
  }
803
 
804
  /**
@@ -807,6 +820,7 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
807
  * @param int $action_id Action ID.
808
  *
809
  * @return void
 
810
  */
811
  public function mark_complete( $action_id ) {
812
  /** @var \wpdb $wpdb */
@@ -823,7 +837,7 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
823
  array( '%d' )
824
  );
825
  if ( empty( $updated ) ) {
826
- throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
827
  }
828
  }
829
 
@@ -833,15 +847,17 @@ class ActionScheduler_DBStore extends ActionScheduler_Store {
833
  * @param int $action_id Action ID.
834
  *
835
  * @return string
 
 
836
  */
837
  public function get_status( $action_id ) {
838
  /** @var \wpdb $wpdb */
839
  global $wpdb;
840
  $sql = "SELECT status FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d";
841
- $sql = $wpdb->prepare( $sql, $action_id );
842
- $status = $wpdb->get_var( $sql );
843
 
844
- if ( $status === null ) {
845
  throw new \InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
846
  } elseif ( empty( $status ) ) {
847
  throw new \RuntimeException( __( 'Unknown status found for action.', 'action-scheduler' ) );
9
  */
10
  class ActionScheduler_DBStore extends ActionScheduler_Store {
11
 
12
+ /**
13
+ * Used to share information about the before_date property of claims internally.
14
+ *
15
+ * This is used in preference to passing the same information as a method param
16
+ * for backwards-compatibility reasons.
17
+ *
18
+ * @var DateTime|null
19
+ */
20
+ private $claim_before_date = null;
21
+
22
  /** @var int */
23
  protected static $max_args_length = 8000;
24
 
32
  */
33
  public function init() {
34
  $table_maker = new ActionScheduler_StoreSchema();
35
+ $table_maker->init();
36
  $table_maker->register_tables();
37
  }
38
 
40
  * Save an action.
41
  *
42
  * @param ActionScheduler_Action $action Action object.
43
+ * @param DateTime $date Optional schedule date. Default null.
44
  *
45
  * @return int Action ID.
46
+ * @throws RuntimeException Throws exception when saving the action fails.
47
  */
48
  public function save_action( ActionScheduler_Action $action, \DateTime $date = null ) {
49
  try {
57
  'status' => ( $action->is_finished() ? self::STATUS_COMPLETE : self::STATUS_PENDING ),
58
  'scheduled_date_gmt' => $this->get_scheduled_date_string( $action, $date ),
59
  'scheduled_date_local' => $this->get_scheduled_date_string_local( $action, $date ),
60
+ 'schedule' => serialize( $action->get_schedule() ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
61
  'group_id' => $this->get_group_id( $action->get_group() ),
62
  );
63
  $args = wp_json_encode( $action->get_args() );
73
  $action_id = $wpdb->insert_id;
74
 
75
  if ( is_wp_error( $action_id ) ) {
76
+ throw new \RuntimeException( $action_id->get_error_message() );
77
  } elseif ( empty( $action_id ) ) {
78
+ throw new \RuntimeException( $wpdb->last_error ? $wpdb->last_error : __( 'Database error.', 'action-scheduler' ) );
79
  }
80
 
81
  do_action( 'action_scheduler_stored_action', $action_id );
173
  unset( $data->extended_args );
174
  }
175
 
176
+ // Convert NULL dates to zero dates.
177
+ $date_fields = array(
178
+ 'scheduled_date_gmt',
179
+ 'scheduled_date_local',
180
+ 'last_attempt_gmt',
181
+ 'last_attempt_gmt',
182
+ );
183
+ foreach ( $date_fields as $date_field ) {
184
+ if ( is_null( $data->$date_field ) ) {
185
+ $data->$date_field = ActionScheduler_StoreSchema::DEFAULT_DATE;
186
+ }
187
+ }
188
+
189
  try {
190
  $action = $this->make_action_from_db_record( $data );
191
  } catch ( ActionScheduler_InvalidActionException $exception ) {
216
 
217
  $hook = $data->hook;
218
  $args = json_decode( $data->args, true );
219
+ $schedule = unserialize( $data->schedule ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
220
 
221
  $this->validate_args( $args, $data->action_id );
222
  $this->validate_schedule( $schedule, $data->action_id );
229
  return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group );
230
  }
231
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  /**
233
  * Returns the SQL statement to query (or count) actions.
234
  *
235
+ * @since x.x.x $query['status'] accepts array of statuses instead of a single status.
236
+ *
237
  * @param array $query Filtering options.
238
  * @param string $select_or_count Whether the SQL should select and return the IDs or just the row count.
239
  *
240
  * @return string SQL statement already properly escaped.
241
+ * @throws InvalidArgumentException If the query is invalid.
242
  */
243
  protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) {
244
 
245
+ if ( ! in_array( $select_or_count, array( 'select', 'count' ), true ) ) {
246
  throw new InvalidArgumentException( __( 'Invalid value for select or count parameter. Cannot query actions.', 'action-scheduler' ) );
247
  }
248
 
292
  }
293
 
294
  if ( $query['status'] ) {
295
+ $statuses = (array) $query['status'];
296
+ $placeholders = array_fill( 0, count( $statuses ), '%s' );
297
+ $sql .= ' AND a.status IN (' . join( ', ', $placeholders ) . ')';
298
+ $sql_params = array_merge( $sql_params, array_values( $statuses ) );
299
  }
300
 
301
  if ( $query['date'] instanceof \DateTime ) {
316
  $sql_params[] = $date_string;
317
  }
318
 
319
+ if ( true === $query['claimed'] ) {
320
  $sql .= ' AND a.claim_id != 0';
321
+ } elseif ( false === $query['claimed'] ) {
322
  $sql .= ' AND a.claim_id = 0';
323
  } elseif ( ! is_null( $query['claimed'] ) ) {
324
  $sql .= ' AND a.claim_id = %d';
341
  }
342
 
343
  if ( 'select' === $select_or_count ) {
344
+ if ( 'ASC' === strtoupper( $query['order'] ) ) {
345
+ $order = 'ASC';
346
+ } else {
347
+ $order = 'DESC';
348
+ }
349
  switch ( $query['orderby'] ) {
350
  case 'hook':
351
+ $sql .= " ORDER BY a.hook $order";
352
  break;
353
  case 'group':
354
+ $sql .= " ORDER BY g.slug $order";
355
  break;
356
  case 'modified':
357
+ $sql .= " ORDER BY a.last_attempt_gmt $order";
358
+ break;
359
+ case 'none':
360
+ break;
361
+ case 'action_id':
362
+ $sql .= " ORDER BY a.action_id $order";
363
  break;
364
  case 'date':
365
  default:
366
+ $sql .= " ORDER BY a.scheduled_date_gmt $order";
367
  break;
368
  }
369
+
 
 
 
 
 
370
  if ( $query['per_page'] > 0 ) {
371
  $sql .= ' LIMIT %d, %d';
372
  $sql_params[] = $query['offset'];
375
  }
376
 
377
  if ( ! empty( $sql_params ) ) {
378
+ $sql = $wpdb->prepare( $sql, $sql_params ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
379
  }
380
 
381
  return $sql;
382
  }
383
 
384
  /**
385
+ * Query for action count or list of action IDs.
386
  *
387
+ * @since x.x.x $query['status'] accepts array of statuses instead of a single status.
 
388
  *
389
+ * @see ActionScheduler_Store::query_actions for $query arg usage.
390
+ *
391
+ * @param array $query Query filtering options.
392
+ * @param string $query_type Whether to select or count the results. Defaults to select.
393
+ *
394
+ * @return string|array|null The IDs of actions matching the query. Null on failure.
395
  */
396
  public function query_actions( $query = array(), $query_type = 'select' ) {
397
  /** @var wpdb $wpdb */
399
 
400
  $sql = $this->get_query_actions_sql( $query, $query_type );
401
 
402
+ return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoSql, WordPress.DB.DirectDatabaseQuery.NoCaching
403
  }
404
 
405
  /**
417
  $actions_count_by_status = array();
418
  $action_stati_and_labels = $this->get_status_labels();
419
 
420
+ foreach ( $wpdb->get_results( $sql ) as $action_data ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
421
+ // Ignore any actions with invalid status.
422
  if ( array_key_exists( $action_data->status, $action_stati_and_labels ) ) {
423
  $actions_count_by_status[ $action_data->status ] = $action_data->count;
424
  }
433
  * @param int $action_id Action ID.
434
  *
435
  * @return void
436
+ * @throws \InvalidArgumentException If the action update failed.
437
  */
438
  public function cancel_action( $action_id ) {
439
  /** @var \wpdb $wpdb */
493
  }
494
 
495
  // Don't cancel actions that are already canceled.
496
+ if ( isset( $query_args['status'] ) && self::STATUS_CANCELED === $query_args['status'] ) {
497
  return;
498
  }
499
 
503
  array(
504
  'per_page' => 1000,
505
  'status' => self::STATUS_PENDING,
506
+ 'orderby' => 'action_id',
507
  )
508
  );
509
 
519
  array_unshift( $parameters, self::STATUS_CANCELED );
520
 
521
  $wpdb->query(
522
+ $wpdb->prepare(
523
+ "UPDATE {$wpdb->actionscheduler_actions} SET status = %s WHERE action_id IN {$query_in}", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
524
  $parameters
525
  )
526
  );
533
  * Delete an action.
534
  *
535
  * @param int $action_id Action ID.
536
+ * @throws \InvalidArgumentException If the action deletion failed.
537
  */
538
  public function delete_action( $action_id ) {
539
  /** @var \wpdb $wpdb */
540
  global $wpdb;
541
  $deleted = $wpdb->delete( $wpdb->actionscheduler_actions, array( 'action_id' => $action_id ), array( '%d' ) );
542
  if ( empty( $deleted ) ) {
543
+ throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
544
  }
545
  do_action( 'action_scheduler_deleted_action', $action_id );
546
  }
550
  *
551
  * @param string $action_id Action ID.
552
  *
 
553
  * @return \DateTime The local date the action is scheduled to run, or the date that it ran.
554
  */
555
  public function get_date( $action_id ) {
563
  *
564
  * @param int $action_id Action ID.
565
  *
566
+ * @throws \InvalidArgumentException If action cannot be identified.
567
  * @return \DateTime The GMT date the action is scheduled to run, or the date that it ran.
568
  */
569
  protected function get_date_gmt( $action_id ) {
571
  global $wpdb;
572
  $record = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d", $action_id ) );
573
  if ( empty( $record ) ) {
574
+ throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
575
  }
576
+ if ( self::STATUS_PENDING === $record->status ) {
577
  return as_get_datetime_object( $record->scheduled_date_gmt );
578
  } else {
579
  return as_get_datetime_object( $record->last_attempt_gmt );
585
  *
586
  * @param int $max_actions Maximum number of action to include in claim.
587
  * @param \DateTime $before_date Jobs must be schedule before this date. Defaults to now.
588
+ * @param array $hooks Hooks to filter for.
589
+ * @param string $group Group to filter for.
590
  *
591
  * @return ActionScheduler_ActionClaim
592
  */
593
  public function stake_claim( $max_actions = 10, \DateTime $before_date = null, $hooks = array(), $group = '' ) {
594
  $claim_id = $this->generate_claim_id();
595
+
596
+ $this->claim_before_date = $before_date;
597
  $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group );
598
+ $action_ids = $this->find_actions_by_claim_id( $claim_id );
599
+ $this->claim_before_date = null;
600
 
601
  return new ActionScheduler_ActionClaim( $claim_id, $action_ids );
602
  }
621
  * @param string $claim_id Claim Id.
622
  * @param int $limit Number of action to include in claim.
623
  * @param \DateTime $before_date Should use UTC timezone.
624
+ * @param array $hooks Hooks to filter for.
625
+ * @param string $group Group to filter for.
626
  *
627
  * @return int The number of actions that were claimed.
628
+ * @throws \InvalidArgumentException Throws InvalidArgumentException if group doesn't exist.
629
+ * @throws \RuntimeException Throws RuntimeException if unable to claim action.
630
  */
631
  protected function claim_actions( $claim_id, $limit, \DateTime $before_date = null, $hooks = array(), $group = '' ) {
632
  /** @var \wpdb $wpdb */
635
  $now = as_get_datetime_object();
636
  $date = is_null( $before_date ) ? $now : clone $before_date;
637
 
638
+ // can't use $wpdb->update() because of the <= condition.
639
  $update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s";
640
  $params = array(
641
  $claim_id,
657
 
658
  $group_id = $this->get_group_id( $group, false );
659
 
660
+ // throw exception if no matching group found, this matches ActionScheduler_wpPostStore's behaviour.
661
  if ( empty( $group_id ) ) {
662
  /* translators: %s: group name */
663
  throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) );
667
  $params[] = $group_id;
668
  }
669
 
670
+ /**
671
+ * Sets the order-by clause used in the action claim query.
672
+ *
673
+ * @since x.x.x
674
+ *
675
+ * @param string $order_by_sql
676
+ */
677
+ $order = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY attempts ASC, scheduled_date_gmt ASC, action_id ASC' );
678
  $params[] = $limit;
679
 
680
+ $sql = $wpdb->prepare( "{$update} {$where} {$order} LIMIT %d", $params ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders
681
+ $rows_affected = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
682
+ if ( false === $rows_affected ) {
 
683
  throw new \RuntimeException( __( 'Unable to claim actions. Database error.', 'action-scheduler' ) );
684
  }
685
 
695
  global $wpdb;
696
 
697
  $sql = "SELECT COUNT(DISTINCT claim_id) FROM {$wpdb->actionscheduler_actions} WHERE claim_id != 0 AND status IN ( %s, %s)";
698
+ $sql = $wpdb->prepare( $sql, array( self::STATUS_PENDING, self::STATUS_RUNNING ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
699
 
700
+ return (int) $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
701
  }
702
 
703
  /**
711
  global $wpdb;
712
 
713
  $sql = "SELECT claim_id FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d";
714
+ $sql = $wpdb->prepare( $sql, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
715
 
716
+ return (int) $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
717
  }
718
 
719
  /**
720
  * Retrieve the action IDs of action in a claim.
721
  *
722
+ * @param int $claim_id Claim ID.
 
723
  * @return int[]
724
  */
725
  public function find_actions_by_claim_id( $claim_id ) {
726
  /** @var \wpdb $wpdb */
727
  global $wpdb;
728
 
729
+ $action_ids = array();
730
+ $before_date = isset( $this->claim_before_date ) ? $this->claim_before_date : as_get_datetime_object();
731
+ $cut_off = $before_date->format( 'Y-m-d H:i:s' );
732
 
733
+ $sql = $wpdb->prepare(
734
+ "SELECT action_id, scheduled_date_gmt FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d",
735
+ $claim_id
736
+ );
737
+
738
+ // Verify that the scheduled date for each action is within the expected bounds (in some unusual
739
+ // cases, we cannot depend on MySQL to honor all of the WHERE conditions we specify).
740
+ foreach ( $wpdb->get_results( $sql ) as $claimed_action ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
741
+ if ( $claimed_action->scheduled_date_gmt <= $cut_off ) {
742
+ $action_ids[] = absint( $claimed_action->action_id );
743
+ }
744
+ }
745
 
746
+ return $action_ids;
747
  }
748
 
749
  /**
781
  * Mark an action as failed.
782
  *
783
  * @param int $action_id Action ID.
784
+ * @throws \InvalidArgumentException Throw an exception if action was not updated.
785
  */
786
  public function mark_failure( $action_id ) {
787
  /** @var \wpdb $wpdb */
794
  array( '%d' )
795
  );
796
  if ( empty( $updated ) ) {
797
+ throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
798
  }
799
  }
800
 
810
  global $wpdb;
811
 
812
  $sql = "UPDATE {$wpdb->actionscheduler_actions} SET attempts = attempts+1, status=%s, last_attempt_gmt = %s, last_attempt_local = %s WHERE action_id = %d";
813
+ $sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
814
+ $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
815
  }
816
 
817
  /**
820
  * @param int $action_id Action ID.
821
  *
822
  * @return void
823
+ * @throws \InvalidArgumentException Throw an exception if action was not updated.
824
  */
825
  public function mark_complete( $action_id ) {
826
  /** @var \wpdb $wpdb */
837
  array( '%d' )
838
  );
839
  if ( empty( $updated ) ) {
840
+ throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
841
  }
842
  }
843
 
847
  * @param int $action_id Action ID.
848
  *
849
  * @return string
850
+ * @throws \InvalidArgumentException Throw an exception if not status was found for action_id.
851
+ * @throws \RuntimeException Throw an exception if action status could not be retrieved.
852
  */
853
  public function get_status( $action_id ) {
854
  /** @var \wpdb $wpdb */
855
  global $wpdb;
856
  $sql = "SELECT status FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d";
857
+ $sql = $wpdb->prepare( $sql, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
858
+ $status = $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
859
 
860
+ if ( null === $status ) {
861
  throw new \InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
862
  } elseif ( empty( $status ) ) {
863
  throw new \RuntimeException( __( 'Unknown status found for action.', 'action-scheduler' ) );
lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_HybridStore.php CHANGED
@@ -53,10 +53,10 @@ class ActionScheduler_HybridStore extends Store {
53
  * @codeCoverageIgnore
54
  */
55
  public function init() {
56
- add_action( 'action_scheduler/created_table', array( $this, 'set_autoincrement' ), 10, 2 );
57
  $this->primary_store->init();
58
  $this->secondary_store->init();
59
- remove_action( 'action_scheduler/created_table', array( $this, 'set_autoincrement' ), 10 );
60
  }
61
 
62
  /**
@@ -78,7 +78,7 @@ class ActionScheduler_HybridStore extends Store {
78
  /** @var \wpdb $wpdb */
79
  global $wpdb;
80
  /**
81
- * A default date of '0000-00-00 00:00:00' is invalid in MySQL 5.7 when configured with
82
  * sql_mode including both STRICT_TRANS_TABLES and NO_ZERO_DATE.
83
  */
84
  $default_date = new DateTime( 'tomorrow' );
@@ -88,7 +88,7 @@ class ActionScheduler_HybridStore extends Store {
88
 
89
  $row_count = $wpdb->insert(
90
  $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE},
91
- array(
92
  'action_id' => $this->demarkation_id,
93
  'hook' => '',
94
  'status' => '',
@@ -96,12 +96,12 @@ class ActionScheduler_HybridStore extends Store {
96
  'scheduled_date_local' => $date_local,
97
  'last_attempt_gmt' => $date_gmt,
98
  'last_attempt_local' => $date_local,
99
- )
100
  );
101
  if ( $row_count > 0 ) {
102
  $wpdb->delete(
103
  $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE},
104
- array( 'action_id' => $this->demarkation_id )
105
  );
106
  }
107
  }
@@ -140,10 +140,10 @@ class ActionScheduler_HybridStore extends Store {
140
  *
141
  * @return string
142
  */
143
- public function find_action( $hook, $params = array() ) {
144
  $found_unmigrated_action = $this->secondary_store->find_action( $hook, $params );
145
  if ( ! empty( $found_unmigrated_action ) ) {
146
- $this->migrate( array( $found_unmigrated_action ) );
147
  }
148
 
149
  return $this->primary_store->find_action( $hook, $params );
@@ -154,12 +154,12 @@ class ActionScheduler_HybridStore extends Store {
154
  * If any are found, migrate them immediately. Then the secondary
155
  * store will contain the canonical results.
156
  *
157
- * @param array $query
158
  * @param string $query_type Whether to select or count the results. Default, select.
159
  *
160
  * @return int[]
161
  */
162
- public function query_actions( $query = array(), $query_type = 'select' ) {
163
  $found_unmigrated_actions = $this->secondary_store->query_actions( $query, 'select' );
164
  if ( ! empty( $found_unmigrated_actions ) ) {
165
  $this->migrate( $found_unmigrated_actions );
@@ -352,19 +352,19 @@ class ActionScheduler_HybridStore extends Store {
352
  */
353
  protected function get_store_from_action_id( $action_id, $primary_first = false ) {
354
  if ( $primary_first ) {
355
- $stores = array(
356
  $this->primary_store,
357
  $this->secondary_store,
358
- );
359
  } elseif ( $action_id < $this->demarkation_id ) {
360
- $stores = array(
361
  $this->secondary_store,
362
  $this->primary_store,
363
- );
364
  } else {
365
- $stores = array(
366
  $this->primary_store,
367
- );
368
  }
369
 
370
  foreach ( $stores as $store ) {
@@ -376,8 +376,7 @@ class ActionScheduler_HybridStore extends Store {
376
  return null;
377
  }
378
 
379
- /*
380
- * * * * * * * * * * * * * * * * * * * * * * * * * *
381
  * All claim-related functions should operate solely
382
  * on the primary store.
383
  * * * * * * * * * * * * * * * * * * * * * * * * * * */
53
  * @codeCoverageIgnore
54
  */
55
  public function init() {
56
+ add_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10, 2 );
57
  $this->primary_store->init();
58
  $this->secondary_store->init();
59
+ remove_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10 );
60
  }
61
 
62
  /**
78
  /** @var \wpdb $wpdb */
79
  global $wpdb;
80
  /**
81
+ * A default date of '0000-00-00 00:00:00' is invalid in MySQL 5.7 when configured with
82
  * sql_mode including both STRICT_TRANS_TABLES and NO_ZERO_DATE.
83
  */
84
  $default_date = new DateTime( 'tomorrow' );
88
 
89
  $row_count = $wpdb->insert(
90
  $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE},
91
+ [
92
  'action_id' => $this->demarkation_id,
93
  'hook' => '',
94
  'status' => '',
96
  'scheduled_date_local' => $date_local,
97
  'last_attempt_gmt' => $date_gmt,
98
  'last_attempt_local' => $date_local,
99
+ ]
100
  );
101
  if ( $row_count > 0 ) {
102
  $wpdb->delete(
103
  $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE},
104
+ [ 'action_id' => $this->demarkation_id ]
105
  );
106
  }
107
  }
140
  *
141
  * @return string
142
  */
143
+ public function find_action( $hook, $params = [] ) {
144
  $found_unmigrated_action = $this->secondary_store->find_action( $hook, $params );
145
  if ( ! empty( $found_unmigrated_action ) ) {
146
+ $this->migrate( [ $found_unmigrated_action ] );
147
  }
148
 
149
  return $this->primary_store->find_action( $hook, $params );
154
  * If any are found, migrate them immediately. Then the secondary
155
  * store will contain the canonical results.
156
  *
157
+ * @param array $query
158
  * @param string $query_type Whether to select or count the results. Default, select.
159
  *
160
  * @return int[]
161
  */
162
+ public function query_actions( $query = [], $query_type = 'select' ) {
163
  $found_unmigrated_actions = $this->secondary_store->query_actions( $query, 'select' );
164
  if ( ! empty( $found_unmigrated_actions ) ) {
165
  $this->migrate( $found_unmigrated_actions );
352
  */
353
  protected function get_store_from_action_id( $action_id, $primary_first = false ) {
354
  if ( $primary_first ) {
355
+ $stores = [
356
  $this->primary_store,
357
  $this->secondary_store,
358
+ ];
359
  } elseif ( $action_id < $this->demarkation_id ) {
360
+ $stores = [
361
  $this->secondary_store,
362
  $this->primary_store,
363
+ ];
364
  } else {
365
+ $stores = [
366
  $this->primary_store,
367
+ ];
368
  }
369
 
370
  foreach ( $stores as $store ) {
376
  return null;
377
  }
378
 
379
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * *
 
380
  * All claim-related functions should operate solely
381
  * on the primary store.
382
  * * * * * * * * * * * * * * * * * * * * * * * * * * */
lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_wpCommentLogger.php CHANGED
@@ -5,17 +5,17 @@
5
  */
6
  class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
7
  const AGENT = 'ActionScheduler';
8
- const TYPE = 'action_log';
9
 
10
  /**
11
- * @param string $action_id
12
- * @param string $message
13
  * @param DateTime $date
14
  *
15
  * @return string The log entry ID
16
  */
17
- public function log( $action_id, $message, DateTime $date = null ) {
18
- if ( empty( $date ) ) {
19
  $date = as_get_datetime_object();
20
  } else {
21
  $date = as_get_datetime_object( clone $date );
@@ -26,18 +26,18 @@ class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
26
 
27
  protected function create_wp_comment( $action_id, $message, DateTime $date ) {
28
 
29
- $comment_date_gmt = $date->format( 'Y-m-d H:i:s' );
30
  ActionScheduler_TimezoneHelper::set_local_timezone( $date );
31
  $comment_data = array(
32
- 'comment_post_ID' => $action_id,
33
- 'comment_date' => $date->format( 'Y-m-d H:i:s' ),
34
  'comment_date_gmt' => $comment_date_gmt,
35
- 'comment_author' => self::AGENT,
36
- 'comment_content' => $message,
37
- 'comment_agent' => self::AGENT,
38
- 'comment_type' => self::TYPE,
39
  );
40
- return wp_insert_comment( $comment_data );
41
  }
42
 
43
  /**
@@ -47,7 +47,7 @@ class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
47
  */
48
  public function get_entry( $entry_id ) {
49
  $comment = $this->get_comment( $entry_id );
50
- if ( empty( $comment ) || $comment->comment_type != self::TYPE ) {
51
  return new ActionScheduler_NullLogEntry();
52
  }
53
 
@@ -63,22 +63,20 @@ class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
63
  */
64
  public function get_logs( $action_id ) {
65
  $status = 'all';
66
- if ( get_post_status( $action_id ) == 'trash' ) {
67
  $status = 'post-trashed';
68
  }
69
- $comments = get_comments(
70
- array(
71
- 'post_id' => $action_id,
72
- 'orderby' => 'comment_date_gmt',
73
- 'order' => 'ASC',
74
- 'type' => self::TYPE,
75
- 'status' => $status,
76
- )
77
- );
78
- $logs = array();
79
  foreach ( $comments as $c ) {
80
  $entry = $this->get_entry( $c );
81
- if ( ! empty( $entry ) ) {
82
  $logs[] = $entry;
83
  }
84
  }
@@ -95,23 +93,23 @@ class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
95
  * @param WP_Comment_Query $query
96
  */
97
  public function filter_comment_queries( $query ) {
98
- foreach ( array( 'ID', 'parent', 'post_author', 'post_name', 'post_parent', 'type', 'post_type', 'post_id', 'post_ID' ) as $key ) {
99
- if ( ! empty( $query->query_vars[ $key ] ) ) {
100
  return; // don't slow down queries that wouldn't include action_log comments anyway
101
  }
102
  }
103
- $query->query_vars['action_log_filter'] = true;
104
  add_filter( 'comments_clauses', array( $this, 'filter_comment_query_clauses' ), 10, 2 );
105
  }
106
 
107
  /**
108
- * @param array $clauses
109
  * @param WP_Comment_Query $query
110
  *
111
  * @return array
112
  */
113
  public function filter_comment_query_clauses( $clauses, $query ) {
114
- if ( ! empty( $query->query_vars['action_log_filter'] ) ) {
115
  $clauses['where'] .= $this->get_where_clause();
116
  }
117
  return $clauses;
@@ -121,7 +119,7 @@ class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
121
  * Make sure Action Scheduler logs are excluded from comment feeds, which use WP_Query, not
122
  * the WP_Comment_Query class handled by @see self::filter_comment_queries().
123
  *
124
- * @param string $where
125
  * @param WP_Query $query
126
  *
127
  * @return string
@@ -147,7 +145,7 @@ class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
147
  * Remove action log entries from wp_count_comments()
148
  *
149
  * @param array $stats
150
- * @param int $post_id
151
  *
152
  * @return object
153
  */
@@ -176,15 +174,9 @@ class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
176
 
177
  $count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} WHERE comment_type NOT IN('order_note','action_log') GROUP BY comment_approved", ARRAY_A );
178
 
179
- $total = 0;
180
- $stats = array();
181
- $approved = array(
182
- '0' => 'moderated',
183
- '1' => 'approved',
184
- 'spam' => 'spam',
185
- 'trash' => 'trash',
186
- 'post-trashed' => 'post-trashed',
187
- );
188
 
189
  foreach ( (array) $count as $row ) {
190
  // Don't count post-trashed toward totals
@@ -239,10 +231,10 @@ class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
239
  }
240
 
241
  public function disable_comment_counting() {
242
- wp_defer_comment_counting( true );
243
  }
244
  public function enable_comment_counting() {
245
- wp_defer_comment_counting( false );
246
  }
247
 
248
  }
5
  */
6
  class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
7
  const AGENT = 'ActionScheduler';
8
+ const TYPE = 'action_log';
9
 
10
  /**
11
+ * @param string $action_id
12
+ * @param string $message
13
  * @param DateTime $date
14
  *
15
  * @return string The log entry ID
16
  */
17
+ public function log( $action_id, $message, DateTime $date = NULL ) {
18
+ if ( empty($date) ) {
19
  $date = as_get_datetime_object();
20
  } else {
21
  $date = as_get_datetime_object( clone $date );
26
 
27
  protected function create_wp_comment( $action_id, $message, DateTime $date ) {
28
 
29
+ $comment_date_gmt = $date->format('Y-m-d H:i:s');
30
  ActionScheduler_TimezoneHelper::set_local_timezone( $date );
31
  $comment_data = array(
32
+ 'comment_post_ID' => $action_id,
33
+ 'comment_date' => $date->format('Y-m-d H:i:s'),
34
  'comment_date_gmt' => $comment_date_gmt,
35
+ 'comment_author' => self::AGENT,
36
+ 'comment_content' => $message,
37
+ 'comment_agent' => self::AGENT,
38
+ 'comment_type' => self::TYPE,
39
  );
40
+ return wp_insert_comment($comment_data);
41
  }
42
 
43
  /**
47
  */
48
  public function get_entry( $entry_id ) {
49
  $comment = $this->get_comment( $entry_id );
50
+ if ( empty($comment) || $comment->comment_type != self::TYPE ) {
51
  return new ActionScheduler_NullLogEntry();
52
  }
53
 
63
  */
64
  public function get_logs( $action_id ) {
65
  $status = 'all';
66
+ if ( get_post_status($action_id) == 'trash' ) {
67
  $status = 'post-trashed';
68
  }
69
+ $comments = get_comments(array(
70
+ 'post_id' => $action_id,
71
+ 'orderby' => 'comment_date_gmt',
72
+ 'order' => 'ASC',
73
+ 'type' => self::TYPE,
74
+ 'status' => $status,
75
+ ));
76
+ $logs = array();
 
 
77
  foreach ( $comments as $c ) {
78
  $entry = $this->get_entry( $c );
79
+ if ( !empty($entry) ) {
80
  $logs[] = $entry;
81
  }
82
  }
93
  * @param WP_Comment_Query $query
94
  */
95
  public function filter_comment_queries( $query ) {
96
+ foreach ( array('ID', 'parent', 'post_author', 'post_name', 'post_parent', 'type', 'post_type', 'post_id', 'post_ID') as $key ) {
97
+ if ( !empty($query->query_vars[$key]) ) {
98
  return; // don't slow down queries that wouldn't include action_log comments anyway
99
  }
100
  }
101
+ $query->query_vars['action_log_filter'] = TRUE;
102
  add_filter( 'comments_clauses', array( $this, 'filter_comment_query_clauses' ), 10, 2 );
103
  }
104
 
105
  /**
106
+ * @param array $clauses
107
  * @param WP_Comment_Query $query
108
  *
109
  * @return array
110
  */
111
  public function filter_comment_query_clauses( $clauses, $query ) {
112
+ if ( !empty($query->query_vars['action_log_filter']) ) {
113
  $clauses['where'] .= $this->get_where_clause();
114
  }
115
  return $clauses;
119
  * Make sure Action Scheduler logs are excluded from comment feeds, which use WP_Query, not
120
  * the WP_Comment_Query class handled by @see self::filter_comment_queries().
121
  *
122
+ * @param string $where
123
  * @param WP_Query $query
124
  *
125
  * @return string
145
  * Remove action log entries from wp_count_comments()
146
  *
147
  * @param array $stats
148
+ * @param int $post_id
149
  *
150
  * @return object
151
  */
174
 
175
  $count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} WHERE comment_type NOT IN('order_note','action_log') GROUP BY comment_approved", ARRAY_A );
176
 
177
+ $total = 0;
178
+ $stats = array();
179
+ $approved = array( '0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed' );
 
 
 
 
 
 
180
 
181
  foreach ( (array) $count as $row ) {
182
  // Don't count post-trashed toward totals
231
  }
232
 
233
  public function disable_comment_counting() {
234
+ wp_defer_comment_counting(true);
235
  }
236
  public function enable_comment_counting() {
237
+ wp_defer_comment_counting(false);
238
  }
239
 
240
  }
lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore.php CHANGED
@@ -9,9 +9,32 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
9
  const SCHEDULE_META_KEY = '_action_manager_schedule';
10
  const DEPENDENCIES_MET = 'as-post-store-dependencies-met';
11
 
12
- /** @var DateTimeZone */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  protected $local_timezone = null;
14
 
 
 
 
 
 
 
 
 
 
15
  public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = null ) {
16
  try {
17
  $this->validate_action( $action );
@@ -22,15 +45,24 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
22
  do_action( 'action_scheduler_stored_action', $post_id );
23
  return $post_id;
24
  } catch ( Exception $e ) {
 
25
  throw new RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 );
26
  }
27
  }
28
 
 
 
 
 
 
 
 
 
29
  protected function create_post_array( ActionScheduler_Action $action, DateTime $scheduled_date = null ) {
30
  $post = array(
31
  'post_type' => self::POST_TYPE,
32
  'post_title' => $action->get_hook(),
33
- 'post_content' => json_encode( $action->get_args() ),
34
  'post_status' => ( $action->is_finished() ? 'publish' : 'pending' ),
35
  'post_date_gmt' => $this->get_scheduled_date_string( $action, $scheduled_date ),
36
  'post_date' => $this->get_scheduled_date_string_local( $action, $scheduled_date ),
@@ -38,6 +70,13 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
38
  return $post;
39
  }
40
 
 
 
 
 
 
 
 
41
  protected function save_post_array( $post_array ) {
42
  add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
43
  add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
@@ -64,10 +103,17 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
64
  return $post_id;
65
  }
66
 
 
 
 
 
 
 
 
67
  public function filter_insert_post_data( $postdata ) {
68
- if ( $postdata['post_type'] == self::POST_TYPE ) {
69
  $postdata['post_author'] = 0;
70
- if ( $postdata['post_status'] == 'future' ) {
71
  $postdata['post_status'] = 'publish';
72
  }
73
  }
@@ -103,16 +149,31 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
103
  * @return string
104
  */
105
  public function set_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) {
106
- if ( self::POST_TYPE == $post_type ) {
107
  $override_slug = uniqid( self::POST_TYPE . '-', true ) . '-' . wp_generate_password( 32, false );
108
  }
109
  return $override_slug;
110
  }
111
 
 
 
 
 
 
 
 
 
112
  protected function save_post_schedule( $post_id, $schedule ) {
113
  update_post_meta( $post_id, self::SCHEDULE_META_KEY, $schedule );
114
  }
115
 
 
 
 
 
 
 
 
116
  protected function save_action_group( $post_id, $group ) {
117
  if ( empty( $group ) ) {
118
  wp_set_object_terms( $post_id, array(), self::GROUP_TAXONOMY, false );
@@ -121,9 +182,15 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
121
  }
122
  }
123
 
 
 
 
 
 
 
124
  public function fetch_action( $action_id ) {
125
  $post = $this->get_post( $action_id );
126
- if ( empty( $post ) || $post->post_type != self::POST_TYPE ) {
127
  return $this->get_null_action();
128
  }
129
 
@@ -137,6 +204,12 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
137
  return $action;
138
  }
139
 
 
 
 
 
 
 
140
  protected function get_post( $action_id ) {
141
  if ( empty( $action_id ) ) {
142
  return null;
@@ -144,10 +217,21 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
144
  return get_post( $action_id );
145
  }
146
 
 
 
 
 
 
147
  protected function get_null_action() {
148
  return new ActionScheduler_NullAction();
149
  }
150
 
 
 
 
 
 
 
151
  protected function make_action_from_post( $post ) {
152
  $hook = $post->post_title;
153
 
@@ -164,9 +248,11 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
164
  }
165
 
166
  /**
167
- * @param string $post_status
 
 
168
  *
169
- * @throws InvalidArgumentException if $post_status not in known status fields returned by $this->get_status_labels()
170
  * @return string
171
  */
172
  protected function get_action_status_by_post_status( $post_status ) {
@@ -190,8 +276,11 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
190
  }
191
 
192
  /**
193
- * @param string $action_status
194
- * @throws InvalidArgumentException if $post_status not in known status fields returned by $this->get_status_labels()
 
 
 
195
  * @return string
196
  */
197
  protected function get_post_status_by_action_status( $action_status ) {
@@ -214,75 +303,18 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
214
  return $post_status;
215
  }
216
 
217
- /**
218
- * @param string $hook
219
- * @param array $params
220
- *
221
- * @return string ID of the next action matching the criteria or NULL if not found
222
- */
223
- public function find_action( $hook, $params = array() ) {
224
- $params = wp_parse_args(
225
- $params,
226
- array(
227
- 'args' => null,
228
- 'status' => ActionScheduler_Store::STATUS_PENDING,
229
- 'group' => '',
230
- )
231
- );
232
- /** @var wpdb $wpdb */
233
- global $wpdb;
234
- $query = "SELECT p.ID FROM {$wpdb->posts} p";
235
- $args = array();
236
- if ( ! empty( $params['group'] ) ) {
237
- $query .= " INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
238
- $query .= " INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
239
- $query .= " INNER JOIN {$wpdb->terms} t ON tt.term_id=t.term_id AND t.slug=%s";
240
- $args[] = $params['group'];
241
- }
242
- $query .= ' WHERE p.post_title=%s';
243
- $args[] = $hook;
244
- $query .= ' AND p.post_type=%s';
245
- $args[] = self::POST_TYPE;
246
- if ( ! is_null( $params['args'] ) ) {
247
- $query .= ' AND p.post_content=%s';
248
- $args[] = json_encode( $params['args'] );
249
- }
250
-
251
- if ( ! empty( $params['status'] ) ) {
252
- $query .= ' AND p.post_status=%s';
253
- $args[] = $this->get_post_status_by_action_status( $params['status'] );
254
- }
255
-
256
- switch ( $params['status'] ) {
257
- case self::STATUS_COMPLETE:
258
- case self::STATUS_RUNNING:
259
- case self::STATUS_FAILED:
260
- $order = 'DESC'; // Find the most recent action that matches
261
- break;
262
- case self::STATUS_PENDING:
263
- default:
264
- $order = 'ASC'; // Find the next action that matches
265
- break;
266
- }
267
- $query .= " ORDER BY post_date_gmt $order LIMIT 1";
268
-
269
- $query = $wpdb->prepare( $query, $args );
270
-
271
- $id = $wpdb->get_var( $query );
272
- return $id;
273
- }
274
-
275
  /**
276
  * Returns the SQL statement to query (or count) actions.
277
  *
278
- * @param array $query Filtering options
279
- * @param string $select_or_count Whether the SQL should select and return the IDs or just the row count
280
- * @throws InvalidArgumentException if $select_or_count not count or select
 
281
  * @return string SQL statement. The returned SQL is already properly escaped.
282
  */
283
  protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) {
284
 
285
- if ( ! in_array( $select_or_count, array( 'select', 'count' ) ) ) {
286
  throw new InvalidArgumentException( __( 'Invalid schedule. Cannot save action.', 'action-scheduler' ) );
287
  }
288
 
@@ -306,7 +338,11 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
306
  )
307
  );
308
 
309
- /** @var wpdb $wpdb */
 
 
 
 
310
  global $wpdb;
311
  $sql = ( 'count' === $select_or_count ) ? 'SELECT count(p.ID)' : 'SELECT p.ID ';
312
  $sql .= "FROM {$wpdb->posts} p";
@@ -330,12 +366,14 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
330
  }
331
  if ( ! is_null( $query['args'] ) ) {
332
  $sql .= ' AND p.post_content=%s';
333
- $sql_params[] = json_encode( $query['args'] );
334
  }
335
 
336
- if ( ! empty( $query['status'] ) ) {
337
- $sql .= ' AND p.post_status=%s';
338
- $sql_params[] = $this->get_post_status_by_action_status( $query['status'] );
 
 
339
  }
340
 
341
  if ( $query['date'] instanceof DateTime ) {
@@ -356,9 +394,9 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
356
  $sql_params[] = $date_string;
357
  }
358
 
359
- if ( $query['claimed'] === true ) {
360
  $sql .= " AND p.post_password != ''";
361
- } elseif ( $query['claimed'] === false ) {
362
  $sql .= " AND p.post_password = ''";
363
  } elseif ( ! is_null( $query['claimed'] ) ) {
364
  $sql .= ' AND p.post_password = %s';
@@ -408,21 +446,32 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
408
  }
409
  }
410
 
411
- return $wpdb->prepare( $sql, $sql_params );
412
  }
413
 
414
  /**
415
- * @param array $query
416
- * @param string $query_type Whether to select or count the results. Default, select.
417
- * @return string|array The IDs of actions matching the query
 
 
 
 
 
 
 
418
  */
419
  public function query_actions( $query = array(), $query_type = 'select' ) {
420
- /** @var wpdb $wpdb */
 
 
 
 
421
  global $wpdb;
422
 
423
  $sql = $this->get_query_actions_sql( $query, $query_type );
424
 
425
- return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql );
426
  }
427
 
428
  /**
@@ -441,7 +490,7 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
441
  try {
442
  $action_status_name = $this->get_action_status_by_post_status( $post_status_name );
443
  } catch ( Exception $e ) {
444
- // Ignore any post statuses that aren't for actions
445
  continue;
446
  }
447
  if ( array_key_exists( $action_status_name, $action_stati_and_labels ) ) {
@@ -453,13 +502,16 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
453
  }
454
 
455
  /**
456
- * @param string $action_id
457
  *
458
- * @throws InvalidArgumentException
 
 
459
  */
460
  public function cancel_action( $action_id ) {
461
  $post = get_post( $action_id );
462
- if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
 
463
  throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
464
  }
465
  do_action( 'action_scheduler_canceled_action', $action_id );
@@ -468,9 +520,17 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
468
  remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
469
  }
470
 
 
 
 
 
 
 
 
471
  public function delete_action( $action_id ) {
472
  $post = get_post( $action_id );
473
- if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
 
474
  throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
475
  }
476
  do_action( 'action_scheduler_deleted_action', $action_id );
@@ -479,9 +539,9 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
479
  }
480
 
481
  /**
482
- * @param string $action_id
483
  *
484
- * @throws InvalidArgumentException
485
  * @return ActionScheduler_DateTime The date the action is schedule to run, or the date that it ran.
486
  */
487
  public function get_date( $action_id ) {
@@ -490,17 +550,20 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
490
  }
491
 
492
  /**
493
- * @param string $action_id
494
  *
495
- * @throws InvalidArgumentException
 
 
496
  * @return ActionScheduler_DateTime The date the action is schedule to run, or the date that it ran.
497
  */
498
  public function get_date_gmt( $action_id ) {
499
  $post = get_post( $action_id );
500
- if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
 
501
  throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
502
  }
503
- if ( $post->post_status == 'publish' ) {
504
  return as_get_datetime_object( $post->post_modified_gmt );
505
  } else {
506
  return as_get_datetime_object( $post->post_date_gmt );
@@ -508,7 +571,9 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
508
  }
509
 
510
  /**
511
- * @param int $max_actions
 
 
512
  * @param DateTime $before_date Jobs must be schedule before this date. Defaults to now.
513
  * @param array $hooks Claim only actions with a hook or hooks.
514
  * @param string $group Claim only actions in the given group.
@@ -518,40 +583,53 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
518
  * @throws InvalidArgumentException When the given group is not valid.
519
  */
520
  public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) {
521
- $claim_id = $this->generate_claim_id();
 
522
  $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group );
523
- $action_ids = $this->find_actions_by_claim_id( $claim_id );
 
524
 
525
  return new ActionScheduler_ActionClaim( $claim_id, $action_ids );
526
  }
527
 
528
  /**
 
 
529
  * @return int
530
  */
531
  public function get_claim_count() {
532
  global $wpdb;
533
 
534
- $sql = "SELECT COUNT(DISTINCT post_password) FROM {$wpdb->posts} WHERE post_password != '' AND post_type = %s AND post_status IN ('in-progress','pending')";
535
- $sql = $wpdb->prepare( $sql, array( self::POST_TYPE ) );
536
-
537
- return $wpdb->get_var( $sql );
 
 
 
538
  }
539
 
 
 
 
 
 
540
  protected function generate_claim_id() {
541
- $claim_id = md5( microtime( true ) . rand( 0, 1000 ) );
542
- return substr( $claim_id, 0, 20 ); // to fit in db field with 20 char limit
543
  }
544
 
545
  /**
546
- * @param string $claim_id
547
- * @param int $limit
 
 
548
  * @param DateTime $before_date Should use UTC timezone.
549
  * @param array $hooks Claim only actions with a hook or hooks.
550
  * @param string $group Claim only actions in the given group.
551
  *
552
- * @return int The number of actions that were claimed
553
- * @throws RuntimeException When there is a database error.
554
- * @throws InvalidArgumentException When the group is invalid.
555
  */
556
  protected function claim_actions( $claim_id, $limit, DateTime $before_date = null, $hooks = array(), $group = '' ) {
557
  // Set up initial variables.
@@ -564,7 +642,11 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
564
  return 0;
565
  }
566
 
567
- /** @var wpdb $wpdb */
 
 
 
 
568
  global $wpdb;
569
 
570
  /*
@@ -608,8 +690,9 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
608
  $params[] = $limit;
609
 
610
  // Run the query and gather results.
611
- $rows_affected = $wpdb->query( $wpdb->prepare( "{$update} {$where} {$order}", $params ) );
612
- if ( $rows_affected === false ) {
 
613
  throw new RuntimeException( __( 'Unable to claim actions. Database error.', 'action-scheduler' ) );
614
  }
615
 
@@ -630,6 +713,7 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
630
  protected function get_actions_by_group( $group, $limit, DateTime $date ) {
631
  // Ensure the group exists before continuing.
632
  if ( ! term_exists( $group, self::GROUP_TAXONOMY ) ) {
 
633
  throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) );
634
  }
635
 
@@ -653,7 +737,7 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
653
  'before' => $date->format( 'Y-m-d H:i' ),
654
  'inclusive' => true,
655
  ),
656
- 'tax_query' => array(
657
  array(
658
  'taxonomy' => self::GROUP_TAXONOMY,
659
  'field' => 'slug',
@@ -667,57 +751,129 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
667
  }
668
 
669
  /**
670
- * @param string $claim_id
 
 
671
  * @return array
672
  */
673
  public function find_actions_by_claim_id( $claim_id ) {
674
- /** @var wpdb $wpdb */
 
 
 
 
675
  global $wpdb;
676
- $sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_password = %s";
677
- $sql = $wpdb->prepare( $sql, array( self::POST_TYPE, $claim_id ) );
678
- $action_ids = $wpdb->get_col( $sql );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
679
  return $action_ids;
680
  }
681
 
 
 
 
 
 
 
 
682
  public function release_claim( ActionScheduler_ActionClaim $claim ) {
683
  $action_ids = $this->find_actions_by_claim_id( $claim->get_id() );
684
  if ( empty( $action_ids ) ) {
685
- return; // nothing to do
686
  }
687
  $action_id_string = implode( ',', array_map( 'intval', $action_ids ) );
688
- /** @var wpdb $wpdb */
 
 
 
 
689
  global $wpdb;
690
- $sql = "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID IN ($action_id_string) AND post_password = %s";
691
- $sql = $wpdb->prepare( $sql, array( $claim->get_id() ) );
692
- $result = $wpdb->query( $sql );
693
- if ( $result === false ) {
 
 
 
 
 
 
 
694
  /* translators: %s: claim ID */
695
  throw new RuntimeException( sprintf( __( 'Unable to unlock claim %s. Database error.', 'action-scheduler' ), $claim->get_id() ) );
696
  }
697
  }
698
 
699
  /**
700
- * @param string $action_id
 
 
 
701
  */
702
  public function unclaim_action( $action_id ) {
703
- /** @var wpdb $wpdb */
 
 
 
 
704
  global $wpdb;
705
- $sql = "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID = %d AND post_type = %s";
706
- $sql = $wpdb->prepare( $sql, $action_id, self::POST_TYPE );
707
- $result = $wpdb->query( $sql );
708
- if ( $result === false ) {
 
 
 
 
 
 
709
  /* translators: %s: action ID */
710
  throw new RuntimeException( sprintf( __( 'Unable to unlock claim on action %s. Database error.', 'action-scheduler' ), $action_id ) );
711
  }
712
  }
713
 
 
 
 
 
 
 
 
 
714
  public function mark_failure( $action_id ) {
715
- /** @var wpdb $wpdb */
 
 
 
 
716
  global $wpdb;
717
- $sql = "UPDATE {$wpdb->posts} SET post_status = %s WHERE ID = %d AND post_type = %s";
718
- $sql = $wpdb->prepare( $sql, self::STATUS_FAILED, $action_id, self::POST_TYPE );
719
- $result = $wpdb->query( $sql );
720
- if ( $result === false ) {
 
 
721
  /* translators: %s: action ID */
722
  throw new RuntimeException( sprintf( __( 'Unable to mark failure on action %s. Database error.', 'action-scheduler' ), $action_id ) );
723
  }
@@ -726,7 +882,7 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
726
  /**
727
  * Return an action's claim ID, as stored in the post password column
728
  *
729
- * @param string $action_id
730
  * @return mixed
731
  */
732
  public function get_claim_id( $action_id ) {
@@ -736,46 +892,85 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
736
  /**
737
  * Return an action's status, as stored in the post status column
738
  *
739
- * @param string $action_id
 
740
  * @return mixed
 
741
  */
742
  public function get_status( $action_id ) {
743
  $status = $this->get_post_column( $action_id, 'post_status' );
744
 
745
- if ( $status === null ) {
746
  throw new InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
747
  }
748
 
749
  return $this->get_action_status_by_post_status( $status );
750
  }
751
 
 
 
 
 
 
 
 
 
752
  private function get_post_column( $action_id, $column_name ) {
753
- /** @var \wpdb $wpdb */
 
 
 
 
754
  global $wpdb;
755
- return $wpdb->get_var( $wpdb->prepare( "SELECT {$column_name} FROM {$wpdb->posts} WHERE ID=%d AND post_type=%s", $action_id, self::POST_TYPE ) );
 
 
 
 
 
 
 
 
756
  }
757
 
758
  /**
759
- * @param string $action_id
 
 
760
  */
761
  public function log_execution( $action_id ) {
762
- /** @var wpdb $wpdb */
 
 
 
 
763
  global $wpdb;
764
 
765
- $sql = "UPDATE {$wpdb->posts} SET menu_order = menu_order+1, post_status=%s, post_modified_gmt = %s, post_modified = %s WHERE ID = %d AND post_type = %s";
766
- $sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id, self::POST_TYPE );
767
- $wpdb->query( $sql );
 
 
 
 
 
 
 
 
768
  }
769
 
770
  /**
771
  * Record that an action was completed.
772
  *
773
- * @param int $action_id ID of the completed action.
774
- * @throws InvalidArgumentException|RuntimeException
 
 
775
  */
776
  public function mark_complete( $action_id ) {
777
  $post = get_post( $action_id );
778
- if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
 
779
  throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
780
  }
781
  add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
@@ -811,6 +1006,7 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
811
  /**
812
  * Determine whether the post store can be migrated.
813
  *
 
814
  * @return bool
815
  */
816
  public function migration_dependencies_met( $setting ) {
@@ -819,7 +1015,7 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
819
  $dependencies_met = get_transient( self::DEPENDENCIES_MET );
820
  if ( empty( $dependencies_met ) ) {
821
  $maximum_args_length = apply_filters( 'action_scheduler_maximum_args_length', 191 );
822
- $found_action = $wpdb->get_var(
823
  $wpdb->prepare(
824
  "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND CHAR_LENGTH(post_content) > %d LIMIT 1",
825
  $maximum_args_length,
@@ -830,7 +1026,7 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
830
  set_transient( self::DEPENDENCIES_MET, $dependencies_met, DAY_IN_SECONDS );
831
  }
832
 
833
- return 'yes' == $dependencies_met ? $setting : false;
834
  }
835
 
836
  /**
@@ -840,19 +1036,20 @@ class ActionScheduler_wpPostStore extends ActionScheduler_Store {
840
  * as we prepare to move to custom tables, and can use an indexed VARCHAR column instead, we want to warn
841
  * developers of this impending requirement.
842
  *
843
- * @param ActionScheduler_Action $action
844
  */
845
  protected function validate_action( ActionScheduler_Action $action ) {
846
  try {
847
  parent::validate_action( $action );
848
  } catch ( Exception $e ) {
 
849
  $message = sprintf( __( '%s Support for strings longer than this will be removed in a future version.', 'action-scheduler' ), $e->getMessage() );
850
- _doing_it_wrong( 'ActionScheduler_Action::$args', $message, '2.1.0' );
851
  }
852
  }
853
 
854
  /**
855
- * @codeCoverageIgnore
856
  */
857
  public function init() {
858
  add_filter( 'action_scheduler_migration_dependencies_met', array( $this, 'migration_dependencies_met' ) );
9
  const SCHEDULE_META_KEY = '_action_manager_schedule';
10
  const DEPENDENCIES_MET = 'as-post-store-dependencies-met';
11
 
12
+ /**
13
+ * Used to share information about the before_date property of claims internally.
14
+ *
15
+ * This is used in preference to passing the same information as a method param
16
+ * for backwards-compatibility reasons.
17
+ *
18
+ * @var DateTime|null
19
+ */
20
+ private $claim_before_date = null;
21
+
22
+ /**
23
+ * Local Timezone.
24
+ *
25
+ * @var DateTimeZone
26
+ */
27
  protected $local_timezone = null;
28
 
29
+ /**
30
+ * Save action.
31
+ *
32
+ * @param ActionScheduler_Action $action Scheduled Action.
33
+ * @param DateTime $scheduled_date Scheduled Date.
34
+ *
35
+ * @throws RuntimeException Throws an exception if the action could not be saved.
36
+ * @return int
37
+ */
38
  public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = null ) {
39
  try {
40
  $this->validate_action( $action );
45
  do_action( 'action_scheduler_stored_action', $post_id );
46
  return $post_id;
47
  } catch ( Exception $e ) {
48
+ /* translators: %s: action error message */
49
  throw new RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 );
50
  }
51
  }
52
 
53
+ /**
54
+ * Create post array.
55
+ *
56
+ * @param ActionScheduler_Action $action Scheduled Action.
57
+ * @param DateTime $scheduled_date Scheduled Date.
58
+ *
59
+ * @return array Returns an array of post data.
60
+ */
61
  protected function create_post_array( ActionScheduler_Action $action, DateTime $scheduled_date = null ) {
62
  $post = array(
63
  'post_type' => self::POST_TYPE,
64
  'post_title' => $action->get_hook(),
65
+ 'post_content' => wp_json_encode( $action->get_args() ),
66
  'post_status' => ( $action->is_finished() ? 'publish' : 'pending' ),
67
  'post_date_gmt' => $this->get_scheduled_date_string( $action, $scheduled_date ),
68
  'post_date' => $this->get_scheduled_date_string_local( $action, $scheduled_date ),
70
  return $post;
71
  }
72
 
73
+ /**
74
+ * Save post array.
75
+ *
76
+ * @param array $post_array Post array.
77
+ * @return int Returns the post ID.
78
+ * @throws RuntimeException Throws an exception if the action could not be saved.
79
+ */
80
  protected function save_post_array( $post_array ) {
81
  add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
82
  add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
103
  return $post_id;
104
  }
105
 
106
+ /**
107
+ * Filter insert post data.
108
+ *
109
+ * @param array $postdata Post data to filter.
110
+ *
111
+ * @return array
112
+ */
113
  public function filter_insert_post_data( $postdata ) {
114
+ if ( self::POST_TYPE === $postdata['post_type'] ) {
115
  $postdata['post_author'] = 0;
116
+ if ( 'future' === $postdata['post_status'] ) {
117
  $postdata['post_status'] = 'publish';
118
  }
119
  }
149
  * @return string
150
  */
151
  public function set_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) {
152
+ if ( self::POST_TYPE === $post_type ) {
153
  $override_slug = uniqid( self::POST_TYPE . '-', true ) . '-' . wp_generate_password( 32, false );
154
  }
155
  return $override_slug;
156
  }
157
 
158
+ /**
159
+ * Save post schedule.
160
+ *
161
+ * @param int $post_id Post ID of the scheduled action.
162
+ * @param string $schedule Schedule to save.
163
+ *
164
+ * @return void
165
+ */
166
  protected function save_post_schedule( $post_id, $schedule ) {
167
  update_post_meta( $post_id, self::SCHEDULE_META_KEY, $schedule );
168
  }
169
 
170
+ /**
171
+ * Save action group.
172
+ *
173
+ * @param int $post_id Post ID.
174
+ * @param string $group Group to save.
175
+ * @return void
176
+ */
177
  protected function save_action_group( $post_id, $group ) {
178
  if ( empty( $group ) ) {
179
  wp_set_object_terms( $post_id, array(), self::GROUP_TAXONOMY, false );
182
  }
183
  }
184
 
185
+ /**
186
+ * Fetch actions.
187
+ *
188
+ * @param int $action_id Action ID.
189
+ * @return object
190
+ */
191
  public function fetch_action( $action_id ) {
192
  $post = $this->get_post( $action_id );
193
+ if ( empty( $post ) || self::POST_TYPE !== $post->post_type ) {
194
  return $this->get_null_action();
195
  }
196
 
204
  return $action;
205
  }
206
 
207
+ /**
208
+ * Get post.
209
+ *
210
+ * @param string $action_id - Action ID.
211
+ * @return WP_Post|null
212
+ */
213
  protected function get_post( $action_id ) {
214
  if ( empty( $action_id ) ) {
215
  return null;
217
  return get_post( $action_id );
218
  }
219
 
220
+ /**
221
+ * Get NULL action.
222
+ *
223
+ * @return ActionScheduler_NullAction
224
+ */
225
  protected function get_null_action() {
226
  return new ActionScheduler_NullAction();
227
  }
228
 
229
+ /**
230
+ * Make action from post.
231
+ *
232
+ * @param WP_Post $post Post object.
233
+ * @return WP_Post
234
+ */
235
  protected function make_action_from_post( $post ) {
236
  $hook = $post->post_title;
237
 
248
  }
249
 
250
  /**
251
+ * Get action status by post status.
252
+ *
253
+ * @param string $post_status Post status.
254
  *
255
+ * @throws InvalidArgumentException Throw InvalidArgumentException if $post_status not in known status fields returned by $this->get_status_labels().
256
  * @return string
257
  */
258
  protected function get_action_status_by_post_status( $post_status ) {
276
  }
277
 
278
  /**
279
+ * Get post status by action status.
280
+ *
281
+ * @param string $action_status Action status.
282
+ *
283
+ * @throws InvalidArgumentException Throws InvalidArgumentException if $post_status not in known status fields returned by $this->get_status_labels().
284
  * @return string
285
  */
286
  protected function get_post_status_by_action_status( $action_status ) {
303
  return $post_status;
304
  }
305
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  /**
307
  * Returns the SQL statement to query (or count) actions.
308
  *
309
+ * @param array $query - Filtering options.
310
+ * @param string $select_or_count - Whether the SQL should select and return the IDs or just the row count.
311
+ *
312
+ * @throws InvalidArgumentException - Throw InvalidArgumentException if $select_or_count not count or select.
313
  * @return string SQL statement. The returned SQL is already properly escaped.
314
  */
315
  protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) {
316
 
317
+ if ( ! in_array( $select_or_count, array( 'select', 'count' ), true ) ) {
318
  throw new InvalidArgumentException( __( 'Invalid schedule. Cannot save action.', 'action-scheduler' ) );
319
  }
320
 
338
  )
339
  );
340
 
341
+ /**
342
+ * Global wpdb object.
343
+ *
344
+ * @var wpdb $wpdb
345
+ */
346
  global $wpdb;
347
  $sql = ( 'count' === $select_or_count ) ? 'SELECT count(p.ID)' : 'SELECT p.ID ';
348
  $sql .= "FROM {$wpdb->posts} p";
366
  }
367
  if ( ! is_null( $query['args'] ) ) {
368
  $sql .= ' AND p.post_content=%s';
369
+ $sql_params[] = wp_json_encode( $query['args'] );
370
  }
371
 
372
+ if ( $query['status'] ) {
373
+ $post_statuses = array_map( array( $this, 'get_post_status_by_action_status' ), (array) $query['status'] );
374
+ $placeholders = array_fill( 0, count( $post_statuses ), '%s' );
375
+ $sql .= ' AND p.post_status IN (' . join( ', ', $placeholders ) . ')';
376
+ $sql_params = array_merge( $sql_params, array_values( $post_statuses ) );
377
  }
378
 
379
  if ( $query['date'] instanceof DateTime ) {
394
  $sql_params[] = $date_string;
395
  }
396
 
397
+ if ( true === $query['claimed'] ) {
398
  $sql .= " AND p.post_password != ''";
399
+ } elseif ( false === $query['claimed'] ) {
400
  $sql .= " AND p.post_password = ''";
401
  } elseif ( ! is_null( $query['claimed'] ) ) {
402
  $sql .= ' AND p.post_password = %s';
446
  }
447
  }
448
 
449
+ return $wpdb->prepare( $sql, $sql_params ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
450
  }
451
 
452
  /**
453
+ * Query for action count or list of action IDs.
454
+ *
455
+ * @since x.x.x $query['status'] accepts array of statuses instead of a single status.
456
+ *
457
+ * @see ActionScheduler_Store::query_actions for $query arg usage.
458
+ *
459
+ * @param array $query Query filtering options.
460
+ * @param string $query_type Whether to select or count the results. Defaults to select.
461
+ *
462
+ * @return string|array|null The IDs of actions matching the query. Null on failure.
463
  */
464
  public function query_actions( $query = array(), $query_type = 'select' ) {
465
+ /**
466
+ * Global $wpdb object.
467
+ *
468
+ * @var wpdb $wpdb
469
+ */
470
  global $wpdb;
471
 
472
  $sql = $this->get_query_actions_sql( $query, $query_type );
473
 
474
+ return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
475
  }
476
 
477
  /**
490
  try {
491
  $action_status_name = $this->get_action_status_by_post_status( $post_status_name );
492
  } catch ( Exception $e ) {
493
+ // Ignore any post statuses that aren't for actions.
494
  continue;
495
  }
496
  if ( array_key_exists( $action_status_name, $action_stati_and_labels ) ) {
502
  }
503
 
504
  /**
505
+ * Cancel action.
506
  *
507
+ * @param int $action_id Action ID.
508
+ *
509
+ * @throws InvalidArgumentException If $action_id is not identified.
510
  */
511
  public function cancel_action( $action_id ) {
512
  $post = get_post( $action_id );
513
+ if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) {
514
+ /* translators: %s is the action ID */
515
  throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
516
  }
517
  do_action( 'action_scheduler_canceled_action', $action_id );
520
  remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
521
  }
522
 
523
+ /**
524
+ * Delete action.
525
+ *
526
+ * @param int $action_id Action ID.
527
+ * @return void
528
+ * @throws InvalidArgumentException If action is not identified.
529
+ */
530
  public function delete_action( $action_id ) {
531
  $post = get_post( $action_id );
532
+ if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) {
533
+ /* translators: %s is the action ID */
534
  throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
535
  }
536
  do_action( 'action_scheduler_deleted_action', $action_id );
539
  }
540
 
541
  /**
542
+ * Get date for claim id.
543
  *
544
+ * @param int $action_id Action ID.
545
  * @return ActionScheduler_DateTime The date the action is schedule to run, or the date that it ran.
546
  */
547
  public function get_date( $action_id ) {
550
  }
551
 
552
  /**
553
+ * Get Date GMT.
554
  *
555
+ * @param int $action_id Action ID.
556
+ *
557
+ * @throws InvalidArgumentException If $action_id is not identified.
558
  * @return ActionScheduler_DateTime The date the action is schedule to run, or the date that it ran.
559
  */
560
  public function get_date_gmt( $action_id ) {
561
  $post = get_post( $action_id );
562
+ if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) {
563
+ /* translators: %s is the action ID */
564
  throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
565
  }
566
+ if ( 'publish' === $post->post_status ) {
567
  return as_get_datetime_object( $post->post_modified_gmt );
568
  } else {
569
  return as_get_datetime_object( $post->post_date_gmt );
571
  }
572
 
573
  /**
574
+ * Stake claim.
575
+ *
576
+ * @param int $max_actions Maximum number of actions.
577
  * @param DateTime $before_date Jobs must be schedule before this date. Defaults to now.
578
  * @param array $hooks Claim only actions with a hook or hooks.
579
  * @param string $group Claim only actions in the given group.
583
  * @throws InvalidArgumentException When the given group is not valid.
584
  */
585
  public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) {
586
+ $this->claim_before_date = $before_date;
587
+ $claim_id = $this->generate_claim_id();
588
  $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group );
589
+ $action_ids = $this->find_actions_by_claim_id( $claim_id );
590
+ $this->claim_before_date = null;
591
 
592
  return new ActionScheduler_ActionClaim( $claim_id, $action_ids );
593
  }
594
 
595
  /**
596
+ * Get claim count.
597
+ *
598
  * @return int
599
  */
600
  public function get_claim_count() {
601
  global $wpdb;
602
 
603
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
604
+ return $wpdb->get_var(
605
+ $wpdb->prepare(
606
+ "SELECT COUNT(DISTINCT post_password) FROM {$wpdb->posts} WHERE post_password != '' AND post_type = %s AND post_status IN ('in-progress','pending')",
607
+ array( self::POST_TYPE )
608
+ )
609
+ );
610
  }
611
 
612
+ /**
613
+ * Generate claim id.
614
+ *
615
+ * @return string
616
+ */
617
  protected function generate_claim_id() {
618
+ $claim_id = md5( microtime( true ) . wp_rand( 0, 1000 ) );
619
+ return substr( $claim_id, 0, 20 ); // to fit in db field with 20 char limit.
620
  }
621
 
622
  /**
623
+ * Claim actions.
624
+ *
625
+ * @param string $claim_id Claim ID.
626
+ * @param int $limit Limit.
627
  * @param DateTime $before_date Should use UTC timezone.
628
  * @param array $hooks Claim only actions with a hook or hooks.
629
  * @param string $group Claim only actions in the given group.
630
  *
631
+ * @return int The number of actions that were claimed.
632
+ * @throws RuntimeException When there is a database error.
 
633
  */
634
  protected function claim_actions( $claim_id, $limit, DateTime $before_date = null, $hooks = array(), $group = '' ) {
635
  // Set up initial variables.
642
  return 0;
643
  }
644
 
645
+ /**
646
+ * Global wpdb object.
647
+ *
648
+ * @var wpdb $wpdb
649
+ */
650
  global $wpdb;
651
 
652
  /*
690
  $params[] = $limit;
691
 
692
  // Run the query and gather results.
693
+ $rows_affected = $wpdb->query( $wpdb->prepare( "{$update} {$where} {$order}", $params ) ); // phpcs:ignore // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
694
+
695
+ if ( false === $rows_affected ) {
696
  throw new RuntimeException( __( 'Unable to claim actions. Database error.', 'action-scheduler' ) );
697
  }
698
 
713
  protected function get_actions_by_group( $group, $limit, DateTime $date ) {
714
  // Ensure the group exists before continuing.
715
  if ( ! term_exists( $group, self::GROUP_TAXONOMY ) ) {
716
+ /* translators: %s is the group name */
717
  throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) );
718
  }
719
 
737
  'before' => $date->format( 'Y-m-d H:i' ),
738
  'inclusive' => true,
739
  ),
740
+ 'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery
741
  array(
742
  'taxonomy' => self::GROUP_TAXONOMY,
743
  'field' => 'slug',
751
  }
752
 
753
  /**
754
+ * Find actions by claim ID.
755
+ *
756
+ * @param string $claim_id Claim ID.
757
  * @return array
758
  */
759
  public function find_actions_by_claim_id( $claim_id ) {
760
+ /**
761
+ * Global wpdb object.
762
+ *
763
+ * @var wpdb $wpdb
764
+ */
765
  global $wpdb;
766
+
767
+ $action_ids = array();
768
+ $before_date = isset( $this->claim_before_date ) ? $this->claim_before_date : as_get_datetime_object();
769
+ $cut_off = $before_date->format( 'Y-m-d H:i:s' );
770
+
771
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
772
+ $results = $wpdb->get_results(
773
+ $wpdb->prepare(
774
+ "SELECT ID, post_date_gmt FROM {$wpdb->posts} WHERE post_type = %s AND post_password = %s",
775
+ array(
776
+ self::POST_TYPE,
777
+ $claim_id,
778
+ )
779
+ )
780
+ );
781
+
782
+ // Verify that the scheduled date for each action is within the expected bounds (in some unusual
783
+ // cases, we cannot depend on MySQL to honor all of the WHERE conditions we specify).
784
+ foreach ( $results as $claimed_action ) {
785
+ if ( $claimed_action->post_date_gmt <= $cut_off ) {
786
+ $action_ids[] = absint( $claimed_action->ID );
787
+ }
788
+ }
789
+
790
  return $action_ids;
791
  }
792
 
793
+ /**
794
+ * Release claim.
795
+ *
796
+ * @param ActionScheduler_ActionClaim $claim Claim object to release.
797
+ * @return void
798
+ * @throws RuntimeException When the claim is not unlocked.
799
+ */
800
  public function release_claim( ActionScheduler_ActionClaim $claim ) {
801
  $action_ids = $this->find_actions_by_claim_id( $claim->get_id() );
802
  if ( empty( $action_ids ) ) {
803
+ return; // nothing to do.
804
  }
805
  $action_id_string = implode( ',', array_map( 'intval', $action_ids ) );
806
+ /**
807
+ * Global wpdb object.
808
+ *
809
+ * @var wpdb $wpdb
810
+ */
811
  global $wpdb;
812
+
813
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
814
+ $result = $wpdb->query(
815
+ $wpdb->prepare(
816
+ "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID IN ($action_id_string) AND post_password = %s", //phpcs:ignore
817
+ array(
818
+ $claim->get_id(),
819
+ )
820
+ )
821
+ );
822
+ if ( false === $result ) {
823
  /* translators: %s: claim ID */
824
  throw new RuntimeException( sprintf( __( 'Unable to unlock claim %s. Database error.', 'action-scheduler' ), $claim->get_id() ) );
825
  }
826
  }
827
 
828
  /**
829
+ * Unclaim action.
830
+ *
831
+ * @param string $action_id Action ID.
832
+ * @throws RuntimeException When unable to unlock claim on action ID.
833
  */
834
  public function unclaim_action( $action_id ) {
835
+ /**
836
+ * Global wpdb object.
837
+ *
838
+ * @var wpdb $wpdb
839
+ */
840
  global $wpdb;
841
+
842
+ //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
843
+ $result = $wpdb->query(
844
+ $wpdb->prepare(
845
+ "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID = %d AND post_type = %s",
846
+ $action_id,
847
+ self::POST_TYPE
848
+ )
849
+ );
850
+ if ( false === $result ) {
851
  /* translators: %s: action ID */
852
  throw new RuntimeException( sprintf( __( 'Unable to unlock claim on action %s. Database error.', 'action-scheduler' ), $action_id ) );
853
  }
854
  }
855
 
856
+ /**
857
+ * Mark failure on action.
858
+ *
859
+ * @param int $action_id Action ID.
860
+ *
861
+ * @return void
862
+ * @throws RuntimeException When unable to mark failure on action ID.
863
+ */
864
  public function mark_failure( $action_id ) {
865
+ /**
866
+ * Global wpdb object.
867
+ *
868
+ * @var wpdb $wpdb
869
+ */
870
  global $wpdb;
871
+
872
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
873
+ $result = $wpdb->query(
874
+ $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_status = %s WHERE ID = %d AND post_type = %s", self::STATUS_FAILED, $action_id, self::POST_TYPE )
875
+ );
876
+ if ( false === $result ) {
877
  /* translators: %s: action ID */
878
  throw new RuntimeException( sprintf( __( 'Unable to mark failure on action %s. Database error.', 'action-scheduler' ), $action_id ) );
879
  }
882
  /**
883
  * Return an action's claim ID, as stored in the post password column
884
  *
885
+ * @param int $action_id Action ID.
886
  * @return mixed
887
  */
888
  public function get_claim_id( $action_id ) {
892
  /**
893
  * Return an action's status, as stored in the post status column
894
  *
895
+ * @param int $action_id Action ID.
896
+ *
897
  * @return mixed
898
+ * @throws InvalidArgumentException When the action ID is invalid.
899
  */
900
  public function get_status( $action_id ) {
901
  $status = $this->get_post_column( $action_id, 'post_status' );
902
 
903
+ if ( null === $status ) {
904
  throw new InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
905
  }
906
 
907
  return $this->get_action_status_by_post_status( $status );
908
  }
909
 
910
+ /**
911
+ * Get post column
912
+ *
913
+ * @param string $action_id Action ID.
914
+ * @param string $column_name Column Name.
915
+ *
916
+ * @return string|null
917
+ */
918
  private function get_post_column( $action_id, $column_name ) {
919
+ /**
920
+ * Global wpdb object.
921
+ *
922
+ * @var wpdb $wpdb
923
+ */
924
  global $wpdb;
925
+
926
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
927
+ return $wpdb->get_var(
928
+ $wpdb->prepare(
929
+ "SELECT {$column_name} FROM {$wpdb->posts} WHERE ID=%d AND post_type=%s", // phpcs:ignore
930
+ $action_id,
931
+ self::POST_TYPE
932
+ )
933
+ );
934
  }
935
 
936
  /**
937
+ * Log Execution.
938
+ *
939
+ * @param string $action_id Action ID.
940
  */
941
  public function log_execution( $action_id ) {
942
+ /**
943
+ * Global wpdb object.
944
+ *
945
+ * @var wpdb $wpdb
946
+ */
947
  global $wpdb;
948
 
949
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
950
+ $wpdb->query(
951
+ $wpdb->prepare(
952
+ "UPDATE {$wpdb->posts} SET menu_order = menu_order+1, post_status=%s, post_modified_gmt = %s, post_modified = %s WHERE ID = %d AND post_type = %s",
953
+ self::STATUS_RUNNING,
954
+ current_time( 'mysql', true ),
955
+ current_time( 'mysql' ),
956
+ $action_id,
957
+ self::POST_TYPE
958
+ )
959
+ );
960
  }
961
 
962
  /**
963
  * Record that an action was completed.
964
  *
965
+ * @param string $action_id ID of the completed action.
966
+ *
967
+ * @throws InvalidArgumentException When the action ID is invalid.
968
+ * @throws RuntimeException When there was an error executing the action.
969
  */
970
  public function mark_complete( $action_id ) {
971
  $post = get_post( $action_id );
972
+ if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) {
973
+ /* translators: %s is the action ID */
974
  throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
975
  }
976
  add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
1006
  /**
1007
  * Determine whether the post store can be migrated.
1008
  *
1009
+ * @param [type] $setting - Setting value.
1010
  * @return bool
1011
  */
1012
  public function migration_dependencies_met( $setting ) {
1015
  $dependencies_met = get_transient( self::DEPENDENCIES_MET );
1016
  if ( empty( $dependencies_met ) ) {
1017
  $maximum_args_length = apply_filters( 'action_scheduler_maximum_args_length', 191 );
1018
+ $found_action = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1019
  $wpdb->prepare(
1020
  "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND CHAR_LENGTH(post_content) > %d LIMIT 1",
1021
  $maximum_args_length,
1026
  set_transient( self::DEPENDENCIES_MET, $dependencies_met, DAY_IN_SECONDS );
1027
  }
1028
 
1029
+ return 'yes' === $dependencies_met ? $setting : false;
1030
  }
1031
 
1032
  /**
1036
  * as we prepare to move to custom tables, and can use an indexed VARCHAR column instead, we want to warn
1037
  * developers of this impending requirement.
1038
  *
1039
+ * @param ActionScheduler_Action $action Action object.
1040
  */
1041
  protected function validate_action( ActionScheduler_Action $action ) {
1042
  try {
1043
  parent::validate_action( $action );
1044
  } catch ( Exception $e ) {
1045
+ /* translators: %s is the error message */
1046
  $message = sprintf( __( '%s Support for strings longer than this will be removed in a future version.', 'action-scheduler' ), $e->getMessage() );
1047
+ _doing_it_wrong( 'ActionScheduler_Action::$args', esc_html( $message ), '2.1.0' );
1048
  }
1049
  }
1050
 
1051
  /**
1052
+ * (@codeCoverageIgnore)
1053
  */
1054
  public function init() {
1055
  add_filter( 'action_scheduler_migration_dependencies_met', array( $this, 'migration_dependencies_met' ) );
lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_PostStatusRegistrar.php CHANGED
@@ -2,7 +2,6 @@
2
 
3
  /**
4
  * Class ActionScheduler_wpPostStore_PostStatusRegistrar
5
- *
6
  * @codeCoverageIgnore
7
  */
8
  class ActionScheduler_wpPostStore_PostStatusRegistrar {
2
 
3
  /**
4
  * Class ActionScheduler_wpPostStore_PostStatusRegistrar
 
5
  * @codeCoverageIgnore
6
  */
7
  class ActionScheduler_wpPostStore_PostStatusRegistrar {
lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_PostTypeRegistrar.php CHANGED
@@ -2,7 +2,6 @@
2
 
3
  /**
4
  * Class ActionScheduler_wpPostStore_PostTypeRegistrar
5
- *
6
  * @codeCoverageIgnore
7
  */
8
  class ActionScheduler_wpPostStore_PostTypeRegistrar {
@@ -17,35 +16,35 @@ class ActionScheduler_wpPostStore_PostTypeRegistrar {
17
  */
18
  protected function post_type_args() {
19
  $args = array(
20
- 'label' => __( 'Scheduled Actions', 'action-scheduler' ),
21
- 'description' => __( 'Scheduled actions are hooks triggered on a cetain date and time.', 'action-scheduler' ),
22
- 'public' => false,
23
  'map_meta_cap' => true,
24
  'hierarchical' => false,
25
- 'supports' => array( 'title', 'editor', 'comments' ),
26
- 'rewrite' => false,
27
- 'query_var' => false,
28
- 'can_export' => true,
29
- 'ep_mask' => EP_NONE,
30
- 'labels' => array(
31
- 'name' => __( 'Scheduled Actions', 'action-scheduler' ),
32
- 'singular_name' => __( 'Scheduled Action', 'action-scheduler' ),
33
- 'menu_name' => _x( 'Scheduled Actions', 'Admin menu name', 'action-scheduler' ),
34
- 'add_new' => __( 'Add', 'action-scheduler' ),
35
- 'add_new_item' => __( 'Add New Scheduled Action', 'action-scheduler' ),
36
- 'edit' => __( 'Edit', 'action-scheduler' ),
37
- 'edit_item' => __( 'Edit Scheduled Action', 'action-scheduler' ),
38
- 'new_item' => __( 'New Scheduled Action', 'action-scheduler' ),
39
- 'view' => __( 'View Action', 'action-scheduler' ),
40
- 'view_item' => __( 'View Action', 'action-scheduler' ),
41
- 'search_items' => __( 'Search Scheduled Actions', 'action-scheduler' ),
42
- 'not_found' => __( 'No actions found', 'action-scheduler' ),
43
  'not_found_in_trash' => __( 'No actions found in trash', 'action-scheduler' ),
44
  ),
45
  );
46
 
47
- $args = apply_filters( 'action_scheduler_post_type_args', $args );
48
  return $args;
49
  }
50
  }
51
-
2
 
3
  /**
4
  * Class ActionScheduler_wpPostStore_PostTypeRegistrar
 
5
  * @codeCoverageIgnore
6
  */
7
  class ActionScheduler_wpPostStore_PostTypeRegistrar {
16
  */
17
  protected function post_type_args() {
18
  $args = array(
19
+ 'label' => __( 'Scheduled Actions', 'action-scheduler' ),
20
+ 'description' => __( 'Scheduled actions are hooks triggered on a cetain date and time.', 'action-scheduler' ),
21
+ 'public' => false,
22
  'map_meta_cap' => true,
23
  'hierarchical' => false,
24
+ 'supports' => array('title', 'editor','comments'),
25
+ 'rewrite' => false,
26
+ 'query_var' => false,
27
+ 'can_export' => true,
28
+ 'ep_mask' => EP_NONE,
29
+ 'labels' => array(
30
+ 'name' => __( 'Scheduled Actions', 'action-scheduler' ),
31
+ 'singular_name' => __( 'Scheduled Action', 'action-scheduler' ),
32
+ 'menu_name' => _x( 'Scheduled Actions', 'Admin menu name', 'action-scheduler' ),
33
+ 'add_new' => __( 'Add', 'action-scheduler' ),
34
+ 'add_new_item' => __( 'Add New Scheduled Action', 'action-scheduler' ),
35
+ 'edit' => __( 'Edit', 'action-scheduler' ),
36
+ 'edit_item' => __( 'Edit Scheduled Action', 'action-scheduler' ),
37
+ 'new_item' => __( 'New Scheduled Action', 'action-scheduler' ),
38
+ 'view' => __( 'View Action', 'action-scheduler' ),
39
+ 'view_item' => __( 'View Action', 'action-scheduler' ),
40
+ 'search_items' => __( 'Search Scheduled Actions', 'action-scheduler' ),
41
+ 'not_found' => __( 'No actions found', 'action-scheduler' ),
42
  'not_found_in_trash' => __( 'No actions found in trash', 'action-scheduler' ),
43
  ),
44
  );
45
 
46
+ $args = apply_filters('action_scheduler_post_type_args', $args);
47
  return $args;
48
  }
49
  }
50
+
lite/includes/libraries/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_TaxonomyRegistrar.php CHANGED
@@ -2,7 +2,6 @@
2
 
3
  /**
4
  * Class ActionScheduler_wpPostStore_TaxonomyRegistrar
5
- *
6
  * @codeCoverageIgnore
7
  */
8
  class ActionScheduler_wpPostStore_TaxonomyRegistrar {
@@ -12,16 +11,16 @@ class ActionScheduler_wpPostStore_TaxonomyRegistrar {
12
 
13
  protected function taxonomy_args() {
14
  $args = array(
15
- 'label' => __( 'Action Group', 'action-scheduler' ),
16
- 'public' => false,
17
- 'hierarchical' => false,
18
  'show_admin_column' => true,
19
- 'query_var' => false,
20
- 'rewrite' => false,
21
  );
22
 
23
  $args = apply_filters( 'action_scheduler_taxonomy_args', $args );
24
  return $args;
25
  }
26
  }
27
-
2
 
3
  /**
4
  * Class ActionScheduler_wpPostStore_TaxonomyRegistrar
 
5
  * @codeCoverageIgnore
6
  */
7
  class ActionScheduler_wpPostStore_TaxonomyRegistrar {
11
 
12
  protected function taxonomy_args() {
13
  $args = array(
14
+ 'label' => __( 'Action Group', 'action-scheduler' ),
15
+ 'public' => false,
16
+ 'hierarchical' => false,
17
  'show_admin_column' => true,
18
+ 'query_var' => false,
19
+ 'rewrite' => false,
20
  );
21
 
22
  $args = apply_filters( 'action_scheduler_taxonomy_args', $args );
23
  return $args;
24
  }
25
  }
26
+
lite/includes/libraries/action-scheduler/classes/migration/ActionMigrator.php CHANGED
@@ -79,10 +79,10 @@ class ActionMigrator {
79
 
80
  try {
81
  switch ( $status ) {
82
- case \ActionScheduler_Store::STATUS_FAILED:
83
  $this->destination->mark_failure( $destination_action_id );
84
  break;
85
- case \ActionScheduler_Store::STATUS_CANCELED:
86
  $this->destination->cancel_action( $destination_action_id );
87
  break;
88
  }
79
 
80
  try {
81
  switch ( $status ) {
82
+ case \ActionScheduler_Store::STATUS_FAILED :
83
  $this->destination->mark_failure( $destination_action_id );
84
  break;
85
+ case \ActionScheduler_Store::STATUS_CANCELED :
86
  $this->destination->cancel_action( $destination_action_id );
87
  break;
88
  }
lite/includes/libraries/action-scheduler/classes/migration/ActionScheduler_DBStoreMigrator.php CHANGED
@@ -17,13 +17,13 @@ class ActionScheduler_DBStoreMigrator extends ActionScheduler_DBStore {
17
  * that when first saving the action.
18
  *
19
  * @param ActionScheduler_Action $action
20
- * @param \DateTime $scheduled_date Optional date of the first instance to store.
21
- * @param \DateTime $last_attempt_date Optional date the action was last attempted.
22
  *
23
  * @return string The action ID
24
  * @throws \RuntimeException When the action is not saved.
25
  */
26
- public function save_action( ActionScheduler_Action $action, \DateTime $scheduled_date = null, \DateTime $last_attempt_date = null ) {
27
  try {
28
  /** @var \wpdb $wpdb */
29
  global $wpdb;
@@ -31,10 +31,10 @@ class ActionScheduler_DBStoreMigrator extends ActionScheduler_DBStore {
31
  $action_id = parent::save_action( $action, $scheduled_date );
32
 
33
  if ( null !== $last_attempt_date ) {
34
- $data = array(
35
  'last_attempt_gmt' => $this->get_scheduled_date_string( $action, $last_attempt_date ),
36
  'last_attempt_local' => $this->get_scheduled_date_string_local( $action, $last_attempt_date ),
37
- );
38
 
39
  $wpdb->update( $wpdb->actionscheduler_actions, $data, array( 'action_id' => $action_id ), array( '%s', '%s' ), array( '%d' ) );
40
  }
17
  * that when first saving the action.
18
  *
19
  * @param ActionScheduler_Action $action
20
+ * @param \DateTime $scheduled_date Optional date of the first instance to store.
21
+ * @param \DateTime $last_attempt_date Optional date the action was last attempted.
22
  *
23
  * @return string The action ID
24
  * @throws \RuntimeException When the action is not saved.
25
  */
26
+ public function save_action( ActionScheduler_Action $action, \DateTime $scheduled_date = null, \DateTime $last_attempt_date = null ){
27
  try {
28
  /** @var \wpdb $wpdb */
29
  global $wpdb;
31
  $action_id = parent::save_action( $action, $scheduled_date );
32
 
33
  if ( null !== $last_attempt_date ) {
34
+ $data = [
35
  'last_attempt_gmt' => $this->get_scheduled_date_string( $action, $last_attempt_date ),
36
  'last_attempt_local' => $this->get_scheduled_date_string_local( $action, $last_attempt_date ),
37
+ ];
38
 
39
  $wpdb->update( $wpdb->actionscheduler_actions, $data, array( 'action_id' => $action_id ), array( '%s', '%s' ), array( '%d' ) );
40
  }
lite/includes/libraries/action-scheduler/classes/migration/BatchFetcher.php CHANGED
@@ -3,6 +3,7 @@
3
 
4
  namespace Action_Scheduler\Migration;
5
 
 
6
  use ActionScheduler_Store as Store;
7
 
8
  /**
@@ -42,7 +43,7 @@ class BatchFetcher {
42
  }
43
  }
44
 
45
- return array();
46
  }
47
 
48
  /**
@@ -54,38 +55,32 @@ class BatchFetcher {
54
  */
55
  private function get_query_strategies( $count ) {
56
  $now = as_get_datetime_object();
57
- $args = array(
58
  'date' => $now,
59
  'per_page' => $count,
60
  'offset' => 0,
61
  'orderby' => 'date',
62
  'order' => 'ASC',
63
- );
64
 
65
- $priorities = array(
66
  Store::STATUS_PENDING,
67
  Store::STATUS_FAILED,
68
  Store::STATUS_CANCELED,
69
  Store::STATUS_COMPLETE,
70
  Store::STATUS_RUNNING,
71
  '', // any other unanticipated status
72
- );
73
 
74
  foreach ( $priorities as $status ) {
75
- yield wp_parse_args(
76
- array(
77
- 'status' => $status,
78
- 'date_compare' => '<=',
79
- ),
80
- $args
81
- );
82
- yield wp_parse_args(
83
- array(
84
- 'status' => $status,
85
- 'date_compare' => '>=',
86
- ),
87
- $args
88
- );
89
  }
90
  }
91
- }
3
 
4
  namespace Action_Scheduler\Migration;
5
 
6
+
7
  use ActionScheduler_Store as Store;
8
 
9
  /**
43
  }
44
  }
45
 
46
+ return [];
47
  }
48
 
49
  /**
55
  */
56
  private function get_query_strategies( $count ) {
57
  $now = as_get_datetime_object();
58
+ $args = [
59
  'date' => $now,
60
  'per_page' => $count,
61
  'offset' => 0,
62
  'orderby' => 'date',
63
  'order' => 'ASC',
64
+ ];
65
 
66
+ $priorities = [
67
  Store::STATUS_PENDING,
68
  Store::STATUS_FAILED,
69
  Store::STATUS_CANCELED,
70
  Store::STATUS_COMPLETE,
71
  Store::STATUS_RUNNING,
72
  '', // any other unanticipated status
73
+ ];
74
 
75
  foreach ( $priorities as $status ) {
76
+ yield wp_parse_args( [
77
+ 'status' => $status,
78
+ 'date_compare' => '<=',
79
+ ], $args );
80
+ yield wp_parse_args( [
81
+ 'status' => $status,
82
+ 'date_compare' => '>=',
83
+ ], $args );
 
 
 
 
 
 
84
  }
85
  }
86
+ }
lite/includes/libraries/action-scheduler/classes/migration/Controller.php CHANGED
@@ -2,7 +2,9 @@
2
 
3
  namespace Action_Scheduler\Migration;
4
 
5
- use Action_Scheduler\WP_CLI\Migration_Command;
 
 
6
  use Action_Scheduler\WP_CLI\ProgressBar;
7
 
8
  /**
@@ -87,12 +89,30 @@ class Controller {
87
  }
88
 
89
  /**
90
- * Set up the background migration process
91
  *
92
  * @return void
93
  */
94
  public function schedule_migration() {
95
- if ( \ActionScheduler_DataController::is_migration_complete() || $this->migration_scheduler->is_migration_scheduled() ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  return;
97
  }
98
 
@@ -139,7 +159,7 @@ class Controller {
139
  * Show a dashboard notice that migration is in progress.
140
  */
141
  public function display_migration_notice() {
142
- printf( '<div class="notice notice-warning"><p>%s</p></div>', __( 'Action Scheduler migration in progress. The list of scheduled actions may be incomplete.', 'action-scheduler' ) );
143
  }
144
 
145
  /**
2
 
3
  namespace Action_Scheduler\Migration;
4
 
5
+ use ActionScheduler_DataController;
6
+ use ActionScheduler_LoggerSchema;
7
+ use ActionScheduler_StoreSchema;
8
  use Action_Scheduler\WP_CLI\ProgressBar;
9
 
10
  /**
89
  }
90
 
91
  /**
92
+ * Set up the background migration process.
93
  *
94
  * @return void
95
  */
96
  public function schedule_migration() {
97
+ $logging_tables = new ActionScheduler_LoggerSchema();
98
+ $store_tables = new ActionScheduler_StoreSchema();
99
+
100
+ /*
101
+ * In some unusual cases, the expected tables may not have been created. In such cases
102
+ * we do not schedule a migration as doing so will lead to fatal error conditions.
103
+ *
104
+ * In such cases the user will likely visit the Tools > Scheduled Actions screen to
105
+ * investigate, and will see appropriate messaging (this step also triggers an attempt
106
+ * to rebuild any missing tables).
107
+ *
108
+ * @see https://github.com/woocommerce/action-scheduler/issues/653
109
+ */
110
+ if (
111
+ ActionScheduler_DataController::is_migration_complete()
112
+ || $this->migration_scheduler->is_migration_scheduled()
113
+ || ! $store_tables->tables_exist()
114
+ || ! $logging_tables->tables_exist()
115
+ ) {
116
  return;
117
  }
118
 
159
  * Show a dashboard notice that migration is in progress.
160
  */
161
  public function display_migration_notice() {
162
+ printf( '<div class="notice notice-warning"><p>%s</p></div>', esc_html__( 'Action Scheduler migration in progress. The list of scheduled actions may be incomplete.', 'action-scheduler' ) );
163
  }
164
 
165
  /**
lite/includes/libraries/action-scheduler/classes/migration/DryRun_LogMigrator.php CHANGED
@@ -20,4 +20,4 @@ class DryRun_LogMigrator extends LogMigrator {
20
  public function migrate( $source_action_id, $destination_action_id ) {
21
  // no-op
22
  }
23
- }
20
  public function migrate( $source_action_id, $destination_action_id ) {
21
  // no-op
22
  }
23
+ }
lite/includes/libraries/action-scheduler/classes/migration/Runner.php CHANGED
@@ -70,7 +70,7 @@ class Runner {
70
  * @return int Size of batch processed.
71
  */
72
  public function run( $batch_size = 10 ) {
73
- $batch = $this->batch_fetcher->fetch( $batch_size );
74
  $batch_size = count( $batch );
75
 
76
  if ( ! $batch_size ) {
@@ -102,17 +102,14 @@ class Runner {
102
  foreach ( $action_ids as $source_action_id ) {
103
  $destination_action_id = $this->action_migrator->migrate( $source_action_id );
104
  if ( $destination_action_id ) {
105
- $this->destination_logger->log(
 
 
 
 
106
  $destination_action_id,
107
- sprintf(
108
- /* translators: 1: source action ID 2: source store class 3: destination action ID 4: destination store class */
109
- __( 'Migrated action with ID %1$d in %2$s to ID %3$d in %4$s', 'action-scheduler' ),
110
- $source_action_id,
111
- get_class( $this->source_store ),
112
- $destination_action_id,
113
- get_class( $this->destination_store )
114
- )
115
- );
116
  }
117
 
118
  if ( $this->progress_bar ) {
70
  * @return int Size of batch processed.
71
  */
72
  public function run( $batch_size = 10 ) {
73
+ $batch = $this->batch_fetcher->fetch( $batch_size );
74
  $batch_size = count( $batch );
75
 
76
  if ( ! $batch_size ) {
102
  foreach ( $action_ids as $source_action_id ) {
103
  $destination_action_id = $this->action_migrator->migrate( $source_action_id );
104
  if ( $destination_action_id ) {
105
+ $this->destination_logger->log( $destination_action_id, sprintf(
106
+ /* translators: 1: source action ID 2: source store class 3: destination action ID 4: destination store class */
107
+ __( 'Migrated action with ID %1$d in %2$s to ID %3$d in %4$s', 'action-scheduler' ),
108
+ $source_action_id,
109
+ get_class( $this->source_store ),
110
  $destination_action_id,
111
+ get_class( $this->destination_store )
112
+ ) );
 
 
 
 
 
 
 
113
  }
114
 
115
  if ( $this->progress_bar ) {
lite/includes/libraries/action-scheduler/classes/migration/Scheduler.php CHANGED
@@ -14,10 +14,10 @@ namespace Action_Scheduler\Migration;
14
  */
15
  class Scheduler {
16
  /** Migration action hook. */
17
- const HOOK = 'action_scheduler/migration_hook';
18
 
19
  /** Migration action group. */
20
- const GROUP = 'action-scheduler-migration';
21
 
22
  /**
23
  * Set up the callback for the scheduled job.
14
  */
15
  class Scheduler {
16
  /** Migration action hook. */
17
+ const HOOK = 'action_scheduler/migration_hook';
18
 
19
  /** Migration action group. */
20
+ const GROUP = 'action-scheduler-migration';
21
 
22
  /**
23
  * Set up the callback for the scheduled job.
lite/includes/libraries/action-scheduler/classes/schedules/ActionScheduler_CanceledSchedule.php CHANGED
@@ -8,7 +8,7 @@ class ActionScheduler_CanceledSchedule extends ActionScheduler_SimpleSchedule {
8
  /**
9
  * Deprecated property @see $this->__wakeup() for details.
10
  **/
11
- private $timestamp = null;
12
 
13
  /**
14
  * @param DateTime $after
8
  /**
9
  * Deprecated property @see $this->__wakeup() for details.
10
  **/
11
+ private $timestamp = NULL;
12
 
13
  /**
14
  * @param DateTime $after
lite/includes/libraries/action-scheduler/classes/schedules/ActionScheduler_CronSchedule.php CHANGED
@@ -8,20 +8,20 @@ class ActionScheduler_CronSchedule extends ActionScheduler_Abstract_RecurringSch
8
  /**
9
  * Deprecated property @see $this->__wakeup() for details.
10
  **/
11
- private $start_timestamp = null;
12
 
13
  /**
14
  * Deprecated property @see $this->__wakeup() for details.
15
  **/
16
- private $cron = null;
17
 
18
  /**
19
  * Wrapper for parent constructor to accept a cron expression string and map it to a CronExpression for this
20
  * objects $recurrence property.
21
  *
22
- * @param DateTime $start The date & time to run the action at or after. If $start aligns with the CronSchedule passed via $recurrence, it will be used. If it does not align, the first matching date after it will be used.
23
  * @param CronExpression|string $recurrence The CronExpression used to calculate the schedule's next instance.
24
- * @param DateTime|null $first (Optional) The date & time the first instance of this interval schedule ran. Default null, meaning this is the first instance.
25
  */
26
  public function __construct( DateTime $start, $recurrence, DateTime $first = null ) {
27
  if ( ! is_a( $recurrence, 'CronExpression' ) ) {
@@ -75,13 +75,10 @@ class ActionScheduler_CronSchedule extends ActionScheduler_Abstract_RecurringSch
75
  $this->start_timestamp = $this->scheduled_timestamp;
76
  $this->cron = $this->recurrence;
77
 
78
- return array_merge(
79
- $sleep_params,
80
- array(
81
- 'start_timestamp',
82
- 'cron',
83
- )
84
- );
85
  }
86
 
87
  /**
8
  /**
9
  * Deprecated property @see $this->__wakeup() for details.
10
  **/
11
+ private $start_timestamp = NULL;
12
 
13
  /**
14
  * Deprecated property @see $this->__wakeup() for details.
15
  **/
16
+ private $cron = NULL;
17
 
18
  /**
19
  * Wrapper for parent constructor to accept a cron expression string and map it to a CronExpression for this
20
  * objects $recurrence property.
21
  *
22
+ * @param DateTime $start The date & time to run the action at or after. If $start aligns with the CronSchedule passed via $recurrence, it will be used. If it does not align, the first matching date after it will be used.
23
  * @param CronExpression|string $recurrence The CronExpression used to calculate the schedule's next instance.
24
+ * @param DateTime|null $first (Optional) The date & time the first instance of this interval schedule ran. Default null, meaning this is the first instance.
25
  */
26
  public function __construct( DateTime $start, $recurrence, DateTime $first = null ) {
27
  if ( ! is_a( $recurrence, 'CronExpression' ) ) {
75
  $this->start_timestamp = $this->scheduled_timestamp;
76
  $this->cron = $this->recurrence;
77
 
78
+ return array_merge( $sleep_params, array(
79
+ 'start_timestamp',
80
+ 'cron'
81
+ ) );
 
 
 
82
  }
83
 
84
  /**
lite/includes/libraries/action-scheduler/classes/schedules/ActionScheduler_IntervalSchedule.php CHANGED
@@ -8,12 +8,12 @@ class ActionScheduler_IntervalSchedule extends ActionScheduler_Abstract_Recurrin
8
  /**
9
  * Deprecated property @see $this->__wakeup() for details.
10
  **/
11
- private $start_timestamp = null;
12
 
13
  /**
14
  * Deprecated property @see $this->__wakeup() for details.
15
  **/
16
- private $interval_in_seconds = null;
17
 
18
  /**
19
  * Calculate when this schedule should start after a given date & time using
@@ -55,13 +55,10 @@ class ActionScheduler_IntervalSchedule extends ActionScheduler_Abstract_Recurrin
55
  $this->start_timestamp = $this->scheduled_timestamp;
56
  $this->interval_in_seconds = $this->recurrence;
57
 
58
- return array_merge(
59
- $sleep_params,
60
- array(
61
- 'start_timestamp',
62
- 'interval_in_seconds',
63
- )
64
- );
65
  }
66
 
67
  /**
8
  /**
9
  * Deprecated property @see $this->__wakeup() for details.
10
  **/
11
+ private $start_timestamp = NULL;
12
 
13
  /**
14
  * Deprecated property @see $this->__wakeup() for details.
15
  **/
16
+ private $interval_in_seconds = NULL;
17
 
18
  /**
19
  * Calculate when this schedule should start after a given date & time using
55
  $this->start_timestamp = $this->scheduled_timestamp;
56
  $this->interval_in_seconds = $this->recurrence;
57
 
58
+ return array_merge( $sleep_params, array(
59
+ 'start_timestamp',
60
+ 'interval_in_seconds'
61
+ ) );
 
 
 
62
  }
63
 
64
  /**
lite/includes/libraries/action-scheduler/classes/schedules/ActionScheduler_NullSchedule.php CHANGED
@@ -16,7 +16,6 @@ class ActionScheduler_NullSchedule extends ActionScheduler_SimpleSchedule {
16
 
17
  /**
18
  * This schedule has no scheduled DateTime, so we need to override the parent __sleep()
19
- *
20
  * @return array
21
  */
22
  public function __sleep() {
16
 
17
  /**
18
  * This schedule has no scheduled DateTime, so we need to override the parent __sleep()
 
19
  * @return array
20
  */
21
  public function __sleep() {
lite/includes/libraries/action-scheduler/classes/schedules/ActionScheduler_Schedule.php CHANGED
@@ -8,11 +8,11 @@ interface ActionScheduler_Schedule {
8
  * @param DateTime $after
9
  * @return DateTime|null
10
  */
11
- public function next( DateTime $after = null );
12
 
13
  /**
14
  * @return bool
15
  */
16
  public function is_recurring();
17
  }
18
-
8
  * @param DateTime $after
9
  * @return DateTime|null
10
  */
11
+ public function next( DateTime $after = NULL );
12
 
13
  /**
14
  * @return bool
15
  */
16
  public function is_recurring();
17
  }
18
+
lite/includes/libraries/action-scheduler/classes/schedules/ActionScheduler_SimpleSchedule.php CHANGED
@@ -8,7 +8,7 @@ class ActionScheduler_SimpleSchedule extends ActionScheduler_Abstract_Schedule {
8
  /**
9
  * Deprecated property @see $this->__wakeup() for details.
10
  **/
11
- private $timestamp = null;
12
 
13
  /**
14
  * @param DateTime $after
@@ -45,12 +45,9 @@ class ActionScheduler_SimpleSchedule extends ActionScheduler_Abstract_Schedule {
45
 
46
  $this->timestamp = $this->scheduled_timestamp;
47
 
48
- return array_merge(
49
- $sleep_params,
50
- array(
51
- 'timestamp',
52
- )
53
- );
54
  }
55
 
56
  /**
8
  /**
9
  * Deprecated property @see $this->__wakeup() for details.
10
  **/
11
+ private $timestamp = NULL;
12
 
13
  /**
14
  * @param DateTime $after
45
 
46
  $this->timestamp = $this->scheduled_timestamp;
47
 
48
+ return array_merge( $sleep_params, array(
49
+ 'timestamp',
50
+ ) );
 
 
 
51
  }
52
 
53
  /**
lite/includes/libraries/action-scheduler/classes/schema/ActionScheduler_LoggerSchema.php CHANGED
@@ -13,27 +13,36 @@ class ActionScheduler_LoggerSchema extends ActionScheduler_Abstract_Schema {
13
  /**
14
  * @var int Increment this value to trigger a schema update.
15
  */
16
- protected $schema_version = 2;
17
 
18
  public function __construct() {
19
- $this->tables = array(
20
  self::LOG_TABLE,
21
- );
 
 
 
 
 
 
 
22
  }
23
 
24
  protected function get_table_definition( $table ) {
25
  global $wpdb;
26
- $table_name = $wpdb->$table;
27
- $charset_collate = $wpdb->get_charset_collate();
28
  switch ( $table ) {
29
 
30
  case self::LOG_TABLE:
 
 
31
  return "CREATE TABLE {$table_name} (
32
  log_id bigint(20) unsigned NOT NULL auto_increment,
33
  action_id bigint(20) unsigned NOT NULL,
34
  message text NOT NULL,
35
- log_date_gmt datetime NOT NULL default '0000-00-00 00:00:00',
36
- log_date_local datetime NOT NULL default '0000-00-00 00:00:00',
37
  PRIMARY KEY (log_id),
38
  KEY action_id (action_id),
39
  KEY log_date_gmt (log_date_gmt)
@@ -43,4 +52,39 @@ class ActionScheduler_LoggerSchema extends ActionScheduler_Abstract_Schema {
43
  return '';
44
  }
45
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  }
13
  /**
14
  * @var int Increment this value to trigger a schema update.
15
  */
16
+ protected $schema_version = 3;
17
 
18
  public function __construct() {
19
+ $this->tables = [
20
  self::LOG_TABLE,
21
+ ];
22
+ }
23
+
24
+ /**
25
+ * Performs additional setup work required to support this schema.
26
+ */
27
+ public function init() {
28
+ add_action( 'action_scheduler_before_schema_update', array( $this, 'update_schema_3_0' ), 10, 2 );
29
  }
30
 
31
  protected function get_table_definition( $table ) {
32
  global $wpdb;
33
+ $table_name = $wpdb->$table;
34
+ $charset_collate = $wpdb->get_charset_collate();
35
  switch ( $table ) {
36
 
37
  case self::LOG_TABLE:
38
+
39
+ $default_date = ActionScheduler_StoreSchema::DEFAULT_DATE;
40
  return "CREATE TABLE {$table_name} (
41
  log_id bigint(20) unsigned NOT NULL auto_increment,
42
  action_id bigint(20) unsigned NOT NULL,
43
  message text NOT NULL,
44
+ log_date_gmt datetime NULL default '${default_date}',
45
+ log_date_local datetime NULL default '${default_date}',
46
  PRIMARY KEY (log_id),
47
  KEY action_id (action_id),
48
  KEY log_date_gmt (log_date_gmt)
52
  return '';
53
  }
54
  }
55
+
56
+ /**
57
+ * Update the logs table schema, allowing datetime fields to be NULL.
58
+ *
59
+ * This is needed because the NOT NULL constraint causes a conflict with some versions of MySQL
60
+ * configured with sql_mode=NO_ZERO_DATE, which can for instance lead to tables not being created.
61
+ *
62
+ * Most other schema updates happen via ActionScheduler_Abstract_Schema::update_table(), however
63
+ * that method relies on dbDelta() and this change is not possible when using that function.
64
+ *
65
+ * @param string $table Name of table being updated.
66
+ * @param string $db_version The existing schema version of the table.
67
+ */
68
+ public function update_schema_3_0( $table, $db_version ) {
69
+ global $wpdb;
70
+
71
+ if ( 'actionscheduler_logs' !== $table || version_compare( $db_version, '3', '>=' ) ) {
72
+ return;
73
+ }
74
+
75
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
76
+ $table_name = $wpdb->prefix . 'actionscheduler_logs';
77
+ $table_list = $wpdb->get_col( "SHOW TABLES LIKE '${table_name}'" );
78
+ $default_date = ActionScheduler_StoreSchema::DEFAULT_DATE;
79
+
80
+ if ( ! empty( $table_list ) ) {
81
+ $query = "
82
+ ALTER TABLE ${table_name}
83
+ MODIFY COLUMN log_date_gmt datetime NULL default '${default_date}',
84
+ MODIFY COLUMN log_date_local datetime NULL default '${default_date}'
85
+ ";
86
+ $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
87
+ }
88
+ // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
89
+ }
90
  }
lite/includes/libraries/action-scheduler/classes/schema/ActionScheduler_StoreSchema.php CHANGED
@@ -11,18 +11,26 @@ class ActionScheduler_StoreSchema extends ActionScheduler_Abstract_Schema {
11
  const ACTIONS_TABLE = 'actionscheduler_actions';
12
  const CLAIMS_TABLE = 'actionscheduler_claims';
13
  const GROUPS_TABLE = 'actionscheduler_groups';
 
14
 
15
  /**
16
  * @var int Increment this value to trigger a schema update.
17
  */
18
- protected $schema_version = 3;
19
 
20
  public function __construct() {
21
- $this->tables = array(
22
  self::ACTIONS_TABLE,
23
  self::CLAIMS_TABLE,
24
  self::GROUPS_TABLE,
25
- );
 
 
 
 
 
 
 
26
  }
27
 
28
  protected function get_table_definition( $table ) {
@@ -30,21 +38,23 @@ class ActionScheduler_StoreSchema extends ActionScheduler_Abstract_Schema {
30
  $table_name = $wpdb->$table;
31
  $charset_collate = $wpdb->get_charset_collate();
32
  $max_index_length = 191; // @see wp_get_db_schema()
 
33
  switch ( $table ) {
34
 
35
  case self::ACTIONS_TABLE:
 
36
  return "CREATE TABLE {$table_name} (
37
  action_id bigint(20) unsigned NOT NULL auto_increment,
38
  hook varchar(191) NOT NULL,
39
  status varchar(20) NOT NULL,
40
- scheduled_date_gmt datetime NOT NULL default '0000-00-00 00:00:00',
41
- scheduled_date_local datetime NOT NULL default '0000-00-00 00:00:00',
42
  args varchar($max_index_length),
43
  schedule longtext,
44
  group_id bigint(20) unsigned NOT NULL default '0',
45
  attempts int(11) NOT NULL default '0',
46
- last_attempt_gmt datetime NOT NULL default '0000-00-00 00:00:00',
47
- last_attempt_local datetime NOT NULL default '0000-00-00 00:00:00',
48
  claim_id bigint(20) unsigned NOT NULL default '0',
49
  extended_args varchar(8000) DEFAULT NULL,
50
  PRIMARY KEY (action_id),
@@ -54,18 +64,20 @@ class ActionScheduler_StoreSchema extends ActionScheduler_Abstract_Schema {
54
  KEY args (args($max_index_length)),
55
  KEY group_id (group_id),
56
  KEY last_attempt_gmt (last_attempt_gmt),
57
- KEY claim_id (claim_id)
58
  ) $charset_collate";
59
 
60
  case self::CLAIMS_TABLE:
 
61
  return "CREATE TABLE {$table_name} (
62
  claim_id bigint(20) unsigned NOT NULL auto_increment,
63
- date_created_gmt datetime NOT NULL default '0000-00-00 00:00:00',
64
  PRIMARY KEY (claim_id),
65
  KEY date_created_gmt (date_created_gmt)
66
  ) $charset_collate";
67
 
68
  case self::GROUPS_TABLE:
 
69
  return "CREATE TABLE {$table_name} (
70
  group_id bigint(20) unsigned NOT NULL auto_increment,
71
  slug varchar(255) NOT NULL,
@@ -77,4 +89,41 @@ class ActionScheduler_StoreSchema extends ActionScheduler_Abstract_Schema {
77
  return '';
78
  }
79
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  }
11
  const ACTIONS_TABLE = 'actionscheduler_actions';
12
  const CLAIMS_TABLE = 'actionscheduler_claims';
13
  const GROUPS_TABLE = 'actionscheduler_groups';
14
+ const DEFAULT_DATE = '0000-00-00 00:00:00';
15
 
16
  /**
17
  * @var int Increment this value to trigger a schema update.
18
  */
19
+ protected $schema_version = 6;
20
 
21
  public function __construct() {
22
+ $this->tables = [
23
  self::ACTIONS_TABLE,
24
  self::CLAIMS_TABLE,
25
  self::GROUPS_TABLE,
26
+ ];
27
+ }
28
+
29
+ /**
30
+ * Performs additional setup work required to support this schema.
31
+ */
32
+ public function init() {
33
+ add_action( 'action_scheduler_before_schema_update', array( $this, 'update_schema_5_0' ), 10, 2 );
34
  }
35
 
36
  protected function get_table_definition( $table ) {
38
  $table_name = $wpdb->$table;
39
  $charset_collate = $wpdb->get_charset_collate();
40
  $max_index_length = 191; // @see wp_get_db_schema()
41
+ $default_date = self::DEFAULT_DATE;
42
  switch ( $table ) {
43
 
44
  case self::ACTIONS_TABLE:
45
+
46
  return "CREATE TABLE {$table_name} (
47
  action_id bigint(20) unsigned NOT NULL auto_increment,
48
  hook varchar(191) NOT NULL,
49
  status varchar(20) NOT NULL,
50
+ scheduled_date_gmt datetime NULL default '${default_date}',
51
+ scheduled_date_local datetime NULL default '${default_date}',
52
  args varchar($max_index_length),
53
  schedule longtext,
54
  group_id bigint(20) unsigned NOT NULL default '0',
55
  attempts int(11) NOT NULL default '0',
56
+ last_attempt_gmt datetime NULL default '${default_date}',
57
+ last_attempt_local datetime NULL default '${default_date}',
58
  claim_id bigint(20) unsigned NOT NULL default '0',
59
  extended_args varchar(8000) DEFAULT NULL,
60
  PRIMARY KEY (action_id),
64
  KEY args (args($max_index_length)),
65
  KEY group_id (group_id),
66
  KEY last_attempt_gmt (last_attempt_gmt),
67
+ KEY `claim_id_status_scheduled_date_gmt` (`claim_id`, `status`, `scheduled_date_gmt`)
68
  ) $charset_collate";
69
 
70
  case self::CLAIMS_TABLE:
71
+
72
  return "CREATE TABLE {$table_name} (
73
  claim_id bigint(20) unsigned NOT NULL auto_increment,
74
+ date_created_gmt datetime NULL default '${default_date}',
75
  PRIMARY KEY (claim_id),
76
  KEY date_created_gmt (date_created_gmt)
77
  ) $charset_collate";
78
 
79
  case self::GROUPS_TABLE:
80
+
81
  return "CREATE TABLE {$table_name} (
82
  group_id bigint(20) unsigned NOT NULL auto_increment,
83
  slug varchar(255) NOT NULL,
89
  return '';
90
  }
91
  }
92
+
93
+ /**
94
+ * Update the actions table schema, allowing datetime fields to be NULL.
95
+ *
96
+ * This is needed because the NOT NULL constraint causes a conflict with some versions of MySQL
97
+ * configured with sql_mode=NO_ZERO_DATE, which can for instance lead to tables not being created.
98
+ *
99
+ * Most other schema updates happen via ActionScheduler_Abstract_Schema::update_table(), however
100
+ * that method relies on dbDelta() and this change is not possible when using that function.
101
+ *
102
+ * @param string $table Name of table being updated.
103
+ * @param string $db_version The existing schema version of the table.
104
+ */
105
+ public function update_schema_5_0( $table, $db_version ) {
106
+ global $wpdb;
107
+
108
+ if ( 'actionscheduler_actions' !== $table || version_compare( $db_version, '5', '>=' ) ) {
109
+ return;
110
+ }
111
+
112
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
113
+ $table_name = $wpdb->prefix . 'actionscheduler_actions';
114
+ $table_list = $wpdb->get_col( "SHOW TABLES LIKE '${table_name}'" );
115
+ $default_date = self::DEFAULT_DATE;
116
+
117
+ if ( ! empty( $table_list ) ) {
118
+ $query = "
119
+ ALTER TABLE ${table_name}
120
+ MODIFY COLUMN scheduled_date_gmt datetime NULL default '${default_date}',
121
+ MODIFY COLUMN scheduled_date_local datetime NULL default '${default_date}',
122
+ MODIFY COLUMN last_attempt_gmt datetime NULL default '${default_date}',
123
+ MODIFY COLUMN last_attempt_local datetime NULL default '${default_date}'
124
+ ";
125
+ $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
126
+ }
127
+ // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
128
+ }
129
  }
lite/includes/libraries/action-scheduler/deprecated/ActionScheduler_AdminView_Deprecated.php CHANGED
@@ -68,7 +68,7 @@ class ActionScheduler_AdminView_Deprecated {
68
  * Print the content for our custom columns.
69
  *
70
  * @param string $column_name The key for the column for which we should output our content.
71
- * @param int $post_id The ID of the 'scheduled-action' post for which this row relates.
72
  */
73
  public static function list_table_column_content( $column_name, $post_id ) {
74
  _deprecated_function( __METHOD__, '2.0.0' );
@@ -115,18 +115,18 @@ class ActionScheduler_AdminView_Deprecated {
115
  /**
116
  * Filter search queries to allow searching by Claim ID (i.e. post_password).
117
  *
118
- * @param string $orderby MySQL orderby string.
119
  * @param WP_Query $query Instance of a WP_Query object
120
  * @return string MySQL orderby string.
121
  */
122
- public function custom_orderby( $orderby, $query ) {
123
  _deprecated_function( __METHOD__, '2.0.0' );
124
  }
125
 
126
  /**
127
  * Filter search queries to allow searching by Claim ID (i.e. post_password).
128
  *
129
- * @param string $search MySQL search string.
130
  * @param WP_Query $query Instance of a WP_Query object
131
  * @return string MySQL search string.
132
  */
@@ -144,4 +144,4 @@ class ActionScheduler_AdminView_Deprecated {
144
  _deprecated_function( __METHOD__, '2.0.0' );
145
  return $messages;
146
  }
147
- }
68
  * Print the content for our custom columns.
69
  *
70
  * @param string $column_name The key for the column for which we should output our content.
71
+ * @param int $post_id The ID of the 'scheduled-action' post for which this row relates.
72
  */
73
  public static function list_table_column_content( $column_name, $post_id ) {
74
  _deprecated_function( __METHOD__, '2.0.0' );
115
  /**
116
  * Filter search queries to allow searching by Claim ID (i.e. post_password).
117
  *
118
+ * @param string $orderby MySQL orderby string.
119
  * @param WP_Query $query Instance of a WP_Query object
120
  * @return string MySQL orderby string.
121
  */
122
+ public function custom_orderby( $orderby, $query ){
123
  _deprecated_function( __METHOD__, '2.0.0' );
124
  }
125
 
126
  /**
127
  * Filter search queries to allow searching by Claim ID (i.e. post_password).
128
  *
129
+ * @param string $search MySQL search string.
130
  * @param WP_Query $query Instance of a WP_Query object
131
  * @return string MySQL search string.
132
  */
144
  _deprecated_function( __METHOD__, '2.0.0' );
145
  return $messages;
146
  }
147
+ }
lite/includes/libraries/action-scheduler/deprecated/ActionScheduler_Schedule_Deprecated.php CHANGED
@@ -9,7 +9,7 @@ abstract class ActionScheduler_Schedule_Deprecated implements ActionScheduler_Sc
9
  * Get the date & time this schedule was created to run, or calculate when it should be run
10
  * after a given date & time.
11
  *
12
- * @param DateTime $after
13
  *
14
  * @return DateTime|null
15
  */
@@ -22,7 +22,7 @@ abstract class ActionScheduler_Schedule_Deprecated implements ActionScheduler_Sc
22
  $replacement_method = 'get_next( $after )';
23
  }
24
 
25
- _deprecated_function( __METHOD__, '3.0.0', __CLASS__ . '::' . $replacement_method );
26
 
27
  return $return_value;
28
  }
9
  * Get the date & time this schedule was created to run, or calculate when it should be run
10
  * after a given date & time.
11
  *
12
+ * @param DateTime $after DateTime to calculate against.
13
  *
14
  * @return DateTime|null
15
  */
22
  $replacement_method = 'get_next( $after )';
23
  }
24
 
25
+ _deprecated_function( __METHOD__, '3.0.0', __CLASS__ . '::' . $replacement_method ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
26
 
27
  return $return_value;
28
  }
lite/includes/libraries/action-scheduler/deprecated/ActionScheduler_Store_Deprecated.php CHANGED
@@ -2,7 +2,6 @@
2
 
3
  /**
4
  * Class ActionScheduler_Store_Deprecated
5
- *
6
  * @codeCoverageIgnore
7
  */
8
  abstract class ActionScheduler_Store_Deprecated {
2
 
3
  /**
4
  * Class ActionScheduler_Store_Deprecated
 
5
  * @codeCoverageIgnore
6
  */
7
  abstract class ActionScheduler_Store_Deprecated {
lite/includes/libraries/action-scheduler/deprecated/functions.php CHANGED
@@ -11,9 +11,9 @@
11
  /**
12
  * Schedule an action to run one time
13
  *
14
- * @param int $timestamp When the job will run
15
  * @param string $hook The hook to trigger
16
- * @param array $args Arguments to pass when the hook triggers
17
  * @param string $group The group to assign this job to
18
  *
19
  * @return string The job ID
@@ -26,10 +26,10 @@ function wc_schedule_single_action( $timestamp, $hook, $args = array(), $group =
26
  /**
27
  * Schedule a recurring action
28
  *
29
- * @param int $timestamp When the first instance of the job will run
30
- * @param int $interval_in_seconds How long to wait between runs
31
  * @param string $hook The hook to trigger
32
- * @param array $args Arguments to pass when the hook triggers
33
  * @param string $group The group to assign this job to
34
  *
35
  * @deprecated 2.1.0
@@ -44,7 +44,7 @@ function wc_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook,
44
  /**
45
  * Schedule an action that recurs on a cron-like schedule.
46
  *
47
- * @param int $timestamp The schedule will start on or after this time
48
  * @param string $schedule A cron-link schedule string
49
  * @see http://en.wikipedia.org/wiki/Cron
50
  * * * * * * *
@@ -57,7 +57,7 @@ function wc_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook,
57
  * | +-------------------- hour (0 - 23)
58
  * +------------------------- min (0 - 59)
59
  * @param string $hook The hook to trigger
60
- * @param array $args Arguments to pass when the hook triggers
61
  * @param string $group The group to assign this job to
62
  *
63
  * @deprecated 2.1.0
@@ -73,7 +73,7 @@ function wc_schedule_cron_action( $timestamp, $schedule, $hook, $args = array(),
73
  * Cancel the next occurrence of a job.
74
  *
75
  * @param string $hook The hook that the job will trigger
76
- * @param array $args Args that would have been passed to the job
77
  * @param string $group
78
  *
79
  * @deprecated 2.1.0
@@ -85,14 +85,14 @@ function wc_unschedule_action( $hook, $args = array(), $group = '' ) {
85
 
86
  /**
87
  * @param string $hook
88
- * @param array $args
89
  * @param string $group
90
  *
91
  * @deprecated 2.1.0
92
  *
93
  * @return int|bool The timestamp for the next occurrence, or false if nothing was found
94
  */
95
- function wc_next_scheduled_action( $hook, $args = null, $group = '' ) {
96
  _deprecated_function( __FUNCTION__, '2.1.0', 'as_next_scheduled_action()' );
97
  return as_next_scheduled_action( $hook, $args, $group );
98
  }
@@ -100,20 +100,20 @@ function wc_next_scheduled_action( $hook, $args = null, $group = '' ) {
100
  /**
101
  * Find scheduled actions
102
  *
103
- * @param array $args Possible arguments, with their default values:
104
- * 'hook' => '' - the name of the action that will be triggered
105
- * 'args' => NULL - the args array that will be passed with the action
106
- * 'date' => NULL - the scheduled date of the action. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime(). Used in UTC timezone.
107
- * 'date_compare' => '<=' - operator for testing "date". accepted values are '!=', '>', '>=', '<', '<=', '='
108
- * 'modified' => NULL - the date the action was last updated. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime(). Used in UTC timezone.
109
- * 'modified_compare' => '<=' - operator for testing "modified". accepted values are '!=', '>', '>=', '<', '<=', '='
110
- * 'group' => '' - the group the action belongs to
111
- * 'status' => '' - ActionScheduler_Store::STATUS_COMPLETE or ActionScheduler_Store::STATUS_PENDING
112
- * 'claimed' => NULL - TRUE to find claimed actions, FALSE to find unclaimed actions, a string to find a specific claim ID
113
- * 'per_page' => 5 - Number of results to return
114
- * 'offset' => 0
115
- * 'orderby' => 'date' - accepted values are 'hook', 'group', 'modified', or 'date'
116
- * 'order' => 'ASC'
117
  * @param string $return_format OBJECT, ARRAY_A, or ids
118
  *
119
  * @deprecated 2.1.0
11
  /**
12
  * Schedule an action to run one time
13
  *
14
+ * @param int $timestamp When the job will run
15
  * @param string $hook The hook to trigger
16
+ * @param array $args Arguments to pass when the hook triggers
17
  * @param string $group The group to assign this job to
18
  *
19
  * @return string The job ID
26
  /**
27
  * Schedule a recurring action
28
  *
29
+ * @param int $timestamp When the first instance of the job will run
30
+ * @param int $interval_in_seconds How long to wait between runs
31
  * @param string $hook The hook to trigger
32
+ * @param array $args Arguments to pass when the hook triggers
33
  * @param string $group The group to assign this job to
34
  *
35
  * @deprecated 2.1.0
44
  /**
45
  * Schedule an action that recurs on a cron-like schedule.
46
  *
47
+ * @param int $timestamp The schedule will start on or after this time
48
  * @param string $schedule A cron-link schedule string
49
  * @see http://en.wikipedia.org/wiki/Cron
50
  * * * * * * *
57
  * | +-------------------- hour (0 - 23)
58
  * +------------------------- min (0 - 59)
59
  * @param string $hook The hook to trigger
60
+ * @param array $args Arguments to pass when the hook triggers
61
  * @param string $group The group to assign this job to
62
  *
63
  * @deprecated 2.1.0
73
  * Cancel the next occurrence of a job.
74
  *
75
  * @param string $hook The hook that the job will trigger
76
+ * @param array $args Args that would have been passed to the job
77
  * @param string $group
78
  *
79
  * @deprecated 2.1.0
85
 
86
  /**
87
  * @param string $hook
88
+ * @param array $args
89
  * @param string $group
90
  *
91
  * @deprecated 2.1.0
92
  *
93
  * @return int|bool The timestamp for the next occurrence, or false if nothing was found
94
  */
95
+ function wc_next_scheduled_action( $hook, $args = NULL, $group = '' ) {
96
  _deprecated_function( __FUNCTION__, '2.1.0', 'as_next_scheduled_action()' );
97
  return as_next_scheduled_action( $hook, $args, $group );
98
  }
100
  /**
101
  * Find scheduled actions
102
  *
103
+ * @param array $args Possible arguments, with their default values:
104
+ * 'hook' => '' - the name of the action that will be triggered
105
+ * 'args' => NULL - the args array that will be passed with the action
106
+ * 'date' => NULL - the scheduled date of the action. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime(). Used in UTC timezone.
107
+ * 'date_compare' => '<=' - operator for testing "date". accepted values are '!=', '>', '>=', '<', '<=', '='
108
+ * 'modified' => NULL - the date the action was last updated. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime(). Used in UTC timezone.
109
+ * 'modified_compare' => '<=' - operator for testing "modified". accepted values are '!=', '>', '>=', '<', '<=', '='
110
+ * 'group' => '' - the group the action belongs to
111
+ * 'status' => '' - ActionScheduler_Store::STATUS_COMPLETE or ActionScheduler_Store::STATUS_PENDING
112
+ * 'claimed' => NULL - TRUE to find claimed actions, FALSE to find unclaimed actions, a string to find a specific claim ID
113
+ * 'per_page' => 5 - Number of results to return
114
+ * 'offset' => 0
115
+ * 'orderby' => 'date' - accepted values are 'hook', 'group', 'modified', or 'date'
116
+ * 'order' => 'ASC'
117
  * @param string $return_format OBJECT, ARRAY_A, or ids
118
  *
119
  * @deprecated 2.1.0
lite/includes/libraries/action-scheduler/functions.php CHANGED
@@ -22,9 +22,9 @@ function as_enqueue_async_action( $hook, $args = array(), $group = '' ) {
22
  /**
23
  * Schedule an action to run one time
24
  *
25
- * @param int $timestamp When the job will run.
26
  * @param string $hook The hook to trigger.
27
- * @param array $args Arguments to pass when the hook triggers.
28
  * @param string $group The group to assign this job to.
29
  *
30
  * @return int The action ID.
@@ -39,10 +39,10 @@ function as_schedule_single_action( $timestamp, $hook, $args = array(), $group =
39
  /**
40
  * Schedule a recurring action
41
  *
42
- * @param int $timestamp When the first instance of the job will run.
43
- * @param int $interval_in_seconds How long to wait between runs.
44
  * @param string $hook The hook to trigger.
45
- * @param array $args Arguments to pass when the hook triggers.
46
  * @param string $group The group to assign this job to.
47
  *
48
  * @return int The action ID.
@@ -57,9 +57,9 @@ function as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook,
57
  /**
58
  * Schedule an action that recurs on a cron-like schedule.
59
  *
60
- * @param int $base_timestamp The first instance of the action will be scheduled
61
- * to run at a time calculated after this timestamp matching the cron
62
- * expression. This can be used to delay the first instance of the action.
63
  * @param string $schedule A cron-link schedule string
64
  * @see http://en.wikipedia.org/wiki/Cron
65
  * * * * * * *
@@ -72,7 +72,7 @@ function as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook,
72
  * | +-------------------- hour (0 - 23)
73
  * +------------------------- min (0 - 59)
74
  * @param string $hook The hook to trigger.
75
- * @param array $args Arguments to pass when the hook triggers.
76
  * @param string $group The group to assign this job to.
77
  *
78
  * @return int The action ID.
@@ -95,7 +95,7 @@ function as_schedule_cron_action( $timestamp, $schedule, $hook, $args = array(),
95
  * by this method also.
96
  *
97
  * @param string $hook The hook that the job will trigger.
98
- * @param array $args Args that would have been passed to the job.
99
  * @param string $group The group the job is assigned to.
100
  *
101
  * @return string|null The scheduled action ID if a scheduled action was found, or null if no matching action found.
@@ -104,27 +104,30 @@ function as_unschedule_action( $hook, $args = array(), $group = '' ) {
104
  if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
105
  return 0;
106
  }
107
- $params = array();
 
 
 
 
 
 
108
  if ( is_array( $args ) ) {
109
  $params['args'] = $args;
110
  }
111
- if ( ! empty( $group ) ) {
112
- $params['group'] = $group;
113
- }
114
- $job_id = ActionScheduler::store()->find_action( $hook, $params );
115
 
116
- if ( ! empty( $job_id ) ) {
117
- ActionScheduler::store()->cancel_action( $job_id );
 
118
  }
119
 
120
- return $job_id;
121
  }
122
 
123
  /**
124
  * Cancel all occurrences of a scheduled action.
125
  *
126
  * @param string $hook The hook that the job will trigger.
127
- * @param array $args Args that would have been passed to the job.
128
  * @param string $group The group the job is assigned to.
129
  */
130
  function as_unschedule_all_actions( $hook, $args = array(), $group = '' ) {
@@ -156,7 +159,7 @@ function as_unschedule_all_actions( $hook, $args = array(), $group = '' ) {
156
  * boolean false will be the return value.
157
  *
158
  * @param string $hook
159
- * @param array $args
160
  * @param string $group
161
  *
162
  * @return int|bool The timestamp for the next occurrence of a pending scheduled action, true for an async or in-progress action or false if there is no matching action.
@@ -165,52 +168,93 @@ function as_next_scheduled_action( $hook, $args = null, $group = '' ) {
165
  if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
166
  return false;
167
  }
168
- $params = array();
 
 
 
 
 
 
 
169
  if ( is_array( $args ) ) {
170
  $params['args'] = $args;
171
  }
172
- if ( ! empty( $group ) ) {
173
- $params['group'] = $group;
174
- }
175
 
176
  $params['status'] = ActionScheduler_Store::STATUS_RUNNING;
177
- $job_id = ActionScheduler::store()->find_action( $hook, $params );
178
- if ( ! empty( $job_id ) ) {
179
  return true;
180
  }
181
 
182
  $params['status'] = ActionScheduler_Store::STATUS_PENDING;
183
- $job_id = ActionScheduler::store()->find_action( $hook, $params );
184
- if ( empty( $job_id ) ) {
185
  return false;
186
  }
187
- $job = ActionScheduler::store()->fetch_action( $job_id );
188
- $scheduled_date = $job->get_schedule()->get_date();
 
189
  if ( $scheduled_date ) {
190
  return (int) $scheduled_date->format( 'U' );
191
  } elseif ( null === $scheduled_date ) { // pending async action with NullSchedule
192
  return true;
193
  }
 
194
  return false;
195
  }
196
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  /**
198
  * Find scheduled actions
199
  *
200
- * @param array $args Possible arguments, with their default values:
201
- * 'hook' => '' - the name of the action that will be triggered
202
- * 'args' => NULL - the args array that will be passed with the action
203
- * 'date' => NULL - the scheduled date of the action. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime(). Used in UTC timezone.
204
- * 'date_compare' => '<=' - operator for testing "date". accepted values are '!=', '>', '>=', '<', '<=', '='
205
- * 'modified' => NULL - the date the action was last updated. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime(). Used in UTC timezone.
206
- * 'modified_compare' => '<=' - operator for testing "modified". accepted values are '!=', '>', '>=', '<', '<=', '='
207
- * 'group' => '' - the group the action belongs to
208
- * 'status' => '' - ActionScheduler_Store::STATUS_COMPLETE or ActionScheduler_Store::STATUS_PENDING
209
- * 'claimed' => NULL - TRUE to find claimed actions, FALSE to find unclaimed actions, a string to find a specific claim ID
210
- * 'per_page' => 5 - Number of results to return
211
- * 'offset' => 0
212
- * 'orderby' => 'date' - accepted values are 'hook', 'group', 'modified', or 'date'
213
- * 'order' => 'ASC'
214
  *
215
  * @param string $return_format OBJECT, ARRAY_A, or ids.
216
  *
@@ -221,9 +265,9 @@ function as_get_scheduled_actions( $args = array(), $return_format = OBJECT ) {
221
  return array();
222
  }
223
  $store = ActionScheduler::store();
224
- foreach ( array( 'date', 'modified' ) as $key ) {
225
- if ( isset( $args[ $key ] ) ) {
226
- $args[ $key ] = as_get_datetime_object( $args[ $key ] );
227
  }
228
  }
229
  $ids = $store->query_actions( $args );
@@ -234,12 +278,12 @@ function as_get_scheduled_actions( $args = array(), $return_format = OBJECT ) {
234
 
235
  $actions = array();
236
  foreach ( $ids as $action_id ) {
237
- $actions[ $action_id ] = $store->fetch_action( $action_id );
238
  }
239
 
240
  if ( $return_format == ARRAY_A ) {
241
  foreach ( $actions as $action_id => $action_object ) {
242
- $actions[ $action_id ] = get_object_vars( $action_object );
243
  }
244
  }
245
 
@@ -258,7 +302,7 @@ function as_get_scheduled_actions( $args = array(), $return_format = OBJECT ) {
258
  * timezone when instantiating datetimes rather than leaving it up to
259
  * the PHP default.
260
  *
261
- * @param mixed $date_string A date/time string. Valid formats are explained in http://php.net/manual/en/datetime.formats.php.
262
  * @param string $timezone A timezone identifier, like UTC or Europe/Lisbon. The list of valid identifiers is available http://php.net/manual/en/timezones.php.
263
  *
264
  * @return ActionScheduler_DateTime
22
  /**
23
  * Schedule an action to run one time
24
  *
25
+ * @param int $timestamp When the job will run.
26
  * @param string $hook The hook to trigger.
27
+ * @param array $args Arguments to pass when the hook triggers.
28
  * @param string $group The group to assign this job to.
29
  *
30
  * @return int The action ID.
39
  /**
40
  * Schedule a recurring action
41
  *
42
+ * @param int $timestamp When the first instance of the job will run.
43
+ * @param int $interval_in_seconds How long to wait between runs.
44
  * @param string $hook The hook to trigger.
45
+ * @param array $args Arguments to pass when the hook triggers.
46
  * @param string $group The group to assign this job to.
47
  *
48
  * @return int The action ID.
57
  /**
58
  * Schedule an action that recurs on a cron-like schedule.
59
  *
60
+ * @param int $base_timestamp The first instance of the action will be scheduled
61
+ * to run at a time calculated after this timestamp matching the cron
62
+ * expression. This can be used to delay the first instance of the action.
63
  * @param string $schedule A cron-link schedule string
64
  * @see http://en.wikipedia.org/wiki/Cron
65
  * * * * * * *
72
  * | +-------------------- hour (0 - 23)
73
  * +------------------------- min (0 - 59)
74
  * @param string $hook The hook to trigger.
75
+ * @param array $args Arguments to pass when the hook triggers.
76
  * @param string $group The group to assign this job to.
77
  *
78
  * @return int The action ID.
95
  * by this method also.
96
  *
97
  * @param string $hook The hook that the job will trigger.
98
+ * @param array $args Args that would have been passed to the job.
99
  * @param string $group The group the job is assigned to.
100
  *
101
  * @return string|null The scheduled action ID if a scheduled action was found, or null if no matching action found.
104
  if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
105
  return 0;
106
  }
107
+ $params = array(
108
+ 'hook' => $hook,
109
+ 'status' => ActionScheduler_Store::STATUS_PENDING,
110
+ 'orderby' => 'date',
111
+ 'order' => 'ASC',
112
+ 'group' => $group,
113
+ );
114
  if ( is_array( $args ) ) {
115
  $params['args'] = $args;
116
  }
 
 
 
 
117
 
118
+ $action_id = ActionScheduler::store()->query_action( $params );
119
+ if ( $action_id ) {
120
+ ActionScheduler::store()->cancel_action( $action_id );
121
  }
122
 
123
+ return $action_id;
124
  }
125
 
126
  /**
127
  * Cancel all occurrences of a scheduled action.
128
  *
129
  * @param string $hook The hook that the job will trigger.
130
+ * @param array $args Args that would have been passed to the job.
131
  * @param string $group The group the job is assigned to.
132
  */
133
  function as_unschedule_all_actions( $hook, $args = array(), $group = '' ) {
159
  * boolean false will be the return value.
160
  *
161
  * @param string $hook
162
+ * @param array $args
163
  * @param string $group
164
  *
165
  * @return int|bool The timestamp for the next occurrence of a pending scheduled action, true for an async or in-progress action or false if there is no matching action.
168
  if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
169
  return false;
170
  }
171
+
172
+ $params = array(
173
+ 'hook' => $hook,
174
+ 'orderby' => 'date',
175
+ 'order' => 'ASC',
176
+ 'group' => $group,
177
+ );
178
+
179
  if ( is_array( $args ) ) {
180
  $params['args'] = $args;
181
  }
 
 
 
182
 
183
  $params['status'] = ActionScheduler_Store::STATUS_RUNNING;
184
+ $action_id = ActionScheduler::store()->query_action( $params );
185
+ if ( $action_id ) {
186
  return true;
187
  }
188
 
189
  $params['status'] = ActionScheduler_Store::STATUS_PENDING;
190
+ $action_id = ActionScheduler::store()->query_action( $params );
191
+ if ( null === $action_id ) {
192
  return false;
193
  }
194
+
195
+ $action = ActionScheduler::store()->fetch_action( $action_id );
196
+ $scheduled_date = $action->get_schedule()->get_date();
197
  if ( $scheduled_date ) {
198
  return (int) $scheduled_date->format( 'U' );
199
  } elseif ( null === $scheduled_date ) { // pending async action with NullSchedule
200
  return true;
201
  }
202
+
203
  return false;
204
  }
205
 
206
+ /**
207
+ * Check if there is a scheduled action in the queue but more efficiently than as_next_scheduled_action().
208
+ *
209
+ * It's recommended to use this function when you need to know whether a specific action is currently scheduled
210
+ * (pending or in-progress).
211
+ *
212
+ * @since x.x.x
213
+ *
214
+ * @param string $hook The hook of the action.
215
+ * @param array $args Args that have been passed to the action. Null will matches any args.
216
+ * @param string $group The group the job is assigned to.
217
+ *
218
+ * @return bool True if a matching action is pending or in-progress, false otherwise.
219
+ */
220
+ function as_has_scheduled_action( $hook, $args = null, $group = '' ) {
221
+ if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
222
+ return false;
223
+ }
224
+
225
+ $query_args = array(
226
+ 'hook' => $hook,
227
+ 'status' => array( ActionScheduler_Store::STATUS_RUNNING, ActionScheduler_Store::STATUS_PENDING ),
228
+ 'group' => $group,
229
+ 'orderby' => 'none',
230
+ );
231
+
232
+ if ( null !== $args ) {
233
+ $query_args['args'] = $args;
234
+ }
235
+
236
+ $action_id = ActionScheduler::store()->query_action( $query_args );
237
+
238
+ return $action_id !== null;
239
+ }
240
+
241
  /**
242
  * Find scheduled actions
243
  *
244
+ * @param array $args Possible arguments, with their default values:
245
+ * 'hook' => '' - the name of the action that will be triggered
246
+ * 'args' => NULL - the args array that will be passed with the action
247
+ * 'date' => NULL - the scheduled date of the action. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime(). Used in UTC timezone.
248
+ * 'date_compare' => '<=' - operator for testing "date". accepted values are '!=', '>', '>=', '<', '<=', '='
249
+ * 'modified' => NULL - the date the action was last updated. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime(). Used in UTC timezone.
250
+ * 'modified_compare' => '<=' - operator for testing "modified". accepted values are '!=', '>', '>=', '<', '<=', '='
251
+ * 'group' => '' - the group the action belongs to
252
+ * 'status' => '' - ActionScheduler_Store::STATUS_COMPLETE or ActionScheduler_Store::STATUS_PENDING
253
+ * 'claimed' => NULL - TRUE to find claimed actions, FALSE to find unclaimed actions, a string to find a specific claim ID
254
+ * 'per_page' => 5 - Number of results to return
255
+ * 'offset' => 0
256
+ * 'orderby' => 'date' - accepted values are 'hook', 'group', 'modified', 'date' or 'none'
257
+ * 'order' => 'ASC'
258
  *
259
  * @param string $return_format OBJECT, ARRAY_A, or ids.
260
  *
265
  return array();
266
  }
267
  $store = ActionScheduler::store();
268
+ foreach ( array('date', 'modified') as $key ) {
269
+ if ( isset($args[$key]) ) {
270
+ $args[$key] = as_get_datetime_object($args[$key]);
271
  }
272
  }
273
  $ids = $store->query_actions( $args );
278
 
279
  $actions = array();
280
  foreach ( $ids as $action_id ) {
281
+ $actions[$action_id] = $store->fetch_action( $action_id );
282
  }
283
 
284
  if ( $return_format == ARRAY_A ) {
285
  foreach ( $actions as $action_id => $action_object ) {
286
+ $actions[$action_id] = get_object_vars($action_object);
287
  }
288
  }
289
 
302
  * timezone when instantiating datetimes rather than leaving it up to
303
  * the PHP default.
304
  *
305
+ * @param mixed $date_string A date/time string. Valid formats are explained in http://php.net/manual/en/datetime.formats.php.
306
  * @param string $timezone A timezone identifier, like UTC or Europe/Lisbon. The list of valid identifiers is available http://php.net/manual/en/timezones.php.
307
  *
308
  * @return ActionScheduler_DateTime
lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression.php CHANGED
@@ -13,295 +13,306 @@
13
  * @author Michael Dowling <mtdowling@gmail.com>
14
  * @link http://en.wikipedia.org/wiki/Cron
15
  */
16
- class CronExpression {
17
-
18
- const MINUTE = 0;
19
- const HOUR = 1;
20
- const DAY = 2;
21
- const MONTH = 3;
22
- const WEEKDAY = 4;
23
- const YEAR = 5;
24
-
25
- /**
26
- * @var array CRON expression parts
27
- */
28
- private $cronParts;
29
-
30
- /**
31
- * @var CronExpression_FieldFactory CRON field factory
32
- */
33
- private $fieldFactory;
34
-
35
- /**
36
- * @var array Order in which to test of cron parts
37
- */
38
- private static $order = array( self::YEAR, self::MONTH, self::DAY, self::WEEKDAY, self::HOUR, self::MINUTE );
39
-
40
- /**
41
- * Factory method to create a new CronExpression.
42
- *
43
- * @param string $expression The CRON expression to create. There are
44
- * several special predefined values which can be used to substitute the
45
- * CRON expression:
46
- *
47
- * @yearly, @annually) - Run once a year, midnight, Jan. 1 - 0 0 1 1 *
48
- * @monthly - Run once a month, midnight, first of month - 0 0 1 * *
49
- * @weekly - Run once a week, midnight on Sun - 0 0 * * 0
50
- * @daily - Run once a day, midnight - 0 0 * * *
51
- * @hourly - Run once an hour, first minute - 0 * * * *
52
- *
53
- * @param CronExpression_FieldFactory $fieldFactory (optional) Field factory to use
54
- *
55
- * @return CronExpression
56
- */
57
- public static function factory( $expression, CronExpression_FieldFactory $fieldFactory = null ) {
58
- $mappings = array(
59
- '@yearly' => '0 0 1 1 *',
60
- '@annually' => '0 0 1 1 *',
61
- '@monthly' => '0 0 1 * *',
62
- '@weekly' => '0 0 * * 0',
63
- '@daily' => '0 0 * * *',
64
- '@hourly' => '0 * * * *',
65
- );
66
-
67
- if ( isset( $mappings[ $expression ] ) ) {
68
- $expression = $mappings[ $expression ];
69
- }
70
-
71
- return new self( $expression, $fieldFactory ? $fieldFactory : new CronExpression_FieldFactory() );
72
- }
73
-
74
- /**
75
- * Parse a CRON expression
76
- *
77
- * @param string $expression CRON expression (e.g. '8 * * * *')
78
- * @param CronExpression_FieldFactory $fieldFactory Factory to create cron fields
79
- */
80
- public function __construct( $expression, CronExpression_FieldFactory $fieldFactory ) {
81
- $this->fieldFactory = $fieldFactory;
82
- $this->setExpression( $expression );
83
- }
84
-
85
- /**
86
- * Set or change the CRON expression
87
- *
88
- * @param string $value CRON expression (e.g. 8 * * * *)
89
- *
90
- * @return CronExpression
91
- * @throws InvalidArgumentException if not a valid CRON expression
92
- */
93
- public function setExpression( $value ) {
94
- $this->cronParts = preg_split( '/\s/', $value, -1, PREG_SPLIT_NO_EMPTY );
95
- if ( count( $this->cronParts ) < 5 ) {
96
- throw new InvalidArgumentException(
97
- $value . ' is not a valid CRON expression'
98
- );
99
- }
100
-
101
- foreach ( $this->cronParts as $position => $part ) {
102
- $this->setPart( $position, $part );
103
- }
104
-
105
- return $this;
106
- }
107
-
108
- /**
109
- * Set part of the CRON expression
110
- *
111
- * @param int $position The position of the CRON expression to set
112
- * @param string $value The value to set
113
- *
114
- * @return CronExpression
115
- * @throws InvalidArgumentException if the value is not valid for the part
116
- */
117
- public function setPart( $position, $value ) {
118
- if ( ! $this->fieldFactory->getField( $position )->validate( $value ) ) {
119
- throw new InvalidArgumentException(
120
- 'Invalid CRON field value ' . $value . ' as position ' . $position
121
- );
122
- }
123
-
124
- $this->cronParts[ $position ] = $value;
125
-
126
- return $this;
127
- }
128
-
129
- /**
130
- * Get a next run date relative to the current date or a specific date
131
- *
132
- * @param string|DateTime $currentTime (optional) Relative calculation date
133
- * @param int $nth (optional) Number of matches to skip before returning a
134
- * matching next run date. 0, the default, will return the current
135
- * date and time if the next run date falls on the current date and
136
- * time. Setting this value to 1 will skip the first match and go to
137
- * the second match. Setting this value to 2 will skip the first 2
138
- * matches and so on.
139
- * @param bool $allowCurrentDate (optional) Set to TRUE to return the
140
- * current date if it matches the cron expression
141
- *
142
- * @return DateTime
143
- * @throws RuntimeException on too many iterations
144
- */
145
- public function getNextRunDate( $currentTime = 'now', $nth = 0, $allowCurrentDate = false ) {
146
- return $this->getRunDate( $currentTime, $nth, false, $allowCurrentDate );
147
- }
148
-
149
- /**
150
- * Get a previous run date relative to the current date or a specific date
151
- *
152
- * @param string|DateTime $currentTime (optional) Relative calculation date
153
- * @param int $nth (optional) Number of matches to skip before returning
154
- * @param bool $allowCurrentDate (optional) Set to TRUE to return the
155
- * current date if it matches the cron expression
156
- *
157
- * @return DateTime
158
- * @throws RuntimeException on too many iterations
159
- * @see CronExpression::getNextRunDate
160
- */
161
- public function getPreviousRunDate( $currentTime = 'now', $nth = 0, $allowCurrentDate = false ) {
162
- return $this->getRunDate( $currentTime, $nth, true, $allowCurrentDate );
163
- }
164
-
165
- /**
166
- * Get multiple run dates starting at the current date or a specific date
167
- *
168
- * @param int $total Set the total number of dates to calculate
169
- * @param string|DateTime $currentTime (optional) Relative calculation date
170
- * @param bool $invert (optional) Set to TRUE to retrieve previous dates
171
- * @param bool $allowCurrentDate (optional) Set to TRUE to return the
172
- * current date if it matches the cron expression
173
- *
174
- * @return array Returns an array of run dates
175
- */
176
- public function getMultipleRunDates( $total, $currentTime = 'now', $invert = false, $allowCurrentDate = false ) {
177
- $matches = array();
178
- for ( $i = 0; $i < max( 0, $total ); $i++ ) {
179
- $matches[] = $this->getRunDate( $currentTime, $i, $invert, $allowCurrentDate );
180
- }
181
-
182
- return $matches;
183
- }
184
-
185
- /**
186
- * Get all or part of the CRON expression
187
- *
188
- * @param string $part (optional) Specify the part to retrieve or NULL to
189
- * get the full cron schedule string.
190
- *
191
- * @return string|null Returns the CRON expression, a part of the
192
- * CRON expression, or NULL if the part was specified but not found
193
- */
194
- public function getExpression( $part = null ) {
195
- if ( null === $part ) {
196
- return implode( ' ', $this->cronParts );
197
- } elseif ( array_key_exists( $part, $this->cronParts ) ) {
198
- return $this->cronParts[ $part ];
199
- }
200
-
201
- return null;
202
- }
203
-
204
- /**
205
- * Helper method to output the full expression.
206
- *
207
- * @return string Full CRON expression
208
- */
209
- public function __toString() {
210
- return $this->getExpression();
211
- }
212
-
213
- /**
214
- * Determine if the cron is due to run based on the current date or a
215
- * specific date. This method assumes that the current number of
216
- * seconds are irrelevant, and should be called once per minute.
217
- *
218
- * @param string|DateTime $currentTime (optional) Relative calculation date
219
- *
220
- * @return bool Returns TRUE if the cron is due to run or FALSE if not
221
- */
222
- public function isDue( $currentTime = 'now' ) {
223
- if ( 'now' === $currentTime ) {
224
- $currentDate = date( 'Y-m-d H:i' );
225
- $currentTime = strtotime( $currentDate );
226
- } elseif ( $currentTime instanceof DateTime ) {
227
- $currentDate = $currentTime->format( 'Y-m-d H:i' );
228
- $currentTime = strtotime( $currentDate );
229
- } else {
230
- $currentTime = new DateTime( $currentTime );
231
- $currentTime->setTime( $currentTime->format( 'H' ), $currentTime->format( 'i' ), 0 );
232
- $currentDate = $currentTime->format( 'Y-m-d H:i' );
233
- $currentTime = (int) ( $currentTime->getTimestamp() );
234
- }
235
-
236
- return $this->getNextRunDate( $currentDate, 0, true )->getTimestamp() == $currentTime;
237
- }
238
-
239
- /**
240
- * Get the next or previous run date of the expression relative to a date
241
- *
242
- * @param string|DateTime $currentTime (optional) Relative calculation date
243
- * @param int $nth (optional) Number of matches to skip before returning
244
- * @param bool $invert (optional) Set to TRUE to go backwards in time
245
- * @param bool $allowCurrentDate (optional) Set to TRUE to return the
246
- * current date if it matches the cron expression
247
- *
248
- * @return DateTime
249
- * @throws RuntimeException on too many iterations
250
- */
251
- protected function getRunDate( $currentTime = null, $nth = 0, $invert = false, $allowCurrentDate = false ) {
252
- if ( $currentTime instanceof DateTime ) {
253
- $currentDate = $currentTime;
254
- } else {
255
- $currentDate = new DateTime( $currentTime ? $currentTime : 'now' );
256
- $currentDate->setTimezone( new DateTimeZone( date_default_timezone_get() ) );
257
- }
258
-
259
- $currentDate->setTime( $currentDate->format( 'H' ), $currentDate->format( 'i' ), 0 );
260
- $nextRun = clone $currentDate;
261
- $nth = (int) $nth;
262
-
263
- // Set a hard limit to bail on an impossible date
264
- for ( $i = 0; $i < 1000; $i++ ) {
265
-
266
- foreach ( self::$order as $position ) {
267
- $part = $this->getExpression( $position );
268
- if ( null === $part ) {
269
- continue;
270
- }
271
-
272
- $satisfied = false;
273
- // Get the field object used to validate this part
274
- $field = $this->fieldFactory->getField( $position );
275
- // Check if this is singular or a list
276
- if ( strpos( $part, ',' ) === false ) {
277
- $satisfied = $field->isSatisfiedBy( $nextRun, $part );
278
- } else {
279
- foreach ( array_map( 'trim', explode( ',', $part ) ) as $listPart ) {
280
- if ( $field->isSatisfiedBy( $nextRun, $listPart ) ) {
281
- $satisfied = true;
282
- break;
283
- }
284
- }
285
- }
286
-
287
- // If the field is not satisfied, then start over
288
- if ( ! $satisfied ) {
289
- $field->increment( $nextRun, $invert );
290
- continue 2;
291
- }
292
- }
293
-
294
- // Skip this match if needed
295
- if ( ( ! $allowCurrentDate && $nextRun == $currentDate ) || --$nth > -1 ) {
296
- $this->fieldFactory->getField( 0 )->increment( $nextRun, $invert );
297
- continue;
298
- }
299
-
300
- return $nextRun;
301
- }
302
-
303
- // @codeCoverageIgnoreStart
304
- throw new RuntimeException( 'Impossible CRON expression' );
305
- // @codeCoverageIgnoreEnd
306
- }
 
 
 
 
 
 
 
 
 
 
 
307
  }
13
  * @author Michael Dowling <mtdowling@gmail.com>
14
  * @link http://en.wikipedia.org/wiki/Cron
15
  */
16
+ class CronExpression
17
+ {
18
+ const MINUTE = 0;
19
+ const HOUR = 1;
20
+ const DAY = 2;
21
+ const MONTH = 3;
22
+ const WEEKDAY = 4;
23
+ const YEAR = 5;
24
+
25
+ /**
26
+ * @var array CRON expression parts
27
+ */
28
+ private $cronParts;
29
+
30
+ /**
31
+ * @var CronExpression_FieldFactory CRON field factory
32
+ */
33
+ private $fieldFactory;
34
+
35
+ /**
36
+ * @var array Order in which to test of cron parts
37
+ */
38
+ private static $order = array(self::YEAR, self::MONTH, self::DAY, self::WEEKDAY, self::HOUR, self::MINUTE);
39
+
40
+ /**
41
+ * Factory method to create a new CronExpression.
42
+ *
43
+ * @param string $expression The CRON expression to create. There are
44
+ * several special predefined values which can be used to substitute the
45
+ * CRON expression:
46
+ *
47
+ * @yearly, @annually) - Run once a year, midnight, Jan. 1 - 0 0 1 1 *
48
+ * @monthly - Run once a month, midnight, first of month - 0 0 1 * *
49
+ * @weekly - Run once a week, midnight on Sun - 0 0 * * 0
50
+ * @daily - Run once a day, midnight - 0 0 * * *
51
+ * @hourly - Run once an hour, first minute - 0 * * * *
52
+ *
53
+ *@param CronExpression_FieldFactory $fieldFactory (optional) Field factory to use
54
+ *
55
+ * @return CronExpression
56
+ */
57
+ public static function factory($expression, CronExpression_FieldFactory $fieldFactory = null)
58
+ {
59
+ $mappings = array(
60
+ '@yearly' => '0 0 1 1 *',
61
+ '@annually' => '0 0 1 1 *',
62
+ '@monthly' => '0 0 1 * *',
63
+ '@weekly' => '0 0 * * 0',
64
+ '@daily' => '0 0 * * *',
65
+ '@hourly' => '0 * * * *'
66
+ );
67
+
68
+ if (isset($mappings[$expression])) {
69
+ $expression = $mappings[$expression];
70
+ }
71
+
72
+ return new self($expression, $fieldFactory ? $fieldFactory : new CronExpression_FieldFactory());
73
+ }
74
+
75
+ /**
76
+ * Parse a CRON expression
77
+ *
78
+ * @param string $expression CRON expression (e.g. '8 * * * *')
79
+ * @param CronExpression_FieldFactory $fieldFactory Factory to create cron fields
80
+ */
81
+ public function __construct($expression, CronExpression_FieldFactory $fieldFactory)
82
+ {
83
+ $this->fieldFactory = $fieldFactory;
84
+ $this->setExpression($expression);
85
+ }
86
+
87
+ /**
88
+ * Set or change the CRON expression
89
+ *
90
+ * @param string $value CRON expression (e.g. 8 * * * *)
91
+ *
92
+ * @return CronExpression
93
+ * @throws InvalidArgumentException if not a valid CRON expression
94
+ */
95
+ public function setExpression($value)
96
+ {
97
+ $this->cronParts = preg_split('/\s/', $value, -1, PREG_SPLIT_NO_EMPTY);
98
+ if (count($this->cronParts) < 5) {
99
+ throw new InvalidArgumentException(
100
+ $value . ' is not a valid CRON expression'
101
+ );
102
+ }
103
+
104
+ foreach ($this->cronParts as $position => $part) {
105
+ $this->setPart($position, $part);
106
+ }
107
+
108
+ return $this;
109
+ }
110
+
111
+ /**
112
+ * Set part of the CRON expression
113
+ *
114
+ * @param int $position The position of the CRON expression to set
115
+ * @param string $value The value to set
116
+ *
117
+ * @return CronExpression
118
+ * @throws InvalidArgumentException if the value is not valid for the part
119
+ */
120
+ public function setPart($position, $value)
121
+ {
122
+ if (!$this->fieldFactory->getField($position)->validate($value)) {
123
+ throw new InvalidArgumentException(
124
+ 'Invalid CRON field value ' . $value . ' as position ' . $position
125
+ );
126
+ }
127
+
128
+ $this->cronParts[$position] = $value;
129
+
130
+ return $this;
131
+ }
132
+
133
+ /**
134
+ * Get a next run date relative to the current date or a specific date
135
+ *
136
+ * @param string|DateTime $currentTime (optional) Relative calculation date
137
+ * @param int $nth (optional) Number of matches to skip before returning a
138
+ * matching next run date. 0, the default, will return the current
139
+ * date and time if the next run date falls on the current date and
140
+ * time. Setting this value to 1 will skip the first match and go to
141
+ * the second match. Setting this value to 2 will skip the first 2
142
+ * matches and so on.
143
+ * @param bool $allowCurrentDate (optional) Set to TRUE to return the
144
+ * current date if it matches the cron expression
145
+ *
146
+ * @return DateTime
147
+ * @throws RuntimeException on too many iterations
148
+ */
149
+ public function getNextRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false)
150
+ {
151
+ return $this->getRunDate($currentTime, $nth, false, $allowCurrentDate);
152
+ }
153
+
154
+ /**
155
+ * Get a previous run date relative to the current date or a specific date
156
+ *
157
+ * @param string|DateTime $currentTime (optional) Relative calculation date
158
+ * @param int $nth (optional) Number of matches to skip before returning
159
+ * @param bool $allowCurrentDate (optional) Set to TRUE to return the
160
+ * current date if it matches the cron expression
161
+ *
162
+ * @return DateTime
163
+ * @throws RuntimeException on too many iterations
164
+ * @see CronExpression::getNextRunDate
165
+ */
166
+ public function getPreviousRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false)
167
+ {
168
+ return $this->getRunDate($currentTime, $nth, true, $allowCurrentDate);
169
+ }
170
+
171
+ /**
172
+ * Get multiple run dates starting at the current date or a specific date
173
+ *
174
+ * @param int $total Set the total number of dates to calculate
175
+ * @param string|DateTime $currentTime (optional) Relative calculation date
176
+ * @param bool $invert (optional) Set to TRUE to retrieve previous dates
177
+ * @param bool $allowCurrentDate (optional) Set to TRUE to return the
178
+ * current date if it matches the cron expression
179
+ *
180
+ * @return array Returns an array of run dates
181
+ */
182
+ public function getMultipleRunDates($total, $currentTime = 'now', $invert = false, $allowCurrentDate = false)
183
+ {
184
+ $matches = array();
185
+ for ($i = 0; $i < max(0, $total); $i++) {
186
+ $matches[] = $this->getRunDate($currentTime, $i, $invert, $allowCurrentDate);
187
+ }
188
+
189
+ return $matches;
190
+ }
191
+
192
+ /**
193
+ * Get all or part of the CRON expression
194
+ *
195
+ * @param string $part (optional) Specify the part to retrieve or NULL to
196
+ * get the full cron schedule string.
197
+ *
198
+ * @return string|null Returns the CRON expression, a part of the
199
+ * CRON expression, or NULL if the part was specified but not found
200
+ */
201
+ public function getExpression($part = null)
202
+ {
203
+ if (null === $part) {
204
+ return implode(' ', $this->cronParts);
205
+ } elseif (array_key_exists($part, $this->cronParts)) {
206
+ return $this->cronParts[$part];
207
+ }
208
+
209
+ return null;
210
+ }
211
+
212
+ /**
213
+ * Helper method to output the full expression.
214
+ *
215
+ * @return string Full CRON expression
216
+ */
217
+ public function __toString()
218
+ {
219
+ return $this->getExpression();
220
+ }
221
+
222
+ /**
223
+ * Determine if the cron is due to run based on the current date or a
224
+ * specific date. This method assumes that the current number of
225
+ * seconds are irrelevant, and should be called once per minute.
226
+ *
227
+ * @param string|DateTime $currentTime (optional) Relative calculation date
228
+ *
229
+ * @return bool Returns TRUE if the cron is due to run or FALSE if not
230
+ */
231
+ public function isDue($currentTime = 'now')
232
+ {
233
+ if ('now' === $currentTime) {
234
+ $currentDate = date('Y-m-d H:i');
235
+ $currentTime = strtotime($currentDate);
236
+ } elseif ($currentTime instanceof DateTime) {
237
+ $currentDate = $currentTime->format('Y-m-d H:i');
238
+ $currentTime = strtotime($currentDate);
239
+ } else {
240
+ $currentTime = new DateTime($currentTime);
241
+ $currentTime->setTime($currentTime->format('H'), $currentTime->format('i'), 0);
242
+ $currentDate = $currentTime->format('Y-m-d H:i');
243
+ $currentTime = (int)($currentTime->getTimestamp());
244
+ }
245
+
246
+ return $this->getNextRunDate($currentDate, 0, true)->getTimestamp() == $currentTime;
247
+ }
248
+
249
+ /**
250
+ * Get the next or previous run date of the expression relative to a date
251
+ *
252
+ * @param string|DateTime $currentTime (optional) Relative calculation date
253
+ * @param int $nth (optional) Number of matches to skip before returning
254
+ * @param bool $invert (optional) Set to TRUE to go backwards in time
255
+ * @param bool $allowCurrentDate (optional) Set to TRUE to return the
256
+ * current date if it matches the cron expression
257
+ *
258
+ * @return DateTime
259
+ * @throws RuntimeException on too many iterations
260
+ */
261
+ protected function getRunDate($currentTime = null, $nth = 0, $invert = false, $allowCurrentDate = false)
262
+ {
263
+ if ($currentTime instanceof DateTime) {
264
+ $currentDate = $currentTime;
265
+ } else {
266
+ $currentDate = new DateTime($currentTime ? $currentTime : 'now');
267
+ $currentDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
268
+ }
269
+
270
+ $currentDate->setTime($currentDate->format('H'), $currentDate->format('i'), 0);
271
+ $nextRun = clone $currentDate;
272
+ $nth = (int) $nth;
273
+
274
+ // Set a hard limit to bail on an impossible date
275
+ for ($i = 0; $i < 1000; $i++) {
276
+
277
+ foreach (self::$order as $position) {
278
+ $part = $this->getExpression($position);
279
+ if (null === $part) {
280
+ continue;
281
+ }
282
+
283
+ $satisfied = false;
284
+ // Get the field object used to validate this part
285
+ $field = $this->fieldFactory->getField($position);
286
+ // Check if this is singular or a list
287
+ if (strpos($part, ',') === false) {
288
+ $satisfied = $field->isSatisfiedBy($nextRun, $part);
289
+ } else {
290
+ foreach (array_map('trim', explode(',', $part)) as $listPart) {
291
+ if ($field->isSatisfiedBy($nextRun, $listPart)) {
292
+ $satisfied = true;
293
+ break;
294
+ }
295
+ }
296
+ }
297
+
298
+ // If the field is not satisfied, then start over
299
+ if (!$satisfied) {
300
+ $field->increment($nextRun, $invert);
301
+ continue 2;
302
+ }
303
+ }
304
+
305
+ // Skip this match if needed
306
+ if ((!$allowCurrentDate && $nextRun == $currentDate) || --$nth > -1) {
307
+ $this->fieldFactory->getField(0)->increment($nextRun, $invert);
308
+ continue;
309
+ }
310
+
311
+ return $nextRun;
312
+ }
313
+
314
+ // @codeCoverageIgnoreStart
315
+ throw new RuntimeException('Impossible CRON expression');
316
+ // @codeCoverageIgnoreEnd
317
+ }
318
  }
lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_AbstractField.php CHANGED
@@ -5,91 +5,96 @@
5
  *
6
  * @author Michael Dowling <mtdowling@gmail.com>
7
  */
8
- abstract class CronExpression_AbstractField implements CronExpression_FieldInterface {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- /**
11
- * Check to see if a field is satisfied by a value
12
- *
13
- * @param string $dateValue Date value to check
14
- * @param string $value Value to test
15
- *
16
- * @return bool
17
- */
18
- public function isSatisfied( $dateValue, $value ) {
19
- if ( $this->isIncrementsOfRanges( $value ) ) {
20
- return $this->isInIncrementsOfRanges( $dateValue, $value );
21
- } elseif ( $this->isRange( $value ) ) {
22
- return $this->isInRange( $dateValue, $value );
23
- }
24
 
25
- return $value == '*' || $dateValue == $value;
26
- }
 
 
 
 
 
 
 
 
 
27
 
28
- /**
29
- * Check if a value is a range
30
- *
31
- * @param string $value Value to test
32
- *
33
- * @return bool
34
- */
35
- public function isRange( $value ) {
36
- return strpos( $value, '-' ) !== false;
37
- }
 
38
 
39
- /**
40
- * Check if a value is an increments of ranges
41
- *
42
- * @param string $value Value to test
43
- *
44
- * @return bool
45
- */
46
- public function isIncrementsOfRanges( $value ) {
47
- return strpos( $value, '/' ) !== false;
48
- }
 
49
 
50
- /**
51
- * Test if a value is within a range
52
- *
53
- * @param string $dateValue Set date value
54
- * @param string $value Value to test
55
- *
56
- * @return bool
57
- */
58
- public function isInRange( $dateValue, $value ) {
59
- $parts = array_map( 'trim', explode( '-', $value, 2 ) );
60
 
61
- return $dateValue >= $parts[0] && $dateValue <= $parts[1];
62
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- /**
65
- * Test if a value is within an increments of ranges (offset[-to]/step size)
66
- *
67
- * @param string $dateValue Set date value
68
- * @param string $value Value to test
69
- *
70
- * @return bool
71
- */
72
- public function isInIncrementsOfRanges( $dateValue, $value ) {
73
- $parts = array_map( 'trim', explode( '/', $value, 2 ) );
74
- $stepSize = isset( $parts[1] ) ? $parts[1] : 0;
75
- if ( $parts[0] == '*' || $parts[0] === '0' ) {
76
- return (int) $dateValue % $stepSize == 0;
77
- }
78
 
79
- $range = explode( '-', $parts[0], 2 );
80
- $offset = $range[0];
81
- $to = isset( $range[1] ) ? $range[1] : $dateValue;
82
- // Ensure that the date value is within the range
83
- if ( $dateValue < $offset || $dateValue > $to ) {
84
- return false;
85
- }
86
 
87
- for ( $i = $offset; $i <= $to; $i += $stepSize ) {
88
- if ( $i == $dateValue ) {
89
- return true;
90
- }
91
- }
92
-
93
- return false;
94
- }
95
  }
5
  *
6
  * @author Michael Dowling <mtdowling@gmail.com>
7
  */
8
+ abstract class CronExpression_AbstractField implements CronExpression_FieldInterface
9
+ {
10
+ /**
11
+ * Check to see if a field is satisfied by a value
12
+ *
13
+ * @param string $dateValue Date value to check
14
+ * @param string $value Value to test
15
+ *
16
+ * @return bool
17
+ */
18
+ public function isSatisfied($dateValue, $value)
19
+ {
20
+ if ($this->isIncrementsOfRanges($value)) {
21
+ return $this->isInIncrementsOfRanges($dateValue, $value);
22
+ } elseif ($this->isRange($value)) {
23
+ return $this->isInRange($dateValue, $value);
24
+ }
25
 
26
+ return $value == '*' || $dateValue == $value;
27
+ }
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ /**
30
+ * Check if a value is a range
31
+ *
32
+ * @param string $value Value to test
33
+ *
34
+ * @return bool
35
+ */
36
+ public function isRange($value)
37
+ {
38
+ return strpos($value, '-') !== false;
39
+ }
40
 
41
+ /**
42
+ * Check if a value is an increments of ranges
43
+ *
44
+ * @param string $value Value to test
45
+ *
46
+ * @return bool
47
+ */
48
+ public function isIncrementsOfRanges($value)
49
+ {
50
+ return strpos($value, '/') !== false;
51
+ }
52
 
53
+ /**
54
+ * Test if a value is within a range
55
+ *
56
+ * @param string $dateValue Set date value
57
+ * @param string $value Value to test
58
+ *
59
+ * @return bool
60
+ */
61
+ public function isInRange($dateValue, $value)
62
+ {
63
+ $parts = array_map('trim', explode('-', $value, 2));
64
 
65
+ return $dateValue >= $parts[0] && $dateValue <= $parts[1];
66
+ }
 
 
 
 
 
 
 
 
67
 
68
+ /**
69
+ * Test if a value is within an increments of ranges (offset[-to]/step size)
70
+ *
71
+ * @param string $dateValue Set date value
72
+ * @param string $value Value to test
73
+ *
74
+ * @return bool
75
+ */
76
+ public function isInIncrementsOfRanges($dateValue, $value)
77
+ {
78
+ $parts = array_map('trim', explode('/', $value, 2));
79
+ $stepSize = isset($parts[1]) ? $parts[1] : 0;
80
+ if ($parts[0] == '*' || $parts[0] === '0') {
81
+ return (int) $dateValue % $stepSize == 0;
82
+ }
83
 
84
+ $range = explode('-', $parts[0], 2);
85
+ $offset = $range[0];
86
+ $to = isset($range[1]) ? $range[1] : $dateValue;
87
+ // Ensure that the date value is within the range
88
+ if ($dateValue < $offset || $dateValue > $to) {
89
+ return false;
90
+ }
 
 
 
 
 
 
 
91
 
92
+ for ($i = $offset; $i <= $to; $i+= $stepSize) {
93
+ if ($i == $dateValue) {
94
+ return true;
95
+ }
96
+ }
 
 
97
 
98
+ return false;
99
+ }
 
 
 
 
 
 
100
  }
lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_DayOfMonthField.php CHANGED
@@ -18,89 +18,93 @@
18
  *
19
  * @author Michael Dowling <mtdowling@gmail.com>
20
  */
21
- class CronExpression_DayOfMonthField extends CronExpression_AbstractField {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
- /**
24
- * Get the nearest day of the week for a given day in a month
25
- *
26
- * @param int $currentYear Current year
27
- * @param int $currentMonth Current month
28
- * @param int $targetDay Target day of the month
29
- *
30
- * @return DateTime Returns the nearest date
31
- */
32
- private static function getNearestWeekday( $currentYear, $currentMonth, $targetDay ) {
33
- $tday = str_pad( $targetDay, 2, '0', STR_PAD_LEFT );
34
- $target = new DateTime( "$currentYear-$currentMonth-$tday" );
35
- $currentWeekday = (int) $target->format( 'N' );
36
 
37
- if ( $currentWeekday < 6 ) {
38
- return $target;
39
- }
40
 
41
- $lastDayOfMonth = $target->format( 't' );
 
 
 
 
 
 
 
 
 
42
 
43
- foreach ( array( -1, 1, -2, 2 ) as $i ) {
44
- $adjusted = $targetDay + $i;
45
- if ( $adjusted > 0 && $adjusted <= $lastDayOfMonth ) {
46
- $target->setDate( $currentYear, $currentMonth, $adjusted );
47
- if ( $target->format( 'N' ) < 6 && $target->format( 'm' ) == $currentMonth ) {
48
- return $target;
49
- }
50
- }
51
- }
52
- }
53
 
54
- /**
55
- * {@inheritdoc}
56
- */
57
- public function isSatisfiedBy( DateTime $date, $value ) {
58
- // ? states that the field value is to be skipped
59
- if ( $value == '?' ) {
60
- return true;
61
- }
62
 
63
- $fieldValue = $date->format( 'd' );
 
 
 
64
 
65
- // Check to see if this is the last day of the month
66
- if ( $value == 'L' ) {
67
- return $fieldValue == $date->format( 't' );
68
- }
 
 
 
 
 
 
 
69
 
70
- // Check to see if this is the nearest weekday to a particular value
71
- if ( strpos( $value, 'W' ) ) {
72
- // Parse the target day
73
- $targetDay = substr( $value, 0, strpos( $value, 'W' ) );
74
- // Find out if the current day is the nearest day of the week
75
- return $date->format( 'j' ) == self::getNearestWeekday(
76
- $date->format( 'Y' ),
77
- $date->format( 'm' ),
78
- $targetDay
79
- )->format( 'j' );
80
- }
81
 
82
- return $this->isSatisfied( $date->format( 'd' ), $value );
83
- }
 
 
 
 
 
 
 
 
 
 
84
 
85
- /**
86
- * {@inheritdoc}
87
- */
88
- public function increment( DateTime $date, $invert = false ) {
89
- if ( $invert ) {
90
- $date->modify( 'previous day' );
91
- $date->setTime( 23, 59 );
92
- } else {
93
- $date->modify( 'next day' );
94
- $date->setTime( 0, 0 );
95
- }
96
 
97
- return $this;
98
- }
99
-
100
- /**
101
- * {@inheritdoc}
102
- */
103
- public function validate( $value ) {
104
- return (bool) preg_match( '/[\*,\/\-\?LW0-9A-Za-z]+/', $value );
105
- }
106
  }
18
  *
19
  * @author Michael Dowling <mtdowling@gmail.com>
20
  */
21
+ class CronExpression_DayOfMonthField extends CronExpression_AbstractField
22
+ {
23
+ /**
24
+ * Get the nearest day of the week for a given day in a month
25
+ *
26
+ * @param int $currentYear Current year
27
+ * @param int $currentMonth Current month
28
+ * @param int $targetDay Target day of the month
29
+ *
30
+ * @return DateTime Returns the nearest date
31
+ */
32
+ private static function getNearestWeekday($currentYear, $currentMonth, $targetDay)
33
+ {
34
+ $tday = str_pad($targetDay, 2, '0', STR_PAD_LEFT);
35
+ $target = new DateTime("$currentYear-$currentMonth-$tday");
36
+ $currentWeekday = (int) $target->format('N');
37
 
38
+ if ($currentWeekday < 6) {
39
+ return $target;
40
+ }
 
 
 
 
 
 
 
 
 
 
41
 
42
+ $lastDayOfMonth = $target->format('t');
 
 
43
 
44
+ foreach (array(-1, 1, -2, 2) as $i) {
45
+ $adjusted = $targetDay + $i;
46
+ if ($adjusted > 0 && $adjusted <= $lastDayOfMonth) {
47
+ $target->setDate($currentYear, $currentMonth, $adjusted);
48
+ if ($target->format('N') < 6 && $target->format('m') == $currentMonth) {
49
+ return $target;
50
+ }
51
+ }
52
+ }
53
+ }
54
 
55
+ /**
56
+ * {@inheritdoc}
57
+ */
58
+ public function isSatisfiedBy(DateTime $date, $value)
59
+ {
60
+ // ? states that the field value is to be skipped
61
+ if ($value == '?') {
62
+ return true;
63
+ }
 
64
 
65
+ $fieldValue = $date->format('d');
 
 
 
 
 
 
 
66
 
67
+ // Check to see if this is the last day of the month
68
+ if ($value == 'L') {
69
+ return $fieldValue == $date->format('t');
70
+ }
71
 
72
+ // Check to see if this is the nearest weekday to a particular value
73
+ if (strpos($value, 'W')) {
74
+ // Parse the target day
75
+ $targetDay = substr($value, 0, strpos($value, 'W'));
76
+ // Find out if the current day is the nearest day of the week
77
+ return $date->format('j') == self::getNearestWeekday(
78
+ $date->format('Y'),
79
+ $date->format('m'),
80
+ $targetDay
81
+ )->format('j');
82
+ }
83
 
84
+ return $this->isSatisfied($date->format('d'), $value);
85
+ }
 
 
 
 
 
 
 
 
 
86
 
87
+ /**
88
+ * {@inheritdoc}
89
+ */
90
+ public function increment(DateTime $date, $invert = false)
91
+ {
92
+ if ($invert) {
93
+ $date->modify('previous day');
94
+ $date->setTime(23, 59);
95
+ } else {
96
+ $date->modify('next day');
97
+ $date->setTime(0, 0);
98
+ }
99
 
100
+ return $this;
101
+ }
 
 
 
 
 
 
 
 
 
102
 
103
+ /**
104
+ * {@inheritdoc}
105
+ */
106
+ public function validate($value)
107
+ {
108
+ return (bool) preg_match('/[\*,\/\-\?LW0-9A-Za-z]+/', $value);
109
+ }
 
 
110
  }
lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_DayOfWeekField.php CHANGED
@@ -15,107 +15,110 @@
15
  *
16
  * @author Michael Dowling <mtdowling@gmail.com>
17
  */
18
- class CronExpression_DayOfWeekField extends CronExpression_AbstractField {
 
 
 
 
 
 
 
 
 
19
 
20
- /**
21
- * {@inheritdoc}
22
- */
23
- public function isSatisfiedBy( DateTime $date, $value ) {
24
- if ( $value == '?' ) {
25
- return true;
26
- }
27
 
28
- // Convert text day of the week values to integers
29
- $value = str_ireplace(
30
- array( 'SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT' ),
31
- range( 0, 6 ),
32
- $value
33
- );
34
 
35
- $currentYear = $date->format( 'Y' );
36
- $currentMonth = $date->format( 'm' );
37
- $lastDayOfMonth = $date->format( 't' );
 
 
 
 
 
38
 
39
- // Find out if this is the last specific weekday of the month
40
- if ( strpos( $value, 'L' ) ) {
41
- $weekday = str_replace( '7', '0', substr( $value, 0, strpos( $value, 'L' ) ) );
42
- $tdate = clone $date;
43
- $tdate->setDate( $currentYear, $currentMonth, $lastDayOfMonth );
44
- while ( $tdate->format( 'w' ) != $weekday ) {
45
- $tdate->setDate( $currentYear, $currentMonth, --$lastDayOfMonth );
46
- }
47
 
48
- return $date->format( 'j' ) == $lastDayOfMonth;
49
- }
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- // Handle # hash tokens
52
- if ( strpos( $value, '#' ) ) {
53
- list($weekday, $nth) = explode( '#', $value );
54
- // Validate the hash fields
55
- if ( $weekday < 1 || $weekday > 5 ) {
56
- throw new InvalidArgumentException( "Weekday must be a value between 1 and 5. {$weekday} given" );
57
- }
58
- if ( $nth > 5 ) {
59
- throw new InvalidArgumentException( 'There are never more than 5 of a given weekday in a month' );
60
- }
61
- // The current weekday must match the targeted weekday to proceed
62
- if ( $date->format( 'N' ) != $weekday ) {
63
- return false;
64
- }
65
 
66
- $tdate = clone $date;
67
- $tdate->setDate( $currentYear, $currentMonth, 1 );
68
- $dayCount = 0;
69
- $currentDay = 1;
70
- while ( $currentDay < $lastDayOfMonth + 1 ) {
71
- if ( $tdate->format( 'N' ) == $weekday ) {
72
- if ( ++$dayCount >= $nth ) {
73
- break;
74
- }
75
- }
76
- $tdate->setDate( $currentYear, $currentMonth, ++$currentDay );
77
- }
78
 
79
- return $date->format( 'j' ) == $currentDay;
80
- }
 
 
 
 
 
 
 
 
81
 
82
- // Handle day of the week values
83
- if ( strpos( $value, '-' ) ) {
84
- $parts = explode( '-', $value );
85
- if ( $parts[0] == '7' ) {
86
- $parts[0] = '0';
87
- } elseif ( $parts[1] == '0' ) {
88
- $parts[1] = '7';
89
- }
90
- $value = implode( '-', $parts );
91
- }
92
 
93
- // Test to see which Sunday to use -- 0 == 7 == Sunday
94
- $format = in_array( 7, str_split( $value ) ) ? 'N' : 'w';
95
- $fieldValue = $date->format( $format );
96
 
97
- return $this->isSatisfied( $fieldValue, $value );
98
- }
 
 
 
 
 
 
 
 
 
 
99
 
100
- /**
101
- * {@inheritdoc}
102
- */
103
- public function increment( DateTime $date, $invert = false ) {
104
- if ( $invert ) {
105
- $date->modify( '-1 day' );
106
- $date->setTime( 23, 59, 0 );
107
- } else {
108
- $date->modify( '+1 day' );
109
- $date->setTime( 0, 0, 0 );
110
- }
111
 
112
- return $this;
113
- }
114
-
115
- /**
116
- * {@inheritdoc}
117
- */
118
- public function validate( $value ) {
119
- return (bool) preg_match( '/[\*,\/\-0-9A-Z]+/', $value );
120
- }
121
  }
15
  *
16
  * @author Michael Dowling <mtdowling@gmail.com>
17
  */
18
+ class CronExpression_DayOfWeekField extends CronExpression_AbstractField
19
+ {
20
+ /**
21
+ * {@inheritdoc}
22
+ */
23
+ public function isSatisfiedBy(DateTime $date, $value)
24
+ {
25
+ if ($value == '?') {
26
+ return true;
27
+ }
28
 
29
+ // Convert text day of the week values to integers
30
+ $value = str_ireplace(
31
+ array('SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'),
32
+ range(0, 6),
33
+ $value
34
+ );
 
35
 
36
+ $currentYear = $date->format('Y');
37
+ $currentMonth = $date->format('m');
38
+ $lastDayOfMonth = $date->format('t');
 
 
 
39
 
40
+ // Find out if this is the last specific weekday of the month
41
+ if (strpos($value, 'L')) {
42
+ $weekday = str_replace('7', '0', substr($value, 0, strpos($value, 'L')));
43
+ $tdate = clone $date;
44
+ $tdate->setDate($currentYear, $currentMonth, $lastDayOfMonth);
45
+ while ($tdate->format('w') != $weekday) {
46
+ $tdate->setDate($currentYear, $currentMonth, --$lastDayOfMonth);
47
+ }
48
 
49
+ return $date->format('j') == $lastDayOfMonth;
50
+ }
 
 
 
 
 
 
51
 
52
+ // Handle # hash tokens
53
+ if (strpos($value, '#')) {
54
+ list($weekday, $nth) = explode('#', $value);
55
+ // Validate the hash fields
56
+ if ($weekday < 1 || $weekday > 5) {
57
+ throw new InvalidArgumentException("Weekday must be a value between 1 and 5. {$weekday} given");
58
+ }
59
+ if ($nth > 5) {
60
+ throw new InvalidArgumentException('There are never more than 5 of a given weekday in a month');
61
+ }
62
+ // The current weekday must match the targeted weekday to proceed
63
+ if ($date->format('N') != $weekday) {
64
+ return false;
65
+ }
66
 
67
+ $tdate = clone $date;
68
+ $tdate->setDate($currentYear, $currentMonth, 1);
69
+ $dayCount = 0;
70
+ $currentDay = 1;
71
+ while ($currentDay < $lastDayOfMonth + 1) {
72
+ if ($tdate->format('N') == $weekday) {
73
+ if (++$dayCount >= $nth) {
74
+ break;
75
+ }
76
+ }
77
+ $tdate->setDate($currentYear, $currentMonth, ++$currentDay);
78
+ }
 
 
79
 
80
+ return $date->format('j') == $currentDay;
81
+ }
 
 
 
 
 
 
 
 
 
 
82
 
83
+ // Handle day of the week values
84
+ if (strpos($value, '-')) {
85
+ $parts = explode('-', $value);
86
+ if ($parts[0] == '7') {
87
+ $parts[0] = '0';
88
+ } elseif ($parts[1] == '0') {
89
+ $parts[1] = '7';
90
+ }
91
+ $value = implode('-', $parts);
92
+ }
93
 
94
+ // Test to see which Sunday to use -- 0 == 7 == Sunday
95
+ $format = in_array(7, str_split($value)) ? 'N' : 'w';
96
+ $fieldValue = $date->format($format);
 
 
 
 
 
 
 
97
 
98
+ return $this->isSatisfied($fieldValue, $value);
99
+ }
 
100
 
101
+ /**
102
+ * {@inheritdoc}
103
+ */
104
+ public function increment(DateTime $date, $invert = false)
105
+ {
106
+ if ($invert) {
107
+ $date->modify('-1 day');
108
+ $date->setTime(23, 59, 0);
109
+ } else {
110
+ $date->modify('+1 day');
111
+ $date->setTime(0, 0, 0);
112
+ }
113
 
114
+ return $this;
115
+ }
 
 
 
 
 
 
 
 
 
116
 
117
+ /**
118
+ * {@inheritdoc}
119
+ */
120
+ public function validate($value)
121
+ {
122
+ return (bool) preg_match('/[\*,\/\-0-9A-Z]+/', $value);
123
+ }
 
 
124
  }
lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_FieldFactory.php CHANGED
@@ -6,49 +6,50 @@
6
  * @author Michael Dowling <mtdowling@gmail.com>
7
  * @link http://en.wikipedia.org/wiki/Cron
8
  */
9
- class CronExpression_FieldFactory {
 
 
 
 
 
10
 
11
- /**
12
- * @var array Cache of instantiated fields
13
- */
14
- private $fields = array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- /**
17
- * Get an instance of a field object for a cron expression position
18
- *
19
- * @param int $position CRON expression position value to retrieve
20
- *
21
- * @return CronExpression_FieldInterface
22
- * @throws InvalidArgumentException if a position is not valid
23
- */
24
- public function getField( $position ) {
25
- if ( ! isset( $this->fields[ $position ] ) ) {
26
- switch ( $position ) {
27
- case 0:
28
- $this->fields[ $position ] = new CronExpression_MinutesField();
29
- break;
30
- case 1:
31
- $this->fields[ $position ] = new CronExpression_HoursField();
32
- break;
33
- case 2:
34
- $this->fields[ $position ] = new CronExpression_DayOfMonthField();
35
- break;
36
- case 3:
37
- $this->fields[ $position ] = new CronExpression_MonthField();
38
- break;
39
- case 4:
40
- $this->fields[ $position ] = new CronExpression_DayOfWeekField();
41
- break;
42
- case 5:
43
- $this->fields[ $position ] = new CronExpression_YearField();
44
- break;
45
- default:
46
- throw new InvalidArgumentException(
47
- $position . ' is not a valid position'
48
- );
49
- }
50
- }
51
-
52
- return $this->fields[ $position ];
53
- }
54
  }
6
  * @author Michael Dowling <mtdowling@gmail.com>
7
  * @link http://en.wikipedia.org/wiki/Cron
8
  */
9
+ class CronExpression_FieldFactory
10
+ {
11
+ /**
12
+ * @var array Cache of instantiated fields
13
+ */
14
+ private $fields = array();
15
 
16
+ /**
17
+ * Get an instance of a field object for a cron expression position
18
+ *
19
+ * @param int $position CRON expression position value to retrieve
20
+ *
21
+ * @return CronExpression_FieldInterface
22
+ * @throws InvalidArgumentException if a position is not valid
23
+ */
24
+ public function getField($position)
25
+ {
26
+ if (!isset($this->fields[$position])) {
27
+ switch ($position) {
28
+ case 0:
29
+ $this->fields[$position] = new CronExpression_MinutesField();
30
+ break;
31
+ case 1:
32
+ $this->fields[$position] = new CronExpression_HoursField();
33
+ break;
34
+ case 2:
35
+ $this->fields[$position] = new CronExpression_DayOfMonthField();
36
+ break;
37
+ case 3:
38
+ $this->fields[$position] = new CronExpression_MonthField();
39
+ break;
40
+ case 4:
41
+ $this->fields[$position] = new CronExpression_DayOfWeekField();
42
+ break;
43
+ case 5:
44
+ $this->fields[$position] = new CronExpression_YearField();
45
+ break;
46
+ default:
47
+ throw new InvalidArgumentException(
48
+ $position . ' is not a valid position'
49
+ );
50
+ }
51
+ }
52
 
53
+ return $this->fields[$position];
54
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  }
lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_FieldInterface.php CHANGED
@@ -5,35 +5,35 @@
5
  *
6
  * @author Michael Dowling <mtdowling@gmail.com>
7
  */
8
- interface CronExpression_FieldInterface {
 
 
 
 
 
 
 
 
 
 
9
 
10
- /**
11
- * Check if the respective value of a DateTime field satisfies a CRON exp
12
- *
13
- * @param DateTime $date DateTime object to check
14
- * @param string $value CRON expression to test against
15
- *
16
- * @return bool Returns TRUE if satisfied, FALSE otherwise
17
- */
18
- public function isSatisfiedBy( DateTime $date, $value);
 
19
 
20
- /**
21
- * When a CRON expression is not satisfied, this method is used to increment
22
- * or decrement a DateTime object by the unit of the cron field
23
- *
24
- * @param DateTime $date DateTime object to change
25
- * @param bool $invert (optional) Set to TRUE to decrement
26
- *
27
- * @return CronExpression_FieldInterface
28
- */
29
- public function increment( DateTime $date, $invert = false);
30
-
31
- /**
32
- * Validates a CRON expression for a given field
33
- *
34
- * @param string $value CRON expression value to validate
35
- *
36
- * @return bool Returns TRUE if valid, FALSE otherwise
37
- */
38
- public function validate( $value);
39
  }
5
  *
6
  * @author Michael Dowling <mtdowling@gmail.com>
7
  */
8
+ interface CronExpression_FieldInterface
9
+ {
10
+ /**
11
+ * Check if the respective value of a DateTime field satisfies a CRON exp
12
+ *
13
+ * @param DateTime $date DateTime object to check
14
+ * @param string $value CRON expression to test against
15
+ *
16
+ * @return bool Returns TRUE if satisfied, FALSE otherwise
17
+ */
18
+ public function isSatisfiedBy(DateTime $date, $value);
19
 
20
+ /**
21
+ * When a CRON expression is not satisfied, this method is used to increment
22
+ * or decrement a DateTime object by the unit of the cron field
23
+ *
24
+ * @param DateTime $date DateTime object to change
25
+ * @param bool $invert (optional) Set to TRUE to decrement
26
+ *
27
+ * @return CronExpression_FieldInterface
28
+ */
29
+ public function increment(DateTime $date, $invert = false);
30
 
31
+ /**
32
+ * Validates a CRON expression for a given field
33
+ *
34
+ * @param string $value CRON expression value to validate
35
+ *
36
+ * @return bool Returns TRUE if valid, FALSE otherwise
37
+ */
38
+ public function validate($value);
 
 
 
 
 
 
 
 
 
 
 
39
  }
lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_HoursField.php CHANGED
@@ -5,40 +5,43 @@
5
  *
6
  * @author Michael Dowling <mtdowling@gmail.com>
7
  */
8
- class CronExpression_HoursField extends CronExpression_AbstractField {
 
 
 
 
 
 
 
 
9
 
10
- /**
11
- * {@inheritdoc}
12
- */
13
- public function isSatisfiedBy( DateTime $date, $value ) {
14
- return $this->isSatisfied( $date->format( 'H' ), $value );
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
- /**
18
- * {@inheritdoc}
19
- */
20
- public function increment( DateTime $date, $invert = false ) {
21
- // Change timezone to UTC temporarily. This will
22
- // allow us to go back or forwards and hour even
23
- // if DST will be changed between the hours.
24
- $timezone = $date->getTimezone();
25
- $date->setTimezone( new DateTimeZone( 'UTC' ) );
26
- if ( $invert ) {
27
- $date->modify( '-1 hour' );
28
- $date->setTime( $date->format( 'H' ), 59 );
29
- } else {
30
- $date->modify( '+1 hour' );
31
- $date->setTime( $date->format( 'H' ), 0 );
32
- }
33
- $date->setTimezone( $timezone );
34
 
35
- return $this;
36
- }
37
-
38
- /**
39
- * {@inheritdoc}
40
- */
41
- public function validate( $value ) {
42
- return (bool) preg_match( '/[\*,\/\-0-9]+/', $value );
43
- }
44
  }
5
  *
6
  * @author Michael Dowling <mtdowling@gmail.com>
7
  */
8
+ class CronExpression_HoursField extends CronExpression_AbstractField
9
+ {
10
+ /**
11
+ * {@inheritdoc}
12
+ */
13
+ public function isSatisfiedBy(DateTime $date, $value)
14
+ {
15
+ return $this->isSatisfied($date->format('H'), $value);
16
+ }
17
 
18
+ /**
19
+ * {@inheritdoc}
20
+ */
21
+ public function increment(DateTime $date, $invert = false)
22
+ {
23
+ // Change timezone to UTC temporarily. This will
24
+ // allow us to go back or forwards and hour even
25
+ // if DST will be changed between the hours.
26
+ $timezone = $date->getTimezone();
27
+ $date->setTimezone(new DateTimeZone('UTC'));
28
+ if ($invert) {
29
+ $date->modify('-1 hour');
30
+ $date->setTime($date->format('H'), 59);
31
+ } else {
32
+ $date->modify('+1 hour');
33
+ $date->setTime($date->format('H'), 0);
34
+ }
35
+ $date->setTimezone($timezone);
36
 
37
+ return $this;
38
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
+ /**
41
+ * {@inheritdoc}
42
+ */
43
+ public function validate($value)
44
+ {
45
+ return (bool) preg_match('/[\*,\/\-0-9]+/', $value);
46
+ }
 
 
47
  }
lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_MinutesField.php CHANGED
@@ -5,32 +5,35 @@
5
  *
6
  * @author Michael Dowling <mtdowling@gmail.com>
7
  */
8
- class CronExpression_MinutesField extends CronExpression_AbstractField {
 
 
 
 
 
 
 
 
9
 
10
- /**
11
- * {@inheritdoc}
12
- */
13
- public function isSatisfiedBy( DateTime $date, $value ) {
14
- return $this->isSatisfied( $date->format( 'i' ), $value );
15
- }
 
 
 
 
16
 
17
- /**
18
- * {@inheritdoc}
19
- */
20
- public function increment( DateTime $date, $invert = false ) {
21
- if ( $invert ) {
22
- $date->modify( '-1 minute' );
23
- } else {
24
- $date->modify( '+1 minute' );
25
- }
26
 
27
- return $this;
28
- }
29
-
30
- /**
31
- * {@inheritdoc}
32
- */
33
- public function validate( $value ) {
34
- return (bool) preg_match( '/[\*,\/\-0-9]+/', $value );
35
- }
36
  }
5
  *
6
  * @author Michael Dowling <mtdowling@gmail.com>
7
  */
8
+ class CronExpression_MinutesField extends CronExpression_AbstractField
9
+ {
10
+ /**
11
+ * {@inheritdoc}
12
+ */
13
+ public function isSatisfiedBy(DateTime $date, $value)
14
+ {
15
+ return $this->isSatisfied($date->format('i'), $value);
16
+ }
17
 
18
+ /**
19
+ * {@inheritdoc}
20
+ */
21
+ public function increment(DateTime $date, $invert = false)
22
+ {
23
+ if ($invert) {
24
+ $date->modify('-1 minute');
25
+ } else {
26
+ $date->modify('+1 minute');
27
+ }
28
 
29
+ return $this;
30
+ }
 
 
 
 
 
 
 
31
 
32
+ /**
33
+ * {@inheritdoc}
34
+ */
35
+ public function validate($value)
36
+ {
37
+ return (bool) preg_match('/[\*,\/\-0-9]+/', $value);
38
+ }
 
 
39
  }
lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_MonthField.php CHANGED
@@ -5,58 +5,51 @@
5
  *
6
  * @author Michael Dowling <mtdowling@gmail.com>
7
  */
8
- class CronExpression_MonthField extends CronExpression_AbstractField {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- /**
11
- * {@inheritdoc}
12
- */
13
- public function isSatisfiedBy( DateTime $date, $value ) {
14
- // Convert text month values to integers
15
- $value = str_ireplace(
16
- array(
17
- 'JAN',
18
- 'FEB',
19
- 'MAR',
20
- 'APR',
21
- 'MAY',
22
- 'JUN',
23
- 'JUL',
24
- 'AUG',
25
- 'SEP',
26
- 'OCT',
27
- 'NOV',
28
- 'DEC',
29
- ),
30
- range( 1, 12 ),
31
- $value
32
- );
33
 
34
- return $this->isSatisfied( $date->format( 'm' ), $value );
35
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
- /**
38
- * {@inheritdoc}
39
- */
40
- public function increment( DateTime $date, $invert = false ) {
41
- if ( $invert ) {
42
- // $date->modify('last day of previous month'); // remove for php 5.2 compat
43
- $date->modify( 'previous month' );
44
- $date->modify( $date->format( 'Y-m-t' ) );
45
- $date->setTime( 23, 59 );
46
- } else {
47
- // $date->modify('first day of next month'); // remove for php 5.2 compat
48
- $date->modify( 'next month' );
49
- $date->modify( $date->format( 'Y-m-01' ) );
50
- $date->setTime( 0, 0 );
51
- }
52
 
53
- return $this;
54
- }
55
-
56
- /**
57
- * {@inheritdoc}
58
- */
59
- public function validate( $value ) {
60
- return (bool) preg_match( '/[\*,\/\-0-9A-Z]+/', $value );
61
- }
62
  }
5
  *
6
  * @author Michael Dowling <mtdowling@gmail.com>
7
  */
8
+ class CronExpression_MonthField extends CronExpression_AbstractField
9
+ {
10
+ /**
11
+ * {@inheritdoc}
12
+ */
13
+ public function isSatisfiedBy(DateTime $date, $value)
14
+ {
15
+ // Convert text month values to integers
16
+ $value = str_ireplace(
17
+ array(
18
+ 'JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN',
19
+ 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'
20
+ ),
21
+ range(1, 12),
22
+ $value
23
+ );
24
 
25
+ return $this->isSatisfied($date->format('m'), $value);
26
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
+ /**
29
+ * {@inheritdoc}
30
+ */
31
+ public function increment(DateTime $date, $invert = false)
32
+ {
33
+ if ($invert) {
34
+ // $date->modify('last day of previous month'); // remove for php 5.2 compat
35
+ $date->modify('previous month');
36
+ $date->modify($date->format('Y-m-t'));
37
+ $date->setTime(23, 59);
38
+ } else {
39
+ //$date->modify('first day of next month'); // remove for php 5.2 compat
40
+ $date->modify('next month');
41
+ $date->modify($date->format('Y-m-01'));
42
+ $date->setTime(0, 0);
43
+ }
44
 
45
+ return $this;
46
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
+ /**
49
+ * {@inheritdoc}
50
+ */
51
+ public function validate($value)
52
+ {
53
+ return (bool) preg_match('/[\*,\/\-0-9A-Z]+/', $value);
54
+ }
 
 
55
  }
lite/includes/libraries/action-scheduler/lib/cron-expression/CronExpression_YearField.php CHANGED
@@ -5,36 +5,39 @@
5
  *
6
  * @author Michael Dowling <mtdowling@gmail.com>
7
  */
8
- class CronExpression_YearField extends CronExpression_AbstractField {
 
 
 
 
 
 
 
 
9
 
10
- /**
11
- * {@inheritdoc}
12
- */
13
- public function isSatisfiedBy( DateTime $date, $value ) {
14
- return $this->isSatisfied( $date->format( 'Y' ), $value );
15
- }
 
 
 
 
 
 
 
 
16
 
17
- /**
18
- * {@inheritdoc}
19
- */
20
- public function increment( DateTime $date, $invert = false ) {
21
- if ( $invert ) {
22
- $date->modify( '-1 year' );
23
- $date->setDate( $date->format( 'Y' ), 12, 31 );
24
- $date->setTime( 23, 59, 0 );
25
- } else {
26
- $date->modify( '+1 year' );
27
- $date->setDate( $date->format( 'Y' ), 1, 1 );
28
- $date->setTime( 0, 0, 0 );
29
- }
30
 
31
- return $this;
32
- }
33
-
34
- /**
35
- * {@inheritdoc}
36
- */
37
- public function validate( $value ) {
38
- return (bool) preg_match( '/[\*,\/\-0-9]+/', $value );
39
- }
40
  }
5
  *
6
  * @author Michael Dowling <mtdowling@gmail.com>
7
  */
8
+ class CronExpression_YearField extends CronExpression_AbstractField
9
+ {
10
+ /**
11
+ * {@inheritdoc}
12
+ */
13
+ public function isSatisfiedBy(DateTime $date, $value)
14
+ {
15
+ return $this->isSatisfied($date->format('Y'), $value);
16
+ }
17
 
18
+ /**
19
+ * {@inheritdoc}
20
+ */
21
+ public function increment(DateTime $date, $invert = false)
22
+ {
23
+ if ($invert) {
24
+ $date->modify('-1 year');
25
+ $date->setDate($date->format('Y'), 12, 31);
26
+ $date->setTime(23, 59, 0);
27
+ } else {
28
+ $date->modify('+1 year');
29
+ $date->setDate($date->format('Y'), 1, 1);
30
+ $date->setTime(0, 0, 0);
31
+ }
32
 
33
+ return $this;
34
+ }
 
 
 
 
 
 
 
 
 
 
 
35
 
36
+ /**
37
+ * {@inheritdoc}
38
+ */
39
+ public function validate($value)
40
+ {
41
+ return (bool) preg_match('/[\*,\/\-0-9]+/', $value);
42
+ }
 
 
43
  }
lite/includes/libraries/action-scheduler/readme.txt ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Action Scheduler ===
2
+ Contributors: Automattic, wpmuguru, claudiosanches, peterfabian1000, vedjain, jamosova, obliviousharmony, konamiman, sadowski, royho, barryhughes-1
3
+ Tags: scheduler, cron
4
+ Requires at least: 5.2
5
+ Tested up to: 5.7
6
+ Stable tag: 3.3.0
7
+ License: GPLv3
8
+ Requires PHP: 5.6
9
+
10
+ Action Scheduler - Job Queue for WordPress
11
+
12
+ == Description ==
13
+
14
+ Action Scheduler is a scalable, traceable job queue for background processing large sets of actions in WordPress. It's specially designed to be distributed in WordPress plugins.
15
+
16
+ Action Scheduler works by triggering an action hook to run at some time in the future. Each hook can be scheduled with unique data, to allow callbacks to perform operations on that data. The hook can also be scheduled to run on one or more occassions.
17
+
18
+ Think of it like an extension to `do_action()` which adds the ability to delay and repeat a hook.
19
+
20
+ ## Battle-Tested Background Processing
21
+
22
+ Every month, Action Scheduler processes millions of payments for [Subscriptions](https://woocommerce.com/products/woocommerce-subscriptions/), webhooks for [WooCommerce](https://wordpress.org/plugins/woocommerce/), as well as emails and other events for a range of other plugins.
23
+
24
+ It's been seen on live sites processing queues in excess of 50,000 jobs and doing resource intensive operations, like processing payments and creating orders, at a sustained rate of over 10,000 / hour without negatively impacting normal site operations.
25
+
26
+ This is all on infrastructure and WordPress sites outside the control of the plugin author.
27
+
28
+ If your plugin needs background processing, especially of large sets of tasks, Action Scheduler can help.
29
+
30
+ ## Learn More
31
+
32
+ To learn more about how to Action Scheduler works, and how to use it in your plugin, check out the docs on [ActionScheduler.org](https://actionscheduler.org).
33
+
34
+ There you will find:
35
+
36
+ * [Usage guide](https://actionscheduler.org/usage/): instructions on installing and using Action Scheduler
37
+ * [WP CLI guide](https://actionscheduler.org/wp-cli/): instructions on running Action Scheduler at scale via WP CLI
38
+ * [API Reference](https://actionscheduler.org/api/): complete reference guide for all API functions
39
+ * [Administration Guide](https://actionscheduler.org/admin/): guide to managing scheduled actions via the administration screen
40
+ * [Guide to Background Processing at Scale](https://actionscheduler.org/perf/): instructions for running Action Scheduler at scale via the default WP Cron queue runner
41
+
42
+ ## Credits
43
+
44
+ Action Scheduler is developed and maintained by [Automattic](http://automattic.com/) with significant early development completed by [Flightless](https://flightless.us/).
45
+
46
+ Collaboration is cool. We'd love to work with you to improve Action Scheduler. [Pull Requests](https://github.com/woocommerce/action-scheduler/pulls) welcome.
47
+
48
+ == Changelog ==
49
+
50
+ = 3.3.0 - 2021-09-15 =
51
+ * Enhancement - Adds as_has_scheduled_action() to provide a performant way to test for existing actions. #645
52
+ * Fix - Improves compatibility with environments where NO_ZERO_DATE is enabled. #519
53
+ * Fix - Adds safety checks to guard against errors when our database tables cannot be created. #645
54
+ * Dev - Now supports queries that use multiple statuses. #649
55
+ * Dev - Minimum requirements for WordPress and PHP bumped (to 5.2 and 5.6 respectively). #723
56
+
57
+ = 3.2.1 - 2021-06-21 =
58
+ * Fix - Add extra safety/account for different versions of AS and different loading patterns. #714
59
+ * Fix - Handle hidden columns (Tools → Scheduled Actions) | #600.
60
+
61
+ = 3.2.0 - 2021-06-03 =
62
+ * Fix - Add "no ordering" option to as_next_scheduled_action().
63
+ * Fix - Add secondary scheduled date checks when claiming actions (DBStore) | #634.
64
+ * Fix - Add secondary scheduled date checks when claiming actions (wpPostStore) | #634.
65
+ * Fix - Adds a new index to the action table, reducing the potential for deadlocks (props: @glagonikas).
66
+ * Fix - Fix unit tests infrastructure and adapt tests to PHP 8.
67
+ * Fix - Identify in-use data store.
68
+ * Fix - Improve test_migration_is_scheduled.
69
+ * Fix - PHP notice on list table.
70
+ * Fix - Speed up clean up and batch selects.
71
+ * Fix - Update pending dependencies.
72
+ * Fix - [PHP 8.0] Only pass action arg values through to do_action_ref_array().
73
+ * Fix - [PHP 8] Set the PHP version to 7.1 in composer.json for PHP 8 compatibility.
74
+ * Fix - add is_initialized() to docs.
75
+ * Fix - fix file permissions.
76
+ * Fix - fixes #664 by replacing __ with esc_html__.
lite/languages/email-subscribers.pot CHANGED
@@ -2,14 +2,14 @@
2
  # This file is distributed under the same license as the Email Subscribers & Newsletters plugin.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: Email Subscribers & Newsletters 4.9.0\n"
6
  "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/email-subscribers\n"
7
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
8
  "Language-Team: LANGUAGE <LL@li.org>\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=UTF-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
12
- "POT-Creation-Date: 2021-10-26T09:21:44+02:00\n"
13
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14
  "X-Generator: WP-CLI 2.4.0\n"
15
  "X-Domain: email-subscribers\n"
2
  # This file is distributed under the same license as the Email Subscribers & Newsletters plugin.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: Email Subscribers & Newsletters 4.9.1\n"
6
  "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/email-subscribers\n"
7
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
8
  "Language-Team: LANGUAGE <LL@li.org>\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=UTF-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
12
+ "POT-Creation-Date: 2021-11-02T07:00:07+01:00\n"
13
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14
  "X-Generator: WP-CLI 2.4.0\n"
15
  "X-Domain: email-subscribers\n"
lite/public/partials/class-es-shortcode.php CHANGED
@@ -308,7 +308,7 @@ class ES_Shortcode {
308
  <input type="hidden" name="esfpx_es_email_page" value="<?php echo esc_attr( $current_page ); ?>"/>
309
  <input type="hidden" name="esfpx_es_email_page_url" value="<?php echo esc_url( $current_page_url ); ?>"/>
310
  <input type="hidden" name="esfpx_status" value="Unconfirmed"/>
311
- <input type="hidden" name="esfpx_es-subscribe" id="es-subscribe" value="<?php echo esc_attr( $nonce ); ?>"/>
312
  <label style="<?php echo esc_attr( $hp_style ); ?>"><input type="email" name="esfpx_es_hp_email" class="es_required_field" tabindex="-1" autocomplete="-1" value=""/></label>
313
  <?php
314
 
308
  <input type="hidden" name="esfpx_es_email_page" value="<?php echo esc_attr( $current_page ); ?>"/>
309
  <input type="hidden" name="esfpx_es_email_page_url" value="<?php echo esc_url( $current_page_url ); ?>"/>
310
  <input type="hidden" name="esfpx_status" value="Unconfirmed"/>
311
+ <input type="hidden" name="esfpx_es-subscribe" id="es-subscribe-<?php echo esc_attr( $unique_id ); ?>" value="<?php echo esc_attr( $nonce ); ?>"/>
312
  <label style="<?php echo esc_attr( $hp_style ); ?>"><input type="email" name="esfpx_es_hp_email" class="es_required_field" tabindex="-1" autocomplete="-1" value=""/></label>
313
  <?php
314
 
readme.txt CHANGED
@@ -6,7 +6,7 @@ Tags: email marketing, subscription, autoresponder, post notification, welcome e
6
  Requires at least: 3.9
7
  Tested up to: 5.8.1
8
  Requires PHP: 5.6
9
- Stable tag: 4.9.0
10
  License: GPLv3
11
  License URI: http://www.gnu.org/licenses
12
 
@@ -310,12 +310,20 @@ Refer [here](https://www.icegram.com/documentation/es-faq/).
310
 
311
  == Upgrade Notice ==
312
 
313
- = 4.9.0 =
314
 
315
- * New: Automatic bounce handling for list
 
 
316
 
317
  == Changelog ==
318
 
 
 
 
 
 
 
319
  **4.9.0 (26.10.2021)**
320
 
321
  * New: Automatic bounce handling for list **[PRO]**
6
  Requires at least: 3.9
7
  Tested up to: 5.8.1
8
  Requires PHP: 5.6
9
+ Stable tag: 4.9.1
10
  License: GPLv3
11
  License URI: http://www.gnu.org/licenses
12
 
310
 
311
  == Upgrade Notice ==
312
 
313
+ = 4.9.1 =
314
 
315
+ * New: Added {{POSTIMAGE-URL}} keyword for Post Notification
316
+ * Update: Action Scheduler Library to 3.3.0
317
+ * Fix: Amazon SES batch sending issue **[PRO]**
318
 
319
  == Changelog ==
320
 
321
+ **4.9.1 (02.11.2021)**
322
+
323
+ * New: Added {{POSTIMAGE-URL}} keyword for Post Notification
324
+ * Update: Action Scheduler Library to 3.3.0
325
+ * Fix: Amazon SES batch sending issue **[PRO]**
326
+
327
  **4.9.0 (26.10.2021)**
328
 
329
  * New: Automatic bounce handling for list **[PRO]**