Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add import command #345

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions inc/composer/class-command.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
namespace Altis\Local_Server\Composer;

use Composer\Command\BaseCommand;
use GuzzleHttp;
use PharData;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
Expand Down Expand Up @@ -75,6 +78,7 @@ protected function configure() {
logs <service> <service> can be php, nginx, db, s3, elasticsearch, xray
Import files from content/uploads directly to s3:
import-uploads Copies files from `content/uploads` to s3
import <export-url> Import database and files from an Altis Cloud export URL.
EOT
)
->addOption( 'xdebug', null, InputOption::VALUE_OPTIONAL, 'Start the server with Xdebug', 'debug' )
Expand Down Expand Up @@ -161,6 +165,8 @@ protected function execute( InputInterface $input, OutputInterface $output ) {
return $this->shell( $input, $output );
} elseif ( $subcommand === 'import-uploads' ) {
return $this->import_uploads( $input, $output );
} elseif ( $subcommand === 'import' ) {
return $this->import( $input, $output );
} elseif ( $subcommand === null ) {
// Default to start command.
return $this->start( $input, $output );
Expand Down Expand Up @@ -687,6 +693,192 @@ protected function import_uploads() {
) );
}

/**
* Human readable file size formatter.
*
* @param float $size
* @param integer $precision
* @return integer
*/
private function human_filesize( float $size, int $precision = 2 ) {
for($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {}
return round( $size, $precision) . ['B','kB','MB','GB','TB','PB','EB','ZB','YB'][$i];
}

/**
* Remove a directory with all files
*
* Taken from https://stackoverflow.com/questions/3338123/how-do-i-recursively-delete-a-directory-and-its-entire-contents-files-sub-dir
*
* @param string $dir
*/
private function rmdir_recursive( string $dir ) {
if (is_dir($dir)) {
$objects = scandir($dir);
foreach ($objects as $object) {
if ($object != "." && $object != "..") {
if (is_dir($dir. DIRECTORY_SEPARATOR .$object) && !is_link($dir."/".$object)) {
rrmdir($dir. DIRECTORY_SEPARATOR .$object);
} else {
unlink($dir. DIRECTORY_SEPARATOR .$object);
}
}
}
rmdir($dir);
}
}

/**
* Gunzip a file
*
* Taken from https://stackoverflow.com/questions/3293121/how-can-i-unzip-a-gz-file-with-php
*
* @param string $file_name
* @return string Path to unzipped file.
*/
private function gunzip_file( string $file_name, string $out_file_name ) : string {
// Raising this value may increase performance
$buffer_size = 4096; // read 4kb at a time

// Open our files (in binary mode)
$file = gzopen( $file_name, 'rb' );
$out_file = fopen( $out_file_name, 'wb' );

// Keep repeating until the end of the input file
while( ! gzeof( $file ) ) {
// Read buffer-size bytes
// Both fwrite and gzread and binary-safe
fwrite( $out_file, gzread( $file, $buffer_size ) );
}

// Files are done, close files
fclose( $out_file );
gzclose( $file );

return $out_file_name;
}

protected function import( InputInterface $input, OutputInterface $output ) {
$export_url = $input->getArgument( 'options' )[0];
$export_urls_parts = parse_url( $export_url );
// Todo: clean up file on sigterm
$download_progress_bar = new ProgressBar( $output, 100 );

$download_file_path = tempnam( sys_get_temp_dir(), basename( $export_urls_parts['path'] ) );
$download_file = fopen( $download_file_path, 'w' );
$guzzle_client = new GuzzleHttp\Client();

$guzzle_client->request( 'GET', $export_url, [
'sink' => $download_file,
'on_headers' => function ( GuzzleHttp\Psr7\Response $response ) use ( $download_progress_bar ) {
$size = $response->getHeaderLine( 'Content-Length' );
$download_progress_bar->setFormat( 'Downloading ' . $this->human_filesize( $size ) . ' [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%' );
$download_progress_bar->start();

},
'progress' => function ( int $total_bytes, int $bytes_so_far ) use ( $download_progress_bar ) {
if ( ! $total_bytes || ! $bytes_so_far ) {
return;
}
$download_progress_bar->setProgress( $bytes_so_far / $total_bytes * 100 );
},
] );

$download_progress_bar->finish();
$output->writeln( 'Complete' );

$extract_dir = sys_get_temp_dir() . '/export';
if ( is_dir( $extract_dir ) ) {
$this->rmdir_recursive( $extract_dir );
}
try {
$phar = new PharData( $download_file_path );
$phar->extractTo( $extract_dir ); // extract all files
} catch ( Exception $e ) {
$output->writeln( sprintf( '<error>Unable to extract tar file: %s</error>', $e->getMessage() ) );
// To do: cleanup
return;
}

if ( file_exists( $extract_dir . '/database.sql.gz' ) ) {
$output->writeln( 'Database found in export, importing...' );
$database_file_path = 'database.sql';
$sql_file = $this->gunzip_file( $extract_dir . '/database.sql.gz', $database_file_path );

// Import the file into mysql
$cli = $this->getApplication()->find( 'local-server' );
$import = $cli->run( new ArrayInput( [
'subcommand' => 'cli',
'options' => [
'db',
'import',
$sql_file
],
] ), $output );

// Flush cache
$flush_cache = $cli->run( new ArrayInput( [
'subcommand' => 'cli',
'options' => [
'cache',
'flush'
],
] ), $output );


ob_start();
$export_sites = $cli->run( new ArrayInput( [
'subcommand' => 'cli',
'options' => [
'db',
'query',
'SELECT DISTINCT domain from wp_site'
],
] ), $output );
$sites_output = ob_get_clean();
// Oh lordy! There's no good way to run a query via WP-CLI and get machine readable output.
preg_match_all( '/ (\S+) /', $sites_output, $domains, PREG_SET_ORDER );
$domains = array_map( function ( array $match ) : string {
return $match[1];
}, $domains );
$domains = array_diff( $domains, [ 'domain' ] );

$output->writeln( sprintf( 'Replacing URLs for sites %s.', implode( ', ', $domains ) ) );
$replacement_domain = $this->get_project_subdomain() . '.altis.dev';
foreach ( $domains as $export_domain ) {
$cli->run( new ArrayInput( [
'subcommand' => 'cli',
'options' => [
'search-replace',
$export_domain,
$replacement_domain,
'--network',
'--url=' . $export_domain,
],
] ), $output );
}

// Flush cache
$flush_cache = $cli->run( new ArrayInput( [
'subcommand' => 'cli',
'options' => [
'cache',
'flush'
],
] ), $output );

$elasticpress_index = $cli->run( new ArrayInput( [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also have the wp altis migrate command now but that's technically not required, just wondering if we can trigger everything hooked onto the multisite-install command instead, without actually running that command!

'subcommand' => 'cli',
'options' => [
'elasticpress',
'index',
'--setup',
'--network-wide'
],
] ), $output );
}
}

/**
* Pass a command through to the minio client.
*
Expand Down