' . esc_html__( 'File integrity monitoring is managed by a network administrator, from the main site.', '404ops-activity-log' ) . '
'; return; } self::render_result_notice(); echo '' . esc_html__( 'Compares WordPress core and wordpress.org plugins with the checksums wordpress.org publishes, compares other plugins and themes with a baseline recorded on the first scan, and looks for executable files in uploads. It only reads files: nothing is ever changed, moved or deleted, and no file contents are stored or sent anywhere.', '404ops-activity-log' ) . '
'; self::render_status(); self::render_settings(); self::render_findings(); } /** 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[ IntegrityActions::RESULT_ARG ] ) ? sanitize_key( wp_unslash( $_GET[ IntegrityActions::RESULT_ARG ] ) ) : ''; if ( ! in_array( $code, IntegrityActions::RESULTS, true ) ) { return; } $messages = array( 'started' => __( 'Scan started. It continues in the background; on a site without WP-Cron, press "Run a batch now" until it finishes.', '404ops-activity-log' ), 'running' => __( 'A scan is already running.', '404ops-activity-log' ), 'batch' => __( 'Batch done. The scan is still running.', '404ops-activity-log' ), 'finished' => __( 'The scan has finished.', '404ops-activity-log' ), 'cancelled' => __( 'Scan cancelled.', '404ops-activity-log' ), 'not_running' => __( 'No scan is running.', '404ops-activity-log' ), 'accepted' => __( 'Finding accepted. The file as it is now is the new baseline, and the acceptance was logged.', '404ops-activity-log' ), 'accepted_unit' => __( 'All findings for that plugin or theme were accepted. It is re-baselined on the next scan, and the acceptance was logged.', '404ops-activity-log' ), 'not_found' => __( 'That finding no longer exists.', '404ops-activity-log' ), 'saved' => __( 'Integrity settings saved.', '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 manage file integrity monitoring.', '404ops-activity-log' ), ); $is_error = in_array( $code, array( 'bad_request', 'bad_nonce', 'forbidden', 'not_found' ), true ); echo '' . esc_html( $messages[ $code ] ) . '
' . esc_html__( 'A scan is running.', '404ops-activity-log' ) . ' ' . esc_html( sprintf( /* translators: 1: current part number, 2: number of parts, 3: the part being scanned, 4: files checked so far. */ __( 'Part %1$d of %2$d (%3$s), %4$d files checked so far.', '404ops-activity-log' ), min( count( $units ), (int) $state['index'] + 1 ), count( $units ), $unit, (int) $state['files'] ) ) . '
'; echo ''; self::button( IntegrityActions::OP_BATCH, '', __( 'Run a batch now', '404ops-activity-log' ), 'primary' ); self::button( IntegrityActions::OP_CANCEL, '', __( 'Cancel the scan', '404ops-activity-log' ), 'secondary' ); echo '
'; } else { if ( array() === $last ) { echo '' . esc_html__( 'No scan has run yet. The first scan records the baseline for plugins and themes wordpress.org cannot vouch for; changes are reported from then on.', '404ops-activity-log' ) . '
'; } else { $new = array_sum( array_map( 'intval', (array) ( $last['new'] ?? array() ) ) ); echo '' . esc_html( sprintf( /* translators: 1: date and time, 2: files checked, 3: new findings. */ __( 'Last scan finished %1$s: %2$d files checked, %3$d new findings.', '404ops-activity-log' ), wp_date( 'Y-m-d H:i', (int) ( $last['finished'] ?? 0 ) ), (int) ( $last['files'] ?? 0 ), $new ) ) . '
'; self::render_scan_caveats( $last ); } echo ''; self::button( IntegrityActions::OP_START, '', __( 'Start a scan', '404ops-activity-log' ), 'primary' ); echo '
'; } echo '' . esc_html( sprintf( /* translators: 1: high, 2: medium, 3: low severity counts. */ __( 'High: %1$d · Medium: %2$d · Low: %3$d', '404ops-activity-log' ), $counts[ Comparison::HIGH ], $counts[ Comparison::MEDIUM ], $counts[ Comparison::LOW ] ) ) . '
'; if ( 0 === $total ) { echo '' . esc_html__( 'Nothing to review.', '404ops-activity-log' ) . '
'; return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- pagination only. $page = isset( $_GET['integrity_page'] ) ? max( 1, absint( $_GET['integrity_page'] ) ) : 1; $rows = Store::findings( self::PER_PAGE, ( $page - 1 ) * self::PER_PAGE ); $roots = Roots::from_wordpress(); $units = array(); echo '| ' . esc_html( $heading ) . ' | '; } echo '||||||
|---|---|---|---|---|---|---|
| ' . esc_html( self::severity_label( (int) $row['severity'] ) ) . ' | '; echo '' . esc_html( self::status_label( (string) $row['status'] ) ) . ' | '; echo '' . esc_html( $roots->display( Scanner::root_of( $unit ), (string) $row['path'] ) ) . '' . esc_html( self::source_label( (string) $row['source'], $unit ) ) . ' | ';
echo '' . esc_html( null === $row['expected'] ? '—' : implode( ', ', Comparison::expected_hashes( (string) $row['expected'] ) ) ) . ' | '; echo '' . esc_html( self::found( $row ) ) . ' | '; echo '' . esc_html( null === $row['detected_at'] ? '—' : (string) $row['detected_at'] . ' UTC' ) . ' | '; echo ''; self::button( IntegrityActions::OP_ACCEPT, (string) (int) $row['id'], __( 'Accept', '404ops-activity-log' ), 'secondary' ); echo ' | '; echo '
'; for ( $i = 1; $i <= $pages; $i++ ) { $url = add_query_arg( array( 'page' => '404ops-activity-log', 'tab' => 'integrity', 'integrity_page' => $i, ), admin_url( 'admin.php' ) ); echo $i === $page ? '' . esc_html( (string) $i ) . ' ' : '' . esc_html( (string) $i ) . ' '; } echo '
'; } if ( array() !== $units ) { echo '' . esc_html__( 'For a plugin or theme you installed or changed yourself outside WordPress\'s updater: forgets its findings and records it as it is now on the next scan.', '404ops-activity-log' ) . '
'; foreach ( array_keys( $units ) as $unit ) { /* translators: %s: plugin or theme identifier. */ self::button( IntegrityActions::OP_ACCEPT_UNIT, $unit, sprintf( __( 'Accept all of %s', '404ops-activity-log' ), $unit ), 'secondary' ); } echo '
'; } } /** * What is on the disk now. * * @param array