id . '_columns', array( $class, 'manage_columns' ) ); parent::__construct( array( 'plural' => Requests_Log_Entity::get_table_name(), // Plural value used for labels and the objects being listed. 'singular' => Requests_Log_Entity::get_table_name(), // Singular label for an object being listed, e.g. 'post'. 'ajax' => false, // If true, the parent class will call the _js_vars() method in the footer. ) ); } /** * Inits class hooks. That is called every time - not in some specific environment set. * * @return void * * @since 2.8.2 */ public static function init() { \add_filter( 'advan_cron_hooks', array( __CLASS__, 'add_cron_job' ) ); \add_action( 'admin_post_' . self::PLUGIN_FILTER_ACTION, array( Requests_View::class, 'plugin_filter_action' ) ); } /** * Adds a cron job for truncating the records in the requests table * * @param array $crons - The array with all the crons associated with the plugin. * * @return array * * @since 2.8.2 */ public static function add_cron_job( $crons ) { if ( -1 !== (int) Settings::get_option( 'advana_rest_requests_clear' ) ) { $crons[ ADVAN_PREFIX . 'request_table_clear' ] = array( 'time' => Settings::get_option( 'advana_rest_requests_clear' ), 'hook' => array( __CLASS__, 'truncate_requests_table' ), 'args' => array(), ); } return $crons; } /** * Truncates the requests table from CRON job * * @return void * * @since 2.8.2 */ public static function truncate_requests_table() { Common_Table::truncate_table( null, Requests_Log_Entity::get_table_name() ); } /** * Adds the module to the main plugin menu * * @return void * * @since 2.8.1 */ public static function menu_add() { $requests_hook = \add_submenu_page( Logs_List::MENU_SLUG, ADVAN_INNER_NAME, \esc_html__( 'Requests viewer', '0-day-analytics' ), 'manage_options', self::REQUESTS_MENU_SLUG, array( Requests_View::class, 'analytics_requests_page' ), 8 ); self::add_screen_options( $requests_hook ); \add_filter( 'manage_' . $requests_hook . '_columns', array( self::class, 'manage_columns' ) ); \add_action( 'load-' . $requests_hook, array( Settings::class, 'aadvana_common_help' ) ); // Process any actions early to allow safe redirects before output. \add_action( 'load-' . $requests_hook, array( self::class, 'process_actions_load' ) ); } /** * Handle actions on the early page load hook to avoid header issues. * * @return void * * @since 4.2.0 */ public static function process_actions_load() { if ( ! \current_user_can( 'manage_options' ) ) { return; } $table = new self( '' ); $table->handle_table_actions(); } /** * 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.0.0 */ public function prepare_items() { // Actions are processed during load- to avoid late redirects. $per_page = self::get_screen_option_per_page(); $current_page = $this->get_pagenum(); if ( 1 < $current_page ) { $offset = $per_page * ( $current_page - 1 ); } else { $offset = 0; } $search_string = self::escaped_search_input(); if ( isset( $_REQUEST['plugin'] ) && ! empty( $_REQUEST['plugin'] ) ) { if ( -1 === (int) $_REQUEST['plugin'] ) { $plugin = -1; } else { $plugin = \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) ); } } else { $plugin = ''; } // Get filter parameters. // phpcs:disable WordPress.Security.NonceVerification.Recommended -- GET parameters for list table filtering. $date_from = isset( $_REQUEST['date_from'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['date_from'] ) ) : ''; $date_to = isset( $_REQUEST['date_to'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['date_to'] ) ) : ''; $status_filter = isset( $_REQUEST['status_filter'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['status_filter'] ) ) : ''; $type_filter = isset( $_REQUEST['type_filter'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['type_filter'] ) ) : ''; $domain_filter = isset( $_REQUEST['domain_filter'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['domain_filter'] ) ) : ''; $runtime_min = isset( $_REQUEST['runtime_min'] ) && '' !== $_REQUEST['runtime_min'] ? \sanitize_text_field( \wp_unslash( $_REQUEST['runtime_min'] ) ) : ''; $runtime_max = isset( $_REQUEST['runtime_max'] ) && '' !== $_REQUEST['runtime_max'] ? \sanitize_text_field( \wp_unslash( $_REQUEST['runtime_max'] ) ) : ''; $direction_filter = isset( $_REQUEST['direction_filter'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['direction_filter'] ) ) : ''; // phpcs:enable WordPress.Security.NonceVerification.Recommended $wpdb_table = $this->get_table_name(); $orderby = ( isset( $_GET['orderby'] ) && '' !== trim( $_GET['orderby'] ) ) ? \esc_sql( \wp_unslash( $_GET['orderby'] ) ) : 'id'; $order = ( isset( $_GET['order'] ) && '' !== trim( $_GET['order'] ) ) ? \esc_sql( \wp_unslash( $_GET['order'] ) ) : 'DESC'; $items = $this->fetch_table_data( array( 'search_string' => $search_string, 'offset' => $offset, 'per_page' => $per_page, 'wpdb_table' => $wpdb_table, 'orderby' => $orderby, 'order' => $order, 'plugin' => $plugin, 'date_from' => $date_from, 'date_to' => $date_to, 'status_filter' => $status_filter, 'type_filter' => $type_filter, 'domain_filter' => $domain_filter, 'runtime_min' => $runtime_min, 'runtime_max' => $runtime_max, 'direction_filter' => $direction_filter, ) ); $columns = self::manage_columns( array() ); $hidden = \get_user_option( 'manage' . WP_Helper::get_wp_screen()->id . 'columnshidden', false ); if ( ! $hidden ) { $hidden = array(); } $sortable = $this->get_sortable_columns(); $this->_column_headers = array( $columns, $hidden, $sortable ); $this->items = $items; // Set the pagination. $this->set_pagination_args( array( 'total_items' => $this->count, 'per_page' => self::get_screen_option_per_page(), 'total_pages' => ceil( $this->count / self::get_screen_option_per_page() ), ) ); } /** * Get a list of columns. The format is: * 'internal-name' => 'Title' * * @since 1.0.0 * * @return array */ public function get_columns() { return self::manage_columns( array() ); } /** * 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() { $first6_columns = array_keys( Requests_Log_Entity::get_column_names_admin() ); $sortable_columns = array(); /** * Actual sorting still needs to be done by prepare_items. * specify which columns should have the sort icon. * * The second bool param sets the column sort order - true ASC, false - DESC or unsorted. */ foreach ( $first6_columns as $value ) { $sortable_columns[ $value ] = array( $value, false ); } return $sortable_columns; } /** * Text displayed when no user data is available * * @since 1.0.0 * * @return void */ public function no_items() { \esc_html_e( 'No rows', '0-day-analytics' ); } /** * Fetch table data from the WordPress database. * * @param array $args - The arguments collected / passed. * * @since 1.0.0 * @since 3.8.0 - added $args param. * * @return array */ public function fetch_table_data( array $args = array() ) { global $wpdb; // Parse. $parsed_args = \wp_parse_args( $args, array( 'offset' => 0, 'search_string' => self::escaped_search_input(), 'per_page' => self::get_screen_option_per_page(), 'wpdb_table' => $this->get_table_name(), 'search_sql' => '', 'orderby' => 'id', 'order' => 'DESC', 'count' => false, 'plugin' => '', 'date_from' => '', 'date_to' => '', 'status_filter' => '', 'type_filter' => '', 'domain_filter' => '', 'runtime_min' => '', 'runtime_max' => '', 'direction_filter' => '', ) ); $search_string = \sanitize_text_field( \wp_unslash( $parsed_args['search_string'] ) ); $offset = (int) $parsed_args['offset']; $per_page = (int) $parsed_args['per_page']; $wpdb_table = \sanitize_text_field( \wp_unslash( $parsed_args['wpdb_table'] ) ); $orderby = \sanitize_text_field( \wp_unslash( $parsed_args['orderby'] ) ); $order = \sanitize_text_field( \wp_unslash( $parsed_args['order'] ) ); $plugin = \sanitize_text_field( \wp_unslash( $parsed_args['plugin'] ) ); $date_from = \sanitize_text_field( \wp_unslash( $parsed_args['date_from'] ) ); $date_to = \sanitize_text_field( \wp_unslash( $parsed_args['date_to'] ) ); $status_filter = \sanitize_text_field( \wp_unslash( $parsed_args['status_filter'] ) ); $type_filter = \sanitize_text_field( \wp_unslash( $parsed_args['type_filter'] ) ); $domain_filter = \sanitize_text_field( \wp_unslash( $parsed_args['domain_filter'] ) ); $runtime_min = \sanitize_text_field( \wp_unslash( $parsed_args['runtime_min'] ) ); $runtime_max = \sanitize_text_field( \wp_unslash( $parsed_args['runtime_max'] ) ); $direction_filter = \sanitize_text_field( \wp_unslash( $parsed_args['direction_filter'] ) ); $order = self::get_order( $order ); $orderby = self::get_order_by( $orderby ); if ( '0' === (string) $plugin ) { $plugin = ''; } $where_sql_parts = array(); $where_args = array(); if ( '' !== $search_string ) { $like = '%' . $wpdb->esc_like( $search_string ) . '%'; $search_columns = array_merge( array( 'id' ), array_keys( Requests_Log_Entity::get_all_columns() ) ); // Exclude columns that are being filtered to avoid conflicts. if ( ! empty( $status_filter ) ) { $search_columns = array_diff( $search_columns, array( 'request_status' ) ); } if ( ! empty( $type_filter ) ) { $search_columns = array_diff( $search_columns, array( 'type' ) ); } if ( ! empty( $domain_filter ) ) { $search_columns = array_diff( $search_columns, array( 'domain' ) ); } if ( '' !== $runtime_min || '' !== $runtime_max ) { $search_columns = array_diff( $search_columns, array( 'runtime' ) ); } if ( ! empty( $date_from ) || ! empty( $date_to ) ) { $search_columns = array_diff( $search_columns, array( 'date_added' ) ); } if ( ! empty( $direction_filter ) ) { $search_columns = array_diff( $search_columns, array( 'direction' ) ); } $like_clauses = array(); foreach ( $search_columns as $col ) { // Column names are from internal whitelist; no user input. $like_clauses[] = $col . ' LIKE %s'; $where_args[] = $like; } $where_sql_parts[] = 'AND (' . implode( ' OR ', $like_clauses ) . ')'; } if ( '' !== $plugin && -1 !== (int) $plugin ) { $where_sql_parts[] = 'AND plugin = %s'; $where_args[] = $plugin; // Already sanitized above. } // Date range filter - validate date format. if ( ! empty( $date_from ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date_from ) ) { $where_sql_parts[] = 'AND date_added >= %s'; $where_args[] = $date_from . ' 00:00:00'; } if ( ! empty( $date_to ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date_to ) ) { $where_sql_parts[] = 'AND date_added <= %s'; $where_args[] = $date_to . ' 23:59:59'; } // Status filter - whitelist validation. if ( ! empty( $status_filter ) && in_array( $status_filter, array( 'success', 'error', 'timeout' ), true ) ) { $where_sql_parts[] = 'AND request_status = %s'; $where_args[] = $status_filter; } // Type filter - whitelist validation. if ( ! empty( $type_filter ) && in_array( $type_filter, array( 'ajax', 'rest_api', 'cron', 'xmlrpc', 'wp-cli', 'login', 'admin', 'frontend', 'core', 'installing', 'activate', 'undetermined' ), true ) ) { $where_sql_parts[] = 'AND type = %s'; $where_args[] = $type_filter; } // Domain filter. if ( ! empty( $domain_filter ) ) { $where_sql_parts[] = 'AND domain LIKE %s'; $where_args[] = '%' . $wpdb->esc_like( $domain_filter ) . '%'; } // Runtime range filter. if ( ! empty( $runtime_min ) && is_numeric( $runtime_min ) && (float) $runtime_min > 0 ) { $where_sql_parts[] = 'AND runtime >= %f'; $where_args[] = (float) $runtime_min; } if ( ! empty( $runtime_max ) && is_numeric( $runtime_max ) && (float) $runtime_max > 0 ) { $where_sql_parts[] = 'AND runtime <= %f'; $where_args[] = (float) $runtime_max; } // Direction filter - whitelist validation. if ( ! empty( $direction_filter ) && in_array( $direction_filter, array( 'internal', 'external' ), true ) ) { $where_sql_parts[] = 'AND direction = %s'; $where_args[] = $direction_filter; } $wpdb_table = $this->get_table_name(); // Build WHERE with prepare on dynamic value placeholders. $where_clause = ''; if ( ! empty( $where_sql_parts ) ) { // Combine parts and prepare using variable args. $sql_unprepared = ' ' . implode( ' ', $where_sql_parts ) . ' '; $where_clause = $wpdb->prepare( $sql_unprepared, $where_args ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } // Count query. $count_sql = "SELECT COUNT(id) FROM {$wpdb_table} WHERE 1=1 {$where_clause}"; $this->count = (int) Requests_Log_Entity::get_var( $count_sql ); if ( $parsed_args['count'] ) { return array(); } // Whitelist order/orderby via helper methods already applied above. $fields = implode( ', ', array_keys( Requests_Log_Entity::get_fields() ) ); $query = "SELECT {$fields} FROM {$wpdb_table} WHERE 1=1 {$where_clause} ORDER BY {$orderby} {$order}"; $query .= $wpdb->prepare( ' LIMIT %d OFFSET %d', $per_page, $offset ); $query_results = Requests_Log_Entity::get_results( $query ); // return result array to prepare_items. return $query_results; } /** * Filter the table data based on the user search key * * @since 1.0.0 * * @param array $table_data - The data from the row. * @param string $search_key - The search key. * * @return array */ public function filter_table_data( $table_data, $search_key ) { $filtered_table_data = array_values( array_filter( $table_data, function( $row ) use ( $search_key ) { foreach ( $row as $row_val ) { if ( stripos( $row_val, $search_key ) !== false ) { return true; } } } ) ); return $filtered_table_data; } /** * 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 2.7.0 */ public function column_default( $item, $column_name ) { switch ( $column_name ) { case 'type': return '' . \esc_html( $item[ $column_name ] ) . ''; case 'direction': $direction = isset( $item['direction'] ) ? $item['direction'] : ''; if ( 'internal' === $direction ) { $badge_style = 'background-color: #d1ecf1; color: #0c5460; padding: 2px 8px; border-radius: 3px; font-size: 12px;'; $icon = '↩'; // ↩ arrow. } else { $badge_style = 'background-color: #fff3cd; color: #856404; padding: 2px 8px; border-radius: 3px; font-size: 12px;'; $icon = '↪'; // ↪ arrow. } return '' . $icon . ' ' . \esc_html( ucfirst( $direction ) ) . ''; case 'url': case 'page_url': $value = \str_replace( array( 'http://', 'https://' ), '', $item[ $column_name ] ); $value = \str_replace( WP_Helper::get_blog_domain(), '', $value ); $title = \esc_html( $value ); $value = substr( $value, 0, 70 ); // Escape & wrap in tag. $value = '' . \esc_html( $value ) . ''; return $value; case 'user_id': if ( isset( $item[ $column_name ] ) && ! empty( $item[ $column_name ] ) && 0 !== $item[ $column_name ] ) { $user = \get_user_by( 'id', $item[ $column_name ] ); if ( $user ) { return '' . \esc_html( $user->display_name ) . ' (' . \esc_html( $user->user_email ) . ')'; } else { return \esc_html__( 'Unknown or deleted user', '0-day-analytics' ); } } else { return \esc_html__( 'WP System or Anonymous user', '0-day-analytics' ); } case 'domain': // Escape & wrap in tag. return '' . \esc_html( $item[ $column_name ] ) . ''; case 'runtime': // Escape & wrap in tag. return '' . \esc_html( \number_format( (float) $item[ $column_name ], 3 ) ) . 's'; case 'request_status': // Escape & wrap in tag. $extra_info = ''; $style = 'style="color: #00ff00 !important;"'; if ( 'error' === $item[ $column_name ] ) { $extra_info = '
' . \esc_html( $item['response'] ) . '
'; $style = 'style="color:rgb(235, 131, 55) !important;"'; } return '' . \esc_html( $item[ $column_name ] ) . '
' . $extra_info . self::format_trace( $item['trace'] ); case 'request_group': case 'request_source': // Escape & wrap in tag. return '' . \esc_html( $item[ $column_name ] ) . ''; case 'date_added': $query_args_view_data = array(); $query_args_view_data['_wpnonce'] = \wp_create_nonce( 'bulk-' . $this->_args['plural'] ); $delete_url = \add_query_arg( array( 'action' => 'delete', 'advan_' . self::$table::get_name() => (int) $item['id'], self::SEARCH_INPUT => self::escaped_search_input(), '_wpnonce' => $query_args_view_data['_wpnonce'], ) ); $actions['delete'] = '' . \esc_html__( 'Delete', '0-day-analytics' ) . ''; $actions['details'] = '' . \esc_html__( 'Details', '0-day-analytics' ) . ''; $data = ''; $data .= ''; $time_format = 'g:i a'; $item['date_added'] = (int) $item['date_added']; $event_datetime_utc = \gmdate( 'Y-m-d H:i:s', $item['date_added'] ); $timezone_local = \wp_timezone(); $event_local = \get_date_from_gmt( $event_datetime_utc, 'Y-m-d' ); $today_local = ( new \DateTimeImmutable( 'now', $timezone_local ) )->format( 'Y-m-d' ); $tomorrow_local = ( new \DateTimeImmutable( 'tomorrow', $timezone_local ) )->format( 'Y-m-d' ); $yesterday_local = ( new \DateTimeImmutable( 'yesterday', $timezone_local ) )->format( 'Y-m-d' ); // If the offset of the date of the event is different from the offset of the site, add a marker. if ( \get_date_from_gmt( $event_datetime_utc, 'P' ) !== get_date_from_gmt( 'now', 'P' ) ) { $time_format .= ' (P)'; } $event_time_local = \get_date_from_gmt( $event_datetime_utc, $time_format ); if ( $event_local === $today_local ) { $date = sprintf( /* translators: %s: Time */ __( 'Today at %s', '0-day-analytics' ), $event_time_local, ); } elseif ( $event_local === $tomorrow_local ) { $date = sprintf( /* translators: %s: Time */ __( 'Tomorrow at %s', '0-day-analytics' ), $event_time_local, ); } elseif ( $event_local === $yesterday_local ) { $date = sprintf( /* translators: %s: Time */ __( 'Yesterday at %s', '0-day-analytics' ), $event_time_local, ); } else { $date = sprintf( /* translators: 1: Date, 2: Time */ __( '%1$s at %2$s', '0-day-analytics' ), \get_date_from_gmt( $event_datetime_utc, 'F jS' ), $event_time_local, ); } $time = sprintf( '', \esc_attr( gmdate( 'c', $item['date_added'] ) ), \esc_html( $date ) ); $until = $item['date_added'] - time(); if ( $until < 0 ) { $ago = sprintf( /* translators: %s: Time period, for example "8 minutes" */ __( '%s ago', '0-day-analytics' ), WP_Helper::interval( abs( $until ) ) ); return sprintf( ' %s
%s', esc_html( $ago ), $time, ) . $this->row_actions( $actions ) . $data; } elseif ( 0 === $until ) { $in = __( 'Now', '0-day-analytics' ); } else { $in = sprintf( /* translators: %s: Time period, for example "8 minutes" */ __( 'In %s', '0-day-analytics' ), WP_Helper::interval( $until ), ); } return sprintf( ' %s
%s', \esc_html( $in ), $time, ) . $this->row_actions( $actions ) . $data; case 'plugin': if ( ! empty( $item['plugin'] ) ) { $plugin = Plugin_Theme_Helper::get_plugin_from_path( $item['plugin'] ); if ( ! empty( $plugin ) ) { return __( 'Plugin: ', '0-day-analytics' ) . '' . \esc_html( $plugin['Name'] ) . '
' . \__( 'Current version: ', '0-day-analytics' ) . \esc_html( $plugin['Version'] ); } } else { return \esc_html__( 'Core or Unknown', '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 2.1.0 */ protected function column_cb( $item ) { $id = isset( $item['id'] ) ? (int) $item['id'] : 0; $table = self::$table::get_name(); return sprintf( '', \esc_attr( $table ), $id, sprintf( /* translators: The column name. */ __( 'Select %s', '0-day-analytics' ), 'id' ) ); } /** * Returns an associative array containing the bulk actions * * @since 1.0.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 Records', '0-day-analytics' ), ); return $actions; } /** * Process actions triggered by the user * * @since 1.0.0 */ public function handle_table_actions() { /** * 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 */ if ( \is_user_logged_in() && \current_user_can( 'manage_options' ) ) { // check for individual row actions. $the_table_action = $this->current_action(); // check for table bulk actions. if ( ( isset( $_REQUEST['action'] ) && 'delete' === $_REQUEST['action'] ) || ( isset( $_REQUEST['action2'] ) && 'delete' === $_REQUEST['action2'] ) ) { // verify the nonce. /** * 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[ 'advan_' . self::$table::get_name() ] ) ) { foreach ( (array) $_REQUEST[ 'advan_' . self::$table::get_name() ] as $id ) { self::$table::delete_by_id( (int) $id ); } } // Build redirect URL preserving all filters and pagination. // phpcs:disable WordPress.Security.NonceVerification.Recommended -- GET parameters for list table filtering. $redirect_args = array( 'page' => self::REQUESTS_MENU_SLUG, 'show_table' => self::$table::get_name(), ); // Preserve pagination. if ( isset( $_REQUEST['paged'] ) && (int) $_REQUEST['paged'] > 1 ) { $redirect_args['paged'] = (int) $_REQUEST['paged']; } // Preserve search input. $search_input = self::escaped_search_input(); if ( ! empty( $search_input ) ) { $redirect_args[ self::SEARCH_INPUT ] = $search_input; } // Preserve plugin filter. if ( isset( $_REQUEST['plugin'] ) && ! empty( $_REQUEST['plugin'] ) ) { if ( -1 === (int) $_REQUEST['plugin'] ) { $redirect_args['plugin'] = -1; } else { $redirect_args['plugin'] = \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) ); } } // Preserve status filter. if ( isset( $_REQUEST['status_filter'] ) && ! empty( $_REQUEST['status_filter'] ) ) { $redirect_args['status_filter'] = \sanitize_text_field( \wp_unslash( $_REQUEST['status_filter'] ) ); } // Preserve type filter. if ( isset( $_REQUEST['type_filter'] ) && ! empty( $_REQUEST['type_filter'] ) ) { $redirect_args['type_filter'] = \sanitize_text_field( \wp_unslash( $_REQUEST['type_filter'] ) ); } // Preserve date filters. if ( isset( $_REQUEST['date_from'] ) && ! empty( $_REQUEST['date_from'] ) ) { $redirect_args['date_from'] = \sanitize_text_field( \wp_unslash( $_REQUEST['date_from'] ) ); } if ( isset( $_REQUEST['date_to'] ) && ! empty( $_REQUEST['date_to'] ) ) { $redirect_args['date_to'] = \sanitize_text_field( \wp_unslash( $_REQUEST['date_to'] ) ); } // Preserve domain filter. if ( isset( $_REQUEST['domain_filter'] ) && ! empty( $_REQUEST['domain_filter'] ) ) { $redirect_args['domain_filter'] = \sanitize_text_field( \wp_unslash( $_REQUEST['domain_filter'] ) ); } // Preserve runtime range filters. if ( isset( $_REQUEST['runtime_min'] ) && '' !== $_REQUEST['runtime_min'] ) { $redirect_args['runtime_min'] = \sanitize_text_field( \wp_unslash( $_REQUEST['runtime_min'] ) ); } if ( isset( $_REQUEST['runtime_max'] ) && '' !== $_REQUEST['runtime_max'] ) { $redirect_args['runtime_max'] = \sanitize_text_field( \wp_unslash( $_REQUEST['runtime_max'] ) ); } // phpcs:enable WordPress.Security.NonceVerification.Recommended $redirect = \add_query_arg( $redirect_args, \admin_url( 'admin.php' ) ); // Use server-side safe redirect instead of inline JS for better security. \wp_safe_redirect( \esc_url_raw( $redirect ) ); exit; } } } /** * View a license information. * * @since 1.0.0 * * @param int $table_id - Record ID. */ public function page_view_data( $table_id ) { // Edit_Data::set_table( $this->table ); // Edit_Data::edit_record( $table_id ); } /** * Table navigation. * * @param string $which - Position of the nav. * * @since 1.1.0 */ public function extra_tablenav( $which ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- GET parameters for list table filtering. if ( isset( $_REQUEST['plugin'] ) && ! empty( $_REQUEST['plugin'] ) ) { if ( -1 === (int) $_REQUEST['plugin'] ) { $plugin = -1; } else { $plugin = \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) ); } } else { $plugin = 0; } // Get current filter values. $date_from = isset( $_REQUEST['date_from'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['date_from'] ) ) : ''; $date_to = isset( $_REQUEST['date_to'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['date_to'] ) ) : ''; $status_filter = isset( $_REQUEST['status_filter'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['status_filter'] ) ) : ''; $type_filter = isset( $_REQUEST['type_filter'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['type_filter'] ) ) : ''; $domain_filter = isset( $_REQUEST['domain_filter'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['domain_filter'] ) ) : ''; $runtime_min = isset( $_REQUEST['runtime_min'] ) && '' !== $_REQUEST['runtime_min'] ? \sanitize_text_field( \wp_unslash( $_REQUEST['runtime_min'] ) ) : ''; $runtime_max = isset( $_REQUEST['runtime_max'] ) && '' !== $_REQUEST['runtime_max'] ? \sanitize_text_field( \wp_unslash( $_REQUEST['runtime_max'] ) ) : ''; $direction_filter = isset( $_REQUEST['direction_filter'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['direction_filter'] ) ) : ''; // phpcs:enable WordPress.Security.NonceVerification.Recommended ?> count > 0 ) { ?>
> >
| | | | |
'', // to display the checkbox. ); self::$admin_columns = \array_merge( $table_columns, $screen_options, $columns ); } return self::$admin_columns; } /** * Formats the trace from the request log. * * @param string $trace - JSON encoded trace. * @param int $how_back - How much records to skip from the trace array. * * @return string * * @since 2.7.0 * @since 3.8.0 - $how_back parameter introduced */ public static function format_trace( string $trace, ?int $how_back = 6 ): string { if ( empty( $trace ) ) { return ''; } $trace = \json_decode( $trace, true ); $defaults = array( 'line' => '', 'file' => '', 'class' => '', 'function' => '', ); $out = ''; if ( \is_array( $trace ) && ! empty( $trace ) ) { $query_array = array( '_wpnonce' => \wp_create_nonce( 'source-view' ), 'action' => 'log_source_view', ); $counter = count( $trace ) - $how_back; for ( $i = 1; $i < $counter; $i++ ) { $sf = (object) \shortcode_atts( $defaults, $trace[ $i + $how_back ] ); $index = $i - 1; $file = isset( $sf->file ) ? $sf->file : ''; $caller = ''; if ( ! empty( $sf->class ) && ! empty( $sf->function ) ) { $caller = $sf->class . '::' . $sf->function . '()'; } elseif ( ! empty( $sf->function ) ) { $caller = $sf->function . '()'; } $source_link = ''; if ( ! empty( $file ) ) { $query_array['error_file'] = $file; $query_array['error_line'] = 1; if ( isset( $sf->line ) && ! empty( $sf->line ) ) { $query_array['error_line'] = (int) $sf->line; } $query_array['TB_iframe'] = 'true'; $view_url = \esc_url_raw( \add_query_arg( $query_array, \admin_url( 'admin-ajax.php' ) ) ); $title_attr = \esc_attr( __( 'Viewing: ', '0-day-analytics' ) . $query_array['error_file'] ); $link_text = \esc_html( $file ) . '(' . ( isset( $sf->line ) ? (int) $sf->line : '' ) . ')'; $source_link = ' ' . $link_text . ''; } $out .= '#' . $index . ' ' . $source_link . ': ' . \esc_html( $caller ) . '
'; } } return $out; } /** * Checks string format and decodes it if it is a valid JSON. * * @param string $string - The string to check and decode. * * @return string * * @since 2.7.0 */ public static function get_formatted_string( $string ) { $encoded = json_decode( $string, true, 512, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); if ( json_last_error() === JSON_ERROR_NONE ) { if ( ! is_array( $encoded ) ) { return $encoded; } foreach ( $encoded as $key => $value ) { if ( ! empty( $value ) && is_string( $value ) && ! is_numeric( $value ) ) { $encoded[ $key ] = self::get_formatted_string( $value ); } } return $encoded; } else { return $string; } } /** * Sets the request type status. * * @param \WP_REST_Request $request - The request object. * * @return \WP_REST_Response|\WP_Error * * @since 2.8.0 */ public static function set_request_status( \WP_REST_Request $request ) { $request_type = $request->get_param( 'request_type' ); $status = $request->get_param( 'status' ); // Restrict to administrators/managers. if ( ! \current_user_can( 'manage_options' ) ) { return new \WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to modify request monitoring settings.', '0-day-analytics' ), array( 'status' => \rest_authorization_required_code() ) ); } if ( ! in_array( $request_type, array( 'http', 'rest' ), true ) ) { return new \WP_Error( 'invalid_request_type', __( 'Invalid request type name.', '0-day-analytics' ), array( 'status' => 400 ) ); } $request_type = 'advana_' . $request_type . '_requests_disable'; $settings = Settings::get_current_options(); if ( 'enable' === $status ) { $settings[ $request_type ] = true; } elseif ( 'disable' === $status ) { $settings[ $request_type ] = false; } else { return new \WP_Error( 'invalid_status', __( 'Invalid status.', '0-day-analytics' ), array( 'status' => 400 ) ); } Settings::store_options( $settings ); Settings::set_current_options( $settings ); return rest_ensure_response( array( 'success' => true, ) ); } /** * Generates content for a single row of the table. * * @param object|array $item - The current item. * * @since 3.3.1 */ public function single_row( $item ) { $classes = ''; if ( isset( $item['request_status'] ) && ! empty( $item['request_status'] ) ) { $classes .= ' ' . $item['request_status']; } 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 3.3.1 */ public function display_tablenav( $which ) { if ( 'top' === $which ) { \wp_nonce_field( 'bulk-' . $this->_args['plural'] ); ?>
has_items() ) { ?>
bulk_actions( $which ); ?>
extra_tablenav( $which ); $this->pagination( $which ); ?>