' . esc_html__( 'You are not allowed to verify the log.', '404ops-activity-log' ) . '
'; return; } self::render_result_notice(); echo '' . esc_html__( 'Every event is sealed into a chain when it is logged: each entry carries a keyed signature over its own content and over the entry before it. Editing an entry, or removing or inserting entries, breaks the chain and shows up here. Retention and privacy erasures are expected changes and are recorded as such.', '404ops-activity-log' ) . '
'; self::render_key_note(); self::render_status(); self::render_findings(); self::render_restart(); } /** The notice for the result an action redirected back with. */ private static function render_result_notice(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only. $code = isset( $_GET[ ChainActions::RESULT_ARG ] ) ? sanitize_key( wp_unslash( $_GET[ ChainActions::RESULT_ARG ] ) ) : ''; if ( ! in_array( $code, ChainActions::RESULTS, true ) ) { return; } $messages = array( 'verify_done' => __( 'Verification finished.', '404ops-activity-log' ), 'verify_running' => __( 'Verification is under way. It continues in the background; press "Verify now" again to carry it on.', '404ops-activity-log' ), 'restarted' => __( 'A new chain was started, and the restart was logged.', '404ops-activity-log' ), 'confirm_needed' => __( 'Tick the box to confirm that you want to start a new chain.', '404ops-activity-log' ), 'bad_request' => __( 'That request was not understood.', '404ops-activity-log' ), 'bad_nonce' => __( 'The link has expired. Reload the page and try again.', '404ops-activity-log' ), 'forbidden' => __( 'You are not allowed to verify the log.', '404ops-activity-log' ), ); $is_error = in_array( $code, array( 'confirm_needed', 'bad_request', 'bad_nonce', 'forbidden' ), true ); echo '' . esc_html( $messages[ $code ] ) . '
' . esc_html__( 'The signing key comes from wp-config.php, so someone who can change the database but cannot read the site\'s files cannot make an altered log verify.', '404ops-activity-log' ) . '
'; return; } echo '' . esc_html__( 'This site\'s secret keys are not set in wp-config.php, so WordPress keeps them in the database. The chain still catches accidental and careless changes, but anyone who can write to the database can also read the key. Add unique keys and salts to wp-config.php, then start a new chain.', '404ops-activity-log' ) . '
' . esc_html__( 'A verification is running.', '404ops-activity-log' ) . '
'; } elseif ( array() === $last ) { echo '' . esc_html__( 'The log has not been verified yet. It is verified once a day, or whenever you press the button.', '404ops-activity-log' ) . '
'; } else { $verdict = (string) ( $last['verdict'] ?? '' ); echo '' . esc_html( self::verdict_label( $verdict ) ) . ' ' . esc_html( sprintf( /* translators: 1: date and time, 2: entries checked. */ __( 'Checked %1$s: %2$d entries.', '404ops-activity-log' ), wp_date( 'Y-m-d H:i', (int) ( $last['finished'] ?? 0 ) ), (int) ( $last['rows'] ?? 0 ) ) ) . '
'; echo '| ' . esc_html__( 'Finding', '404ops-activity-log' ) . ' | ' . esc_html__( 'Entry', '404ops-activity-log' ) . ' |
|---|---|
| ' . esc_html( self::finding_label( (string) ( $finding['kind'] ?? '' ) ) ) . ' | '; echo '' . ( $id > 0 ? esc_html( (string) $id ) : '—' ) . ' | '; echo '
' . esc_html__( 'Use this after the site\'s secret keys changed, or to accept a finding you have investigated. Entries logged so far stay in the log but are no longer verified. The restart is written to the log, with the verdict it replaced.', '404ops-activity-log' ) . '
'; echo ''; } /** * One POST button to admin-post.php. * * @param string $op Operation. * @param string $label Button text. * @param string $kind primary or secondary. * @param string $extra Unused spacing hook, kept empty. */ private static function post_form( string $op, string $label, string $kind, string $extra ): void { echo ''; } /** * @param string $verdict A Verifier verdict. */ private static function verdict_label( string $verdict ): string { switch ( $verdict ) { case 'intact': return __( 'The log is intact.', '404ops-activity-log' ); case 'warning': return __( 'The log verifies, with a warning: some entries were stored without a seal.', '404ops-activity-log' ); case 'tampered': return __( 'The log has been altered.', '404ops-activity-log' ); case 'key_changed': return __( 'The signing key has changed, so the log cannot be verified.', '404ops-activity-log' ); case 'empty': return __( 'Nothing has been sealed yet.', '404ops-activity-log' ); default: return __( 'Unknown result.', '404ops-activity-log' ); } } /** * @param string $kind A Walk finding kind. */ private static function finding_label( string $kind ): string { switch ( $kind ) { case Walk::EDITED: return __( 'Entry content was changed after it was logged', '404ops-activity-log' ); case Walk::FORGED: return __( 'Entry seal is invalid (altered or inserted without the key)', '404ops-activity-log' ); case Walk::GAP: return __( 'Entries are missing, inserted or reordered just before this one', '404ops-activity-log' ); case Walk::TAIL: return __( 'The newest entries were removed (the last recorded entry is gone)', '404ops-activity-log' ); case Walk::HEAD: return __( 'The record of the newest entry is missing or invalid', '404ops-activity-log' ); case Walk::LEDGER: return __( 'The record of retention deletions is invalid', '404ops-activity-log' ); case Walk::UNSEALED: return __( 'Entry was stored without a seal', '404ops-activity-log' ); default: return __( 'Unknown finding', '404ops-activity-log' ); } } }