} */ public static function parse( $text, callable $lookup ) { $text = (string) $text; if ( strlen( $text ) > self::MAX_BYTES ) { return self::file_error( __( 'The file is larger than 1 MB. Split it into smaller files.', 'layout-foundry-quantities' ) ); } // Excel adds a byte order mark; line endings may be Windows, Mac or Unix. $text = preg_replace( '/^\xEF\xBB\xBF/', '', $text ); $lines = self::read_rows( $text ); if ( count( $lines ) < 2 ) { return self::file_error( __( 'The file needs a header row and at least one product row.', 'layout-foundry-quantities' ) ); } if ( count( $lines ) - 1 > self::MAX_ROWS ) { /* translators: %d: most rows allowed. */ return self::file_error( sprintf( __( 'The file has more than %d product rows. Split it into smaller files.', 'layout-foundry-quantities' ), self::MAX_ROWS ) ); } $fields = array_keys( LFQTY_Fields::definitions() ); $headers = array_map( static function ( $header ) { return strtolower( trim( (string) $header ) ); }, $lines[0] ); $unknown = array_diff( array_filter( $headers, 'strlen' ), array_merge( array( 'sku', 'product_id' ), $fields ) ); if ( $unknown ) { /* translators: 1: unknown column names, 2: allowed column names. */ return self::file_error( sprintf( __( 'Unknown columns: %1$s. Allowed columns: %2$s.', 'layout-foundry-quantities' ), implode( ', ', $unknown ), implode( ', ', array_merge( array( 'sku', 'product_id' ), $fields ) ) ) ); } if ( ! in_array( 'sku', $headers, true ) && ! in_array( 'product_id', $headers, true ) ) { return self::file_error( __( 'The header row needs a sku or product_id column.', 'layout-foundry-quantities' ) ); } if ( count( $headers ) !== count( array_unique( $headers ) ) ) { return self::file_error( __( 'A column name appears twice in the header row.', 'layout-foundry-quantities' ) ); } $rows = array(); $seen = array(); foreach ( array_slice( $lines, 1 ) as $index => $cells ) { $line = $index + 2; if ( array( '' ) === array_map( 'trim', $cells ) || array() === array_filter( array_map( 'trim', $cells ), 'strlen' ) ) { continue; // Blank line. } $errors = array(); if ( count( $cells ) !== count( $headers ) ) { /* translators: 1: cells found, 2: columns in the header. */ $errors[] = sprintf( __( 'This row has %1$d cells; the header has %2$d.', 'layout-foundry-quantities' ), count( $cells ), count( $headers ) ); } $cells = array_pad( array_slice( $cells, 0, count( $headers ) ), count( $headers ), '' ); $data = array_combine( $headers, array_map( 'trim', $cells ) ); $sku = isset( $data['sku'] ) ? $data['sku'] : ''; $id_text = isset( $data['product_id'] ) ? $data['product_id'] : ''; $product = null; if ( '' !== $id_text && ! preg_match( '/^\d{1,12}$/', $id_text ) ) { /* translators: %s: value given. */ $errors[] = sprintf( __( 'product_id "%s" must be a whole number.', 'layout-foundry-quantities' ), $id_text ); } elseif ( '' === $sku && '' === $id_text ) { $errors[] = __( 'The row has no sku or product_id.', 'layout-foundry-quantities' ); } else { $product = call_user_func( $lookup, $sku, (int) $id_text ); if ( ! $product ) { $errors[] = '' !== $sku /* translators: %s: SKU. */ ? sprintf( __( 'No simple product has the SKU "%s".', 'layout-foundry-quantities' ), $sku ) /* translators: %s: product id. */ : sprintf( __( 'No simple product has the ID %s.', 'layout-foundry-quantities' ), $id_text ); } } if ( $product && isset( $seen[ $product['id'] ] ) ) { /* translators: %d: line number of the earlier row. */ $errors[] = sprintf( __( 'This product is already on line %d.', 'layout-foundry-quantities' ), $seen[ $product['id'] ] ); } if ( $product ) { $seen[ $product['id'] ] = $line; } $raw = array_intersect_key( $data, array_flip( $fields ) ); if ( ! isset( $raw['enabled'] ) || '' === $raw['enabled'] ) { $raw['enabled'] = 'yes'; } $checked = LFQTY_Fields::validate( $raw ); $rows[] = array( 'line' => $line, 'product_id' => $product ? (int) $product['id'] : 0, 'product_name' => $product ? (string) $product['name'] : '', 'values' => $checked['values'], 'errors' => array_merge( $errors, $checked['errors'] ), ); } if ( ! $rows ) { return self::file_error( __( 'The file needs a header row and at least one product row.', 'layout-foundry-quantities' ) ); } return array( 'errors' => array(), 'rows' => $rows, ); } /** * Rows of cells. Handles quoted cells with commas, quotes and line breaks. * * @param string $text CSV text. * @return array */ private static function read_rows( $text ) { $file = new SplTempFileObject(); $file->fwrite( $text ); $file->rewind(); $file->setFlags( SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY | SplFileObject::READ_AHEAD ); $file->setCsvControl( ',', '"', '' ); $rows = array(); foreach ( $file as $cells ) { if ( is_array( $cells ) && array( null ) !== $cells ) { $rows[] = array_map( 'strval', $cells ); } if ( count( $rows ) > self::MAX_ROWS + 1 ) { break; } } return $rows; } /** * Result for a file that cannot be read at all. * * @param string $message Message. * @return array */ private static function file_error( $message ) { return array( 'errors' => array( $message ), 'rows' => array(), ); } }