'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 { } }