'csv',
],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'file' => Yii::t('product', 'File'),
];
}
/**
* Get import type
*
* @see Import::type
* @return string
*/
public function getType()
{
if (!$this->type) {
$this->type = 'products';
}
return $this->type;
}
/**
* Import prices
*
* @param int $from Start row
* @param null $limit Row limit
*
* @return array|bool Array if OK, false otherwise
*/
public function goPrices($from = 0, $limit = null)
{
set_time_limit(0);
if (!( $handle = $this->getProductsFile('uploadFilePrices') )) {
$this->errors[] = 'File not found';
return false;
}
$filesize = filesize(Yii::getAlias('@uploadDir') . '/' . Yii::getAlias('@uploadFilePrices'));
if ($from) {
fseek($handle, $from);
}
$j = 0;
$is_utf = ( preg_match(
'//u',
file_get_contents(
Yii::getAlias('@uploadDir') . '/' . Yii::getAlias('@uploadFilePrices'),
null,
null,
null,
1000000
)
) );
while (( empty( $limit ) || $j++ < $limit ) && ( $data = fgetcsv($handle, 10000, ";") ) !== false) {
foreach ($data as &$value) {
if (!$is_utf) {
$value = iconv('windows-1251', "UTF-8//TRANSLIT//IGNORE", $value);
}
$value = trim($value);
}
// данные строк
$modification_code = @$data[ 0 ];
$price = floatval(@$data[ 1 ]);
$price_promo = floatval(@$data[ 2 ]);
$count = intval(@$data[ 3 ]);
$city_name = @$data[ 4 ];
$product_title = @$data[ 5 ];
if (empty ( $modification_code )) {
continue;
}
// товары в пути
// if (empty ( $city_name )) {
// $this->output[] = 'Товар ' . $product_title . ' в пути';
// continue;
// }
/**
* @var ProductVariant $productVariant
*/
if (( $productVariant = ProductVariant::find()
->filterWhere([ 'sku' => $modification_code ])
->one() ) === null
) {
$this->output[] = 'Для товара ' . $product_title . ' не найдено соотвествие';
continue;
}
// ===== Set stock ====
if ($city_name) {
if (( $stock = Stock::find()
->filterWhere([ 'stock.title' => trim($city_name) ])
->one() ) === null
) {
// Create stock
$stock = new Stock();
$stock->title = trim($city_name);
$stock->save(false);
}
$productStock = ProductStock::find()
->where(
[
'product_variant_id' => $productVariant->id,
'stock_id' => $stock->id,
]
)
->one();
if (!$productStock instanceof ProductStock) {
$productStock = new ProductStock();
$productStock->product_variant_id = $productVariant->id;
$productStock->stock_id = $stock->id;
}
$productStock->quantity = $count;
$productStock->save(false);
$productStocks = ProductStock::find()
->where(
[ 'product_variant_id' => $productVariant->id ]
)
->andWhere(
[
'<>',
'stock_id',
$stock->id,
]
)
->all();
$quantity = array_sum(ArrayHelper::getColumn($productStocks, 'quantity')) + $count;
} else {
$productStocks = ProductStock::find()
->where(
[ 'product_variant_id' => $productVariant->id ]
)
->all();
if ($productStocks instanceof ProductStock) {
$quantity = array_sum(ArrayHelper::getColumn($productStocks, 'quantity')) + $count;
} else {
$quantity = 0;
}
}
if ($price_promo) {
$productVariant->price_old = $price;
$productVariant->price = $price_promo;
} else {
$productVariant->price = $price;
$productVariant->price_old = $price_promo;
}
$productVariant->stock = $quantity;
$productVariant->save(false);
$this->output[] = 'Товар ' . $product_title . ' успешно сохранен';
}
$result = [
'end' => feof($handle),
'from' => ftell($handle),
'totalsize' => $filesize,
'items' => $this->output,
];
fclose($handle);
if ($result[ 'end' ]) {
$this->flushCache();
unlink(Yii::getAlias('@uploadDir') . '/' . Yii::getAlias('@uploadFilePrices'));
}
return $result;
}
/**
* Pull name and remote_id from formatted string
*
* @param string $name
*
* @return array
*/
private function parseName(string $name):array
{
$pattern = '/^(?P.*)(?:\(#(?P\w+)#\))?$/U';
$name = trim($name);
$matches = [];
if (preg_match($pattern, $name, $matches)) {
if (!isset( $matches[ 'remote_id' ] )) {
$matches[ 'remote_id' ] = '';
}
return $matches;
}
return [
'name' => $name,
'remote_id' => '',
];
}
/**
* Save categories
*
* @param array $catalog_names
*
* @return int[] Category IDs
* @throws \Exception
*/
private function saveCatalog(array $catalog_names):array
{
$category_id = [];
foreach ($catalog_names as $catalog_name) {
if(preg_match_all('/\[(.*)>(.*)\]/', $catalog_name, $out,PREG_SET_ORDER)){
$count = count($out[0]);
$parent_id = 0;
for($i=1; $i<$count; $i++){
if(isset($out[0][$i])){
// ==== Set category ====
$parsed_name = $this->parseName($out[0][$i]);
if (!empty( $parsed_name[ 'remote_id' ] ) && ( $category = Category::find()
->joinWith('lang')
->andFilterWhere(
[ 'remote_id' => $parsed_name[ 'remote_id' ] ]
)
->one() ) !== null
) {
if (!empty( $category->lang )) {
if($i != 1){
$category->parent_id = $parent_id;
}else{
$parent_id = $category->id;
}
$category->lang->title = $parsed_name[ 'name' ];
$category->lang->save(false);
} else {
throw new \Exception(
'Category with ID ' . $category->id . ' and lang ' . Language::getCurrent(
)->id . ' doesn\'t exist'
);
}
} else {
// Create category
$category = new Category();
$category->generateLangs();
$category_langs = $category->modelLangs;
foreach ($category_langs as $category_lang) {
$category_lang->title = $parsed_name[ 'name' ];
}
if($i != 1){
$category->parent_id = $parent_id;
}
$category->remote_id = $parsed_name[ 'remote_id' ];
$category->save(false);
if($i == 1){
$parent_id = $category->id;
}
}
$category_id[] = $category->id;
}
}
} else if(preg_match_all('/\[(.*)\]/', $catalog_name, $out,PREG_SET_ORDER)){
if(isset($out[0][1])){
// ==== Set category ====
$parsed_name = $this->parseName($out[0][1]);
if (!empty( $parsed_name[ 'remote_id' ] ) && ( $category = Category::find()
->joinWith('lang')
->andFilterWhere(
[ 'remote_id' => $parsed_name[ 'remote_id' ] ]
)
->one() ) !== null
) {
if (!empty( $category->lang )) {
$category->lang->title = $parsed_name[ 'name' ];
$category->lang->save(false);
} else {
throw new \Exception(
'Category with ID ' . $category->id . ' and lang ' . Language::getCurrent(
)->id . ' doesn\'t exist'
);
}
} else {
// Create category
$category = new Category();
$category->generateLangs();
$category_langs = $category->modelLangs;
foreach ($category_langs as $category_lang) {
$category_lang->title = $parsed_name[ 'name' ];
}
$category->remote_id = $parsed_name[ 'remote_id' ];
$category->save(false);
}
$category_id[] = $category->id;
}
} else {
throw new \Exception(
'Wrong category format!'
);
}
}
return $category_id;
}
/**
* Save brand
*
* @param string|null $brand_name
*
* @return int|null New Brand ID if inserted or exist or null if skipped
* @throws \Exception
*/
private function saveBrand(string $brand_name = null):int
{
$parsed_name = $this->parseName($brand_name);
if (!empty( $brand_name )) {
/**
* @var Brand $brand
*/
if (!empty( $parsed_name[ 'remote_id' ] ) && ( $brand = Brand::find()
->joinWith('lang')
->andFilterWhere(
[ 'remote_id' => $parsed_name[ 'remote_id' ] ]
)
->one() ) !== null
) {
if (!empty( $brand->lang )) {
$brand->lang->title = $parsed_name[ 'name' ];
$brand->lang->save(false);
} else {
throw new \Exception(
'Brand with ID ' . $brand->id . ' and lang ' . Language::getCurrent(
)->id . ' doesn\'t exist'
);
}
return $brand->id;
} else {
// Create brand
$brand = new Brand();
$brand->generateLangs();
$brand_langs = $brand->modelLangs;
foreach ($brand_langs as $brand_lang) {
$brand_lang->title = $parsed_name[ 'name' ];
}
$brand->remote_id = $parsed_name[ 'remote_id' ];
$brand->save(false);
return $brand->id;
}
}
return null;
}
/**
* Save Product or ProductVariant photoes
*
* @param string[] $fotos Photoes names
* @param int $product_id
* @param int|null $product_variant_id Null if photo for Product
*/
private function saveFotos(array $fotos, int $product_id, int $product_variant_id = null)
{
if (!empty( $fotos )) {
foreach ($fotos as $foto) {
if (empty( $foto )) {
continue;
}
$source_image = Yii::getAlias('@uploadDir') . '/product_images/' . urlencode($foto);
if (file_exists($source_image)) {
if (( $productImage = ProductImage::find()
->andWhere([ 'image' => $foto ])
->andWhere([ 'product_id' => $product_id ])
->andFilterWhere(
[ 'product_variant_id' => $product_variant_id ]
)
->one() ) === null
) {
copy($source_image, Yii::getAlias('@productsDir') . "/" . $foto);
$productImage = new ProductImage();
$productImage->product_id = $product_id;
$productImage->product_variant_id = $product_variant_id;
$productImage->image = $foto;
$productImage->save(false);
}
}
}
}
}
/**
* Save ProductVariants
*
* @param array $data ProductVariats data
* @param float $product_cost_old Old price
* @param int $product_id Product ID
* @param array $category_id Ca
* @param float|null $product_cost
*
* @return int[] Array of ProductVariants IDs
* @throws \Exception
*/
private function saveVariants(
array $data,
float $product_cost_old,
int $product_id,
array $category_id,
float $product_cost = null
):array
{
$MOD_ARRAY = [];
for ($i = 13; $i < count($data); $i++) {
if (!empty ( $data[ $i ] )) {
$mod_arr = explode('=', $data[ $i ]);
$mod_art = $mod_arr[ 0 ];
$mod_art_parsed = $this->parseName($mod_art);
$variant_filters = explode('*', $mod_arr[ 1 ]);
$mod_name = $mod_arr[ 2 ];
if (empty( $mod_name )) {
$mod_name = $mod_art_parsed[ 'name' ];
}
$mod_image = $mod_arr[ 3 ];
$mod_stock = isset( $mod_arr[ 4 ] ) ? $mod_arr[ 4 ] : 1;
$mod_cost = isset( $product_cost ) ? floatval($product_cost) : 0;
$mod_old_cost = floatval($product_cost_old);
// Check product variant
/**
* @var ProductVariant $_productVariant
*/
if (( $_productVariant = ProductVariant::find()
->joinWith('lang')
->andFilterWhere(
[ 'remote_id' => $mod_art_parsed[ 'remote_id' ] ]
)
->andFilterWhere(
[ 'product_variant.product_id' => $product_id ]
)
->one() ) === null
) {
$_productVariant = new ProductVariant();
$_productVariant->product_id = $product_id;
if (!empty( $mod_art_parsed[ 'remote_id' ] )) {
$_productVariant->remote_id = $mod_art_parsed[ 'remote_id' ];
}
$_productVariant->generateLangs();
$product_variant_langs = $_productVariant->modelLangs;
foreach ($product_variant_langs as $product_variant_lang) {
$product_variant_lang->title = $mod_name;
}
} else {
if (!empty( $_productVariant->lang )) {
$_productVariant->lang->title = $mod_name;
$_productVariant->lang->save(false);
} else {
throw new \Exception(
'Product variant with ID ' . $_productVariant->id . ' and lang ' . Language::getCurrent(
)->id . ' doesn\'t exist'
);
}
}
$_productVariant->product_unit_id = 1;
$_productVariant->sku = $mod_art_parsed[ 'name' ];
$_productVariant->price = $mod_cost;
$_productVariant->price_old = $mod_old_cost;
$_productVariant->stock = $mod_stock;
if (!empty ( $variant_filters )) {
$variants_options = $this->saveFilters($variant_filters, 1, $category_id);
}
if (isset( $variants_options ) && !empty( $variants_options )) {
$_productVariant->options = $variants_options;
}
/**
* @todo set to false
*/
$_productVariant->save(false);
$MOD_ARRAY[] = $_productVariant->id;
$this->saveFotos([ $mod_image ], $product_id, $_productVariant->id);
}
}
return $MOD_ARRAY;
}
/**
* Perform product import
*
* @param int $from Begin row
* @param null $limit Row limit
*
* @return array|bool Array if OK, false if error
*/
public function goProducts($from = 0, $limit = null)
{
set_time_limit(0);
if (!( $handle = $this->getProductsFile('uploadFileProducts') )) {
$this->errors[] = 'File not found';
return false;
}
$filesize = filesize(Yii::getAlias('@uploadDir') . '/' . Yii::getAlias('@uploadFileProducts'));
if ($from) {
fseek($handle, $from);
}
$j = 0;
$is_utf = ( preg_match(
'//u',
file_get_contents(
Yii::getAlias('@uploadDir') . '/' . Yii::getAlias('@uploadFileProducts'),
null,
null,
null,
1000000
)
) );
$result_items = [];
$connection = Yii::$app->getDb();
$connection->createCommand()->dropForeignKey('product_option_tax_option_tax_option_id_fk','product_option')->execute();
$connection->createCommand()->dropForeignKey('product_option_product_product_id_fk','product_option')->execute();
$connection->createCommand()->dropForeignKey('product_variant_option_tax_option_tax_option_id_fk','product_variant_option')->execute();
$connection->createCommand()->dropForeignKey('product_variant_option_product_variant_product_variant_id_fk','product_variant_option')->execute();
$connection->createCommand()->dropPrimaryKey('product_option_pkey','product_option')->execute();
$connection->createCommand()->dropForeignKey('product_variant_option_pkey','product_variant_option')->execute();
while (( empty( $limit ) || $j++ < $limit ) && ( $data = fgetcsv($handle, 10000, ";") ) !== false) {
try {
foreach ($data as &$value) {
if (!$is_utf) {
$value = iconv('windows-1251', "UTF-8//TRANSLIT//IGNORE", $value);
}
$value = trim($value);
}
// будет всегда 19 элементов
for ($i = 0; $i <= 18; $i++) {
if (!isset ( $data[ $i ] )) {
$data[ $i ] = null;
}
}
// 1 Группа (категория)
$catalog_names = explode(',', $data[ 0 ]);
if (empty ( $catalog_names )) {
$result_items[] = "Не указана категория (строка $j)";
continue;
}
// 2 Бренд
$brand_name = $data[ 1 ];
// if(empty ( $brand_name )) {
// $result_items[] = "Не указан бренд (строка $j)";
// continue;
// }
// 3 Название товара
$product_name = $data[ 2 ];
if (empty ( $product_name )) {
$result_items[] = "Не указано наименование товара (строка $j)";
continue;
}
// 5 Описание товара
$product_body = $data[ 3 ];
// 6 Фильтр
$filters = explode('*', $data[ 4 ]);
// 11 Цена акция
$product_cost_old = floatval($data[ 6 ]);
$product_cost = null;
// 10 Цена
if ($product_cost_old) {
$product_cost_old = floatval($data[ 5 ]);
$product_cost = floatval($data[ 6 ]);
}
// 12 Акция
$product_discount = (bool) $data[ 7 ];
// 13 Сопуд. Тов.
$similar = explode(',', $data[ 8 ]);
// 14 Новинки
$product_new = (bool) $data[ 9 ];
// 15 Топ продаж
$product_top = (bool) $data[ 10 ];
// 17 ВИДЕО КОД
$product_video = $data[ 11 ];
// 18 Галлерея фото
$fotos = [];
if (trim($data[ 12 ])) {
$fotos = explode(',', trim($data[ 12 ]));
}
$categories = $this->saveCatalog($catalog_names);
$brand_id = $this->saveBrand($brand_name);
$options = [];
if (!empty ( $filters )) {
$options = $this->saveFilters($filters, 0, $categories);
}
$parsed_name = $this->parseName($product_name);
/**
* @var Product $_product
*/
if (!empty( $parsed_name[ 'remote_id' ] ) && ( $_product = Product::find()
->joinWith('lang')
->andFilterWhere(
[ 'remote_id' => $parsed_name[ 'remote_id' ] ]
)
->one() ) !== null
) {
if (!empty( $_product->lang )) {
$_product->lang->title = $parsed_name[ 'name' ];
$_product->lang->description = $product_body;
$_product->lang->save(false);
} else {
throw new \Exception(
'Product with ID ' . $_product->id . ' and lang ' . Language::getCurrent(
)->id . ' doesn\'t exist'
);
}
} else {
$_product = new Product();
$_product->generateLangs();
$product_langs = $_product->modelLangs;
foreach ($product_langs as $product_lang) {
$product_lang->title = $parsed_name[ 'name' ];
$product_lang->description = $product_body;
}
}
$is_new_product = empty( $_product->id );
$_product->categories = $categories;
$_product->brand_id = $brand_id;
$_product->video = $product_video;
$_product->is_top = $product_top;
$_product->is_discount = $product_discount;
$_product->is_new = $product_new;
if (!empty( $options )) {
$_product->options = $options;
}
if (!empty( $_product->lang )) {
$product_name_inserted = $_product->lang->title;
} else {
$product_name_inserted = $_product->modelLangs[ Language::$current->id ]->title;
}
if (( $_product->save(false) === false ) || !$_product->transactionStatus) {
$result_items[] = 'Product #' . $product_name_inserted . ' not saved' . " (line $j)";
continue;
}
$this->saveFotos($fotos, $_product->id);
// нужно для проставления характеристик относящихся к модификациям
$this->saveVariants($data, $product_cost_old, $_product->id, $_product->categories, $product_cost);
// $_product->save(false);
$result_items[] = "Product {$product_name_inserted} #{$_product->id} saved (" . ( $is_new_product ? 'new product' : 'exists product' ) . ")" . " (line $j)";
} catch (\Exception $e) {
$result_items[] = $e->getMessage() . '(line ' . $j . ')';
}
}
$connection->createCommand()->addPrimaryKey('product_variant_option_pkey','product_variant_option',['product_variant_id', 'option_id'])->execute();
$connection->createCommand()->addPrimaryKey('product_option_pkey','product_option',['product_id', 'option_id'])->execute();
$connection->createCommand()->addForeignKey('product_variant_option_product_variant_product_variant_id_fk','product_variant_option','product_variant_id','product_variant','id')->execute();
$connection->createCommand()->addForeignKey('product_variant_option_tax_option_tax_option_id_fk','product_variant_option','option_id','tax_option','id')->execute();
$connection->createCommand()->addForeignKey('product_option_product_product_id_fk','product_option','product_id','product','id','CASCADE','CASCADE' )->execute();
$connection->createCommand()->addForeignKey('product_option_tax_option_tax_option_id_fk','product_option','option_id', 'tax_option', 'id','CASCADE','CASCADE' )->execute();
$result = [
'end' => feof($handle),
'from' => ftell($handle),
'totalsize' => $filesize,
'items' => $result_items,
];
fclose($handle);
if ($result[ 'end' ]) {
$this->flushCache();
// unlink(Yii::getAlias('@uploadDir') . '/' . Yii::getAlias('@uploadFileProducts'));
}
return $result;
}
/**
* Get import file
*
* @param string $file_type
*
* @return bool|resource false if File not found and file resource if OK
*/
private function getProductsFile($file_type)
{
$filename = Yii::getAlias('@uploadDir') . '/' . Yii::getAlias('@' . $file_type);
if (!is_file($filename)) {
$this->errors[] = "File $filename not found";
return false;
}
return fopen($filename, 'r');
}
/**
* Save filters
*
* @param array $filters array of filters like [['pol'='мужской'],['god' =
* '2013'],['volume'='25 л']*['size'='49 x 30 x
* 20см'],['composition'='600D полиэстер']]
* @param int $level 0 for products and 1 for product variant
* @param int[] $catalog_names array catalogs id
*
* @return array
* @throws \Exception
*/
private function saveFilters(array $filters, int $level, array $catalog_names):array
{
$options = [];
foreach ($filters as $filter) {
preg_match_all('/\[(.*):(.*)\]/', $filter, $filter);
if (empty( $filter[ 1 ][ 0 ] )) {
continue;
}
$filter_name = trim($filter[ 1 ][ 0 ]);
$parsed_group_name = $this->parseName($filter_name);
/**
* @var TaxGroup $taxGroup
*/
if (!empty( $parsed_group_name[ 'remote_id' ] ) && ( $taxGroup = TaxGroup::find()
->joinWith('lang')
->andFilterWhere(
[ 'remote_id' => $parsed_group_name[ 'remote_id' ] ]
)
->one() ) !== null
) {
if (!empty( $taxGroup->lang )) {
$taxGroup->lang->title = $parsed_group_name[ 'name' ];
$taxGroup->lang->save(false);
} else {
throw new \Exception(
'Tax group with ID ' . $taxGroup->id . ' and lang ' . Language::getCurrent()->id . ' doesn\'t exist'
);
}
} else {
$taxGroup = new TaxGroup();
$taxGroup->generateLangs();
$tax_group_langs = $taxGroup->modelLangs;
foreach ($tax_group_langs as $tax_group_lang) {
$tax_group_lang->title = $parsed_group_name[ 'name' ];
}
$taxGroup->level = $level;
$taxGroup->categories = $catalog_names;
$taxGroup->is_filter = false;
$taxGroup->save(false);
}
$filters_options = explode(',', $filter[ 2 ][ 0 ]);
foreach ($filters_options as $filter_options) {
$parsed_option_name = $this->parseName($filter_options);
/**
* @var TaxOption $option
*/
if (!empty( $parsed_option_name[ 'remote_id' ] ) && ( $option = TaxOption::find()
->joinWith('lang')
->andFilterWhere(
[ 'remote_id' => $parsed_option_name[ 'remote_id' ] ]
)
->andFilterWhere(
[ 'tax_group_id' => $taxGroup->id ]
)
->one() ) !== null
) {
if (!empty( $option->lang )) {
$option->lang->value = $parsed_option_name[ 'name' ];
$option->lang->save(false);
} else {
throw new \Exception(
'Tax option with ID ' . $option->id . ' and lang ' . Language::getCurrent(
)->id . ' doesn\'t exist'
);
}
} else {
// Create option
$option = new TaxOption();
$option->generateLangs();
$option_langs = $option->modelLangs;
foreach ($option_langs as $option_lang) {
$option_lang->value = $parsed_option_name[ 'name' ];
}
$option->id = $taxGroup->id;
$option->save(false);
}
$options[] = $option->id;
}
}
return $options;
}
/**
* Flush cache
*
* @param string $component
*
* @return bool
*/
protected function flushCache(string $component = 'cache')
{
/**
* @var Cache $cache
*/
$cache = \Yii::$app->get($component, false);
if(!empty($cache)) {
return $cache->flush();
}
}
}