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 : ''; } }