run( trim( $statement ) ); if ( 'executed' === $result ) { ++$executed; } elseif ( 'skipped' === $result ) { ++$skipped; } else { ++$failed; } $statement = ''; } } fclose( $handle ); return [ 'executed' => $executed, 'skipped' => $skipped, 'failed' => $failed, ]; } /** Execute one statement if the allow list accepts it. */ private function run( string $statement ): string { global $wpdb; if ( '' === $statement || ';' === $statement ) { return 'skipped'; } if ( ! self::is_allowed( $statement ) ) { return 'skipped'; } // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- A dump is a sequence of complete statements with nothing to bind. What may run is constrained by self::ALLOWED, checked immediately above. $result = $wpdb->query( $statement ); if ( false === $result ) { /* * The failing statement is deliberately not returned to the caller: * a dump line can carry password hashes or private option values. */ return 'failed'; } return 'executed'; } private static function is_allowed( string $statement ): bool { foreach ( self::ALLOWED as $pattern ) { if ( preg_match( $pattern, $statement ) ) { return true; } } return false; } /** * Whether a quoted value is still open at the end of this line. * * Escaped backslashes and escaped quotes are removed first so only real * string delimiters are counted. An odd count flips the state. */ private static function closes_open_string( string $line, bool $in_string ): bool { $stripped = str_replace( [ '\\\\', "\\'" ], '', $line ); $quotes = substr_count( $stripped, "'" ); if ( 0 === $quotes % 2 ) { return $in_string; } return ! $in_string; } }