'XML soubor',
AutomaticImport::TYPE_XLS => 'Excel XLS soubor',
AutomaticImport::TYPE_CSV => 'CSV (Comma separated values)',
AutomaticImport::TYPE_CycloConnect => 'CycloConnect',
AutomaticImport::TYPE_WinShop => 'WinShop',
AutomaticImport::TYPE_DBF => 'DBF databáze',
AutomaticImport::TYPE_WinoraConnect => 'WinoraConnect',
AutomaticImport::TYPE_AspireConnect => 'AspireConnect',
AutomaticImport::TYPE_ZIP => 'ZIP',
AutomaticImport::TYPE_JSON => 'JSON',
];
public static $translationTypes = [
'products' => ProductsTranslation::class,
'parameters' => ParametersTranslation::class,
];
/**
* AutomaticImport ID.
*
* @var int
*/
public $id;
/**
* String description of error.
*
* @var string
*/
public $error;
/**
* File name of downloaded source file.
*
* @var string
*/
public $sourceFile;
/**
* Parsed and transformed input XML.
*
* @var DOMDocument
*/
public $xml;
public $products = [];
/**
* Whether to only show info about import instead of importing.
*
* @var bool
*/
public $display = false;
public $listOfProducts;
public $updatedCreatedProducts = [];
/**
* Will it delete tempfile?
*
* @var bool
*/
private $clearFlag = false;
/**
* Database data.
*/
protected $id_supplier;
protected $source;
protected $type;
protected $transformation;
protected $add_new;
protected $delete_old;
/**
* Configuration parameters.
*
* @var array{
* transaction?: bool,
* hide_products?: bool,
* autotranslate?: array{
* source: string,
* to: string[]
* }
* }
*/
protected $params = [];
protected $modify_in_store;
protected $pair = true;
public $stats = ['products' => 0, 'products_created' => 0, 'products_updated' => 0, 'variations' => 0, 'variations_created' => 0, 'variations_updated' => 0,
'category' => [], 'vat' => [], 'producer' => [], 'label' => [], 'deleted' => [], 'parameters' => [], ];
/* Caches */
protected $listProducer = [];
protected $listVAT = [];
protected $listLabel = [];
protected $listTemplates = [];
protected $listTemplatesByName = [];
protected $listParameter = [];
protected $listParameterGroups = [];
protected $listPriceLevels = [];
protected $listPriceLists = [];
protected $listUnits = [];
/**
* @var Parameter[]
*/
protected $listParameterAll = [];
protected $listVATDefault;
/**
* should be db updated.
*/
public $updateDB = true;
public $name;
protected $stores;
/**
* @var KupShop\StoresBundle\Utils\StoresInStore
*/
protected $storeService;
protected ?LoggingContext $loggingContext = null;
private ?VatsUtil $vatsUtil = null;
protected $productsBatch = [];
protected $batchPairElement = 'code';
/**
* Class function.
*
* Finds all imports ready to be processed
*
* @return bool
*/
public static function processAll()
{
QueryHint::routeToMaster();
$SQL = sqlQuery('SELECT id, name
FROM '.getTableName('import').'
WHERE `interval` > 0 AND COALESCE( last_sync, 0 ) < (NOW() - INTERVAL (`interval` *24) HOUR)');
$res = true;
while (($needProcess = sqlFetchAssoc($SQL)) !== false) {
$res &= self::processOne($needProcess['id']);
}
QueryHint::routeToMaster(false);
return $res;
}
public static function processOne($ID)
{
$startTime = getScriptTime();
$import = ServiceContainer::getService(\KupShop\AdminBundle\Util\AutomaticImport::class);
$import->setData($ID);
$retImp = $import->process();
$import->deleteOldProducts();
$import->showUpdatedProducts();
$duration = number_format(getScriptTime() - $startTime, 2);
if ($retImp) {
$import->addActivityLog(ActivityLog::SEVERITY_SUCCESS,
"Automatický import '{$import->name}' proběhl v pořádku za {$duration} vteřin.",
[
'Vytvořeno '.($import->stats['products_created'] + $import->stats['variations_created']).', '.
'Aktualizováno '.($import->stats['products_updated'] + $import->stats['variations_updated']).', '.
'Smazáno '.count($import->stats['deleted']).' položek.',
]
);
} else {
$import->addActivityLog(ActivityLog::SEVERITY_ERROR,
"Automatický import '{$import->name}' selhal!",
[$import->error, "Trval {$duration} vteřin"]
);
}
return $retImp;
}
/**
* Constructor.
*
* @param array $data
*/
/**
* @var SectionTree
*/
private $sectionTree;
public function __construct(mixed $data = [])
{
$this->setData($data);
}
public function setData(mixed $data = [])
{
if (is_numeric($data)) {
// Load DB data
$data = sqlQueryBuilder()
->select('*')
->from('import')
->where(Operator::equals(['id' => $data]))
->execute()->fetchAssociative();
}
foreach ($data as $key => $value) {
$this->{$key} = $value;
}
if ($this->add_new == 2) {
$this->delete_old = 0;
}
}
public function process($display = false)
{
// Leave session
session_write_close();
sqlQuery('set wait_timeout=3600');
sqlQuery('set interactive_timeout=3600');
set_time_limit(3600);
$this->display = $display;
if (!$this->parseParams()) {
return false;
}
$time_start = microtime(true);
if (!$this->getSourceFile()) {
$this->error .= 'Can not download source file.';
return false;
}
$time_end = microtime(true);
$time = round($time_end - $time_start, 3);
if ($display) {
echo "Stahování datového souboru: {$time} sekund
\n";
}
$time_start = microtime(true);
try {
if (!$this->getXML()) {
$this->error .= 'Can not get XML file.';
return false;
}
} catch (Exception $e) {
$this->error .= 'Nelze načíst soubor s importem: '.$e->getMessage();
return false;
}
$time_end = microtime(true);
$time = round($time_end - $time_start, 3);
if ($display) {
echo "Převod datového souboru na XML: {$time} sekund
\n";
}
$time_start = microtime(true);
if (!$this->transformXML()) {
$this->error .= 'Can not transform XML file.';
return false;
}
$time_end = microtime(true);
$time = round($time_end - $time_start, 3);
if ($display) {
echo "Aplikování transformace: {$time} sekund
\n";
}
$time_start = microtime(true);
if (!$this->parseProducts()) {
$this->error .= 'Can not parse products.';
return false;
}
if ($this->isAutoTranslateEnabled()) {
$this->autoTranslatePreprocess();
}
$time_end = microtime(true);
$time = round($time_end - $time_start, 3);
if ($display) {
echo "Párování produktů: {$time} sekund
\n";
}
if (!$display) {
return $this->importProducts();
}
if ($this->isAutoTranslateEnabled()) {
$this->getAutoTranslateUtil()->processAutoTranslate();
}
return true;
}
public function processIfNeeded()
{
$needProcess = returnSQLResult('SELECT COUNT(*)
FROM '.getTableName('import')."
WHERE id={$this->id} AND `interval` > 0 AND COALESCE( last_sync, 0 ) < (NOW() - INTERVAL(`interval` *24) HOUR)");
if ($needProcess > 0) {
return $this->process();
}
}
public function getXML()
{
if (($this->params['decompress'] ?? false) == 'gz') {
$this->decompressGz($this->sourceFile);
}
switch ($this->type) {
case AutomaticImport::TYPE_XML:
$this->xml = AutomaticImportTransform::LoadXMLFromFile($this->sourceFile);
if (!$this->xml) {
$this->error .= 'Can not parse XML file.';
return false;
}
break;
case AutomaticImport::TYPE_CSV:
case AutomaticImport::TYPE_XLS:
$transformation = new AutomaticImportTransform($this->sourceFile);
$data = [];
$dataLine = getVal('data', $this->params, 2);
$headerLine = getVal('header', $this->params, 1);
$sheet = getVal('sheet', $this->params, null);
$encoding = getVal('encoding', $this->params, null);
if (($forceCSVHeaderLine = getVal('headerLine', $this->params)) !== null) {
$data['forceHeaderLine'] = $forceCSVHeaderLine;
}
$transformation->setEncoding($encoding);
$this->xml = $transformation->GetXml($headerLine, $dataLine, $sheet, $data);
if (!$this->xml) {
$this->error .= 'Can not transform XML file.';
return false;
}
break;
case AutomaticImport::TYPE_WinShop:
$transformation = new AutomaticImportTransform($this->sourceFile, 'WinShop');
$dataLine = getVal('data', $this->params, 9);
$headerLine = getVal('header', $this->params, 6);
$separator = getVal('separator', $this->params, '');
$encoding = getVal('encoding', $this->params, 'CP1250');
$transformation->setEncoding($encoding);
$this->xml = $transformation->GetXml($headerLine, $dataLine, $separator);
if (!$this->xml) {
$this->error .= 'Can not transform XML file.';
return false;
}
break;
case AutomaticImport::TYPE_CycloConnect:
$CycloConnect = new AutomaticImportCycloConnect();
$CycloConnect->N = getVal('N', $this->params, 5);
$CycloConnect->BuyersID = getVal('buyersID', $this->params, null);
$CycloConnect->Password = getVal('password', $this->params, null);
$CycloConnect->BaseUrl = getVal('url', $this->params, null);
if (!$CycloConnect->BuyersID || !$CycloConnect->Password || !$CycloConnect->BaseUrl) {
$this->error .= 'Params buyersID, password and url are required.';
return false;
}
$CycloConnect->SaveTo = tempnam(sys_get_temp_dir(), 'autoImport');
$CycloConnect->Run();
if ($CycloConnect->SaveTo && file_exists($CycloConnect->SaveTo)) {
$this->xml = simplexml_load_file($CycloConnect->SaveTo);
unlink($CycloConnect->SaveTo);
}
break;
case AutomaticImport::TYPE_WinoraConnect:
$WinoraConnect = new AutomaticImportWinoraConnect();
$WinoraConnect->N = getVal('N', $this->params, 100);
$WinoraConnect->TotalCount = getVal('TotalCount', $this->params);
$WinoraConnect->loginid = getVal('loginid', $this->params, null);
$WinoraConnect->Password = getVal('password', $this->params, null);
$WinoraConnect->BaseUrl = getVal('url', $this->params, null);
if (!$WinoraConnect->loginid || !$WinoraConnect->Password || !$WinoraConnect->BaseUrl) {
$this->error .= 'Params loginid, password and url are required.';
return false;
}
$this->xml = $WinoraConnect->Run();
break;
case AutomaticImport::TYPE_AspireConnect:
$AspireConnect = new AutomaticImportAspireConnect();
$AspireConnect->loginid = getVal('loginid', $this->params, null);
$AspireConnect->Password = getVal('password', $this->params, null);
$AspireConnect->url = getVal('url', $this->params, null);
$this->xml = $AspireConnect->Run();
break;
case AutomaticImport::TYPE_DBF:
$transformation = new AutomaticImportTransform($this->sourceFile, 'dbf');
$encoding = getVal('encoding', $this->params, 'CP1250');
$transformation->setEncoding($encoding);
$this->xml = $transformation->GetXml();
if (!$this->xml) {
$this->error .= 'Can not transform XML file.';
return false;
}
break;
case AutomaticImport::TYPE_ZIP:
if (!$this->params['filename']) {
$this->error .= 'Missing filename.';
return false;
}
$this->xml = $this->extractZIP($this->sourceFile);
if (!$this->xml) {
$this->error .= 'Can not transform XML file.';
return false;
}
break;
case AutomaticImport::TYPE_JSON:
$transformation = new AutomaticImportTransform($this->sourceFile, 'json');
$encoding = getVal('encoding', $this->params, 'CP1250');
$transformation->setEncoding($encoding);
$this->xml = $transformation->GetXml();
if (!$this->xml) {
$this->error .= 'Can not transform XML file.';
return false;
}
break;
default:
$this->error .= "{$this->name}: Unsupported import type.";
return false;
}
if ($this->clearFlag && $this->sourceFile && file_exists($this->sourceFile)) {
unlink($this->sourceFile);
}
return true;
}
protected function decompressGz($sourceFile)
{
$content = file_get_contents($sourceFile);
$decompressed = gzdecode($content);
if (!$decompressed) {
$this->error .= 'Failed to decompress .gz file';
return false;
}
return (bool) file_put_contents($sourceFile, $decompressed);
}
public function transformXML()
{
if (!empty($this->transformation)) {
$xsl = new DOMDocument();
if (!$xsl->loadXML($this->transformation)) {
$this->error .= "{$this->name}: Can not read XSLT transformation.";
return false;
}
$this->xml = AutomaticImportTransform::TransformXml($xsl, $this->xml);
if (!$this->xml) {
$this->error .= "{$this->name}: Can not transform XML file.";
return false;
}
}
return true;
}
public function dumpXML()
{
header('Content-type: text/xml; charset=utf-8');
echo $this->xml->saveXML();
}
public function getSourceFile()
{
global $cfg;
if (empty($this->source)) {
$this->sourceFile = $cfg['Path']['data'].'/tmp/autoimport_'.$this->id;
}
if (!empty($this->sourceFile) && file_exists($this->sourceFile)) {
return true;
}
if (empty($this->source)) {
$this->error .= 'Není nahraný soubor.';
return false;
}
$this->sourceFile = tempnam(sys_get_temp_dir(), 'autoImport');
$this->clearFlag = true;
$len = $this->getDownloader()->copyRemoteFile($this->source, $this->sourceFile);
return $len > 0;
}
public function parseProducts()
{
global $cfg;
$this->loadCaches();
$productCodes = [];
$xml = simplexml_import_dom($this->xml);
$this->xml = null;
$this->productsBatchLoad($xml);
foreach ($xml->SHOPITEM as $item) {
$product = ['error' => []];
// parse translations fields
if (isset($item->LANG)) {
foreach ($item->LANG as $lang) {
$languageId = $lang->attributes()->id->__toString() ?? null;
if ($languageId) {
foreach ($item->LANG->children() as $typeElement) {
$type = strtolower($typeElement->getName());
$product['translations'][$languageId][$type] = $this->prepareTranslationData($type, $typeElement);
}
} else {
$product['error'][] = 'Chybí ID jazyka v elementu LANG';
}
}
}
$product['title'] = trim(strval($item->PRODUCT));
if (!empty($item->PRODUCT['force'])) {
$product['title_force'] = true;
}
// Get Code
if (!empty($item->CODE)) {
$product['code'] = trim(strval($item->CODE));
if (isset($productCodes[$product['code']]) && !getVal('skipProductCodeCompare', $this->params, false)) {
continue;
}
$productCodes[$product['code']] = true;
}
if (!empty($item->PRODUCT_CODE)) {
$product['product_code'] = trim(strval($item->PRODUCT_CODE));
}
if (!empty($item->EAN)) {
$product['ean'] = trim(strval($item->EAN));
}
if (!empty($item->EAN_SUPPLIER)) {
$product['ean_supplier'] = trim(strval($item->EAN_SUPPLIER));
}
if (!empty($item->EAN['force'])) {
$product['ean_force'] = true;
}
if (!empty($item->COLLECTION['force'])) {
$product['collection_force'] = true;
}
// Check if already exists
$this->findProduct($product, $item);
$sync = isset($product['sync']) ? $product['sync'] : false;
if (!empty($item->SHORT_DESCRIPTION)) {
$product['short_descr'] = strip_tags(trim(strval($item->SHORT_DESCRIPTION)));
if (isset($item->SHORT_DESCRIPTION['force']) && $item->SHORT_DESCRIPTION['force'] != 'true' && intval($item->SHORT_DESCRIPTION['force']) == 0) {
$product['short_descr_force'] = false;
}
if (isset($item->SHORT_DESCRIPTION['force']) && ($item->SHORT_DESCRIPTION['force'] == 'empty')) {
$product['short_descr_force'] = 'empty';
}
}
if (!empty($item->DESCRIPTION)) {
$product['long_descr'] = trim(strval($item->DESCRIPTION));
if (isset($item->DESCRIPTION['force']) && $item->DESCRIPTION['force'] != 'true' && intval($item->DESCRIPTION['force']) == 0) {
$product['long_descr_force'] = false;
}
if (isset($item->DESCRIPTION['force']) && ($item->DESCRIPTION['force'] == 'empty')) {
$product['long_descr_force'] = 'empty';
}
}
if (!empty($item->META_TITLE)) {
$product['meta_title'] = trim(strval($item->META_TITLE));
}
if (!empty($item->META_DESCRIPTION)) {
$product['meta_description'] = trim(strval($item->META_DESCRIPTION));
}
if (!empty($item->PARAMETERS)) {
$product['parameters'] = trim(strval($item->PARAMETERS));
if (isset($item->PARAMETERS['force']) && $item->PARAMETERS['force'] != 'true' && intval($item->PARAMETERS['force']) == 0) {
$product['parameters_force'] = false;
}
if (isset($item->PARAMETERS['force']) && ($item->PARAMETERS['force'] == 'empty')) {
$product['parameters_force'] = 'empty';
}
}
// Get Prices
if (isset($item->PRICE)) {
$product['price'] = floatval(strtr($item->PRICE, ',', '.'));
if (isset($item->PRICE['force']) && $item->PRICE['force'] == 'empty') {
$product['price_force'] = 'empty';
}
}
if (isset($item->PRICE_BUY)) {
$product['price_buy'] = floatval(strtr($item->PRICE_BUY, ',', '.'));
if (isset($item->PRICE_BUY['force']) && $item->PRICE_BUY['force'] == 'empty') {
$product['price_buy_force'] = 'empty';
}
}
if (isset($item->PRICE_SELL)) {
$product['price_sell'] = floatval(strtr($item->PRICE_SELL, ',', '.'));
}
if (!empty($item->PRICE_FOR_DISCOUNT)) {
$product['price_for_discount'] = $item->PRICE_FOR_DISCOUNT;
}
if (isset($item->WEIGHT)) {
$product['weight'] = floatval(strtr($item->WEIGHT, ',', '.'));
}
if (isset($item->WIDTH)) {
$product['width'] = floatval(strtr($item->WIDTH, ',', '.'));
}
if (isset($item->HEIGHT)) {
$product['height'] = floatval(strtr($item->HEIGHT, ',', '.'));
}
if (isset($item->DEPTH)) {
$product['depth'] = floatval(strtr($item->DEPTH, ',', '.'));
}
if (isset($item->VAT)) {
$product['vat_text'] = floatval(strtr($item->VAT, ',', '.'));
$product['vat'] = $this->findVAT($product['vat_text']);
}
if (findModule(Modules::OSS_VATS) && isset($item->VAT_CATEGORY)) {
$product['id_cn'] = $this->findOSSVatCategory(trim(strval($item->VAT_CATEGORY)));
}
if (isset($item->DISCOUNT)) {
$discount = floatval(strtr($item->DISCOUNT, ',', '.'));
$product['discount'] = round($discount, 4);
}
if (isset($item->PRICE_COMMON)) {
$product['price_common'] = floatval(strtr($item->PRICE_COMMON, ',', '.'));
}
if (isset($item->AVAILABILITY) && trim(strval($item->AVAILABILITY)) !== '') {
$this->findDeliveryTime(trim(strval($item->AVAILABILITY)), $product);
}
if (isset($item->FORECASTED_DELIVERY->DATE)) {
$product['forecasted_delivery_date'] = strval($item->FORECASTED_DELIVERY->DATE);
}
if (isset($item->FORECASTED_DELIVERY->PIECES)) {
$product['forecasted_delivery_pieces'] = strval($item->FORECASTED_DELIVERY->PIECES);
}
if (findModule(Modules::PRODUCTS, \Modules::SUB_UNITS)) {
if (isset($item->UNIT) && trim(strval($item->UNIT)) !== '') {
$product['unit'] = $this->findUnitId(trim(strval($item->UNIT)));
}
if (isset($item->MEASURE_UNIT) && trim(strval($item->MEASURE_UNIT)) !== '') {
$product['measure_unit'] = $this->findUnitId(trim(strval($item->MEASURE_UNIT)));
}
if (isset($item->MEASURE_QUANTITY) && trim(strval($item->MEASURE_QUANTITY)) !== '') {
$product['measure_quantity'] = (float) strtr($item->MEASURE_QUANTITY, ',', '.');
}
}
if (isset($item->NOTE)) {
$product['note'] = trim(strval($item->NOTE));
}
if (isset($item->DATA)) {
$tmpData = [];
foreach ($item->DATA as $data) {
$tmpData[trim(strval($data['name']))] = !empty($data['json']) ? json_decode(trim(strval($data))) : trim(strval($data));
}
$product['data'] = json_encode($tmpData);
}
if (findModule(Modules::PRODUCTS, Modules::SUB_RECYCLING_FEE) && isset($item->RECYCLING_FEE)) {
$productCustomData = json_decode($product['data'] ?? '', true) ?: [];
$productCustomData['recycling_fee'] = floatval(strtr($item->RECYCLING_FEE, ',', '.'));
$product['data'] = json_encode($productCustomData);
}
if (!empty($item->FLAGS)) {
$allFlags = $cfg['Products']['Flags'];
$product['flags'] = [];
foreach ($item->FLAGS->FLAG as $flag) {
$name = trim(strval($flag['name']));
switch ($name) {
case 'A': // Akce
$name = 'D';
break;
case 'P': // nejprodavanejsi
$name = 'S';
break;
case 'U': // na uvodu
$name = 'L';
break;
case 'V': // Vyprodej
$name = 'A';
break;
case 'D': // Doprava zdarma
$name = 'Z';
break;
}
if (empty($allFlags[$name])) {
$product['error'][] = "Neexistujici flag: {$flag['name']}";
continue;
}
$product['flags'][$name] = intval(strval($flag));
}
if (!empty($product['id_product'])) {
$campaign = returnSQLResult('SELECT campaign FROM '.getTableName('products')." WHERE id={$product['id_product']}");
$productFlags = explodeFlags($campaign ?: '');
foreach ($allFlags as $flag => $tmp) {
if (!isset($product['flags'][$flag])) {
$product['flags'][$flag] = isset($productFlags[$flag]);
}
}
}
$product['campaign'] = '';
$product['campaign_text'] = '';
foreach ($product['flags'] as $flag => $value) {
if ($value) {
$product['campaign'] .= "{$flag},";
$product['campaign_text'] .= "{$allFlags[$flag]['short']},";
}
}
}
// Get Others
if (!empty($item->PRODUCER)) {
$product['producer_text'] = trim(strval($item->PRODUCER));
$product['producer'] = $this->findProducer($product['producer_text']);
}
if (isset($item->IN_STORE) && trim(strval($item->IN_STORE)) !== '') {
$product['in_store'] = intval($item->IN_STORE);
}
if (isset($item->WARRANTY)) {
$product['guarantee'] = intval($item->WARRANTY);
}
if (isset($item->SHOW_IN_FEED)) {
if ($item->SHOW_IN_FEED == 'Y' || $item->SHOW_IN_FEED == 'N') {
$product['show_in_feed'] = $item->SHOW_IN_FEED;
} else {
$product['show_in_feed'] = intval($item->SHOW_IN_FEED);
}
}
if (isset($item->MAX_CPC)) {
$product['max_cpc'] = floatval($item->MAX_CPC);
}
if (isset($item->HIDDEN)) {
$tmp = strval($item->HIDDEN);
$product['figure'] = $tmp == 'Y' ? 'N' : ($tmp == 'O' ? 'O' : 'Y');
$product['figure_force'] = $this->parseXMLFlagValue($item->HIDDEN['force'], $product, default: true);
}
// Product templates
if (isset($item->TEMPLATES)) {
$templates = [];
foreach ($item->TEMPLATES->TEMPLATE as $template) {
$id = $this->findTemplate(intval(strval($template)));
if (!$id && !empty($template['name'])) {
$id = $this->findTemplateByName($template['name']);
}
if ($id) {
$templates[(string) $template['category_id']] = ['id' => $id];
} else {
$product['error'][] = "Neexistujici šablona ID {$id}";
}
}
$product['templates'] = $templates;
}
// Get Category
if (isset($item->CATEGORY) && $item->CATEGORY->count() && $sync) {
foreach ($item->CATEGORY as $category) {
if (!empty($category['force'])) {
$product['category_force'] = true;
}
if (!empty($category['delete'])) {
$product['category_delete'] = true;
}
$product['category'] = trim(strval($category));
$parts = $this->parseCategoryParts($product);
$categories = (array) $this->findCategory($parts);
$product['category_id'] = array_unique(array_merge($categories, $product['category_id'] ?? []));
}
}
// Get photos
if (!empty($item->PHOTOS)) {
if (!empty($item->PHOTOS['force'])) {
$product['photos_force'] = (string) $item->PHOTOS->attributes()?->force;
}
$product['photos'] = [];
foreach ($item->PHOTOS->PHOTO as $photo) {
$photo = trim($photo);
if (!parse_url($photo, PHP_URL_SCHEME)) {
$photo = $cfg['Path']['photos'].'import/'.$photo;
}
$product['photos'][] = [
'id' => null,
'url' => (string) $photo,
];
}
}
// Get attachments
if (!empty($item->ATTACHMENTS)) {
$product['attachments'] = [];
foreach ($item->ATTACHMENTS->ATTACHMENT as $attach) {
$attributeTitle = (string) $attach->attributes()?->title;
$force = (string) $attach->attributes()?->force;
$attach = trim($attach);
if (!parse_url($attach, PHP_URL_SCHEME)) {
$attach = $cfg['Path']['photos'].'import/'.$attach;
}
$attachment = [
'url' => (string) $attach,
'title' => $attributeTitle ?: urldecode(basename($attach)),
];
if ($force) {
$attachment['force'] = $force;
}
$product['attachments'][] = $attachment;
}
}
if (!empty($item->LINKS)) {
$product['links'] = [];
foreach ($item->LINKS->LINK as $link) {
$linkType = !empty($link['type']) ? trim((string) $link['type']) : 'link';
$link = trim($link);
$product['links'][] = [
'link' => $link,
'type' => $linkType,
];
}
}
// Get related products
if (!empty($item->RELATED)) {
$product['related'] = [];
foreach ($item->RELATED->PRODUCT as $rel) {
$product['related'][] = trim($rel);
}
$product['related_force'] = $this->parseXMLFlagValue($item->RELATED['force'], $product);
}
// Get collections
if (!empty($item->COLLECTION)) {
$product['collection'] = [];
foreach ($item->COLLECTION->PRODUCT as $collection) {
$product['collection'][] = trim($collection);
}
}
// Price levels
if (isset($item->PRICELEVEL)) {
$priceLevels = [];
foreach ($item->PRICELEVEL as $priceLevel) {
$name = strval(getVal('name', $priceLevel));
if ($id = $this->findPriceLevel($name)) {
$price = floatval(strtr($priceLevel->PRICE, ',', '.'));
$priceLevels[$id] = ['discount' => $price, 'unit' => 'final_price'];
} else {
$product['error'][] = "Neexistujici cenová hladina {$priceLevel}";
}
}
$product['price_levels'] = $priceLevels;
}
// SETS
if (isset($item->SETS)) {
$sets = [];
foreach ($item->SETS->SET as $set_product) {
$code = strval($set_product->CODE);
if ($searched_product = $this->findPureProduct($code)) {
$sets[$searched_product['id']] = ['id' => $searched_product['id'],
'price' => floatval(strtr($set_product->PRICE, ',', '.')) ?? $searched_product['price'],
'pieces' => intval($set_product->PIECES),
];
} else {
$product['error'][] = "Neexistujici kod produktu: {$set_product}";
}
}
$product['sets'] = $sets;
}
if (!empty($item->PRICELISTS)) {
$product['pricelists'] = [];
foreach ($item->PRICELISTS->PRICELIST as $pricelist) {
$id = isset($pricelist['name']) ? array_search((string) $pricelist['name'], $this->listPriceLists) : strval(getVal('id', $pricelist));
if ($this->findPriceList($id)) {
if (!empty($pricelist->PRICE)) {
$product['pricelists'][$id]['price'] = floatval(strtr($pricelist->PRICE, ',', '.'));
}
if (isset($pricelist->DISCOUNT)) {
if ($pricelist->DISCOUNT->__toString() !== '') {
$product['pricelists'][$id]['discount'] = floatval($pricelist->DISCOUNT);
}
}
} else {
$product['error'][] = "Neexistujici ceník {$id}:{$pricelist['name']}";
}
}
}
// Get id_parameter_group
if (!empty($item->PARAMETER_GROUP)) {
$product['id_parameter_group'] = $this->findParameterGroups($item->PARAMETER_GROUP);
}
// Get product_parameters
if (isset($item->PARAMETER) && $item->PARAMETER->count()) {
$product['product_parameters'] = [];
foreach ($item->PARAMETER as $param) {
$value = trim(strval($param));
// Preskocim parametr s prazdnou hodnotou, protoze jinak to udela prazdnej radek v parameters_products
if (empty($value)) {
continue;
}
$createParameterIfNotExists = false;
if (isset($param['create_parameter'])) {
$createParameterIfNotExists = true;
}
$ignoreMissingValues = false;
if (isset($param['ignore_missing_value'])) {
$ignoreMissingValues = true;
}
$parameterName = trim(strval($param['name']));
if ($this->isAutoTranslateEnabled() && !empty($product['sync'])) {
[$parameterName, $value] = $this->getAutoTranslateUtil()->preprocessParameterData(
$this->params['autotranslate'],
$parameterName,
$value,
$product
);
}
$parId = $this->findParameter($parameterName, $value, $product, $createParameterIfNotExists, $ignoreMissingValues);
if (!isset($product['product_parameters'][$parId])) {
$product['product_parameters'][$parId] = [];
}
if ($parId > 0) {
$product['product_parameters'][$parId][] = $value;
$product['product_parameters_force'][$parId] = $this->parseXMLFlagValue($param['force'], $product, default: null);
if (isset($param['replace'])) {
$product['product_parameters_replace'][$parId] = true;
}
if (isset($param['delete'])) {
$product['product_parameters_delete'][$parId] = true;
}
}
}
}
if (findModule(\Modules::MISSING_PRODUCTS)) {
if (isset($item->IN_STORE_MIN)) {
$product['in_store_min'] = intval($item->IN_STORE_MIN);
}
}
if (findModule(Modules::STORES)) {
$this->addStoreItem($product, $item);
}
$this->addCustomProductLoad($product, $item);
// Import variants
if ($sync && !empty($item->VARIATIONS)) {
$variationsOnNotExists = strval(getVal('onNotExists', $item->VARIATIONS));
foreach ($item->VARIATIONS->VARIATION as $variation) {
$product_variation = [
'error' => [],
'variation' => [],
];
$product_variation['variation_text'] = '';
foreach ($variation->LABEL as $label) {
$product_variation['variation'][$this->findLabel(trim(strval($label['name'])))] = trim(strval($label));
$product_variation['variation_text'] .= trim(strval($label['name'])).':'.trim(strval($label)).';';
}
// Get search
if (isset($variation['search']) && intval($variation['search']) == 0) {
$product_variation['search'] = false;
}
// Get group_by
if (trim(strval($variation['group_by']))) {
$product_variation['@group_by'] = strtolower(trim(strval($variation['group_by'])));
}
// Get sum
if (trim(strval($variation['sum']))) {
$product_variation['@sum'] = strtolower(trim(strval($variation['sum'])));
}
// Get photos
if (!empty($variation->PHOTOS)) {
$product_variation['photos'] = [];
foreach ($variation->PHOTOS->PHOTO as $photo) {
if (!parse_url($photo, PHP_URL_SCHEME)) {
$photo = $cfg['Path']['photos'].'import/'.$photo;
}
if (findModule('products_variations_photos')) {
$product_variation['photos'][] = [
'id' => null,
'url' => (string) $photo,
];
} else {
$product['photos'][] = [
'id' => null,
'url' => (string) $photo,
];
}
}
}
// Get Code
if (!empty($variation->CODE)) {
$product_variation['code'] = trim(strval($variation->CODE));
}
// Get Product_Code
if (!empty($variation->PRODUCT_CODE)) {
$product_variation['product_code'] = trim(strval($variation->PRODUCT_CODE));
}
// Get EAN
if (!empty($variation->EAN)) {
$product_variation['ean'] = trim(strval($variation->EAN));
}
// Get Price
if (!empty($variation->PRICE)) {
$forcePrice = strval(getVal('force', $variation->PRICE));
$product_variation['price'] = floatval(strtr($variation->PRICE, ',', '.'));
if (!empty($forcePrice)) {
$product_variation['price_force'] = true;
} else {
$product_variation['price_force'] = false;
}
// Override product price - it should be smallest of all variations
if ($product_variation['price'] > 0 && getVal('price', $product, 0) > $product_variation['price']) {
$product['price'] = $product_variation['price'];
}
}
if (isset($variation->PRICE_BUY)) {
$product_variation['price_buy'] = floatval(strtr($variation->PRICE_BUY, ',', '.'));
}
if (isset($variation->PRICE_SELL)) {
$product_variation['price_sell'] = floatval(strtr($variation->PRICE_SELL, ',', '.'));
}
if (!empty($variation->PRICE_FOR_DISCOUNT)) {
$product_variation['price_for_discount'] = $variation->PRICE_FOR_DISCOUNT;
}
if (isset($variation->IN_STORE) && trim(strval($variation->IN_STORE)) !== '') {
$product_variation['in_store'] = intval($variation->IN_STORE);
}
if (isset($variation->IN_STORE_MIN) && trim(strval($variation->IN_STORE_MIN)) !== '') {
$product_variation['in_store_min'] = intval($variation->IN_STORE_MIN);
}
if (isset($variation->AVAILABILITY) && trim(strval($variation->AVAILABILITY)) !== '') {
$this->findDeliveryTime(trim(strval($variation->AVAILABILITY)), $product_variation);
}
if (isset($variation->FORECASTED_DELIVERY->DATE)) {
$product_variation['forecasted_delivery_date'] = strval($variation->FORECASTED_DELIVERY->DATE);
}
if (isset($variation->FORECASTED_DELIVERY->PIECES)) {
$product_variation['forecasted_delivery_pieces'] = strval($variation->FORECASTED_DELIVERY->PIECES);
}
if (!empty($variation->TITLE)) {
$product_variation['title'] = trim(strval($variation->TITLE));
}
if (isset($variation->WEIGHT)) {
$product_variation['weight'] = floatval(strtr($variation->WEIGHT, ',', '.'));
}
if (isset($variation->WIDTH)) {
$product_variation['width'] = floatval(strtr($variation->WIDTH, ',', '.'));
}
if (isset($variation->HEIGHT)) {
$product_variation['height'] = floatval(strtr($variation->HEIGHT, ',', '.'));
}
if (isset($variation->DEPTH)) {
$product_variation['depth'] = floatval(strtr($variation->DEPTH, ',', '.'));
}
if (!$product_variation['error']) {
unset($product_variation['error']);
}
if (isset($variation->NOTE)) {
$product_variation['note'] = trim(strval($variation->NOTE));
}
if (isset($variation->DATA)) {
$tmpData = [];
foreach ($variation->DATA as $data) {
$tmpData[trim(strval($data['name']))] = !empty($data['json']) ? json_decode(trim(strval($data))) : trim(strval($data));
}
$product_variation['data'] = json_encode($tmpData);
}
if (findModule(Modules::STORES)) {
$this->addStoreItem($product_variation, $variation);
}
if (isset($variation->HIDDEN)) {
$hidden_tmp = strval($variation->HIDDEN);
$product_variation['figure'] = $hidden_tmp == 'Y' ? 'N' : 'Y';
if (isset($variation->HIDDEN['force'])) {
$product_variation['figure_force'] = $variation->HIDDEN['force'] == 'true';
} else { // backward compatibility
$product_variation['figure_force'] = true;
}
}
if (!empty($variation->PRICELISTS)) {
$product_variation['pricelists'] = [];
foreach ($variation->PRICELISTS->PRICELIST as $pricelist) {
$id = isset($pricelist['name']) ? array_search($pricelist['name'], $this->listPriceLists) : strval(getVal('id', $pricelist));
if ($this->findPriceList($id)) {
if (!empty($pricelist->PRICE)) {
$product_variation['pricelists'][$id]['price'] = floatval(strtr($pricelist->PRICE, ',', '.'));
}
if (isset($pricelist->DISCOUNT)) {
$product_variation['pricelists'][$id]['discount'] = floatval($pricelist->DISCOUNT);
}
} else {
$product_variation['error'][] = "Neexistujici ceník {$id}:{$pricelist}";
}
}
}
if (findModule(Modules::PRODUCTS, Modules::SUB_UNITS)) {
if (isset($variation->MEASURE_QUANTITY) && trim(strval($variation->MEASURE_QUANTITY)) !== '') {
$product_variation['measure_quantity'] = (float) strtr($variation->MEASURE_QUANTITY, ',', '.');
// Do not set product measure_quantity when updating variation measure_quantity
unset($product['measure_quantity']);
}
}
$this->addCustomVariationLoad($product_variation, $variation);
$this->findVariation($product, $product_variation);
if (!empty($product_variation['variation'])) {
foreach ($product_variation['variation'] as $variation_id => $variation_value) {
if ($variation_value == '') {
unset($product_variation['variation'][$variation_id]);
}
}
}
$product['variations'][] = $product_variation;
$this->stats['variations']++;
}
// Fix price
if (!empty($product['variations'])) {
foreach ($product['variations'] as &$product_variation) {
if (isset($product_variation['price']) && isset($product['price']) && $product_variation['price'] == $product['price'] && ($product_variation['price_force'] ?? false) !== true) {
unset($product_variation['price']);
}
}
}
// handle on not exists
if (isset($variationsOnNotExists) && $variationsOnNotExists == 'remove') {
$product['variations'] = array_filter($product['variations'], function ($x) {
return $x['id_variation'] !== null;
});
}
$tmp = null;
$product_variation = &$tmp;
} elseif ($sync && isset($product['id_variation']) && isset($item['convert_to_variation'])) {
$data = [
'sync' => true,
'variation' => [],
'status' => 'aktualizace',
'id_variation' => $product['id_variation'],
'convert_to_variation' => true,
'id_pos' => $product['id_pos'] ?? null,
];
foreach (array_diff(array_merge($this->getVariatonFields(), ['pricelists', 'stores', 'code']), ['title']) as $key) {
if (isset($product[$key])) {
$data[$key] = $product[$key];
unset($product[$key]);
}
}
$product['variations'][] = $data;
}
// odstranit EAN od produktu pokud je nastaven a zaroven ma varianty (na produktu potom nema co delat)
if (!empty($product['ean']) && !empty($product['variations'])) {
unset($product['ean']);
}
if (!$product['error']) {
unset($product['error']);
}
$this->products[] = $product;
$this->stats['products']++;
}
return true;
}
public function getVariatonFields()
{
$variationFields = ['title', 'delivery_time', 'price', 'ean', 'figure', 'width', 'height', 'depth', 'data', 'price_for_discount'];
if ($this->modify_in_store) {
$variationFields[] = 'in_store';
}
if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_BUY)) {
$variationFields[] = 'price_buy';
}
if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_COMMON)) {
$variationFields[] = 'price_common';
}
if (findModule(\Modules::PRODUCTS_VARIATIONS, \Modules::SUB_CODE)) {
$variationFields[] = 'product_code';
}
if (findModule(Modules::PRODUCTS, Modules::SUB_WEIGHT)) {
$variationFields[] = 'weight';
}
if (findModule(Modules::PRODUCTS, Modules::SUB_NOTE)) {
$variationFields[] = 'note';
}
if (findModule(\Modules::MISSING_PRODUCTS)) {
$variationFields[] = 'in_store_min';
}
if (findModule(Modules::PRODUCTS, Modules::SUB_UNITS)) {
$variationFields[] = 'measure_quantity';
}
return $variationFields;
}
public function getUpdateFields()
{
$updateFields = ['title', 'product_code', 'short_descr', 'long_descr', 'meta_title', 'meta_description', 'parameters',
'price', 'price_common', 'vat', 'producer', 'delivery_time', 'discount', 'delivery_time',
'campaign', 'guarantee', 'show_in_feed', 'max_cpc', 'figure', 'ean', 'width', 'height', 'depth', 'data', 'price_for_discount',
];
if ($this->modify_in_store) {
$updateFields[] = 'in_store';
}
if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_BUY)) {
$updateFields[] = 'price_buy';
}
if (findModule(Modules::PRODUCTS, Modules::SUB_WEIGHT)) {
$updateFields[] = 'weight';
}
if (findModule(Modules::PRODUCTS, Modules::SUB_UNITS)) {
$updateFields[] = 'unit';
$updateFields[] = 'measure_unit';
$updateFields[] = 'measure_quantity';
}
if (findModule(Modules::PRODUCTS, Modules::SUB_NOTE)) {
$updateFields[] = 'note';
}
if (findModule(Modules::PARAMETER_GROUPS)) {
$updateFields[] = 'id_parameter_group';
}
if (findModule(Modules::OSS_VATS)) {
$updateFields[] = 'id_cn';
}
if (findModule(\Modules::MISSING_PRODUCTS)) {
$updateFields[] = 'in_store_min';
}
return $updateFields;
}
public function importProducts()
{
$cfg = Config::get();
$updateFields = $this->getUpdateFields();
$variationFields = $this->getVariatonFields();
$this->modifyFields($updateFields, $variationFields);
foreach ($this->products as &$product) {
try {
$exists = getVal('id_product', $product) > 0;
if (!isset($product['sync'])) {
continue;
}
if ($product['sync']) {
// Do not update title of existing products
if ($exists && empty($product['title_force'])) {
unset($product['title']);
}
if ($exists && empty($product['figure_force'])) {
unset($product['figure']);
}
if ($exists && !empty($product['ean_force'])) {
unset($product['ean']);
}
// if (empty($product['campaign'])) {
// unset($product['campaign']);
// }
if ($exists && isset($product['short_descr_force'])) {
$short_descr_force = getVal('short_descr_force', $product, true);
if (!$short_descr_force || ($short_descr_force == 'empty' && empty($product['empty_short_descr']))) {
// nechceme prepsat popis existujiciho produktu, pokud short_descr_force = false,
// nebo short_descr_force = 'empty' a produkt uz ma vyplneny popis (!empty_short_descr)
unset($product['short_descr']);
}
}
if ($exists && isset($product['long_descr_force'])) {
$long_descr_force = getVal('long_descr_force', $product, true);
if (!$long_descr_force || ($long_descr_force == 'empty' && empty($product['empty_long_descr']))) {
// nechceme prepsat popis existujiciho produktu, pokud long_descr_force = false,
// nebo long_descr_force = 'empty' a produkt uz ma vyplneny popis (!empty_long_descr)
unset($product['long_descr']);
}
}
if ($exists && isset($product['parameters_force'])) {
$parameters_force = getVal('parameters_force', $product, true);
if (!$parameters_force || ($parameters_force == 'empty' && empty($product['empty_parameters']))) {
unset($product['parameters']);
}
}
if ($exists && isset($product['price_force'])) {
if ($product['price_force'] === 'empty' && empty($product['empty_price'])) {
unset($product['price']);
}
}
if (findModule(Modules::PRODUCTS, Modules::SUB_PRICE_BUY) && $exists && isset($product['price_buy_force'])) {
if ($product['price_buy_force'] === 'empty' && empty($product['empty_price_buy'])) {
unset($product['price_buy']);
}
}
$fields = [];
foreach ($updateFields as $field) {
if (isset($product[$field])) {
$fields[$field] = $product[$field];
}
}
if (!empty($fields)) {
if (!empty($fields['product_code'])) {
$fields['code'] = $fields['product_code'];
unset($fields['product_code']);
}
// Insert into DB
if (!$exists) {
if (empty($fields['vat'])) {
$fields['vat'] = $this->listVATDefault;
}
$fields['date_added'] = new DateTime();
if (!$this->insertSQL('products', $fields, [], ['date_added' => 'datetime'])) {
continue;
}
$product['id_product'] = sqlInsertId();
// dispatch product.created event
$productObj = new Product();
$productObj->createFromDB($product['id_product']);
$eventDispatcher = ServiceContainer::getService('event_dispatcher');
$event = new ProductEvent($productObj);
$eventDispatcher->dispatch($event, ProductEvent::PRODUCT_CREATED);
$this->stats['products_created']++;
$product['counted'] = true;
$this->updatedCreatedProducts[] = $product;
} elseif (!empty($product['id_product']) && $product['id_product'] > 0) {
if (!empty($fields['data'])) {
$data = $this->selectSQL('products', ['id' => $product['id_product']], ['data'])->fetchOne();
$data = array_merge(
json_decode($data ?? '', true) ?? [],
json_decode($fields['data'] ?? '', true) ?? []
) ?? [];
if (!empty($data)) {
$fields['data'] = json_encode($data);
}
}
if ($this->updateSQL('products', $fields, ['id' => $product['id_product']])) {
$this->stats['products_updated']++;
$this->updatedCreatedProducts[] = $product;
}
$product['counted'] = true;
}
}
// Insert product_parameters into DB
foreach (getVal('product_parameters', $product, []) as $id_parameter => $base_parameter) {
if ($exists && !($product['product_parameters_force'][$id_parameter] ?? true)) {
continue;
}
if (empty($base_parameter)) {
continue;
}
$parameter = $this->listParameterAll[$id_parameter];
if (!empty($product['product_parameters_replace'][$parameter->id]) || !empty($product['product_parameters_delete'][$parameter->id])) {
$this->deleteSQL('parameters_products', ['id_product' => $product['id_product'], 'id_parameter' => $parameter->id]);
}
foreach ($base_parameter as $value) {
$data = [
'id_product' => $product['id_product'],
'id_parameter' => $parameter->id,
];
if ($parameter->value_type != 'float') {
$data["value_{$parameter->value_type}"] = $value;
$id = sqlFetchAssoc($this->selectSQL('parameters_products', $data, ['id']))['id'] ?? false;
} else {
// Special handling for floats :-(
$where = $this->createWhere($data);
$data['value'] = $value;
$id = sqlFetchAssoc(sqlQuery("SELECT id FROM parameters_products WHERE {$where} AND (value_float - :value) < 0.001", $data))['id'];
}
if (!$id && empty($product['product_parameters_delete'][$parameter->id])) {
$data = [
'id_product' => $product['id_product'],
'id_parameter' => $parameter->id,
'value' => $value,
'unit' => getVal(0, $parameter->unit),
];
$parameter->setValue($data);
}
}
}
// Insert templates
if (!empty($product['templates'])) {
$this->deleteSQL('templates_products', ['id_product' => $product['id_product']]);
foreach ($product['templates'] as $template) {
try {
$this->insertSQL('templates_products', ['id_product' => $product['id_product'], 'id_template' => $template['id']]);
} catch (Doctrine\DBAL\DBALException $e) {
$product['import_error'][] = "Neexistujici šablona ID {$template['id']}";
}
}
}
// Insert sets
if (!empty($product['sets'])) {
$this->deleteSQL('products_sets', ['id_product' => $product['id_product']]);
foreach ($product['sets'] as $set) {
try {
$this->insertSQL('products_sets', [
'id_product' => $product['id_product'],
'id_product_set' => $set['id'],
'price' => $set['price'],
'pieces' => $set['pieces'],
]);
} catch (Doctrine\DBAL\DBALException $e) {
$product['import_error'][] = "Neexistujici product {$product['id_product']}";
}
}
}
// Insert price levels
if (!empty($product['price_levels'])) {
foreach ($product['price_levels'] as $id_priceLevel => $priceLevel) {
// Delete if discount is zero
if ($priceLevel['discount'] == 0) {
$this->deleteSQL('price_levels_products', [
'id_price_level' => $id_priceLevel,
'id_product' => $product['id_product'],
]);
continue;
}
$this->replaceSQL('price_levels_products', [
'id_price_level' => $id_priceLevel,
'id_product' => $product['id_product'],
'discount' => $priceLevel['discount'],
'unit' => $priceLevel['unit'],
]);
}
}
if (!empty($product['stores'])) {
foreach ($product['stores'] as $store_product) {
$this->updateStoreItem($store_product, $product);
}
}
// Insert price levels
if (!empty($product['pricelists'])) {
foreach ($product['pricelists'] as $id_pricelist => $values) {
$values['id_product'] = $product['id_product'];
$values['id_pricelist'] = $id_pricelist;
$id = sqlQueryBuilder()
->select('id')
->from('pricelists_products')
->where(
\Query\Operator::equals(
['id_pricelist' => $values['id_pricelist'], 'id_product' => $product['id_product']]
)
)
->execute()
->fetchColumn();
if ($id) {
sqlQueryBuilder()
->update('pricelists_products')
->directValues($this->filterFields($values, ['price', 'discount']))
->where(
\Query\Operator::equals(
['id_pricelist' => $values['id_pricelist'], 'id_product' => $product['id_product']]
)
)
->execute();
} else {
$this->insertSQL('pricelists_products', $values);
}
// if (!isset($values['discount'])) {
// sqlQuery("INSERT INTO pricelists_products (id_pricelist, id_product, id_variation, price)
// VALUES (:id_pricelist, :id_product, NULL, :price)
// ON DUPLICATE KEY UPDATE price=:price", $values);
// } else {
// sqlQuery("INSERT INTO pricelists_products (id_pricelist, id_product, id_variation, price, discount)
// VALUES (:id_pricelist, :id_product, NULL, :price, :discount)
// ON DUPLICATE KEY UPDATE price=:price, discount=:discount", $values);
// }
}
}
// Insert category into DB
if (!empty($product['category_id']) && (!$exists || !empty($product['category_force']))) {
if ($product['category_delete'] ?? false) {
sqlQueryBuilder()
->delete('products_in_sections')
->where(Operator::equals(['id_product' => $product['id_product']]))
->execute();
}
foreach ((array) $product['category_id'] as $category_id) {
sqlQuery('INSERT IGNORE INTO '.getTableName('products_in_sections').'
(id_product, id_section) VALUES (:id_product, :id_section)', ['id_product' => $product['id_product'], 'id_section' => $category_id]);
}
}
// Download photos
if (!empty($product['photos'])) {
// Check that no photos present
$query = 'SELECT COUNT(*) FROM '.getTableName('photos-products')." WHERE id_product={$product['id_product']}";
if (findModule('products_variations_photos')) {
$query .= ' AND id_variation IS NULL';
}
$photos = returnSQLResult($query);
$force = false;
if ((!empty($product['photos_force']) && $photos < count($product['photos'])) || ($product['photos_force'] ?? false)) {
$force = $product['photos_force'];
}
$first = true;
if (!$force && $exists && $photos != 0) {
$first = false;
}
if ($force === 'update') {
$this->updatePhotos($product['id_product'], $product['photos']);
}
if ($first || $force) {
$insertPhotos = [];
foreach ($product['photos'] as $photo) {
try {
$insertPhotos[] = [
'id_photo' => $this->getDownloader()->importProductImage($photo['url'], (bool) $force),
'id_product' => $product['id_product'],
'show_in_lead' => ($first || ($photo['figure'] ?? false)) ? 'Y' : 'N',
'active' => 'Y',
'url' => $photo['url'],
];
$first = false;
} catch (ImagickException $e) {
$product['import_error'][] = "Nelze stáhnout obrázek: {$photo['url']}";
continue;
}
}
sqlGetConnection()->transactional(function () use ($product, $insertPhotos) {
$inserted = [];
$qb = sqlQueryBuilder()
->delete('photos_products_relation', 'pr')
->where(Operator::equals(['pr.id_product' => $product['id_product']]));
if (findModule(Modules::VIDEOS)) {
$qb->leftJoin('pr', 'videos', 'v', 'v.id_photo = pr.id_photo')
->andWhere('v.id_photo IS NULL');
}
$qb->execute();
foreach ($insertPhotos as $insertPhoto) {
if ($insertPhoto['id_photo']) {
if (empty($inserted[$insertPhoto['id_photo']])) {
unset($insertPhoto['url']);
sqlQueryBuilder()
->insert('photos_products_relation')
->directValues($insertPhoto)
->execute();
}
$inserted[$insertPhoto['id_photo']] = true;
} else {
$product['import_error'][] = "Nelze stáhnout obrázek: {$insertPhoto['url']}";
}
}
});
}
}
// Download attachments
if (!empty($product['attachments'])) {
// Check that attachments
$count = sqlQueryBuilder()
->select('COUNT(*)')
->from('attachments')
->where(Operator::equals(['id_product' => $product['id_product']]))
->execute()->fetchColumn();
$first = true;
if ($exists && $count != 0) {
$first = false;
}
$force = false;
if ($count < count($product['attachments'])) {
$force = true;
}
$inserted = [];
foreach ($product['attachments'] as $attachment) {
if ($first || $force || !empty($attachment['force'])) {
$basename = urldecode(basename($attachment['url']));
$parsedUrl = parse_url($attachment['url']);
$title = $attachment['title'];
$dest = $cfg['Path']['data'].'files/attachments'.$parsedUrl['path'];
if (empty($inserted[$basename])) {
if (($attachment['force'] ?? false) === 'diff') {
// zjistim jestli soubor se stejnym nazvem uz mam, abych ho nestahoval zbytecne
$attachmentCount = sqlQueryBuilder()
->select('COUNT(*)')
->from('attachments')
->andWhere(Operator::equals([
'id_product' => $product['id_product'],
'link' => '/'.$dest,
]))->execute()->fetchOne();
if ($attachmentCount > 0) {
continue;
}
}
if ($this->getDownloader()->copyRemoteFile($attachment['url'], $dest)) {
sqlGetConnection()->transactional(function () use (&$product, $title, $basename, $dest) {
sqlQueryBuilder()
->delete('attachments')
->andWhere(Operator::equals([
'id_product' => $product['id_product'],
'link' => $dest,
]))->execute();
sqlQueryBuilder()
->insert('attachments')
->values(['date' => 'NOW()'])
->directValues([
'id_product' => $product['id_product'],
'title' => $title ?: $basename,
'link' => '/'.$dest,
])->execute();
});
$inserted[$basename] = true;
} else {
$product['import_error'][] = "Nelze stáhnout přílohu: {$attachment['url']}";
}
}
}
}
}
// Add links to product
if (!empty($product['links'])) {
$productLinks = Mapping::mapKeys(sqlQueryBuilder()
->select('id, link')
->from('links')
->where(Operator::equals(['id_product' => $product['id_product']]))
->execute()->fetchAllAssociative(), fn ($k, $v) => [$v['link'], $v['id']]);
foreach ($product['links'] as $link) {
// link uz existuje, takze nic neinsertuju
if ($productLinks[$link['link']] ?? false) {
continue;
}
sqlQueryBuilder()
->insert('links')
->directValues(
[
'id_product' => $product['id_product'],
'link' => $link['link'],
'type' => $link['type'],
]
)->execute();
$productLinks[$link['link']] = true;
}
}
// Append related products
if (!empty($product['related'])) {
// Check that attachments
$count = sqlQueryBuilder()
->select('COUNT(*)')
->from('products_related')
->where(\Query\Operator::equals(['id_top_product' => $product['id_product']]))
->execute()->fetchColumn();
$first = true;
if ($exists && $count != 0) {
$first = false;
}
$force = false;
if ($product['related_force'] || $count < count($product['related'])) {
$force = true;
}
if ($first || $force) {
$inserted = [];
foreach ($product['related'] as $related) {
if (empty($inserted[$related])) {
$temp_product = [
'code' => $related,
];
$this->findProduct($temp_product);
if (!empty($temp_product['id_product'])) {
sqlQuery("INSERT IGNORE INTO products_related (id_top_product, id_rel_product) VALUES ({$product['id_product']}, {$temp_product['id_product']})");
$inserted[$related] = true;
}
}
}
}
}
// Append collection products
if (!empty($product['collection'])) {
// Check that collection
$count = sqlQueryBuilder()
->select('COUNT(*)')
->from('products_collections')
->where(\Query\Operator::equals(['id_product' => $product['id_product']]))
->execute()->fetchColumn();
$first = true;
if ($exists && $count != 0) {
$first = false;
}
$force = false;
if (!empty($product['collection_force']) || $count < count($product['collection'])) {
$force = true;
}
if ($first || $force) {
$inserted = [];
foreach ($product['collection'] as $collection) {
if (empty($inserted[$collection])) {
$temp_product = [
'code' => $collection,
];
$this->findProduct($temp_product);
if (!empty($temp_product['id_product'])) {
sqlQuery("INSERT IGNORE INTO products_collections (id_product, id_product_related) VALUES ({$product['id_product']}, {$temp_product['id_product']})");
$inserted[$collection] = true;
}
}
}
}
}
}
// Import variants
if (getVal('id_product', $product) > 0) { // else: Product doesn't exist and product sync is false.
if (!empty($product['variations'])) {
$first = false;
if (empty($product['photos'])) {
$first = true;
}
$productObj = new Product($product['id_product']);
// Group variations
$productVariations = [];
foreach ($product['variations'] as $variation) {
if (!empty($variation['@group_by'])) {
if (!empty($variation['@sum'])) {
$variation[$variation['@sum']] += $productVariations[$variation[$variation['@group_by']]][$variation['@sum']];
}
$productVariations[$variation[$variation['@group_by']]] = $variation;
} else {
$productVariations[] = $variation;
}
}
$product['variations'] = $productVariations;
// Get static labels
$productVariationLabels = [
'last' => [],
'counter' => [],
'count' => count($product['variations']),
];
foreach ($product['variations'] as $variation) {
foreach ($variation['variation'] as $key => $value) {
if (empty($productVariationLabels['counter'][$key])) {
$productVariationLabels['counter'][$key] = 0;
}
if (getVal($key, $productVariationLabels['last']) == $value) {
$productVariationLabels['counter'][$key]++;
}
$productVariationLabels['last'][$key] = $value;
}
}
foreach ($product['variations'] as &$variation) {
if (!isset($variation['sync'])) {
continue;
}
// Remove static labels
foreach ($productVariationLabels['counter'] as $key => $value) {
if ($productVariationLabels['count'] > 1) {
if ($productVariationLabels['counter'][$key] == $productVariationLabels['count']) {
unset($variation['variation'][$key]);
}
}
}
if (empty($variation['id_variation'])) {
// skipnout kdyz je prazda varianta, vytvarelo to potom varianty k produktum, ktere varianty mit nemaji
if (empty($variation['variation'])) {
continue;
}
$variation['id_variation'] = \Variations::createProductVariation($product['id_product'], $variation['variation'], $variation['search'] ?? true);
}
if (!empty($variation['id_variation']) && empty($variation['figure_force'])) {
unset($variation['figure']);
}
if (!empty($variation['data'])) {
$data = $this->selectSQL('products_variations', ['id' => $variation['id_variation']], ['data'])->fetchOne();
$data = array_merge(
json_decode($data ?? '', true) ?? [],
json_decode($variation['data'] ?? '', true) ?? []
) ?? [];
if (!empty($data)) {
$variation['data'] = json_encode($data);
}
}
$fields = [];
foreach ($variationFields as $field) {
if (isset($variation[$field])) {
$fields[$field] = $variation[$field];
}
}
if (!empty($fields['product_code'])) {
$fields['code'] = $fields['product_code'];
unset($fields['product_code']);
}
$fields = queryCreate($fields, true);
try {
if (trim($fields) && (($this->modify_in_store || ($variation['price_force'] ?? false) === true) || ($variation['convert_to_variation'] ?? false) || !empty($variation['data']))) {
sqlQuery("UPDATE products_variations SET {$fields} WHERE id={$variation['id_variation']}");
$productObj->updateInStore();
$productObj->updateDeliveryTime();
}
} catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) {
if (isLocalDevelopment()) {
throw $e;
}
$product['import_error'] = $this->translateException($e);
continue;
}
$this->updateSupplier($product, $variation);
$this->customProductUpdate($product, $variation);
// Download photos
if (findModule('products_variations_photos') && !empty($variation['photos'])) {
if (returnSQLResult('SELECT COUNT(*) FROM '.getTableName('photos-products')." WHERE id_product={$product['id_product']} AND id_variation={$variation['id_variation']}") == 0) {
foreach ($variation['photos'] as $photo) {
$photoId = $this->getDownloader()->importProductImage($photo['url']);
if ($photoId) {
sqlQuery('INSERT INTO '.getTableName('photos-products')." (id_photo, id_product, id_variation, show_in_lead, active) VALUES ({$photoId}, {$product['id_product']}, {$variation['id_variation']}, '".($first ? 'Y' : 'N')."', 'Y')");
$first = false;
} else {
$product['import_error'][] = "Nelze stáhnout obrázek: {$photo['url']}";
}
}
}
}
foreach ($variation['stores'] ?? [] as $store_product) {
$this->updateStoreItem($store_product, $product, $variation);
}
if ($variation['status'] == 'nový') {
$this->stats['variations_created']++;
} else {
$this->stats['variations_updated']++;
}
if (!empty($variation['pricelists'])) {
foreach ($variation['pricelists'] as $id_pricelist => $values) {
$values['id_variation'] = $variation['id_variation'];
$values['id_pricelist'] = $id_pricelist;
$values['id_product'] = $product['id_product'];
$id = sqlQueryBuilder()
->select('id')
->from('pricelists_products')
->andWhere(\Query\Operator::equals([
'id_pricelist' => $values['id_pricelist'],
'id_variation' => $variation['id_variation'],
'id_product' => $product['id_product'],
]))
->execute()
->fetchColumn();
if ($id) {
sqlQueryBuilder()
->update('pricelists_products')
->directValues($this->filterFields($values, ['price', 'discount']))
->andWhere(\Query\Operator::equals([
'id_pricelist' => $values['id_pricelist'],
'id_product' => $product['id_product'],
'id_variation' => $variation['id_variation'],
]))->execute();
} else {
$this->insertSQL('pricelists_products', $values);
}
}
}
}
unset($variation);
} else {
$this->updateSupplier($product, null);
}
}
// import translations
if (!empty($product['translations']) && $product['id_product'] > 0) {
$languageContext = \KupShop\KupShopBundle\Util\Compat\ServiceContainer::getService(LanguageContext::class);
$supported = $languageContext->getSupported();
foreach ($product['translations'] as $language => $data) {
if (!isset($supported[$language])) {
$product['import_error'] = 'Jazyk '.$language.' není podporován! Je potřeba jej přidat.';
continue;
}
foreach ($data as $type => $fields) {
if (!isset(static::$translationTypes[$type])) {
$product['import_error'] = 'Nepodporovaný typ překladu '.$type;
continue;
}
$this->updateTranslations($product['id_product'], $type, $language, $fields);
}
}
}
$this->customProductUpdate($product);
} catch (Doctrine\DBAL\DBALException $e) {
if (isLocalDevelopment()) {
throw $e;
}
$product['import_error'] = $this->translateException($e);
continue;
}
}
if (!$this->display) {
$this->automaticUpdateFlagForNewProducts();
}
if (!$this->display) {
$this->updateDB();
}
$this->clearCaches();
return true;
}
public function updateTranslation($translationClass, $languageID, $values, $objectID)
{
try {
$return = $this->getTranslationService($translationClass)->saveSingleObject($languageID, $objectID, $values);
} catch (\KupShop\I18nBundle\Translations\UndefinedTranslationColumnException $e) {
$return = false;
}
return $return;
}
public function updateTranslations(int $productId, string $type, string $language, array $data): void
{
switch ($type) {
// update parameters translations
case 'parameters':
$parameterFinder = ServiceContainer::getService(ParameterFinder::class);
foreach ($data as $originalName => $parameter) {
if (!($parameterId = $parameterFinder->getParameterId($originalName))) {
continue;
}
// update parameter name translation
if (!empty($parameter['name'])) {
$this->updateTranslation(ParametersTranslation::class, $language, ['name' => $parameter['name']], $parameterId);
}
// update values translation
foreach ($parameter['values'] as $originalValue => $value) {
if (!($valueId = $parameterFinder->getParameterValueId($parameterId, $originalValue))) {
continue;
}
$this->updateTranslation(ParametersListTranslation::class, $language, ['value' => $value], $valueId);
}
}
return;
}
$this->updateTranslation(static::$translationTypes[$type], $language, $data, $productId);
}
public function prepareTranslationData(string $type, SimpleXMLElement $element): array
{
switch ($type) {
case 'parameters':
$result = [];
foreach ($element->PARAMETER as $parameter) {
$parameterName = (string) $parameter->attributes()->name;
if (empty($parameterName)) {
continue;
}
if (!isset($result[$parameterName])) {
$result[$parameterName] = [
'name' => (string) $parameter->attributes()->name_translated,
'values' => [],
];
}
$originalValue = (string) $parameter->attributes()->value;
if (empty($originalValue)) {
continue;
}
$value = (string) $parameter;
if (empty($value) || $originalValue === $value) {
continue;
}
$result[$parameterName]['values'][$originalValue] = (string) $parameter;
}
return $result;
}
$result = [];
foreach ($element as $field) {
$result[strtolower($field->getName())] = $field->__toString();
}
return $result;
}
public function updateSupplier($product, $variation)
{
if ($this->params['skip_supplier_update'] ?? false) {
return;
}
if ($this->id_supplier <= 0) {
return;
}
$id_pos = null;
if (isset($product['id_pos']) && empty($variation)) {
$id_pos = $product['id_pos'];
}
if (!empty($variation['id_pos'])) {
$id_pos = $variation['id_pos'];
}
// When not pairing, disallow creation of POS record
if (!$this->pair && empty($id_pos)) {
return;
}
$fields = [];
if (isset($product['in_store'])) {
$fields['in_store'] = $product['in_store'];
}
if (isset($variation['in_store'])) {
$fields['in_store'] = $variation['in_store'];
}
if ($variation) {
if (isset($variation['ean_supplier'])) {
$fields['ean'] = $variation['ean_supplier'];
}
} else {
if (isset($product['ean_supplier'])) {
$fields['ean'] = $product['ean_supplier'];
}
}
if (isset($product['price_buy'])) {
$fields['price_buy'] = $product['price_buy'];
}
if (isset($variation['price_buy'])) {
$fields['price_buy'] = $variation['price_buy'];
}
if (isset($product['price_sell'])) {
$fields['price_sell'] = $product['price_sell'];
}
if (isset($variation['price_sell'])) {
$fields['price_sell'] = $variation['price_sell'];
}
if (isset($variation['forecasted_delivery_date']) || isset($product['forecasted_delivery_date'])) {
$fields['forecasted_delivery_date'] = $this->prepareDate(DateTime::createFromFormat('d.m.Y', $variation['forecasted_delivery_date'] ?? $product['forecasted_delivery_date']));
}
if (isset($variation['forecasted_delivery_pieces']) || isset($product['forecasted_delivery_pieces'])) {
$fields['forecasted_pieces'] = $variation['forecasted_delivery_pieces'] ?? $product['forecasted_delivery_pieces'];
}
if (empty($id_pos)) {
$posKey['id_product'] = $product['id_product'];
$posKey['id_variation'] = $variation ? $variation['id_variation'] : getVal('id_variation', $product);
$posKey['id_supplier'] = $this->id_supplier;
$fields['code'] = $variation ? $variation['code'] : $product['code'];
$fields = queryCreate(array_merge($fields, $posKey), true);
sqlQuery('INSERT INTO '.getTableName('products_of_suppliers')." SET {$fields}, last_sync=NOW() ON DUPLICATE KEY UPDATE {$fields}, last_sync=NOW()");
if (!$this->modify_in_store && empty($product['counted'])) {
$this->stats['products_created']++;
$this->updatedCreatedProducts[] = $product;
}
} else {
if (!empty($fields)) {
$fields = trim(queryCreate($fields, true));
sqlQuery("UPDATE products_of_suppliers SET {$fields} WHERE id={$id_pos}");
}
sqlQuery("UPDATE products_of_suppliers SET last_sync=NOW() WHERE id={$id_pos}");
if (!$this->modify_in_store && empty($product['counted'])) {
$this->stats['products_updated']++;
$this->updatedCreatedProducts[] = $product;
}
}
}
public function loadCaches()
{
$query = sqlQueryBuilder()
->select('id, vat, is_default')
->from('vats');
if (findModule(\Modules::OSS_VATS)) {
$query = $query->where(Operator::equals(['automanaged' => 0]));
}
foreach ($query->execute()->fetchAll() as $row) {
$this->listVAT[$row['id']] = $row['vat'];
if ($row['is_default'] == 'Y') {
$this->listVATDefault = $row['id'];
}
}
$query = sqlQuery('SELECT id, name FROM '.getTableName('producers'));
while ($row = sqlFetchArray($query)) {
$this->listProducer[$row['id']] = mb_strtolower($row['name']);
}
$query = sqlQuery('SELECT id, label FROM '.getTableName('products_variations_choices_labels'));
while ($row = sqlFetchArray($query)) {
$this->listLabel[$row['id']] = mb_strtolower($row['label']);
}
$this->listParameterAll = Parameter::get();
foreach ($this->listParameterAll as $parameter) {
$this->listParameter[$parameter->id] = mb_strtolower($parameter->name, 'utf-8');
}
if (findModule('templates')) {
$this->listTemplates = sqlFetchAll($this->selectSQL('templates', [], ['id']), 'id');
$this->listTemplatesByName = Mapping::mapKeys(
$this->selectSQL('templates', [], ['id', 'name'])->fetchAll(),
function ($k, $v) {
return [$v['name'], (int) $v['id']];
}
);
}
if (findModule(\Modules::PRICE_LEVELS)) {
$this->listPriceLevels = sqlFetchAll($this->selectSQL('price_levels', [], ['name', 'id']), ['name' => 'id']);
}
if (findModule(\Modules::PRICELISTS)) {
$this->listPriceLists = sqlFetchAll($this->selectSQL('pricelists', [], ['name', 'id']), ['id' => 'name']);
}
if (findModule(Modules::PRODUCTS, \Modules::SUB_UNITS)) {
$this->listUnits = sqlFetchAll(sqlQueryBuilder()->select('id, LOWER(short_name) as short_name')->from('products_units')->execute(), ['short_name' => 'id']);
}
$this->clearCaches();
}
public function clearCaches()
{
clearCache('menu', true);
if ($this->sectionTree) {
$this->sectionTree->clearCache();
}
}
public function findVAT($percent)
{
if ($index = array_search($percent, $this->listVAT)) {
return $index;
}
if (!$this->display) {
$this->insertSQL('vats', ['descr' => "Daň {$percent}%", 'vat' => $percent]);
$index = sqlInsertId();
} else {
$this->stats['vat'][] = $percent;
$index = -count($this->listVAT);
}
$this->listVAT[$index] = $percent;
return $index;
}
public function findProducer($name)
{
if ($name == '') {
return 0;
}
if (($index = array_search(mb_strtolower($name), $this->listProducer)) !== false) {
return $index;
}
if (!$this->display) {
try {
$this->insertSQL('producers', ['name' => $name]);
$index = sqlInsertId();
} catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) {
$index = sqlQueryBuilder()
->select('id')
->from('producers')
->where(\Query\Operator::equals(['name' => $name]))
->execute()
->fetchColumn();
}
} else {
$this->stats['producer'][] = $name;
$index = -count($this->listProducer);
}
$this->listProducer[$index] = mb_strtolower($name);
return $index;
}
public function findParameterGroups($name)
{
if (!findModule(\Modules::PARAMETER_GROUPS)) {
return null;
}
if (!$this->listParameterGroups) {
$this->listParameterGroups = array_map(function ($x) {
return mb_strtolower($x, 'utf-8');
}, sqlFetchAll($this->selectSQL('parameter_groups', [], ['name', 'id']), ['id' => 'name']));
}
if ($name == '') {
return null;
}
if (($index = array_search(mb_strtolower($name, 'utf-8'), $this->listParameterGroups)) !== false) {
return $index;
}
return null;
}
public function findLabel($name)
{
if ($name == '') {
return 0;
}
if (($index = array_search(mb_strtolower($name, 'utf-8'), $this->listLabel)) !== false) {
return $index;
}
if (!$this->display) {
try {
$this->insertSQL('products_variations_choices_labels', ['label' => $name]);
$index = sqlInsertId();
} catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) {
$index = sqlQueryBuilder()
->select('id')
->from('products_variations_choices_labels')
->where(\Query\Operator::equals(['label' => $name]))
->execute()
->fetchColumn();
}
} else {
$this->stats['label'][] = $name;
$index = -count($this->listLabel);
}
$this->listLabel[$index] = mb_strtolower($name, 'utf-8');
return $index;
}
public function findTemplate($id)
{
if (isset($this->listTemplates[$id])) {
return $id;
}
return null;
}
public function findTemplateByName(string $name): ?int
{
return $this->listTemplatesByName[$name] ?? null;
}
public function findPriceLevel($name)
{
if (isset($this->listPriceLevels[$name])) {
return $this->listPriceLevels[$name];
}
return null;
}
public function findPriceList($id)
{
if (isset($this->listPriceLists[$id])) {
return $this->listPriceLists[$id];
}
return null;
}
public function findPureProduct($code)
{
if (isset($this->listOfProducts[$code])) {
return $this->listOfProducts[$code];
}
$this->listOfProducts[$code] = sqlQueryBuilder()->select('id, price')
->fromProducts('p')
->where(\Query\Operator::equals(['code' => $code]))
->setMaxResults(1)
->execute()
->fetch();
return $this->listOfProducts[$code];
}
public function findParameter($name, &$value, &$product, $createIfNotExists = false, $ignoreMissingListValues = false)
{
if ($name == '') {
return 0;
}
if ($createIfNotExists && ($index = array_search(mb_strtolower($name, 'utf-8'), $this->listParameter)) === false) {
if (!$this->display) {
try {
$this->insertSQL('parameters', ['name' => $name, 'value_type' => 'list']);
// reload listParameter
$this->listParameterAll = Parameter::get();
foreach ($this->listParameterAll as $parameter) {
$this->listParameter[$parameter->id] = mb_strtolower($parameter->name, 'utf-8');
}
} catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) {
$name = sqlQueryBuilder()->select('name')->from('parameters')
->where(\Query\Operator::equals(['name' => $name, 'value_type' => 'list']))
->execute()->fetchColumn();
}
} else {
if (!in_array($name, $this->stats['parameters'])) {
$this->stats['parameters'][] = $name;
}
return 0;
}
}
if (($index = array_search(mb_strtolower($name, 'utf-8'), $this->listParameter)) !== false) {
$parameter = &$this->listParameterAll[$index];
if ($parameter->value_type == 'list') {
$searchValue = mb_strtolower($value, 'utf-8');
// Find parameter in list
foreach ($parameter->fetchListValues() as $listValue) {
if (mb_strtolower($listValue['value'], 'utf-8') == $searchValue) {
$value = $listValue['id'];
return $index;
}
}
if (!$this->display && !$ignoreMissingListValues) {
try {
$this->insertSQL('parameters_list', ['id_parameter' => $parameter->id, 'value' => $value]);
$value = sqlInsertId();
$parameter->fetchListValues(true);
} catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) {
$value = sqlFetchAssoc($this->selectSQL('parameters_list', ['value' => $value, 'id_parameter' => $parameter->id], ['id']))['id'];
}
return $index;
} else {
$product['error'][] = "Bude vytvořena hodnota parametru {$name}: {$value}";
return 0;
}
} elseif ($parameter['value_type'] == 'float') {
$value = str_replace(',', '.', $value);
}
return $index;
}
$product['error'][] = "Neexistujici parametr: {$name}";
return -1;
}
public function findOSSVatCategory(string $vatCategory): ?int
{
static $vatCategoriesCache = [];
if (empty($vatCategory)) {
return null;
}
if (($vatCategoriesCache[$vatCategory] ?? false) === false) {
$vatCategoriesCache[$vatCategory] = $this->getVatsUtil()->getCNKey($vatCategory);
}
return $vatCategoriesCache[$vatCategory];
}
public function findCategory(&$categories, &$parentCat = null)
{
// Get next category part
$categoryName = array_shift($categories);
if ($categoryName === null) { // Terminate recurse
return $parentCat['id'];
}
if (empty($categoryName)) {
return $this->findCategory($categories, $parentCat);
}
if (!$this->sectionTree) {
$this->sectionTree = ServiceContainer::getService(SectionTree::class);
}
// Get parent ID and menu
$parentId = null;
if ($parentCat) {
/** @var $menu ArrayCollection */
$menu = $parentCat->getChildren();
$parentId = $parentCat['id'];
} else {
// always load category tree in default language
$menu = &$this->sectionTree->getTree(
Contexts::get(LanguageContext::class)->getDefaultId()
);
}
// Find matching section
$found = null;
if (!empty($menu)) {
foreach ($menu as &$category) {
if (mb_strtolower($category['title']) == mb_strtolower($categoryName)) {
$found = &$category;
break;
}
}
}
// If not found, create one
if (empty($found)) {
if (!$this->display) {
// TODO: tmp logging - kvuli bikepornu, kde se uplne haluzne vytvori sekce, ktera uz existuje
$logger = ServiceContainer::getService('logger');
$logMenu = $menu;
if ($logMenu instanceof ArrayCollection) {
$logMenu = $logMenu->toArray();
}
$logger->notice('[AutomaticImport] Creating new section', [
'categoryName' => $categoryName,
'categories' => $categories,
'parentId' => $parentId,
'menuIds' => array_keys($logMenu),
]);
$this->insertSQL('sections', ['name' => $categoryName, 'lead_figure' => 'N', 'behaviour' => 2]);
$catId = sqlInsertId();
$this->insertSQL('sections_relation', ['id_section' => $catId, 'id_topsection' => $parentId, 'position' => 99]);
} else {
$this->stats['category'][] = $categoryName;
$catId = -1;
}
$found = new Section();
$found->setId($catId)->setName(mb_strtolower($categoryName));
// if parent section is present, $menu is ArrayCollection -> use add method
if ($parentCat) {
$menu->add($found);
} else {
$menu[] = &$found;
}
}
return $this->findCategory($categories, $found);
}
public function findDeliveryTime($avail, &$product)
{
global $cfg;
$delTimes = $cfg['Products']['DeliveryTime'];
if (is_numeric($avail)) {
if ($avail <= 0 && empty($delTimes[$avail])) {
$product['error'][] = "Neexistujici dostupnost: {$avail}";
}
} else {
$avail = array_search($avail, $delTimes);
if ($avail === false) {
$product['error'][] = "Neexistujici dostupnost: {$avail}";
}
}
$product['delivery_time'] = intval($avail);
if ($avail <= 0) {
$product['delivery_time_text'] = getVal($avail, $delTimes);
} else {
$product['delivery_time_text'] = sprintf($delTimes['1'], strval($avail));
}
}
public function findUnitId($unitName)
{
$unitName = mb_strtolower($unitName, 'utf-8');
if (!empty($this->listUnits[$unitName])) {
return $this->listUnits[$unitName];
}
return $this->listUnits[$unitName] = sqlGetConnection()->transactional(function () use ($unitName) {
sqlQueryBuilder()
->insert('products_units')
->directValues([
'short_name' => $unitName,
'short_name_admin' => $unitName,
])
->execute();
return sqlInsertId();
});
}
public function trySearch(&$SQL, &$search, &$tried, $data = '')
{
$tried = true;
if (!empty($SQL)) {
return $SQL;
}
$SQL = sqlQuery(join(' UNION ', $search), $data);
$search = [];
if (sqlNumRows($SQL) > 0) {
return $SQL;
} else {
sqlFreeResult($SQL);
$SQL = null;
}
}
public function findProduct(&$product, &$item = null)
{
$SQL = null;
$tried = false;
if (!empty($product['code'])) {
if ($pb = ($this->productsBatch[$product['code']] ?? false)) {
$product['id_product'] = $pb['id_product'];
$product['id_variation'] = $pb['id_variation'];
$product['id_pos'] = $pb['id_pos'];
$product['empty_short_descr'] = $pb['empty_short_descr'];
$product['empty_long_descr'] = $pb['empty_long_descr'];
$product['empty_parameters'] = $pb['empty_parameters'];
$product['empty_price'] = $pb['empty_price'];
$product['empty_price_buy'] = $pb['empty_price_buy'] ?? '';
$product['status'] = 'aktualizace';
if ($this->add_new == 2) {
$product['status'] = 'ignorován';
} else {
$product['sync'] = true;
}
// Bojim se to cely upravovat, tak je to tady proste duplicitne no.
if ($this->display) {
$this->stats['products_updated']++;
$this->updatedCreatedProducts[] = $product;
}
return;
} elseif ($this->add_new > 0 && $this->add_new != 3) {
$product['status'] = 'nový';
$product['sync'] = true;
}
$search[] = 'SELECT DISTINCT id_product, id_variation, pos.id AS id_pos, p.figure="Y" visible
FROM '.getTableName('products_of_suppliers').' pos
LEFT JOIN '.getTableName('products').' p ON pos.id_product = p.id
WHERE pos.code=:code AND id_supplier=:id_supplier';
$this->trySearch($SQL, $search, $tried, ['code' => $product['code'], 'id_supplier' => $this->id_supplier]);
}
if ($this->pair) {
if (!empty($product['code'])) {
if (!empty($GLOBALS['cfg']['Modules']['products_variations']['variationCode'])) {
$search[] = 'SELECT DISTINCT pv.id_product, pv.id, NULL, p.figure="Y" visible FROM '.getTableName('products_variations').' pv LEFT JOIN '.getTableName('products').' p ON pv.id_product = p.id WHERE pv.code=:code';
}
$search[] = 'SELECT DISTINCT p.id, NULL, NULL, p.figure="Y" visible FROM '.getTableName('products').' p WHERE p.code=:code';
$this->trySearch($SQL, $search, $tried, ['code' => $product['code']]);
}
if (!empty($product['ean'])) {
$search[] = 'SELECT DISTINCT id_product, id_variation, pos.id AS id_pos, p.figure="Y" visible
FROM products_of_suppliers pos
LEFT JOIN products p ON pos.id_product = p.id
WHERE pos.ean=:ean AND id_supplier=:id_supplier';
$search[] = 'SELECT DISTINCT p.id, NULL, NULL, p.figure="Y" visible FROM products p WHERE p.ean=:ean';
$search[] = 'SELECT DISTINCT pv.id_product, pv.id, NULL, p.figure="Y" visible FROM products_variations pv LEFT JOIN products p ON pv.id_product = p.id WHERE pv.ean=:ean';
$this->trySearch($SQL, $search, $tried, ['ean' => $product['ean'], 'id_supplier' => $this->id_supplier]);
}
}
if (!$tried) {
$product['error'][] = 'Nelze najit udaj pro sparovani - EAN nebo kod produktu.';
return null;
}
if (sqlNumRows($SQL) > 1) {
$product['error'][] = 'Danému kódu/ean odpovídá více položek.'.print_r($search, true);
return null;
}
if (sqlNumRows($SQL) <= 0) {
if ($this->add_new > 0 && $this->add_new != 3) {
if ($this->display) {
$this->stats['products_created']++;
$this->updatedCreatedProducts[] = $product;
}
$product['status'] = 'nový';
$product['sync'] = true;
} else {
$product['status'] = 'ignorován';
}
return null;
} elseif ($this->add_new == 2) {
$product['status'] = 'ignorován';
$product['sync'] = false;
$product['id_product'] = -1;
return null;
} else {
$product['sync'] = true;
}
$ids = sqlFetchArray($SQL);
$product['id_product'] = $ids[0];
if (!empty($ids[1])) {
$product['id_variation'] = $ids[1];
}
if (!empty($ids[2])) {
$product['id_pos'] = $ids[2];
}
if (!isset($product['status'])) {
$product['status'] = 'aktualizace';
}
if (findModule('kupkolo')) {
// TODO: Big ugly HACK
if ($ids[3] != 1) {
$product['error'][] = 'nezobrazovaný produkt';
}
}
if ($this->display) {
$this->stats['products_updated']++;
$this->updatedCreatedProducts[] = $product;
}
}
public function findVariation(&$product, &$product_variation)
{
$SQL = sqlQuery('SELECT id_product, id_variation, id AS id_pos FROM '.getTableName('products_of_suppliers').' pos WHERE pos.code=:code AND id_supplier=:id_supplier', ['code' => $product_variation['code'], 'id_supplier' => $this->id_supplier]);
if (sqlNumRows($SQL) <= 0 && $this->pair) {
$search = [];
$search_data = [];
/*
if (!empty($product_variation['ean']))
{
$search_data['ean'] = $product_variation['ean'];
$search[] = 'SELECT DISTINCT pv.id_product, pv.id FROM ' . getTableName('products_variations') . ' pv WHERE pv.ean=:ean';
}
*/
if (!empty($product_variation['ean'])) {
$search_data['ean'] = $product_variation['ean'];
$search_data['id_supplier'] = $this->id_supplier;
$search[] = 'SELECT id_product, id_variation FROM products_of_suppliers pos WHERE pos.ean=:ean AND id_supplier=:id_supplier';
$search[] = 'SELECT DISTINCT pv.id_product, pv.id FROM products_variations pv WHERE pv.ean=:ean';
}
if (!empty($GLOBALS['cfg']['Modules']['products_variations']['variationCode'])) {
if (!empty($product_variation['code'])) {
$search_data['code'] = $product_variation['code'];
$search[] = 'SELECT DISTINCT pv.id_product, pv.id FROM '.getTableName('products_variations').' pv WHERE pv.code=:code';
}
}
if (!empty($product['id_product']) && !empty($product_variation['variation'])) {
// Discover whether variation already exists
$query = 'SELECT DISTINCT pv.id_product, pv.id
FROM '.getTableName('products_variations').' AS pv ';
$queryWhere = 'WHERE pv.id_product=:id_product ';
$search_data['id_product'] = $product['id_product'];
$valid = true;
foreach ($product_variation['variation'] as $variant => $value) {
if ($variant <= 0) {
$valid = false;
}
$query .= 'LEFT JOIN '.getTableName('products_variations_combination').' AS v'.$variant.' ON pv.id=v'.$variant.'.id_variation and v'.$variant.'.id_label='.$variant.'
LEFT JOIN '.getTableName('products_variations_choices_values').' AS vv'.$variant.' ON v'.$variant.'.id_value=vv'.$variant.'.id ';
$queryWhere .= ' AND vv'.$variant.".code=:code_{$variant} ";
$search_data["code_{$variant}"] = $value;
}
if ($valid) {
$search[] = $query.$queryWhere;
}
}
if (!empty($search)) {
// $product_variation['error'][] = "Nelze najit udaj pro sparovani - EAN nebo kod produktu.";
// return null;
// var_dump([join(" UNION ", $search), $search_data]);
$SQL = sqlQuery(join(' UNION ', $search), $search_data);
}
}
if (sqlNumRows($SQL) > 1) {
$product_variation['error'][] = 'Danému kódu/ean odpovídá více položek.';
return null;
}
if (sqlNumRows($SQL) <= 0) {
if ($this->add_new > 0) {
if ($this->display) {
$this->stats['variations_created']++;
}
$product_variation['status'] = 'nový';
if (!isset($product['sync'])) {
$product['sync'] = false;
}
$product_variation['sync'] = true;
} else {
$product_variation['status'] = 'ignorován';
}
return null;
} elseif ($this->add_new == 2) {
$product_variation['status'] = 'ignorován';
return null;
}
if (!isset($product['sync'])) {
$product['sync'] = false;
}
$product_variation['sync'] = true;
$ids = sqlFetchArray($SQL);
if (!empty($product['id_product']) && $product['id_product'] != $ids['id_product']) {
$product_variation['error'][] = "Spatny kod produktu! {$product['id_product']} != {$ids['id_product']}";
}
$product['id_product'] = $ids['id_product'];
if (!empty($ids[2])) {
$product_variation['id_pos'] = $ids[2];
} else {
$product_variation['id_pos'] = null;
}
$product_variation['id_variation'] = $ids[1];
$product_variation['status'] = 'aktualizace';
if ($this->display) {
$this->stats['variations_updated']++;
}
}
public function updateDB()
{
if ($this->updateDB) {
sqlQuery('UPDATE '.getTableName('import')."
SET last_sync=NOW(), last_count={$this->stats['products']}
WHERE id={$this->id}");
}
}
public function removeDuplicates(): void
{
if (!getVal('remove_duplicate_products', $this->params, false)) {
return;
}
$posTable = ['products_of_suppliers', 'pos'];
$duplicates = sqlQueryBuilder()
->select('pos.id_product, pos.id_variation, pos.id_supplier, count(*) as qty')
->from(...$posTable)
->groupBy('pos.id_product, pos.id_variation, pos.id_supplier')
->having('qty > 1')
->execute()
->fetchAllAssociative();
$deleteIds = [];
foreach ($duplicates as $duplicate) {
$tmp = sqlQueryBuilder()
->select('pos.id')
->from(...$posTable)
->andWhere(
Operator::equalsNullable([
'pos.id_product' => $duplicate['id_product'],
'pos.id_variation' => $duplicate['id_variation'],
])
)
->andWhere(Operator::equals(['pos.id_supplier' => $duplicate['id_supplier']]))
->orderBy('pos.last_sync', 'DESC')
->execute()
->fetchFirstColumn();
array_shift($tmp);
$deleteIds = array_merge($deleteIds, $tmp);
}
if ($deleteIds) {
$rowCount = sqlQueryBuilder()
->delete(...$posTable)
->where(Operator::inIntArray($deleteIds, 'pos.id'))
->execute();
$this->addActivityLog(
ActivityLog::SEVERITY_NOTICE,
"Při automatické importu bylo z products_of_suppliers odstraněno: {$rowCount} duplicitních řádků.",
$duplicates
);
}
}
public function showUpdatedProducts()
{
if (!getVal('show_updated_products', $this->params, false)) {
return;
}
sqlQueryBuilder()
->update('products', 'p')
->leftJoin('p', 'products_of_suppliers', 'pos', 'pos.id_product = p.id')
->directValues(['figure' => 'Y'])
->where('p.in_store > 0')
->andWhere(Operator::equals(['p.figure' => 'O']))
->andWhere(Operator::equals(['id_supplier' => $this->id_supplier]))
->andWhere('pos.last_sync > NOW() - INTERVAL '.ceil($this->delete_old * 24).' HOUR AND pos.last_sync IS NOT NULL')
->execute();
}
public function deleteOldProducts()
{
if (!(float) $this->delete_old) {
return;
}
$this->removeDuplicates();
$SQL = sqlQuery('SELECT pos.id, pos.id_product, pos.id_variation, p.title title_product, pv.title title_variation, TIMESTAMPDIFF(HOUR, pos.last_sync, NOW()) as age,
(
SELECT COUNT(*)
FROM products_of_suppliers pos2
WHERE pos2.id_product=pos.id_product AND ((pos.id_product IS NULL AND pos2.id_variation IS NULL) OR pos2.id_variation=pos.id_variation)
) as "count"
FROM products_of_suppliers pos
LEFT JOIN products p ON p.id=pos.id_product
LEFT JOIN products_variations pv ON pv.id=pos.id_variation
WHERE (pos.last_sync < NOW() - INTERVAL '.ceil($this->delete_old * 24)." HOUR OR pos.last_sync IS NULL) AND id_supplier={$this->id_supplier}");
while (($productToDelete = sqlFetchAssoc($SQL)) !== false) {
if (!$this->display) {
// Delete product/variation only if in_store is set directly to product. Otherwise delete only products_of_suppliers entry
if ($this->modify_in_store) {
if (getVal('force_delete', $this->params, false)) {
if ($productToDelete['count'] <= 1) {
$product = new Product($productToDelete['id_product']);
$product->deleteVariation($productToDelete['id_variation']);
if (getVal('force_delete_product', $this->params, false)) {
$product->delete();
}
}
sqlQuery('DELETE FROM products_of_suppliers WHERE id=:id', ['id' => $productToDelete['id']]);
} elseif ($delivery = getVal('set_delivery', $this->params, false)) {
$cfg = Config::get();
if (!empty($cfg['Products']['DeliveryTime'][$delivery])) {
if (empty($productToDelete['id_variation'])) {
$this->updateSQL('products', ['delivery_time' => $delivery], ['id' => $productToDelete['id_product']]);
} else {
$this->updateSQL('products_variations', ['delivery_time' => $delivery], ['id' => $productToDelete['id_variation']]);
}
}
} elseif (getVal('hide_products', $this->params, false)) {
if (empty($productToDelete['id_variation'])) {
$this->updateSQL('products', ['figure' => 'O'], ['id' => $productToDelete['id_product']]);
} else {
$this->updateSQL('products_variations', ['figure' => 'O'], ['id' => $productToDelete['id_variation']]);
}
} else {
if (empty($productToDelete['id_variation'])) {
sqlQuery('UPDATE products SET in_store=0 WHERE id=:id_product', $productToDelete);
} else {
sqlQuery('UPDATE products_variations SET in_store=0 WHERE id=:id_variation', $productToDelete);
}
}
} else {
sqlQuery('UPDATE products_of_suppliers SET in_store=0 WHERE id=:id', ['id' => $productToDelete['id']]);
if ($deleteOld = getVal('really_delete_old', $this->params)) {
if ($productToDelete['age'] > $deleteOld * 24) {
sqlQuery('DELETE FROM products_of_suppliers WHERE id=:id', ['id' => $productToDelete['id']]);
}
} elseif (getVal('hide_products', $this->params, false)) {
if (empty($productToDelete['id_variation'])) {
$this->updateSQL('products', ['figure' => 'O'], ['id' => $productToDelete['id_product']]);
} else {
$this->updateSQL('products_variations', ['figure' => 'O'], ['id' => $productToDelete['id_variation']]);
}
} elseif ($delivery = getVal('set_delivery', $this->params, false)) {
$cfg = Config::get();
if (!empty($cfg['Products']['DeliveryTime'][$delivery])) {
if (empty($productToDelete['id_variation'])) {
$this->updateSQL('products', ['delivery_time' => $delivery], ['id' => $productToDelete['id_product']]);
} else {
$this->updateSQL('products_variations', ['delivery_time' => $delivery], ['id' => $productToDelete['id_variation']]);
}
}
} elseif (getVal('reset_products_stock', $this->params, false)) {
if (empty($productToDelete['id_variation'])) {
sqlQuery('UPDATE products SET in_store=0 WHERE id=:id_product', $productToDelete);
} else {
sqlQuery('UPDATE products_variations SET in_store=0 WHERE id=:id_variation', $productToDelete);
}
if (findModule(Modules::STORES) && ($resetStores = getVal('reset_products_stock', $this->params)['storeIds'] ?? [])) {
$qb = sqlQueryBuilder()
->update('stores_items')
->directValues(['quantity' => 0])
->andWhere(Operator::inIntArray($resetStores, 'id_store'));
if (empty($productToDelete['id_variation'])) {
$qb->andWhere(Operator::equalsNullable(['id_product' => $productToDelete['id_product'], 'id_variation' => null]));
} else {
$qb->andWhere(Operator::equals(['id_product' => $productToDelete['id_product'], 'id_variation' => $productToDelete['id_variation']]));
}
$qb->execute();
}
}
}
} else {
if ($productToDelete['count'] > 1) {
continue;
}
if ($deleteOld = getVal('really_delete_old', $this->params)) {
if ($productToDelete['age'] > $deleteOld * 24) {
$age = round($productToDelete['age'] / 24);
echo "Skutecne smazat {$age} dnu nesynchronizovany produkt: {$productToDelete['title_product']}{$productToDelete['title_variation']}
";
}
}
// Find product/variation in curretly synced ones
$found = false;
foreach ($this->products as $product) {
if (getVal('id_product', $product) == $productToDelete['id_product']) {
if (!empty($product['variations']) && !empty($productToDelete['id_variation'])) {
foreach ($product['variations'] as $variation) {
if (isset($variation['id_variation']) && $variation['id_variation'] == $productToDelete['id_variation']) {
$found = true;
break;
}
}
if ($found) {
break;
} else {
continue;
}
}
$found = true;
}
}
if ($found) {
continue;
}
}
// Not found, should be deleted
$this->stats['deleted'][$productToDelete['title_product']][] = $productToDelete['title_variation'];
}
}
public function getDebugData()
{
$fields = [
'import_error' => [
'name' => 'Chyba importu',
'size' => 5,
],
'id_product' => [
'name' => 'ID Prod.',
],
'id_variation' => [
'name' => 'ID Var.',
],
'id_pos' => [
'name' => 'ID POS',
],
'variation_text' => [
'name' => 'Varianta',
'size' => 2,
],
'shop_title' => [
'name' => 'Název v shopu',
'size' => 5,
],
'title' => [
'name' => 'Název',
'size' => 5,
],
'similarity' => [
'name' => 'Podobnost',
],
'code' => [
'name' => 'Kód',
],
'product_code' => [
'name' => 'Kód produktu',
],
'ean' => [
'name' => 'EAN',
],
'ean_supplier' => [
'name' => 'EAN dodavatele',
],
'short_descr' => [
'name' => 'Anotace',
'size' => 5,
],
'long_descr' => [
'name' => 'Popis',
'size' => 5,
],
'meta_title' => [
'name' => 'SEO Titulek',
],
'meta_description' => [
'name' => 'SEO Popis',
],
'parameters' => [
'name' => 'Parametry',
],
'price' => [
'name' => 'Cena bez DPH',
],
'price_buy' => [
'name' => 'Cena nákupní',
],
'price_sell' => [
'name' => 'Cena prodejní',
],
'forecasted_delivery_date' => [
'name' => 'Na cestě - Dostupné od',
],
'forecasted_delivery_pieces' => [
'name' => 'Na cestě - Počet kusů',
],
'discount' => [
'name' => 'Sleva',
],
'photos' => [
'name' => 'Foto',
],
'templates' => [
'name' => 'Šablony produktů',
],
'vat_text' => [
'name' => 'DPH',
],
'price_common' => [
'name' => 'Škrtlá cena',
],
'producer_text' => [
'name' => 'Výrobce',
],
'in_store' => [
'name' => 'Skladem',
],
'in_store_min' => [
'name' => 'Minimální počet skladem',
],
'campaign_text' => [
'name' => 'Kampan',
],
'weight' => [
'name' => 'Hmotnost',
],
'delivery_time_text' => [
'name' => 'Dostupnost',
],
'category' => [
'name' => 'Kategorie',
],
'figure' => [
'name' => 'Zobrazovat',
],
'id_cn' => [
'name' => 'OSS kategorie',
],
'status' => [
'name' => 'Stav',
],
'product_parameters' => [
'name' => 'Seznam parametrů',
],
'price_levels' => [
'name' => 'Cenová hladina',
],
'guarantee' => [
'name' => 'Záruka',
],
'error' => [
'name' => 'Chyba',
'size' => 5,
],
'data' => [
'name' => 'Data',
],
];
$products = $this->products;
if (getVal('checkNames')) {
$products = array_filter($products, function ($x) {
return !empty($x['id_product']);
});
foreach ($products as &$product) {
$product['shop_title'] = returnSQLResult("SELECT CONCAT_WS(' - ', p.title, pv.title)
FROM ".getTableName('products').' p
LEFT JOIN '.getTableName('products_variations')." pv ON pv.id_product = p.id
WHERE p.id={$product['id_product']} AND pv.id".(empty($product['id_variation']) ? ' IS NULL' : "={$product['id_variation']}"));
// $product['id_product'] = "{$product['id_product']}";
$p = 0;
similar_text($product['title'], $product['shop_title'], $p);
$product['similarity'] = round($p, 1);
}
usort($products, function ($a, $b) {
return $a['similarity'] - $b['similarity'];
});
}
$columns = [];
$columnsVariations = [];
foreach ($products as $product) {
$columns = array_unique(array_merge($columns, array_keys($product)));
if (isset($product['variations'])) {
foreach ($product['variations'] as $variation) {
$columnsVariations = array_unique(array_merge($columnsVariations, array_keys($variation)));
}
}
}
$columns = array_filter($columns, function ($x) use ($fields) {
return isset($fields[$x]);
});
$columnsVariations = array_filter($columnsVariations, function ($x) use ($fields) {
return isset($fields[$x]);
});
$field_keys = array_keys($fields);
usort($columns, function ($a, $b) use ($field_keys) {
return array_search($a, $field_keys) - array_search($b, $field_keys);
});
usort($columnsVariations, function ($a, $b) use ($field_keys) {
return array_search($a, $field_keys) - array_search($b, $field_keys);
});
return [
'columns' => $columns,
'columnsVariations' => $columnsVariations,
'fields' => $fields,
'products' => $products,
];
}
public function debugPrintValue($name, $product)
{
if (!isset($product[$name])) {
return;
}
$value = $product[$name];
switch ($name) {
case 'id_product':
echo "{$value}";
break;
case 'photos':
if ($value) {
foreach ($value as $photo) {
?>
', $value) : $value;
break;
case 'product_parameters':
foreach ($value as $parameter_id => $base_parameter) {
foreach ($base_parameter as $parameter_value) {
$parameter = $this->listParameterAll[$parameter_id];
echo "{$parameter->name}:";
if ($parameter->value_type == 'list') {
echo $parameter->fetchListValues()[$parameter_value]['value'];
} else {
echo $parameter_value;
}
echo '
';
}
}
break;
case 'figure':
echo $value == 'N' ? 'Ne' : ($value == 'O' ? 'Ukončen' : 'Ano');
break;
case 'long_descr':
case 'short_descr':
case 'parameters':
case 'meta_description':
$strip_value = strip_tags($value);
if (strlen($strip_value) > 100) {
echo mb_substr(print_r($strip_value, true), 0, 50).'...';
break;
}
// Fall-through
// no break
default:
if (is_float($value)) {
echo round($value, 1);
} else {
print_r($value);
}
}
}
public function parseParams()
{
if ($this->params) {
$this->params = json_decode($this->params, true);
if (!$this->params) {
$this->error .= 'Can not parse parameters: '.json_last_error();
return false;
}
} else {
$this->params = [];
}
return true;
}
/**
* @return Downloader
*/
protected function getDownloader()
{
static $downloader = null;
if (is_null($downloader)) {
$downloader = new Downloader();
if (getVal('ftp', $this->params, false)) {
$downloader->setMethod('ftp');
} elseif (getVal('sftp', $this->params, false)) {
$downloader->setMethod(getVal('sftp', $this->params, false));
}
if (getVal('curl', $this->params, false)) {
$downloader->setMethod('curl');
if (getVal('bearer_token', $this->params, false)) {
$downloader->setCurlHeader('Authorization: Bearer '.getVal('bearer_token', $this->params, false));
}
if (getVal('user_agent', $this->params, false)) {
$downloader->setCurlHeader('User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36');
}
}
}
return $downloader;
}
protected function extractZIP($sourceFile)
{
$zip = new ZipArchive();
if ($zip->open($sourceFile) === true) {
$pathFinder = PathFinder::getService();
$zipFolder = $pathFinder->getTmpDir().'automaticImportZip/';
$zip->extractTo($zipFolder);
$zip->close();
$xml = simplexml_load_file($zipFolder.$this->params['filename']);
FileUtil::deleteDir($zipFolder);
return $xml;
} else {
return false;
}
}
/**
* @return array
*/
protected function parseCategoryParts($product)
{
$parts = explode('/', $product['category']);
foreach ($parts as &$part) {
$part = trim($part);
}
return $parts;
}
protected function customProductUpdate($product, $variation = null)
{
}
protected function addStoreItem(&$outputItem, $inputItem)
{
foreach ($inputItem->STORE ?? [] as $store) {
$storeId = intval($store['id']);
$storeQuantity = strval($store);
if ($storeId && $this->storeExists($storeId)) {
$outputItem['stores'][] =
array_merge([
'id' => $storeId,
'quantity' => $storeQuantity,
],
isset($store['min_quantity'])
? ['min_quantity' => intval($store['min_quantity'])]
: [],
isset($store['increment_quantity'])
? ['increment_quantity' => strval($store['increment_quantity']) == 'true']
: []);
}
}
}
protected function storeExists($storeId)
{
if (is_null($this->stores)) {
$this->storeService = ServiceContainer::getService(\KupShop\StoresBundle\Utils\StoresInStore::class);
$this->stores = $this->storeService->getStores();
}
return !empty($this->stores[$storeId]);
}
/**
* @param $updateFields - product fields
* @param $variationFields - variation fields
*/
protected function modifyFields(&$updateFields, &$variationFields)
{
}
/**
* @param $product_variation - array of loaded variation fields
* @param $variation - xml variation element
*/
protected function addCustomVariationLoad(&$product_variation, $variation)
{
}
/**
* @param $product - array of loaded product fields
* @param $item - xml product element
*/
protected function addCustomProductLoad(&$product, $item)
{
}
protected function updateStoreItem($store_product, $product, $variation = null): void
{
if (!$this->loggingContext) {
$this->loggingContext = ServiceContainer::getService(LoggingContext::class);
}
$callback = function () use ($store_product, $product, $variation) {
$this->storeService->updateStoreItem(
array_merge([
'quantity' => $store_product['quantity'],
'id_store' => $store_product['id'],
'id_product' => $product['id_product'],
'id_variation' => $variation['id_variation'] ?? null,
],
isset($store_product['min_quantity'])
? ['min_quantity' => $store_product['min_quantity']]
: []),
$store_product['increment_quantity'] ?? false
);
};
$this->loggingContext->activateAutomaticImport($this->id, $callback);
}
protected function automaticUpdateFlagForNewProducts()
{
Product::automaticUpdateFlagForNewProducts();
}
protected function addActivityLog($severity, $msg, $data)
{
addActivityLog($severity, ActivityLog::TYPE_IMPORT, $msg, $data);
}
protected function productsBatchLoad($xml)
{
$this->productsBatch = [];
$pairElement = strtoupper($this->batchPairElement);
$batch = [];
foreach ($xml->SHOPITEM as $ITEM) {
if (!empty($ITEM->{$pairElement})) {
$batch[] = trim(strval($ITEM->{$pairElement}));
}
if (!empty($ITEM->VARIATIONS)) {
foreach ($ITEM->VARIATIONS->VARIATION as $VARIATION) {
if (!empty($VARIATION->{$pairElement})) {
$batch[] = trim(strval($VARIATION->{$pairElement}));
}
}
}
}
$chunks = array_chunk($batch, 5000);
foreach ($chunks as $chunk) {
$this->productsBatch = $this->productsBatch + sqlFetchAll($this->productsBatchQuery($chunk), $this->batchPairElement);
}
}
protected function productsBatchQuery($chunk)
{
return $this->getProductBatchQueryBuilder($chunk)
->execute();
}
protected function getProductBatchQueryBuilder(array $chunk): Query\QueryBuilder
{
$qb = sqlQueryBuilder()
->select('pos.id_product, pos.id_variation, pos.code, pos.id as id_pos',
"COALESCE(p.short_descr, '') = '' as empty_short_descr",
"COALESCE(p.long_descr, '') = '' as empty_long_descr",
"COALESCE(p.parameters, '') = '' as empty_parameters",
'(p.price = 0) as empty_price'
)
->from('products_of_suppliers', 'pos')
->leftJoin('pos', 'products', 'p', 'pos.id_product = p.id')
->andWhere(Operator::inStringArray(array_values($chunk), 'pos.code'))
->andWhere(Operator::equals(['pos.id_supplier' => $this->id_supplier]))
->groupBy('pos.id_product, pos.id_variation');
if (findModule(Modules::PRODUCTS, Modules::SUB_PRICE_BUY)) {
$qb->addSelect('(COALESCE(p.price_buy, 0) = 0) as empty_price_buy');
}
return $qb;
}
protected function getVatsUtil(): VatsUtil
{
if (!$this->vatsUtil) {
$this->vatsUtil = ServiceContainer::getService(VatsUtil::class);
}
return $this->vatsUtil;
}
protected function parseXMLFlagValue(?string $value, array &$product, $default = false, $mapping = []): bool|string|null
{
// If empty value, use default
if ($value === '' || $value === null) {
return $default;
}
// If numeric, assume positive=true, else false
if (is_numeric($value)) {
return intval($value) > 0;
}
$mapping = ['true' => true, 'false' => false, ...$mapping];
// If one of accepted values, use it
if (array_key_exists($value, $mapping)) {
return $mapping[$value];
}
// Report unsupported value and return default
$product['error'][] = "Neplatná hodnota příznaku: {$value}";
return $default;
}
protected function autoTranslatePreprocess(): void
{
foreach ($this->products as &$product) {
// pokud produkt nema byt synchronizovany, tak je zbytecny spoustet automaticky preklad
if (empty($product['sync'])) {
continue;
}
$product = $this->getAutoTranslateUtil()->preprocessProductData($this->params['autotranslate'], $product);
}
}
protected function isAutoTranslateEnabled(): bool
{
if (empty($this->params['autotranslate'])) {
return false;
}
if ($this->add_new !== self::PROCESS_TYPE_ADD) {
throw new RuntimeException('Autotranslate is supported only for process type `PROCESS_TYPE_ADD`');
}
$config = $this->params['autotranslate'];
$defaultLanguage = Contexts::get(LanguageContext::class)->getDefaultId();
if (empty($config['source']) || $config['source'] === $defaultLanguage) {
return false;
}
return true;
}
protected function getAutoTranslateUtil(): \KupShop\I18nBundle\Util\AutomaticImport\AutoTranslateUtil
{
return ServiceContainer::getService(\KupShop\I18nBundle\Util\AutomaticImport\AutoTranslateUtil::class);
}
protected function getTranslationService(string $translationClass): ITranslation
{
static $translationService = [];
if (!($translationService[$translationClass] ?? false)) {
/* @var \KupShop\I18nBundle\Translations\ITranslation $translationService */
$translationService[$translationClass] = \KupShop\KupShopBundle\Util\Compat\ServiceContainer::getService($translationClass);
}
return $translationService[$translationClass];
}
private function isFileChanged(string $url): bool
{
// kdyz bych fotku v db nenasel prohlasim ji za zmenenou aby se znovu stahla
if (!$photo = $this->getPhotoBySyncId($url)) {
return true;
}
$pathFinder = PathFinder::getService();
$localFile = $pathFinder->dataPath('photos/'.$photo['source'].$photo['image_2']);
// pokud soubor existuje lokalne, tak budu provadet porovnani lokalniho a remote souboru, abych zjistit, jestli tam je zmena
if ($localFile && file_exists($localFile)) {
$localFileHash = $this->getFileHash($localFile);
$remoteFileHash = $this->getFileHash($url);
// pokud hash nesedi, tak to znamena, ze se ten soubor nejak musel zmenit
if ($localFileHash === $remoteFileHash) {
return false;
}
}
return true;
}
protected function getPhotoBySyncId(string $syncId): ?array
{
static $cache;
if (!empty($cache[$syncId])) {
$photo = $cache[$syncId];
} else {
$photo = sqlQueryBuilder()->select('id, source, image_2')
->from('photos')
->where(Operator::equals(['sync_id' => $syncId]))
->execute()->fetchAssociative();
$cache[$syncId] = $photo;
}
if (empty($photo)) {
return null;
}
return $photo;
}
protected function getFileHash(string $file): ?string
{
$content = file_get_contents($file);
return $content ? md5($content) : null;
}
/**
* Updatuje fotky produktu, pokud se nejakym zpusobem zmenila zdrojove fotky z importu.
*/
protected function updatePhotos(int $productId, array $photos): void
{
foreach ($photos as $item) {
// kdyz fotku nemam v db chci ji stahnout
if (!$photo = $this->getPhotoBySyncId($item['url'])) {
continue;
}
$pathFinder = PathFinder::getService();
$dest = $pathFinder->dataPath('photos/'.$photo['source'].$photo['image_2']);
// pokud je na obrazku nejaka zmena, tak ho smazu aby se nasledne stahul znovu
if ($this->isFileChanged($item['url'])) {
ServiceContainer::getService(ImageLocator::class)->clearThumbnails($photo['id']);
sqlQueryBuilder()
->delete('photos')
->where(Operator::equals(['id' => $photo['id']]))
->execute();
if (file_exists($dest)) {
unlink($dest);
}
}
}
}
}
if (empty($subclass)) {
class AutomaticImport extends AutomaticImportBase
{
}
}