PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.4
WooCommerce v10.9.4
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / packages / action-scheduler / classes / ActionScheduler_ListTable.php
woocommerce / packages / action-scheduler / classes Last commit date
WP_CLI 11 months ago abstracts 11 months ago actions 1 year ago data-stores 11 months ago migration 1 year ago schedules 1 year ago schema 11 months ago ActionScheduler_ActionClaim.php 1 year ago ActionScheduler_ActionFactory.php 1 year ago ActionScheduler_AdminView.php 1 year ago ActionScheduler_AsyncRequest_QueueRunner.php 1 year ago ActionScheduler_Compatibility.php 1 year ago ActionScheduler_DataController.php 11 months ago ActionScheduler_DateTime.php 1 year ago ActionScheduler_Exception.php 3 years ago ActionScheduler_FatalErrorMonitor.php 1 year ago ActionScheduler_InvalidActionException.php 1 year ago ActionScheduler_ListTable.php 1 year ago ActionScheduler_LogEntry.php 1 year ago ActionScheduler_NullLogEntry.php 1 year ago ActionScheduler_OptionLock.php 1 year ago ActionScheduler_QueueCleaner.php 1 year ago ActionScheduler_QueueRunner.php 1 year ago ActionScheduler_RecurringActionScheduler.php 11 months ago ActionScheduler_SystemInformation.php 1 year ago ActionScheduler_Versions.php 1 year ago ActionScheduler_WPCommentCleaner.php 1 year ago ActionScheduler_wcSystemStatus.php 11 months ago
ActionScheduler_ListTable.php
676 lines
1 <?php
2
3 /**
4 * Implements the admin view of the actions.
5 *
6 * @codeCoverageIgnore
7 */
8 class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
9
10 /**
11 * The package name.
12 *
13 * @var string
14 */
15 protected $package = 'action-scheduler';
16
17 /**
18 * Columns to show (name => label).
19 *
20 * @var array
21 */
22 protected $columns = array();
23
24 /**
25 * Actions (name => label).
26 *
27 * @var array
28 */
29 protected $row_actions = array();
30
31 /**
32 * The active data stores
33 *
34 * @var ActionScheduler_Store
35 */
36 protected $store;
37
38 /**
39 * A logger to use for getting action logs to display
40 *
41 * @var ActionScheduler_Logger
42 */
43 protected $logger;
44
45 /**
46 * A ActionScheduler_QueueRunner runner instance (or child class)
47 *
48 * @var ActionScheduler_QueueRunner
49 */
50 protected $runner;
51
52 /**
53 * Bulk actions. The key of the array is the method name of the implementation.
54 * Example: bulk_<key>(array $ids, string $sql_in).
55 *
56 * See the comments in the parent class for further details
57 *
58 * @var array
59 */
60 protected $bulk_actions = array();
61
62 /**
63 * Flag variable to render our notifications, if any, once.
64 *
65 * @var bool
66 */
67 protected static $did_notification = false;
68
69 /**
70 * Array of seconds for common time periods, like week or month, alongside an internationalised string representation, i.e. "Day" or "Days"
71 *
72 * @var array
73 */
74 private static $time_periods;
75
76 /**
77 * Sets the current data store object into `store->action` and initialises the object.
78 *
79 * @param ActionScheduler_Store $store Store object.
80 * @param ActionScheduler_Logger $logger Logger object.
81 * @param ActionScheduler_QueueRunner $runner Runner object.
82 */
83 public function __construct( ActionScheduler_Store $store, ActionScheduler_Logger $logger, ActionScheduler_QueueRunner $runner ) {
84
85 $this->store = $store;
86 $this->logger = $logger;
87 $this->runner = $runner;
88
89 $this->table_header = __( 'Scheduled Actions', 'woocommerce' );
90
91 $this->bulk_actions = array(
92 'delete' => __( 'Delete', 'woocommerce' ),
93 );
94
95 $this->columns = array(
96 'hook' => __( 'Hook', 'woocommerce' ),
97 'status' => __( 'Status', 'woocommerce' ),
98 'args' => __( 'Arguments', 'woocommerce' ),
99 'group' => __( 'Group', 'woocommerce' ),
100 'recurrence' => __( 'Recurrence', 'woocommerce' ),
101 'schedule' => __( 'Scheduled Date', 'woocommerce' ),
102 'log_entries' => __( 'Log', 'woocommerce' ),
103 );
104
105 $this->sort_by = array(
106 'schedule',
107 'hook',
108 'group',
109 );
110
111 $this->search_by = array(
112 'hook',
113 'args',
114 'claim_id',
115 );
116
117 $request_status = $this->get_request_status();
118
119 if ( empty( $request_status ) ) {
120 $this->sort_by[] = 'status';
121 } elseif ( in_array( $request_status, array( 'in-progress', 'failed' ), true ) ) {
122 $this->columns += array( 'claim_id' => __( 'Claim ID', 'woocommerce' ) );
123 $this->sort_by[] = 'claim_id';
124 }
125
126 $this->row_actions = array(
127 'hook' => array(
128 'run' => array(
129 'name' => __( 'Run', 'woocommerce' ),
130 'desc' => __( 'Process the action now as if it were run as part of a queue', 'woocommerce' ),
131 ),
132 'cancel' => array(
133 'name' => __( 'Cancel', 'woocommerce' ),
134 'desc' => __( 'Cancel the action now to avoid it being run in future', 'woocommerce' ),
135 'class' => 'cancel trash',
136 ),
137 ),
138 );
139
140 self::$time_periods = array(
141 array(
142 'seconds' => YEAR_IN_SECONDS,
143 /* translators: %s: amount of time */
144 'names' => _n_noop( '%s year', '%s years', 'woocommerce' ),
145 ),
146 array(
147 'seconds' => MONTH_IN_SECONDS,
148 /* translators: %s: amount of time */
149 'names' => _n_noop( '%s month', '%s months', 'woocommerce' ),
150 ),
151 array(
152 'seconds' => WEEK_IN_SECONDS,
153 /* translators: %s: amount of time */
154 'names' => _n_noop( '%s week', '%s weeks', 'woocommerce' ),
155 ),
156 array(
157 'seconds' => DAY_IN_SECONDS,
158 /* translators: %s: amount of time */
159 'names' => _n_noop( '%s day', '%s days', 'woocommerce' ),
160 ),
161 array(
162 'seconds' => HOUR_IN_SECONDS,
163 /* translators: %s: amount of time */
164 'names' => _n_noop( '%s hour', '%s hours', 'woocommerce' ),
165 ),
166 array(
167 'seconds' => MINUTE_IN_SECONDS,
168 /* translators: %s: amount of time */
169 'names' => _n_noop( '%s minute', '%s minutes', 'woocommerce' ),
170 ),
171 array(
172 'seconds' => 1,
173 /* translators: %s: amount of time */
174 'names' => _n_noop( '%s second', '%s seconds', 'woocommerce' ),
175 ),
176 );
177
178 parent::__construct(
179 array(
180 'singular' => 'action-scheduler',
181 'plural' => '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 *
211 * The WordPress human_time_diff() function only calculates the time difference to one degree, meaning
212 * even if an action is 1 day and 11 hours away, it will display "1 day". This function goes one step
213 * further to display two degrees of accuracy.
214 *
215 * Inspired by the Crontrol::interval() function by Edward Dale: https://wordpress.org/plugins/wp-crontrol/
216 *
217 * @param int $interval A interval in seconds.
218 * @param int $periods_to_include Depth of time periods to include, e.g. for an interval of 70, and $periods_to_include of 2, both minutes and seconds would be included. With a value of 1, only minutes would be included.
219 * @return string A human friendly string representation of the interval.
220 */
221 private static function human_interval( $interval, $periods_to_include = 2 ) {
222
223 if ( $interval <= 0 ) {
224 return __( 'Now!', 'woocommerce' );
225 }
226
227 $output = '';
228 $num_time_periods = count( self::$time_periods );
229
230 for ( $time_period_index = 0, $periods_included = 0, $seconds_remaining = $interval; $time_period_index < $num_time_periods && $seconds_remaining > 0 && $periods_included < $periods_to_include; $time_period_index++ ) {
231
232 $periods_in_interval = floor( $seconds_remaining / self::$time_periods[ $time_period_index ]['seconds'] );
233
234 if ( $periods_in_interval > 0 ) {
235 if ( ! empty( $output ) ) {
236 $output .= ' ';
237 }
238 $output .= sprintf( translate_nooped_plural( self::$time_periods[ $time_period_index ]['names'], $periods_in_interval, 'action-scheduler' ), $periods_in_interval );
239 $seconds_remaining -= $periods_in_interval * self::$time_periods[ $time_period_index ]['seconds'];
240 $periods_included++;
241 }
242 }
243
244 return $output;
245 }
246
247 /**
248 * Returns the recurrence of an action or 'Non-repeating'. The output is human readable.
249 *
250 * @param ActionScheduler_Action $action Action object.
251 *
252 * @return string
253 */
254 protected function get_recurrence( $action ) {
255 $schedule = $action->get_schedule();
256 if ( $schedule->is_recurring() && method_exists( $schedule, 'get_recurrence' ) ) {
257 $recurrence = $schedule->get_recurrence();
258
259 if ( is_numeric( $recurrence ) ) {
260 /* translators: %s: time interval */
261 return sprintf( __( 'Every %s', 'woocommerce' ), self::human_interval( $recurrence ) );
262 } else {
263 return $recurrence;
264 }
265 }
266
267 return __( 'Non-repeating', 'woocommerce' );
268 }
269
270 /**
271 * Serializes the argument of an action to render it in a human friendly format.
272 *
273 * @param array $row The array representation of the current row of the table.
274 *
275 * @return string
276 */
277 public function column_args( array $row ) {
278 if ( empty( $row['args'] ) ) {
279 return apply_filters( 'action_scheduler_list_table_column_args', '', $row );
280 }
281
282 $row_html = '<ul>';
283 foreach ( $row['args'] as $key => $value ) {
284 $row_html .= sprintf( '<li><code>%s => %s</code></li>', esc_html( var_export( $key, true ) ), esc_html( var_export( $value, true ) ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
285 }
286 $row_html .= '</ul>';
287
288 return apply_filters( 'action_scheduler_list_table_column_args', $row_html, $row );
289 }
290
291 /**
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 array $row Action array.
295 * @return string
296 */
297 public function column_log_entries( array $row ) {
298
299 $log_entries_html = '<ol>';
300
301 $timezone = new DateTimezone( 'UTC' );
302
303 foreach ( $row['log_entries'] as $log_entry ) {
304 $log_entries_html .= $this->get_log_entry_html( $log_entry, $timezone );
305 }
306
307 $log_entries_html .= '</ol>';
308
309 return $log_entries_html;
310 }
311
312 /**
313 * Prints the logs entries inline. We do so to avoid loading Javascript and other hacks to show it in a modal.
314 *
315 * @param ActionScheduler_LogEntry $log_entry Log entry object.
316 * @param DateTimezone $timezone Timestamp.
317 * @return string
318 */
319 protected function get_log_entry_html( ActionScheduler_LogEntry $log_entry, DateTimezone $timezone ) {
320 $date = $log_entry->get_date();
321 $date->setTimezone( $timezone );
322 return sprintf( '<li><strong>%s</strong><br/>%s</li>', esc_html( $date->format( 'Y-m-d H:i:s O' ) ), esc_html( $log_entry->get_message() ) );
323 }
324
325 /**
326 * Only display row actions for pending actions.
327 *
328 * @param array $row Row to render.
329 * @param string $column_name Current row.
330 *
331 * @return string
332 */
333 protected function maybe_render_actions( $row, $column_name ) {
334 if ( 'pending' === strtolower( $row['status_name'] ) ) {
335 return parent::maybe_render_actions( $row, $column_name );
336 }
337
338 return '';
339 }
340
341 /**
342 * Renders admin notifications
343 *
344 * Notifications:
345 * 1. When the maximum number of tasks are being executed simultaneously.
346 * 2. Notifications when a task is manually executed.
347 * 3. Tables are missing.
348 */
349 public function display_admin_notices() {
350 global $wpdb;
351
352 if ( ( is_a( $this->store, 'ActionScheduler_HybridStore' ) || is_a( $this->store, 'ActionScheduler_DBStore' ) ) && apply_filters( 'action_scheduler_enable_recreate_data_store', true ) ) {
353 $table_list = array(
354 'actionscheduler_actions',
355 'actionscheduler_logs',
356 'actionscheduler_groups',
357 'actionscheduler_claims',
358 );
359
360 $found_tables = $wpdb->get_col( "SHOW TABLES LIKE '{$wpdb->prefix}actionscheduler%'" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
361 foreach ( $table_list as $table_name ) {
362 if ( ! in_array( $wpdb->prefix . $table_name, $found_tables, true ) ) {
363 $this->admin_notices[] = array(
364 'class' => 'error',
365 'message' => __( 'It appears one or more database tables were missing. Attempting to re-create the missing table(s).', 'woocommerce' ),
366 );
367 $this->recreate_tables();
368 parent::display_admin_notices();
369
370 return;
371 }
372 }
373 }
374
375 if ( $this->runner->has_maximum_concurrent_batches() ) {
376 $claim_count = $this->store->get_claim_count();
377 $this->admin_notices[] = array(
378 'class' => 'updated',
379 'message' => sprintf(
380 /* translators: %s: amount of claims */
381 _n(
382 'Maximum simultaneous queues already in progress (%s queue). No additional queues will begin processing until the current queues are complete.',
383 'Maximum simultaneous queues already in progress (%s queues). No additional queues will begin processing until the current queues are complete.',
384 $claim_count,
385 'woocommerce'
386 ),
387 $claim_count
388 ),
389 );
390 } elseif ( $this->store->has_pending_actions_due() ) {
391
392 $async_request_lock_expiration = ActionScheduler::lock()->get_expiration( 'async-request-runner' );
393
394 // No lock set or lock expired.
395 if ( false === $async_request_lock_expiration || $async_request_lock_expiration < time() ) {
396 $in_progress_url = add_query_arg( 'status', 'in-progress', remove_query_arg( 'status' ) );
397 /* translators: %s: process URL */
398 $async_request_message = sprintf( __( 'A new queue has begun processing. <a href="%s">View actions in-progress &raquo;</a>', 'woocommerce' ), esc_url( $in_progress_url ) );
399 } else {
400 /* translators: %d: seconds */
401 $async_request_message = sprintf( __( 'The next queue will begin processing in approximately %d seconds.', 'woocommerce' ), $async_request_lock_expiration - time() );
402 }
403
404 $this->admin_notices[] = array(
405 'class' => 'notice notice-info',
406 'message' => $async_request_message,
407 );
408 }
409
410 $notification = get_transient( 'action_scheduler_admin_notice' );
411
412 if ( is_array( $notification ) ) {
413 delete_transient( 'action_scheduler_admin_notice' );
414
415 $action = $this->store->fetch_action( $notification['action_id'] );
416 $action_hook_html = '<strong><code>' . $action->get_hook() . '</code></strong>';
417
418 if ( 1 === absint( $notification['success'] ) ) {
419 $class = 'updated';
420 switch ( $notification['row_action_type'] ) {
421 case 'run':
422 /* translators: %s: action HTML */
423 $action_message_html = sprintf( __( 'Successfully executed action: %s', 'woocommerce' ), $action_hook_html );
424 break;
425 case 'cancel':
426 /* translators: %s: action HTML */
427 $action_message_html = sprintf( __( 'Successfully canceled action: %s', 'woocommerce' ), $action_hook_html );
428 break;
429 default:
430 /* translators: %s: action HTML */
431 $action_message_html = sprintf( __( 'Successfully processed change for action: %s', 'woocommerce' ), $action_hook_html );
432 break;
433 }
434 } else {
435 $class = 'error';
436 /* translators: 1: action HTML 2: action ID 3: error message */
437 $action_message_html = sprintf( __( 'Could not process change for action: "%1$s" (ID: %2$d). Error: %3$s', 'woocommerce' ), $action_hook_html, esc_html( $notification['action_id'] ), esc_html( $notification['error_message'] ) );
438 }
439
440 $action_message_html = apply_filters( 'action_scheduler_admin_notice_html', $action_message_html, $action, $notification );
441
442 $this->admin_notices[] = array(
443 'class' => $class,
444 'message' => $action_message_html,
445 );
446 }
447
448 parent::display_admin_notices();
449 }
450
451 /**
452 * Prints the scheduled date in a human friendly format.
453 *
454 * @param array $row The array representation of the current row of the table.
455 *
456 * @return string
457 */
458 public function column_schedule( $row ) {
459 return $this->get_schedule_display_string( $row['schedule'] );
460 }
461
462 /**
463 * Get the scheduled date in a human friendly format.
464 *
465 * @param ActionScheduler_Schedule $schedule Action's schedule.
466 * @return string
467 */
468 protected function get_schedule_display_string( ActionScheduler_Schedule $schedule ) {
469
470 $schedule_display_string = '';
471
472 if ( is_a( $schedule, 'ActionScheduler_NullSchedule' ) ) {
473 return __( 'async', 'woocommerce' );
474 }
475
476 if ( ! method_exists( $schedule, 'get_date' ) || ! $schedule->get_date() ) {
477 return '0000-00-00 00:00:00';
478 }
479
480 $next_timestamp = $schedule->get_date()->getTimestamp();
481
482 $schedule_display_string .= $schedule->get_date()->format( 'Y-m-d H:i:s O' );
483 $schedule_display_string .= '<br/>';
484
485 if ( gmdate( 'U' ) > $next_timestamp ) {
486 /* translators: %s: date interval */
487 $schedule_display_string .= sprintf( __( ' (%s ago)', 'woocommerce' ), self::human_interval( gmdate( 'U' ) - $next_timestamp ) );
488 } else {
489 /* translators: %s: date interval */
490 $schedule_display_string .= sprintf( __( ' (%s)', 'woocommerce' ), self::human_interval( $next_timestamp - gmdate( 'U' ) ) );
491 }
492
493 return $schedule_display_string;
494 }
495
496 /**
497 * Bulk delete.
498 *
499 * Deletes actions based on their ID. This is the handler for the bulk delete. It assumes the data
500 * properly validated by the callee and it will delete the actions without any extra validation.
501 *
502 * @param int[] $ids Action IDs.
503 * @param string $ids_sql Inherited and unused.
504 */
505 protected function bulk_delete( array $ids, $ids_sql ) {
506 foreach ( $ids as $id ) {
507 try {
508 $this->store->delete_action( $id );
509 } catch ( Exception $e ) {
510 // A possible reason for an exception would include a scenario where the same action is deleted by a
511 // concurrent request.
512 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
513 error_log(
514 sprintf(
515 /* translators: 1: action ID 2: exception message. */
516 __( 'Action Scheduler was unable to delete action %1$d. Reason: %2$s', 'woocommerce' ),
517 $id,
518 $e->getMessage()
519 )
520 );
521 }
522 }
523 }
524
525 /**
526 * Implements the logic behind running an action. ActionScheduler_Abstract_ListTable validates the request and their
527 * parameters are valid.
528 *
529 * @param int $action_id Action ID.
530 */
531 protected function row_action_cancel( $action_id ) {
532 $this->process_row_action( $action_id, 'cancel' );
533 }
534
535 /**
536 * Implements the logic behind running an action. ActionScheduler_Abstract_ListTable validates the request and their
537 * parameters are valid.
538 *
539 * @param int $action_id Action ID.
540 */
541 protected function row_action_run( $action_id ) {
542 $this->process_row_action( $action_id, 'run' );
543 }
544
545 /**
546 * Force the data store schema updates.
547 */
548 protected function recreate_tables() {
549 if ( is_a( $this->store, 'ActionScheduler_HybridStore' ) ) {
550 $store = $this->store;
551 } else {
552 $store = new ActionScheduler_HybridStore();
553 }
554 add_action( 'action_scheduler/created_table', array( $store, 'set_autoincrement' ), 10, 2 );
555
556 $store_schema = new ActionScheduler_StoreSchema();
557 $logger_schema = new ActionScheduler_LoggerSchema();
558 $store_schema->register_tables( true );
559 $logger_schema->register_tables( true );
560
561 remove_action( 'action_scheduler/created_table', array( $store, 'set_autoincrement' ), 10 );
562 }
563 /**
564 * Implements the logic behind processing an action once an action link is clicked on the list table.
565 *
566 * @param int $action_id Action ID.
567 * @param string $row_action_type The type of action to perform on the action.
568 */
569 protected function process_row_action( $action_id, $row_action_type ) {
570 try {
571 switch ( $row_action_type ) {
572 case 'run':
573 $this->runner->process_action( $action_id, 'Admin List Table' );
574 break;
575 case 'cancel':
576 $this->store->cancel_action( $action_id );
577 break;
578 }
579 $success = 1;
580 $error_message = '';
581 } catch ( Exception $e ) {
582 $success = 0;
583 $error_message = $e->getMessage();
584 }
585
586 set_transient( 'action_scheduler_admin_notice', compact( 'action_id', 'success', 'error_message', 'row_action_type' ), 30 );
587 }
588
589 /**
590 * {@inheritDoc}
591 */
592 public function prepare_items() {
593 $this->prepare_column_headers();
594
595 $per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
596
597 $query = array(
598 'per_page' => $per_page,
599 'offset' => $this->get_items_offset(),
600 'status' => $this->get_request_status(),
601 'orderby' => $this->get_request_orderby(),
602 'order' => $this->get_request_order(),
603 'search' => $this->get_request_search_query(),
604 );
605
606 /**
607 * Change query arguments to query for past-due actions.
608 * Past-due actions have the 'pending' status and are in the past.
609 * This is needed because registering 'past-due' as a status is overkill.
610 */
611 if ( 'past-due' === $this->get_request_status() ) {
612 $query['status'] = ActionScheduler_Store::STATUS_PENDING;
613 $query['date'] = as_get_datetime_object();
614 }
615
616 $this->items = array();
617
618 $total_items = $this->store->query_actions( $query, 'count' );
619
620 $status_labels = $this->store->get_status_labels();
621
622 foreach ( $this->store->query_actions( $query ) as $action_id ) {
623 try {
624 $action = $this->store->fetch_action( $action_id );
625 } catch ( Exception $e ) {
626 continue;
627 }
628 if ( is_a( $action, 'ActionScheduler_NullAction' ) ) {
629 continue;
630 }
631 $this->items[ $action_id ] = array(
632 'ID' => $action_id,
633 'hook' => $action->get_hook(),
634 'status_name' => $this->store->get_status( $action_id ),
635 'status' => $status_labels[ $this->store->get_status( $action_id ) ],
636 'args' => $action->get_args(),
637 'group' => $action->get_group(),
638 'log_entries' => $this->logger->get_logs( $action_id ),
639 'claim_id' => $this->store->get_claim_id( $action_id ),
640 'recurrence' => $this->get_recurrence( $action ),
641 'schedule' => $action->get_schedule(),
642 );
643 }
644
645 $this->set_pagination_args(
646 array(
647 'total_items' => $total_items,
648 'per_page' => $per_page,
649 'total_pages' => ceil( $total_items / $per_page ),
650 )
651 );
652 }
653
654 /**
655 * Prints the available statuses so the user can click to filter.
656 */
657 protected function display_filter_by_status() {
658 $this->status_counts = $this->store->action_counts() + $this->store->extra_action_counts();
659 parent::display_filter_by_status();
660 }
661
662 /**
663 * Get the text to display in the search box on the list table.
664 */
665 protected function get_search_box_button_text() {
666 return __( 'Search hook, args and claim ID', 'woocommerce' );
667 }
668
669 /**
670 * {@inheritDoc}
671 */
672 protected function get_per_page_option_name() {
673 return str_replace( '-', '_', $this->screen->id ) . '_per_page';
674 }
675 }
676