type = $type; if ( empty( $this->type ) ) { add_action( 'admin_post_welcart_csv_upload', array( $this, 'handle_error' ) ); add_action( 'admin_post_welcart_csv_mapping', array( $this, 'handle_error' ) ); } else { add_action( 'admin_post_welcart_csv_upload', array( $this, 'handle_csv_upload' ) ); add_action( 'admin_post_welcart_csv_mapping', array( $this, 'handle_csv_mapping' ) ); add_action( 'admin_post_download_csv_error_log', array( $this, 'download_error_log' ) ); add_action( 'wp_ajax_process_csv_chunk', array( $this, 'process_csv_chunk' ) ); $this->register_csv_import_handling_page(); } } /** * Get instance * * @param string $type Import type. * @return Welcart_CSV_Importer */ public static function get_instance( $type ) { if ( null === self::$instance ) { self::$instance = new Welcart_CSV_Importer( $type ); } return self::$instance; } /** * Set condition data * * @param array $condition Condition data. */ public function set_condition( $condition ) { $this->page_title = isset( $condition['page_title'] ) ? $condition['page_title'] : __( 'Data Import', 'usces' ); $this->table_columns = isset( $condition['columns'] ) ? $condition['columns'] : array(); $this->meta_fields = isset( $condition['meta_fields'] ) ? $condition['meta_fields'] : array(); } /** * Add handling page */ public function register_csv_import_handling_page() { add_submenu_page( 'wel-hidden-page', // Specify a fake parent page to hide from the menu. __( 'Welcart CSV Import', 'usces' ), __( 'Welcart CSV Import', 'usces' ), 'manage_options', 'welcart-csv-importer', array( $this, 'handle_import_page_action' ) ); } /** * Switch page */ public function handle_import_page_action() { if ( ! current_user_can( 'manage_options' ) ) { $this->go_to_error_page( __( 'Permission error.', 'usces' ) . ' (#073)' ); } check_admin_referer( 'welcart_csv_import', 'wc_nonce' ); $type = isset( $_GET['type'] ) ? sanitize_text_field( wp_unslash( $_GET['type'] ) ) : ''; if ( $this->type !== $type ) { $this->render_error_page( __( 'Type does not match.', 'usces' ) . ' (#001)' ); } $step = isset( $_GET['step'] ) ? sanitize_text_field( wp_unslash( $_GET['step'] ) ) : 'upload'; $import_id = isset( $_GET['import_id'] ) ? sanitize_text_field( wp_unslash( $_GET['import_id'] ) ) : ''; if ( ! $import_id && 'upload' !== $step ) { $this->render_error_page( __( 'Invalid access.', 'usces' ) . ' (#050)' ); } switch ( $step ) { case 'upload': $this->render_upload_form( $type ); break; case 'preview': $encoding = isset( $_GET['encoding'] ) ? sanitize_text_field( wp_unslash( $_GET['encoding'] ) ) : 'UTF-8'; $this->render_preview_and_mapping( $type, $import_id, $encoding ); break; case 'import': $this->render_import_progress( $type, $import_id ); break; case 'result': $this->render_result_page( $type, $import_id ); break; case 'error': $this->render_error_page(); break; default: $this->render_error_page( __( 'Invalid step. (#002)', 'usces' ) ); } } /** * Upload form * * @param string $type Import type. */ private function render_upload_form( $type ) { if ( $this->type !== $type ) { $this->render_error_page( __( 'Type does not match.', 'usces' ) . ' (#005)' ); } ?>

page_title ); ?>

go_to_error_page( __( 'Permission error.', 'usces' ) . ' (#072)' ); } check_admin_referer( 'welcart_csv_import', 'wc_nonce' ); $type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : ''; if ( $this->type !== $type ) { $this->go_to_error_page( __( 'Type does not match.', 'usces' ) . ' (#007)' ); } if ( false !== $this->file_upload_error() ) { $message = $this->file_upload_error(); $this->go_to_error_page( $message . __( ' (#008)', 'usces' ) ); } $file_name_org = isset( $_FILES['csv_file']['name'] ) ? sanitize_text_field( wp_unslash( $_FILES['csv_file']['name'] ) ) : ''; if ( empty( $file_name_org ) ) { $this->go_to_error_page( __( 'Failed to get file name.', 'usces' ) . ' (#009)' ); } $file_name_org = sanitize_file_name( basename( urldecode( $file_name_org ) ) ); $temp_file_name = wp_unique_filename( WEL_IMPORT_UPLOADS_DIR, $file_name_org ); $uploaded_file = WEL_IMPORT_UPLOADS_DIR . '/' . $temp_file_name; $allowed_types = array( 'text/csv', 'application/zip' ); $file_type = wp_check_filetype_and_ext( $uploaded_file, $file_name_org ); if ( ! in_array( $file_type['type'], $allowed_types, true ) ) { $this->go_to_error_page( __( 'Please specify a CSV or ZIP file.', 'usces' ) . ' (#009)' ); } // Delete all CSV files. $files = glob( WEL_IMPORT_UPLOADS_DIR . '/*.csv' ); if ( is_array( $files ) ) { foreach ( $files as $file ) { if ( is_file( $file ) ) { wp_delete_file( $file ); } } } $tmp_name_org = isset( $_FILES['csv_file']['tmp_name'] ) ? sanitize_text_field( wp_unslash( $_FILES['csv_file']['tmp_name'] ) ) : ''; if ( empty( $tmp_name_org ) || ! move_uploaded_file( $tmp_name_org, $uploaded_file ) ) { $this->go_to_error_page( __( 'Failed to save file.', 'usces' ) . ' (#009)' ); } $ext = strtolower( pathinfo( $file_name_org, PATHINFO_EXTENSION ) ); if ( 'zip' === $ext ) { // Extract CSV from ZIP. $zip = new ZipArchive(); if ( true === $zip->open( $uploaded_file ) ) { if ( $zip->numFiles > 2 ) { $this->go_to_error_page( __( 'Too many files in ZIP.', 'usces' ) . ' (#030)' ); } $csv_found = false; for ( $i = 0; $i < $zip->numFiles; $i++ ) { $entry = $zip->getNameIndex( $i ); if ( 'csv' === strtolower( pathinfo( $entry, PATHINFO_EXTENSION ) ) ) { $csv_file_name = basename( $entry ); $temp_file_name = wp_unique_filename( WEL_IMPORT_UPLOADS_DIR, $csv_file_name ); $temp_file = WEL_IMPORT_UPLOADS_DIR . '/' . $temp_file_name; if ( ! copy( "zip://{$uploaded_file}#{$entry}", $temp_file ) ) { $this->go_to_error_page( __( 'Failed to extract CSV file from ZIP.', 'usces' ) . ' (#015)' ); } $csv_found = true; break; } else { $this->go_to_error_page( __( 'CSV file not found in ZIP.', 'usces' ) . ' (#031)' ); } } $zip->close(); wp_delete_file( $uploaded_file ); if ( ! $csv_found ) { $this->go_to_error_page( __( 'CSV file not found in ZIP.', 'usces' ) . ' (#012)' ); } $csv_name = $csv_file_name; } else { $this->go_to_error_page( __( 'Unable to open ZIP file.', 'usces' ) . ' (#013)' ); } } else { $temp_file = $uploaded_file; $csv_name = $file_name_org; } $import_id = uniqid(); $data = array( 'temp_file' => $temp_file_name, 'csv_name' => $csv_name, ); set_transient( $this->import_transient_prefix . $import_id, $data, 30 * MINUTE_IN_SECONDS ); $redirect_url = add_query_arg( array( 'page' => 'welcart-csv-importer', 'step' => 'preview', 'type' => $this->type, 'import_id' => $import_id, 'wc_nonce' => wp_create_nonce( 'welcart_csv_import' ), ), admin_url( 'admin.php' ) ); wp_safe_redirect( $redirect_url ); exit; } /** * Preview & Mapping page * * @param string $type Import type. * @param string $import_id Import ID. * @param string $encoding Character encoding. */ private function render_preview_and_mapping( $type, $import_id, $encoding ) { $data = get_transient( $this->import_transient_prefix . $import_id ); if ( ! $data ) { $this->render_error_page( __( 'Import session not found.', 'usces' ) . ' (#051)' ); } $temp_file_name = sanitize_file_name( basename( sanitize_text_field( wp_unslash( $data['temp_file'] ) ) ) ); $temp_file = WEL_IMPORT_UPLOADS_DIR . '/' . $temp_file_name; $csv_name = sanitize_text_field( wp_unslash( $data['csv_name'] ) ); if ( ! file_exists( $temp_file ) ) { $this->render_error_page( __( 'Temporary file not found.', 'usces' ) . ' ' . __( 'Please start over from the beginning.', 'usces' ) . ' (#017)' ); } // To handle large files with low memory, do not use WP_Filesystem. $handle = fopen( $temp_file, 'r' ); if ( false === $handle ) { $this->render_error_page( __( 'Failed to read file.', 'usces' ) . ' ' . __( 'Please start over from the beginning.', 'usces' ) . ' (#018)' ); } $sample = fread( $handle, 1000 ); $enc_type = mb_detect_encoding( $sample, array( 'SJIS-win', 'SJIS', 'UTF-8', 'EUC-JP' ), true ); rewind( $handle ); if ( 'SJIS' === $encoding && 'UTF-8' !== $enc_type ) { stream_filter_append( $handle, 'convert.iconv.sjis-win/utf-8' ); } $rows = array(); $max_rows = 6; // Retrieve maximum 6 rows for preview. $i = 0; while ( $i < $max_rows ) { $data = fgetcsv( $handle ); if ( false === $data ) { // End of file reached. break; } // Exclude empty rows (allowing '0'). if ( ! empty( array_filter( $data, 'strlen' ) ) ) { // Remove BOM from first field in first row. if ( 0 === $i && isset( $data[0] ) && 0 === strncmp( $data[0], "\xEF\xBB\xBF", 3 ) ) { $data[0] = substr( $data[0], 3 ); } $rows[] = $data; ++$i; } } fclose( $handle ); if ( 'SJIS' === $encoding && 'UTF-8' === $enc_type ) { $rows = array(); } $num_columns = ! empty( $rows ) ? count( $rows[0] ) : 0; $num_all_rows = $this->count_csv_lines( $temp_file, $encoding ); ?>

