Queue Triage screen. * * @package QueueTriage */ namespace QueueTriage; defined( 'ABSPATH' ) || exit; /** * Registers and renders the read-only screen. It has no forms and handles no input. */ class Admin_Page { const SLUG = 'queue-triage'; /** * The screen's hook suffix. * * @var string */ private static $hook_suffix = ''; /** * Hooks the screen in. */ public static function register() { add_action( 'admin_menu', array( __CLASS__, 'add_page' ) ); add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue' ) ); add_filter( 'plugin_action_links_' . plugin_basename( QUEUE_TRIAGE_FILE ), array( __CLASS__, 'action_links' ) ); } /** * The capability needed to see the screen. * * @return string */ public static function capability() { /** * Filters the capability needed to see Queue Triage's screen, Site Health tests and report. * * @param string $capability Default manage_options. */ return (string) apply_filters( 'queue_triage_capability', 'manage_options' ); } /** * Adds the page under Tools. */ public static function add_page() { $suffix = add_management_page( __( 'Asyncra Queue Triage for Action Scheduler', 'asyncra-queue-triage' ), __( 'Queue Triage', 'asyncra-queue-triage' ), self::capability(), self::SLUG, array( __CLASS__, 'render' ) ); self::$hook_suffix = $suffix ? $suffix : ''; } /** * The screen's URL. * * @return string */ public static function url() { return admin_url( 'tools.php?page=' . self::SLUG ); } /** * Adds a link to the screen on the Plugins list. * * @param string[] $links Action links. * @return string[] */ public static function action_links( $links ) { if ( current_user_can( self::capability() ) ) { array_unshift( $links, '' . esc_html__( 'Open report', 'asyncra-queue-triage' ) . '' ); } return $links; } /** * Loads the stylesheet and the copy button's script on this screen only. * * @param string $hook_suffix Current screen. */ public static function enqueue( $hook_suffix ) { if ( '' === self::$hook_suffix || $hook_suffix !== self::$hook_suffix ) { return; } wp_enqueue_style( 'queue-triage-admin', plugins_url( 'assets/admin.css', QUEUE_TRIAGE_FILE ), array(), QUEUE_TRIAGE_VERSION ); wp_enqueue_script( 'queue-triage-admin', plugins_url( 'assets/admin.js', QUEUE_TRIAGE_FILE ), array(), QUEUE_TRIAGE_VERSION, true ); wp_localize_script( 'queue-triage-admin', 'queueTriage', array( 'copied' => __( 'Copied', 'asyncra-queue-triage' ), 'failed' => __( 'Select the text and copy it', 'asyncra-queue-triage' ), ) ); } /** * Link to Action Scheduler's own screen, filtered by status and hook. * * @param string $status Status. * @param string $hook Hook to search for. * @return string */ public static function scheduled_actions_url( $status = '', $hook = '' ) { $args = array( 'page' => 'action-scheduler' ); if ( '' !== $status ) { $args['status'] = $status; } if ( '' !== $hook ) { $args['s'] = $hook; } return add_query_arg( array_map( 'rawurlencode', $args ), admin_url( 'tools.php' ) ); } /** * Renders the screen. */ public static function render() { if ( ! current_user_can( self::capability() ) ) { wp_die( esc_html__( 'Sorry, you are not allowed to view this page.', 'asyncra-queue-triage' ) ); } $report = Plugin::report(); $data = $report['data']; $env = $data['environment']; $now = (int) $data['now']; echo '
'; esc_html_e( 'A read-only report on the Action Scheduler queue. Nothing on this page runs, retries, cancels or deletes an action.', 'asyncra-queue-triage' ); if ( $env['loaded'] ) { echo ' ' . esc_html__( 'Open Scheduled Actions', 'asyncra-queue-triage' ) . ''; } echo '
'; self::render_checks( $report['checks'] ); if ( ! $data['has_tables'] ) { self::render_environment( $data ); echo ''; printf( /* translators: %s: date and time. */ esc_html__( 'Read at %s. Reload the page to read again.', 'asyncra-queue-triage' ), esc_html( wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $now ) ) ); echo '
'; echo ''; } /** * The status name shown on a check. * * @param string $status Status. * @return string */ private static function status_name( $status ) { if ( Report::CRITICAL === $status ) { return __( 'Critical', 'asyncra-queue-triage' ); } if ( Report::RECOMMENDED === $status ) { return __( 'Recommended', 'asyncra-queue-triage' ); } return __( 'Good', 'asyncra-queue-triage' ); } /** * The checks at the top. * * @param array $checks Checks. */ private static function render_checks( array $checks ) { echo '' . esc_html( $check['detail'] ) . '
'; echo '' . esc_html__( 'There are no failed actions.', 'asyncra-queue-triage' ) . '
'; return; } echo ''; printf( /* translators: 1: number of actions read, 2: number of failed actions, 3: number of groups. */ esc_html__( 'The newest %1$s of %2$s failed actions, grouped by hook and by their error with ids, dates, amounts and other values taken out: %3$s groups.', 'asyncra-queue-triage' ), esc_html( Format::number( $read ) ), esc_html( Format::number( $total ) ), esc_html( Format::number( count( $groups ) ) ) ); echo ' '; esc_html_e( 'The labels come from fixed rules that read the error and the hook name. They cannot tell whether a rerun is safe, so read each group before you act on it.', 'asyncra-queue-triage' ); echo '
'; $by_label = array( Triage::FIX => 0, Triage::RETRY => 0, Triage::REVIEW => 0, ); foreach ( $groups as $group ) { $by_label[ $group['label'] ] += $group['count']; } echo '| ' . esc_html__( 'Label', 'asyncra-queue-triage' ) . ' | '; echo '' . esc_html__( 'Hook', 'asyncra-queue-triage' ) . ' | '; echo '' . esc_html__( 'Failures', 'asyncra-queue-triage' ) . ' | '; echo '' . esc_html__( 'Error and what to check', 'asyncra-queue-triage' ) . ' | '; echo '' . esc_html__( 'Latest', 'asyncra-queue-triage' ) . ' | '; echo '
|---|---|---|---|---|
| ' . esc_html( Report::label_name( $group['label'] ) ) . ' | '; echo '' . esc_html( $group['hook'] ) . '';
if ( $group['groups'] ) {
echo '' . esc_html( implode( ', ', $group['groups'] ) ) . ''; } if ( $data['environment']['loaded'] ) { echo ' ' . esc_html__( 'View these actions', 'asyncra-queue-triage' ) . ''; } echo ' | ';
echo '' . esc_html( Format::number( $group['count'] ) );
if ( $group['recurring'] > 0 ) {
echo ' ' . esc_html( sprintf( /* translators: %s: number of actions. */ __( '%s recurring', 'asyncra-queue-triage' ), Format::number( $group['recurring'] ) ) ) . ''; } echo ' | ';
echo '';
if ( '' === $group['example'] ) {
echo '' . esc_html__( 'No failure message in the log.', 'asyncra-queue-triage' ) . '';
} else {
echo '' . esc_html( $group['example'] ) . '';
}
echo '' . esc_html( Report::cause_text( $group['cause'] ) ) . ' '; foreach ( Report::group_notes( $group ) as $note ) { echo '' . esc_html( $note ) . ' '; } echo ' | ';
echo '' . ( null === $group['latest'] ? '–' : esc_html( sprintf( /* translators: %s: duration, such as "3 hours". */ __( '%s ago', 'asyncra-queue-triage' ), Format::duration( $now - $group['latest'] ) ) ) ) . ' | '; echo '
' . esc_html( sprintf( /* translators: %s: number of groups. */ __( '%s smaller groups are not shown.', 'asyncra-queue-triage' ), Format::number( count( $groups ) - 100 ) ) ) . '
'; } } /** * Past-due actions by hook. * * @param array $data Collected data. */ private static function render_past_due( array $data ) { $past_due = $data['past_due']; $now = (int) $data['now']; echo '' . esc_html__( 'No pending action is past its scheduled time.', 'asyncra-queue-triage' ) . '
'; return; } echo ''; printf( /* translators: 1: number of actions, 2: number of actions. */ esc_html__( '%1$s pending actions are past their scheduled time; %2$s of them by more than a day. The ten hooks with the most:', 'asyncra-queue-triage' ), esc_html( Format::number( $past_due['count'] ) ), esc_html( Format::number( $past_due['over_day'] ) ) ); echo '
'; echo '| ' . esc_html__( 'Hook', 'asyncra-queue-triage' ) . ' | '; echo '' . esc_html__( 'Past due', 'asyncra-queue-triage' ) . ' | '; echo '' . esc_html__( 'Oldest is late by', 'asyncra-queue-triage' ) . ' | '; echo '
|---|---|---|
' . esc_html( $row['hook'] ) . '';
if ( $data['environment']['loaded'] ) {
echo ' ' . esc_html__( 'View', 'asyncra-queue-triage' ) . '';
}
echo ' | ' . esc_html( Format::number( $row['count'] ) ) . ' | '; echo '' . ( null === $row['oldest'] ? '–' : esc_html( Format::duration( $now - $row['oldest'] ) ) ) . ' |
| ' . esc_html__( 'Status', 'asyncra-queue-triage' ) . ' | ' . esc_html__( 'Actions', 'asyncra-queue-triage' ) . ' |
|---|---|
| '; if ( $data['environment']['loaded'] ) { echo '' . esc_html( $status ) . ''; } else { echo esc_html( $status ); } echo ' | ' . esc_html( Format::number( $count ) ) . ' |
| ' . esc_html__( 'in progress for more than 5 minutes', 'asyncra-queue-triage' ) . ' | ' . esc_html( Format::number( $data['running']['long'] ) ) . ' |
| ' . esc_html__( 'open claims (older than 5 minutes)', 'asyncra-queue-triage' ) . ' | ' . esc_html( Format::number( $data['claims']['count'] ) . ' (' . Format::number( $data['claims']['stale'] ) . ')' ) . ' |
| ' . esc_html__( 'Table', 'asyncra-queue-triage' ) . ' | '; echo '' . esc_html__( 'Rows (estimate)', 'asyncra-queue-triage' ) . ' | '; echo '' . esc_html__( 'Size', 'asyncra-queue-triage' ) . ' | '; echo '
|---|---|---|
' . esc_html( $data['tables'][ $key ] ) . ' | ';
echo '' . esc_html( Format::number( $table['rows'] ) ) . ' | '; echo '' . esc_html( Format::bytes( $table['bytes'] ) ) . ' |
' . esc_html__( 'Row counts and sizes are the database server\'s estimates and can lag behind.', 'asyncra-queue-triage' ) . '
'; echo '' . esc_html__( 'No active plugin or theme loads Action Scheduler.', 'asyncra-queue-triage' ) . '
'; return; } $rows = array( __( 'Version in use', 'asyncra-queue-triage' ) => $env['version'] . ( '' !== $env['source'] ? ' (' . $env['source'] . ')' : '' ), __( 'Data store', 'asyncra-queue-triage' ) => $env['store'], ); if ( $env['sources'] ) { $copies = array(); foreach ( $env['sources'] as $source ) { $copies[] = $source['version'] . ' ' . $source['path']; } $rows[ __( 'Copies bundled by plugins', 'asyncra-queue-triage' ) ] = implode( "\n", $copies ); } self::render_rows( $rows ); if ( ! $env['custom_tables'] ) { echo '' . esc_html__( 'This site does not use Action Scheduler\'s own tables as its data store, so the figures above may be incomplete.', 'asyncra-queue-triage' ) . '
'; } } /** * The copyable support report. * * @param array $report Report. */ private static function render_support( array $report ) { echo '' . esc_html__( 'Counts, settings, hook names and error signatures, with ids, values, email addresses, IP addresses and server paths left out. Read it before you share it.', 'asyncra-queue-triage' ) . '
'; echo ''; echo ''; } /** * A two-column table of labels and values. * * @param array
| ' . esc_html( $label ) . ' | ' . nl2br( esc_html( $value ) ) . ' |
|---|