prefix . 'guardforge_login_log'; if ( isset( $_POST['guardforge_clear_login_log'] ) && check_admin_referer( 'guardforge_clear_login_log' ) ) { // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- plugin-owned table ($wpdb->prefix + a literal); a table name cannot be a placeholder, and this runs only on uninstall or behind the nonce-checked clear button. $wpdb->query( "TRUNCATE TABLE {$guardforge_table}" ); echo '
✓ ' . esc_html__( 'Login log cleared.', 'guardforge' ) . '
'; } $per_page = 50; $paged = max( 1, ( isset( $_GET['lpaged'] ) ? absint( wp_unslash( $_GET['lpaged'] ) ) : 1 ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only: this screen's own filter or pager; it narrows what is listed and changes nothing. $guardforge_filter_r = ( isset( $_GET['result'] ) ? sanitize_text_field( wp_unslash( $_GET['result'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only: this screen's own filter or pager; it narrows what is listed and changes nothing. $guardforge_filter_ip = ( isset( $_GET['ip'] ) ? sanitize_text_field( wp_unslash( $_GET['ip'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only: this screen's own filter or pager; it narrows what is listed and changes nothing. $guardforge_where = []; if ( $guardforge_filter_r ) { $guardforge_where[] = $wpdb->prepare( 'result = %s', $guardforge_filter_r ); } if ( $guardforge_filter_ip ) { $guardforge_where[] = $wpdb->prepare( 'ip LIKE %s', '%' . $wpdb->esc_like( $guardforge_filter_ip ) . '%' ); } $guardforge_wsql = $guardforge_where ? 'WHERE ' . implode( ' AND ', $guardforge_where ) : ''; $guardforge_total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$guardforge_table} {$guardforge_wsql}" ); // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- plugin-owned table ($wpdb->prefix + a literal); $guardforge_wsql is built only from clauses each passed through $wpdb->prepare above. $pages = (int) ceil( $guardforge_total / $per_page ); // Clamped before the OFFSET is worked out: a bookmarked ?lpaged=9 that pruning // has since put out of range would otherwise draw an empty table under a // "Page 9 of 2" the operator cannot get out of. $paged = min( $paged, max( 1, $pages ) ); // Newest first, and among events of the same second the newest row (highest // id) first: event_time is a second wide, and with no last key MySQL may cut // such a tie one way for page 1 and another for page 2, so a row would show // on two pages and another on none. // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is prefixed constant, $guardforge_wsql uses $wpdb->prepare clauses, LIMIT/OFFSET are cast integers. $guardforge_rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$guardforge_table} {$guardforge_wsql} ORDER BY event_time DESC, id DESC LIMIT %d OFFSET %d", $per_page, ( $paged - 1 ) * $per_page ) ); $guardforge_explainer = class_exists( 'GUARDFORGE_Event_Explainer' ) ? GUARDFORGE_Event_Explainer::instance() : null; $guardforge_events = []; if ( $guardforge_explainer ) { foreach ( $guardforge_rows as $guardforge_row ) { $guardforge_events[ (int) $guardforge_row->id ] = $guardforge_explainer->normalise( 'login', $guardforge_row ); } $guardforge_explainer->prime( $guardforge_events ); } // GF-3: a successful login is not an event anyone needs explained — the // button only earns its place on a row that is already a question. These // are the three values class-bruteforce.php::log_attempt actually writes // besides 'success' (see on_login_failed); a fourth show up here only if // that vocabulary changes, and this list has to change with it. $guardforge_explainable_results = [ 'failed', 'soft_locked', 'locked' ]; /* translators: %d is the total number of authentication attempts. */ $guardforge_subtitle = sprintf( __( 'Authentication attempts · total: %d', 'guardforge' ), $guardforge_total ); ?>
render_explanation_row( $guardforge_events[ (int) $guardforge_row->id ], 6 ); } ?>
event_time ); ?> ip ); ?> username ); ?> result ); ?> user_agent ); ?> result, $guardforge_explainable_results, true ) ) : ?> render_control( 'login', $guardforge_events[ (int) $guardforge_row->id ] ); ?> " would, or every successful row // in the page goes visibly shorter than the ones around it. ?>