diff --git a/modules/backend/behaviors/ImportExportController.php b/modules/backend/behaviors/ImportExportController.php index 7e83b3ab0..72d407c83 100644 --- a/modules/backend/behaviors/ImportExportController.php +++ b/modules/backend/behaviors/ImportExportController.php @@ -2,7 +2,7 @@ use Str; use Backend\Classes\ControllerBehavior; -use League\Csv\Writer as CsvWrtier; +use League\Csv\Writer as CsvWriter; use League\Csv\Reader as CsvReader; use ApplicationException; use Exception; @@ -86,7 +86,22 @@ class ImportExportController extends ControllerBehavior public function onImport() { - // traceLog(post()); + try { + $model = $this->importGetModel(); + $matches = post('column_match', []); + $sessionKey = $this->importUploadFormWidget->getSessionKey(); + + $model->importDataFromColumnMatch($matches, $sessionKey, [ + 'firstRowTitles' => post('first_row_titles', false) + ]); + + $this->vars['importResults'] = $model->getResultStats(); + } + catch (Exception $ex) { + $this->controller->handleError($ex); + } + + return $this->importExportMakePartial('import_result_form'); } public function onImportLoadForm() @@ -151,7 +166,7 @@ class ImportExportController extends ControllerBehavior $this->vars['importDbColumns'] = $this->getImportDbColumns(); $this->vars['importFileColumns'] = $this->getImportFileColumns(); - // Make these variables to widgets + // Make these variables available to widgets $this->controller->vars += $this->vars; } @@ -214,17 +229,9 @@ class ImportExportController extends ControllerBehavior protected function getImportFilePath() { - $model = $this->importGetModel(); - $file = $model - ->import_file() - ->withDeferred($this->importUploadFormWidget->getSessionKey()) - ->first(); - - if (!$file) { - return null; - } - - return $file->getLocalPath(); + return $this + ->importGetModel() + ->getImportFilePath($this->importUploadFormWidget->getSessionKey()); } public function importIsColumnRequired($columnName) diff --git a/modules/backend/behaviors/importexportcontroller/assets/js/october.import.js b/modules/backend/behaviors/importexportcontroller/assets/js/october.import.js index 2e1b1ffe1..0e863db55 100644 --- a/modules/backend/behaviors/importexportcontroller/assets/js/october.import.js +++ b/modules/backend/behaviors/importexportcontroller/assets/js/october.import.js @@ -101,7 +101,12 @@ this.processImport = function () { var $form = $('#importFileColumns').closest('form') - $form.request('onImport') + $form.request('onImport', { + success: function(data) { + $('#importContainer').html(data.result) + $(document).trigger('render') + } + }) } } diff --git a/modules/backend/behaviors/importexportcontroller/partials/_import_result_form.htm b/modules/backend/behaviors/importexportcontroller/partials/_import_result_form.htm new file mode 100644 index 000000000..018653e60 --- /dev/null +++ b/modules/backend/behaviors/importexportcontroller/partials/_import_result_form.htm @@ -0,0 +1,102 @@ +fatalError): ?> + + + + + + + + + + \ No newline at end of file diff --git a/modules/backend/lang/en/lang.php b/modules/backend/lang/en/lang.php index 1a76e8a8c..b470b2c41 100644 --- a/modules/backend/lang/en/lang.php +++ b/modules/backend/lang/en/lang.php @@ -187,6 +187,7 @@ return [ 'close' => 'Close', 'confirm' => 'Confirm', 'reload' => 'Reload', + 'complete' => 'Complete', 'ok' => 'OK', 'or' => 'or', 'confirm_tab_close' => 'Do you really want to close the tab? Unsaved changes will be lost.', diff --git a/modules/backend/models/ImportModel.php b/modules/backend/models/ImportModel.php index c62c44172..3d45f1ffb 100644 --- a/modules/backend/models/ImportModel.php +++ b/modules/backend/models/ImportModel.php @@ -1,6 +1,7 @@ ['System\Models\File'] ]; + protected $resultStats = [ + 'updated' => 0, + 'created' => 0, + 'errors' => [], + 'warnings' => [], + 'skipped' => [] + ]; + /** * Called when data is being imported. */ - abstract public function importData(); + abstract public function importData($results, $sessionKey = null); + public function importDataFromColumnMatch($matches, $sessionKey = null, $options = []) + { + $path = $this->getImportFilePath($sessionKey); + $data = $this->processImportData($path, $matches, $options); + return $this->importData($data, $sessionKey); + } + + /** + * Converts column index to database column map to an array containing + * database column names and values pulled from the CSV file. + * Eg: + * [0 => [first_name], 1 => [last_name]] + * + * Will return: + * [first_name => Joe, last_name => Blogs], + * [first_name => Harry, last_name => Potter], + * [...] + * + * @return array + */ + protected function processImportData($filePath, $matches, $options) + { + extract(array_merge([ + 'firstRowTitles' => true + ], $options)); + + $reader = CsvReader::createFromPath($filePath); + + if ($firstRowTitles) { + $reader->setOffset(1); + } + + $result = []; + $contents = $reader->fetchAll(); + foreach ($contents as $row) { + $result[] = $this->processImportRow($row, $matches); + } + + return $result; + } + + /** + * Converts a single row of CSV data to the column map. + * @return array + */ + protected function processImportRow($rowData, $matches) + { + $newRow = []; + + foreach ($matches as $columnIndex => $dbNames) { + $value = array_get($rowData, $columnIndex); + foreach ((array) $dbNames as $dbName) { + $newRow[$dbName] = $value; + } + } + + return $newRow; + } + + /** + * Returns an attached imported file local path, if available. + * @return string + */ + public function getImportFilePath($sessionKey = null) + { + $file = $this + ->import_file() + ->withDeferred($sessionKey) + ->first(); + + if (!$file) { + return null; + } + + return $file->getLocalPath(); + } + + // + // Result logging + // + + public function getResultStats() + { + $this->resultStats['errorCount'] = count($this->resultStats['errors']); + $this->resultStats['warningCount'] = count($this->resultStats['warnings']); + $this->resultStats['skippedCount'] = count($this->resultStats['skipped']); + + $this->resultStats['hasMessages'] = ( + $this->resultStats['errorCount'] > 0 || + $this->resultStats['warningCount'] > 0 || + $this->resultStats['skippedCount'] > 0 + ); + + return (object) $this->resultStats; + } + + protected function logUpdated() + { + $this->resultStats['updated']++; + } + + protected function logCreated() + { + $this->resultStats['created']++; + } + + protected function logError($rowIndex, $message) + { + $this->resultStats['errors'][$rowIndex] = $message; + } + + protected function logWarning($rowIndex, $message) + { + $this->resultStats['warnings'][$rowIndex] = $message; + } + + protected function logSkipped($rowIndex, $message) + { + $this->resultStats['skipped'][$rowIndex] = $message; + } } \ No newline at end of file