mirror of
https://github.com/wintercms/winter.git
synced 2024-06-28 05:33:29 +02:00
Separation of concerns, the language file shouldn't really be treated as a data source for the supported encoding options.
272 lines
6.7 KiB
PHP
272 lines
6.7 KiB
PHP
<?php namespace Backend\Models;
|
|
|
|
use Str;
|
|
use Lang;
|
|
use Model;
|
|
use League\Csv\Reader as CsvReader;
|
|
|
|
/**
|
|
* Model used for importing data
|
|
*
|
|
* @package october\backend
|
|
* @author Alexey Bobkov, Samuel Georges
|
|
*/
|
|
abstract class ImportModel extends Model
|
|
{
|
|
use \October\Rain\Database\Traits\Validation;
|
|
|
|
/**
|
|
* The attributes that aren't mass assignable.
|
|
* @var array
|
|
*/
|
|
protected $guarded = [];
|
|
|
|
/**
|
|
* Relations
|
|
*/
|
|
public $attachOne = [
|
|
'import_file' => ['System\Models\File']
|
|
];
|
|
|
|
/**
|
|
* @var array Import statistics store.
|
|
*/
|
|
protected $resultStats = [
|
|
'updated' => 0,
|
|
'created' => 0,
|
|
'errors' => [],
|
|
'warnings' => [],
|
|
'skipped' => []
|
|
];
|
|
|
|
/**
|
|
* Called when data is being imported.
|
|
* The $results array should be in the format of:
|
|
*
|
|
* [
|
|
* 'db_name1' => 'Some value',
|
|
* 'db_name2' => 'Another value'
|
|
* ],
|
|
* [...]
|
|
*
|
|
*/
|
|
abstract public function importData($results, $sessionKey = null);
|
|
|
|
/**
|
|
* Import data based on column names matching header indexes in the CSV.
|
|
* The $matches array should be in the format of:
|
|
*
|
|
* [
|
|
* 0 => [db_name1, db_name2],
|
|
* 1 => [db_name3],
|
|
* ...
|
|
* ]
|
|
*
|
|
* The key (0, 1) is the column index in the CSV and the value
|
|
* is another array of target database column names.
|
|
*/
|
|
public function import($matches, $options = [])
|
|
{
|
|
$sessionKey = array_get($options, 'sessionKey');
|
|
$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)
|
|
{
|
|
/*
|
|
* Parse options
|
|
*/
|
|
$defaultOptions = [
|
|
'firstRowTitles' => true,
|
|
'delimiter' => null,
|
|
'enclosure' => null,
|
|
'escape' => null
|
|
];
|
|
|
|
$options = array_merge($defaultOptions, $options);
|
|
|
|
/*
|
|
* Read CSV
|
|
*/
|
|
$reader = CsvReader::createFromPath($filePath, 'r');
|
|
|
|
// Filter out empty rows
|
|
$reader->addFilter(function(array $row) {
|
|
return count($row) > 1 || reset($row) !== null;
|
|
});
|
|
|
|
if ($options['delimiter'] !== null) {
|
|
$reader->setDelimiter($options['delimiter']);
|
|
}
|
|
|
|
if ($options['enclosure'] !== null) {
|
|
$reader->setEnclosure($options['enclosure']);
|
|
}
|
|
|
|
if ($options['escape'] !== null) {
|
|
$reader->setEscape($options['escape']);
|
|
}
|
|
|
|
if ($options['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;
|
|
}
|
|
|
|
/**
|
|
* Explodes a string using pipes (|) to a single dimension array
|
|
* @return array
|
|
*/
|
|
protected function decodeArrayValue($value, $delimeter = '|')
|
|
{
|
|
if (strpos($value, $delimeter) === false) return [$value];
|
|
|
|
$data = preg_split('~(?<!\\\)' . preg_quote($delimeter, '~') . '~', $value);
|
|
$newData = [];
|
|
|
|
foreach ($data as $_value) {
|
|
$newData[] = str_replace('\\'.$delimeter, $delimeter, $_value);
|
|
}
|
|
|
|
return $newData;
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
|
|
/**
|
|
* Returns all available encodings values from the localization config
|
|
* @return array
|
|
*/
|
|
public function getFormatEncodingOptions()
|
|
{
|
|
$options = [
|
|
'utf-8',
|
|
'us-ascii',
|
|
'iso-8859-1',
|
|
'iso-8859-2',
|
|
'iso-8859-3',
|
|
'iso-8859-4',
|
|
'iso-8859-5',
|
|
'iso-8859-6',
|
|
'iso-8859-7',
|
|
'iso-8859-8',
|
|
'iso-8859-0',
|
|
'iso-8859-10',
|
|
'iso-8859-11',
|
|
'iso-8859-13',
|
|
'iso-8859-14',
|
|
'iso-8859-15',
|
|
'Windows-1251',
|
|
'Windows-1252'
|
|
];
|
|
|
|
$translated = array_map(function($option){
|
|
return Lang::get('backend::lang.import_export.encodings.'.Str::slug($option, '_'));
|
|
}, $options);
|
|
|
|
return array_combine($options, $translated);
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
}
|