admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( self::NONCE ), 'i18n' => array( 'working' => __( 'Working...', 'shiftdb' ), 'confirmReplace' => __( 'This will modify your database. A recovery snapshot will be created. Continue?', 'shiftdb' ), 'confirmRestore' => __( 'Restore this recovery snapshot? Current values in affected rows will be overwritten.', 'shiftdb' ), 'confirmDelete' => __( 'Delete this recovery snapshot permanently?', 'shiftdb' ), ), ) ); } public function render_page(): void { $this->authorize(); $tables = ShiftDB_Database::get_tables(); $autoload = ShiftDB_Database::autoload_options( 50 ); $snapshots = ShiftDB_Backup::list_snapshots(); $history = ShiftDB_Logger::recent( 50 ); $cleanup = ShiftDB_Cleanup::counts(); $total_size = 0; $total_overhead = 0; $autoload_size = 0; foreach ( $tables as $table ) { $total_size += (int) ( $table['Data_length'] ?? 0 ) + (int) ( $table['Index_length'] ?? 0 ); $total_overhead += (int) ( $table['Data_free'] ?? 0 ); } foreach ( $autoload as $option ) { $autoload_size += (int) ( $option['bytes'] ?? 0 ); } $largest_tables = $tables; usort( $largest_tables, static function ( array $a, array $b ): int { $a_size = (int) ( $a['Data_length'] ?? 0 ) + (int) ( $a['Index_length'] ?? 0 ); $b_size = (int) ( $b['Data_length'] ?? 0 ) + (int) ( $b['Index_length'] ?? 0 ); return $b_size <=> $a_size; } ); $largest_tables = array_slice( $largest_tables, 0, 5 ); $health_score = 100; if ( $total_size > 0 && ( $total_overhead / $total_size ) > 0.10 ) { $health_score -= 15; } if ( $autoload_size > 1048576 ) { $health_score -= 15; } if ( (int) ( $cleanup['revisions'] ?? 0 ) > 100 ) { $health_score -= 10; } if ( (int) ( $cleanup['transients'] ?? 0 ) > 100 ) { $health_score -= 10; } $health_score = max( 0, $health_score ); ?>

stat_card( __( 'Database Size', 'shiftdb' ), size_format( $total_size, 2 ) ); ?> stat_card( __( 'Tables', 'shiftdb' ), number_format_i18n( count( $tables ) ) ); ?> stat_card( __( 'Recovery Points', 'shiftdb' ), number_format_i18n( count( $snapshots ) ) ); ?> stat_card( __( 'Autoload Entries', 'shiftdb' ), number_format_i18n( count( $autoload ) ) . '+' ); ?>

= 80 ? __( 'Healthy', 'shiftdb' ) : __( 'Needs attention', 'shiftdb' ) ); ?>
/100

$table ) : $table_size = (int) ( $table['Data_length'] ?? 0 ) + (int) ( $table['Index_length'] ?? 0 ); $percent = $total_size > 0 ? max( 2, min( 100, ( $table_size / $total_size ) * 100 ) ) : 2; ?>

render_table_selector( $tables, 'shiftdb-sr-tables' ); ?>

cleanup_item( 'revisions', __( 'Post Revisions', 'shiftdb' ), (int) $cleanup['revisions'] ); ?> cleanup_item( 'spam', __( 'Spam Comments', 'shiftdb' ), (int) $cleanup['spam'] ); ?> cleanup_item( 'trash', __( 'Trashed Comments', 'shiftdb' ), (int) $cleanup['ttrash'] ); ?> cleanup_item( 'expired_transients', __( 'Expired Transients', 'shiftdb' ), (int) $cleanup['transients'] ); ?>

