'404ops-activity-log', 'tab' => 'chain', self::RESULT_ARG => $result, ), admin_url( 'admin.php' ) ) ); exit; } /** * Carry out one posted operation. * * @param array $request The posted fields, unslashed. * @return string A RESULTS code. */ public static function perform( array $request ): string { $op = isset( $request['op'] ) && is_string( $request['op'] ) ? sanitize_key( $request['op'] ) : ''; $nonce = isset( $request['_wpnonce'] ) && is_string( $request['_wpnonce'] ) ? sanitize_text_field( $request['_wpnonce'] ) : ''; if ( ! in_array( $op, array( self::OP_VERIFY, self::OP_RESTART ), true ) ) { return 'bad_request'; } if ( false === wp_verify_nonce( $nonce, self::nonce_action( $op ) ) ) { return 'bad_nonce'; } if ( ! current_user_can( self::capability() ) ) { return 'forbidden'; } if ( self::OP_VERIFY === $op ) { if ( self::advance( self::STEP_SECONDS ) ) { self::schedule(); return 'verify_running'; } return 'verify_done'; } if ( empty( $request['confirm'] ) ) { return 'confirm_needed'; } self::restart(); return 'restarted'; } /** * Work on the verification for a while; true while it is still running. * * @param float $seconds Budget. */ public static function advance( float $seconds ): bool { return Verifier::DONE !== Verifier::run( $seconds )['status']; } /** * Begin a new chain: everything logged so far becomes "before the * chain", the head and ledger are cleared, and the restart is the first * thing the new chain records. */ public static function restart(): void { global $wpdb; $key = Key::get(); $table = Database::get_table_name(); $last = Verifier::last(); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- the plugin's own table; $table is prefix-composed. $floor = (int) $wpdb->get_var( "SELECT MAX(id) FROM $table" ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $head = Ledger::head( $key ); Ledger::write_cuts( $key, $floor, array() ); delete_option( Ledger::HEAD_OPTION ); delete_option( Verifier::STATE_OPTION ); wp_clear_scheduled_hook( self::CRON_HOOK ); EventLogger::log( Verifier::EVENT_RESTART, 'Log chain restarted', 'chain', null, array( 'previous_verdict' => isset( $last['verdict'] ) ? (string) $last['verdict'] : 'never_verified', 'previous_head_id' => $head['id'], 'floor' => $floor, ) ); } /** * The daily cron's part: run a verification's first slice, or the next * one if a run is already under way (a continuation event lost to a * site without WP-Cron must not strand it). */ public static function daily(): void { self::cron(); } /** WP-Cron continuation: run for a while, and come back while there is more. */ public static function cron(): void { if ( self::advance( self::CRON_SECONDS ) ) { self::schedule(); } } private static function schedule(): void { if ( ! wp_next_scheduled( self::CRON_HOOK ) ) { wp_schedule_single_event( time() + 30, self::CRON_HOOK ); } } /** Remove everything this feature stored. Uninstall only. */ public static function uninstall(): void { Ledger::uninstall(); delete_option( Verifier::STATE_OPTION ); delete_option( Verifier::LAST_OPTION ); } }