d36bb954d1
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
84 lines
2.3 KiB
PHP
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;
|
|
}
|
|
}
|