Add a string encoding selector to the product importer (#36819)

This commit is contained in:
Barry Hughes 2023-02-24 09:08:26 -08:00 committed by GitHub
commit af7c3f380d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 111 additions and 43 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add an encoding selector to the product importer

View File

@ -5,14 +5,15 @@
* productImportForm handles the import process. * productImportForm handles the import process.
*/ */
var productImportForm = function( $form ) { var productImportForm = function( $form ) {
this.$form = $form; this.$form = $form;
this.xhr = false; this.xhr = false;
this.mapping = wc_product_import_params.mapping; this.mapping = wc_product_import_params.mapping;
this.position = 0; this.position = 0;
this.file = wc_product_import_params.file; this.file = wc_product_import_params.file;
this.update_existing = wc_product_import_params.update_existing; this.update_existing = wc_product_import_params.update_existing;
this.delimiter = wc_product_import_params.delimiter; this.delimiter = wc_product_import_params.delimiter;
this.security = wc_product_import_params.import_nonce; this.security = wc_product_import_params.import_nonce;
this.character_encoding = wc_product_import_params.character_encoding;
// Number of import successes/failures. // Number of import successes/failures.
this.imported = 0; this.imported = 0;
@ -39,13 +40,14 @@
type: 'POST', type: 'POST',
url: ajaxurl, url: ajaxurl,
data: { data: {
action : 'woocommerce_do_ajax_product_import', action : 'woocommerce_do_ajax_product_import',
position : $this.position, position : $this.position,
mapping : $this.mapping, mapping : $this.mapping,
file : $this.file, file : $this.file,
update_existing : $this.update_existing, update_existing : $this.update_existing,
delimiter : $this.delimiter, delimiter : $this.delimiter,
security : $this.security security : $this.security,
character_encoding: $this.character_encoding
}, },
dataType: 'json', dataType: 'json',
success: function( response ) { success: function( response ) {

View File

@ -147,11 +147,13 @@ class WC_Admin_Importers {
public function post_importer_compatibility() { public function post_importer_compatibility() {
global $wpdb; global $wpdb;
if ( empty( $_POST['import_id'] ) || ! class_exists( 'WXR_Parser' ) ) { // PHPCS: input var ok, CSRF ok. // phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( empty( $_POST['import_id'] ) || ! class_exists( 'WXR_Parser' ) ) {
return; return;
} }
$id = absint( $_POST['import_id'] ); // PHPCS: input var ok. // phpcs:ignore WordPress.Security.NonceVerification.Missing
$id = absint( $_POST['import_id'] );
$file = get_attached_file( $id ); $file = get_attached_file( $id );
$parser = new WXR_Parser(); $parser = new WXR_Parser();
$import_data = $parser->parse( $file ); $import_data = $parser->parse( $file );
@ -216,12 +218,21 @@ class WC_Admin_Importers {
$file = wc_clean( wp_unslash( $_POST['file'] ) ); // PHPCS: input var ok. $file = wc_clean( wp_unslash( $_POST['file'] ) ); // PHPCS: input var ok.
$params = array( $params = array(
'delimiter' => ! empty( $_POST['delimiter'] ) ? wc_clean( wp_unslash( $_POST['delimiter'] ) ) : ',', // PHPCS: input var ok. 'delimiter' => ! empty( $_POST['delimiter'] ) ? wc_clean( wp_unslash( $_POST['delimiter'] ) ) : ',', // PHPCS: input var ok.
'start_pos' => isset( $_POST['position'] ) ? absint( $_POST['position'] ) : 0, // PHPCS: input var ok. 'start_pos' => isset( $_POST['position'] ) ? absint( $_POST['position'] ) : 0, // PHPCS: input var ok.
'mapping' => isset( $_POST['mapping'] ) ? (array) wc_clean( wp_unslash( $_POST['mapping'] ) ) : array(), // PHPCS: input var ok. 'mapping' => isset( $_POST['mapping'] ) ? (array) wc_clean( wp_unslash( $_POST['mapping'] ) ) : array(), // PHPCS: input var ok.
'update_existing' => isset( $_POST['update_existing'] ) ? (bool) $_POST['update_existing'] : false, // PHPCS: input var ok. 'update_existing' => isset( $_POST['update_existing'] ) ? (bool) $_POST['update_existing'] : false, // PHPCS: input var ok.
'lines' => apply_filters( 'woocommerce_product_import_batch_size', 30 ), 'character_encoding' => isset( $_POST['character_encoding'] ) ? wc_clean( wp_unslash( $_POST['character_encoding'] ) ) : '',
'parse' => true,
/**
* Batch size for the product import process.
*
* @param int $size Batch size.
*
* @since
*/
'lines' => apply_filters( 'woocommerce_product_import_batch_size', 30 ),
'parse' => true,
); );
// Log failures. // Log failures.

View File

@ -72,6 +72,13 @@ class WC_Product_CSV_Importer_Controller {
*/ */
protected $update_existing = false; protected $update_existing = false;
/**
* The character encoding to use to interpret the input file, or empty string for autodetect.
*
* @var string
*/
protected $character_encoding = 'UTF-8';
/** /**
* Get importer instance. * Get importer instance.
* *
@ -141,11 +148,12 @@ class WC_Product_CSV_Importer_Controller {
$this->steps = apply_filters( 'woocommerce_product_csv_importer_steps', $default_steps ); $this->steps = apply_filters( 'woocommerce_product_csv_importer_steps', $default_steps );
// phpcs:disable WordPress.Security.NonceVerification.Recommended // phpcs:disable WordPress.Security.NonceVerification.Recommended
$this->step = isset( $_REQUEST['step'] ) ? sanitize_key( $_REQUEST['step'] ) : current( array_keys( $this->steps ) ); $this->step = isset( $_REQUEST['step'] ) ? sanitize_key( $_REQUEST['step'] ) : current( array_keys( $this->steps ) );
$this->file = isset( $_REQUEST['file'] ) ? wc_clean( wp_unslash( $_REQUEST['file'] ) ) : ''; $this->file = isset( $_REQUEST['file'] ) ? wc_clean( wp_unslash( $_REQUEST['file'] ) ) : '';
$this->update_existing = isset( $_REQUEST['update_existing'] ) ? (bool) $_REQUEST['update_existing'] : false; $this->update_existing = isset( $_REQUEST['update_existing'] ) ? (bool) $_REQUEST['update_existing'] : false;
$this->delimiter = ! empty( $_REQUEST['delimiter'] ) ? wc_clean( wp_unslash( $_REQUEST['delimiter'] ) ) : ','; $this->delimiter = ! empty( $_REQUEST['delimiter'] ) ? wc_clean( wp_unslash( $_REQUEST['delimiter'] ) ) : ',';
$this->map_preferences = isset( $_REQUEST['map_preferences'] ) ? (bool) $_REQUEST['map_preferences'] : false; $this->map_preferences = isset( $_REQUEST['map_preferences'] ) ? (bool) $_REQUEST['map_preferences'] : false;
$this->character_encoding = isset( $_REQUEST['character_encoding'] ) ? wc_clean( wp_unslash( $_REQUEST['character_encoding'] ) ) : 'UTF-8';
// phpcs:enable // phpcs:enable
// Import mappings for CSV data. // Import mappings for CSV data.
@ -182,12 +190,13 @@ class WC_Product_CSV_Importer_Controller {
} }
$params = array( $params = array(
'step' => $keys[ $step_index + 1 ], 'step' => $keys[ $step_index + 1 ],
'file' => str_replace( DIRECTORY_SEPARATOR, '/', $this->file ), 'file' => str_replace( DIRECTORY_SEPARATOR, '/', $this->file ),
'delimiter' => $this->delimiter, 'delimiter' => $this->delimiter,
'update_existing' => $this->update_existing, 'update_existing' => $this->update_existing,
'map_preferences' => $this->map_preferences, 'map_preferences' => $this->map_preferences,
'_wpnonce' => wp_create_nonce( 'woocommerce-csv-importer' ), // wp_nonce_url() escapes & to & breaking redirects. 'character_encoding' => $this->character_encoding,
'_wpnonce' => wp_create_nonce( 'woocommerce-csv-importer' ), // wp_nonce_url() escapes & to & breaking redirects.
); );
return add_query_arg( $params ); return add_query_arg( $params );
@ -367,8 +376,9 @@ class WC_Product_CSV_Importer_Controller {
protected function mapping_form() { protected function mapping_form() {
check_admin_referer( 'woocommerce-csv-importer' ); check_admin_referer( 'woocommerce-csv-importer' );
$args = array( $args = array(
'lines' => 1, 'lines' => 1,
'delimiter' => $this->delimiter, 'delimiter' => $this->delimiter,
'character_encoding' => $this->character_encoding,
); );
$importer = self::get_importer( $this->file, $args ); $importer = self::get_importer( $this->file, $args );
@ -430,14 +440,15 @@ class WC_Product_CSV_Importer_Controller {
'wc-product-import', 'wc-product-import',
'wc_product_import_params', 'wc_product_import_params',
array( array(
'import_nonce' => wp_create_nonce( 'wc-product-import' ), 'import_nonce' => wp_create_nonce( 'wc-product-import' ),
'mapping' => array( 'mapping' => array(
'from' => $mapping_from, 'from' => $mapping_from,
'to' => $mapping_to, 'to' => $mapping_to,
), ),
'file' => $this->file, 'file' => $this->file,
'update_existing' => $this->update_existing, 'update_existing' => $this->update_existing,
'delimiter' => $this->delimiter, 'delimiter' => $this->delimiter,
'character_encoding' => $this->character_encoding,
) )
); );
wp_enqueue_script( 'wc-product-import' ); wp_enqueue_script( 'wc-product-import' );

View File

@ -60,6 +60,9 @@ if ( ! defined( 'ABSPATH' ) ) {
<input type="hidden" name="file" value="<?php echo esc_attr( $this->file ); ?>" /> <input type="hidden" name="file" value="<?php echo esc_attr( $this->file ); ?>" />
<input type="hidden" name="delimiter" value="<?php echo esc_attr( $this->delimiter ); ?>" /> <input type="hidden" name="delimiter" value="<?php echo esc_attr( $this->delimiter ); ?>" />
<input type="hidden" name="update_existing" value="<?php echo (int) $this->update_existing; ?>" /> <input type="hidden" name="update_existing" value="<?php echo (int) $this->update_existing; ?>" />
<?php if ( $args['character_encoding'] ) { ?>
<input type="hidden" name="character_encoding" value="<?php echo esc_html( $args['character_encoding'] ); ?>" />
<?php } ?>
<?php wp_nonce_field( 'woocommerce-csv-importer' ); ?> <?php wp_nonce_field( 'woocommerce-csv-importer' ); ?>
</div> </div>
</form> </form>

View File

@ -78,6 +78,20 @@ if ( ! defined( 'ABSPATH' ) ) {
<th><label><?php esc_html_e( 'Use previous column mapping preferences?', 'woocommerce' ); ?></label><br/></th> <th><label><?php esc_html_e( 'Use previous column mapping preferences?', 'woocommerce' ); ?></label><br/></th>
<td><input type="checkbox" id="woocommerce-importer-map-preferences" name="map_preferences" value="1" /></td> <td><input type="checkbox" id="woocommerce-importer-map-preferences" name="map_preferences" value="1" /></td>
</tr> </tr>
<tr class="woocommerce-importer-advanced hidden">
<th><label><?php esc_html_e( 'Character encoding of the file', 'woocommerce' ); ?></label><br/></th>
<td><select id="woocommerce-importer-character-encoding" name="character_encoding">
<option value="" selected><?php esc_html_e( 'Autodetect', 'woocommerce' ); ?></option>
<?php
$encodings = mb_list_encodings();
sort( $encodings, SORT_NATURAL );
foreach ( $encodings as $encoding ) {
echo '<option>' . esc_html( $encoding ) . '</option>';
}
?>
</select>
</td>
</tr>
</tbody> </tbody>
</table> </table>
</section> </section>

View File

@ -6,6 +6,8 @@
* @version 3.1.0 * @version 3.1.0
*/ */
use Automattic\WooCommerce\Utilities\ArrayUtil;
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
@ -66,6 +68,17 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
$this->read_file(); $this->read_file();
} }
/**
* Convert a string from the input encoding to UTF-8.
*
* @param string $value The string to convert.
* @return string The converted string.
*/
private function adjust_character_encoding( $value ) {
$encoding = $this->params['character_encoding'];
return 'UTF-8' === $encoding ? $value : mb_convert_encoding( $value, 'UTF-8', $encoding );
}
/** /**
* Read file. * Read file.
*/ */
@ -77,7 +90,11 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
$handle = fopen( $this->file, 'r' ); // @codingStandardsIgnoreLine. $handle = fopen( $this->file, 'r' ); // @codingStandardsIgnoreLine.
if ( false !== $handle ) { if ( false !== $handle ) {
$this->raw_keys = version_compare( PHP_VERSION, '5.3', '>=' ) ? array_map( 'trim', fgetcsv( $handle, 0, $this->params['delimiter'], $this->params['enclosure'], $this->params['escape'] ) ) : array_map( 'trim', fgetcsv( $handle, 0, $this->params['delimiter'], $this->params['enclosure'] ) ); // @codingStandardsIgnoreLine $this->raw_keys = array_map( 'trim', fgetcsv( $handle, 0, $this->params['delimiter'], $this->params['enclosure'], $this->params['escape'] ) ); // @codingStandardsIgnoreLine
if ( ArrayUtil::is_truthy( $this->params, 'character_encoding' ) ) {
$this->raw_keys = array_map( array( $this, 'adjust_character_encoding' ), $this->raw_keys );
}
// Remove line breaks in keys, to avoid mismatch mapping of keys. // Remove line breaks in keys, to avoid mismatch mapping of keys.
$this->raw_keys = wc_clean( wp_unslash( $this->raw_keys ) ); $this->raw_keys = wc_clean( wp_unslash( $this->raw_keys ) );
@ -92,9 +109,13 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
} }
while ( 1 ) { while ( 1 ) {
$row = version_compare( PHP_VERSION, '5.3', '>=' ) ? fgetcsv( $handle, 0, $this->params['delimiter'], $this->params['enclosure'], $this->params['escape'] ) : fgetcsv( $handle, 0, $this->params['delimiter'], $this->params['enclosure'] ); // @codingStandardsIgnoreLine $row = fgetcsv( $handle, 0, $this->params['delimiter'], $this->params['enclosure'], $this->params['escape'] ); // @codingStandardsIgnoreLine
if ( false !== $row ) { if ( false !== $row ) {
if ( ArrayUtil::is_truthy( $this->params, 'character_encoding' ) ) {
$row = array_map( array( $this, 'adjust_character_encoding' ), $row );
}
$this->raw_data[] = $row; $this->raw_data[] = $row;
$this->file_positions[ count( $this->raw_data ) ] = ftell( $handle ); $this->file_positions[ count( $this->raw_data ) ] = ftell( $handle );
@ -1005,6 +1026,8 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
* *
* @param array $parsed_data Parsed data. * @param array $parsed_data Parsed data.
* @param WC_Product_Importer $importer Importer instance. * @param WC_Product_Importer $importer Importer instance.
*
* @since
*/ */
$this->parsed_data[] = apply_filters( 'woocommerce_product_importer_parsed_data', $this->expand_data( $data ), $this ); $this->parsed_data[] = apply_filters( 'woocommerce_product_importer_parsed_data', $this->expand_data( $data ), $this );
} }