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
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user