] * : Backup name shown in the list (alias: --name). * * [--name=] * : Backup name. * * [--tables=] * : Comma-separated table names (custom scope). * * [--site=] * : Multisite: back up one site. * * [--include-users] * : With --site: include that site's users. * * [--convert-prefix] * : With --site: rename tables to the main prefix for a standalone install. * * [--run] * : Run the backup to completion in this process instead of in the background. * * ## EXAMPLES * * wp dbmg create --description="Before plugin updates" --run * wp dbmg create --tables=wp_options --run * * @param array $args Positional. * @param array $assoc Flags. */ public function create( $args, $assoc ) { $name = isset( $assoc['description'] ) ? $assoc['description'] : ( isset( $assoc['name'] ) ? $assoc['name'] : '' ); $input = array( 'type' => 'full' ); if ( ! empty( $assoc['tables'] ) ) { $input = array( 'type' => 'custom', 'tables' => array_filter( array_map( 'trim', explode( ',', $assoc['tables'] ) ) ), ); } elseif ( ! empty( $assoc['site'] ) ) { $input = array( 'type' => 'subsite', 'blog_id' => (int) $assoc['site'], 'include_users' => ! empty( $assoc['include-users'] ), 'convert_prefix' => ! empty( $assoc['convert-prefix'] ), ); } if ( ! Lock::acquire( 60 ) ) { \WP_CLI::error( Errors::message( 'job_running' ) ); } try { $existing = Job::load(); if ( $existing && $existing->is_active() ) { Lock::release(); \WP_CLI::error( Errors::message( 'job_running' ) . ' Use `wp dbmg status`.' ); } $scope = Table_Catalog::normalize_scope( $input ); $tables = Table_Catalog::resolve( $scope ); $pre = Diskspace::preflight( $tables, Job::default_part_size() ); if ( 'insufficient' === $pre['status'] ) { Lock::release(); $labels = Admin_Page::preflight_labels( $pre ); \WP_CLI::error( $labels['text'] ); } $job = Job::create( $scope, $tables, $name, get_current_user_id(), 'background' ); } catch ( Backup_Exception $e ) { Lock::release(); \WP_CLI::error( Errors::message( $e->get_error_code(), $e->get_context() ) . ' (' . $e->getMessage() . ')' ); } Lock::release(); \WP_CLI::log( sprintf( 'Created backup %d "%s" (%d tables).', $job->s['id'], $job->s['name'], count( $tables ) ) ); if ( ! empty( $assoc['run'] ) ) { $this->run( array(), array() ); } else { Runner::kickoff(); \WP_CLI::log( 'Running in the background. Follow it with `wp dbmg status` or finish it here with `wp dbmg run`.' ); } } /** * Development alias: create a full backup and run it synchronously. * * @subcommand dump-sync * @param array $args Positional. * @param array $assoc Flags. */ public function dump_sync( $args, $assoc ) { $assoc['run'] = true; $this->create( $args, $assoc ); } /** * Run ticks in a loop until the active backup finishes. * * @param array $args Positional. * @param array $assoc Flags. */ public function run( $args, $assoc ) { $job = Job::load(); if ( ! $job || ! $job->is_active() ) { \WP_CLI::log( 'No backup is running.' ); return; } if ( 'stalled' === $job->s['stage'] ) { Lock::clear_stale(); if ( Lock::acquire( 60 ) ) { $job->resume(); Lock::release(); } } $id = (int) $job->s['id']; $last = -1; $busy = 0; while ( true ) { $result = Runner::run_tick( 'cli', false, false ); $job = Job::load(); if ( ! $job || (int) $job->s['id'] !== $id || ! $job->has_work() ) { break; } if ( 'busy' === $result ) { if ( ++$busy > 600 ) { \WP_CLI::error( 'Another process holds the backup lock and made no progress for 10 minutes.' ); } sleep( 1 ); continue; } $busy = 0; $snap = $job->snapshot(); if ( $snap['percent'] !== $last ) { $last = $snap['percent']; \WP_CLI::log( sprintf( '%3d%% %s', $snap['percent'], $this->label( $snap ) ) ); } } $manifest = Storage::find( $id ); if ( ! $manifest ) { \WP_CLI::error( 'The backup ended without a manifest.' ); } if ( 'ready' === $manifest['status'] ) { \WP_CLI::success( sprintf( 'Backup %d ready: %s (%s SQL, %d parts).', $id, size_format( $manifest['zip_bytes'], 1 ), size_format( $manifest['sql_bytes'], 1 ), $manifest['parts'] ) ); \WP_CLI::log( Storage::dir() . '/' . $manifest['zip_file'] ); return; } $err = $manifest['error']; \WP_CLI::error( sprintf( '%s %s [%s] %s', $err['message'], $err['fix'], $err['code'], $err['detail'] ) ); } /** * @param array $snap Snapshot. * @return string */ private function label( array $snap ) { switch ( $snap['stage'] ) { case 'dumping': return sprintf( 'Exporting %s (%d of %d tables)', $snap['current_table'], min( $snap['table_count'], $snap['table_index'] + 1 ), $snap['table_count'] ); case 'zipping': return sprintf( 'Compressing part %d of %d', $snap['parts_zipped'] + 1, max( $snap['parts_total'], $snap['parts_zipped'] + 1 ) ); case 'verifying': return 'Verifying'; default: return ucfirst( $snap['stage'] ); } } /** * Show the running backup. * * [--format=] * : table or json. * --- * default: table * --- * * @param array $args Positional. * @param array $assoc Flags. */ public function status( $args, $assoc ) { $job = Job::load(); if ( ! $job || ! $job->is_active() ) { \WP_CLI::log( 'No backup is running.' ); return; } $snap = $job->snapshot(); if ( isset( $assoc['format'] ) && 'json' === $assoc['format'] ) { \WP_CLI::line( wp_json_encode( $snap ) ); return; } \WP_CLI::log( sprintf( 'Backup %d "%s": %s, %d%% — %s', $snap['id'], $snap['name'], $snap['stage'], $snap['percent'], $this->label( $snap ) ) ); } /** * List backups. * * [--format=] * : table, json, csv. * --- * default: table * --- * * @subcommand list * @param array $args Positional. * @param array $assoc Flags. */ public function list_( $args, $assoc ) { $items = array(); foreach ( Storage::manifests() as $m ) { $items[] = array( 'id' => $m['id'], 'date' => wp_date( 'Y-m-d H:i', (int) $m['id'] ), 'name' => $m['name'], 'status' => $m['status'], 'size' => 'ready' === $m['status'] ? size_format( $m['zip_bytes'], 1 ) : '', 'file' => 'ready' === $m['status'] ? $m['zip_file'] : '', ); } \WP_CLI\Utils\format_items( isset( $assoc['format'] ) ? $assoc['format'] : 'table', $items, array( 'id', 'date', 'name', 'status', 'size', 'file' ) ); } /** * Delete backups. * * ... * : Backup IDs. * * @param array $args IDs. * @param array $assoc Flags. */ public function delete( $args, $assoc ) { $job = Job::load(); $active = ( $job && $job->is_active() ) ? (int) $job->s['id'] : 0; foreach ( $args as $id ) { if ( (int) $id === $active ) { \WP_CLI::warning( "Backup $id is running. Cancel it first with `wp dbmg cancel`." ); continue; } if ( Storage::delete( (int) $id ) ) { \WP_CLI::success( "Deleted backup $id." ); } else { \WP_CLI::warning( "Backup $id not found." ); } } } /** * Cancel the running backup. * * @param array $args Positional. * @param array $assoc Flags. */ public function cancel( $args, $assoc ) { $job = Job::load(); if ( ! $job || ! $job->is_active() ) { \WP_CLI::log( 'No backup is running.' ); return; } Options::set( Job::CANCEL_OPTION, array( 'id' => (int) $job->s['id'], 'user' => get_current_user_id(), ) ); Runner::run_tick( 'cli', true, false ); \WP_CLI::success( 'Cancel requested.' ); } }