3982 lines
156 KiB
PHP
3982 lines
156 KiB
PHP
<?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 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
|
||
{
|
||
}
|
||
}
|