AlkantarClanX12
| Current Path : /www/capitalgmcbuickregina_830/public/wp-content/plugins/wp-cerber/admin/ |
| Current File : /www/capitalgmcbuickregina_830/public/wp-content/plugins/wp-cerber/admin/CRB_Traffic_Log_Screen.php |
<?php
/**
* Generates HTML code for the Traffic Log admin page to view, filter, and search HTTP requests log entry
*
* This file contains the refactored class for rendering the Traffic Log screen.
* The main entry point is CRB_Traffic_Log_Screen::render_screen().
*
* @since 9.6.9.7
*
*/
final class CRB_Traffic_Log_Screen {
/** @var array<string,mixed> Render arguments for this screen render pass. */
private array $args;
/** Whether toolbar and search controls should be rendered. */
private bool $ui_controls_enabled;
/** Whether Traffic Inspector logging is enabled for this render pass. */
private bool $logging_enabled;
/** Whether the current request applies Traffic Log search or filter criteria. */
private bool $search_active;
/** Whether the Traffic Log table has no stored entries. */
private bool $traffic_log_empty;
/**
* Initializes request-local Traffic Log screen state.
*
* @param array<string,mixed> $args Screen rendering arguments.
*
* @since 9.8.1
*/
private function __construct( array $args = array() ) {
$this->args = $args;
$this->ui_controls_enabled = empty( $args['compact_view'] );
$this->logging_enabled = (bool) crb_get_settings( 'timode' );
$this->search_active = (bool) array_intersect_key( CRB_Traffic_Log::QUERY_PARAMS, crb_get_query_params() );
$this->traffic_log_empty = CRB_Traffic_Log::is_empty();
}
/**
* Renders and outputs the HTTP Traffic Log admin screen.
*
* @param array<string,mixed> $args Optional arguments to control UI, filtering, and row limits.
* @param bool $echo Whether to echo the result or return it as a string.
*
* @return void|string
*
* @since 9.8.1
*/
public static function render_screen( array $args = array(), $echo = true ) {
$screen = new self( $args );
$result_html = $screen->render();
if ( $echo ) {
echo $result_html;
return;
}
return $result_html;
}
/**
* Renders HTTP Traffic Log admin screen.
*
* This is the main orchestrator method. It fetches data and calls other
* rendering methods to build the final HTML output for the screen.
*
* @return string
*
* @since 9.6.9.7
*/
private function render() {
$result_html = '';
$fetched = CRB_Traffic_Log::fetch_rows( $this->args );
$filters = $fetched->get_element( 'filters', array() );
$traffic_log_rows = $fetched->get_element( 'rows', array() );
$total_rows = (int) $fetched->get_element( 'total', 0 );
if ( $this->ui_controls_enabled ) {
$toolbar_html = self::render_toolbar( $this->traffic_log_empty, (int) crb_get_settings( 'tithreshold' ), (bool) $total_rows );
$info_blocks_html = self::render_info_blocks( $filters['ip'], $filters['user'] );
$form_html = self::render_search_form();
$result_html .= '<div class="cerber-margin">' . $toolbar_html . $form_html . $info_blocks_html . '</div>';
}
if ( $fetched->has_errors() ) {
$result_html .= crb_ui_renderer()->render_element( crb_ui_log_fetch_error_box( $fetched ) );
}
elseif ( $traffic_log_rows ) {
// Fetch and prepare all related data
$activity_rows = self::fetch_activity_events( $traffic_log_rows );
$grouped_events = self::group_activity_events( $activity_rows );
$enriched_data = self::enrich_log_data( $traffic_log_rows );
// Render the main content table
$table_html = self::render_log_table( $traffic_log_rows, $enriched_data, $grouped_events, $this->args );
// Add pagination
if ( $this->ui_controls_enabled ) {
$table_html .= cerber_page_navi( $total_rows, $filters['per_page'] );
}
$result_html .= $table_html;
if ( ! $this->logging_enabled ) {
$result_html = '<p class="cerber-margin" style="padding: 0.5em; font-weight: 600;">' . __( 'Logging is currently disabled, you are viewing historical information.', 'wp-cerber' ) . '</p>' . $result_html;
}
}
else {
// Render the "no results" message
$result_html .= $this->render_empty_state();
}
return $result_html;
}
/**
* Fetches related activity events for a given set of traffic log rows.
*
* @param array $log_rows The traffic log rows.
*
* @return array The fetched activity rows.
*
* @since 9.6.9.7
*/
private static function fetch_activity_events( $log_rows ) {
if ( ! $log_rows ) {
return array();
}
$session_ids = array_keys( $log_rows );
$query = 'SELECT * FROM ' . CERBER_LOG_TABLE . ' WHERE session_id IN ("' . implode( '", "', array_map( 'cerber_db_real_escape', $session_ids ) ) . '" ) ORDER BY stamp DESC';
$activity_rows = crb_q_cache_get( array(
array( $query, MYSQL_FETCH_OBJECT )
), CERBER_LOG_TABLE );
return $activity_rows ?: array();
}
/**
* Groups activity events by session ID.
*
* @param array $activity_rows The activity rows to group.
*
* @return array The events grouped by session_id.
*
* @since 9.6.9.7
*/
private static function group_activity_events( $activity_rows ) {
if ( ! $activity_rows ) {
return array();
}
$events = array();
foreach ( $activity_rows as $activity_row ) {
$events[ $activity_row->session_id ][] = $activity_row;
}
return $events;
}
/**
* Enriches log data with user information, ACL status, and post titles.
*
* @param array $log_rows The traffic log rows.
*
* @return array An array of enriched data: ['users' => [], 'acl' => [], 'block' => [], 'entities' => []].
*
* @since 9.6.9.7
*/
private static function enrich_log_data( $log_rows ) {
global $wpdb;
$enriched_data = array(
'users' => array(),
'acl' => array(),
'block' => array(),
'entities' => array(),
);
if ( ! $log_rows ) {
return $enriched_data;
}
$roles = wp_roles()->roles;
foreach ( $log_rows as $traffic_row ) {
// Enrich user data
if ( $traffic_row->user_id && ! isset( $enriched_data['users'][ $traffic_row->user_id ] ) ) {
if ( $user_data = crb_get_userdata( $traffic_row->user_id ) ) {
$user_roles = '';
if ( ! is_multisite() && $user_data->roles ) {
$role_names = array();
foreach ( $user_data->roles as $role_key ) {
if ( isset( $roles[ $role_key ]['name'] ) ) {
$role_names[] = $roles[ $role_key ]['name'];
}
}
$user_roles = '<span class="crb_act_role">' . implode( ', ', $role_names ) . '</span>';
}
$name = $user_data->display_name;
}
else {
$name = __( 'Unknown', 'wp-cerber' ) . ' (' . $traffic_row->user_id . ')';
$user_roles = '';
}
$enriched_data['users'][ $traffic_row->user_id ] = array( 'name' => $name, 'roles' => $user_roles );
}
// Enrich IP ACL status
if ( ! isset( $enriched_data['acl'][ $traffic_row->ip ] ) ) {
$enriched_data['acl'][ $traffic_row->ip ] = cerber_acl_check( $traffic_row->ip );
}
// Enrich IP block status
if ( ! isset( $enriched_data['block'][ $traffic_row->ip ] ) ) {
$enriched_data['block'][ $traffic_row->ip ] = cerber_block_check( $traffic_row->ip );
}
// Enrich WordPress entity (e.g., post title)
if ( $traffic_row->wp_type == 601 && $traffic_row->wp_id > 0 && ! isset( $enriched_data['entities'][ $traffic_row->wp_id ] ) ) {
$post_title = cerber_db_get_var( 'SELECT post_title FROM ' . $wpdb->posts . ' WHERE ID = ' . absint( $traffic_row->wp_id ) );
if ( $post_title ) {
$enriched_data['entities'][ $traffic_row->wp_id ] = apply_filters( 'the_title', $post_title, $traffic_row->wp_id );
}
}
}
return $enriched_data;
}
/**
* Renders the top toolbar with quick filters and action buttons.
*
* @param bool $is_log_empty Whether the log is completely empty.
* @param int $threshold The processing time threshold for a "long" request.
* @param bool $has_results Whether there are log rows to display.
*
* @return string The HTML for the toolbar.
*
* @since 9.6.9.7
*/
private static function render_toolbar( $is_log_empty, $threshold, $has_results ) {
$filter_links = '';
$search_button = '';
$export_button = '';
$refresh_button = '';
if ( ! $is_log_empty ) {
$nav_links = array(
array( array(), __( 'View all', 'wp-cerber' ) ),
array( array( 'filter_set' => 1 ), __( 'Suspicious requests', 'wp-cerber' ) ),
array( array( 'filter_http_code' => 399, 'filter_http_code_mode' => 'GT' ), __( 'Errors', 'wp-cerber' ) ),
array( array( 'filter_user' => '*' ), __( 'Users', 'wp-cerber' ) ),
array( array( 'filter_user' => 0 ), __( 'Non-authenticated', 'wp-cerber' ) ),
array( array( 'filter_method' => 'POST', 'filter_wp_type' => 520, 'filter_wp_type_mode' => 'GT' ), __( 'Form submissions', 'wp-cerber' ) ),
array( array( 'filter_http_code' => 404 ), __( 'Page Not Found', 'wp-cerber' ) ),
array( array( 'filter_wp_type' => 520 ), 'REST API' ),
array( array( 'filter_wp_type' => 515 ), 'XML-RPC' ),
array( array( 'filter_user' => get_current_user_id() ), __( 'My requests', 'wp-cerber' ) ),
array( array( 'filter_ip' => cerber_get_remote_ip() ), __( 'My IP', 'wp-cerber' ) ),
);
if ( $threshold ) {
$nav_links[] = array( array( 'filter_processing' => $threshold ), __( 'Longer than', 'wp-cerber' ) . ' ' . (int) $threshold . ' ms' );
}
$filter_links = crb_build_quick_nav_block( $nav_links, 'traffic' );
$search_button = '<a href="#" id="traffic-search-btn" class="button button-secondary cerber-button" title="Search in the request history"><i class="crb-icon crb-icon-bx-search"></i> ' . __( 'Advanced Search', 'wp-cerber' ) . '</a>';
}
if ( $has_results ) {
// This URL is escaped, no escaping is needed
$export_url = cerber_admin_link_add( array( 'cerber_admin_do' => 'export', 'type' => 'traffic' ), true );
$export_button = '<a class="button button-secondary cerber-button" href="' . $export_url . '"><i class="crb-icon crb-icon-bx-download"></i> ' . __( 'Export', 'wp-cerber' ) . '</a>';
}
if ( crb_get_settings( 'timode' ) && $has_results ) {
$refresh_button = '<a href="" style="white-space: pre;"><i class="dashicons dashicons-update" style=""></i> <span>' . __( 'Refresh', 'wp-cerber' ) . '</span></a>';
}
$right_actions = $search_button . ' ' . $export_button;
return '<div id="crb-admin-toolbar-secondary-row"><div><div style="display: table-cell; width: 80%;">' . $filter_links . '</div><div style="display: table-cell;">' . $refresh_button . '</div></div><div>' . $right_actions . '</div></div><br style="clear: both;">';
}
/**
* Renders additional info blocks when filtering by IP or user.
*
* @param string $filter_ip The IP being filtered, if any.
* @param int $filter_user The user ID being filtered, if any.
*
* @return string The HTML for the info blocks.
*
* @since 9.6.9.7
*/
private static function render_info_blocks( $filter_ip, $filter_user ) {
$info_html = '';
if ( $filter_ip ) {
$info_html .= cerber_ip_extra_view( $filter_ip, 'traffic' );
}
if ( is_numeric( $filter_user ) && $filter_user > 0 ) {
$info_html .= cerber_user_extra_view( $filter_user, 'traffic' );
}
return $info_html;
}
/**
* Renders the main log table.
*
* @param array $log_rows The traffic log rows.
* @param array $enriched_data Additional data for users, ACL, etc.
* @param array $events Grouped activity events.
* @param array $args Original arguments for rendering options.
*
* @return string The HTML for the log table.
*
* @since 9.6.9.7
*/
private static function render_log_table( $log_rows, $enriched_data, $events, $args ) {
$columns = array(
'crb_date_col' => __( 'Date', 'wp-cerber' ),
'crb_request_col' => __( 'Request', 'wp-cerber' ),
'crb_ip_col' => '<div class="crb_act_icon"></div>' . __( 'IP Address', 'wp-cerber' ),
'crb_hostinfo_col' => __( 'Host Info', 'wp-cerber' ),
'crb_ua_col' => __( 'User Agent', 'wp-cerber' ),
'crb_user_col' => __( 'Local User', 'wp-cerber' ),
);
$table_heading = '';
foreach ( $columns as $id => $title ) {
$table_heading .= '<th id="' . crb_attr_escape( $id ) . '">' . $title . '</th>';
}
$table_footer = '<th>' . implode( '</th><th>', $columns ) . '</th>';
$table_body = '';
$is_even_row = false;
foreach ( $log_rows as $log_entry ) {
$row_class = $is_even_row ? 'crb-even' : 'crb-odd';
$table_body .= self::render_table_row( $log_entry, $enriched_data, $events, $row_class, $args );
$is_even_row = ! $is_even_row;
}
return '<table id="crb-traffic" class="widefat crb-table cerber-margin"><thead><tr>' . $table_heading . '</tr></thead><tfoot><tr>' . $table_footer . '</tr></tfoot><tbody>' . $table_body . '</tbody></table>';
}
/**
* Renders a single row in the log table, including the hidden details row.
*
* @param object $log_entry A single log entry object.
* @param array $enriched_data Additional data for users, ACL, etc.
* @param array $events Grouped activity events.
* @param string $row_class The CSS class for the row (crb-even/crb-odd).
* @param array $args Original arguments for rendering options.
*
* @return string The HTML for a single `<tr>` pair.
*
* @since 9.6.9.7
*/
private static function render_table_row( $log_entry, $enriched_data, $events, $row_class, $args ) {
$base_url = cerber_admin_link( 'traffic' );
// Date cell
if ( 'ago' == ( $args['date'] ?? '' ) ) {
$date_cell = cerber_ago_time( $log_entry->stamp );
}
else {
$date_cell = '<span title="' . crb_attr_escape( $log_entry->stamp . ' / ' . $log_entry->session_id ) . '">' . cerber_date( $log_entry->stamp ) . '</span>';
}
// User cell
$user_cell = '';
if ( $log_entry->user_id && isset( $enriched_data['users'][ $log_entry->user_id ] ) ) {
$user = $enriched_data['users'][ $log_entry->user_id ];
$user_cell = '<a href="' . crb_escape_url( $base_url . '&filter_user=' . $log_entry->user_id ) . '"><b>' . crb_escape_html( $user['name'] ) . '</b></a><br/>' . $user['roles'];
}
// Host Info cell
$country_html = ( lab_lab() ) ? '<p style="">' . crb_country_html( $log_entry->country, $log_entry->ip ) . '</p>' : '';
if ( ! empty( $log_entry->hostname ) ) {
$hostname = $log_entry->hostname;
}
else {
$ip_info = cerber_get_ip_info( $log_entry->ip );
$hostname = $ip_info['hostname_html'] ?? crb_get_ajax_placeholder( 'hostname', cerber_get_id_ip( $log_entry->ip ) );
}
$host_info_cell = $hostname . $country_html;
// User Agent cell
$ua_cell = crb_escape_html( CRB_User_Agent_Parser::detect( $log_entry->request_details[1] ?? '' ) );
// IP cell
$ip_cell = crb_admin_ip_cell( $log_entry->ip, $base_url . '&filter_ip=' . $log_entry->ip );
// Request cell (the most complex one)
$request_details_html = self::render_request_details( $log_entry, $enriched_data, $events, $base_url );
$toggle_class = $request_details_html['details'] ? 'crb-toggle' : '';
// Build the final row HTML
$visible_row = '<tr class="' . $row_class . ' ' . $toggle_class . '">
<td>' . $date_cell . '</td>
<td class="crb-request">' . $request_details_html['summary'] . '</td>
<td>' . $ip_cell . '</td>
<td>' . $host_info_cell . '</td>
<td>' . $ua_cell . '</td>
<td>' . $user_cell . '</td>
</tr>';
$hidden_row = '<tr class="' . $row_class . ' crb-request-details" style="display: none;">
<td></td>
<td colspan="5" class="crb-traffic-details" data-session-id="' . crb_attr_escape( $log_entry->session_id ) . '">
<div>' . $request_details_html['details'] . '</div>
</td>
</tr>';
return $visible_row . $hidden_row;
}
/**
* Formats URI data for display.
*
* @param string $raw_uri The raw URI from the log entry.
*
* @return array An array containing 'display_html' and 'card' data.
*
* @since 9.6.9.7
*/
private static function format_uri_data( $raw_uri ) {
$full_uri = urldecode( $raw_uri );
$display_uri = $full_uri;
if ( ! defined( 'CERBER_FULL_URI' ) || ! CERBER_FULL_URI ) {
$site_home_url = cerber_get_home_url();
if ( 0 === strpos( $full_uri, $site_home_url ) ) {
$display_uri = substr( $full_uri, strlen( rtrim( $site_home_url, '/' ) ) );
}
}
$is_truncated = strlen( $display_uri ) > 220;
$display_html = crb_escape_html( $is_truncated ? substr( $display_uri, 0, 220 ) : $display_uri );
if ( $is_truncated ) {
$display_html .= ' <span style="color: red;">…</span>';
}
$card = array();
if ( $is_truncated ) {
$card = array( 'title' => 'Full URL', 'content' => $full_uri, 'type' => 'monospace' );
}
return array(
'display_html' => $display_html,
'card' => $card,
);
}
/**
* Builds a structured array of request details data from a log entry.
*
* @param object $log_entry A single log entry object.
*
* @return array A structured array of data cards for rendering.
*
* @since 9.6.9.7
*/
private static function build_request_details_data( $log_entry ) {
$details = $log_entry->request_details;
$fields = $log_entry->request_fields;
$cards = array();
// Server headers and redirection
$server_headers = array();
if ( ! empty( $details[10] ) ) {
foreach ( $details[10] as $item ) {
$e = explode( ':', $item, 2 );
if ( count( $e ) == 2 ) {
$server_headers[ trim( $e[0] ) ] = trim( $e[1] );
}
}
}
if ( ! empty( $server_headers['Location'] ) ) {
$cards[] = array( 'title' => 'Redirection URL', 'content' => urldecode( $server_headers['Location'] ), 'type' => 'monospace' );
}
// Common request data
if ( ! empty( $details[2] ) ) {
$cards[] = array( 'title' => 'Referrer', 'content' => urldecode( $details[2] ), 'type' => 'monospace' );
}
if ( ! empty( $details[1] ) ) {
$cards[] = array( 'title' => 'User Agent', 'content' => $details[1], 'type' => 'monospace' );
}
// Request body data
if ( ! empty( $fields[1] ) ) {
crb_highlight_fields_type_two( $fields[1], 1 );
$cards[] = array( 'title' => __( 'Form Fields', 'wp-cerber' ), 'content' => $fields[1], 'type' => 'table' );
}
if ( ! empty( $fields[4] ) ) {
$cards[] = array( 'title' => __( 'JSON Payload', 'wp-cerber' ), 'content' => $fields[4], 'type' => 'table' );
}
if ( ! empty( $fields[2] ) ) {
$cards[] = array( 'title' => __( 'Query Parameters', 'wp-cerber' ), 'content' => $fields[2], 'type' => 'table' );
}
// Files
if ( ! empty( $fields[3] ) ) {
$files_content = array();
foreach ( $fields[3] as $field_name => $file ) {
$f_err = is_array( $file['error'] ) ? array_shift( $file['error'] ) : $file['error'];
if ( $f_err == UPLOAD_ERR_NO_FILE ) {
continue;
}
$f_name = is_array( $file['name'] ) ? array_shift( $file['name'] ) : $file['name'];
$f_size = is_array( $file['size'] ) ? array_shift( $file['size'] ) : $file['size'];
$files_content[ $field_name ] = crb_escape_html( $f_name ) . ', size: ' . absint( $f_size ) . ' bytes';
}
if ( $files_content ) {
$cards[] = array( 'title' => __( 'Files Submitted', 'wp-cerber' ), 'content' => $files_content, 'type' => 'table' );
}
}
// Other details
if ( ! empty( $details[5] ) ) {
$cards[] = array( 'title' => __( 'XML-RPC Payload', 'wp-cerber' ), 'content' => $details[5], 'type' => 'monospace' );
}
if ( ! empty( $details[6] ) ) {
$cards[] = array( 'title' => __( 'Client Request Headers', 'wp-cerber' ), 'content' => $details[6], 'type' => 'table' );
}
if ( ! empty( $server_headers ) ) {
if ( ! empty( $details[11] ) ) {
crb_highlight_fields_type_one( $server_headers, array( 'Location' => $details[11] ) );
}
$cards[] = array( 'title' => __( 'Server Response Headers', 'wp-cerber' ), 'content' => $server_headers, 'type' => 'table' );
}
if ( ! empty( $details[8] ) ) {
crb_highlight_fields_type_two( $details[8], 2 );
$cards[] = array( 'title' => __( 'Client Request Cookies', 'wp-cerber' ), 'content' => $details[8], 'type' => 'table' );
}
// Server Response Cookies
if ( ! empty( $details[9] ) ) {
$server_cookies = array();
foreach ( $details[9] as $item ) {
if ( 0 === stripos( $item, 'Set-Cookie:' ) ) {
$item = substr( $item, 11 );
}
$parts = explode( '=', $item, 2 );
if ( count( $parts ) == 2 ) {
$pos = strpos( $parts[1], ';' );
$server_cookies[ trim( $parts[0] ) ] = urldecode( substr( $parts[1], 0, $pos ) ) . substr( $parts[1], $pos );
}
}
crb_highlight_fields_type_two( $server_cookies, 2 );
$cards[] = array( 'title' => __( 'Server Response Cookies', 'wp-cerber' ), 'content' => $server_cookies, 'type' => 'table' );
}
if ( ! empty( $details[7] ) ) {
$cards[] = array( 'title' => '$_SERVER', 'content' => $details[7], 'type' => 'table' );
}
// PHP Errors
if ( $err_list = $log_entry->php_errors ) {
$errors = array();
foreach ( $err_list as $err ) {
$errors[] = array(
'type' => cerber_get_err_level( $err[0] ) . ' (' . $err[0] . ')',
'info' => $err[1],
'file' => $err[2],
'line' => $err[3],
'source' => crb_get_file_owner( $err[2] ) ?: ''
);
}
$cards[] = array( 'title' => __( 'Software Errors', 'wp-cerber' ), 'content' => $errors, 'type' => 'table' );
}
return $cards;
}
/**
* Renders a string of HTML labels for the request summary line.
*
* @param object $log_entry A single log entry object.
* @param array $status_labels An array of status labels.
*
* @return string The concatenated HTML for the labels.
*
* @since 9.6.9.7
*/
private static function render_summary_labels( $log_entry, $status_labels ) {
$labels_html = '';
// Files label
if ( ! empty( $log_entry->request_fields[3] ) ) {
$labels_html .= '<span class="crb-ffile">F</span>';
}
// WP Type label
if ( $wp_type = cerber_get_wp_type( $log_entry->wp_type ) ) {
$labels_html .= '<span class="crb-wp-type-' . $log_entry->wp_type . '">' . $wp_type . '</span>';
}
// HTTP Code label
$labels_html .= crb_get_http_code_label( (int) $log_entry->http_code );
// Request Status label
if ( ! empty( $log_entry->req_status ) ) {
$status = (int) $log_entry->req_status;
if ( isset( $status_labels[ $status ] ) ) {
$labels_html .= '<span class="crb-req-status-' . $status . '" title="' . $status . '">' . $status_labels[ $status ] . '</span>';
}
}
// PHP Errors label
if ( $errors = $log_entry->php_errors ) {
$labels_html .= '<span class="crb-php-error">SE ' . count( $errors ) . '</span>';
}
return $labels_html;
}
/**
* Renders the summary and detailed view of a request from prepared data.
*
* @param object $log_entry A single log entry object.
* @param array $enriched_data Additional data for users, ACL, etc.
* @param array $events Grouped activity events.
* @param string $base_url The base URL for filter links.
*
* @return array The HTML for the request details, split into 'summary' and 'details'.
*
* @since 9.6.9.7
*/
private static function render_request_details( $log_entry, $enriched_data, $events, $base_url ) {
$activity_labels = cerber_get_labels( 'activity' );
$status_labels = cerber_get_labels( 'status' ) + crb_get_lockout_reason();
// Get formatted URI data and the main body of cards
$uri_data = self::format_uri_data( $log_entry->uri );
$cards_data = self::build_request_details_data( $log_entry );
// Prepend the URI card if it exists
if ( ! empty( $uri_data['card'] ) ) {
array_unshift( $cards_data, $uri_data['card'] );
}
// Build Details HTML from the final list of cards
$cards_html = '';
foreach ( $cards_data as $card ) {
$title_html = '<p style="font-weight: 600;">' . $card['title'] . '</p>';
switch ( $card['type'] ) {
case 'table':
if ( $table_element = crb_ui_table_view( $card['title'], $card['content'] ) ) {
$cards_html .= crb_ui_renderer()->render_element( $table_element );
}
break;
case 'monospace':
$cards_html .= '<div>' . $title_html . '<span class="crb-monospace">' . crb_escape_html( $card['content'] ) . '</span></div>';
break;
}
}
// Build summary HTML
$display_uri_html = $uri_data['display_html'];
$labels_html = self::render_summary_labels( $log_entry, $status_labels );
$processing_time = '';
if ( $log_entry->processing ) {
$processing_time = (int) $log_entry->processing . ' ms';
$threshold = (int) crb_get_settings( 'tithreshold' );
if ( $threshold > 0
&& $log_entry->processing > $threshold ) {
$processing_time = '<span class="crb-processing crb-above">' . $processing_time . '</span>';
}
}
$more_link = $cards_html ? '<a href="#" class="crb-traffic-more">' . __( 'Details', 'wp-cerber' ) . '</a>' : '';
$summary_html = '<b>' . $display_uri_html . '</b>'
. '<p style="margin-top:1em;">'
. '<span class="crb-' . $log_entry->request_method . '">' . $log_entry->request_method . '</span>'
. $labels_html
. '<span>' . $processing_time . '</span> ' . $more_link . '</p>';
// Append activity events to summary
$activity_html = '';
if ( ! empty( $events[ $log_entry->session_id ] ) ) {
foreach ( $events[ $log_entry->session_id ] as $activity_row ) {
$activity_html .= '<p class="' . crb_get_act_style( $activity_row->activity ) . '"><span><a href="' . crb_escape_url( $base_url . '&filter_activity=' . $activity_row->activity ) . '" title="' . crb_attr_escape( $activity_row->activity ) . '" data-no-js="1">' . crb_escape_html( $activity_labels[ $activity_row->activity ] ) . '</a></span>';
$status = (int) $activity_row->ac_status;
if ( ! empty( $status ) && $status != 500 ) { // 500 Whitelisted
$activity_html .= ' <span class = "crb-log-status crb-status-' . $status . '" title="' . crb_attr_escape( $status_labels[ $status ] ) . '">' . crb_escape_html( $status_labels[ $status ] ) . '</span>';
}
$activity_html .= '</p>';
}
}
$summary_html .= $activity_html;
// Append WP entity to summary
if ( isset( $enriched_data['entities'][ $log_entry->wp_id ] ) ) {
$summary_html .= '<p><i>' . crb_escape_html( $enriched_data['entities'][ $log_entry->wp_id ] ) . '</i></p>';
}
return array(
'summary' => $summary_html,
'details' => $cards_html,
);
}
/**
* Renders the UI for when no log entries are found using request-local screen state.
*
* @return string The HTML for the empty state message.
*
* @since 9.6.9.7
*/
private function render_empty_state() {
$ui_hints = array();
$base_url = cerber_admin_link( 'traffic' );
if ( $this->search_active ) {
$ui_hints[] = __( 'No requests found using the given search criteria', 'wp-cerber' );
if ( ! $this->traffic_log_empty ) {
$ui_hints[] = crb_ui_element( 'p', array(), crb_ui_link( $base_url, __( 'View all logged requests', 'wp-cerber' ) ) );
}
}
else {
$ui_hints[] = __( 'No requests have been logged yet.', 'wp-cerber' );
}
if ( ! $this->logging_enabled ) {
$ui_hints[] = __( 'Note: Logging is currently disabled', 'wp-cerber' );
if ( ! $this->search_active ) {
$ui_hints[] = crb_ui_element( 'p', array(), crb_ui_link( 'https://wpcerber.com/wordpress-traffic-logging/', __( 'Documentation', 'wp-cerber' ), array( 'target' => '_blank' ) ) );
}
}
// Self-repair if the log table we think is missing or corrupted
if ( $this->traffic_log_empty ) {
cerber_watchdog( true );
}
return crb_ui_renderer()->render_element( crb_ui_message_box( $ui_hints ) );
}
/**
* Builds and renders the traffic search form using CRB_UI_Form_Builder.
*
* This version is the most declarative and clean, fully abstracting
* the form's structure and layout into the builder class.
*
* @note It temporarily integrates legacy input fields via a raw_html block. Needs to be refactored ASAP.
*
* TODO: refactor legacy dropdown select inputs ASAP
*
* TODO: in the next version it should return CRB_UI_Element The complete search form element
*
* @return string HTML code for the search form
*
* @since 9.6.9.8
*/
private static function render_search_form(): string {
$form_builder = CRB_UI_Form_Builder::create( 'crb-traffic-form', 'get' )
->add_hidden_field( 'page', 'cerber-traffic' );
// --- Column 1 ---
$form_builder
->add_custom_element( crb_ui_element( 'p', [ 'style' => 'width: 100%;' ], [
crb_ui_element( 'label', [], 'Activity' ),
crb_ui_element( 'raw_html', [], '', [ 'content' => crb_get_activity_dd() ] ),
] ) )
->add_field( 'text', 'search_traffic[uri]', 'URL contains' )
->add_select( 'filter_method', 'Request method', [
'0' => 'Any',
'GET' => 'GET',
'POST' => 'POST',
] )
->add_field( 'text', 'search_traffic[fields]', 'Request fields contain', 'Search in POST, GET, REST API, JSON data' )
->add_field( 'text', 'search_traffic[details]', 'Miscellaneous details contains', 'Search in cookies, headers, user agents, referrers' )
->add_field( 'number', 'filter_http_code', 'HTTP Response Code equals' )
->add_field( 'date', 'search_traffic[date_from]', 'Date from', '', [ 'placeholder' => 'month/day/year' ] )
->add_field( 'date', 'search_traffic[date_to]', 'Date to', '', [ 'placeholder' => 'month/day/year' ] );
// --- Column 2 ---
$form_builder->next_column()
->add_field( 'text', 'search_traffic[ip]', 'Remote IP address contains or equals' )
->add_custom_element( crb_ui_element( 'p', [], [
crb_ui_element( 'label', [], 'User' ),
crb_ui_element( 'br' ),
crb_ui_element( 'raw_html', [], '', [ 'content' => cerber_select( 'filter_user', [], null, 'crb-select2-ajax', '', false, '', [ 'min_symbols' => 3 ] ) ] ),
] ) )
->add_field( 'text', 'filter_user_alt', 'User ID', '', [ 'placeholder' => 'Use comma to specify multiple IDs' ] )
->add_select( 'filter_user_mode', 'User operator', [
'0' => 'Include',
'1' => 'Exclude',
] )
->add_field( 'text', 'filter_sid', 'RID', '', [ 'placeholder' => 'Request ID' ] )
->add_field( 'text', 'search_traffic[errors]', 'Software errors contain' )
->add_checkbox( 'filter_errors', 'Any software error' )
->add_custom_element( crb_ui_element( 'p', [ 'style' => 'margin-top: 3em;' ], 'The search uses AND logic for all non-empty fields' ) )
->add_submit( 'Search', [ 'style' => 'width: 8rem;' ] );
$form_element = $form_builder->to_element();
$the_form = crb_ui_element( 'div', [ 'id' => 'crb-traffic-search', 'style' => 'display: none;' ], [ $form_element ] );
return crb_ui_renderer()->render_element( $the_form );
}
}