'crontrol-event', 'plural' => 'crontrol-events', 'ajax' => false, 'screen' => 'crontrol-events', ) ); } /** * Prepares the list table items and arguments. */ public function prepare_items() { self::$persistent_core_hooks = \Crontrol\get_persistent_core_hooks(); self::$can_edit_files = current_user_can( 'edit_files' ); self::$count_by_hook = count_by_hook(); $events = get(); $this->all_events = $events; if ( ! empty( $_GET['s'] ) ) { $s = sanitize_text_field( wp_unslash( $_GET['s'] ) ); $events = array_filter( $events, function( $event ) use ( $s ) { return ( false !== strpos( $event->hook, $s ) ); } ); } if ( ! empty( $_GET['hooks_type'] ) ) { $hooks_type = sanitize_text_field( $_GET['hooks_type'] ); $filtered = $this->get_filtered_events( $events ); if ( isset( $filtered[ $hooks_type ] ) ) { $events = $filtered[ $hooks_type ]; } } $count = count( $events ); $per_page = 50; $offset = ( $this->get_pagenum() - 1 ) * $per_page; $this->items = array_slice( $events, $offset, $per_page ); $has_late = (bool) array_filter( array_map( __NAMESPACE__ . '\\is_late', $this->items ) ); if ( $has_late ) { add_action( 'admin_notices', function() { printf( '

%1$s

%3$s

', /* translators: %s: Help page URL. */ esc_html__( 'One or more cron events have missed their schedule.', 'wp-crontrol' ), 'https://github.com/johnbillion/wp-crontrol/wiki/Cron-events-that-have-missed-their-schedule', esc_html__( 'More information', 'wp-crontrol' ) ); } ); } $this->set_pagination_args( array( 'total_items' => $count, 'per_page' => $per_page, 'total_pages' => ceil( $count / $per_page ), ) ); } /** * Returns events filtered by various parameters * * @param array $events The list of all events. * @return array Array of filtered events keyed by parameter. */ protected function get_filtered_events( array $events ) { $all_core_hooks = \Crontrol\get_all_core_hooks(); $filtered = array( 'all' => $events, ); $filtered['noaction'] = array_filter( $events, function( $event ) { $hook_callbacks = \Crontrol\get_hook_callbacks( $event->hook ); return empty( $hook_callbacks ); } ); $filtered['core'] = array_filter( $events, function( $event ) use ( $all_core_hooks ) { return ( in_array( $event->hook, $all_core_hooks, true ) ); } ); $filtered['custom'] = array_filter( $events, function( $event ) use ( $all_core_hooks ) { return ( ! in_array( $event->hook, $all_core_hooks, true ) ); } ); return $filtered; } /** * Returns an array of column names for the table. * * @return string[] Array of column names keyed by their ID. */ public function get_columns() { return array( 'cb' => '', 'crontrol_hook' => __( 'Hook', 'wp-crontrol' ), 'crontrol_args' => __( 'Arguments', 'wp-crontrol' ), 'crontrol_next' => sprintf( /* translators: %s: UTC offset */ __( 'Next Run (%s)', 'wp-crontrol' ), \Crontrol\get_utc_offset() ), 'crontrol_actions' => __( 'Action', 'wp-crontrol' ), 'crontrol_recurrence' => __( 'Recurrence', 'wp-crontrol' ), ); } /** * Columns to make sortable. * * @return array */ public function get_sortable_columns() { return array( 'crontrol_hook' => array( 'crontrol_hook', true ), 'crontrol_next' => array( 'crontrol_next', false ), ); } /** * Returns an array of CSS class names for the table. * * @return string[] Array of class names. */ protected function get_table_classes() { return array( 'widefat', 'striped', $this->_args['plural'] ); } /** * Get an associative array ( option_name => option_title ) with the list * of bulk actions available on this table. * * @since 3.1.0 * * @return array */ protected function get_bulk_actions() { return array( 'delete_crons' => esc_html__( 'Delete', 'wp-crontrol' ), ); } /** * Display the list of hook types. * * @return string[] */ public function get_views() { $filtered = $this->get_filtered_events( $this->all_events ); $views = array(); $hooks_type = ( ! empty( $_GET['hooks_type'] ) ? $_GET['hooks_type'] : 'all' ); $types = array( 'all' => __( 'All events', 'wp-crontrol' ), 'noaction' => __( 'Events with no action', 'wp-crontrol' ), 'core' => __( 'WordPress core events', 'wp-crontrol' ), 'custom' => __( 'Custom events', 'wp-crontrol' ), ); $url = admin_url( 'tools.php?page=crontrol_admin_manage_page' ); foreach ( $types as $key => $type ) { $views[ $key ] = sprintf( '%3$s (%4$s)', 'all' === $key ? $url : add_query_arg( 'hooks_type', $key, $url ), $hooks_type === $key ? ' class="current"' : '', $type, count( $filtered[ $key ] ) ); } return $views; } /** * Generates content for a single row of the table. * * @param object $event The current event. */ public function single_row( $event ) { $classes = array(); if ( ( 'crontrol_cron_job' === $event->hook ) && ! empty( $event->args['syntax_error_message'] ) ) { $classes[] = 'crontrol-error'; } $schedule_name = ( $event->interval ? get_schedule_name( $event ) : false ); if ( is_wp_error( $schedule_name ) ) { $classes[] = 'crontrol-error'; } $callbacks = \Crontrol\get_hook_callbacks( $event->hook ); if ( ! $callbacks ) { $classes[] = 'crontrol-warning'; } else { foreach ( $callbacks as $callback ) { if ( ! empty( $callback['callback']['error'] ) ) { $classes[] = 'crontrol-error'; break; } } } if ( is_late( $event ) || is_too_frequent( $event ) ) { $classes[] = 'crontrol-warning'; } printf( '', esc_attr( implode( ' ', $classes ) ) ); $this->single_row_columns( $event ); echo ''; } /** * Generates and displays row action links for the table. * * @param stdClass $event The cron event for the current row. * @param string $column_name Current column name. * @param string $primary Primary column name. * @return string The row actions HTML. */ protected function handle_row_actions( $event, $column_name, $primary ) { if ( $primary !== $column_name ) { return ''; } $links = array(); if ( ( 'crontrol_cron_job' !== $event->hook ) || self::$can_edit_files ) { $link = array( 'page' => 'crontrol_admin_manage_page', 'action' => 'edit-cron', 'id' => rawurlencode( $event->hook ), 'sig' => rawurlencode( $event->sig ), 'next_run_utc' => rawurlencode( $event->time ), ); $link = add_query_arg( $link, admin_url( 'tools.php' ) ); $links[] = "" . esc_html__( 'Edit', 'wp-crontrol' ) . ''; } $link = array( 'page' => 'crontrol_admin_manage_page', 'action' => 'run-cron', 'id' => rawurlencode( $event->hook ), 'sig' => rawurlencode( $event->sig ), 'next_run_utc' => rawurlencode( $event->time ), ); $link = add_query_arg( $link, admin_url( 'tools.php' ) ); $link = wp_nonce_url( $link, "run-cron_{$event->hook}_{$event->sig}" ); $links[] = "" . esc_html__( 'Run Now', 'wp-crontrol' ) . ''; if ( ! in_array( $event->hook, self::$persistent_core_hooks, true ) && ( ( 'crontrol_cron_job' !== $event->hook ) || self::$can_edit_files ) ) { $link = array( 'page' => 'crontrol_admin_manage_page', 'action' => 'delete-cron', 'id' => rawurlencode( $event->hook ), 'sig' => rawurlencode( $event->sig ), 'next_run_utc' => rawurlencode( $event->time ), ); $link = add_query_arg( $link, admin_url( 'tools.php' ) ); $link = wp_nonce_url( $link, "delete-cron_{$event->hook}_{$event->sig}_{$event->time}" ); $links[] = "" . esc_html__( 'Delete', 'wp-crontrol' ) . ''; } if ( function_exists( 'wp_unschedule_hook' ) && ! in_array( $event->hook, self::$persistent_core_hooks, true ) && ( 'crontrol_cron_job' !== $event->hook ) ) { if ( self::$count_by_hook[ $event->hook ] > 1 ) { $link = array( 'page' => 'crontrol_admin_manage_page', 'action' => 'delete-hook', 'id' => rawurlencode( $event->hook ), ); $link = add_query_arg( $link, admin_url( 'tools.php' ) ); $link = wp_nonce_url( $link, "delete-hook_{$event->hook}" ); $text = sprintf( /* translators: %s: Number of events with a given name */ _n( 'Delete All %s', 'Delete All %s', self::$count_by_hook[ $event->hook ], 'wp-crontrol' ), number_format_i18n( self::$count_by_hook[ $event->hook ] ) ); $links[] = "" . esc_html( $text ) . ''; } } return $this->row_actions( $links ); } /** * Outputs the checkbox cell of a table row. * * @param stdClass $event The cron event for the current row. * @return string The cell output. */ protected function column_cb( $event ) { $id = sprintf( 'wp-crontrol-delete-%1$s-%2$s-%3$s', $event->time, rawurlencode( $event->hook ), $event->sig ); if ( in_array( $event->hook, self::$persistent_core_hooks, true ) ) { return sprintf( ' %s', esc_html__( 'This is a WordPress core event and cannot be deleted', 'wp-crontrol' ) ); } elseif ( ( 'crontrol_cron_job' !== $event->hook ) || self::$can_edit_files ) { return sprintf( ' ', esc_attr( $id ), esc_html__( 'Select this row', 'wp-crontrol' ), esc_attr( $event->time ), esc_attr( rawurlencode( $event->hook ) ), esc_attr( $event->sig ) ); } return ''; } /** * Returns the output for the hook name cell of a table row. * * @param stdClass $event The cron event for the current row. * @return string The cell output. */ protected function column_crontrol_hook( $event ) { if ( 'crontrol_cron_job' === $event->hook ) { if ( ! empty( $event->args['name'] ) ) { /* translators: 1: The name of the PHP cron event. */ return '' . esc_html( sprintf( __( 'PHP Cron (%s)', 'wp-crontrol' ), $event->args['name'] ) ) . ''; } else { return '' . esc_html__( 'PHP Cron', 'wp-crontrol' ) . ''; } } return esc_html( $event->hook ); } /** * Returns the output for the arguments cell of a table row. * * @param stdClass $event The cron event for the current row. * @return string The cell output. */ protected function column_crontrol_args( $event ) { if ( ! empty( $event->args ) ) { $args = \Crontrol\json_output( $event->args ); } if ( 'crontrol_cron_job' === $event->hook ) { $return = '' . esc_html__( 'PHP Code', 'wp-crontrol' ) . ''; if ( ! empty( $event->args['syntax_error_message'] ) ) { $return .= '
'; $return .= sprintf( /* translators: 1: Line number, 2: Error message text */ esc_html__( 'Line %1$s: %2$s', 'wp-crontrol' ), esc_html( number_format_i18n( $event->args['syntax_error_line'] ) ), esc_html( $event->args['syntax_error_message'] ) ); $return .= ''; } if ( ! empty( $event->args['code'] ) ) { $lines = explode( "\n", trim( $event->args['code'] ) ); $code = reset( $lines ); $code = substr( $code, 0, 50 ); $return .= '
'; $return .= sprintf( '%s…', esc_html( $code ) ); } return $return; } else { if ( empty( $event->args ) ) { return sprintf( '%s', esc_html__( 'None', 'wp-crontrol' ) ); } else { return sprintf( '
%s
', esc_html( $args ) ); } } } /** * Returns the output for the actions cell of a table row. * * @param stdClass $event The cron event for the current row. * @return string The cell output. */ protected function column_crontrol_actions( $event ) { $hook_callbacks = \Crontrol\get_hook_callbacks( $event->hook ); if ( 'crontrol_cron_job' === $event->hook ) { return '' . esc_html__( 'WP Crontrol', 'wp-crontrol' ) . ''; } elseif ( ! empty( $hook_callbacks ) ) { $callbacks = array(); foreach ( $hook_callbacks as $callback ) { $callbacks[] = \Crontrol\output_callback( $callback ); } return implode( '
', $callbacks ); // WPCS:: XSS ok. } else { return sprintf( ' %s', esc_html__( 'None', 'wp-crontrol' ) ); } } /** * Returns the output for the next run cell of a table row. * * @param stdClass $event The cron event for the current row. * @return string The cell output. */ protected function column_crontrol_next( $event ) { $date_utc = gmdate( 'Y-m-d\TH:i:s+00:00', $event->time ); $date_local = get_date_from_gmt( date( 'Y-m-d H:i:s', $event->time ), 'Y-m-d H:i:s' ); $time = sprintf( '', esc_attr( $date_utc ), esc_html( $date_local ) ); $until = $event->time - time(); $late = is_late( $event ); if ( $late ) { // Show a warning for events that are late. $ago = sprintf( /* translators: %s: Time period, for example "8 minutes" */ __( '%s ago', 'wp-crontrol' ), \Crontrol\interval( abs( $until ) ) ); return sprintf( '%s
%s', $time, esc_html( $ago ) ); } return sprintf( '%s
%s', $time, esc_html( \Crontrol\interval( $until ) ) ); } /** * Returns the output for the recurrence cell of a table row. * * @param stdClass $event The cron event for the current row. * @return string The cell output. */ protected function column_crontrol_recurrence( $event ) { if ( $event->schedule ) { $schedule_name = get_schedule_name( $event ); if ( is_wp_error( $schedule_name ) ) { return sprintf( ' %s', esc_html( $schedule_name->get_error_message() ) ); } elseif ( is_too_frequent( $event ) ) { return sprintf( '%1$s
%2$s
', esc_html( $schedule_name ), sprintf( /* translators: 1: The name of the configuration constant, 2: The value of the configuration constant */ esc_html__( 'This interval is less than the %1$s constant which is set to %2$s seconds. Events that use it may not run on time.', 'wp-crontrol' ), 'WP_CRON_LOCK_TIMEOUT', intval( WP_CRON_LOCK_TIMEOUT ) ) ); } else { return esc_html( $schedule_name ); } } else { return esc_html__( 'Non-repeating', 'wp-crontrol' ); } } /** * Outputs a message when there are no items to show in the table. */ public function no_items() { if ( empty( $_GET['s'] ) && empty( $_GET['hooks_type'] ) ) { esc_html_e( 'There are currently no scheduled cron events.', 'wp-crontrol' ); } else { esc_html_e( 'No matching cron events.', 'wp-crontrol' ); } } }