php_version_required, '>=' ) ) {
return;
}
global $wpmdb;
$this->wpmdb = $wpmdb;
add_filter( 'wpmdb_cli_finalize_migration_response', array( $this, 'finalize_ajax' ), 10, 1 );
}
/**
* Checks profile data before CLI migration.
*
* @param int|array $profile Profile key or array.
*
* @return mixed|WP_Error
*/
public function pre_cli_migration_check( $profile ) {
if ( ! version_compare( PHP_VERSION, $this->php_version_required, '>=' ) ) {
return $this->cli_error( sprintf( __( 'CLI addon requires PHP %1$s+', 'wp-migrate-db-cli' ), $this->php_version_required ) );
}
if ( is_array( $profile ) ) {
$query_str = http_build_query( $profile );
$profile = $this->wpmdb->parse_migration_form_data( $query_str );
$profile = wp_parse_args( $profile, array(
'save_computer' => '0',
'gzip_file' => '0',
'replace_guids' => '0',
'exclude_transients' => '0',
'exclude_spam' => '0',
'keep_active_plugins' => '0',
'compatibility_older_mysql' => '0',
) );
}
$this->profile = $profile = apply_filters( 'wpmdb_cli_profile_before_migration', $profile );
if ( is_wp_error( $profile ) ) {
return $profile;
}
return true;
}
/**
* Performs CLI migration given a profile data.
*
* @param int|array $profile Profile key or array.
*
* @return bool|WP_Error Returns true if succeed or WP_Error if failed.
*/
public function cli_migration( $profile ) {
$pre_check = $this->pre_cli_migration_check( $profile );
if ( is_wp_error( $pre_check ) ) {
return $pre_check;
}
// At this point, $profile has been checked a retrieved into $this->profile, so should not be used in this function any further.
if ( empty( $this->profile ) ) {
return $this->cli_error( __( 'Profile not found or unable to be generated from params.', 'wp-migrate-db-cli' ) );
}
unset( $profile );
$this->set_time_limit();
$this->wpmdb->set_cli_migration();
if ( 'savefile' === $this->profile['action'] ) {
$this->post_data['intent'] = 'savefile';
if ( ! empty( $this->profile['export_dest'] ) ) {
$this->post_data['export_dest'] = $this->profile['export_dest'];
} else {
$this->post_data['export_dest'] = 'ORIGIN';
}
}
if ( 'find_replace' === $this->profile['action'] ) {
$this->post_data['intent'] = 'find_replace';
}
// Ensure local site_details available.
$this->post_data['site_details']['local'] = $this->site_details();
// Check for tables specified in migration profile that do not exist in the source database
if ( ! empty( $this->profile['select_tables'] ) ) {
$source_tables = apply_filters( 'wpmdb_cli_filter_source_tables', $this->get_tables() );
if ( ! empty( $source_tables ) ) {
// Return error if selected tables do not exist in source database
$nonexistent_tables = array();
foreach ( $this->profile['select_tables'] as $table ) {
if ( ! in_array( $table, $source_tables ) ) {
$nonexistent_tables[] = $table;
}
}
if ( ! empty( $nonexistent_tables ) ) {
$local_or_remote = ( 'pull' === $this->profile['action'] ) ? 'remote' : 'local';
return $this->cli_error( sprintf( __( 'The following table(s) do not exist in the %1$s database: %2$s', 'wp-migrate-db-cli' ), $local_or_remote, implode( ', ', $nonexistent_tables ) ) );
}
}
}
$this->profile = apply_filters( 'wpmdb_cli_filter_before_cli_initiate_migration', $this->profile );
if ( is_wp_error( $this->profile ) ) {
return $this->profile;
}
do_action( 'wpmdb_cli_before_migration', $this->post_data, $this->profile );
$this->migration = $this->cli_initiate_migration();
if ( is_wp_error( $this->migration ) ) {
return $this->migration;
}
$this->post_data['migration_state_id'] = $this->migration['migration_state_id'];
$tables_to_process = $this->migrate_tables();
if ( is_wp_error( $tables_to_process ) ) {
return $tables_to_process;
}
$this->post_data['tables'] = implode( ',', $tables_to_process );
$finalize = $this->finalize_migration();
if ( is_wp_error( $finalize ) || 'savefile' === $this->profile['action'] ) {
return $finalize;
}
return true;
}
/**
* Verify CLI response from endpoint.
*
* @param string $response Response from endpoint.
* @param string $function_name Name of called function.
*
* @return WP_Error|string
*/
function verify_cli_response( $response, $function_name ) {
$response = trim( $response );
if ( false === $response ) {
return $this->cli_error( $this->error );
}
if ( false === $this->wpmdb->is_json( $response ) ) {
return $this->cli_error( sprintf( __( 'We were expecting a JSON response, instead we received: %2$s (function name: %1$s)', 'wp-migrate-db-cli' ), $function_name, $response ) );
}
$response = json_decode( $response, true );
if ( isset( $response['wpmdb_error'] ) ) {
return $this->cli_error( $response['body'] );
}
// Display warnings and non fatal error messages as CLI warnings without aborting.
if ( isset( $response['wpmdb_warning'] ) || isset( $response['wpmdb_non_fatal_error'] ) ) {
$body = ( isset ( $response['cli_body'] ) ) ? $response['cli_body'] : $response['body'];
$messages = maybe_unserialize( $body );
foreach ( ( array ) $messages as $message ) {
if ( $message ) {
WP_CLI::warning( self::cleanup_message( $message ) );
}
}
}
return $response;
}
/**
* Return instance of WP_Error.
*
* @param string $message Error message.
*
* @return WP_Error.
*/
function cli_error( $message ) {
return new WP_Error( 'wpmdb_cli_error', self::cleanup_message( $message ) );
}
/**
* Cleanup message, replacing
with \n and removing HTML.
*
* @param string $message Error message.
*
* @return string $message.
*/
static function cleanup_message( $message ) {
$message = html_entity_decode( $message, ENT_QUOTES );
$message = preg_replace( '#
#', "\n", $message );
$message = trim( strip_tags( $message ) );
return $message;
}
/**
* Initiates migration and verifies result
*
* @return array|WP_Error
*/
function cli_initiate_migration() {
do_action( 'wpmdb_cli_before_initiate_migration', $this->profile );
WP_CLI::log( __( 'Initiating migration...', 'wp-migrate-db-cli' ) );
$migration_args = $this->post_data;
$migration_args['form_data'] = http_build_query( $this->profile );
$migration_args['stage'] = 'migrate';
$migration_args['site_details']['local'] = $this->site_details();
if ( 'find_replace' === $this->profile['action'] ) {
$migration_args['stage'] = 'find_replace';
}
$this->post_data = apply_filters( 'wpmdb_cli_initiate_migration_args', $migration_args, $this->profile );
$this->post_data['site_details'] = json_encode( $this->post_data['site_details'] );
$response = $this->initiate_migration( $this->post_data );
$initiate_migration_response = $this->verify_cli_response( $response, 'initiate_migration()' );
if ( ! is_wp_error( $initiate_migration_response ) ) {
$initiate_migration_response = apply_filters( 'wpmdb_cli_initiate_migration_response', $initiate_migration_response );
}
return $initiate_migration_response;
}
/**
* Determine which tables to migrate
*
* @return array|WP_Error
*/
function get_tables_to_migrate() {
$tables_to_migrate = $this->get_tables( 'prefix' );
return apply_filters( 'wpmdb_cli_tables_to_migrate', $tables_to_migrate, $this->profile, $this->migration );
}
/**
* Returns a WP-CLI progress bar instance
*
* @param array $tables
* @param int $stage
*
* @return \cli\progress\Bar
*/
function get_progress_bar( $tables, $stage ) {
$progress_label = __( 'Exporting tables', 'wp-migrate-db-cli' );
if ( 'find_replace' === $this->profile['action'] ) {
$progress_label = __( 'Running find & replace', 'wp-migrate-db-cli' );
}
$progress_label = apply_filters( 'wpmdb_cli_progress_label', $progress_label, $stage, $tables );
$progress_label = str_pad( $progress_label, 20, ' ' );
$count = $this->get_total_rows_from_table_list( $tables, $stage );
return new \cli\progress\Bar( $progress_label, $count );
}
/**
* Returns total rows from list of tables
*
* @param array $tables
* @param int $stage
*
* @return Int
*/
function get_total_rows_from_table_list( $tables, $stage ) {
static $cached_results = array();
if ( isset( $cached_results[ $stage ] ) ) {
return $cached_results[ $stage ];
}
$table_rows = $this->get_row_counts_from_table_list( $tables, $stage );
$cached_results[ $stage ] = array_sum( array_intersect_key( $table_rows, array_flip( $tables ) ) );
return $cached_results[ $stage ];
}
/**
* Returns row counts from list of tables
*
* @param array $tables
* @param int $stage
*
* @return mixed
*/
function get_row_counts_from_table_list( $tables, $stage ) {
static $cached_results = array();
if ( isset( $cached_results[ $stage ] ) ) {
return $cached_results[ $stage ];
}
$local_table_rows = $this->wpmdb->get_table_row_count();
$cached_results[ $stage ] = apply_filters( 'wpmdb_cli_get_row_counts_from_table_list', $local_table_rows, $stage );
return $cached_results[ $stage ];
}
/**
* @return array|mixed|string|void|WP_Error
*/
function migrate_tables() {
$tables_to_migrate = $this->get_tables_to_migrate();
$tables = $tables_to_migrate;
$stage_iterator = 2;
$filtered_vars = apply_filters( 'wpmdb_cli_filter_before_migrate_tables', array(
'tables' => $tables,
'stage_iterator' => $stage_iterator,
) );
if ( ! is_array( $filtered_vars ) ) {
return $filtered_vars;
} else {
extract( $filtered_vars, EXTR_OVERWRITE );
}
if ( empty( $tables ) ) {
return $this->cli_error( __( 'No tables selected for migration.', 'wp-migrate-db' ) );
}
$table_rows = $this->get_row_counts_from_table_list( $tables, $stage_iterator );
do_action( 'wpmdb_cli_before_migrate_tables', $this->profile, $this->migration );
$notify = $this->get_progress_bar( $tables, $stage_iterator );
$args = $this->post_data;
do {
$migration_progress = 0;
foreach ( $tables as $key => $table ) {
$current_row = -1;
$primary_keys = '';
$table_progress = 0;
$table_progress_last = 0;
$args['table'] = $table;
$args['last_table'] = ( $key == count( $tables ) - 1 ) ? '1' : '0';
do {
// reset the current chunk
$this->wpmdb->empty_current_chunk();
$args['current_row'] = $current_row;
$args['primary_keys'] = $primary_keys;
$args = apply_filters( 'wpmdb_cli_migrate_table_args', $args, $this->profile, $this->migration );
$response = $this->migrate_table( $args );
$migrate_table_response = $this->verify_cli_response( $response, 'migrate_table()' );
if ( is_wp_error( $migrate_table_response ) ) {
return $migrate_table_response;
}
$migrate_table_response = apply_filters( 'wpmdb_cli_migrate_table_response', $migrate_table_response, $_POST, $this->profile, $this->migration );
$current_row = $migrate_table_response['current_row'];
$primary_keys = $migrate_table_response['primary_keys'];
$last_migration_progress = $migration_progress;
if ( -1 == $current_row ) {
$migration_progress -= $table_progress;
$migration_progress += $table_rows[ $table ];
} else {
if ( 0 === $table_progress_last ) {
$table_progress_last = $current_row;
$table_progress = $table_progress_last;
$migration_progress += $table_progress_last;
} else {
$iteration_progress = $current_row - $table_progress_last;
$table_progress_last = $current_row;
$table_progress += $iteration_progress;
$migration_progress += $iteration_progress;
}
}
$increment = $migration_progress - $last_migration_progress;
$notify->tick( $increment );
} while ( -1 != $current_row );
}
$notify->finish();
++$stage_iterator;
$args['stage'] = 'migrate';
$tables = $tables_to_migrate;
$table_rows = $this->get_row_counts_from_table_list( $tables, $stage_iterator );
if ( $stage_iterator < 3 ) {
$notify = $this->get_progress_bar( $tables, $stage_iterator );
}
} while ( $stage_iterator < 3 );
$this->post_data = $args;
return $tables;
}
/**
* Finalize migration
*
* @return bool|WP_Error
*/
function finalize_migration() {
do_action( 'wpmdb_cli_before_finalize_migration', $this->profile, $this->migration );
WP_CLI::log( __( 'Cleaning up...', 'wp-migrate-db-cli' ) );
$finalize = apply_filters( 'wpmdb_cli_finalize_migration', true, $this->profile, $this->migration );
if ( is_wp_error( $finalize ) ) {
return $finalize;
}
$this->post_data = apply_filters( 'wpmdb_cli_finalize_migration_args', $this->post_data, $this->profile, $this->migration );
if ( 'savefile' === $this->post_data['intent'] ) {
return $this->finalize_export();
}
$response = null;
$response = apply_filters( 'wpmdb_cli_finalize_migration_response', $response );
if ( ! empty( $response ) && '1' !== $response ) {
return $this->cli_error( $response );
}
do_action( 'wpmdb_cli_after_finalize_migration', $this->profile, $this->migration );
return true;
}
/**
* Stub for ajax_initiate_migration()
*
* @param array|bool $args
*
* @return string
*/
function initiate_migration( $args = false ) {
$_POST = $args;
$response = $this->wpmdb->ajax_initiate_migration();
return $response;
}
/**
* stub for ajax_migrate_table()
*
* @param array|bool $args
*
* @return string
*/
function migrate_table( $args = false ) {
$_POST = $args;
$response = $this->wpmdb->ajax_migrate_table();
return $response;
}
/**
* Stub for ajax_finalize_migration()
* hooks on: wpmdb_cli_finalize_migration_response
*
* @param string $response
*
* @return string
*/
function finalize_ajax( $response ) {
// don't send redundant POST variables
$args = $this->filter_post_elements( $this->post_data, array( 'action', 'migration_state_id', 'prefix', 'tables' ) );
$_POST = $args;
$response = $this->wpmdb->ajax_finalize_migration();
return trim( $response );
}
/**
* Finalize Export by moving file to specified destination
*
* @return string|error
*/
function finalize_export() {
$state_data = $this->wpmdb->state_data;
$temp_file = $state_data['dump_path'];
if ( 'ORIGIN' === $state_data['export_dest'] ) {
$response = $temp_file;
} else {
$dest_file = $state_data['export_dest'];
if ( file_exists( $temp_file ) && rename( $temp_file, $dest_file ) ) {
$response = $dest_file;
} else {
$response = $this->cli_error( __( 'Unable to move exported file.', 'wp-migrate-db' ) );
}
}
return $response;
}
/**
* Returns array of CLI options that are unknown to plugin and addons.
*
* @param array $assoc_args
*
* @return array
*/
public function get_unknown_args( $assoc_args = array() ) {
$unknown_args = array();
if ( empty( $assoc_args ) ) {
return $unknown_args;
}
$known_args = array(
'action',
'export_dest',
'find',
'replace',
'exclude-spam',
'gzip-file',
'exclude-post-revisions',
'skip-replace-guids',
'include-transients',
);
$known_args = apply_filters( 'wpmdb_cli_filter_get_extra_args', $known_args );
$unknown_args = array_diff( array_keys( $assoc_args ), $known_args );
return $unknown_args;
}
/**
* Get profile data from CLI args.
*
* @param array $args
* @param array $assoc_args
*
* @return array|WP_Error
*/
public function get_profile_data_from_args( $args, $assoc_args ) {
//load correct cli class
if ( function_exists( 'wp_migrate_db_pro_cli_addon' ) ) {
$wpmdb_cli = wp_migrate_db_pro_cli_addon();
} elseif ( function_exists( 'wpmdb_pro_cli' ) ) {
$wpmdb_cli = wpmdb_pro_cli();
} else {
$wpmdb_cli = wpmdb_cli();
}
$unknown_args = $this->get_unknown_args( $assoc_args );
if ( ! empty( $unknown_args ) ) {
$message = __( 'Parameter errors: ', 'wp-migrate-db-cli' );
foreach ( $unknown_args as $unknown_arg ) {
$message .= "\n " . sprintf( __( 'unknown %s parameter', 'wp-migrate-db-cli' ), '--' . $unknown_arg );
}
if ( is_a( $wpmdb_cli, 'WPMDBPro_CLI' ) ) {
$message .= "\n" . __( 'Please make sure that you have activated the appropriate addons for WP Migrate DB Pro.', 'wp-migrate-db-cli' );
}
return $wpmdb_cli->cli_error( $message );
}
if ( empty( $assoc_args['action'] ) ) {
return $wpmdb_cli->cli_error( __( 'Missing action parameter', 'wp-migrate-db-cli' ) );
}
if ( 'savefile' === $assoc_args['action'] && ! empty( $assoc_args['export_dest'] ) ) {
$export_dest = $assoc_args['export_dest'];
}
$action = $assoc_args['action'];
// --find= and --replace=
$replace_old = array();
$replace_new = array();
if ( ! empty( $assoc_args['find'] ) ) {
$replace_old = str_getcsv( $assoc_args['find'] );
} else {
if ( 'find_replace' === $assoc_args['action'] ) {
return $wpmdb_cli->cli_error( __( 'Missing find and replace values.', 'wp-migrate-db-cli' ) );
}
}
if ( ! empty( $assoc_args['replace'] ) ) {
$replace_new = str_getcsv( $assoc_args['replace'] );
}
if ( count( $replace_old ) !== count( $replace_new ) ) {
return $wpmdb_cli->cli_error( sprintf( __( '%1$s and %2$s must contain the same number of values', 'wp-migrate-db-cli' ), '--find', '--replace' ) );
}
array_unshift( $replace_old, '' );
array_unshift( $replace_new, '' );
// --exclude-spam
$exclude_spam = intval( isset( $assoc_args['exclude-spam'] ) );
// --gzip-file
$gzip_file = intval( isset( $assoc_args['gzip-file'] ) );
$select_post_types = array();
// --exclude-post-revisions
if ( ! empty( $assoc_args['exclude-post-revisions'] ) ) {
$select_post_types[] = 'revision';
}
$exclude_post_types = count( $select_post_types ) > 0 ? 1 : 0;
// --skip-replace-guids
$replace_guids = 1;
if ( isset( $assoc_args['skip-replace-guids'] ) ) {
$replace_guids = 0;
}
$select_tables = array();
$table_migrate_option = 'migrate_only_with_prefix';
// --include-transients.
$exclude_transients = intval( ! isset( $assoc_args['include-transients'] ) );
//cleanup filename for exports
if ( ! empty( $export_dest ) ) {
if ( $gzip_file ) {
if ( 'gz' !== pathinfo( $export_dest, PATHINFO_EXTENSION ) ) {
if ( 'sql' === pathinfo( $export_dest, PATHINFO_EXTENSION ) ) {
$export_dest .= '.gz';
} else {
$export_dest .= '.sql.gz';
}
}
} elseif ( 'sql' !== pathinfo( $export_dest, PATHINFO_EXTENSION ) ) {
$export_dest = preg_replace( '/(\.sql)?(\.gz)?$/i', '', $export_dest ) . '.sql';
}
// ensure export destination is writable
if ( ! @touch( $export_dest ) ) {
return $wpmdb_cli->cli_error( sprintf( __( 'Cannot write to file "%1$s". Please ensure that the specified directory exists and is writable.', 'wp-migrate-db-cli' ), $export_dest ) );
}
}
$profile = compact( 'action', 'replace_old', 'table_migrate_option', 'replace_new', 'select_tables', 'exclude_post_types', 'select_post_types', 'replace_guids', 'exclude_spam', 'gzip_file', 'exclude_transients', 'export_dest' );
$profile = apply_filters( 'wpmdb_cli_filter_get_profile_data_from_args', $profile, $args, $assoc_args );
return $profile;
}
}