.schema and db/
.jsonl entries the database steps ask for come from the work folder the
* convert-wpress step fills from database.sql, in exactly the .mmig format. So the import steps run unchanged.
*
* @package Mench_Migrate
*/
defined( 'ABSPATH' ) || exit;
// phpcs:disable WordPress.WP.AlternativeFunctions -- Streams backups, uploads and job files (often several GB) chunk by chunk inside the plugin's own storage folder and the site being restored; WP_Filesystem has no streaming or locking API.
class Mench_Migrate_Wpress_Source implements Mench_Migrate_Import_Source {
private const BLOCK = 1048576;
private Mench_Migrate_Wpress_Reader $reader;
/** Folder holding .schema and .jsonl. */
private string $db_dir;
public function __construct( Mench_Migrate_Wpress_Reader $reader, string $db_dir ) {
$this->reader = $reader;
$this->db_dir = $db_dir;
}
public function reader(): Mench_Migrate_Wpress_Reader {
return $this->reader;
}
public function entries(): array {
return $this->reader->entries();
}
public function entry_count(): int {
return $this->reader->entry_count();
}
public function entry_at( int $i ): array {
return $this->reader->entry_at( $i );
}
public function each(): iterable {
return $this->reader->each();
}
public function find( string $rel ): ?array {
return $this->reader->find( $rel );
}
public function manifest(): array {
return $this->reader->manifest();
}
public function is_encrypted(): bool {
return false;
}
public function read_string( string $rel ): string {
$file = $this->converted( $rel );
if ( null === $file ) {
return $this->reader->read_string( $rel );
}
$data = is_file( $file ) ? file_get_contents( $file ) : false; // phpcs:ignore WordPress.WP.AlternativeFunctions
if ( false === $data ) {
throw new Mench_Migrate_Archive_Exception( 'unknown_entry', 'archive' );
}
return $data;
}
public function read_head( string $rel, int $max ): string {
return null === $this->converted( $rel ) ? $this->reader->read_head( $rel, $max ) : substr( $this->read_string( $rel ), 0, max( 0, $max ) );
}
/** Converted table files are copied block by block, resumable from {written}; everything else comes from the archive. */
public function extract( string $rel, string $dest_abs, array $cursor, float $deadline ): array {
$file = $this->converted( $rel );
if ( null === $file ) {
return $this->reader->extract( $rel, $dest_abs, $cursor, $deadline );
}
clearstatcache( true, $file );
$size = is_file( $file ) ? (int) filesize( $file ) : -1;
if ( $size < 0 ) {
throw new Mench_Migrate_Archive_Exception( 'unknown_entry', 'archive' );
}
$written = $cursor['written'] ?? 0;
if ( ! is_int( $written ) || $written < 0 || $written > $size ) {
throw new Mench_Migrate_Archive_Exception( 'bad_cursor', 'extract' );
}
$out = @fopen( $dest_abs, isset( $cursor['written'] ) ? 'c+b' : 'wb' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
$in = @fopen( $file, 'rb' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
if ( false === $out || false === $in ) {
throw new Mench_Migrate_Archive_Exception( 'write_failed', 'extract' );
}
try {
if ( fstat( $out )['size'] < $written ) {
throw new Mench_Migrate_Archive_Exception( 'truncated', 'extract' );
}
ftruncate( $out, $written );
fseek( $out, $written );
fseek( $in, $written );
while ( $written < $size ) {
$data = (string) fread( $in, min( self::BLOCK, $size - $written ) );
if ( '' === $data || fwrite( $out, $data ) !== strlen( $data ) ) {
throw new Mench_Migrate_Archive_Exception( 'write_failed', 'extract' );
}
$written += strlen( $data );
if ( microtime( true ) >= $deadline ) {
break;
}
}
} finally {
fclose( $in );
fclose( $out );
}
return [ 'done' => $written >= $size, 'cursor' => [ 'written' => $written ] ];
}
/** The work file behind db/.schema|jsonl, or null for an archive entry. */
private function converted( string $rel ): ?string {
if ( ! preg_match( '#^db/(.+)\.(schema|jsonl)$#D', $rel, $m ) ) {
return null;
}
if ( ! Mench_Migrate_Schema::is_valid_name( $m[1] ) ) {
throw new Mench_Migrate_Archive_Exception( 'unsafe_path', 'archive' );
}
return $this->db_dir . '/' . $m[1] . '.' . $m[2];
}
}