page_title ); ?>

table_columns as $col_key => $col_label ) : ?>
meta_fields ) ) : ?> meta_fields as $col_key => $col_label ) : ?>

Start import */ public function handle_csv_mapping() { if ( ! current_user_can( 'manage_options' ) ) { $this->go_to_error_page( __( 'Permission error.', 'usces' ) . ' (#071)' ); } check_admin_referer( 'welcart_csv_import', 'wc_nonce' ); $type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : ''; if ( $this->type !== $type ) { $this->go_to_error_page( __( 'Type does not match.', 'usces' ) . ' (#020)' ); } $import_id = isset( $_POST['import_id'] ) ? sanitize_text_field( wp_unslash( $_POST['import_id'] ) ) : ''; if ( ! $import_id ) { $this->go_to_error_page( __( 'Invalid access.', 'usces' ) . ' (#053)' ); } $data = get_transient( $this->import_transient_prefix . $import_id ); if ( ! $data ) { $this->go_to_error_page( __( 'Import session not found.', 'usces' ) . ' (#054)' ); } $temp_file_name = sanitize_file_name( basename( sanitize_text_field( wp_unslash( $data['temp_file'] ) ) ) ); $temp_file = WEL_IMPORT_UPLOADS_DIR . '/' . $temp_file_name; $csv_name = sanitize_text_field( wp_unslash( $data['csv_name'] ) ); $csv_encoding = isset( $_POST['csv_encoding'] ) ? sanitize_text_field( wp_unslash( $_POST['csv_encoding'] ) ) : 'UTF-8'; $has_header = isset( $_POST['has_header'] ) ? (bool) sanitize_text_field( wp_unslash( $_POST['has_header'] ) ) : false; if ( isset( $_POST['mapping'] ) && is_array( $_POST['mapping'] ) && ! empty( $_POST['mapping'] ) ) { $mapping = array_map( 'sanitize_text_field', wp_unslash( $_POST['mapping'] ) ); } else { $this->go_to_error_page( __( 'Required fields have not been selected. (#033)', 'usces' ) ); } if ( empty( $temp_file ) || ! file_exists( $temp_file ) ) { $this->go_to_error_page( __( 'Temporary file not found.', 'usces' ) . ' ' . __( 'Please re-upload the CSV.', 'usces' ) . ' (#021)' ); } $total_lines = $this->count_csv_lines( $temp_file, $csv_encoding ); if ( $total_lines <= 0 ) { $this->go_to_error_page( __( 'CSV file is empty. Please check the content.', 'usces' ) . ' (#034)' ); } $data = array( 'temp_file' => $temp_file, 'csv_name' => $csv_name, 'csv_encoding' => $csv_encoding, 'has_header' => $has_header, 'mapping' => $mapping, 'total_lines' => $total_lines, 'offset' => $has_header ? 1 : 0, 'imported' => 0, 'start_time' => time(), 'inserted_count' => 0, 'updated_count' => 0, 'memory_limit' => ini_get( 'memory_limit' ), ); $data = apply_filters( 'welcart_csv_import_transient_data', $data, $import_id ); set_transient( $this->import_transient_prefix . $import_id, $data, 30 * MINUTE_IN_SECONDS ); // Initialize error log. $error_log_path = $this->get_error_log_path(); file_put_contents( $error_log_path, '' ); $redirect_url = add_query_arg( array( 'page' => 'welcart-csv-importer', 'step' => 'import', 'type' => $this->type, 'import_id' => $import_id, 'wc_nonce' => wp_create_nonce( 'welcart_csv_import' ), ), admin_url( 'admin.php' ) ); wp_safe_redirect( $redirect_url ); exit; } /** * Import progress page * * @param string $type Import type. * @param string $import_id Import ID. */ private function render_import_progress( $type, $import_id ) { $data = get_transient( $this->import_transient_prefix . $import_id ); if ( ! $data ) { $this->render_error_page( __( 'Import session not found.', 'usces' ) . ' (#024)' ); } $memory_limit = isset( $data['memory_limit'] ) ? $data['memory_limit'] : ''; $memory_usage = isset( $data['memory_usage'] ) ? $data['memory_usage'] : ''; $has_header = isset( $data['has_header'] ) ? $data['has_header'] : false; if ( $has_header ) { $total_lines = (int) $data['total_lines'] - 1; } else { $total_lines = (int) $data['total_lines']; } ?>

page_title ); ?>

