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' ) ); ?>
$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; ?>
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;
}
}