<?php
namespace App\Controller\Addon;
use App\Controller\BaseApiController;
use App\Entity\Config\SmartAlternativeConfigRuleTranslation;
use App\Exception\AdminException;
use App\Exception\ShoptetAddonException;
use App\Repository\Config\SmartAlternativeConfigRuleCategoryRepository;
use App\Repository\Config\SmartAlternativeConfigRuleRepository;
use App\Service\AdminCacheService;
use App\Service\ConfigRuleService;
use App\Service\ShoptetApiService;
use App\Service\ShoptetProductsService;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Service\Attribute\Required;
use Twig\Environment;
#[Route(path: '/suggestions', name: 'suggestions_')]
class SmartSuggestionController extends BaseApiController
{
#[Required]
public ShoptetProductsService $productsService;
#[Required]
public AdminCacheService $cacheService;
#[Required]
public ShoptetApiService $apiService;
#[Required]
public ConfigRuleService $ruleService;
#[Required]
public SmartAlternativeConfigRuleRepository $ruleRepository;
#[Required]
public SmartAlternativeConfigRuleCategoryRepository $ruleCategoryRepository;
#[Required]
public Environment $twig;
#[Route(path: '/{_locale}/{eshopId}/{productId}', name: "main", requirements: ['_locale' => 'cs|sk|en|de|vi|pl|hu|ro'], methods: ['GET'])]
public function getSuggestions(Request $request, MessageBusInterface $messageBus, int $eshopId, string $productId): Response
{
$locale = $request->getLocale();
$installation = $this->findInstallation($eshopId);
$currencyOverride = $request->query->get("currencyCode");
if (!$installation) {
throw new ShoptetAddonException(
404,
"Installation for shoptet ID $eshopId does not exist. Could not find any smart suggestions.",
"smart_suggestions.no_installation"
);
}
try {
$config = $this->getConfig($installation, $eshopId, $messageBus);
} catch (AdminException $exception) {
$this->convertAdminExceptionToApiException($exception);
}
if (!$config->isIsActive()) {
return new Response(null, 204);
}
if ($config->getProductsExpiry() === null) {
if (!$config->isIsProductsRetrieved())
$this->productsService->retrieveAllProducts($installation);
throw new ShoptetAddonException(
400,
"Products are not yet retrieved for this eshop. Could not find any smart suggestions.",
"smart_suggestions.no_product_list"
);
}
$foreignLanguagesActive = $this->apiService->checkModuleExists($eshopId, $installation, "foreignLanguages");
$productDetail = $this->cacheService->getProductDetail($productId, $locale, $eshopId, $installation, $foreignLanguagesActive);
if (!$productDetail) {
throw new ShoptetAddonException(
404,
"Product with $productId was not found. Could not find any smart suggestions.",
"smart_suggestions.product_not_found"
);
}
$ruleCategory = $this->findActiveRuleCategoryRecursively($productDetail, $installation);
if ($ruleCategory === null) {
try {
$rule = $this->ruleService->getGlobalConfigRule($installation->getConfig()->getId());
// Check if global rule is activated
if (!$rule->isIsGlobalActivated() && $rule->isIsGlobal()) {
return new Response(null, 204);
}
} catch (AdminException $exception) {
throw new ShoptetAddonException($exception->getStatusCode(), $exception->getMessage(), $exception->getErrorSlug());
}
} else {
$rule = $ruleCategory->getRule();
}
if ($config->willProductsExpire())
$this->productsService->retrieveAllProducts($installation);
if (
$rule->isIsStockStatusVisitedSuggestion()
&& !$this->productsService->checkProductStockStatusId($productDetail["variants"][0], $this->cacheService->getDefaultStockStatuses($eshopId, $installation, $locale), $rule->getStockStatusVisitedIds())
)
return new Response(null, 204);
$suggestions = $this->cacheService->getSmartSuggestions($eshopId, $locale, $productDetail, $installation, $rule, $ruleCategory, $currencyOverride);
// Get title for the suggestion
/** @var SmartAlternativeConfigRuleTranslation $ruleTranslation */
$ruleTranslation = $rule->translate($locale);
$title = $ruleTranslation->getSectionTitle();
$template = $request->query->get("template");
return new JsonResponse([
"title" => $title,
"html" => $this->twig->render("suggestions/suggestions.html.twig", ["products" => $suggestions, "template" => $template]),
"customHtmlSelector" => $config->getCustomHtmlSelector(),
]);
}
/**
* Recursively (iteratively) find the active rule category by traversing up the category tree.
*/
private function findActiveRuleCategoryRecursively(array $productDetail, $installation)
{
// Start with the product's default category
$currentCategoryGuid = $productDetail["defaultCategory"]["guid"] ?? null;
$visited = [];
while ($currentCategoryGuid) {
if (in_array($currentCategoryGuid, $visited, true)) {
break;
}
$visited[] = $currentCategoryGuid;
$ruleCategory = $this->ruleCategoryRepository->getRuleActiveCategory($currentCategoryGuid);
if ($ruleCategory !== null) {
return $ruleCategory;
}
// Find parent guid
$parentGuid = null;
// Try to find parent in productDetail categories
if (isset($productDetail["categories"])) {
foreach ($productDetail["categories"] as $cat) {
if (($cat["guid"] ?? null) === $currentCategoryGuid) {
$parentGuid = $cat["parentGuid"] ?? null;
break;
}
}
}
// If not found, fetch from API
if ($parentGuid === null) {
$categoryData = $this->cacheService->getNextCategoryGuid($currentCategoryGuid, $installation);
$parentGuid = $categoryData["parentGuid"] ?? null;
}
$currentCategoryGuid = $parentGuid;
}
return null;
}
}