<?php declare(strict_types=1);
namespace Spf\Storefront\Controller;
use Shopware\Core\Checkout\Customer\CustomerEntity;
use Shopware\Core\Content\Cms\SalesChannel\SalesChannelCmsPageLoader;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\AndFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\ContainsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Storefront\Controller\StorefrontController;
use Shopware\Storefront\Page\Account\Overview\AccountOverviewPageLoader;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\Routing\Annotation\LoginRequired;
use Shopware\Storefront\Page\Navigation\NavigationPageLoader;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepository;
use Doctrine\DBAL\Connection;
/**
* @RouteScope(scopes={"storefront"})
*/
class SpfController extends StorefrontController
{
private AccountOverviewPageLoader $overviewPageLoader;
private SalesChannelRepository $productRepo;
private EntityRepositoryInterface $fullProductRepo;
private EntityRepositoryInterface $orderRepo;
private EntityRepositoryInterface $ruleRepo;
private EntityRepositoryInterface $priceRepo;
private EntityRepositoryInterface $ruleConditionRepo;
public function __construct(
AccountOverviewPageLoader $overviewPageLoader,
NavigationPageLoader $pageLoader,
EntityRepositoryInterface $orderRepo,
SalesChannelRepository $productRepo,
EntityRepositoryInterface $fullProductRepo,
EntityRepositoryInterface $ruleRepo,
EntityRepositoryInterface $ruleConditionRepo,
EntityRepositoryInterface $priceRepo,
Connection $connection)
{
$this->overviewPageLoader = $overviewPageLoader;
$this->pageLoader = $pageLoader;
$this->productRepo = $productRepo;
$this->fullProductRepo = $fullProductRepo;
$this->orderRepo = $orderRepo;
$this->connection = $connection;
$this->ruleRepo = $ruleRepo;
$this->priceRepo = $priceRepo;
$this->ruleConditionRepo = $ruleConditionRepo;
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/ersatzteilfinder/{id}", name="frontend.spf.spf", methods={"GET"})
*/
public function showSpf(string $id, Request $request, SalesChannelContext $context): Response
{
$page = $this->pageLoader->load($request, $context);
$articles = [];
$allGroups = [];
$groups = [];
$pointsGrouped = [];
$shopProducts = [];
$title = '';
$categories = [];
$noResult = true;
$model = self::getIdFromSlug($id);
if (!empty($model))
{
$connection = \Shopware\Core\Kernel::getConnection();
$result = $connection->executeQuery("SELECT s.*, c.name, n.name as article_description, IF (p.active = 1, lower(hex(p.id)), null) as product_id FROM hobaIT_sparepartfinder AS s
LEFT JOIN hobaIT_csb_article_data AS c ON c.article_number = s.article_number
LEFT JOIN hobaIT_sparepartfinder_article_names n ON n.article_number = s.article_number
LEFT JOIN product AS p ON p.product_number = s.article_number
WHERE model_number = ? ORDER BY `group`, sequence_number ASC", [$model]);
foreach ($result as $res)
{
if (empty($groups[$res['group']]))
{
$groups[$res['group']] = [];
}
$groups[$res['group']][] = $res;
$articles[$res['article_number']] = 1;
$shopProducts[$res['product_id']] = 1;
}
array_walk($articles, function (&$value, $key) {
$value = '"' . $key . '"';
});
if (!empty($groups))
{
$noResult = false;
$allGroups = $connection->executeQuery("SELECT * FROM hobaIT_sparepartfinder_groups WHERE id IN (" . implode(',', array_keys($groups)) . ") ORDER BY number, part_type ASC")->fetchAllAssociative();
$points = $connection->executeQuery("SELECT * FROM hobaIT_sparepartfinder_images WHERE group_id IN (" . implode(',', array_keys($groups)) . ") AND article_number IN (" . implode(',', $articles) . ")")->fetchAllAssociative();
$model = $connection->executeQuery("SELECT * FROM hobaIT_sparepartfinder_models WHERE model_number = ?", [$model])->fetchAssociative();
$powerParts = $connection->executeQuery("SELECT s.product_number as product_number, lower(hex(p.id)) as id FROM hobaIT_sparepartfinder_powerparts as s LEFT JOIN product as p on p.product_number = s.product_number WHERE model_number = ?", [$model['model_number']])->fetchAllAssociative();
foreach ($points as $point)
{
if (empty($pointsGrouped[$point['group_id']][$point['article_number']]))
{
$pointsGrouped[$point['group_id']][$point['article_number']] = [];
}
$pointsGrouped[$point['group_id']][$point['article_number']][] = [
'x' => $point['point_x'],
'y' => $point['point_y']
];
}
$model['has_frame'] = false;
$model['has_engine'] = false;
foreach ($allGroups as &$g)
{
if ($g['part_type'] == 0)
{
$model['has_frame'] = true;
}
if ($g['part_type'] == 1)
{
$model['has_engine'] = true;
}
$g['articles'] = $groups[$g['id']];
}
$title = $page->getMetaInformation()->getMetaTitle();
$page->getMetaInformation()->setMetaTitle($title . ' - Ersatzteile ' . $model['model_name']);
$shopProducts = $this->getProductsByIds(array_keys($shopProducts), $context);
}
if (!empty($powerParts))
{
$ppId = (array_column($powerParts, 'id'));
$powerParts = self::getProductsByIds($ppId, $context);
foreach ($powerParts as $item)
{
if (empty($categories[$item->getCategories()->first()->getName()]))
{
$categories[$item->getCategories()->first()->getName()] = [];
}
$categories[$item->getCategories()->first()->getName()][] = $item;
}
ksort($categories);
}
}
if ($noResult)
{
$model = [];
$model['model_name'] = 'Kein Ergebnis';
$model['has_frame'] = false;
$model['has_engine'] = false;
$page->getMetaInformation()->setMetaTitle($title . ' Ersatzteilfinder - Fehler: Keine Resultate');
}
return $this->renderStorefront('@Spf/storefront/page/spf.html.twig', [
'page' => $page,
'groups' => $allGroups,
'model' => $model,
'points' => json_encode($pointsGrouped),
'shopProducts' => $shopProducts,
'powerparts' => $categories
]
);
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/ersatzteilfinder", name="frontend.spf.models", methods={"GET"})
*/
public function showModelList(Request $request, SalesChannelContext $context): Response
{
$page = $this->pageLoader->load($request, $context);
$connection = \Shopware\Core\Kernel::getConnection();
$title = $page->getMetaInformation()->getMetaTitle();
$page->getMetaInformation()->setMetaTitle($title . ' - Ersatzteilfinder Modellübersicht');
$results = $connection->executeQuery('SELECT DISTINCT m.model_number AS id , m.model_name as `name` , m.model_code AS `hex`, count(s.group) AS results FROM hobaIT_sparepartfinder_models AS m LEFT JOIN hobaIT_sparepartfinder AS s ON m.model_number=s.model_number WHERE manufacturer = "KTM" GROUP BY m.model_number order BY m.model_name ASC');
$ktmModels = self::processModelList($results);
$results = $connection->executeQuery('SELECT DISTINCT m.model_number AS id , m.model_name as `name` , m.model_code AS `hex`, count(s.group) AS results FROM hobaIT_sparepartfinder_models AS m LEFT JOIN hobaIT_sparepartfinder AS s ON m.model_number=s.model_number WHERE manufacturer = "Husqvarna" GROUP BY m.model_number order BY m.model_name ASC');
$huskyModels = self::processModelList($results);
$results = $connection->executeQuery('SELECT DISTINCT m.model_number AS id , m.model_name as `name` , m.model_code AS `hex`, count(s.group) AS results FROM hobaIT_sparepartfinder_models AS m LEFT JOIN hobaIT_sparepartfinder AS s ON m.model_number=s.model_number WHERE manufacturer = "GASGAS" GROUP BY m.model_number order BY m.model_name ASC');
$gasgasModels = self::processModelList($results);
return $this->renderStorefront('@Spf/storefront/page/models.html.twig', [
'page' => $page,
'ktmModels' => $ktmModels,
'huskyModels' => $huskyModels,
'gasgasModels' => $gasgasModels,
]
);
}
private static function processModelList(\Doctrine\DBAL\ForwardCompatibility\Result $results)
{
$models = [];
foreach ($results as $result)
{
$result['slug'] = self::slugify($result['name']);
$models[] = $result;
}
return $models;
}
/**
* @param array $cats
* @param string $articlesFile
*
* @return void
*/
public static function getCachedNewsPosts(array $cats = [5, 7, 14], string $articlesFile = 'article-data.json')
{
$forceReload = $_GET['forceReload'] ?? 0;
if ($forceReload || time() - filemtime($articlesFile) >= 6 * 3600)
{
$cats = [5, 7, 14];
$host = 'https://newsblog.ktm-sturm.de/wp-json/wp/v2/posts?categories=' . implode(',', $cats) . '&per_page=100';
$ch = curl_init($host);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$return = json_decode(curl_exec($ch));
curl_close($ch);
$posts = [];
foreach ($return as $item)
{
$content = self::truncate($item->content->rendered);
$posts[$item->id]['title'] = $item->title->rendered;
$posts[$item->id]['content'] = $content;
$posts[$item->id]['content_full'] = $item->content->rendered;
$posts[$item->id]['date'] = $item->date;
$posts[$item->id]['id'] = $item->id;
$posts[$item->id]['slug'] = self::slugify($item->title->rendered) . '-' . $item->id;
$posts[$item->id]['cat'] = $item->categories;
if (!empty($item->featured_image_url))
{
$posts[$item->id]['featured_image_url'] = $item->featured_image_url;
}
if (!empty($item->featured_image_url))
{
$posts[$item->id]['thumb_image_url'] = $item->thumb_image_url;
}
}
file_put_contents($articlesFile, json_encode($posts));
}
}
/**
* @param array $cats
* @param string $articlesFile
*
* @return array
*/
public static function getNewsPostsFromCache(array $cats, string $articlesFile = 'article-data.json')
{
$matching = [];
$posts = json_decode(file_get_contents($articlesFile), true);
foreach ($posts as $post)
{
if (count(array_intersect($cats, $post['cat'])) > 0)
{
$matching[] = $post;
}
}
return $matching;
}
/**
* @param array $cats
* @param int $perPage
*
* @return array
*/
public static function getNewsPosts(array $cats = [5, 7, 14], int $perPage = 9, int $limit = 0)
{
$articlesFile = 'article-data.json';
$pagination = '';
self::getCachedNewsPosts($cats, $articlesFile);
$articles = self::getNewsPostsFromCache($cats, $articlesFile);
if ($limit)
{
$articles = array_slice($articles, 0, $limit);
}
if ($perPage)
{
$page = $_GET['page'] ?? 1;
$total = count($articles);
$posts = array_slice($articles, ($page - 1) * $perPage, $perPage);
$pageCount = ((int) ($total / $perPage) + 1);
$pagination = '<div class="pagination">';
if ($page > 1)
{
$pagination .= '<a class="page prev" href="/news?page=' . ($page - 1) . '">«</a>';
}
for ($i = 1; $i <= $pageCount; $i++)
{
if ($i == $page)
{
$pagination .= '<span class="page active">[' . $i . ']</span>';
}
else
{
$pagination .= '<a class="page" href="/news?page=' . $i . '"> ' . $i . ' </a>';
}
}
if ($page < $pageCount)
{
$pagination .= '<a class="page next" href="/news?page=' . ($page + 1) . '">»</a>';
}
$pagination .= '</div>';
}
else
{
$posts = $articles;
}
return [$posts, $pagination];
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/news", name="frontend.news.article", methods={"GET"})
*/
public function showNews(Request $request, SalesChannelContext $context): Response
{
list($posts, $pagination) = self::getNewsPosts([5],9);
$page = $this->pageLoader->load($request, $context);
return $this->renderStorefront('@Spf/storefront/page/news.html.twig', [
'page' => $page,
'posts' => $posts,
'pagination' => $pagination
]
);
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/blog", name="frontend.blog.article", methods={"GET"})
*/
public function showBlog(Request $request, SalesChannelContext $context): Response
{
list($posts, $pagination) = self::getNewsPosts([14], 9);
$page = $this->pageLoader->load($request, $context);
return $this->renderStorefront('@Spf/storefront/page/blog.html.twig', [
'page' => $page,
'posts' => $posts,
'pagination' => $pagination
]
);
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/news/{id}", name="frontend.news.news", methods={"GET"})
*/
public function showArticle(string $id, Request $request, SalesChannelContext $context): Response
{
$page = $this->pageLoader->load($request, $context);
$articles = json_decode(file_get_contents('article-data.json'));
$id = self::getIdFromSlug($id);
return $this->renderStorefront('@Spf/storefront/page/article.html.twig', [
'page' => $page,
'item' => $articles->{$id}
]
);
}
/**
* Get id from a slug
*
* @param string $slug
*
* @return string
*/
private static function getIdFromSlug(string $slug): int
{
$exp = explode('-', $slug);
$id = $exp[count($exp) - 1];
if ('modelcode' == $exp[count($exp) - 2])
{
$connection = \Shopware\Core\Kernel::getConnection();
$id = $connection->executeQuery("SELECT model_number FROM hobaIT_sparepartfinder_models WHERE model_code LIKE ?", ['%' . $id . '%'])->fetchFirstColumn()[0];
}
return (int) $id;
}
/**
* Truncate text
*
* @param string $str
* @param int $width
*
* @return string
*/
private
static function truncate(string $str, int $width = 300)
{
if (strlen($str) <= $width)
{
return $str;
}
$str = str_replace("\n", '', strip_tags($str));
return substr($str, 0, strpos(wordwrap($str, $width), "\n")) . '...';
}
/**
* Create a slug
*
* @param $text
* @param string $divider
*
* @return string
*/
private
static function slugify($text, string $divider = '-')
{
// replace non letter or digits by divider
$text = preg_replace('~[^\pL\d]+~u', $divider, $text);
// transliterate
$text = iconv('utf-8', 'ISO-8859-1//TRANSLIT', $text);
// remove unwanted characters
$text = preg_replace('~[^-\w]+~', '', $text);
// trim
$text = trim($text, $divider);
// remove duplicate divider
$text = preg_replace('~-+~', $divider, $text);
// lowercase
$text = strtolower($text);
if (empty($text))
{
return 'n-a';
}
return $text;
}
/**
* @param array $productIds
* @param $context
*
* @return object
*/
public
function getProductsByIds(array $productIds, $context): object
{
$productsCriteria = (new Criteria($productIds))
->addAssociation('prices')
->addAssociation('categories')
->addAssociation('media');
return $this->productRepo->search($productsCriteria, $context)->getEntities();
}
}