0, 'skipped' => 0, 'errors' => array( __( 'Empty CSV.', 'rafgrid' ) ) );
}
$header = str_getcsv( array_shift( $lines ) );
$keys = array_map( array( __CLASS__, 'header_to_key' ), $header );
$cols = Columns::get_columns( $post_type );
$updated = 0;
$skipped = 0;
$errors = array();
foreach ( $lines as $i => $line ) {
$cells = str_getcsv( $line );
$row = array();
foreach ( $keys as $idx => $k ) {
$row[ $k ] = isset( $cells[ $idx ] ) ? $cells[ $idx ] : '';
}
if ( empty( $row['ID'] ) || ! get_post( absint( $row['ID'] ) ) ) {
++$skipped;
continue;
}
$id = absint( $row['ID'] );
foreach ( $row as $k => $v ) {
if ( 'ID' === $k || '_edit_link' === $k || '_view_link' === $k ) {
continue;
}
if ( ! isset( $cols[ $k ] ) && ! str_starts_with( $k, 'meta:' ) && ! str_starts_with( $k, 'tax:' ) ) {
continue;
}
if ( isset( $cols[ $k ] ) && empty( $cols[ $k ]['editable'] ) ) {
continue;
}
$res = Saving::save_cell( $id, $k, $v );
if ( is_wp_error( $res ) ) {
/* translators: 1: CSV row number, 2: column key, 3: error message */
$errors[] = sprintf( __( 'Row %1$d, column %2$s: %3$s', 'rafgrid' ), $i + 2, $k, $res->get_error_message() );
}
}
++$updated;
}
return array(
'updated' => $updated,
'skipped' => $skipped,
'errors' => array_slice( $errors, 0, 20 ),
);
}
/**
* Minimal XLSX export (inline strings, no external library).
* Returns binary string or empty string when ZipArchive is missing.
*
* @param string $post_type Post type.
* @param array $params Query params.
* @return string
*/
public static function to_xlsx( $post_type, $params ) {
if ( ! class_exists( 'ZipArchive' ) ) {
return '';
}
$params['page'] = 1;
$params['per_page'] = min( 2000, max( 1, absint( isset( $params['per_page'] ) ? $params['per_page'] : 500 ) ) );
$data = Query::fetch( $post_type, $params );
$cols = Columns::get_columns( $post_type );
$wanted = isset( $params['columns'] ) && is_array( $params['columns'] ) ? $params['columns'] : array_keys( $cols );
$wanted = array_values( array_intersect( $wanted, array_keys( $cols ) ) );
if ( empty( $wanted ) ) {
$wanted = array( 'ID', 'post_title' );
}
$rows_xml = '';
$r = 1;
$header = array();
foreach ( $wanted as $ck ) {
$header[] = isset( $cols[ $ck ]['label'] ) ? $cols[ $ck ]['label'] . ' [' . $ck . ']' : $ck;
}
$rows_xml .= self::xlsx_row( $header, $r++ );
foreach ( $data['rows'] as $row ) {
$line = array();
foreach ( $wanted as $ck ) {
$v = isset( $row[ $ck ] ) ? $row[ $ck ] : '';
if ( is_array( $v ) && isset( $v['id'] ) ) {
$v = $v['id'];
} elseif ( is_array( $v ) ) {
$v = implode( ',', $v );
}
$line[] = (string) $v;
}
$rows_xml .= self::xlsx_row( $line, $r++ );
}
$sheet = '' .
'' . $rows_xml . '';
$tmp = wp_tempnam( 'rafgrid-xlsx' );
$zip = new \ZipArchive();
if ( true !== $zip->open( $tmp, \ZipArchive::OVERWRITE ) ) {
return '';
}
$zip->addFromString( '[Content_Types].xml', '' );
$zip->addFromString( '_rels/.rels', '' );
$zip->addFromString( 'xl/workbook.xml', '' );
$zip->addFromString( 'xl/_rels/workbook.xml.rels', '' );
$zip->addFromString( 'xl/worksheets/sheet1.xml', $sheet );
$zip->close();
$bin = file_get_contents( $tmp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_get_contents
wp_delete_file( $tmp );
return $bin ? $bin : '';
}
private static function xlsx_row( $cells, $r ) {
$out = '';
$c = 1;
foreach ( $cells as $v ) {
$ref = self::xlsx_col( $c++ ) . $r;
$out .= '' . esc_xml( (string) $v ) . '';
}
return $out . '
';
}
private static function xlsx_col( $n ) {
$s = '';
while ( $n > 0 ) {
$m = ( $n - 1 ) % 26;
$s = chr( 65 + $m ) . $s;
$n = intdiv( $n - 1, 26 );
}
return $s;
}
/**
* Convert XLSX binary (first sheet) to CSV text for the CSV importer.
*
* @param string $bin XLSX binary.
* @return string CSV text (empty on failure).
*/
public static function xlsx_to_csv( $bin ) {
if ( ! class_exists( 'ZipArchive' ) ) {
return '';
}
$tmp = wp_tempnam( 'rafgrid-xlsx-in' );
file_put_contents( $tmp, $bin ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
$zip = new \ZipArchive();
if ( true !== $zip->open( $tmp ) ) {
wp_delete_file( $tmp );
return '';
}
$shared = array();
$ss = $zip->getFromName( 'xl/sharedStrings.xml' );
if ( false !== $ss ) {
$xml = simplexml_load_string( $ss );
if ( $xml ) {
foreach ( $xml->si as $si ) {
$shared[] = (string) $si->t;
}
}
}
$sheet = $zip->getFromName( 'xl/worksheets/sheet1.xml' );
$zip->close();
wp_delete_file( $tmp );
if ( false === $sheet ) {
return '';
}
$xml = simplexml_load_string( $sheet );
if ( ! $xml || ! isset( $xml->sheetData ) ) {
return '';
}
$fh = fopen( 'php://temp', 'r+' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- In-memory php://temp stream for CSV conversion; no filesystem path is involved.
foreach ( $xml->sheetData->row as $row ) {
$cells = array();
foreach ( $row->c as $c ) {
$type = (string) ( $c['t'] ?? '' );
if ( 'inlineStr' === $type ) {
$cells[] = (string) $c->is->t;
} elseif ( 's' === $type ) {
$cells[] = $shared[ (int) $c->v ] ?? '';
} else {
$cells[] = (string) $c->v;
}
}
fputcsv( $fh, $cells );
}
rewind( $fh );
$csv = stream_get_contents( $fh );
fclose( $fh ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing an in-memory php://temp stream (see above).
return $csv ? $csv : '';
}
}