prefix . 'guardforge_audit_log'; if ( isset( $_POST['guardforge_clear_audit_log'] ) && check_admin_referer( 'guardforge_clear_audit_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__( 'Audit log cleared.', 'guardforge' ) . '
'; } $per_page = 50; $paged = max( 1, ( isset( $_GET['apaged'] ) ? absint( wp_unslash( $_GET['apaged'] ) ) : 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_ev = ( isset( $_GET['event_type'] ) ? sanitize_text_field( wp_unslash( $_GET['event_type'] ) ) : '' ); // 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_user = ( isset( $_GET['user_login'] ) ? sanitize_text_field( wp_unslash( $_GET['user_login'] ) ) : '' ); // 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_ev ) { $guardforge_where[] = $wpdb->prepare( 'event_type LIKE %s', '%' . $wpdb->esc_like( $guardforge_filter_ev ) . '%' ); } if ( $guardforge_filter_user ) { $guardforge_where[] = $wpdb->prepare( 'user_login LIKE %s', '%' . $wpdb->esc_like( $guardforge_filter_user ) . '%' ); } $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 ?apaged=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( 'audit', $guardforge_row ); } $guardforge_explainer->prime( $guardforge_events ); } /* translators: %d is the total number of admin action events. */ $guardforge_subtitle = sprintf( __( 'Admin actions · total: %d', 'guardforge' ), $guardforge_total ); ?>
✓ 1 ) { echo ' ' . esc_html( sprintf( /* translators: %d is how many times the chain restarted from a fresh genesis. */ _n( '(chain restarted %d time — pre-upgrade or unhashed rows are in the mix; each restart is only expected once, at row 1.)', '(chain restarted %d times — pre-upgrade or unhashed rows are in the mix; each restart is only expected once, at row 1.)', $guardforge_verify_g, 'guardforge' ), $guardforge_verify_g ) ); } if ( $guardforge_verify_truncated ) { echo ' ' . esc_html__( 'This table is larger than one check can walk within a single request, so only the oldest rows were checked this time — nothing broken was found among them. Lowering how long the audit log is kept (Settings) shrinks the table so a future check can cover all of it.', 'guardforge' ); } ?>

render_export_form(); ?>

render_explanation_row( $guardforge_events[ (int) $guardforge_row->id ], 7 ); } ?>
event_time ); ?> user_login ?: '—' ); ?> event_type ); ?> object_type ); ?> object_name . ' ' . $guardforge_row->details ) ); ?> ip ); ?> render_control( 'audit', $guardforge_events[ (int) $guardforge_row->id ] ); ?>