356 lines
10 KiB
PHP
356 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Hura8\System\Controller;
|
|
|
|
use Hura8\System\ReadExcel;
|
|
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
|
|
|
|
|
abstract class aExcelUploadController
|
|
{
|
|
|
|
protected $update_option = [];
|
|
|
|
protected static $column_names = [
|
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
|
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
|
//...
|
|
];
|
|
|
|
protected $file_input_name = "file_excel"; //
|
|
|
|
protected $client_excel_col_config = [
|
|
/*"A" => 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);
|
|
}
|
|
|
|
}
|