'2meet-data-optimizer', 'wpdo_export' => $type, 'format' => $format, ); if ( 'health' === $type ) { $args['days'] = $days; } return wp_nonce_url( add_query_arg( $args, admin_url( 'tools.php' ) ), self::NONCE ); } // ─── handlers ────────────────────────────────────────────────────── /** * Send health export. * * @param string $format csv|json. * @param int $days Number of days to include. * @return void */ private static function send_health( string $format, int $days ): void { global $wpdb; $audit = $wpdb->prefix . 'wpdo_audit'; $exists = (int) $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB 'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s', $audit ) ); $rows = array(); if ( 1 === $exists ) { $raw = (array) $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- {$audit} is a trusted table name via $wpdb->prefix "SELECT ts, op, value_after FROM `{$audit}` WHERE op = 'health_check_daily' AND ts >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY) ORDER BY ts DESC", $days ), ARRAY_A ); foreach ( $raw as $r ) { $ctx = isset( $r['value_after'] ) ? json_decode( (string) $r['value_after'], true ) : array(); if ( ! is_array( $ctx ) ) { $ctx = array(); } $rows[] = array( 'ts' => (string) $r['ts'], 'critical' => (int) ( $ctx['critical'] ?? 0 ), 'recommended' => (int) ( $ctx['recommended'] ?? 0 ), 'duration_ms' => (int) ( $ctx['duration_ms'] ?? 0 ), 'autoload_kb' => (int) ( $ctx['autoload_kb'] ?? 0 ), 'conflicts' => (int) ( $ctx['conflicts'] ?? 0 ), 'module_suggestions_count' => (int) ( $ctx['module_suggestions_count'] ?? 0 ), ); } } $headers = array( 'ts', 'critical', 'recommended', 'duration_ms', 'autoload_kb', 'conflicts', 'module_suggestions_count' ); self::respond( $format, 'health', $headers, $rows ); } /** * Send snapshots export. * * @param string $format csv|json. * @return void */ private static function send_snapshots( string $format ): void { global $wpdb; $snap = $wpdb->prefix . 'wpdo_snapshots'; $exists = (int) $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB 'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s', $snap ) ); $rows = array(); if ( 1 === $exists ) { $rows = (array) $wpdb->get_results( "SELECT snapshot_id, trigger_type, size_bytes, row_count, storage, file_path, file_sha256, notes, created_at, expires_at FROM `{$snap}` ORDER BY created_at DESC", // phpcs:ignore WordPress.DB ARRAY_A ); } $headers = array( 'snapshot_id', 'trigger_type', 'size_bytes', 'row_count', 'storage', 'file_path', 'file_sha256', 'notes', 'created_at', 'expires_at' ); self::respond( $format, 'snapshots', $headers, $rows ); } /** * Send monthly summary export. * * @param string $format csv|json. * @return void */ private static function send_monthly( string $format ): void { $latest = (array) get_option( 'wpdo_monthly_summary_latest', array() ); $history = (array) get_option( 'wpdo_monthly_summary_history', array() ); $all = array(); if ( ! empty( $latest ) ) { $all[] = $latest; } foreach ( $history as $h ) { if ( is_array( $h ) ) { $all[] = $h; } } if ( 'json' === $format ) { self::respond_json( 'monthly', $all ); return; } // Flatten for CSV — top-level metric only. $rows = array(); foreach ( $all as $row ) { $health = (array) ( $row['health'] ?? array() ); $snap = (array) ( $row['snapshots'] ?? array() ); $rows[] = array( 'period_start' => (string) ( $row['period_start'] ?? '' ), 'period_end' => (string) ( $row['period_end'] ?? '' ), 'generated_at' => (string) ( $row['generated_at'] ?? '' ), 'health_total' => (int) ( $health['total'] ?? 0 ), 'health_success' => (int) ( $health['success'] ?? 0 ), 'health_critical' => (int) ( $health['critical'] ?? 0 ), 'snapshots_total' => (int) ( $snap['total'] ?? 0 ), 'snapshots_bytes' => (int) ( $snap['total_bytes'] ?? 0 ), 'autoload_size' => (int) ( $row['autoload_size'] ?? 0 ), ); } $headers = array( 'period_start', 'period_end', 'generated_at', 'health_total', 'health_success', 'health_critical', 'snapshots_total', 'snapshots_bytes', 'autoload_size' ); self::respond_csv( 'monthly', $headers, $rows ); } // ─── output helpers ──────────────────────────────────────────────── /** * Dispatch to csv or json responder. * * @param string $format csv|json. * @param string $type Export type slug. * @param array $headers Column headers. * @param array $rows Data rows. * @return void */ private static function respond( string $format, string $type, array $headers, array $rows ): void { if ( 'json' === $format ) { self::respond_json( $type, $rows ); return; } self::respond_csv( $type, $headers, $rows ); } /** * Send CSV response. * * @param string $type Export type slug. * @param array $headers Column headers. * @param array $rows Data rows. * @return void */ private static function respond_csv( string $type, array $headers, array $rows ): void { $body = TMDO_CSV_Writer::build( $headers, $rows ); self::send_attachment( $type, 'csv', 'text/csv; charset=UTF-8', $body ); } /** * Send JSON response. * * @param string $type Export type slug. * @param array $rows Data rows. * @return void */ private static function respond_json( string $type, array $rows ): void { $body = wp_json_encode( $rows, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); self::send_attachment( $type, 'json', 'application/json; charset=UTF-8', (string) $body ); } /** * Send file attachment response. * * @param string $type Export type slug. * @param string $ext File extension. * @param string $mime MIME type. * @param string $body File body content. * @return void */ private static function send_attachment( string $type, string $ext, string $mime, string $body ): void { $site_slug = sanitize_title( (string) get_option( 'blogname', 'site' ) ) ?: 'site'; $filename = sprintf( 'wpdo-%s-%s-%s.%s', $type, $site_slug, gmdate( 'Ymd' ), $ext ); nocache_headers(); header( 'Content-Type: ' . $mime ); header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); header( 'Content-Length: ' . strlen( $body ) ); echo $body; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped exit; } }