guard(); $runner = new Job_Runner(); $queued = $runner->queue_backup( array( 'components' => array( 'database' => true, 'plugins' => true, 'themes' => true, 'uploads' => true, ), ) ); if ( empty( $queued['ok'] ) ) { wp_send_json_error( array( 'message' => (string) ( $queued['error'] ?? __( 'Unable to start backup.', '007aj-backup-restore-verifier' ) ), ), 400 ); } $job_id = (int) $queued['job_id']; $tick = $runner->tick( $job_id ); wp_send_json_success( array( 'job_id' => $job_id, 'status' => $tick['status'] ?? 'running', 'phase' => $tick['phase'] ?? 'init', 'progress_pct' => (int) ( $tick['progress_pct'] ?? 0 ), 'error' => $tick['error'] ?? null, 'result' => $tick['result'] ?? array(), ) ); } /** * Queue a restore from a local artifact and run the first tick. */ public function restore_now(): void { $this->guard(); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified in guard(). $artifact_id = isset( $_POST['artifact_id'] ) ? absint( wp_unslash( $_POST['artifact_id'] ) ) : 0; if ( $artifact_id <= 0 ) { wp_send_json_error( array( 'message' => __( 'Missing artifact_id.', '007aj-backup-restore-verifier' ) ), 400 ); } $components = array( 'database' => false, 'plugins' => false, 'themes' => false, 'uploads' => false, ); // phpcs:disable WordPress.Security.NonceVerification.Missing -- verified in guard(). $raw = isset( $_POST['components'] ) && is_array( $_POST['components'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['components'] ) ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- each value is sanitized by array_map. : null; // phpcs:enable WordPress.Security.NonceVerification.Missing if ( is_array( $raw ) ) { foreach ( array( 'database', 'plugins', 'themes', 'uploads' ) as $key ) { if ( ! empty( $raw[ $key ] ) && '0' !== (string) $raw[ $key ] ) { $components[ $key ] = true; } } } else { // Default: restore all components when none specified. $components = array( 'database' => true, 'plugins' => true, 'themes' => true, 'uploads' => true, ); } if ( ! in_array( true, $components, true ) ) { wp_send_json_error( array( 'message' => __( 'Select at least one component to restore.', '007aj-backup-restore-verifier' ) ), 400 ); } $runner = new Job_Runner(); $queued = $runner->queue_restore( array( 'artifact_id' => $artifact_id, 'components' => $components, ) ); if ( empty( $queued['ok'] ) ) { wp_send_json_error( array( 'message' => (string) ( $queued['error'] ?? __( 'Unable to start restore.', '007aj-backup-restore-verifier' ) ), ), 400 ); } $job_id = (int) $queued['job_id']; $tick = $runner->tick( $job_id ); wp_send_json_success( array( 'job_id' => $job_id, 'status' => $tick['status'] ?? 'running', 'phase' => $tick['phase'] ?? 'validate', 'progress_pct' => (int) ( $tick['progress_pct'] ?? 0 ), 'error' => $tick['error'] ?? null, 'result' => $tick['result'] ?? array(), ) ); } /** * Advance a job one tick. */ public function job_tick(): void { $this->guard(); $job_id = isset( $_POST['job_id'] ) ? absint( wp_unslash( $_POST['job_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified in guard(). if ( $job_id <= 0 ) { wp_send_json_error( array( 'message' => __( 'Missing job_id.', '007aj-backup-restore-verifier' ) ), 400 ); } $runner = new Job_Runner(); $tick = $runner->tick( $job_id ); if ( 'locked' === ( $tick['status'] ?? '' ) ) { // Another request is advancing this job right now (second tab, reloaded page): report its progress and poll again. $job = ( new Job_Repository() )->get( $job_id ); if ( is_array( $job ) ) { $tick = array( 'ok' => true, 'status' => (string) $job['status'], 'phase' => (string) $job['phase'], 'progress_pct' => (int) $job['progress_pct'], 'error' => $job['error_message'] ?? null, 'result' => $job['result'] ?? array(), ); } } wp_send_json_success( array( 'job_id' => $job_id, 'status' => $tick['status'] ?? 'running', 'phase' => $tick['phase'] ?? '', 'progress_pct' => (int) ( $tick['progress_pct'] ?? 0 ), 'error' => $tick['error'] ?? null, 'result' => $tick['result'] ?? array(), 'ok' => ! empty( $tick['ok'] ), ) ); } /** * Job status + recent logs. */ public function job_status(): void { $this->guard(); $job_id = isset( $_REQUEST['job_id'] ) ? absint( wp_unslash( $_REQUEST['job_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- verified in guard(). if ( $job_id <= 0 ) { wp_send_json_error( array( 'message' => __( 'Missing job_id.', '007aj-backup-restore-verifier' ) ), 400 ); } $repo = new Job_Repository(); $job = $repo->get( $job_id ); if ( null === $job ) { wp_send_json_error( array( 'message' => __( 'Job not found.', '007aj-backup-restore-verifier' ) ), 404 ); } wp_send_json_success( array( 'job_id' => $job_id, 'status' => $job['status'], 'phase' => $job['phase'], 'progress_pct' => (int) $job['progress_pct'], 'error' => $job['error_message'] ?? null, 'result' => $job['result'] ?? array(), 'logs' => Logger::recent( $job_id, 30 ), 'artifacts' => $repo->artifacts_for_job( $job_id ), ) ); } /** * Capability + nonce gate. */ private function guard(): void { if ( ! check_ajax_referer( 'brverify_admin', 'nonce', false ) ) { wp_send_json_error( array( 'message' => __( 'Invalid nonce.', '007aj-backup-restore-verifier' ) ), 403 ); } if ( ! current_user_can( 'manage_brverify' ) ) { wp_send_json_error( array( 'message' => __( 'Forbidden.', '007aj-backup-restore-verifier' ) ), 403 ); } } }