529 lines
17 KiB
PHP
529 lines
17 KiB
PHP
<?php
|
|
|
|
namespace KupShop\ContentBundle\View;
|
|
|
|
use KupShop\CatalogBundle\Entity\Section;
|
|
use KupShop\CatalogBundle\Entity\Wrapper\ProductWrapper;
|
|
use KupShop\CatalogBundle\ProductList\ProductCollection;
|
|
use KupShop\CatalogBundle\ProductList\ProductList;
|
|
use KupShop\CatalogBundle\Repository\SectionsRepository;
|
|
use KupShop\CatalogBundle\Util\ActiveCategory;
|
|
use KupShop\ComponentsBundle\Twig\DataProvider\ProductDataProvider;
|
|
use KupShop\ComponentsBundle\View\ComponentsViewInterface;
|
|
use KupShop\ComponentsBundle\View\ComponentsViewTrait;
|
|
use KupShop\ContentBundle\Entity\Placeholder;
|
|
use KupShop\ContentBundle\Util\Block;
|
|
use KupShop\I18nBundle\Translations\ProductsTranslation;
|
|
use KupShop\KupShopBundle\Context\UserContext;
|
|
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
|
use KupShop\KupShopBundle\Util\Contexts;
|
|
use KupShop\KupShopBundle\Util\StringUtil;
|
|
use KupShop\KupShopBundle\Util\System\PathFinder;
|
|
use KupShop\KupShopBundle\Views\Traits\PlaceholdersInterface;
|
|
use KupShop\KupShopBundle\Views\Traits\RequestTrait;
|
|
use KupShop\KupShopBundle\Views\View;
|
|
use KupShop\OrderingBundle\Util\Purchase\PurchaseUtil;
|
|
use Query\Operator;
|
|
use Query\QueryBuilder;
|
|
use Query\Translation;
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
use Symfony\Contracts\Service\Attribute\Required;
|
|
|
|
class ProductView extends View implements PlaceholdersInterface, ComponentsViewInterface
|
|
{
|
|
use RequestTrait;
|
|
use ComponentsViewTrait;
|
|
|
|
protected $template = 'product.tpl';
|
|
|
|
protected string $entrypoint = 'product';
|
|
|
|
protected ?ProductCollection $productCollection = null;
|
|
|
|
/** @var \Product */
|
|
private $product;
|
|
|
|
/** @var ActiveCategory */
|
|
private $activeCategory;
|
|
|
|
/** @var ProductWrapper */
|
|
private $wrapper;
|
|
|
|
private $productId;
|
|
/**
|
|
* @var PathFinder
|
|
*/
|
|
private $pathFinder;
|
|
|
|
private $productList;
|
|
|
|
private ?array $sectionsBreadcrumbsCache = null;
|
|
|
|
protected ?SectionsRepository $sectionsRepository = null;
|
|
|
|
public function __construct(ActiveCategory $activeCategory, ProductWrapper $wrapper, PathFinder $pathFinder, ProductList $productList,
|
|
protected Block $block,
|
|
protected ?ProductDataProvider $productDataProvider = null,
|
|
) {
|
|
$this->activeCategory = $activeCategory;
|
|
$this->wrapper = $wrapper;
|
|
$this->pathFinder = $pathFinder;
|
|
$this->productList = $productList;
|
|
$this->proxyCacheEnabled = findModule(\Modules::PROXY_CACHE, 'product');
|
|
}
|
|
|
|
public function getHeaderVariables()
|
|
{
|
|
$header = parent::getHeaderVariables();
|
|
|
|
if ($this->getSearchString()) {
|
|
$header['searchString'] = $this->getSearchString();
|
|
}
|
|
|
|
return $header;
|
|
}
|
|
|
|
public function getBodyVariables()
|
|
{
|
|
$this->checkProductExist();
|
|
|
|
if (findModule(\Modules::COMPONENTS)) {
|
|
$product = $this->getProduct();
|
|
$this->productDataProvider->addProduct($product);
|
|
$vars['id_product'] = $this->productId;
|
|
$vars['id_variation'] = intval(getVal('id_variation')) ?: null; // TODO: HACK!!!!!
|
|
} else {
|
|
$vars = parent::getBodyVariables();
|
|
if ($this->search()) {
|
|
$vars['search'] = $this->search();
|
|
}
|
|
|
|
$vars['product'] = $this->wrapper->setObject($this->getProduct());
|
|
$vars['productCollection'] = $this->productCollection;
|
|
}
|
|
|
|
return $vars;
|
|
}
|
|
|
|
public function getDeliveries()
|
|
{
|
|
return \DeliveryType::getDeliveries();
|
|
}
|
|
|
|
public function getSearchString()
|
|
{
|
|
if ($this->request->get('search') != null && trim($this->request->get('search')) != '') {
|
|
$search = trim($this->request->get('search'));
|
|
|
|
return $search;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function search()
|
|
{
|
|
if ($this->getSearchString()) {
|
|
$search = trim($this->getSearchString());
|
|
|
|
$word = [];
|
|
$word = preg_split('/[[:blank:]]+/', $search);
|
|
|
|
if (count($word) > 0) {
|
|
for ($i = 0; $i < count($word); $i++) {
|
|
if (trim($word[$i]) == '') {
|
|
unset($word[$i]);
|
|
}
|
|
|
|
$word[$i] = preg_quote($word[$i]);
|
|
}
|
|
}
|
|
|
|
return $word;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function getBreadcrumbs(): array
|
|
{
|
|
$returnNav = [];
|
|
$navArray = null;
|
|
|
|
$product = $this->getProduct();
|
|
$qb = sqlQueryBuilder()
|
|
->select('ps.id_section')
|
|
->from('products_in_sections', 'ps')
|
|
->join('ps', 'sections', 's', 'ps.id_section = s.id')
|
|
->where(Operator::equals(['id_product' => $product->id]))
|
|
->andWhere(Operator::not(Operator::equals(['s.figure' => 'N'])))
|
|
->orderBy('s.priority', 'DESC')
|
|
->execute();
|
|
|
|
foreach ($qb as $index => $row) {
|
|
$navArray = getReturnNavigation($row['id_section'], 'PRODUCT', [$product->title]);
|
|
|
|
if ($index == 0) {
|
|
// Set opened menu section - first available section
|
|
if ($this->activeCategory->loadCategoryId() == null) {
|
|
$this->activeCategory->setCategoryId($row['id_section']);
|
|
}
|
|
}
|
|
|
|
$returnNav = array_merge($returnNav, $navArray);
|
|
|
|
if ($this->activeCategory->loadCategoryId() == null) {
|
|
$this->activeCategory->setCategoryId($row['id_section']);
|
|
}
|
|
}
|
|
|
|
return $returnNav;
|
|
}
|
|
|
|
public function getBreadcrumbsNew(): array
|
|
{
|
|
$returnNav = $this->getSectionsBreadcrumbs();
|
|
|
|
return reset($returnNav);
|
|
}
|
|
|
|
public function getSectionsBreadcrumbs(): array
|
|
{
|
|
if ($this->sectionsBreadcrumbsCache) {
|
|
return $this->sectionsBreadcrumbsCache;
|
|
}
|
|
|
|
$returnNav = [];
|
|
$product = $this->getProduct();
|
|
$home = [
|
|
'text' => translate('getSections', 'functions')['home'],
|
|
'link' => path('home'),
|
|
];
|
|
|
|
$this->productDataProvider->multiFetch()->fetchSectionsDirectly();
|
|
$sections = $product->fetchSections();
|
|
|
|
if (!is_array($sections)) {
|
|
$sections = [$sections];
|
|
}
|
|
|
|
/** @var Section $section */
|
|
foreach (array_values($sections) as $index => $section) {
|
|
$navArray = [$home];
|
|
foreach ($section->getParents() as $parent) {
|
|
$navArray[] = [
|
|
'text' => $parent->getNameShort() ?: $parent->getName(),
|
|
'section_name' => $parent->getName(),
|
|
'link' => '/'.$parent->getUrl(),
|
|
'ID' => $parent->getId(),
|
|
];
|
|
}
|
|
$navArray[] = ['text' => $product->title];
|
|
|
|
if ($index == 0) {
|
|
// Set opened menu section - first available section
|
|
if ($this->activeCategory->loadCategoryId() == null) {
|
|
$this->activeCategory->setCategoryId($section->getId());
|
|
}
|
|
}
|
|
|
|
$returnNav[] = $navArray;
|
|
|
|
if ($this->activeCategory->loadCategoryId() == null) {
|
|
$this->activeCategory->setCategoryId($section->getId());
|
|
}
|
|
}
|
|
|
|
if (empty($returnNav)) {
|
|
$returnNav[] = [$home, ['text' => $product->title]];
|
|
}
|
|
|
|
$this->sectionsBreadcrumbsCache = $returnNav;
|
|
|
|
return $this->sectionsBreadcrumbsCache;
|
|
}
|
|
|
|
public function getDeliveryDays()
|
|
{
|
|
$days = sqlQueryBuilder()->select('MAX(time_days) as days')->from('delivery_type_delivery')->execute()->fetch();
|
|
if ($days) {
|
|
$days = ['min' => $days['days']];
|
|
}
|
|
|
|
if (findModule(\Modules::DELIVERY_PRICELIST)) {
|
|
$priceList = ServiceContainer::getService(\KupShop\DeliveryPriceListBundle\DeliveryPriceCalculator::class);
|
|
$countryContext = ServiceContainer::getService(\KupShop\KupShopBundle\Context\CountryContext::class);
|
|
$days = $priceList->getDeliveryTime($countryContext->getActiveId());
|
|
}
|
|
|
|
return $days;
|
|
}
|
|
|
|
public function checkProductExist()
|
|
{
|
|
if (!$this->getProduct()) {
|
|
throw new NotFoundHttpException('Product not found');
|
|
}
|
|
}
|
|
|
|
public function getProduct()
|
|
{
|
|
if ($this->product === null) {
|
|
$productList = $this->productList;
|
|
$productList->applyDefaultFilterParams();
|
|
|
|
$productList->andSpec(function (QueryBuilder $qb) {
|
|
$qb->andWhere(Operator::equals(['p.id' => $this->getProductId()]))
|
|
->addSelect('pv.id as variation_id')
|
|
->having('figure = "Y"')
|
|
->andWhere(
|
|
Translation::coalesceTranslatedFields(
|
|
ProductsTranslation::class,
|
|
['long_descr', 'parameters', 'meta_title', 'meta_description', 'meta_keywords', 'figure']
|
|
));
|
|
$qb->addSelect(\Query\Product::withProductPhotoId());
|
|
});
|
|
|
|
if (!findModule(\Modules::COMPONENTS)) {
|
|
$productList->fetchSections()->fetchProductLabels('detail');
|
|
}
|
|
|
|
$products = $productList->getProducts();
|
|
// pokud se v produkt nevrati z ProductListu tak je neco spatne
|
|
if (!($this->product = $products->first())) {
|
|
$visible = false;
|
|
|
|
// zkusim vytvorit produkt pres class product
|
|
$this->product = new \Product();
|
|
// kdyz produkt neexistuje vrat 404
|
|
if (!$this->product->createFromDB($this->getProductId())) {
|
|
// nastavim na false, protoze jsem produkt nenasl a pri pripadnym dalsim volani uz ho nechci zkouset znova nacitat
|
|
$this->product = false;
|
|
|
|
return false;
|
|
}
|
|
|
|
$products = new ProductCollection([$this->product->id => $this->product]);
|
|
|
|
$multiFetch = ServiceContainer::getService(\KupShop\CatalogBundle\ProductList\MultiFetch::class);
|
|
$multiFetch->fetchSections($products);
|
|
$multiFetch->fetchProductLabels($products, 'detail');
|
|
|
|
// pro admina nezobrazovat 404, pokud je produkt skryty
|
|
if (getAdminUser()) {
|
|
$visible = true;
|
|
}
|
|
|
|
$dbcfg = \Settings::getDefault();
|
|
$doNotShowProductsNotInStore = $dbcfg->prod_show_not_in_store === 'N';
|
|
$productIsNotInStore = $this->product->inStore <= 0 && $this->product->getInStoreSuppliers() <= 0;
|
|
|
|
// nastavit produkt vyprodan/prodej ukoncen, pokud neni skladem a neni zaskrtnuto Zobrazit zboží, které není skladem
|
|
if ($this->product->isVisible()
|
|
&& ($this->product->isOld() || ($doNotShowProductsNotInStore && $productIsNotInStore))) {
|
|
$this->product->visible = 'O';
|
|
}
|
|
|
|
if ($this->product->visible == 'O') {
|
|
$visible = true;
|
|
}
|
|
|
|
if (!$visible) {
|
|
// nastavim na false, protoze jsem produkt nenasl a pri pripadnym dalsim volani uz ho nechci zkouset znova nacitat
|
|
$this->product = false;
|
|
|
|
return false;
|
|
}
|
|
|
|
// ProductList uz to jako jediny vola, tady musime zavolat rucne
|
|
$this->product->prepareDeliveryText();
|
|
}
|
|
|
|
$this->productCollection = $products;
|
|
|
|
if (!findModule(\Modules::COMPONENTS)) {
|
|
$this->product->fetchProducer();
|
|
$this->product->fetchParameters();
|
|
$this->product->fetchVariations();
|
|
$this->product->fetchLinks();
|
|
$this->product->fetchAttachments();
|
|
$this->product->fetchImages(3, 4);
|
|
$this->product->fetchSets();
|
|
$this->product->fetchGifts();
|
|
if (findModule(\Modules::BONUS_PROGRAM)) {
|
|
$this->product->fetchBonusPoints();
|
|
}
|
|
}
|
|
|
|
$sections = array_map(function ($section) {
|
|
return $section->getId();
|
|
}, $this->product->sections ?? []);
|
|
|
|
$this->activeCategory->validate($sections, $this->product->id);
|
|
}
|
|
|
|
return $this->product;
|
|
}
|
|
|
|
public function getShowInSearch(): bool
|
|
{
|
|
return $this->getProduct() && ($this->getProduct()->showInSearch === 'Y');
|
|
}
|
|
|
|
public function getTitle()
|
|
{
|
|
return $this->getProduct()->title;
|
|
}
|
|
|
|
public function getWpjToolbar()
|
|
{
|
|
$arr = [
|
|
'url' => getAdminUrl('products', ['ID' => $this->getProductId()]),
|
|
'title' => 'Upravit produkt',
|
|
'translateUrl' => getAdminUrl('list', [
|
|
'type' => 'translateProducts',
|
|
'iframe' => 1,
|
|
'objectIDs' => $this->getProductId(),
|
|
]),
|
|
'translateTitle' => 'Překlad produktu',
|
|
];
|
|
|
|
return array_merge(parent::getWpjToolbar(), $arr);
|
|
}
|
|
|
|
public function setProductId($id)
|
|
{
|
|
$this->productId = $id;
|
|
}
|
|
|
|
public function getProductId()
|
|
{
|
|
return $this->productId;
|
|
}
|
|
|
|
public function getMetaTitle()
|
|
{
|
|
if (!empty($this->getProduct()->meta_title)) {
|
|
return $this->getProduct()->meta_title;
|
|
}
|
|
|
|
return $this->getTitle();
|
|
}
|
|
|
|
public function getMetaKeywords()
|
|
{
|
|
return $this->getProduct()->meta_keywords;
|
|
}
|
|
|
|
public function getMetaDescription()
|
|
{
|
|
$product = $this->getProduct();
|
|
// Get meta description from product
|
|
if (!empty($product->meta_description)) {
|
|
return $product->meta_description;
|
|
}
|
|
|
|
// Get first 160 chars (entire words) from description+
|
|
if (findModule(\Modules::PRODUCTS, \Modules::SUB_DESCR_PLUS)) {
|
|
if ($product->id_block) {
|
|
$content = $this->block->getBlockContent($product->id_block, true);
|
|
if (!empty($content)) {
|
|
return StringUtil::replacePunctuationWithPeriod(StringUtil::cutToWords($content, 160));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get first 160 chars from product description
|
|
if (!empty($product->longDescr)) {
|
|
$content = strip_tags($product->longDescr);
|
|
$content = preg_replace("/[\n]/i", ' ', $content);
|
|
$content = StringUtil::normalizeWhitespace($content);
|
|
if (!empty($content)) {
|
|
return StringUtil::replacePunctuationWithPeriod(StringUtil::cutToWords($content, 160));
|
|
}
|
|
}
|
|
|
|
// Get first 160 chars from producer description
|
|
$producer = $product->fetchProducer();
|
|
if (!empty($producer['id_block'])) {
|
|
$content = $this->block->getBlockContent($producer['id_block'], true);
|
|
if (!empty($content)) {
|
|
return StringUtil::replacePunctuationWithPeriod(StringUtil::cutToWords($content, 160));
|
|
}
|
|
}
|
|
|
|
// Get meta description from producer
|
|
$description = $producer['meta_description'] ?? '';
|
|
if (!empty($description)) {
|
|
return StringUtil::replacePunctuationWithPeriod(StringUtil::cutToWords($description, 160));
|
|
}
|
|
|
|
// Get eshop default meta description
|
|
return \Settings::getDefault()->shop_description;
|
|
}
|
|
|
|
public function getCorrectUrl(): ?string
|
|
{
|
|
if (!$this->getProduct()) {
|
|
return null;
|
|
}
|
|
|
|
return path('products', ['id' => $this->getProduct()->id, 'name' => StringUtil::slugify($this->getProduct()->title)]);
|
|
}
|
|
|
|
protected function getLastModifiedDate(): \DateTime
|
|
{
|
|
$productId = $this->getProductId();
|
|
|
|
if ($productId) {
|
|
$date = sqlQueryBuilder()->select('updated')
|
|
->from('products')
|
|
->where(Operator::equals(['id' => $productId]))
|
|
->execute()->fetchOne();
|
|
|
|
if ($date) {
|
|
return new \DateTime($date);
|
|
}
|
|
}
|
|
|
|
return parent::getLastModifiedDate();
|
|
}
|
|
|
|
public function getPlaceholders(): iterable
|
|
{
|
|
return $this->placeholders ??= [
|
|
new Placeholder('POCET_BODU', 'Počet bodů získaných nákupem produktu', function () {
|
|
return $this->getProduct()->getBonusPoints()?->printFloatValue(-2) ?? '';
|
|
}),
|
|
new Placeholder('DOPRAVA_ZDARMA_OD', 'Částka, od které je doprava zdarma', function () {
|
|
$purchaseUtil = ServiceContainer::getService(PurchaseUtil::class);
|
|
$freeDeliveryFrom = $purchaseUtil->getFreeDeliveryFrom(true);
|
|
|
|
if (Contexts::get(UserContext::class)->getActive()) {
|
|
$freeDeliveryFrom = $freeDeliveryFrom['registered'];
|
|
} else {
|
|
$freeDeliveryFrom = $freeDeliveryFrom['unregistered'];
|
|
}
|
|
|
|
if ($freeDeliveryFrom === false) {
|
|
return '';
|
|
}
|
|
|
|
return printPrice($freeDeliveryFrom);
|
|
}),
|
|
];
|
|
}
|
|
|
|
#[Required]
|
|
final public function setSectionsRepository(?SectionsRepository $sectionsRepository): void
|
|
{
|
|
$this->sectionsRepository = $sectionsRepository;
|
|
}
|
|
|
|
public function getTemplates(): iterable
|
|
{
|
|
yield $this->template;
|
|
// yield 'product.old.tpl'; // TODO: Co s touhle šablonou? Zrušit?
|
|
}
|
|
}
|