Files
kupshop/class/class.AutomaticImport.php
2025-08-02 16:30:27 +02:00

3982 lines
156 KiB
PHP
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
use Doctrine\Common\Collections\ArrayCollection;
use KupShop\AdminBundle\Util\ActivityLog;
use KupShop\CatalogBundle\Entity\Section;
use KupShop\CatalogBundle\Event\ProductEvent;
use KupShop\CatalogBundle\Parameters\ParameterFinder;
use KupShop\CatalogBundle\Section\SectionTree;
use KupShop\ContentBundle\Util\ImageLocator;
use KupShop\I18nBundle\Translations\ITranslation;
use KupShop\I18nBundle\Translations\ParametersListTranslation;
use KupShop\I18nBundle\Translations\ParametersTranslation;
use KupShop\I18nBundle\Translations\ProductsTranslation;
use KupShop\KupShopBundle\Config;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\Database\QueryHint;
use KupShop\KupShopBundle\Util\FileUtil;
use KupShop\KupShopBundle\Util\Functional\Mapping;
use KupShop\KupShopBundle\Util\LoggingContext;
use KupShop\KupShopBundle\Util\System\PathFinder;
use KupShop\OSSVatsBundle\Util\VatsUtil;
use Query\Operator;
ini_set('memory_limit', '2048M');
#[AllowDynamicProperties]
class AutomaticImportBase
{
use \KupShop\AdminBundle\Util\CategoryTree;
/**
* Supported file formats for import.
*/
public const TYPE_XML = 0;
public const TYPE_XLS = 1;
public const TYPE_CSV = 2;
public const TYPE_CycloConnect = 3;
public const TYPE_WinShop = 4;
public const TYPE_DBF = 5;
public const TYPE_WinoraConnect = 6;
public const TYPE_AspireConnect = 7;
public const TYPE_ZIP = 8;
public const TYPE_JSON = 9;
/** Pouze aktualizovat stávající */
public const PROCESS_TYPE_UPDATE = 0;
/** Přidat nové i aktualizovat stávající */
public const PROCESS_TYPE_ADD_AND_UPDATE = 1;
/** Pouze přidat nové */
public const PROCESS_TYPE_ADD = 2;
/** Přidat a aktualizovat pouze varianty */
public const PROCESS_TYPE_ADD_AND_UPDATE_VARIATIONS = 3;
public static $types = [
AutomaticImport::TYPE_XML => '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<br>\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<br>\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<br>\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<br>\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']}<br>";
}
}
// 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&nbsp;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'] = "<a href=\"javascript:nw('product', '{$product['id_product']}', 'noopener');\">{$product['id_product']}</a>";
$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 "<a href='javascript:nw(\"product\", {$value})'>{$value}</a>";
break;
case 'photos':
if ($value) {
foreach ($value as $photo) {
?>
<a href="<?php echo $photo['url']; ?>" target="_blank"><?php echo basename($photo['url']); ?></a><br>
<?php
}
}
break;
case 'import_error':
case 'error':
echo is_array($value) ? join('<br>', $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 '<br>';
}
}
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
{
}
}