'generated-cron', 'plural' => 'generated-crons', 'ajax' => true, 'screen' => WP_Helper::get_wp_screen(), ) ); self::$columns = self::manage_columns( array() ); self::$table_name = 'advanced_crons'; } /** * Inits the module hook. * * @return void * * @since 2.8.1 */ public static function hooks_init() { \add_action( 'admin_print_styles-' . self::PAGE_SLUG, array( Settings::class, 'print_styles' ) ); \add_action( 'admin_post_' . self::UPDATE_ACTION, array( Crons_View::class, 'update_cron' ) ); \add_action( 'admin_post_' . self::NEW_ACTION, array( Crons_View::class, 'new_cron' ) ); \add_action( 'admin_post_' . self::PLUGIN_FILTER_ACTION, array( Crons_View::class, 'plugin_filter_action' ) ); \add_action( 'admin_post_' . self::SITE_FILTER_ACTION, array( Crons_View::class, 'site_filter_action' ) ); } /** * Adds the module to the main plugin menu * * @return void * * @since 2.8.1 */ public static function menu_add() { /* Crons */ $cron_hook = \add_submenu_page( Logs_List::MENU_SLUG, ADVAN_INNER_NAME, \esc_html__( 'Crons viewer', '0-day-analytics' ), 'manage_options', self::CRON_MENU_SLUG, array( Crons_View::class, 'analytics_cron_page' ), 4 ); self::add_screen_options( $cron_hook ); \add_filter( 'manage_' . $cron_hook . '_columns', array( self::class, 'manage_columns' ) ); \add_action( 'load-' . $cron_hook, array( Settings::class, 'aadvana_common_help' ) ); // Process actions early to avoid header warnings on redirects. \add_action( 'load-' . $cron_hook, array( self::class, 'process_actions_load' ) ); /* Crons end */ } /** * Handle cron table actions on the early page load hook. * * @return void * * @since 4.2.0 */ public static function process_actions_load() { if ( ! \current_user_can( 'manage_options' ) ) { return; } $table = new self( array() ); $table->handle_table_actions(); } /** * Displays the search box. * * @since 1.1.0 * * @param string $text The 'submit' button label. * @param string $input_id ID attribute value for the search input field. */ public function search_box( $text, $input_id ) { if ( empty( $_REQUEST[ self::SEARCH_INPUT ] ) && ! self::are_there_items() ) { return; } $input_id = $input_id . '-search-input'; ?> '', // to display the checkbox. 'hook' => __( 'Hook', '0-day-analytics' ), 'schedule' => esc_html( sprintf( /* translators: %s: UTC offset */ __( 'Next Run (%s)', '0-day-analytics' ), WP_Helper::get_timezone_location() ), ), 'recurrence' => __( 'Interval', '0-day-analytics' ), 'args' => __( 'Args', '0-day-analytics' ), 'actions' => __( 'Actions', '0-day-analytics' ), ); // Insert site column for multisite network admins for clarity. if ( function_exists( 'is_multisite' ) && is_multisite() && current_user_can( 'manage_network' ) ) { $admin_fields = array( 'cb' => $admin_fields['cb'], 'hook' => $admin_fields['hook'], 'site' => __( 'Site', '0-day-analytics' ), 'schedule' => $admin_fields['schedule'], 'recurrence' => $admin_fields['recurrence'], 'args' => $admin_fields['args'], 'actions' => $admin_fields['actions'], ); } self::$admin_columns = \array_merge( $admin_fields, $columns ); } return self::$admin_columns; } /** * Returns the table name. * * @since 1.1.0 */ public static function get_table_name(): string { return self::$table_name; } /** * Prepares the list of items for displaying. * * Query, filter data, handle sorting, and pagination, and any other data-manipulation required prior to rendering * * @since 1.1.0 */ public function prepare_items() { $columns = $this->get_columns(); $hidden = array(); $sortable = $this->get_sortable_columns(); $this->_column_headers = array( $columns, $hidden, $sortable ); $this->fetch_table_data(); $hidden = \get_user_option( 'manage' . WP_Helper::get_wp_screen()->id . 'columnshidden', false ); if ( ! $hidden ) { $hidden = array(); } $this->_column_headers = array( self::$columns, $hidden, $sortable ); } /** * Returns the currently hidden column headers for the current user * * @return array * * @since 1.1.0 */ public static function get_hidden_columns() { return array_filter( (array) \get_user_option( 'manage' . Settings::get_main_menu_page_hook() . 'columnshidden', false ) ); } /** * Get a list of sortable columns. The format is: * 'internal-name' => 'orderby' * or * 'internal-name' => array( 'orderby', true ). * * The second format will make the initial sorting order be descending * * @since 1.1.0 * * @return array */ public function get_sortable_columns() { return array( 'hook' => array( 'hook', false ), 'schedule' => array( 'schedule', false, null, null, 'asc' ), 'recurrence' => array( 'recurrence', false ), ); } /** * Text displayed when no user data is available. * * @since 1.1.0 * * @return void */ public function no_items() { \esc_html_e( 'No crons found', '0-day-analytics' ); } /** * Fetch table data from the WordPress database. * * @param array $args - Arguments for fetching data. * * @since 1.1.0 * * @return array */ public function fetch_table_data( array $args = array() ) { $this->items = self::get_cron_items(); return $this->items; } /** * Collect cron items. * * @param bool $no_type_filtering - When true, events returned are without type filtering applied. * * @return array * * @since 1.1.0 * @since 2.9.0 - Introduced flag getting the events without the type filtering */ public static function get_cron_items( bool $no_type_filtering = false ): array { if ( null === self::$read_items || $no_type_filtering ) { self::$read_items = Crons_Helper::get_events(); } if ( isset( $_REQUEST['schedules_filter'] ) && ! empty( $_REQUEST['schedules_filter'] ) ) { $s = \sanitize_text_field( \wp_unslash( $_REQUEST['schedules_filter'] ) ); if ( 'single_event' === $s ) { $s = ''; self::$read_items = array_filter( self::$read_items, function ( $event ) use ( $s ) { return ( $s === $event['recurrence'] ); } ); } else { self::$read_items = array_filter( self::$read_items, function ( $event ) use ( $s ) { return ( false !== strpos( $event['recurrence'], $s ) ); } ); } } if ( ! empty( $_REQUEST[ self::SEARCH_INPUT ] ) && is_string( $_REQUEST[ self::SEARCH_INPUT ] ) ) { $s = \sanitize_text_field( \wp_unslash( $_REQUEST[ self::SEARCH_INPUT ] ) ); self::$read_items = array_filter( self::$read_items, function ( $event ) use ( $s ) { return ( false !== strpos( $event['hook'], $s ) ); } ); } // Plugin filtering (only when not requesting unfiltered set for counts). $site_filter = ( isset( $_REQUEST['site'] ) && '' !== trim( (string) $_REQUEST['site'] ) && -1 !== (int) $_REQUEST['site'] ) ? (int) sanitize_text_field( wp_unslash( $_REQUEST['site'] ) ) : -1; if ( -1 !== $site_filter && function_exists( 'is_multisite' ) && is_multisite() ) { self::$read_items = Crons_Helper::get_events_for_site( $site_filter ); } if ( isset( $_REQUEST['plugin'] ) && '' !== trim( (string) $_REQUEST['plugin'] ) && -1 !== (int) $_REQUEST['plugin'] ) { $plugin_filter = sanitize_text_field( wp_unslash( $_REQUEST['plugin'] ) ); self::$read_items = array_filter( self::$read_items, function ( $event ) use ( $plugin_filter ) { if ( isset( $event['plugin_slugs'] ) && is_array( $event['plugin_slugs'] ) ) { return in_array( $plugin_filter, $event['plugin_slugs'], true ); } return ( isset( $event['plugin_slug'] ) && '' !== $event['plugin_slug'] && $event['plugin_slug'] === $plugin_filter ); } ); } if ( ! $no_type_filtering ) { if ( ! empty( $_REQUEST['event_type'] ) && is_string( $_REQUEST['event_type'] ) ) { $hooks_type = \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ); $filtered = self::get_filtered_events( self::$read_items ); if ( isset( $filtered[ $hooks_type ] ) ) { self::$read_items = $filtered[ $hooks_type ]; } } } if ( null !== self::$read_items ) { uasort( self::$read_items, array( __CLASS__, 'uasort_order_events' ) ); } return self::$read_items ?? array(); } /** * Render a column when no column specific method exists. * * Use that method for common rendering and separate columns logic in different methods. See below. * * @param array $item - Array with the current row values. * @param string $column_name - The name of the currently processed column. * * @return mixed * * @since 1.1.0 */ public function column_default( $item, $column_name ) { return self::format_column_value( $item, $column_name ); } /** * Render a column when no column specific method exists. * * Use that method for common rendering and separate columns logic in different methods. See below. * * @param array $item - Array with the current row values. * @param string $column_name - The name of the currently processed column. * * @return mixed * * @since 1.1.0 */ public static function format_column_value( $item, $column_name ) { switch ( $column_name ) { case 'site': if ( isset( $item['site_id'] ) ) { $site_id = (int) $item['site_id']; $site_name = isset( $item['site_name'] ) ? $item['site_name'] : ''; return '' . esc_html( $site_id ) . ' ' . ( ! empty( $site_name ) ? esc_html( $site_name ) : '' ); } return esc_html__( 'N/A', '0-day-analytics' ); case 'hook': $query_args_view_data = array(); $query_args_view_data['hash'] = $item['hash']; $query_args_view_data['_wpnonce'] = \wp_create_nonce( 'bulk-custom-delete' ); $actions['delete'] = '' . \esc_html__( 'Delete', '0-day-analytics' ) . ''; $actions['run'] = '' . \esc_html__( 'Run', '0-day-analytics' ) . ''; $edit_url = \remove_query_arg( array( 'updated', 'deleted' ), \add_query_arg( array( 'action' => 'edit_cron', 'hash' => $item['hash'], self::SEARCH_INPUT => self::escaped_search_input(), '_wpnonce' => $query_args_view_data['_wpnonce'], ) ) ); $actions['edit'] = '' . \esc_html__( 'Edit', '0-day-analytics' ) . ''; $core_crons = ''; if ( in_array( $item['hook'], Crons_Helper::WP_CORE_CRONS, true ) ) { $core_crons = ' '; } else { foreach ( Crons_Helper::WP_CORE_CRONS as $cron_name ) { if ( \str_starts_with( $item['hook'], $cron_name ) ) { $core_crons = ' '; break; } } } return '' . $core_crons . '' . \esc_html( (string) $item['hook'] ) . '' . self::single_row_actions( $actions ); case 'recurrence': return ( ! empty( $item['recurrence'] ) ? \esc_html( (string) $item['recurrence'] ) : __( 'once', '0-day-analytics' ) ); case 'args': if ( empty( $item['args'] ) ) { return __( 'NO', '0-day-analytics' ); } $display_args = is_string( $item['args'] ) ? $item['args'] : wp_json_encode( $item['args'], JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR ); return '
' . \esc_html( (string) $display_args ) . '
'; case 'schedule': return WP_Helper::time_formatter( $item, esc_html__( 'overdue', '0-day-analytics' ) ); case 'actions': $hook_callbacks = Crons_Helper::get_cron_callbacks( $item['hook'] ); if ( ! empty( $hook_callbacks ) ) { $callbacks = array(); foreach ( $hook_callbacks as $callback ) { if ( \key_exists( 'error', $callback['callback'] ) ) { if ( \is_a( $callback['callback']['error'], '\WP_Error' ) ) { $callbacks[] = '' . \esc_html__( 'Error occurred with cron callback', '0-day-analytics' ) . ' - ' . \esc_html( $callback['callback']['error']->get_error_message() ) . ''; } else { $callbacks[] = '' . \esc_html__( 'Unknown error occurred', '0-day-analytics' ) . ''; } } else { $callbacks[] = self::output_filename( $callback['callback']['name'], $callback['callback']['file'], $callback['callback']['line'] ); } if ( isset( $callback['callback']['component'] ) && ! empty( $callback['callback']['component'] ) && isset( $callback['callback']['component']['name'] ) && ! empty( $callback['callback']['component']['name'] ) ) { $callbacks[ \array_key_last( $callbacks ) ] .= '
' . \esc_html( $callback['callback']['component']['name'] ) . ''; } } if ( 'action_scheduler_run_queue' === $item['hook'] ) { if ( \count( $callbacks ) ) { $callbacks[ \array_key_last( $callbacks ) ] .= sprintf( '
%s', \admin_url( 'tools.php?page=action-scheduler' ), \esc_html__( 'View the scheduled actions here »', '0-day-analytics' ) ); } else { $callbacks[] = sprintf( ' %s', \admin_url( 'tools.php?page=action-scheduler' ), \esc_html__( 'View the scheduled actions here »', '0-day-analytics' ) ); } } return '
  1. ' . implode( '

  2. ', $callbacks ) . '
'; // WPCS:: XSS ok. } return ''; default: return isset( $item[ $column_name ] ) ? \esc_html( $item[ $column_name ] ) : __( 'Column "', '0-day-analytics' ) . \esc_html( $column_name ) . __( '" not found', '0-day-analytics' ); } } /** * Get value for checkbox column. * * The special 'cb' column * * @param object $item - A row's data. * * @return string Text to be placed inside the column < td > . * * @since 1.1.0 */ protected function column_cb( $item ) { return sprintf( '' . '' ); } /** * Returns an associative array containing the bulk actions. * * @since 1.1.0 * * @return array */ public function get_bulk_actions() { /** * On hitting apply in bulk actions the url params are set as * ?action=bulk-download&paged=1&action2=-1. * * Action and action2 are set based on the triggers above or below the table */ $actions = array( 'delete' => __( 'Delete', '0-day-analytics' ), 'run' => __( 'Run', '0-day-analytics' ), ); return $actions; } /** * Process actions triggered by the user. * * @since 1.1.0 */ public function handle_table_actions() { if ( ! isset( $_REQUEST[ self::$table_name ] ) ) { return; } // Enforce capability for destructive bulk actions. if ( ! \current_user_can( 'manage_options' ) ) { return; } /** * Note: Table bulk_actions can be identified by checking $_REQUEST['action'] and $_REQUEST['action2']. * * Action - is set if checkbox from top-most select-all is set, otherwise returns -1 * Action2 - is set if checkbox the bottom-most select-all checkbox is set, otherwise returns -1 */ // check for individual row actions. // check for table bulk actions. if ( ( ( isset( $_REQUEST['action'] ) && 'delete' === $_REQUEST['action'] ) || ( isset( $_REQUEST['action2'] ) && 'delete' === $_REQUEST['action2'] ) ) ) { /** * Note: the nonce field is set by the parent class * wp_nonce_field( 'bulk-' . $this->_args['plural'] );. */ WP_Helper::verify_admin_nonce( 'bulk-' . $this->_args['plural'] ); if ( isset( $_REQUEST[ self::$table_name ] ) && \is_array( $_REQUEST[ self::$table_name ] ) ) { foreach ( \wp_unslash( $_REQUEST[ self::$table_name ] ) as $id ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $id = \sanitize_text_field( $id ); if ( ! empty( $id ) ) { // Delete the cron. Crons_Helper::delete_event( $id ); } } } $redirect = \remove_query_arg( array( 'delete', '_wpnonce' ), \add_query_arg( array( self::SEARCH_INPUT => self::escaped_search_input(), 'page' => self::CRON_MENU_SLUG, ), \admin_url( 'admin.php' ) ) ); \wp_safe_redirect( $redirect ); exit; } if ( ( ( isset( $_REQUEST['action'] ) && 'run' === $_REQUEST['action'] ) || ( isset( $_REQUEST['action2'] ) && 'run' === $_REQUEST['action2'] ) ) ) { /** * Note: the nonce field is set by the parent class * wp_nonce_field( 'bulk-' . $this->_args['plural'] );. */ WP_Helper::verify_admin_nonce( 'bulk-' . $this->_args['plural'] ); if ( isset( $_REQUEST[ self::$table_name ] ) && \is_array( $_REQUEST[ self::$table_name ] ) ) { foreach ( \wp_unslash( $_REQUEST[ self::$table_name ] ) as $id ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $id = \sanitize_text_field( $id ); if ( ! empty( $id ) ) { // Delete the cron. Crons_Helper::execute_event( $id ); } } } $redirect = \remove_query_arg( array( 'delete', '_wpnonce' ), \add_query_arg( array( self::SEARCH_INPUT => self::escaped_search_input(), 'page' => self::CRON_MENU_SLUG, ), \admin_url( 'admin.php' ) ) ); \wp_safe_redirect( $redirect ); exit; } } /** * Adds a screen options to the current screen table. * * @param \WP_Hook $hook - The hook object to attach to. * * @return void * * @since 1.1.0 */ public static function add_screen_options( $hook ) { return; } /** * Table navigation. * * @param string $which - Position of the nav. * * @since 1.1.0 */ public function extra_tablenav( $which ) { // If the position is not top then render. // Show site alerts widget. // NOTE: this is shown when the filter IS NOT true. } /** * Generates content for a single row of the table. * * @param object|array $item - The current item. * * @since 1.1.0 */ public function single_row( $item ) { $late = Crons_Helper::is_late( $item ); if ( $late ) { $classes = ' late'; } else { $classes = ' on-time'; } echo ''; $this->single_row_columns( $item ); echo ''; } /** * Generates the table navigation above or below the table * * @param string $which - Holds info about the top and bottom navigation. * * @since 1.1.0 */ public function display_tablenav( $which ) { if ( 'top' === $which ) { \wp_nonce_field( 'bulk-' . $this->_args['plural'] ); ?>
has_items() ) { ?>
bulk_actions( $which ); ?>
'schedules-submit' ) ); ?>
has_items() ) { ?>

extra_tablenav( $which ); if ( 'bottom' === $which ) { $schedules = \wp_get_schedules(); uasort( $schedules, array( __CLASS__, 'sort_schedules' ) ); ?>

$schedule ) { ?>
'; $i = 0; foreach ( $actions as $action => $link ) { ++$i; $separator = ( $i < $action_count ) ? ' | ' : ''; $output .= "{$link}{$separator}"; } $output .= ''; $output .= ''; return $output; } /** * Checks and returns if there are items to show. * * @return bool * * @since 1.4.0 */ public static function are_there_items(): bool { return ( isset( self::$read_items ) && ! empty( self::$read_items ) ); } /** * Returns a file path, name, and line number, or a clickable link to the file. Safe for output. * * @param string $text The display text, such as a function name or file name. * @param string $file The full file path and name. * @param int $line Optional. A line number, if appropriate. * @param bool $is_filename Optional. Is the text a plain file name? Default false. * * @return string The fully formatted file link or file name, safe for output. * * @since 2.3.0 */ public static function output_filename( $text, $file, $line = 0, $is_filename = false ) { if ( empty( $file ) ) { if ( $is_filename ) { return esc_html( $text ); } else { return '' . esc_html( $text ) . ''; } } $link_line = $line ? $line : 1; $source_link = ''; $query_array = array( '_wpnonce' => \wp_create_nonce( 'source-view' ), 'action' => 'log_source_view', ); if ( isset( $file ) && ! empty( $file ) ) { $query_array['error_file'] = $file; if ( isset( $link_line ) && ! empty( $link_line ) ) { $query_array['error_line'] = $link_line; } $query_array['TB_iframe'] = 'true'; $view_url = \esc_url( \add_query_arg( $query_array, \admin_url( 'admin-ajax.php' ) ) ); $title = __( 'Viewing: ', '0-day-analytics' ) . $query_array['error_file']; $source_link = '
' . __( 'view source', '0-day-analytics' ) . '
'; } if ( ! self::has_clickable_links() ) { $fallback = WP_Helper::standard_dir( $file, '' ); if ( $line ) { $fallback .= ':' . $line; } if ( $is_filename ) { $return = esc_html( $text ); } else { $return = '' . esc_html( $text ) . ''; } if ( $fallback !== $text ) { $return .= '
' . esc_html( $fallback ) . '' . $source_link; } return $return; } $map = self::get_file_path_map(); if ( ! empty( $map ) ) { foreach ( $map as $from => $to ) { $file = str_replace( $from, $to, $file ); } } $link_format = self::get_file_link_format(); $link = sprintf( $link_format, rawurlencode( $file ), intval( $link_line ) ); if ( $is_filename ) { $format = '%2$s%3$s'; } else { $format = '%2$s%3$s'; } return sprintf( $format, \esc_attr( $link ), \esc_html( $text ), ( 'edit' ) ); } /** * Returns file path map * * @return array * * @since 1.4.0 */ public static function get_file_path_map() { $map = array(); $host_path = getenv( 'HOST_PATH' ); if ( ! empty( $host_path ) ) { $source = rtrim( ABSPATH, DIRECTORY_SEPARATOR ); $replacement = rtrim( $host_path, DIRECTORY_SEPARATOR ); $map[ $source ] = $replacement; } return $map; } /** * Returns the extracted file format. * * @return string|false * * @since 1.4.0 */ public static function get_file_link_format() { if ( ! isset( self::$file_link_format ) ) { $format = ini_get( 'xdebug.file_link_format' ); if ( empty( $format ) ) { self::$file_link_format = false; } else { self::$file_link_format = str_replace( array( '%f', '%l' ), array( '%1$s', '%2$d' ), $format ); } } return self::$file_link_format; } /** * Check if there are clickable links in the file formatter. * * @return bool * * @since 1.4.0 */ public static function has_clickable_links(): bool { return ( false !== self::get_file_link_format() ); } /** * Sorts the events by the selected column. * * @param array $a - First item to compare. * @param array $b - Second item to compare. * * @return int * * @since 1.4.0 */ private static function uasort_order_events( $a, $b ) { $orderby = ( ! empty( $_GET['orderby'] ) && is_string( $_GET['orderby'] ) ) ? \sanitize_text_field( \wp_unslash( $_GET['orderby'] ) ) : 'crontrol_next'; $order = ( ! empty( $_GET['order'] ) && is_string( $_GET['order'] ) ) ? \sanitize_text_field( \wp_unslash( $_GET['order'] ) ) : 'asc'; $compare = 0; switch ( $orderby ) { case 'hook': if ( 'asc' === $order ) { $compare = strcmp( $a['hook'], $b['hook'] ); } else { $compare = strcmp( $b['hook'], $a['hook'] ); } break; case 'recurrence': if ( 'asc' === $order ) { $compare = ( $a['recurrence'] ?? 0 ) <=> ( $b['recurrence'] ?? 0 ); } else { $compare = ( $b['recurrence'] ?? 0 ) <=> ( $a['recurrence'] ?? 0 ); } break; default: if ( 'asc' === $order ) { $compare = $a['schedule'] <=> $b['schedule']; } else { $compare = $b['schedule'] <=> $a['schedule']; } break; } return $compare; } /** * Display the list of hook types. * * @return array * * @since 2.9.0 */ public function get_views() { $views = array(); $hooks_type = ( isset( $_REQUEST['event_type'] ) && is_string( $_REQUEST['event_type'] ) ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : ''; $types = array( // 'all' => __( 'All events', '0-day-analytics' ), 'noaction' => __( 'Events with no action', '0-day-analytics' ), 'core' => __( 'WordPress core events', '0-day-analytics' ), 'custom' => __( 'Custom events', '0-day-analytics' ), // 'url' => __( 'URL events', '0-day-analytics' ), ); $url = \add_query_arg( array( 'page' => self::CRON_MENU_SLUG, // self::SEARCH_INPUT => self::escaped_search_input(), // 'schedules_filter' => isset( $_REQUEST['schedules_filter'] ) && ! empty( $_REQUEST['schedules_filter'] ) ? $_REQUEST['schedules_filter'] : '', 'event_type' => 'all', ), \admin_url( 'admin.php' ) ); $views['all'] = sprintf( '%3$s (%4$s)', \esc_url( $url ), $hooks_type === 'all' ? ' class="current"' : '', \esc_html__( 'All events (no filters)', '0-day-analytics' ), \esc_html( \number_format_i18n( count( Crons_Helper::get_events() ) ) ) ); $filtered = self::get_filtered_events( self::get_cron_items( true ) ); /** * @var array $types */ foreach ( $types as $key => $type ) { if ( ! isset( $filtered[ $key ] ) ) { continue; } $count = count( $filtered[ $key ] ); if ( ! $count ) { continue; } $url = \add_query_arg( array( 'page' => self::CRON_MENU_SLUG, self::SEARCH_INPUT => self::escaped_search_input(), 'schedules_filter' => isset( $_REQUEST['schedules_filter'] ) && ! empty( $_REQUEST['schedules_filter'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['schedules_filter'] ) ) : '', 'event_type' => $key, ), \admin_url( 'admin.php' ) ); $views[ $key ] = sprintf( '%3$s (%4$s)', \esc_url( $url ), $hooks_type === $key ? ' class="current"' : '', \esc_html( $type ), \esc_html( \number_format_i18n( $count ) ) ); } return $views; } /** * Returns events filtered by various parameters * * @param array $events The list of all events. * @return array> Array of filtered events keyed by filter name. * * @since 2.9.0 */ public static function get_filtered_events( array $events ) { $filtered['noaction'] = array_filter( $events, function ( $event ) { $hook_callbacks = Crons_Helper::get_cron_callbacks( $event['hook'] ); return empty( $hook_callbacks ); } ); $filtered['core'] = array_filter( $events, function ( $event ) { return ( in_array( $event['hook'], Crons_Helper::WP_CORE_CRONS, true ) ); } ); $filtered['custom'] = array_filter( $events, function ( $event ) { return ( ! in_array( $event['hook'], Crons_Helper::WP_CORE_CRONS, true ) ); } ); // $filtered['url'] = array_filter( // $events, // function ( $event ) { // return ( 'crontrol_url_cron_job' === $event->hook ); // } // ); return $filtered; } /** * Builds sites dropdown (multisite only) listing all sites (optionally only those having cron events). * * @param int $selected Selected site id or -1. * @param string $which Position top|bottom. * @return string */ public static function get_sites_dropdown( $selected = -1, string $which = 'top' ): string { if ( ! function_exists( 'is_multisite' ) || ! is_multisite() ) { return ''; } if ( ! current_user_can( 'manage_network' ) ) { return ''; } $which = in_array( $which, array( 'top', 'bottom' ), true ) ? $which : 'top'; $sites = get_sites( array( 'number' => 0 ) ); if ( empty( $sites ) ) { return ''; } $output = ''; return $output; } /** * Builds plugins dropdown based on detected plugin slugs from cron callbacks. * * @param string|int $selected Currently selected plugin slug or -1 for all. * @param string $which Position (top|bottom) for unique element naming. * * @return string HTML select or empty string if no plugins detected. */ public static function get_plugins_dropdown( $selected = -1, $which = 'top' ): string { $which = in_array( $which, array( 'top', 'bottom' ), true ) ? $which : 'top'; $all_events = self::get_cron_items( true ); $plugins = array(); foreach ( $all_events as $event ) { if ( isset( $event['plugin_slugs'] ) && is_array( $event['plugin_slugs'] ) ) { foreach ( $event['plugin_slugs'] as $slug ) { if ( ! empty( $slug ) ) { $plugins[ $slug ] = $slug; } } } elseif ( isset( $event['plugin_slug'] ) && '' !== $event['plugin_slug'] ) { $plugins[ $event['plugin_slug'] ] = $event['plugin_slug']; } } if ( empty( $plugins ) ) { return ''; } $output = ''; return $output; } } }