Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Controller/Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ public function viewMore(Request $request, string $filterField): Response
}

$choices = [];
/** @var Taxon $currentTaxon */
$currentTaxon = $this->taxonRepository->find($request->get('taxon'));
/** @var ?Taxon $currentTaxon */
$currentTaxon = $request->get('taxon') ? $this->taxonRepository->find($request->get('taxon')) : null;
/** @var ChannelInterface $currentChannel */
$currentChannel = $this->channelContext->getChannel();
$currentLocaleCode = $this->localeContext->getLocaleCode();
Expand All @@ -86,7 +86,7 @@ public function viewMore(Request $request, string $filterField): Response
['sku', 'source'],
1,
0,
$currentTaxon->getCode(),
$currentTaxon?->getCode(),
$search,
$gallyFilters,
);
Expand Down
159 changes: 159 additions & 0 deletions src/Controller/Shop/SearchController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php
/**
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade Gally to newer versions in the future.
*
* @package Gally
* @author Stephan Hochdörfer <S.Hochdoerfer@bitexpert.de>, Gally Team <elasticsuite@smile.fr>
* @copyright 2022-present Smile
* @license Open Software License v. 3.0 (OSL-3.0)
*/

declare(strict_types=1);

namespace Gally\SyliusPlugin\Controller\Shop;

use Gally\Sdk\GraphQl\Response as GallyResponse;
use Gally\SyliusPlugin\Form\Type\SearchFormType;
use Gally\SyliusPlugin\Model\GallyChannelInterface;
use Gally\SyliusPlugin\Search\Finder;
use Sylius\Component\Channel\Context\ChannelContextInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;

class SearchController extends AbstractController
{
public function __construct(
private Finder $finder,
private ChannelContextInterface $channelContext,
) {
}

public function getForm(Request $renderRequest, RequestStack $requestStack): Response
{
/** @var string $query */
$query = $requestStack->getMainRequest()?->get('query');
if (empty($query)) {
/** @var array $query */
$query = $requestStack->getMainRequest()?->get('criteria', []);
$query = $query['search']['value'] ?? '';
}

$searchForm = $this->createForm(
SearchFormType::class,
['query' => $query],
['action' => $this->generateUrl('gally_search_result_page'), 'method' => 'POST']
);

return $this->render(
'@GallySyliusPlugin/shop/shared/components/header/search/form.html.twig',
[
'searchForm' => $searchForm->createView(),
'mobileMode' => $renderRequest->get('mobile_mode'),
]
);
}

public function getResults(Request $request): Response
{
$searchForm = $this->createForm(SearchFormType::class);
$searchForm->handleRequest($request);

if ($searchForm->isSubmitted() && $searchForm->isValid()) {
return new RedirectResponse(
$this->generateUrl('gally_search_result_page', ['query' => $searchForm->get('query')->getData()])
);
}

return new RedirectResponse('/');
}

public function getPreview(Request $request): Response
{
$searchForm = $this->createForm(SearchFormType::class);
$searchForm->handleRequest($request);
/** @var GallyChannelInterface $currentChannel */
$currentChannel = $this->channelContext->getChannel();

if ($searchForm->isSubmitted() && $searchForm->isValid()) {
/** @var string $query */
$query = $searchForm->get('query')->getData();

$products = $this->getProductAutocomplete($query, $currentChannel);

return new JsonResponse([
'htmlResults' => $this->renderView(
'@GallySyliusPlugin/shop/shared/components/header/search/autocomplete/results.html.twig',
[
'products' => $products->getCollection(),
'categories' => $this->getCategoryAutocomplete($query, $currentChannel),
'attributes' => $this->getAttributeAutocomplete($products, $currentChannel),
'query' => $query, $this->generateUrl('gally_search_result_page', ['query' => $query]),
]
),
]);
}

return new JsonResponse(['gallyError' => true]);
}

private function getProductAutocomplete(string $query, GallyChannelInterface $channel): GallyResponse
{
return $this->finder->getAutocompleteResults(
$query,
$channel->getGallyAutocompleteProductMaxSize(),
'product',
['sku', 'name', 'slug', 'image']
);
}

private function getCategoryAutocomplete(string $query, GallyChannelInterface $channel): array
{
$categories = $this->finder
->getAutocompleteResults(
$query,
$channel->getGallyAutocompleteCategoryMaxSize(),
'category',
['id', 'name', 'path', 'slug']
)
->getCollection();

foreach ($categories as &$category) {
$category['path'] = implode(
' > ',
array_map(
fn (string $slug) => ucfirst($slug),
explode('/', $category['slug'])
)
);
}

return $categories;
}

private function getAttributeAutocomplete(GallyResponse $products, GallyChannelInterface $channel): array
{
$attributes = [];
$count = 0;
foreach ($products->getAggregations() as $aggregation) {
foreach ($aggregation['options'] as $option) {
$attributes[] = [
'field' => $aggregation['field'],
'label' => $aggregation['label'],
'option_label' => $option['label'],
];
++$count;
if ($count >= $channel->getGallyAutocompleteAttributeMaxSize()) {
break 2;
}
}
}

return $attributes;
}
}
9 changes: 9 additions & 0 deletions src/Form/Extension/ChannelTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
])
->add('gallyCategoryIndexBatchSize', IntegerType::class, [
'label' => 'gally_sylius.form.category_index_batch_size',
])
->add('gallyAutocompleteProductMaxSize', IntegerType::class, [
'label' => 'gally_sylius.form.gally_autocomplete_product_max_size',
])
->add('gallyAutocompleteCategoryMaxSize', IntegerType::class, [
'label' => 'gally_sylius.form.gally_autocomplete_category_max_size',
])
->add('gallyAutocompleteAttributeMaxSize', IntegerType::class, [
'label' => 'gally_sylius.form.gally_autocomplete_attribute_max_size',
]);
}

