modImporter. Настройка импорта в minishop2 из XLSX
В продолжение прошлой статьи, предлагаю разобрать очередной пример импорта в минишоп, на этот раз из Экселя (XLSX). В этот раз не будет картинок и прочих деталей (их выше крыше было в прошлой статье), а просто рассмотрим конечный код процессора. Так меньше по объему и наверняка так будет многим более понятно.
И так, не считая контроллера в пару строк, при настройке импорта мы создаем и используем только один индивидуальный процессор. Собственно, вот именно этот процессор сможет каждый для себя использовать при импорте в минишоп из экселя. Все, что потребуется — изменить параметры шаблонов категорий и товаров, и корневого раздела каталога, а так же подправить поля-колонки.
Уточню: этот импортер рассчитан на два уровня вложенности: Корневой раздел -> Категория -> Товар. Для многоуровневой вложенности его придется чуть-чуть допилить.
Код процессора:
И так, не считая контроллера в пару строк, при настройке импорта мы создаем и используем только один индивидуальный процессор. Собственно, вот именно этот процессор сможет каждый для себя использовать при импорте в минишоп из экселя. Все, что потребуется — изменить параметры шаблонов категорий и товаров, и корневого раздела каталога, а так же подправить поля-колонки.
Уточню: этот импортер рассчитан на два уровня вложенности: Корневой раздел -> Категория -> Товар. Для многоуровневой вложенности его придется чуть-чуть допилить.
Код процессора:
<?php
require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/modimporter/import/xlsx/console.class.php';
class modModimporterCustomServersImportConsoleProcessor extends modModimporterImportXlsxConsoleProcessor{
public function initialize(){
$this->setDefaultProperties(array(
"category_root_id" => 4, // Серверы
"category_template_id" => 5, // Шаблон категории
"good_template_id" => 4, // Шаблон товара
));
return parent::initialize();
}
protected function StepWriteTmpCommercialInfo(){
return $this->nextStep("modimporter_write_tmp_goods", "Начинаем разбор исходных данных", null, xPDO::LOG_LEVEL_WARN);
}
/*
Записываем категории
*/
protected function StepWriteTmpGoods(){
if(!$reader = & $this->getReader()){
return $this->failure('Не был получен ридер');
}
$filename = 'xl/worksheets/sheet1.xml';
if(!$path = $this->getImportPath()){
return $this->failure("Не была получена директория файлов");
}
$schema = array(
"worksheet" => array(
"sheetData" => array(
"row" => array(
"parse" => true,
),
),
),
);
$index = 0;
$skip = 1; // Сколько строчек пропустить (техническая информация)
$limit = $this->getProperty("limit", 0);
$count = 0;
if(!$inserted = (int)$this->getSessionValue("inserted")){
$inserted = 0;
}
$next_step = false;
$result = $reader->read(array(
"file" => $path.$filename,
), function(modImporterXmlReader $reader) use (& $schema, & $index, & $skip, $limit, &$count, &$inserted, &$next_step){
$xmlReader = & $reader->getReader();
$node = $reader->getNodeName($xmlReader);
if(!$reader->isNodeText($xmlReader) && $reader->getSchemaNodeByKey($schema, $node) && $reader->isNode($node, $xmlReader)){
if(isset($schema["parse"]) && $schema["parse"] && $node == 'row'){
if($index + 1 > $skip){
// Счетчик прочтенных элементов
$count++;
// XML-массив строчки из экселя
$xml = $reader->getXMLNode($xmlReader);
# print_r($xml);
$attributes = $xml->attributes();
$columns_data = array(); // Данные колонок
foreach($xml->c as $column_index => $column){
$column_real_index = $column_index + 1; // Для человеков
$column_attributes = $column->attributes();
$value = (string)$column->v;
// Определяем данные колонки в таблице строковой находятся или в самом XML
if(isset($column_attributes['t']) AND $column_attributes['t'] == 's'){
if(!$string_object = $this->getImportObject($value, "shared_string")){
$error = "Не был получен строковый объект для записи. Строка '{$index}' колонка '{$column_real_index}'";
$this->modx->log(xPDO::LOG_LEVEL_ERROR, $error, '', __CLASS__, __FILE__, __LINE__);
return $error;
}
else{
$value = $string_object->tmp_title;
}
}
$columns_data[] = $value;
}
// Если есть данные строчки и артикул, то записываем данные
if(
$columns_data
AND !empty($columns_data[0])
AND $article = trim($columns_data[0])
){
$object = $this->createImportObject($article, array(
"tmp_title" => $article, // Артикул
"tmp_parent" => $columns_data[1], // Категория
"tmp_raw_data" => $columns_data, // Все данные колонок строки
), "product");
# print_r($object->toArray());
if(!$object->save()){
$this->modx->log(2, "Ошибка сохранения записи товара. Строка '{$index}'");
}
}
}
$index++;
return true;
}
}
return true;
});
if($result !== true AND $result !== false){
return $this->failure($result);
}
if($next_step){
return $this->progress("Прочитано {$inserted} строк.", null, xPDO::LOG_LEVEL_DEBUG);
}
return $this->nextStep("modimporter_write_tmp_categories", "Товары успешно записаны");
}
/*
Записываем категории
*/
protected function StepWriteTmpCategories(){
/*
Категории получаем просто выборкой уникальных родителей товаров из временной таблицы
*/
$q = $this->modx->newQuery("modImporterObject");
$q->distinct();
$q->select(array(
"tmp_parent",
));
$q->where(array(
"tmp_object_type" => "product",
));
$s = $q->prepare();
$s->execute();
# print_r($s->errorInfo());
while($row = $s->fetch(PDO::FETCH_ASSOC)){
# print_r($row);
$category = $row['tmp_parent'];
$object = $this->createImportObject($category, array(
"tmp_title" => $category, // Артикул
"tmp_parent" => null,
), "category");
# print_r($object->toArray());
if(!$object->save()){
return "Ошибка сохранения записи категории '{$row['tmp_parent']}'";
}
}
return $this->nextStep("modimporter_import_data", "Категории успешно записаны");
}
/*
Обновляем категории
*/
protected function StepImportUpdateCategories(){
/*
Получаем только те временные данные, для которых есть имеющиеся категории
*/
$category_root_id = $this->getProperty("category_root_id");
$category_template_id = $this->getProperty("category_template_id");
$q = $this->modx->newQuery("modImporterObject");
$q->innerJoin("modResource", "category", "category.parent = {$category_root_id} AND category.template = {$category_template_id} AND category.pagetitle = modImporterObject.tmp_external_key");
$q->where(array(
"tmp_object_type" => "category",
"tmp_processed" => 0,
));
$q->limit(1);
while($tmp_object = $this->modx->getObject("modImporterObject", $q)){
$tmp_object->tmp_processed = 1;
$tmp_object->save();
}
return parent::StepImportUpdateCategories();
}
// Создаем категории
protected function StepImportCreateCategories(){
$category_root_id = $this->getProperty("category_root_id");
$category_template_id = $this->getProperty("category_template_id");
$q = $this->modx->newQuery("modImporterObject");
$q->where(array(
"tmp_object_type" => "category",
"tmp_processed" => 0,
));
$q->limit(1);
while($tmp_object = $this->modx->getObject("modImporterObject", $q)){
$tmp_object->tmp_processed = 1;
$tmp_object->save();
$pagetitle = $tmp_object->tmp_external_key;
$alias = "test-{$pagetitle}";
$data = array(
"pagetitle" => $pagetitle,
"parent" => $category_root_id,
"alias" => $alias,
"template" => $category_template_id,
"class_key" => 'msCategory',
"published" => 1,
"isfolder" => 1,
);
# print_r($data);
#
# return;
$response = $this->modx->runProcessor('resource/create', $data);
if($response->isError()){
$tmp_object->tmp_error = 1;
$tmp_object->tmp_error_msg = json_encode($response->getResponse());
$tmp_object->save();
return $response->getResponse();
}
// else
$object = $response->getObject();
$tmp_object->tmp_resource_id = $object['id'];
$tmp_object->save();
}
return parent::StepImportCreateCategories();
}
/*
Обновляем товары
*/
protected function StepImportUpdateGoods(){
/*
Получаем только те временные данные, для которых есть имеющиеся категории
*/
$good_template_id = $this->getProperty('good_template_id');
$category_root_id = $this->getProperty("category_root_id");
$category_template_id = $this->getProperty("category_template_id");
$q = $this->modx->newQuery("modImporterObject");
$alias = $q->getAlias();
$q->innerJoin("msProductData", "product_data", "product_data.article = {$alias}.tmp_external_key");
$q->innerJoin("msProduct", "product", "product.id = product_data.id");
$q->where(array(
"tmp_object_type" => "product",
"tmp_processed" => 0,
));
$columns = $this->modx->getSelectColumns("msProduct", $tableAlias= 'product', $columnPrefix= '', $columns= array ("id", "class_key"), $exclude= true);
$columns = explode(", ", $columns);
$q->select($columns);
$columns = $this->modx->getSelectColumns("msProductData", $tableAlias= 'product_data', $columnPrefix= '', $columns= array ("id"), $exclude= true);
$columns = explode(", ", $columns);
$q->select($columns);
$q->select(array(
"{$alias}.*",
"product.id as resource_id",
));
$q->limit(1);
$limit = $this->getProperty("limit", 50);
if(!$processed = (int)$this->getSessionValue("goods_updated")){
$processed = 0;
}
while($tmp_object = $this->modx->getObject("modImporterObject", $q)){
$tmp_object->set('tmp_resource_id', $tmp_object->resource_id);
$tmp_object->tmp_processed = 1;
$tmp_object->save();
$data = $tmp_object->toArray();
# print_r($data);
$raw_data = $data['tmp_raw_data'];
// Переопределяем значения обновляемых товаров
$data = array_merge($data, array(
"id" => $data['resource_id'],
"warranty" => $raw_data[4],
"stock" => $raw_data[5],
"price" => $raw_data[6],
));
# print_r($data);
#
# return;
$response = $this->modx->runProcessor('resource/update', $data);
if($response->isError()){
$tmp_object->tmp_error = 1;
$tmp_object->tmp_error_msg = json_encode($response->getResponse());
$this->modx->log(xPDO::LOG_LEVEL_ERROR, print_r($response->getResponse(), 1), '', __CLASS__, __FILE__, __LINE__);
}
else{
$object = $response->getObject();
$tmp_object->tmp_resource_id = $object['id'];
}
$tmp_object->save();
$processed++;
if($limit AND $processed%$limit === 0){
$this->setSessionValue("goods_updated", $processed);
return $this->progress("Обновлено {$processed} товаров");
}
}
return parent::StepImportUpdateGoods();
}
// Создаем товары
protected function StepImportCreateGoods(){
$good_template_id = $this->getProperty('good_template_id');
$category_root_id = $this->getProperty("category_root_id");
$category_template_id = $this->getProperty("category_template_id");
$q = $this->modx->newQuery("modImporterObject");
$alias = $q->getAlias();
$q->innerJoin("modResource", "category", "category.parent = {$category_root_id} AND category.template = {$category_template_id} AND category.pagetitle = modImporterObject.tmp_parent");
$q->where(array(
"tmp_object_type" => "product",
"tmp_processed" => 0,
));
$q->select(array(
"{$alias}.*",
"tmp_external_key as pagetitle",
"tmp_title as longtitle",
"category.id as parent",
));
$q->limit(1);
$limit = $this->getProperty("limit", 100);
if(!$inserted = (int)$this->getSessionValue("goods_inserted")){
$inserted = 0;
}
while($tmp_object = $this->modx->getObject("modImporterObject", $q)){
$tmp_object->tmp_processed = 1;
$tmp_object->save();
$raw_data = $tmp_object->get('tmp_raw_data');
# print_r($raw_data);
$article = $raw_data[0];
$pagetitle = $raw_data[2];
$alias = "{$pagetitle}-{$article}";
$data = array(
"class_key" => 'msProduct',
"published" => 1,
"isfolder" => 0,
"template" => $good_template_id,
"parent" => $tmp_object->parent,
"article" => $article,
"pagetitle" => $pagetitle,
"alias" => $alias,
"longtitle" => $raw_data[3],
"warranty" => $raw_data[4],
"stock" => $raw_data[5],
"price" => $raw_data[6],
);
# $data = array_merge($tmp_object->toArray(), array(
# ));
#
# print_r($data);
#
# return;
$response = $this->modx->runProcessor('resource/create', $data);
if($response->isError()){
$tmp_object->tmp_error = 1;
$tmp_object->tmp_error_msg = json_encode($response->getResponse());
$tmp_object->save();
return $response->getResponse();
}
$object = $response->getObject();
$tmp_object->tmp_resource_id = $object['id'];
$tmp_object->save();
$inserted++;
if($limit AND $inserted%$limit === 0){
$this->setSessionValue("goods_inserted", $inserted);
return $this->progress("Создано {$inserted} новых товаров");
}
# return false;
}
return parent::StepImportCreateGoods();
}
}
return 'modModimporterCustomServersImportConsoleProcessor';
Предлагаю каждому самостоятельно почитать код, а возникающие вопросы писать в комменты. Так мы сосредоточимся на самом важном и не понятном. Комментарии: 9
При повторной загрузке xlsx файла с обновленными параметрами выдает ошибку
Товар не обновляется
Что-то пошло не так... Попробуйте еще разок!
Товары успешно обновлены
Товары успешно сняты с публикации
Тестировал код без корректировок.Товар не обновляется
Антон, добрый вечер. Ваш вопрос был решен?)
Да, В техподдержке вы ответили на все вопросы и решили проблему. Еще раз вам спасибо!
Подскажите, пожалуйста, текущая версия (1.6.3) — рабочая?
Создаю свой расширяющий класс:
И на выходе получаю ошибку:
Создаю свой расширяющий класс:
<?php
require_once dirname(dirname(dirname(dirname(__FILE__)))).'/xlsx/console.class.php';
class modModimporterCustomImportMinishopConsoleProcessor extends modModimporterImportXlsxConsoleProcessor {
public function initialize(){
$this->setDefaultProperties(array(
"category_root_id" => 1,
"category_template_id" => (int) $this->modx->getOption('modimporter.category_template_id'),
"good_template_id" => (int) $this->modx->getOption('modimporter.product_template_id'),
));
return parent::initialize();
}
protected function StepWriteTmpCommercialInfo(){
return $this->nextStep("modimporter_write_tmp_goods", "Тест", null, xPDO::LOG_LEVEL_WARN);
}
}
return 'modModimporterCustomImportMinishopConsoleProcessor';
И на выходе получаю ошибку:
PHP warning: Declaration of modImporterReader::initialize(modProcessor &$processor) should be compatible with modProcessor::initialize()
Да, рабочая, но изначально там было нарушение рекларации, и сейчас просто так это не поправить (есть зависимый функционал).
Позже переделаю, а пока скорее всего поможет в начале своего расширяющего процессора прописать ini_set(«display_errors», 0); или типа того, чтобы подавить ошибки.
Позже переделаю, а пока скорее всего поможет в начале своего расширяющего процессора прописать ini_set(«display_errors», 0); или типа того, чтобы подавить ошибки.
Можно поинтересоваться, для чего были закомментированы следующие строки?
(правда, все равно ничего не импортирует, но это уже другой вопрос :) )
$filename = 'xl/sharedStrings.xml';
# if(!$filename = $this->getProperty('filename')){
# return $this->failure("Не был указан файл");
# }
После раскомментирования, проблема с декларацией исчезла (правда, все равно ничего не импортирует, но это уже другой вопрос :) )
Данные строки прописаны конкретно для разбора XML-а из XLSX-файлов. То есть сначала эвселевский файл распаковывается как обычный архив, потом читается файл с данными (путь всегда один). Если вы именно этот механизм пытаетесь использовать для чего-то другого, скорее всего он не будет работать как надо.
Теперь строчки выше возвращают:
[2017-06-19 10:07:37] (ERROR @ /core/components/modimporter/model/modimporter/reader/modimporterxmlreader.class.php : 126) PHP warning: XMLReader::read(): PK
[2017-06-19 10:07:37] (ERROR @ /core/components/modimporter/model/modimporter/reader/modimporterxmlreader.class.php : 126) PHP warning: XMLReader::read(): ^
Ничего кроме «Что-то пошло не так» я не могу сказать, для этого надо смотреть проект.
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.