array( 'name' => 'ID Sản phẩm Web', 'width' => '10', 'data_field_name' => 'id', ), "B" => array( 'name' => 'Mã kho (SKU)', 'width' => '10', 'data_field_name' => 'storeSKU', ),*/ //... ]; protected $field_column_mappings = [ //'name' => "A", //'price' => "B", ]; // hold cache for some operations protected $cache = []; protected $format_item_middlewares = []; // list of middleware object to format item public function __construct($client_config_file_name = '', $file_input_name = '', array $update_option = []) { if(!$client_config_file_name) { return true; } //this from a config file for each client $client_config_file = "config/client/excel/" . $client_config_file_name; if (!file_exists(ROOT_DIR . '/' . $client_config_file)) { die("Please create config file: " . $client_config_file); } $client_fields_config = include ROOT_DIR . '/' . $client_config_file; if($file_input_name) { $this->file_input_name = $file_input_name; } // auto add excel column names based on fields' index $this->client_excel_col_config = $this->_make_columns(array_values($client_fields_config)); // create field-col map $field_column_mappings = []; foreach ($this->client_excel_col_config as $column_name => $_prop) { if(!$_prop['data_field_name']) continue; // skip column which is not for upload if(isset($_prop['for_upload']) && !$_prop['for_upload']) continue; $field_column_mappings[$_prop['data_field_name']] = $column_name; } $this->field_column_mappings = $field_column_mappings; $this->update_option = $update_option; return true; } public function updateUpdateOptions(array $update_option = []) { foreach ($update_option as $key => $value) { $this->update_option[$key] = $value; } } public function getColumnNotForUpload() { $result = []; foreach ($this->client_excel_col_config as $column_name => $_prop) { // skip column which is not for upload if(isset($_prop['for_upload']) && !$_prop['for_upload']) $result[$column_name] = $_prop['name'] ; } return $result; } public function getColumnCanUpdate() { $result = []; foreach ($this->client_excel_col_config as $column_name => $_prop) { // skip column which is not for upload if(isset($_prop['for_upload']) && !$_prop['for_upload']) continue; // skip column which is not for update if(isset($_prop['can_update']) && !$_prop['can_update']) continue; $result[] = $_prop ; } return $result; } protected function getRequiredFields() { $result = []; foreach ($this->client_excel_col_config as $column_name => $_prop) { // skip column which is not for upload if(isset($_prop['required']) && $_prop['required']) $result[] = $_prop['data_field_name'] ; } return $result; } protected function _make_columns($fields_config) { $new_array = []; $total_names = sizeof(static::$column_names); foreach ($fields_config as $index => $config) { if ($index >= $total_names) break; $new_array[static::$column_names[$index]] = $config; } return $new_array; } protected function getExcelFileExt($excel_file){ $ext = substr(strrchr($excel_file, "."), 1); return (in_array($ext, ["xls", "xlsx"])) ? $ext : false; } public function start($sheet_start_row = 3, $batch_mode = false, $batch_size=100) { $ext = $this->getExcelFileExt($_FILES[$this->file_input_name]["name"]); if(!$ext) { return [ 'status' => 'error', 'message' => 'Invalid excel file', ]; } $objReadExcel = new ReadExcel($ext); $all_rows = $objReadExcel->read( $_FILES[$this->file_input_name]["tmp_name"], $sheet_start_row, $this->field_column_mappings, '', true ); $this->beforeProcessRows($all_rows); $success_row_counter = 0; if($batch_mode) { // batch mode $small_batch = []; $counter = 0; foreach ($all_rows as $sheet_index => $sheet_content) { foreach ($sheet_content as $row_id => $row_content) { $formatted_info = $this->formatItemInfo($this->convertColToField($row_content)); if(!$formatted_info) continue; $counter += 1; $small_batch[] = $formatted_info; if($counter % $batch_size == 0) { $success_row_counter += $this->processBatchItems($small_batch); // reset $counter = 0; $small_batch = []; } } } // process the remain if(sizeof($small_batch)) { $success_row_counter += $this->processBatchItems($small_batch); } } else { // single item mode foreach ($all_rows as $sheet_index => $sheet_content) { foreach ($sheet_content as $row_index => $row_content) { $formatted_info = $this->formatItemInfo($this->convertColToField($row_content)); if(!$formatted_info) continue; if($this->processItem($formatted_info, $row_index)){ $success_row_counter += 1; } } } } //unset($all_rows); return [ 'status' => 'success', 'message' => '', 'success_counter' => $success_row_counter, ]; } protected function convertExcelDateValue($value) { // Date value in Excel's cell can be displayed as text or date value, we need to check for both $format_value = $this->formatExcelUploadDate($value); if(!$format_value) { // check if it's excel date try { $format_value = (is_numeric($value)) ? date("d-m-Y", Date::excelToTimestamp($value, date_default_timezone_get())) : ''; }catch (\Exception $e) { $format_value = ''; } } return $format_value; } protected function formatExcelUploadDate($input_date) { $check_date_pattern = "/\d{1,2}-\d{1,2}-\d{4}/i"; $format_date = str_replace("/", "-", $input_date); if(preg_match($check_date_pattern, $format_date)) { return date("d-m-Y", strtotime($format_date)); } return null; } protected function convertExcelHourMinuteValue($value) { $format_value = $this->formatExcelUploadHourMinute($value); if(!$format_value) { // check if it's excel date try { $format_value = (is_numeric($value)) ? date("H:i", Date::excelToTimestamp($value, date_default_timezone_get())) : ''; }catch (\Exception $e) { $format_value = ''; } } return $format_value; } protected function formatExcelUploadHourMinute($input_date) { $check_date_pattern = "/\d{1,2}:\d{1,2}/i"; //$format_date = str_replace("/", "-", $input_date); if(preg_match($check_date_pattern, $input_date)) { return date("H:i", strtotime($input_date)); } return null; } // ['A' => '12', 'B' => 'ten sp'] => ['id' => '12', 'product_name' => 'ten sp'] protected function convertColToField(array $row_content ) { if(!$this->field_column_mappings) { return array_values($row_content); } $item_info = []; foreach ($this->field_column_mappings as $field => $col) { //if(!isset($row_content[$col])) continue; $item_info[$field] = $row_content[$col]; } return $item_info; } // abstract methods protected function beforeProcessRows(array &$all_read_rows){ // default nothing // derived class should overwrite this method } abstract protected function processItem(array $item_info, $row_index=0); abstract protected function processBatchItems(array $item_list); /** * @param array $item_info * @return array|null */ abstract protected function formatItemInfoBeforeProcess(array $item_info); public function registerFormatItemInfoMiddleware($middleware_ojb) { $this->format_item_middlewares[] = $middleware_ojb; } protected $date_fields = []; public function setDateFields(array $date_fields) { $this->date_fields = $date_fields; } protected $hour_minute_fields = []; public function setHourMinuteFields(array $hour_minute_fields) { $this->hour_minute_fields = $hour_minute_fields; } /** * @param array $item_info * @return array|null */ protected function formatItemInfo(array $item_info) { $copy = $item_info; // apply middleware if (sizeof($this->format_item_middlewares)) { foreach ($this->format_item_middlewares as $_middleware) { $copy = call_user_func($_middleware, $copy); } } foreach ($this->date_fields as $field) { $copy[$field] = $this->convertExcelDateValue($item_info[$field]); } foreach ($this->hour_minute_fields as $field) { $copy[$field] = $this->convertExcelHourMinuteValue($item_info[$field]); } return $this->formatItemInfoBeforeProcess($copy); } }