$page, 'per_page' => self::BATCH_SIZE ] ); foreach ( $result['items'] as $entry ) { $row = [ $entry['created_at'] ]; foreach ( $csv_fields as $field ) { $row[] = Entries_Page::display_value( $entry['values'][ $field['id'] ] ?? '' ); } fputcsv( $out, array_map( [ __CLASS__, 'csv_safe_cell' ], $row ) ); } $page++; } while ( count( $result['items'] ) === self::BATCH_SIZE ); fclose( $out ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose exit; } /** One object per entry with column-labeled keys (not the field-id-keyed raw shape used internally), so the file reads the same way the CSV columns do. Fetched in batches like stream_csv() above, though (unlike CSV) the transformed rows still accumulate in memory until wp_json_encode() runs at the end — a real JSON array needs its full contents before it can be encoded. */ public static function stream_json( array $form, $form_id, array $csv_fields ) { $rows = []; $page = 1; do { $result = Entry::query_for_form( $form_id, [ 'page' => $page, 'per_page' => self::BATCH_SIZE ] ); foreach ( $result['items'] as $entry ) { $row = [ 'date' => $entry['created_at'], 'status' => $entry['status'] ]; foreach ( $csv_fields as $field ) { $row[ Entries_Page::column_label( $field ) ] = $entry['values'][ $field['id'] ] ?? ''; } $rows[] = $row; } $page++; } while ( count( $result['items'] ) === self::BATCH_SIZE ); nocache_headers(); header( 'Content-Type: application/json; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="' . sanitize_file_name( $form['title'] ) . '-entries.json"' ); echo wp_json_encode( $rows, JSON_PRETTY_PRINT ); exit; } /** * Neutralizes CSV/formula injection: a submitted value starting with * =, +, -, or @ (or a leading tab/CR) is interpreted as a formula by * Excel/Sheets when the exported file is opened, which is a well-known * attack vector for form plugins — a visitor's own submission shouldn't * be able to run a formula on the admin's machine later. Prefixing a * single quote is the standard OWASP-recommended mitigation; it forces * spreadsheet apps to treat the cell as plain text. */ public static function csv_safe_cell( $value ) { $value = (string) $value; return preg_match( '/^[=+\-@\t\r]/', $value ) ? "'" . $value : $value; } }