Files
2meet-data-optimizer/includes/export/class-tmdo-csv-writer.php
T
wpdev d36bb954d1 chore: initial snapshot of 2meet-data-optimizer v0.1.0
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
2026-07-31 05:06:36 +08:00

84 lines
2.3 KiB
PHP

<?php
/**
* TMDO_CSV_Writer — Lightweight CSV writer with UTF-8 BOM (v2.5.0 M14).
*
* Excel reads UTF-8 CSV correctly only when the file starts with EF BB BF
* BOM. This writer always emits BOM, escapes embedded `"` and wraps fields
* containing `,` / newline / `"`.
*
* @package WP_Data_Optimizer
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Builder API: build_csv_string( $headers, $rows ): string.
*/
class TMDO_CSV_Writer {
/** UTF-8 BOM bytes. */
public const BOM = "\xEF\xBB\xBF";
/**
* Build a complete CSV string with BOM + header + rows.
*
* @param array $headers Header column names.
* @param array $rows Each row is an associative array keyed by header.
* @return string CSV body ready for Content-Disposition: attachment.
*/
public static function build( array $headers, array $rows ): string {
$out = self::BOM;
$out .= self::row_to_csv( $headers );
foreach ( $rows as $row ) {
$line = array();
foreach ( $headers as $h ) {
$v = $row[ $h ] ?? '';
if ( is_array( $v ) || is_object( $v ) ) {
$v = wp_json_encode( $v );
}
$line[] = (string) $v;
}
$out .= self::row_to_csv( $line );
}
return $out;
}
/**
* Format one row of fields with proper escaping.
*
* @param array $fields Field values.
* @return string CSV row including trailing CRLF.
*/
private static function row_to_csv( array $fields ): string {
$escaped = array();
foreach ( $fields as $f ) {
$s = self::sanitize_cell( (string) $f );
$needs_quote = ( str_contains( $s, ',' ) || str_contains( $s, '"' ) || str_contains( $s, "\n" ) || str_contains( $s, "\r" ) );
if ( $needs_quote ) {
$s = '"' . str_replace( '"', '""', $s ) . '"';
}
$escaped[] = $s;
}
return implode( ',', $escaped ) . "\r\n";
}
/**
* Neutralise CSV formula-injection characters (=, +, -, @, TAB, CR).
*
* Excel/Sheets treat cells starting with these as formulas. Prefixing with
* a literal single-quote forces text interpretation without altering the
* visual output for normal users.
*
* @param string $s Raw cell value.
* @return string Safe cell value.
*/
private static function sanitize_cell( string $s ): string {
if ( '' !== $s && preg_match( '/^[=+\-@\t\r]/', $s ) ) {
return "'" . $s;
}
return $s;
}
}