:

:

:

:

:

: /

: --

0%

__( 'Permission error.', 'usces' ) . ' (#070)' ) ); } if ( ! check_ajax_referer( 'process_csv_chunk_nonce', '_wpnonce', false ) ) { wp_send_json_error( array( 'message' => __( 'Security check failed. Please reload the page.', 'usces' ) ) ); } $type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : ''; if ( $this->type !== $type ) { wp_send_json_error( array( 'message' => __( 'Type is invalid.', 'usces' ) ) ); } $import_id = isset( $_POST['import_id'] ) ? sanitize_text_field( wp_unslash( $_POST['import_id'] ) ) : ''; $offset = isset( $_POST['offset'] ) ? intval( $_POST['offset'] ) : 0; if ( empty( $import_id ) ) { wp_send_json_error( array( 'message' => __( 'Invalid import ID. Please re-upload the CSV.', 'usces' ) ) ); } // Get session. $data = get_transient( $this->import_transient_prefix . $import_id ); if ( ! $data ) { wp_send_json_error( array( 'message' => __( 'Import session has expired. Please re-upload the CSV.', 'usces' ) ) ); } $temp_file = $data['temp_file']; $csv_encoding = $data['csv_encoding']; $has_header = $data['has_header']; $mapping = $data['mapping']; $total_lines = $data['total_lines']; $imported = isset( $data['imported'] ) ? intval( $data['imported'] ) : 0; $start_time = isset( $data['start_time'] ) ? intval( $data['start_time'] ) : time(); $inserted_count = isset( $data['inserted_count'] ) ? intval( $data['inserted_count'] ) : 0; $updated_count = isset( $data['updated_count'] ) ? intval( $data['updated_count'] ) : 0; if ( ! file_exists( $temp_file ) ) { wp_send_json_error( array( 'message' => __( 'CSV file not found. Please re-upload the CSV.', 'usces' ) ) ); } $handle = fopen( $temp_file, 'r' ); if ( 'SJIS' === $csv_encoding ) { stream_filter_append( $handle, 'convert.iconv.sjis-win/utf-8' ); } if ( ! $handle ) { wp_send_json_error( array( 'message' => __( 'Unable to open CSV file. Please re-upload the CSV.', 'usces' ) ) ); } // Move pointer according to offset. for ( $i = 0; $i < $offset; $i++ ) { fgetcsv( $handle ); } $process_info = array( 'inserted_count' => 0, 'updated_count' => 0, 'imported' => 0, 'processed' => 0, ); // Process each chunk via hook. $process_info = apply_filters( 'wel_csv_importer_chunk_process', $process_info, $handle, $data ); fclose( $handle ); $new_offset = $offset + $process_info['processed']; $data['offset'] = $new_offset; $data['imported'] += $process_info['imported']; $data['inserted_count'] += $process_info['inserted_count']; $data['updated_count'] += $process_info['updated_count']; set_transient( $this->import_transient_prefix . $import_id, $data, 30 * MINUTE_IN_SECONDS ); $percent = min( 100, intval( ( $new_offset / $total_lines ) * 100 ) ); $complete = ( $new_offset >= $total_lines ); $elapsed = time() - $start_time; $avg_time_per_row = $offset > 0 ? ( $elapsed / $offset ) : 0; $remaining_rows = $total_lines - $new_offset; $estimated_time = round( $avg_time_per_row * $remaining_rows ); $formatted_time = $this->format_seconds( $estimated_time ); $memory_usage = number_format( memory_get_peak_usage() / 1024 / 1024, 1 ) . ' M'; wp_send_json_success( array( 'next_offset' => $new_offset, 'percent' => $percent, 'complete' => $complete, 'total' => $total_lines, 'imported' => $data['imported'], 'added' => $data['inserted_count'], 'updated' => $data['updated_count'], 'estimated_time' => $formatted_time, 'memory_usage' => $memory_usage, ) ); } /** * Import completion page * * @param string $type Import type. * @param string $import_id Import ID. */ private function render_result_page( $type, $import_id ) { $data = get_transient( $this->import_transient_prefix . $import_id ); if ( ! $data ) { $this->render_error_page( __( 'Import session not found.', 'usces' ) . ' (#024)' ); } if ( file_exists( WEL_IMPORT_UPLOADS_DIR ) ) { // Delete all existing CSV files. $files = glob( WEL_IMPORT_UPLOADS_DIR . '/*.csv' ); if ( is_array( $files ) ) { foreach ( $files as $file ) { if ( is_file( $file ) ) { wp_delete_file( $file ); } } } } $total = isset( $data['total_lines'] ) ? $data['total_lines'] : 0; $updated = isset( $data['updated_count'] ) ? $data['updated_count'] : ''; $added = isset( $data['inserted_count'] ) ? $data['inserted_count'] : ''; $imported = isset( $data['imported'] ) ? $data['imported'] : ''; $csv_name = isset( $data['csv_name'] ) ? $data['csv_name'] : ''; $has_header = isset( $data['has_header'] ) ? $data['has_header'] : false; if ( $has_header ) { $total_lines = (int) $total - 1; } else { $total_lines = (int) $total; } ?>

