PluginProbe
WANotifier for Forms and Actions / 3.1.1
WANotifier for Forms and Actions v3.1.1
3.1.1 3.1.0 3.0.4 2.7.10 2.7.11 2.7.12 2.7.13 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 3.0.0 3.0.1 3.0.2 3.0.3 trunk 0.1.0 0.1.1 1.0.0 1.0.1 1.0.2 All 67 releases
notifier / libraries / action-scheduler / classes / ActionScheduler_ListTable.php

ActionScheduler_ListTable.php in WANotifier for Forms and Actions 3.1.1, at libraries/action-scheduler/classes/ActionScheduler_ListTable.php

701 lines 21.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 * Statuses for which the Claim ID column is shown.
71 *
72 * @var string[]
73 */
74 private static $statuses_with_claim_id = array( 'in-progress', 'failed' );
75
76 /**
77 * Array of seconds for common time periods, like week or month, alongside an internationalised string representation, i.e. "Day" or "Days"
78 *
79 * @var array
80 */
81 private static $time_periods;
82
83 /**
84 * Sets the current data store object into `store->action` and initialises the object.
85 *
86 * @param ActionScheduler_Store $store Store object.
87 * @param ActionScheduler_Logger $logger Logger object.
88 * @param ActionScheduler_QueueRunner $runner Runner object.
89 */
90 public function __construct( ActionScheduler_Store $store, ActionScheduler_Logger $logger, ActionScheduler_QueueRunner $runner ) {
91
92 $this->store = $store;
93 $this->logger = $logger;
94 $this->runner = $runner;
95
96 $this->table_header = __( 'Scheduled Actions', 'action-scheduler' );
97
98 $this->bulk_actions = array(
99 'delete' => __( 'Delete', 'action-scheduler' ),
100 );
101
102 $this->columns = array(
103 'hook' => __( 'Hook', 'action-scheduler' ),
104 'status' => __( 'Status', 'action-scheduler' ),
105 'args' => __( 'Arguments', 'action-scheduler' ),
106 'group' => __( 'Group', 'action-scheduler' ),
107 'recurrence' => __( 'Recurrence', 'action-scheduler' ),
108 'schedule' => __( 'Scheduled Date', 'action-scheduler' ),
109 'log_entries' => __( 'Log', 'action-scheduler' ),
110 );
111
112 $this->sort_by = array(
113 'schedule',
114 'hook',
115 'group',
116 );
117
118 /*
119 * Only the emptiness of this property matters here: it is what causes the search box to be
120 * rendered. The individual entries are unused, because this class overrides prepare_items()
121 * and delegates searching to the store, rather than using the parent's
122 * get_items_query_search().
123 */
124 $this->search_by = array(
125 'hook',
126 'args',
127 'claim_id',
128 );
129
130 $request_status = $this->get_request_status();
131
132 if ( empty( $request_status ) ) {
133 $this->sort_by[] = 'status';
134 } elseif ( in_array( $request_status, self::$statuses_with_claim_id, true ) ) {
135 $this->columns += array( 'claim_id' => __( 'Claim ID', 'action-scheduler' ) );
136 $this->sort_by[] = 'claim_id';
137 }
138
139 $this->row_actions = array(
140 'hook' => array(
141 'run' => array(
142 'name' => __( 'Run', 'action-scheduler' ),
143 'desc' => __( 'Process the action now as if it were run as part of a queue', 'action-scheduler' ),
144 ),
145 'cancel' => array(
146 'name' => __( 'Cancel', 'action-scheduler' ),
147 'desc' => __( 'Cancel the action now to avoid it being run in future', 'action-scheduler' ),
148 'class' => 'cancel trash',
149 ),
150 ),
151 );
152
153 self::$time_periods = array(
154 array(
155 'seconds' => YEAR_IN_SECONDS,
156 /* translators: %s: amount of time */
157 'names' => _n_noop( '%s year', '%s years', 'action-scheduler' ),
158 ),
159 array(
160 'seconds' => MONTH_IN_SECONDS,
161 /* translators: %s: amount of time */
162 'names' => _n_noop( '%s month', '%s months', 'action-scheduler' ),
163 ),
164 array(
165 'seconds' => WEEK_IN_SECONDS,
166 /* translators: %s: amount of time */
167 'names' => _n_noop( '%s week', '%s weeks', 'action-scheduler' ),
168 ),
169 array(
170 'seconds' => DAY_IN_SECONDS,
171 /* translators: %s: amount of time */
172 'names' => _n_noop( '%s day', '%s days', 'action-scheduler' ),
173 ),
174 array(
175 'seconds' => HOUR_IN_SECONDS,
176 /* translators: %s: amount of time */
177 'names' => _n_noop( '%s hour', '%s hours', 'action-scheduler' ),
178 ),
179 array(
180 'seconds' => MINUTE_IN_SECONDS,
181 /* translators: %s: amount of time */
182 'names' => _n_noop( '%s minute', '%s minutes', 'action-scheduler' ),
183 ),
184 array(
185 'seconds' => 1,
186 /* translators: %s: amount of time */
187 'names' => _n_noop( '%s second', '%s seconds', 'action-scheduler' ),
188 ),
189 );
190
191 parent::__construct(
192 array(
193 'singular' => 'action-scheduler',
194 'plural' => 'action-scheduler',
195 'ajax' => false,
196 )
197 );
198
199 add_screen_option(
200 'per_page',
201 array(
202 'default' => $this->items_per_page,
203 )
204 );
205
206 add_filter( 'set_screen_option_' . $this->get_per_page_option_name(), array( $this, 'set_items_per_page_option' ), 10, 3 );
207 set_screen_options();
208 }
209
210 /**
211 * Handles setting the items_per_page option for this screen.
212 *
213 * @param mixed $status Default false (to skip saving the current option).
214 * @param string $option Screen option name.
215 * @param int $value Screen option value.
216 * @return int
217 */
218 public function set_items_per_page_option( $status, $option, $value ) {
219 return $value;
220 }
221 /**
222 * Convert an interval of seconds into a two part human friendly string.
223 *
224 * The WordPress human_time_diff() function only calculates the time difference to one degree, meaning
225 * even if an action is 1 day and 11 hours away, it will display "1 day". This function goes one step
226 * further to display two degrees of accuracy.
227 *
228 * Inspired by the Crontrol::interval() function by Edward Dale: https://wordpress.org/plugins/wp-crontrol/
229 *
230 * @param int $interval A interval in seconds.
231 * @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.
232 * @return string A human friendly string representation of the interval.
233 */
234 private static function human_interval( $interval, $periods_to_include = 2 ) {
235
236 if ( $interval <= 0 ) {
237 return __( 'Now!', 'action-scheduler' );
238 }
239
240 $output = '';
241 $num_time_periods = count( self::$time_periods );
242
243 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++ ) {
244
245 $periods_in_interval = floor( $seconds_remaining / self::$time_periods[ $time_period_index ]['seconds'] );
246
247 if ( $periods_in_interval > 0 ) {
248 if ( ! empty( $output ) ) {
249 $output .= ' ';
250 }
251 $output .= sprintf( translate_nooped_plural( self::$time_periods[ $time_period_index ]['names'], $periods_in_interval, 'action-scheduler' ), $periods_in_interval );
252 $seconds_remaining -= $periods_in_interval * self::$time_periods[ $time_period_index ]['seconds'];
253 $periods_included++;
254 }
255 }
256
257 return $output;
258 }
259
260 /**
261 * Returns the recurrence of an action or 'Non-repeating'. The output is human readable.
262 *
263 * @param ActionScheduler_Action $action Action object.
264 *
265 * @return string
266 */
267 protected function get_recurrence( $action ) {
268 $schedule = $action->get_schedule();
269 if ( $schedule->is_recurring() && method_exists( $schedule, 'get_recurrence' ) ) {
270 $recurrence = $schedule->get_recurrence();
271
272 if ( is_numeric( $recurrence ) ) {
273 /* translators: %s: time interval */
274 return sprintf( __( 'Every %s', 'action-scheduler' ), self::human_interval( $recurrence ) );
275 } else {
276 return $recurrence;
277 }
278 }
279
280 return __( 'Non-repeating', 'action-scheduler' );
281 }
282
283 /**
284 * Serializes the argument of an action to render it in a human friendly format.
285 *
286 * @param array $row The array representation of the current row of the table.
287 *
288 * @return string
289 */
290 public function column_args( array $row ) {
291 if ( empty( $row['args'] ) ) {
292 return apply_filters( 'action_scheduler_list_table_column_args', '', $row );
293 }
294
295 $row_html = '<ul>';
296 foreach ( $row['args'] as $key => $value ) {
297 $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
298 }
299 $row_html .= '</ul>';
300
301 return apply_filters( 'action_scheduler_list_table_column_args', $row_html, $row );
302 }
303
304 /**
305 * Prints the logs entries inline. We do so to avoid loading Javascript and other hacks to show it in a modal.
306 *
307 * @param array $row Action array.
308 * @return string
309 */
310 public function column_log_entries( array $row ) {
311
312 $log_entries_html = '<ol>';
313
314 $timezone = new DateTimezone( 'UTC' );
315
316 foreach ( $row['log_entries'] as $log_entry ) {
317 $log_entries_html .= $this->get_log_entry_html( $log_entry, $timezone );
318 }
319
320 $log_entries_html .= '</ol>';
321
322 return $log_entries_html;
323 }
324
325 /**
326 * Prints the logs entries inline. We do so to avoid loading Javascript and other hacks to show it in a modal.
327 *
328 * @param ActionScheduler_LogEntry $log_entry Log entry object.
329 * @param DateTimezone $timezone Timestamp.
330 * @return string
331 */
332 protected function get_log_entry_html( ActionScheduler_LogEntry $log_entry, DateTimezone $timezone ) {
333 $date = $log_entry->get_date();
334 $date->setTimezone( $timezone );
335 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() ) );
336 }
337
338 /**
339 * Render the hook name and action ID.
340 *
341 * @param array $row Action data.
342 * @return string
343 */
344 public function column_hook( $row ) {
345 $action_id = sprintf(
346 /* translators: %d: Action ID. */
347 __( 'ID: %d', 'action-scheduler' ),
348 absint( $row['ID'] )
349 );
350
351 return esc_html( $row['hook'] ) . '<br><small>' . esc_html( $action_id ) . '</small>' . $this->maybe_render_actions( $row, 'hook' );
352 }
353
354 /**
355 * Only display row actions for pending actions.
356 *
357 * @param array $row Row to render.
358 * @param string $column_name Current row.
359 *
360 * @return string
361 */
362 protected function maybe_render_actions( $row, $column_name ) {
363 if ( 'pending' === strtolower( $row['status_name'] ) ) {
364 return parent::maybe_render_actions( $row, $column_name );
365 }
366
367 return '';
368 }
369
370 /**
371 * Renders admin notifications
372 *
373 * Notifications:
374 * 1. When the maximum number of tasks are being executed simultaneously.
375 * 2. Notifications when a task is manually executed.
376 * 3. Tables are missing.
377 */
378 public function display_admin_notices() {
379 global $wpdb;
380
381 if ( ( is_a( $this->store, 'ActionScheduler_HybridStore' ) || is_a( $this->store, 'ActionScheduler_DBStore' ) ) && apply_filters( 'action_scheduler_enable_recreate_data_store', true ) ) {
382 $table_list = array(
383 'actionscheduler_actions',
384 'actionscheduler_logs',
385 'actionscheduler_groups',
386 'actionscheduler_claims',
387 );
388
389 $found_tables = $wpdb->get_col( "SHOW TABLES LIKE '{$wpdb->prefix}actionscheduler%'" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
390 foreach ( $table_list as $table_name ) {
391 if ( ! in_array( $wpdb->prefix . $table_name, $found_tables, true ) ) {
392 $this->admin_notices[] = array(
393 'type' => 'error',
394 'message' => __( 'It appears one or more database tables were missing. Attempting to re-create the missing table(s).', 'action-scheduler' ),
395 );
396 $this->recreate_tables();
397 parent::display_admin_notices();
398
399 return;
400 }
401 }
402 }
403
404 if ( $this->runner->has_maximum_concurrent_batches() ) {
405 $claim_count = $this->store->get_claim_count();
406 $this->admin_notices[] = array(
407 'type' => 'info',
408 'message' => sprintf(
409 /* translators: %s: amount of claims */
410 _n(
411 'Maximum simultaneous queues already in progress (%s queue). No additional queues will begin processing until the current queues are complete.',
412 'Maximum simultaneous queues already in progress (%s queues). No additional queues will begin processing until the current queues are complete.',
413 $claim_count,
414 'action-scheduler'
415 ),
416 $claim_count
417 ),
418 );
419 } elseif ( ! empty( $this->status_counts['past-due'] ) ) {
420
421 $async_request_lock_expiration = ActionScheduler::lock()->get_expiration( 'async-request-runner' );
422
423 // No lock set or lock expired.
424 if ( false === $async_request_lock_expiration || $async_request_lock_expiration < time() ) {
425 $in_progress_url = add_query_arg( 'status', 'in-progress', remove_query_arg( 'status' ) );
426 /* translators: %s: process URL */
427 $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 ) );
428 } else {
429 /* translators: %d: seconds */
430 $async_request_message = sprintf( __( 'The next queue will begin processing in approximately %d seconds.', 'action-scheduler' ), $async_request_lock_expiration - time() );
431 }
432
433 $this->admin_notices[] = array(
434 'type' => 'info',
435 'message' => $async_request_message,
436 );
437 }
438
439 $notification = get_transient( 'action_scheduler_admin_notice' );
440
441 if ( is_array( $notification ) ) {
442 delete_transient( 'action_scheduler_admin_notice' );
443
444 $action = $this->store->fetch_action( $notification['action_id'] );
445 $action_hook_html = '<strong><code>' . $action->get_hook() . '</code></strong>';
446
447 if ( 1 === (int) $notification['success'] ) {
448 $type = 'success';
449 switch ( $notification['row_action_type'] ) {
450 case 'run':
451 /* translators: %s: action HTML */
452 $action_message_html = sprintf( __( 'Successfully executed action: %s', 'action-scheduler' ), $action_hook_html );
453 break;
454 case 'cancel':
455 /* translators: %s: action HTML */
456 $action_message_html = sprintf( __( 'Successfully canceled action: %s', 'action-scheduler' ), $action_hook_html );
457 break;
458 default:
459 /* translators: %s: action HTML */
460 $action_message_html = sprintf( __( 'Successfully processed change for action: %s', 'action-scheduler' ), $action_hook_html );
461 break;
462 }
463 } else {
464 $type = 'error';
465 /* translators: 1: action HTML 2: action ID 3: error message */
466 $action_message_html = sprintf( __( 'Could not process change for action: %1$s (ID: %2$d). Error: %3$s', 'action-scheduler' ), $action_hook_html, esc_html( $notification['action_id'] ), esc_html( $notification['error_message'] ) );
467 }
468
469 $action_message_html = apply_filters( 'action_scheduler_admin_notice_html', $action_message_html, $action, $notification );
470
471 $this->admin_notices[] = array(
472 'type' => $type,
473 'message' => $action_message_html,
474 );
475 }
476
477 parent::display_admin_notices();
478 }
479
480 /**
481 * Prints the scheduled date in a human friendly format.
482 *
483 * @param array $row The array representation of the current row of the table.
484 *
485 * @return string
486 */
487 public function column_schedule( $row ) {
488 return $this->get_schedule_display_string( $row['schedule'] );
489 }
490
491 /**
492 * Get the scheduled date in a human friendly format.
493 *
494 * @param ActionScheduler_Schedule $schedule Action's schedule.
495 * @return string
496 */
497 protected function get_schedule_display_string( ActionScheduler_Schedule $schedule ) {
498
499 $schedule_display_string = '';
500
501 if ( is_a( $schedule, 'ActionScheduler_NullSchedule' ) ) {
502 return __( 'async', 'action-scheduler' );
503 }
504
505 if ( ! method_exists( $schedule, 'get_date' ) || ! $schedule->get_date() ) {
506 return '0000-00-00 00:00:00';
507 }
508
509 $next_timestamp = $schedule->get_date()->getTimestamp();
510
511 $schedule_display_string .= $schedule->get_date()->format( 'Y-m-d H:i:s O' );
512 $schedule_display_string .= '<br/>';
513
514 if ( gmdate( 'U' ) > $next_timestamp ) {
515 /* translators: %s: date interval */
516 $schedule_display_string .= sprintf( __( ' (%s ago)', 'action-scheduler' ), self::human_interval( gmdate( 'U' ) - $next_timestamp ) );
517 } else {
518 /* translators: %s: date interval */
519 $schedule_display_string .= sprintf( __( ' (%s)', 'action-scheduler' ), self::human_interval( $next_timestamp - gmdate( 'U' ) ) );
520 }
521
522 return $schedule_display_string;
523 }
524
525 /**
526 * Bulk delete.
527 *
528 * Deletes actions based on their ID. This is the handler for the bulk delete. It assumes the data
529 * properly validated by the callee and it will delete the actions without any extra validation.
530 *
531 * @param int[] $ids Action IDs.
532 * @param string $ids_sql Inherited and unused.
533 */
534 protected function bulk_delete( array $ids, $ids_sql ) {
535 foreach ( $ids as $id ) {
536 try {
537 $this->store->delete_action( $id );
538 } catch ( Exception $e ) {
539 // A possible reason for an exception would include a scenario where the same action is deleted by a
540 // concurrent request.
541 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
542 error_log(
543 sprintf(
544 /* translators: 1: action ID 2: exception message. */
545 __( 'Action Scheduler was unable to delete action %1$d. Reason: %2$s', 'action-scheduler' ),
546 $id,
547 $e->getMessage()
548 )
549 );
550 }
551 }
552 }
553
554 /**
555 * Implements the logic behind running an action. ActionScheduler_Abstract_ListTable validates the request and their
556 * parameters are valid.
557 *
558 * @param int $action_id Action ID.
559 */
560 protected function row_action_cancel( $action_id ) {
561 $this->process_row_action( $action_id, 'cancel' );
562 }
563
564 /**
565 * Implements the logic behind running an action. ActionScheduler_Abstract_ListTable validates the request and their
566 * parameters are valid.
567 *
568 * @param int $action_id Action ID.
569 */
570 protected function row_action_run( $action_id ) {
571 $this->process_row_action( $action_id, 'run' );
572 }
573
574 /**
575 * Force the data store schema updates.
576 */
577 protected function recreate_tables() {
578 if ( is_a( $this->store, 'ActionScheduler_HybridStore' ) ) {
579 $store = $this->store;
580 } else {
581 $store = new ActionScheduler_HybridStore();
582 }
583 add_action( 'action_scheduler/created_table', array( $store, 'set_autoincrement' ), 10, 2 );
584
585 $store_schema = new ActionScheduler_StoreSchema();
586 $logger_schema = new ActionScheduler_LoggerSchema();
587 $store_schema->register_tables( true );
588 $logger_schema->register_tables( true );
589
590 remove_action( 'action_scheduler/created_table', array( $store, 'set_autoincrement' ), 10 );
591 }
592 /**
593 * Implements the logic behind processing an action once an action link is clicked on the list table.
594 *
595 * @param int $action_id Action ID.
596 * @param string $row_action_type The type of action to perform on the action.
597 */
598 protected function process_row_action( $action_id, $row_action_type ) {
599 try {
600 switch ( $row_action_type ) {
601 case 'run':
602 $this->runner->process_action( $action_id, 'Admin List Table' );
603 break;
604 case 'cancel':
605 $this->store->cancel_action( $action_id );
606 break;
607 }
608 $success = 1;
609 $error_message = '';
610 } catch ( Exception $e ) {
611 $success = 0;
612 $error_message = $e->getMessage();
613 }
614
615 set_transient( 'action_scheduler_admin_notice', compact( 'action_id', 'success', 'error_message', 'row_action_type' ), 30 );
616 }
617
618 /**
619 * {@inheritDoc}
620 */
621 public function prepare_items() {
622 $this->prepare_column_headers();
623
624 $per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
625
626 $query = array(
627 'per_page' => $per_page,
628 'offset' => $this->get_items_offset(),
629 'status' => $this->get_request_status(),
630 'orderby' => $this->get_request_orderby(),
631 'order' => $this->get_request_order(),
632 'search' => $this->get_request_search_query(),
633 );
634
635 /**
636 * Change query arguments to query for past-due actions.
637 * Past-due actions have the 'pending' status and are in the past.
638 * This is needed because registering 'past-due' as a status is overkill.
639 */
640 if ( 'past-due' === $this->get_request_status() ) {
641 $query['status'] = ActionScheduler_Store::STATUS_PENDING;
642 $query['date'] = as_get_datetime_object();
643 }
644
645 $this->items = array();
646
647 $total_items = $this->store->query_actions( $query, 'count' );
648
649 $status_labels = $this->store->get_status_labels();
650 $show_claim_id = in_array( $this->get_request_status(), self::$statuses_with_claim_id, true );
651
652 foreach ( $this->store->query_actions( $query ) as $action_id ) {
653 try {
654 $action = $this->store->fetch_action( $action_id );
655 } catch ( Exception $e ) {
656 continue;
657 }
658 if ( is_a( $action, 'ActionScheduler_NullAction' ) ) {
659 continue;
660 }
661 $status_name = $this->store->get_status( $action_id );
662 $this->items[ $action_id ] = array(
663 'ID' => $action_id,
664 'hook' => $action->get_hook(),
665 'status_name' => $status_name,
666 'status' => $status_labels[ $status_name ],
667 'args' => $action->get_args(),
668 'group' => $action->get_group(),
669 'log_entries' => $this->logger->get_logs( $action_id ),
670 'claim_id' => $show_claim_id ? $this->store->get_claim_id( $action_id ) : null,
671 'recurrence' => $this->get_recurrence( $action ),
672 'schedule' => $action->get_schedule(),
673 );
674 }
675
676 $this->set_pagination_args(
677 array(
678 'total_items' => $total_items,
679 'per_page' => $per_page,
680 'total_pages' => ceil( $total_items / $per_page ),
681 )
682 );
683
684 $this->status_counts = $this->store->action_counts() + $this->store->extra_action_counts();
685 }
686
687 /**
688 * Get the text to display in the search box on the list table.
689 */
690 protected function get_search_box_button_text() {
691 return __( 'Search hook, args, action ID and claim ID', 'action-scheduler' );
692 }
693
694 /**
695 * {@inheritDoc}
696 */
697 protected function get_per_page_option_name() {
698 return str_replace( '-', '_', $this->screen->id ) . '_per_page';
699 }
700 }
701