Header cells, or empty array. * @since 1.3.0 */ public static function read_header( $token ) { $path = self::path_for( $token ); if ( false === $path ) { return []; } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Reading a private import file line by line to bound memory. $handle = fopen( $path, 'r' ); if ( false === $handle ) { return []; } $header = fgetcsv( $handle, 0, ',', '"', '' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing the import file handle. fclose( $handle ); if ( ! is_array( $header ) ) { return []; } if ( isset( $header[0] ) ) { $header[0] = self::strip_bom( (string) $header[0] ); } return array_map( static function ( $cell ) { return (string) $cell; }, $header ); } /** * Read a range of data rows (excluding the header), 0-indexed. * * @param string $token File token. * @param int $offset Data-row offset. * @param int $limit Max rows to return. * @return array> Rows of cell values. * @since 1.3.0 */ public static function read_range( $token, $offset, $limit ) { $path = self::path_for( $token ); if ( false === $path ) { return []; } $offset = max( 0, (int) $offset ); $limit = max( 0, (int) $limit ); if ( 0 === $limit ) { return []; } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Reading a private import file line by line to bound memory. $handle = fopen( $path, 'r' ); if ( false === $handle ) { return []; } fgetcsv( $handle, 0, ',', '"', '' ); // Skip header. $row_index = 0; $rows = []; while ( true ) { $row = fgetcsv( $handle, 0, ',', '"', '' ); if ( false === $row ) { break; } if ( $row_index >= $offset && count( $rows ) < $limit ) { $rows[] = array_map( static function ( $cell ) { return (string) $cell; }, $row ); } ++$row_index; if ( count( $rows ) >= $limit ) { break; } } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing the import file handle. fclose( $handle ); return $rows; } /** * Read the next batch of data rows starting from a byte offset. * * Unlike read_range() (which rewinds and re-scans from the header every * call), this seeks straight to $byte_offset, so a full import costs one * linear pass instead of O(n^2/batch) re-scans. Pass 0 for the first batch * (the header is skipped); the updated position is written back by * reference for the next call. * * @param string $token File token. * @param int $byte_offset Byte position to resume from (updated by reference). * @param int $limit Max rows to return. * @return array> Rows of cell values. * @since 1.3.0 */ public static function read_batch( $token, &$byte_offset, $limit ) { $path = self::path_for( $token ); if ( false === $path ) { return []; } $limit = max( 0, (int) $limit ); if ( 0 === $limit ) { return []; } $byte_offset = max( 0, (int) $byte_offset ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Reading a private import file line by line to bound memory. $handle = fopen( $path, 'r' ); if ( false === $handle ) { return []; } if ( $byte_offset > 0 ) { fseek( $handle, $byte_offset ); } else { fgetcsv( $handle, 0, ',', '"', '' ); // Skip header on the first batch. } $rows = []; $read = 0; while ( $read < $limit ) { $row = fgetcsv( $handle, 0, ',', '"', '' ); if ( false === $row ) { break; } $rows[] = array_map( static function ( $cell ) { return (string) $cell; }, $row ); ++$read; } $pos = ftell( $handle ); $byte_offset = false === $pos ? $byte_offset : (int) $pos; // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing the import file handle. fclose( $handle ); return $rows; } /** * Read the first N data rows (for the analyze preview). * * @param string $token File token. * @param int $limit Number of sample rows. * @return array> Sample rows. * @since 1.3.0 */ public static function read_sample( $token, $limit = 5 ) { return self::read_range( $token, 0, $limit ); } /** * Count data rows (excluding the header). * * @param string $token File token. * @return int Row count. * @since 1.3.0 */ public static function count_rows( $token ) { $path = self::path_for( $token ); if ( false === $path ) { return 0; } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Reading a private import file line by line to bound memory. $handle = fopen( $path, 'r' ); if ( false === $handle ) { return 0; } $count = -1; // Start at -1 so the header row isn't counted. while ( fgetcsv( $handle, 0, ',', '"', '' ) !== false ) { ++$count; } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing the import file handle. fclose( $handle ); return max( 0, $count ); } /** * Strip a leading UTF-8 BOM from a value. * * @param string $value Value. * @return string Value without a leading BOM. * @since 1.3.0 */ private static function strip_bom( $value ) { if ( 0 === strncmp( $value, "\xEF\xBB\xBF", 3 ) ) { return substr( $value, 3 ); } return $value; } }