downloader = new stFileDownloader(); $this->logger = $import->getLogger(); $this->import = $import; } /** * Zwraca instancje klasy do pobierania plików * * @return stFileDownloader */ public function getFileDownloader(): stFileDownloader { return $this->downloader; } /** * Zwraca utworzony produkt * * @param string $code Kod produktu * @param string $culture Opcjonalna wersja językowa (domyślnie brany jest domyślny język dla panelu administracyjnego) * @return Product |null * @throws PropelException */ public function getProduct(string $code, ?string $culture = null): ?Product { $code = $this->addProductCodePrefix($code); $product = ProductPeer::retrieveByCode($code); if (null !== $product) { $product->setCulture(null !== $culture ? $culture : stLanguage::getOptLanguage()); } if (null !== $product) { $taskSchedulerImportProduct = stTaskSchedulerImportProductPeer::retrieveByProduct($product, $this->import); if (null === $taskSchedulerImportProduct) { $taskSchedulerImportProduct = stTaskSchedulerImportProductPeer::create($product, $this->import); } $taskSchedulerImportProduct->setAvailable(true); if (!$taskSchedulerImportProduct->isNew() && $taskSchedulerImportProduct->isColumnModified(stTaskSchedulerImportProductPeer::AVAILABLE)) { if ($this->import->getConfigurationParameter('withdraw_availability') == 'set_availability') { $product->setAvailabilityId($this->import->getConfigurationParameter('default_availability')); } if (!$product->getActive()) { $product->setActive($this->import->getConfigurationParameter('default_product_status', true)); } } } return $product; } /** * Zwraca utworzony lub tworzy i zwraca nową instancję produktu * * @param string $code Kod produktu * @param string $culture Opcjonalna wersja językowa (domyślnie brany jest domyślny język dla panelu administracyjnego) * @return Product * @throws PropelException */ public function getOrCreateProduct(string $code, ?string $culture = null): Product { $product = $this->getProduct($code, $culture); if (null === $product) { $product = new Product(); $product->setTax(stTax::getDefault()); $product->setActive($this->import->getConfigurationParameter('default_product_status', true)); $limits = stLimits::getInstance(); try { $limits->checkLimits($product); } catch (stLimitsException $e) { throw new stTaskSchedulerImportLogException("Limit produktów został przekroczony (maks. **%%limit%%**). Produkt o kodzie **%%code%%** nie został dodany.", stTaskLogger::TYPE_ERROR, [ '%%limit%%' => $limits->getLimit(Product::class), '%%code%%' => $code, ]); } $product->setCulture(null !== $culture ? $culture : stLanguage::getOptLanguage()); $product->setCode($this->addProductCodePrefix($code)); $product->setAvailabilityId($this->import->getConfigurationParameter('default_availability')); stTaskSchedulerImportProductPeer::create($product, $this->import); } return $product; } /** * Zwraca cena + marża * * @param string $price Cena * @return string Cena + marża */ public function applyPriceMargin($price) { if (empty($price)) { return $price; } $price = strval($price); if ($price[0] == '-' || $price[0] == '+' || substr($price, -1) == '%') { return $price; } if ($this->import->getConfiguration()->getOption('version') > 1 && null !== $this->currentMapping && $this->currentMapping->hasPriceMargin()) { return $this->currentMapping->applyPriceMargin($price); } $priceMargin = $this->import->getConfigurationParameter('price_margin', [ 'value' => 0, 'type' => stPrice::PRICE_MARGIN_PERCENT, ]); $price = stPrice::applyPriceMargin($price, $priceMargin['value'], $priceMargin['type']); $price = $this->applyPriceArray($price); return $price; } public function addProductCodePrefix($code) { $prefix = $this->import->getConfigurationParameter('code_prefix'); return $prefix.$code; } /** * Dodaje zdjęcie do produktu * * @param Product $product * @param string $url * @param bool $default * @param null|string $filename * @return sfAsset|null * @throws Exception * @throws PropelException */ public function addImageFromUrl(Product $product, string $url, $default = false, ?string $filename = null): ?sfAsset { $asset = null; if ($product->isNew()) { throw new Exception("To add images product must be saved first"); } try { $this->downloader->download($url, function(stFileDownloaderFile $file) use ($product, $url, $filename, $default, &$asset) { if (null === $filename) { $matches = null; if (preg_match('#([a-z0-9-_]+).(jpeg|jpg|gif|png)#', $url, $matches)) { $filename = $matches[0]; } else { $filename = basename($file) . '.' . $file->getExtension(); } } $pha = new ProductHasSfAsset(); $pha->setProductId($product->getId()); $pha->createAsset($filename, $file, ProductHasSfAssetPeer::IMAGE_FOLDER); $pha->setIsDefault($default); $pha->save(); $asset = $pha->getsfAsset(); }); } catch (stFileDownloaderException $e) { $this->logger->error('Wystąpił błąd `%error%` podczas pobierania zdjęcia <%image%> dla produktu **%product%**', array( '%error%' => $e->getMessage(), '%image%' => $url, '%product%' => $product->getName() )); } return $asset; } /** * Dodaje dostępność do produktu * * @param Product $product Produkt * @param string $name Nazwa dostępności * @param bool $create * @return Availability * @throws PropelException */ public function addAvailability(Product $product, string $name, bool $create = true): Availability { $availability = AvailabilityPeer::retrieveByName($name); if (null === $availability) { if ($create) { $availability = new Availability(); $availability->setCulture($product->getCulture()); $availability->setAvailabilityName($name); } else { $this->import->getLogger()->warning('Dostepność o nazwie *%name%* nie istnieje, dlatego nie została dodana dla produktu *%product%*', [ '%name%' => $name, '%product%' => $this->getProductName($product), ]); } } $product->setAvailability($availability); return $availability; } /** * Dodaje producenta do produktu * * @param Product $product * @param string $name * @param bool $create * @return Producer * @throws PropelException */ public function addProducer(Product $product, string $name, bool $create = true): Producer { $producer = ProducerPeer::retrieveByName($name); if (null === $producer) { if ($create) { $producer = new Producer(); $producer->setCulture($product->getCulture()); $producer->setName($name); } else { $this->import->getLogger()->warning('Producent o nazwie *%name%* nie istnieje, dlatego nie został dodany dla produktu *%product%*', [ '%name%' => $name, '%product%' => $this->getProductName($product), ]); } } $product->setProducer($producer); return $producer; } /** * Dodaje atrybuty do produktu * * @param Product $product * @param Category $category * @param array $attributes * @return void * @throws PropelException * @throws SQLException */ public function addAttributes(Product $product, array $attributes) { if ($product->isNew()) { throw new Exception("To add attributes product must be saved first"); } $c = new Criteria(); $c->add(appProductAttributeVariantHasProductPeer::PRODUCT_ID, $product->getId()); appProductAttributeVariantHasProductPeer::doDelete($c); $category = $product->getDefaultCategory(); $culture = $product->getCulture(); foreach ($attributes as $name => $data) { $values = $data['values']; $attribute = $this->getOrCreateAttribute($name, isset($data['type']) ? strtoupper($data['type']) : 'T', $culture); if ($category) { $c = new Criteria(); $c->add(appProductAttributeHasCategoryPeer::CATEGORY_ID, $category->getId()); $c->add(appProductAttributeHasCategoryPeer::ATTRIBUTE_ID, $attribute->getId()); if (!appProductAttributeHasCategoryPeer::doCount($c)) { $pahc = new appProductAttributeHasCategory(); $pahc->setAttributeId($attribute->getId()); $pahc->setCategoryId($category->getId()); $pahc->save(); } } if (empty($values)) { continue; } /** * @var appProductAttributeVariant[] */ $variants = []; $c = new Criteria(); $c->addJoin(appProductAttributeVariantPeer::ID, appProductAttributeHasVariantPeer::VARIANT_ID); if ($attribute->getType() == 'C') { $c->add(appProductAttributeVariantPeer::OPT_NAME, array_keys($values), Criteria::IN); } else { $c->add(appProductAttributeVariantPeer::OPT_VALUE, array_keys($values), Criteria::IN); } $c->add(appProductAttributeHasVariantPeer::ATTRIBUTE_ID, $attribute->getId()); foreach (appProductAttributeVariantPeer::doSelect($c) as $variant) { $name = $attribute->getType() == 'C' ? $variant->getOptName() : $variant->getOptValue(); $variants[$name] = $variant; } foreach ($values as $name => $params) { if (!isset($variants[$name])) { $v = new appProductAttributeVariant(); $v->setCulture($culture); $ahv = new appProductAttributeHasVariant(); $ahv->setAttributeId($attribute->getId()); $v->addappProductAttributeHasVariant($ahv); if ($attribute->getType() == 'C') { $v->setType($params['color'][0] == '#' ? appProductAttributeVariantPeer::COLOR_TYPE : appProductAttributeVariantPeer::PICTURE_TYPE); $v->setName($name); if (isset($params['color'])) { if ($v->getType() == appProductAttributeVariantPeer::COLOR_TYPE) { $v->setColor(ltrim($params['color'], '#')); } else { try { $url = $params['color']; $this->downloader->download($url, function(stFileDownloaderFile $file) use ($v, $attribute, $product, $url) { $filename = uniqid().'.'.$file->getExtension(); $v->setPicture($filename); if (!is_dir($v->getUploadDir(true))) { mkdir($v->getUploadDir(true), 0755, true); } $target = $v->getPicturePath(true); if (!$file->move($target)) { throw new stTaskSchedulerImportLogException("Wystąpił błąd podczas zapisu zdjęcia <%image%> do lokalizacji **%target%** dla atrybutu **%attr%** produktu **%product%**", stTaskLogger::TYPE_ERROR, [ '%attr%' => $attribute->getName(). '.' . $v->getName(), '%image%' => $url, '%target%' => $target, '%product%' => $this->getProductName($product), ]); } }); } catch (stFileDownloaderException $e) { throw new stTaskSchedulerImportLogException('Wystąpił błąd `%error%` podczas pobierania zdjęcia <%image%> dla atrybutu **%attr%** dla produktu **%product%**', stTaskLogger::TYPE_ERROR, [ '%error%' => $e->getMessage(), '%attr%' => $attribute->getName(). '.' . $v->getName(), '%image%' => $params['color'], '%product%' => $this->getProductName($product), ]); } } } } elseif ($attribute->getType() == 'B') { $v->setValue(''); } else { $v->setValue($name); } } else { $v = $variants[$name]; } if ($attribute->getType() != 'B' || isset($params['checked']) && $params['checked']) { $vhp = new appProductAttributeVariantHasProduct(); $vhp->setProductId($product->getId()); $v->addappProductAttributeVariantHasProduct($vhp); $v->save(); } } } } /** * Dodaje kategorie do produktu * * @param Product $product Obiekt modelu produktu * @param array $categories array('Kategoria1/Podkategoria1', 'Kategoria2/Podkategoria2/PodPodKategoria2', 'itd...') * @param string $separator Separator ścieżki domyślnie '/' * @param null|int $default Indeks kategorii domyślnej * @return stTaskSchedulerImportCategoryMapping * @throws PropelException * @throws SQLException * @throws stTaskSchedulerImportLogException */ public function addCategories(Product $product, array $categories, ?string $separator = '/', ?int $default = null): stTaskSchedulerImportCategoryMapping { if (!$product->isNew() && !$this->import->getConfigurationParameter('disable_categories_update')) { $this->removeFromCategories($product); } if (null === $default) { $default = key($categories); } /** * @var stTaskSchedulerImportCategoryMapping[] */ $excluded = []; $this->currentMapping = null; foreach ($categories as $index => $category) { $isDefault = $default == $index; $currentMapping = $this->import->getCategoryMapper()->addCategory($product, $category, $isDefault, $separator); if (null !== $currentMapping && $currentMapping->getIsExcluded()) { $excluded[] = $currentMapping; continue; } if ($isDefault) { $this->currentMapping = $currentMapping; } } if (!empty($excluded)) { if ($product->isNew()) { throw new stTaskSchedulerImportLogException('Kategoria **%category%** została wykluczona import produktu **%product%** został przerwany', stTaskLogger::TYPE_WARNING, [ '%category%' => implode(', ', array_map(function(stTaskSchedulerImportCategoryMapping $mapping) { return $mapping->getCategoryPath(); }, $excluded)), '%product%' => $this->getProductName($product), ]); } elseif ($this->import->getConfigurationParameter('delete_product_with_excluded_category')) { $product->delete(); throw new stTaskSchedulerImportLogException('Kategoria **%category%** została wykluczona produkt **%product%** zgodnie z konfiguracją został usunięty', stTaskLogger::TYPE_WARNING, [ '%category%' => implode(', ', array_map(function(stTaskSchedulerImportCategoryMapping $mapping) { return $mapping->getCategoryPath(); }, $excluded)), '%product%' => $this->getProductName($product), ]); } } if (null === $this->currentMapping) { $this->currentMapping = $currentMapping; } return $this->currentMapping; } /** * Dodaje opcje produktu * * @param Product $product Instancja modelu produktu * @param array $data Opcje produktu * @param string|null $priceType Typ ceny * @return void * @throws PropelException * @throws SQLException */ public function setProductOptions(Product $product, array $data, string $priceType = null, ?\Closure $callback = null, bool $updateStockAvailability = true) { $root = ProductOptionsValuePeer::getOrCreateRoot($product); $root->setPriceType($priceType); $current = $this->getProductOptions($product); $utility = $this; $process = function(Product $product, array $data, array $current, ProductOptionsValue $root) use (&$process, $utility, $callback) { $count = 0; $fieldOrder = 0; $version = $this->import->getConfiguration()->getOption('version'); foreach ($data as $name => $values) { $defaultOption = null; $name = trim($name); $optionAttributes = isset($values['@attributes']) ? $values['@attributes'] : []; $filter = isset($optionAttributes['filter']) ? ProductOptionsFilterPeer::retrieveByName($optionAttributes['filter']) : null; if (!isset($current[$name])) { $pof = new ProductOptionsField(); $pof->disableProductUpdate(); $pof->setCulture($product->getCulture()); $pof->setFieldOrder($fieldOrder); $pof->setName($name); } else { $pof = ProductOptionsFieldPeer::retrieveByPK($current[$name]['@id']); } if ($filter) { $pof->setProductOptionsFilterId($filter->getId()); } $pof->save(); if (!isset($current[$name])) { $current[$name] = [ '@id' => $pof->getId(), ]; } foreach ($values as $value => $params) { if ($value == '@attributes') { continue; } $value = trim($value); $attributes = isset($params['@attributes']) ? $params['@attributes'] : []; if (!isset($current[$name][$value])) { $pov = new ProductOptionsValue(); $pov->setCulture($product->getCulture()); $pov->setProductId($product->getId()); $pov->setProductOptionsFieldId($current[$name]['@id']); $pov->insertAsLastChildOf($root); $pov->setValue($value); } else { $pov = ProductOptionsValuePeer::retrieveByPK($current[$name][$value]['@id']); } if ($filter) { $pov->setOptFilterId($filter->getId()); } if (isset($attributes['stock'])) { $pov->setStock('' !== $attributes['stock'] ? $attributes['stock'] : null); } if (isset($attributes['price']) && !empty($attributes['price'])) { $pov->setPrice($version > 1 ? $utility->applyPriceMargin($attributes['price']) : $attributes['price']); } if (isset($attributes['oldPrice']) && !empty($attributes['oldPrice'])) { $pov->setOldPrice($version > 1 ? $utility->applyPriceMargin($attributes['oldPrice']) : $attributes['oldPrice']); } if (isset($attributes['weight'])) { $pov->setWeight($attributes['weight']); } if (isset($attributes['code'])) { $pov->setUseProduct($attributes['code']); } if (isset($attributes['ean'])) { $pov->setManCode($attributes['ean']); } if (isset($attributes['color']) && $attributes['color'][0] == '#' && null !== $filter && $filter->getFilterType() == ProductOptionsFilterPeer::COLOR_FILTER) { $pov->setColor(ltrim($attributes['color'], '#')); $pov->setUseImageAsColor(false); } if (isset($attributes['product_image'])) { $pov->setSfAssetId($attributes['product_image']); } if (isset($attributes['default']) && $attributes['default'] && null === $defaultOption) { $defaultOption = $pov->getOptValue(); } $isNew = $pov->isNew(); if (null !== $callback) { call_user_func($callback, $pov, $attributes); } $pov->disableProductUpdate(); $pov->save(); if ($isNew) { $current[$name][$value] = [ '@id' => $pov->getId(), ]; } if (isset($attributes['color']) && $attributes['color'][0] != '#' && null !== $filter && $filter->getFilterType() == ProductOptionsFilterPeer::COLOR_FILTER) { try { $url = $attributes['color']; $this->downloader->download($url, function(stFileDownloaderFile $file) use ($pov, $product, $url) { $filename = uniqid() . '.' . $file->getExtension(); $pov->setUseImageAsColor(true); $pov->setColorImage($filename); $target = $pov->getColorImagePath(true); $targetDir = $pov->getColorImageDir(true); if (!is_dir($targetDir)) { mkdir($targetDir, 0755, true); } if (!$file->move($target)) { throw new stTaskSchedulerImportLogException('Wystąpił błąd podczas zapisu zdjęcia <%image%> do lokalizacji **%target%** dla opcji **%option%** produktu **%product%**', stTaskLogger::TYPE_ERROR, [ '%option%' => $pov->getOptValue(), '%image%' => $url, '%target%' => $target, '%product%' => $this->getProductName($product), ]); } $pov->save(); }); } catch (stFileDownloaderException $e) { throw new stTaskSchedulerImportLogException('Wystąpił błąd `%error%` podczas pobierania zdjęcia <%image%> dla opcji **%option%** produktu **%product%**', stTaskLogger::TYPE_ERROR, [ '%error%' => $e->getMessage(), '%option%' => $pov->getOptValue(), '%image%' => $attributes['color'], '%product%' => $this->getProductName($product), ]); } } if (isset($params['@children'])) { $count += $process($product, $params['@children'], isset($current[$name][$value]['@children']) ? $current[$name][$value]['@children'] : array(), $isNew ? $pov->reload() : $pov); } $root = $root->reload(); $count++; } if ($defaultOption) { $pof->setOptDefaultValue($defaultOption); if ($pof->isModified()) { ProductOptionsFieldPeer::doUpdate($pof); } } $fieldOrder++; } return $count; }; $updateAvailableOptions = function(array $data, array $current) use (&$updateAvailableOptions) { foreach ($current as $name => $values) { if ($name == '@id') { continue; } foreach ($values as $value => $params) { if ($value == '@id' || $value == '@children') { continue; } if (isset($params['@children'])) { $updateAvailableOptions(isset($data[$name][$value]['@children']) ? $data[$name][$value]['@children'] : [], $params['@children']); } elseif (!isset($data[$name][$value])) { $pov = ProductOptionsValuePeer::retrieveByPK($params['@id']); $pov->disableProductUpdate(); $pov->setIsActive(false); $pov->save(); } } } }; $count = $process($product, $data, $current, $root); if ($updateStockAvailability) { $updateAvailableOptions($data, $current); } $product->setOptHasOptions($count + 1); $stock = ProductOptionsValuePeer::updateStock($product, false); $product->setStock($stock); ProductOptionsValuePeer::updateProductColor($product); if ($product->isModified()) { $product->save(); } } private function getProductOptions(Product $product): array { $c = new Criteria(); $c->addSelectColumn(ProductOptionsValuePeer::ID); $c->addSelectColumn(ProductOptionsValuePeer::PRODUCT_OPTIONS_FIELD_ID); $c->addSelectColumn(ProductOptionsValuePeer::DEPTH); $c->addSelectColumn(ProductOptionsValuePeer::OPT_VALUE); $c->addSelectColumn(ProductOptionsValuePeer::LFT); $c->addSelectColumn(ProductOptionsValuePeer::RGT); $c->addSelectColumn(ProductOptionsFieldPeer::OPT_NAME); $c->add(ProductOptionsValuePeer::PRODUCT_ID, $product->getId()); $c->add(ProductOptionsValuePeer::PRODUCT_OPTIONS_VALUE_ID, null, Criteria::ISNOTNULL); $c->addJoin(ProductOptionsValuePeer::PRODUCT_OPTIONS_FIELD_ID, ProductOptionsFieldPeer::ID); $c->addAscendingOrderByColumn(ProductOptionsFieldPeer::FIELD_ORDER); $c->addAscendingOrderByColumn(ProductOptionsValuePeer::LFT); $rs = ProductOptionsValuePeer::doSelectRS($c); $hydrate = function (ResultSet $rs, int $depth = 1) use (&$hydrate): array { $results = []; $rs->setFetchmode(ResultSet::FETCHMODE_ASSOC); while ($rs->next()) { if ($depth != $rs->getInt('DEPTH')) { $rs->previous(); break; } $fieldName = $rs->getString('OPT_NAME'); $optionValue = $rs->getString('OPT_VALUE'); $hasChildren = $rs->getInt('RGT') - $rs->getInt('LFT') > 1; if (!isset($results[$fieldName])) { $results[$fieldName] = [ '@id' => $rs->getInt('PRODUCT_OPTIONS_FIELD_ID'), ]; } $results[$fieldName][$optionValue] = [ '@id' => $rs->getInt('ID'), ]; if ($hasChildren) { $results[$fieldName][$optionValue]['@children'] = $hydrate($rs, $depth + 1); } } return $results; }; return $hydrate($rs); } private function getOrCreateAttribute($name, $type, $culture) { $c = new Criteria(); $c->add(appProductAttributePeer::IMPORT_NAME, $name); $attribute = appProductAttributePeer::doSelectOne($c); if (null === $attribute) { $attribute = new appProductAttribute(); $attribute->setCulture($culture); $attribute->setName($name); $attribute->setImportName($name); $attribute->setType($type); $attribute->save(); } return $attribute; } /** * Usuwa przypisanie do kategorii * * @param Product $product * @return void */ public function removeFromCategories(Product $product) { $c = new Criteria(); $c->add(ProductHasCategoryPeer::IMPORT_HASH_ID, $this->import->getConfiguration()->getHashId()); $c->add(ProductHasCategoryPeer::PRODUCT_ID, $product->getId()); $sql = BasePeer::createSqlQuery($c); list(,$from) = explode(' FROM ', $sql); $sql = 'DELETE ' . ProductHasCategoryPeer::TABLE_NAME . ' FROM ' . $from; Propel::getConnection()->executeQuery($sql); } /** * Usuwa zdjęcia z produktu * * @param Product $product * @return void * @throws PropelException */ public function removeImages(Product $product) { foreach ($product->getImages() as $image) { $image->delete(); } } protected function getProductName(Product $product) { $name = $product->getName(); if ($name) { $name .= ' ('.$product->getCode().')'; } else { $name = $product->getCode(); } return $name; } /** * Zwraca cenę zmodyfikowaną o reguły określone w tabeli narzutów * * @param string|float $price * @return string|float */ protected function applyPriceArray($price) { if ($this->import->getConfiguration()->getUserOption('price_array_enabled')) { $prices = $this->import->getConfiguration()->getUserOption('price_array'); foreach($prices as $range) { if($price >= $range['from'] && $price <= $range['to']) { return $price * $range['multiplier'] + $range['add']; } } } return $price; } }