Expand Down
3 changes: 2 additions & 1 deletion src/Form/Type/Filter/GallyDynamicFilterType.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ private function buildHasMoreUrl(string $field): string
$parameters = new Parameters($queryParameters);
/** @var array<string, array<string, mixed>> $criteria */
$criteria = $parameters->get('criteria', []);
$search = (isset($criteria['search'], $criteria['search']['value'])) ? $criteria['search']['value'] : '';
$query = $parameters->get('query', null);
$search = $query ?: ((isset($criteria['search'], $criteria['search']['value'])) ? $criteria['search']['value'] : '');
unset($criteria['search']);
/** @var string $slug */
$slug = $request?->attributes->get('slug') ?? '';
Expand Down
40 changes: 40 additions & 0 deletions src/Form/Type/SearchFormType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
/**
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade Gally to newer versions in the future.
*
* @package Gally
* @author Stephan Hochdörfer <S.Hochdoerfer@bitexpert.de>, Gally Team <elasticsuite@smile.fr>
* @copyright 2022-present Smile
* @license Open Software License v. 3.0 (OSL-3.0)
*/

declare(strict_types=1);

namespace Gally\SyliusPlugin\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class SearchFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add(
'query',
TextType::class,
[
'label' => false,
'attr' => [
'class' => 'form-control',
'placeholder' => 'gally_sylius.form.header.query.placeholder',
'autocomplete' => 'off',
'aria-label' => 'form.header.query.label',
'aria-describedby' => 'collapsedSearchResults', // This should be the ID of the button below
],
]
);
}
}
2 changes: 1 addition & 1 deletion src/Grid/DataProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function __construct(

public function getData(Grid $grid, Parameters $parameters)
{
if ('sylius_shop_product' === $grid->getCode()) {
if ('sylius_shop_product' === $grid->getCode() || 'gally_shop_product_search' === $grid->getCode()) {
$channel = $this->channelContext->getChannel();
if (($channel instanceof GallyChannelInterface) && $channel->getGallyActive()) {
$dataSource = $this->dataSourceProvider->getDataSource($grid, $parameters);
Expand Down
72 changes: 72 additions & 0 deletions src/Grid/Gally/Search/DataSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php
/**
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade Gally to newer versions in the future.
*
* @package Gally
* @author Stephan Hochdörfer <S.Hochdoerfer@bitexpert.de>, Gally Team <elasticsuite@smile.fr>
* @copyright 2022-present Smile
* @license Open Software License v. 3.0 (OSL-3.0)
*/

declare(strict_types=1);

namespace Gally\SyliusPlugin\Grid\Gally\Search;

use Doctrine\ORM\QueryBuilder;
use Gally\Sdk\Service\SearchManager;
use Gally\SyliusPlugin\Grid\Gally\ExpressionBuilder;
use Gally\SyliusPlugin\Grid\Gally\PagerfantaGally;
use Gally\SyliusPlugin\Indexer\Provider\CatalogProvider;
use Sylius\Component\Grid\Data\DataSourceInterface;
use Sylius\Component\Grid\Data\ExpressionBuilderInterface;
use Sylius\Component\Grid\Parameters;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

final class DataSource implements DataSourceInterface
{
private ExpressionBuilderInterface $expressionBuilder;
private array $filters = [];

public function __construct(
private QueryBuilder $queryBuilder,
private SearchManager $searchManager,
private CatalogProvider $catalogProvider,
private EventDispatcherInterface $eventDispatcher
) {
$this->expressionBuilder = new ExpressionBuilder();
}

public function restrict($expression, string $condition = DataSourceInterface::CONDITION_AND): void
{
$this->filters[] = $expression;
}

public function getExpressionBuilder(): ExpressionBuilderInterface
{
return $this->expressionBuilder;
}

public function getData(Parameters $parameters)
{
/** @var int|string $page */
$page = $parameters->get('page', 1);
$page = (int) $page;
$paginator = new PagerfantaGally(
new SearchAdapter(
$this->queryBuilder,
$this->searchManager,
$this->catalogProvider,
$this->eventDispatcher,
$parameters,
$this->filters
)
);

$paginator->setNormalizeOutOfRangePages(true);
$paginator->setCurrentPage($page > 0 ? $page : 1);

return $paginator;
}
}
Loading