src/Service/Shop/ProductService.php line 352

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by Elements.at New Media Solutions GmbH
  4.  *
  5.  */
  6. namespace App\Service\Shop;
  7. use App\Model\Shop\Merchandise\MerchandiseProduct;
  8. use App\Model\Shop\Ticket\ShopTicketCatalog;
  9. use App\Model\Shop\Ticket\TicketConsumerCategory;
  10. use App\Service\FormService;
  11. use App\Service\LinkGenerator\MerchandiseLinkGenerator;
  12. use App\Service\SiteConfigService;
  13. use Carbon\Carbon;
  14. use Elements\Bundle\RecurringDatesTypeBundle\Templating\RecurringDatesHelper;
  15. use Knp\Component\Pager\PaginatorInterface;
  16. use Pimcore\Log\ApplicationLogger;
  17. use Pimcore\Model\DataObject;
  18. use Pimcore\Model\DataObject\Concrete;
  19. use Pimcore\Model\DataObject\ShopEvent;
  20. use Pimcore\Model\DataObject\ShopNotification;
  21. use Pimcore\Model\DataObject\ShopProductInterest;
  22. use Pimcore\Model\DataObject\SiteConfig;
  23. use Pimcore\Model\DataObject\TicketShopCategory;
  24. use Pimcore\Translation\Translator;
  25. use Symfony\Component\HttpFoundation\Request;
  26. class ProductService
  27. {
  28.     private SiteConfig $siteConfig;
  29.     public function __construct(
  30.         protected MerchandiseLinkGenerator $merchLinkGenerator,
  31.         protected Translator $translator,
  32.         protected ApplicationLogger $logger,
  33.         protected FormService $formService,
  34.         protected SmartPricerService $smartPricerService,
  35.         protected RecurringDatesHelper $recurringDatesHelper,
  36.         SiteConfigService $siteConfigService
  37.     ) {
  38.         $this->siteConfig $siteConfigService->getSiteConfig();
  39.     }
  40.     public function getMerchandiseTopTitle(MerchandiseProduct $product): string
  41.     {
  42.         $toptitle = [];
  43.         foreach ($product->getCategories() as $category) {
  44.             $toptitle[] = $category->getName();
  45.         }
  46.         return implode(' | '$toptitle);
  47.     }
  48.     /**
  49.      * @param MerchandiseProduct $product
  50.      *
  51.      * @return array<mixed>
  52.      */
  53.     public function getMerchandiseVariantOptions(MerchandiseProduct $productstring $currentProductId): array
  54.     {
  55.         $options = [];
  56.         $product $product->getReference();
  57.         foreach ($product->getChildren([DataObject::OBJECT_TYPE_VARIANT]) as $variant) {
  58.             if ($variant instanceof MerchandiseProduct) {
  59.                 $label $variant->getVariantName();
  60.                 if (!$variant->getQuota()) {
  61.                     $label .= ' - ' $this->translator->trans('shop.detail.not-available');
  62.                 }
  63.                 $options[] = [
  64.                     'label' => $label,
  65.                     'value' => $variant->getId(),
  66.                     'selected' => $variant->getId() == $currentProductId,
  67.                     'disabled' => !$variant->getQuota(),
  68.                     'attr' => ['data-merchandise-ajax-url' => $this->merchLinkGenerator->generate($variant) . '?reload-variant=' $variant->getId()],
  69.                 ];
  70.             }
  71.         }
  72.         return $options;
  73.     }
  74.     public function handleNotificationRequest(string $emailint $productIdstring $locale): bool
  75.     {
  76.         try {
  77.             $product DataObject::getById($productId);
  78.             if (!$product instanceof MerchandiseProduct && !$product instanceof DataObject\ShopTicketCatalog && !$product instanceof ShopEvent) {
  79.                 throw new \Exception('No object with id ' $productId ' found');
  80.             }
  81.             $newNotification = new ShopNotification();
  82.             $newNotification->setKey('notification-' uniqid() . '-' time());
  83.             $path = ($this->siteConfig->getShopNotificationFolder() ?: 'Notifications') . '/' Carbon::now()->format('Y/m/d');
  84.             $newNotification->setParent(DataObject\Service::createFolderByPath($path));
  85.             $newNotification->setEmail($email);
  86.             $newNotification->setProduct($product);
  87.             $newNotification->setLocale($locale);
  88.             $newNotification->setSignedForNotification(Carbon::now());
  89.             $newNotification->setPublished(true);
  90.             $newNotification->save();
  91.             return true;
  92.         } catch (\Exception $e) {
  93.             $this->logger->error('Not able to save ShopNotification: ' $e->getMessage(), [
  94.                 'component' => 'ShopNotification',
  95.             ]);
  96.             return false;
  97.         }
  98.     }
  99.     /**
  100.      * @param DataObject\Listing\Concrete $listing
  101.      * @param Request $request
  102.      * @param PaginatorInterface $paginator
  103.      * @param String[] $tenants
  104.      *
  105.      * @return array<mixed>
  106.      */
  107.     public function prepareProductData(DataObject\Listing\Concrete $listingRequest $requestPaginatorInterface $paginator, array $tenants = ['b2c']): array
  108.     {
  109.         $categories = new TicketShopCategory\Listing();
  110.         $interests = new ShopProductInterest\Listing();
  111.         $returnData['filterLabels'] = [];
  112.         $listing->setOrderKey('sort DESC, name ASC'false);
  113.         $tenantCondition = [];
  114.         foreach ($tenants as $tenant) {
  115.             $tenantCondition[] = 'FIND_IN_SET("' $tenant '", tenant)';
  116.         }
  117.         if ($tenantCondition) {
  118.             $listing->addConditionParam(implode(' OR '$tenantCondition));
  119.         }
  120.         $categories->addConditionParam("o_id IN (SELECT dest_id FROM object_relations_{$listing->getClassId()} WHERE fieldname = 'productCategory' AND src_id IN (" implode(','$listing->loadIdList()) . '))');
  121.         $interests->addConditionParam("o_id IN (SELECT dest_id FROM object_relations_{$listing->getClassId()} WHERE fieldname = 'interests ' AND src_id IN (" implode(','$listing->loadIdList()) . '))');
  122.         // FILTER
  123.         if ($keyword $request->get('search')) {
  124.             $keyword htmlspecialchars($keyword);
  125.             $listing->addConditionParam('name LIKE :keyword OR shortDescription LIKE :keyword', [
  126.                 'keyword' => "%$keyword%",
  127.             ]);
  128.             $returnData['filterLabels'][] = [
  129.                 'label' => $keyword,
  130.                 'name' => 'search',
  131.                 'value' => $keyword,
  132.             ];
  133.         }
  134.         if ($filterCategoryIds $request->get('categories')) {
  135.             if ($filterCategoryIds array_filter($filterCategoryIds)) {
  136.                 array_walk($filterCategoryIds, [$this'filterParams']);
  137.                 $listing->addConditionParam('FIND_IN_SET(productCategory__id, :categoryIds)', [
  138.                     'categoryIds' => implode(','$filterCategoryIds),
  139.                 ]);
  140.                 $returnData['categoryLabels'] = $this->getFilterLabels($filterCategoryIds);
  141.                 $returnData['filterLabels'] = array_merge($returnData['filterLabels'], $this->getFilterLabels($filterCategoryIdstrue'categories[]'));
  142.             }
  143.         }
  144.         if ($filterInterestIds $request->get('interests')) {
  145.             if (is_array($filterInterestIds)) {
  146.                 if ($filterInterestIds array_filter($filterInterestIds)) {
  147.                     array_walk($filterInterestIds, [$this'filterParams']);
  148.                     $query = [];
  149.                     foreach ($filterInterestIds as $interestId) {
  150.                         $id intval($interestId);
  151.                         $query[] = "FIND_IN_SET({$id}, interests)";
  152.                     }
  153.                     $listing->addConditionParam(implode(' OR '$query));
  154.                     $returnData['interestLabels'] = $this->getFilterLabels($filterInterestIds);
  155.                     $returnData['filterLabels'] = array_merge($returnData['filterLabels'], $this->getFilterLabels($filterInterestIdstrue'interests[]'));
  156.                 }
  157.             }
  158.         }
  159.         $paginator $paginator->paginate($listing, (int)$request->get('page'1), 12);
  160.         $returnData['products'] = $paginator;
  161.         $returnData['categories'] = $categories;
  162.         $returnData['interests'] = $interests;
  163.         return $returnData;
  164.     }
  165.     /**
  166.      * @param DataObject\Listing\Concrete $listing
  167.      * @param Request $request
  168.      * @param PaginatorInterface $paginator
  169.      *
  170.      * @return array<mixed>
  171.      */
  172.     public function prepareMerchData(DataObject\Listing\Concrete $listingRequest $requestPaginatorInterface $paginator): array
  173.     {
  174.         $categories $this->formService->getRelatedObjects($listing'categories');
  175.         $returnData['filterLabels'] = [];
  176.         // FILTER
  177.         if ($keyword htmlspecialchars($request->get('search'))) {
  178.             $listing->addConditionParam('name LIKE :keyword OR description LIKE :keyword', [
  179.                 'keyword' => "%$keyword%",
  180.             ]);
  181.             $returnData['filterLabels'][] = [
  182.                 'label' => $keyword,
  183.                 'name' => 'search',
  184.                 'value' => $keyword,
  185.             ];
  186.         }
  187.         if ($filterCategoryIds $request->get('categories')) {
  188.             if ($filterCategoryIds array_filter($filterCategoryIds)) {
  189.                 array_walk($filterCategoryIds, [$this'filterParams']);
  190.                 $query = [];
  191.                 foreach ($filterCategoryIds as $interestId) {
  192.                     $query[] = "FIND_IN_SET({$interestId}, categories)";
  193.                 }
  194.                 $listing->addConditionParam(implode(' OR '$query));
  195.                 $returnData['categoryLabels'] = $this->getFilterLabels($filterCategoryIds);
  196.                 $returnData['filterLabels'] = array_merge($returnData['filterLabels'], $this->getFilterLabels($filterCategoryIdstrue'categories[]'));
  197.             }
  198.         }
  199.         $paginator $paginator->paginate($listing, (int)$request->get('page'1), 12);
  200.         $returnData['products'] = $paginator;
  201.         $returnData['categories'] = array_map(function ($c) use ($request) {
  202.             return [
  203.                 'id' => $c->getId(),
  204.                 'name' => 'categories[]',
  205.                 'value' => $c->getId(),
  206.                 'label' => $c->getName(),
  207.                 'selected' => $request->get('categories') && in_array($c->getId(), $request->get('categories')),
  208.                 'checked' => $request->get('categories') && in_array($c->getId(), $request->get('categories')),
  209.             ];
  210.         }, $categories);
  211.         return $returnData;
  212.     }
  213.     /**
  214.      * @param array<int> $ids
  215.      * @param bool $full
  216.      * @param string $name
  217.      *
  218.      * @return array<mixed>
  219.      */
  220.     public function getFilterLabels(array $idsbool $full falsestring $name ''): array
  221.     {
  222.         $labels = [];
  223.         foreach ($ids as $id) {
  224.             $obj Concrete::getById($id);
  225.             if ($obj instanceof TicketShopCategory || $obj instanceof ShopProductInterest) {
  226.                 if ($full) {
  227.                     $labels[] = [
  228.                         'label' => $obj->getName(),
  229.                         'value' => $obj->getId(),
  230.                         'name' => $name,
  231.                     ];
  232.                 } else {
  233.                     $labels[] = $obj->getName();
  234.                 }
  235.             }
  236.         }
  237.         return $labels;
  238.     }
  239.     public function filterParams(mixed &$value): void
  240.     {
  241.         $value htmlspecialchars($value);
  242.     }
  243.     /**
  244.      * @param DataObject\Listing\Concrete $listing
  245.      * @param String $tenant
  246.      *
  247.      * @return array<mixed>
  248.      */
  249.     public function getAvailableCategoryInterests(DataObject\Listing\Concrete $listingstring $tenant 'b2c'): array
  250.     {
  251.         $interests $categories $categoriesPerInterest = [];
  252.         $listing->addConditionParam("tenant LIKE '%{$tenant}%'");
  253.         /** @var ShopTicketCatalog|ShopEvent $item */
  254.         foreach ($listing as $item) {
  255.             if ($item instanceof ShopTicketCatalog && !$item->isAvailableInDateRange()) {
  256.                 continue;
  257.             }
  258.             /** @var ShopProductInterest $interest */
  259.             foreach ($item->getInterests() as $interest) {
  260.                 $category $item->getProductCategory();
  261.                 $categoryId $category->getId();
  262.                 // add category if not exists yet
  263.                 if (!key_exists($categoryId$categories)) {
  264.                     $categories[$categoryId] = $category;
  265.                 }
  266.                 // add interest if not exists yet
  267.                 if (!key_exists($interest->getId(), $interests)) {
  268.                     $interests[$interest->getId()] = $interest;
  269.                     $categoriesPerInterest[$interest->getId()][] = $categoryId;
  270.                     continue;
  271.                 }
  272.                 // add category to interest if not exists yet
  273.                 if (!in_array($categoryId$categoriesPerInterest[$interest->getId()])) {
  274.                     $categoriesPerInterest[$interest->getId()][] = $categoryId;
  275.                 }
  276.             }
  277.         }
  278.         uasort($categories, [$this'orderDescending']);
  279.         uasort($interests, [$this'orderDescending']);
  280.         $returnData['categories'] = $categories;
  281.         $returnData['interests'] = $interests;
  282.         $returnData['categoriesPerInterest'] = $categoriesPerInterest;
  283.         return $returnData;
  284.     }
  285.     /**
  286.      * @param DataObject\Listing\Concrete $listing
  287.      * @param int $interestId
  288.      * @param int[] $categoryIds
  289.      * @param string $tenant
  290.      *
  291.      * @return array<mixed>
  292.      */
  293.     public function getFilteredPriceGroups(DataObject\Listing\Concrete $listingint $interestId, array $categoryIdsstring $tenant 'b2c'): array
  294.     {
  295.         $listing->addConditionParam("tenant LIKE '%{$tenant}%'");
  296.         $listing->addConditionParam("o_id IN (SELECT src_id FROM object_relations_{$listing->getClassId()} WHERE fieldname = 'interests' AND dest_id = " $interestId ')');
  297.         $listing->addConditionParam("o_id IN (SELECT src_id FROM object_relations_{$listing->getClassId()} WHERE fieldname = 'productCategory' AND dest_id IN (" implode(','$categoryIds) . '))');
  298.         $priceGroups = [];
  299.         /** @var ShopTicketCatalog|ShopEvent $item */
  300.         foreach ($listing as $item) {
  301.             if ($item instanceof ShopTicketCatalog && !$item->isAvailableInDateRange()) {
  302.                 continue;
  303.             }
  304.             foreach ($item->getTicketConsumerCategories() as $group) {
  305.                 if (!$group->hasChildren()) { // if $group has no children then it is a child of a Meta Category
  306.                     $parent $group->getParent();
  307.                     if ($parent instanceof TicketConsumerCategory && !key_exists($parent->getId(), $priceGroups)) {
  308.                         $priceGroups[$parent->getId()] = $parent;
  309.                     }
  310.                 }
  311.             }
  312.         }
  313.         uasort($priceGroups, [$this'orderDescending']);
  314.         return $priceGroups;
  315.     }
  316.     /**
  317.      * @param DataObject\Listing\Concrete $listing
  318.      * @param int|null $interestId
  319.      * @param int[] $categoryIds
  320.      * @param int[] $priceGroupIds
  321.      * @param string $tenant
  322.      *
  323.      * @return DataObject\Listing\Concrete
  324.      */
  325.     public function getFilteredCatalogs(
  326.         DataObject\Listing\Concrete $listing,
  327.         ?int $interestId null,
  328.         array $categoryIds = [],
  329.         array $priceGroupIds = [],
  330.         string $tenant 'b2c'): DataObject\Listing\Concrete
  331.     {
  332.         $listing->addConditionParam("tenant LIKE '%{$tenant}%'");
  333.         if ($interestId) {
  334.             $listing->addConditionParam("o_id IN (SELECT src_id FROM object_relations_{$listing->getClassId()} WHERE fieldname = 'interests' AND dest_id = " $interestId ')');
  335.         }
  336.         if (!empty($categoryIds)) {
  337.             $listing->addConditionParam("o_id IN (SELECT src_id FROM object_relations_{$listing->getClassId()} WHERE fieldname = 'productCategory' AND dest_id IN (" implode(','$categoryIds) . '))');
  338.         }
  339.         if (!empty($priceGroupIds)) { // price groups are more complex since it has children and variants
  340.             $tempIds = [];
  341.             foreach ($priceGroupIds as $priceGroupId) {
  342.                 $tempIds[] = $priceGroupId// TODO: maybe find a better algorithm?
  343.                 $category TicketConsumerCategory::getById($priceGroupId);
  344.                 if ($category->hasChildren()) {
  345.                     foreach ($category->getChildren() as $child) {
  346.                         if ($child instanceof TicketConsumerCategory && !in_array($child->getId(), $tempIds)) {
  347.                             $tempIds[] = $child->getId();
  348.                         }
  349.                     }
  350.                 }
  351.             }
  352.             $listing->addConditionParam("o_id IN (SELECT src_id FROM object_relations_{$listing->getClassId()} WHERE fieldname = 'ticketConsumerCategories' AND dest_id IN (" implode(','$tempIds) . '))');
  353.         }
  354.         return $listing;
  355.     }
  356.     /**
  357.      * Helper function for getting the price tendency. $pressures needs to be fetched from DB and therefore is not in this method (performance reasons).
  358.      *
  359.      * @param Carbon $date
  360.      * @param array<mixed> $pressures
  361.      *
  362.      * @return string
  363.      */
  364.     public function getPriceTendency(Carbon $date, array $pressures): string
  365.     {
  366.         $priceTendency 'low';
  367.         $demandPressureTimestamp $date->copy()->startOfDay()->getTimestamp();
  368.         if (array_key_exists($demandPressureTimestamp$pressures) && $pressures[$demandPressureTimestamp] > 0) {
  369.             $priceTendency $this->smartPricerService->getPressureText($pressures[$demandPressureTimestamp]);
  370.         }
  371.         return $priceTendency;
  372.     }
  373.     /**
  374.      * @param ShopTicketCatalog[] $catalogs
  375.      *
  376.      * @return array<mixed>
  377.      */
  378.     public function getPriceCalculatorData(array $catalogs): array
  379.     {
  380.         $recurringDates = [];
  381.         $useNextDayForAll true;
  382.         $minSelectableDates 2;
  383.         $maxSelectableDates 1;
  384.         $minSelectableDays $maxSelectableDays null;
  385.         foreach ($catalogs as $catalog) {
  386.             $recurringDates array_merge($this->recurringDatesHelper->getCalculatedDates($catalog'getValidityDates'), $recurringDates);
  387.             if ($useNextDayForAll && !$catalog->getUseNextDay()) {
  388.                 $useNextDayForAll false;
  389.             }
  390.             if (== $minSelectableDates && == $catalog->getMinSelectableDates()) {
  391.                 $minSelectableDates 1;
  392.             }
  393.             if (== $maxSelectableDates && == $catalog->getMaxSelectableDates()) {
  394.                 $maxSelectableDates 2;
  395.             }
  396.             if (null == $minSelectableDays || $catalog->getMinSelectableDays() < $minSelectableDays) {
  397.                 $minSelectableDays $catalog->getMinSelectableDays();
  398.             }
  399.             if (null == $maxSelectableDays || $catalog->getMaxSelectableDays() > $maxSelectableDays) {
  400.                 $maxSelectableDays $catalog->getMaxSelectableDays();
  401.             }
  402.         }
  403.         return [$recurringDates$useNextDayForAll$minSelectableDates$maxSelectableDates$minSelectableDays$maxSelectableDays];
  404.     }
  405.     /**
  406.      * @param Concrete $a
  407.      * @param Concrete $b
  408.      *
  409.      * @return int
  410.      */
  411.     private function orderDescending(Concrete $aConcrete $b)
  412.     {
  413.         if (method_exists($a'getOrder') && method_exists($b'getOrder')) {
  414.             $orderA $a->getOrder() ?? 0;
  415.             $orderB $b->getOrder() ?? 0;
  416.             return $orderB <=> $orderA;
  417.         }
  418.         return 0;
  419.     }
  420. }