Files
kupshop/bundles/KupShop/ContentBundle/View/ProductView.php
2025-08-02 16:30:27 +02:00

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?
}
}