<?php declare(strict_types=1);
namespace Nds\RecipeManager\Controller;
use Shopware\Core\Framework\Context;
use Shopware\Storefront\Page\MetaInformation;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Shopware\Storefront\Page\GenericPageLoader;
use Symfony\Component\Routing\Annotation\Route;
use Nds\RecipeManager\Core\Content\Recipe\RecipeEntity;
use Shopware\Storefront\Controller\StorefrontController;
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Nds\RecipeManager\Core\Content\Category\CategoryEntity;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
use Shopware\Core\Content\Cms\SalesChannel\SalesChannelCmsPageLoaderInterface;
use Nds\RecipeManager\Core\Content\Recipe\Aggregate\RecipeTranslation\RecipeTranslationEntity;
use Nds\RecipeManager\Core\Content\Category\Aggregate\CategoryTranslation\CategoryTranslationEntity;
/**
* @RouteScope(scopes={"storefront"})
*/
class NdsRecipeManagerController extends StorefrontController
{
protected GenericPageLoader $genericPageLoader;
protected SystemConfigService $systemConfigService;
protected EntityRepository $recipeRepository;
protected EntityRepository $recipeTranslationRepository;
protected EntityRepository $categoryRepository;
protected EntityRepository $categoryTranslationRepository;
protected EntityRepository $tagRepository;
protected SalesChannelCmsPageLoaderInterface $cmsPageLoader;
public function __construct(
EntityRepository $recipeRepository,
EntityRepository $recipeTranslationRepository,
EntityRepository $categoryRepository,
EntityRepository $categoryTranslationRepository,
EntityRepository $tagRepository,
GenericPageLoader $genericPageLoader,
SystemConfigService $systemConfigService
) {
$this->recipeRepository = $recipeRepository;
$this->recipeTranslationRepository = $recipeTranslationRepository;
$this->categoryRepository = $categoryRepository;
$this->categoryTranslationRepository = $categoryTranslationRepository;
$this->tagRepository = $tagRepository;
$this->genericPageLoader = $genericPageLoader;
$this->systemConfigService = $systemConfigService;
}
/**
* @Route("/rcp_category/{slug}", name="rcp.frontend.category", methods={"GET"})
*/
public function category(Request $request, Context $context, SalesChannelContext $salesChannelContext, string $slug): Response
{
$criteria = new Criteria();
$criteria
->addFilter(new EqualsFilter('slug', $slug))
->addFilter(new EqualsFilter('active', '1'))
->addAssociation('recipes');
/** @var CategoryEntity $category */
$category = $this->categoryRepository->search(
$criteria,
$context
)->first();
if (! $category) {
/** @var CategoryEntity $categoryTranslation */
$categoryTranslation = $this->categoryRepository->search(
new Criteria([$this->getCategoryIdBySlug($slug, $context)]),
$context
)->first();
if ($categoryTranslation) {
$url = $request->attributes->get('sw-sales-channel-base-url') . '/' . $categoryTranslation->getSlug();
return $this->redirect($url);
}
throw $this->createNotFoundException('The category ' . $slug . ' does not exist');
}
$page = $this->genericPageLoader->load($request, $salesChannelContext);
$page->setMetaInformation((new MetaInformation())->assign([
'metaTitle' => empty($category->getTranslation('metaTitle'))
? $category->getTranslation('name')
: $category->getTranslation('metaTitle'),
'metaDescription' => $category->getTranslation('metaDescription') ?? $category->getTranslation('description') ?? '',
'robots' => $category->getRobots() ?? '',
]));
return $this->renderStorefront(
'@Storefront/rcpmanager/page/recipe-list.html.twig',
[
'page' => $page,
'items' => $category,
]
);
}
/**
* @Route("/rcp_tag/{slug}", name="rcp.frontend.tag", methods={"GET"})
*/
public function tag(Request $request, Context $context, SalesChannelContext $salesChannelContext, string $slug): Response
{
$criteria = new Criteria();
$criteria
->addFilter(new EqualsFilter('slug', $slug))
->addFilter(new EqualsFilter('active', '1'))
->addAssociation('recipes');
$tag = $this->tagRepository->search(
$criteria,
$context
)->first();
if (! $tag) {
throw $this->createNotFoundException('The tag-slug: ' . $slug . ' does not exist');
}
$page = $this->genericPageLoader->load($request, $salesChannelContext);
return $this->renderStorefront(
'@Storefront/rcpmanager/page/recipe-list.html.twig',
[
'page' => $page,
'items' => $tag,
]
);
}
/**
* @Route("/rcp_recipe/{slug}", name="rcp.frontend.recipe", methods={"GET"})
*/
public function recipe(Request $request, Context $context, SalesChannelContext $salesChannelContext, string $slug): Response
{
$recipe = $this->getRecipe($slug, $context);
if (! $recipe) {
/** @var RecipeEntity $recipeTranslation */
$recipeTranslation = $this->recipeRepository->search(
new Criteria([$this->getRecipeIdBySlug($slug, $context)]),
$context
)->first();
if ($recipeTranslation) {
$url = $request->attributes->get('sw-sales-channel-base-url') . '/' . $recipeTranslation->getSlug();
return $this->redirect($url);
}
throw $this->createNotFoundException('The recipe ' . $slug . ' does not exist');
}
$page = $this->genericPageLoader->load($request, $salesChannelContext);
$page->setMetaInformation((new MetaInformation())->assign([
'metaTitle' => empty($recipe->getTranslation('metaTitle'))
? $recipe->getTranslation('name')
: $recipe->getTranslation('metaTitle'),
'metaDescription' => $this->getMetaDescription($recipe),
'robots' => $recipe->getRobots() ?? '',
]));
return $this->renderStorefront(
'@Storefront/rcpmanager/page/detail.html.twig',
[
'page' => $page,
'recipe' => $recipe,
'backLink' => $request->headers->get('referer'),
'schemaJson' => $this->getSchemaJson($recipe),
]
);
}
protected function getRecipe(string $slug, Context $context): ?RecipeEntity
{
$criteria = new Criteria();
$criteria
->addFilter(new EqualsFilter('slug', $slug))
->addFilter(new EqualsFilter('active', '1'))
->addAssociations(
[
'relatedRecipes',
'relatedRecipes.relatedRecipe',
'tags',
'categories',
'ingredients',
'ingredients.product',
'ingredients.unit',
'media',
'cover',
]
);
$criteria->getAssociation('ingredients')->addSorting(new FieldSorting('position', FieldSorting::ASCENDING));
$criteria->getAssociation('relatedRecipes')->addSorting(new FieldSorting('position', FieldSorting::ASCENDING));
$criteria->getAssociation('media')->addSorting(new FieldSorting('position', FieldSorting::ASCENDING));
return $this->recipeRepository->search(
$criteria,
$context
)->first();
}
protected function getCategoryIdBySlug(string $slug, Context $context): ?string
{
$criteria = new Criteria();
$criteria
->addFilter(new EqualsFilter('slug', $slug));
/** @var CategoryTranslationEntity $categoryTranslation */
$categoryTranslation = $this->categoryTranslationRepository->search(
$criteria,
$context
)->first();
if ($categoryTranslation === null) {
return null;
}
return $categoryTranslation->getNdsRcpCategoryId();
}
protected function getRecipeIdBySlug(string $slug, Context $context): ?string
{
$criteria = new Criteria();
$criteria
->addFilter(new EqualsFilter('slug', $slug));
/** @var RecipeTranslationEntity $recipeTranslation */
$recipeTranslation = $this->recipeTranslationRepository->search(
$criteria,
$context
)->first();
if ($recipeTranslation === null) {
return null;
}
return $recipeTranslation->getNdsRcpRecipeId();
}
protected function getMetaDescription($recipe): string
{
if ($recipe->getTranslation('metaDescription') !== null) {
return $recipe->getTranslation('metaDescription');
}
if ($recipe->getTranslation('description') !== null) {
return $recipe->getTranslation('description');
}
return '';
}
protected function getSchemaJson($recipe): string
{
$schemaJson['@context'] = 'https://schema.org';
$schemaJson['@type'] = 'Recipe';
$recipeName = $recipe->getTranslation('name');
$recipeSubhead = empty($recipe->getTranslation('subhead')) ? '' : ' - ' . $recipe->getTranslation('subhead');
$schemaJson['name'] = $recipeName . $recipeSubhead;
if (! empty($recipe->getTranslation('description'))) {
$schemaJson['description'] = $recipe->getTranslation('description');
}
if (! empty($recipe->coverId)) {
foreach ($recipe->media as $media) {
if ($recipe->coverId === $media->mediaId) {
$coverMedia = $media->media;
$schemaJson['image'] = $coverMedia->getUrl();
continue;
}
}
}
if (! empty($recipe->getTranslation('preparation'))) {
$schemaJson['recipeInstructions'] = $recipe->getPreparation();
}
if (! empty($recipe->getIngredients())) {
$ingredientList = [];
foreach ($recipe->getIngredients() as $ingredient) {
$ingredientUnits = $ingredient->getQuantity();
if ($ingredient->getUnit()) {
$ingredientUnits .= ' ' . $ingredient->getUnit()->getTranslation('value');
}
if (! empty($ingredient->getName())) {
$ingredientName = $ingredient->getTranslation('name');
} elseif (! empty($ingredient->getProductId()) && $ingredient->product !== null) {
$ingredientName = $ingredient->product->getTranslation('name');
} else {
continue;
}
$ingredientList[] = $ingredientUnits . ' ' . $ingredientName;
}
if (! empty($ingredientList)) {
$schemaJson['recipeIngredient'] = $ingredientList;
}
}
if ($recipe->getCategories()->count()) {
$categoryList = [];
foreach ($recipe->getCategories() as $category) {
$categoryList[] = $category->getTranslation('name');
}
$schemaJson['recipeCategory'] = implode(',', $categoryList);
}
if (! empty($recipe->getPreparationTime())) {
$schemaJson['totalTime'] = $this->Iso8601($recipe->getPreparationTime());
}
if ($recipe->hasNutritionalValues()) {
$nutritionalList = array_merge(['@type' => 'NutritionInformation'], $this->nutritionInformation($recipe));
$schemaJson['nutrition'] = $nutritionalList;
}
return json_encode($schemaJson);
}
protected function Iso8601($time): string
{
$time = (int) $time;
$time *= 60;
$units = [
'Y' => 365 * 24 * 3600,
'D' => 24 * 3600,
'H' => 3600,
'M' => 60,
'S' => 1,
];
$str = 'P';
$isTime = false;
foreach ($units as $unitName => $unit) {
$quot = (int) ($time / $unit);
$time -= $quot * $unit;
$unit = $quot;
if ($unit > 0) {
if (! $isTime && \in_array($unitName, ['H', 'M', 'S'], true)) { // There may be a better way to do this
$str .= 'T';
$isTime = true;
}
$str .= (string) $unit . $unitName;
}
}
return $str;
}
protected function nutritionInformation(RecipeEntity $recipe): array
{
$ret = [];
if ($recipe->getCalories()) {
$ret['calories'] = number_format($recipe->getCalories(), 1, ',', '.') . ' kcal';
}
if ($recipe->getCarbohydrates()) {
$ret['carbohydrateContent'] = number_format($recipe->getCarbohydrates(), 1, ',', '.') . ' g';
}
if ($recipe->getFat()) {
$ret['fatContent'] = number_format($recipe->getCarbohydrates(), 1, ',', '.') . ' g';
}
if ($recipe->getProtein()) {
$ret['proteinContent'] = number_format($recipe->getProtein(), 1, ',', '.') . ' g';
}
return $ret;
}
}