page_title ); ?>

get_error_log_path(); if ( file_exists( $error_log_path ) ) { if ( 0 === filesize( $error_log_path ) ) { // No error. file_put_contents( $error_log_path, __( "No error.\n", 'usces' ) ); $first50 = __( "No error.\n", 'usces' ); echo '

' . esc_html__( 'No Error', 'usces' ) . '

'; } else { $lines = file( $error_log_path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES ); $first50 = array_slice( $lines, 0, 50 ); $error_log = implode( "\n", $first50 ); $num_log = count( $lines ); if ( $num_log > 50 ) { $error_log .= "\n" . __( 'More...', 'usces' ); } $args = array( 'action' => 'download_csv_error_log', 'type' => $this->type, 'wc_nonce' => wp_create_nonce( 'download_csv_error_log' ), ); $admin_url = add_query_arg( $args, admin_url( 'admin-post.php' ) ); ?>

' . esc_html__( 'No log file found.', 'usces' ) . '

'; } ?>

page_title ); ?>

'welcart-csv-importer', 'step' => 'error', 'type' => $this->type, 'wc_nonce' => wp_create_nonce( 'welcart_csv_import' ), ), admin_url( 'admin.php' ) ); wp_safe_redirect( $redirect_url ); exit; } /** * Download error log */ public function download_error_log() { if ( ! current_user_can( 'manage_options' ) ) { $this->go_to_error_page( __( 'Permission error.', 'usces' ) . ' (#060)' ); } check_admin_referer( 'download_csv_error_log', 'wc_nonce' ); $error_log_path = $this->get_error_log_path(); if ( ! file_exists( $error_log_path ) ) { $this->render_error_page( __( 'Error log file not found.', 'usces' ) . ' (#028)' ); } header( 'Content-Description: File Transfer' ); header( 'Content-Type: text/plain; charset=UTF-8' ); header( 'Content-Disposition: attachment; filename="csv_error_log.txt"' ); header( 'Content-Length: ' . filesize( $error_log_path ) ); readfile( $error_log_path ); exit; } /** * Count CSV lines * * @param string $file File path. * @param string $encoding Character encoding. * @return int Line count. */ private function count_csv_lines( $file, $encoding ) { $count = 0; $handle = fopen( $file, 'r' ); if ( ! $handle ) { return 0; } while ( true ) { $data = fgetcsv( $handle ); if ( false === $data ) { break; } if ( ! empty( array_filter( $data ) ) ) { ++$count; } } fclose( $handle ); return $count; } /** * Get main columns */ public function get_columns() { return $this->table_columns; } /** * Get meta fields */ public function get_meta_fields() { return $this->meta_fields; } /** * Format seconds to time string * * @param int $seconds Seconds. * @return string Formatted time. */ private function format_seconds( $seconds ) { $hours = floor( $seconds / 3600 ); $minutes = floor( ( $seconds % 3600 ) / 60 ); $secs = $seconds % 60; $str = ''; if ( $hours > 0 ) { $str .= $hours . __( ' hours ', 'usces' ); } if ( $minutes > 0 ) { $str .= $minutes . __( ' minutes ', 'usces' ); } $str .= $secs . __( ' seconds', 'usces' ); return $str; } /** * File upload error message * * @return string Error message. */ private function file_upload_error() { // phpcs:disable WordPress.Security.NonceVerification -- Already verified inside CSV importer class if ( ! isset( $_FILES['csv_file'] ) || empty( $_FILES['csv_file']['name'] ) ) { return __( 'Please select a file to upload.', 'usces' ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput $file = $_FILES['csv_file']; // phpcs:enable WordPress.Security.NonceVerification.Recommended // Get error code. $error_code = absint( $file['error'] ); $error_message = ''; // Error code messages. switch ( $error_code ) { case UPLOAD_ERR_OK: $error_message = false; break; case UPLOAD_ERR_INI_SIZE: $error_message = __( 'File size is too large.', 'usces' ); break; case UPLOAD_ERR_FORM_SIZE: $error_message = __( 'Exceeds maximum file size specified in the form.', 'usces' ); break; case UPLOAD_ERR_PARTIAL: $error_message = __( 'File was only partially uploaded.', 'usces' ); break; case UPLOAD_ERR_NO_FILE: $error_message = __( 'No file selected.', 'usces' ); break; case UPLOAD_ERR_NO_TMP_DIR: $error_message = __( 'Missing a temporary folder.', 'usces' ); break; case UPLOAD_ERR_CANT_WRITE: $error_message = __( 'Failed to write file to disk.', 'usces' ); break; case UPLOAD_ERR_EXTENSION: $error_message = __( 'File upload stopped by extension.', 'usces' ); break; default: $error_message = __( 'An unknown error occurred.', 'usces' ); break; } return $error_message; } /** * Error handling */ public function handle_error() { $this->render_error_page( __( 'Import type not specified. (#029)', 'usces' ) ); } /** * Get error log file path */ public function get_error_log_path() { return WEL_IMPORT_UPLOADS_DIR . '/csv-error-log.txt'; } }