authorize(); } private function selected_tables(): array { check_ajax_referer( self::NONCE, 'nonce' ); $tables = isset( $_POST['tables'] ) && is_array( $_POST['tables'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['tables'] ) ) : array(); return ShiftDB_Database::validate_tables( $tables ); } public function ajax_dry_run(): void { $this->ajax_authorize(); check_ajax_referer( self::NONCE, 'nonce' ); $search = isset( $_POST['search'] ) ? sanitize_textarea_field( wp_unslash( $_POST['search'] ) ) : ''; $replace = isset( $_POST['replace'] ) ? sanitize_textarea_field( wp_unslash( $_POST['replace'] ) ) : ''; $case = ! empty( $_POST['case_sensitive'] ); $result = ShiftDB_Database::dry_run( $search, $replace, $this->selected_tables(), $case ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message(), 'code' => $result->get_error_code() ), 400 ); } ShiftDB_Logger::add( 'dry_run', 'success', array( 'rows' => $result['total_rows'], 'replacements' => $result['total_replacements'] ) ); wp_send_json_success( $result ); } public function ajax_replace(): void { $this->ajax_authorize(); check_ajax_referer( self::NONCE, 'nonce' ); $search = isset( $_POST['search'] ) ? sanitize_textarea_field( wp_unslash( $_POST['search'] ) ) : ''; $replace = isset( $_POST['replace'] ) ? sanitize_textarea_field( wp_unslash( $_POST['replace'] ) ) : ''; $case = ! empty( $_POST['case_sensitive'] ); $protect = ! empty( $_POST['protect_guids'] ); $result = ShiftDB_Database::replace( $search, $replace, $this->selected_tables(), $case, $protect ); if ( is_wp_error( $result ) ) { ShiftDB_Logger::add( 'replace', 'error', array( 'code' => $result->get_error_code() ) ); wp_send_json_error( array( 'message' => $result->get_error_message(), 'code' => $result->get_error_code() ), 400 ); } ShiftDB_Logger::add( 'replace', 'success', array( 'rows' => $result['rows'], 'replacements' => $result['replacements'], 'snapshot' => $result['snapshot'] ) ); wp_send_json_success( $result ); } public function ajax_restore(): void { $this->ajax_authorize(); check_ajax_referer( self::NONCE, 'nonce' ); $file = isset( $_POST['file'] ) ? sanitize_file_name( wp_unslash( $_POST['file'] ) ) : ''; $result = ShiftDB_Database::restore_snapshot( $file ); if ( is_wp_error( $result ) ) { ShiftDB_Logger::add( 'restore', 'error', array( 'code' => $result->get_error_code() ) ); wp_send_json_error( array( 'message' => $result->get_error_message(), 'code' => $result->get_error_code() ), 400 ); } ShiftDB_Logger::add( 'restore', 'success', array( 'snapshot' => $file, 'rows' => $result['rows'] ) ); wp_send_json_success( $result ); } public function ajax_delete_snapshot(): void { $this->ajax_authorize(); check_ajax_referer( self::NONCE, 'nonce' ); $file = isset( $_POST['file'] ) ? sanitize_file_name( wp_unslash( $_POST['file'] ) ) : ''; if ( ! ShiftDB_Backup::delete_snapshot( $file ) ) { wp_send_json_error( array( 'message' => __( 'Recovery snapshot could not be deleted.', 'shiftdb' ) ), 400 ); } ShiftDB_Logger::add( 'delete_snapshot', 'success', array( 'snapshot' => $file ) ); wp_send_json_success( array( 'message' => __( 'Recovery snapshot deleted.', 'shiftdb' ) ) ); } public function ajax_cleanup(): void { $this->ajax_authorize(); check_ajax_referer( self::NONCE, 'nonce' ); $types = isset( $_POST['types'] ) && is_array( $_POST['types'] ) ? array_map( 'sanitize_key', wp_unslash( $_POST['types'] ) ) : array(); $result = ShiftDB_Cleanup::cleanup( $types ); ShiftDB_Logger::add( 'cleanup', 'success', $result ); wp_send_json_success( $result ); } public function ajax_optimize(): void { $this->ajax_authorize(); check_ajax_referer( self::NONCE, 'nonce' ); $result = ShiftDB_Cleanup::optimize_tables( $this->selected_tables() ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message(), 'code' => $result->get_error_code() ), 400 ); } ShiftDB_Logger::add( 'optimize', 'success', array( 'tables' => array_keys( $result ) ) ); wp_send_json_success( $result ); } public function handle_export(): void { $this->authorize(); check_admin_referer( 'shiftdb_export', 'shiftdb_export_nonce' ); $tables = isset( $_POST['tables'] ) && is_array( $_POST['tables'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['tables'] ) ) : array(); $sql = ShiftDB_Database::export_sql( $tables ); if ( is_wp_error( $sql ) ) { wp_die( esc_html( $sql->get_error_message() ) ); } ShiftDB_Logger::add( 'export', 'success', array( 'tables' => count( ShiftDB_Database::validate_tables( $tables ) ) ) ); nocache_headers(); header( 'Content-Type: application/sql; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="shiftdb-' . gmdate( 'Ymd-His' ) . '.sql"' ); echo $sql; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- SQL download response, not HTML. exit; } }