prepare_to_printing_export($post_ids, $redirect_to); case 'set_status_to_prepare_to_printing': return $this->set_status_to_prepare_to_printing($post_ids, $redirect_to); case 'set_status_to_in_printing': return $this->set_status_to_in_printing($post_ids, $redirect_to); } return $redirect_to; } /** * Atomic order: pull rows, build the CSV in memory, then flip statuses, then * stream the file. If the export query returns nothing, statuses are still * advanced (the spec requires it) but the user is told no CSV was produced. */ private function prepare_to_printing_export($post_ids, $redirect_to) { $orders_data = Studiou_DB_Manager::get_orders_for_printing($post_ids); $csv_data = !empty($orders_data) ? $this->generate_csv($orders_data) : ''; $updated = Studiou_DB_Manager::set_orders_to_prepare_printing($post_ids); $this->notify_moved_count($updated, 'to-print'); if ($csv_data === '') { UtilsLog::message( __('No exportable line items were found for the selected orders. CSV was not generated.', 'studiou-wc-ord-print-statuses'), 'warning' ); return $redirect_to; } $this->output_csv($csv_data, 'prepare_to_printing_export.csv'); // exits } private function set_status_to_prepare_to_printing($post_ids, $redirect_to) { $updated = Studiou_DB_Manager::set_orders_to_prepare_printing($post_ids); $this->notify_moved_count($updated, 'to-print'); return add_query_arg('bulk_action', 'marked_prepare_to_printing', $redirect_to); } private function set_status_to_in_printing($post_ids, $redirect_to) { $updated = Studiou_DB_Manager::set_orders_to_in_printing($post_ids); $this->notify_moved_count($updated, 'in-print'); return add_query_arg('bulk_action', 'marked_in_printing', $redirect_to); } /** * Emit a "%d orders moved to …" notice unless zero orders were actually * changed — in that case the "skipped because terminal status" warning * from Studiou_DB_Manager is more informative on its own. */ private function notify_moved_count($count, $slug) { if ($count <= 0) { return; } if ($slug === 'to-print') { $message = _n( '%d order moved to "Prepare to Printing".', '%d orders moved to "Prepare to Printing".', $count, 'studiou-wc-ord-print-statuses' ); } else { $message = _n( '%d order moved to "In Printing".', '%d orders moved to "In Printing".', $count, 'studiou-wc-ord-print-statuses' ); } UtilsLog::message(sprintf($message, $count), 'success'); } private function generate_csv($data) { $output = fopen('php://temp', 'w+'); // Explicit empty-string escape suppresses the PHP 8.4 deprecation // notice for the implicit '\\' default. fputcsv($output, array_keys((array) $data[0]), ',', '"', ''); foreach ($data as $row) { fputcsv($output, (array) $row, ',', '"', ''); } rewind($output); $csv = stream_get_contents($output); fclose($output); return $csv; } /** * Stream CSV with UTF-8 BOM so Excel on Windows reads diacritics correctly. * If output has already started (some upstream code printed something) we * cannot reliably stream a CSV download — bail with a clear error rather * than echoing CSV bytes into an HTML page. */ private function output_csv($csv_data, $filename) { if (headers_sent($file, $line)) { UtilsLog::log(sprintf( 'output_csv: headers already sent from %s:%d — aborting CSV stream.', $file, $line )); wp_die(esc_html__( 'The CSV export could not be streamed because output had already started. Check for plugin/theme code that prints before headers are sent.', 'studiou-wc-ord-print-statuses' )); } nocache_headers(); header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="' . $filename . '"'); echo "\xEF\xBB\xBF" . $csv_data; // UTF-8 BOM exit; } }