Skip to content

Commit 325562e

Browse files
Add attribute in autocomplete
Add urls Add category paths
1 parent b02531d commit 325562e

File tree

21 files changed

+374
-88
lines changed

21 files changed

+374
-88
lines changed
Lines changed: 93 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
<?php
2+
/**
3+
* DISCLAIMER
4+
*
5+
* Do not edit or add to this file if you wish to upgrade Gally to newer versions in the future.
6+
*
7+
* @package Gally
8+
* @author Stephan Hochdörfer <S.Hochdoerfer@bitexpert.de>, Gally Team <elasticsuite@smile.fr>
9+
* @copyright 2022-present Smile
10+
* @license Open Software License v. 3.0 (OSL-3.0)
11+
*/
212

313
declare(strict_types=1);
414

515
namespace Gally\SyliusPlugin\Controller\Shop;
616

17+
use Gally\Sdk\GraphQl\Response as GallyResponse;
718
use Gally\SyliusPlugin\Form\Type\SearchFormType;
19+
use Gally\SyliusPlugin\Model\GallyChannelInterface;
820
use Gally\SyliusPlugin\Search\Finder;
21+
use Sylius\Component\Channel\Context\ChannelContextInterface;
922
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1023
use Symfony\Component\HttpFoundation\JsonResponse;
1124
use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -15,36 +28,37 @@
1528

1629
class SearchController extends AbstractController
1730
{
18-
public function __construct(private Finder $finder)
19-
{
31+
public function __construct(
32+
private Finder $finder,
33+
private ChannelContextInterface $channelContext,
34+
) {
2035
}
2136

22-
public function renderForm(RequestStack $requestStack): Response
37+
public function getForm(RequestStack $requestStack): Response
2338
{
24-
$query = $requestStack->getMainRequest()->get('query');
25-
// Try the search filter
39+
/** @var string $query */
40+
$query = $requestStack->getMainRequest()?->get('query');
2641
if (empty($query)) {
27-
$query = $requestStack->getMainRequest()->get('criteria', [])['search']['value'] ?? '';
42+
/** @var array $query */
43+
$query = $requestStack->getMainRequest()?->get('criteria', []);
44+
$query = $query['search']['value'] ?? '';
2845
}
2946

3047
$searchForm = $this->createForm(
3148
SearchFormType::class,
3249
['query' => $query],
33-
[
34-
'action' => $this->generateUrl('gally_search_result_page'),
35-
'method' => 'POST',
36-
]
50+
['action' => $this->generateUrl('gally_search_result_page'), 'method' => 'POST']
3751
);
3852

39-
return $this->render('@GallySyliusPlugin/shop/shared/components/header/search/form.html.twig', [
40-
'searchForm' => $searchForm->createView(),
41-
]);
53+
return $this->render(
54+
'@GallySyliusPlugin/shop/shared/components/header/search/form.html.twig',
55+
['searchForm' => $searchForm->createView()]
56+
);
4257
}
4358

4459
public function getResults(Request $request): Response
4560
{
4661
$searchForm = $this->createForm(SearchFormType::class);
47-
4862
$searchForm->handleRequest($request);
4963

5064
if ($searchForm->isSubmitted() && $searchForm->isValid()) {
@@ -60,24 +74,83 @@ public function getPreview(Request $request): Response
6074
{
6175
$searchForm = $this->createForm(SearchFormType::class);
6276
$searchForm->handleRequest($request);
77+
/** @var GallyChannelInterface $currentChannel */
78+
$currentChannel = $this->channelContext->getChannel();
6379

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

84+
$products = $this->getProductAutocomplete($query, $currentChannel);
85+
6786
return new JsonResponse([
6887
'htmlResults' => $this->renderView(
6988
'@GallySyliusPlugin/shop/shared/components/header/search/autocomplete/results.html.twig',
7089
[
71-
'products' => $this->finder->getAutocompleteResults($query),
72-
'categories' => $this->finder->getAutocompleteResults($query, 'category', ['source', 'path', 'name']),
73-
'view_all_results_link' => $this->generateUrl('gally_search_result_page', [
74-
'query' => $query
75-
]),
90+
'products' => $products->getCollection(),
91+
'categories' => $this->getCategoryAutocomplete($query, $currentChannel),
92+
'attributes' => $this->getAttributeAutocomplete($products, $currentChannel),
93+
'query' => $query, $this->generateUrl('gally_search_result_page', ['query' => $query]),
7694
]
77-
)
95+
),
7896
]);
7997
}
8098

8199
return new JsonResponse(['gallyError' => true]);
82100
}
101+
102+
private function getProductAutocomplete(string $query, GallyChannelInterface $channel): GallyResponse
103+
{
104+
return $this->finder->getAutocompleteResults(
105+
$query,
106+
$channel->getGallyAutocompleteProductMaxSize(),
107+
'product',
108+
['sku', 'name', 'slug', 'image']
109+
);
110+
}
111+
112+
private function getCategoryAutocomplete(string $query, GallyChannelInterface $channel): array
113+
{
114+
$categories = $this->finder
115+
->getAutocompleteResults(
116+
$query,
117+
$channel->getGallyAutocompleteCategoryMaxSize(),
118+
'category',
119+
['id', 'name', 'path', 'slug']
120+
)
121+
->getCollection();
122+
123+
foreach ($categories as &$category) {
124+
$category['path'] = implode(
125+
' > ',
126+
array_map(
127+
fn (string $slug) => ucfirst($slug),
128+
explode('/', $category['slug'])
129+
)
130+
);
131+
}
132+
133+
return $categories;
134+
}
135+
136+
private function getAttributeAutocomplete(GallyResponse $products, GallyChannelInterface $channel): array
137+
{
138+
$attributes = [];
139+
$count = 0;
140+
foreach ($products->getAggregations() as $aggregation) {
141+
foreach ($aggregation['options'] as $option) {
142+
$attributes[] = [
143+
'field' => $aggregation['field'],
144+
'label' => $aggregation['label'],
145+
'option_label' => $option['label'],
146+
];
147+
++$count;
148+
if ($count >= $channel->getGallyAutocompleteAttributeMaxSize()) {
149+
break 2;
150+
}
151+
}
152+
}
153+
154+
return $attributes;
155+
}
83156
}

src/Form/Extension/ChannelTypeExtension.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
3434
])
3535
->add('gallyCategoryIndexBatchSize', IntegerType::class, [
3636
'label' => 'gally_sylius.form.category_index_batch_size',
37+
])
38+
->add('gallyAutocompleteProductMaxSize', IntegerType::class, [
39+
'label' => 'gally_sylius.form.gally_autocomplete_product_max_size',
40+
])
41+
->add('gallyAutocompleteCategoryMaxSize', IntegerType::class, [
42+
'label' => 'gally_sylius.form.gally_autocomplete_category_max_size',
43+
])
44+
->add('gallyAutocompleteAttributeMaxSize', IntegerType::class, [
45+
'label' => 'gally_sylius.form.gally_autocomplete_attribute_max_size',
3746
]);
3847
}
3948

src/Form/Type/SearchFormType.php

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,40 @@
11
<?php
2+
/**
3+
* DISCLAIMER
4+
*
5+
* Do not edit or add to this file if you wish to upgrade Gally to newer versions in the future.
6+
*
7+
* @package Gally
8+
* @author Stephan Hochdörfer <S.Hochdoerfer@bitexpert.de>, Gally Team <elasticsuite@smile.fr>
9+
* @copyright 2022-present Smile
10+
* @license Open Software License v. 3.0 (OSL-3.0)
11+
*/
212

313
declare(strict_types=1);
414

515
namespace Gally\SyliusPlugin\Form\Type;
616

717
use Symfony\Component\Form\AbstractType;
8-
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
918
use Symfony\Component\Form\Extension\Core\Type\TextType;
1019
use Symfony\Component\Form\FormBuilderInterface;
1120

1221
class SearchFormType extends AbstractType
1322
{
1423
public function buildForm(FormBuilderInterface $builder, array $options): void
1524
{
16-
$builder
17-
->add('query', TextType::class, [
25+
$builder->add(
26+
'query',
27+
TextType::class,
28+
[
1829
'label' => false,
1930
'attr' => [
2031
'class' => 'form-control',
2132
'placeholder' => 'gally_sylius.form.header.query.placeholder',
2233
'autocomplete' => 'off',
2334
'aria-label' => 'form.header.query.label',
24-
'aria-describedby' => 'collapsedSearchResults' // This should be the ID of the button below
25-
]
26-
])
27-
;
35+
'aria-describedby' => 'collapsedSearchResults', // This should be the ID of the button below
36+
],
37+
]
38+
);
2839
}
2940
}

src/Grid/Gally/Search/DataSource.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ public function getExpressionBuilder(): ExpressionBuilderInterface
5050

5151
public function getData(Parameters $parameters)
5252
{
53-
/** @var string|int $page */
54-
$page = (int) $parameters->get('page', 1);
55-
53+
/** @var int|string $page */
54+
$page = $parameters->get('page', 1);
55+
$page = (int) $page;
5656
$paginator = new PagerfantaGally(
5757
new SearchAdapter(
5858
$this->queryBuilder,

src/Grid/Gally/Search/SearchAdapter.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,24 @@ public function getNbResults(): int
5555

5656
public function getSlice(int $offset, int $length): iterable
5757
{
58-
$search = $this->parameters->get('query', $this->parameters->get('criteria', [])['search']['value'] ?? '');
58+
/** @var array $criteria */
59+
$criteria = $this->parameters->get('criteria', []);
60+
/** @var string $search */
61+
$search = $this->parameters->get('query', $criteria['search']['value'] ?? '');
5962
/** @var array<string> $sorting */
6063
$sorting = $this->parameters->get('sorting', []);
64+
/** @var string|int $page */
65+
$page = $this->parameters->get('page', 1);
6166
$sortField = array_key_first($sorting);
6267
$sortDirection = $sorting[$sortField] ?? null;
63-
$page = (int) $this->parameters->get('page', 1);
68+
$page = (int) $page;
6469

6570
$request = new Request(
6671
$this->catalogProvider->getLocalizedCatalog(),
6772
new Metadata('product'),
6873
false, // @todo: parameterize
6974
['sku', 'source'],
70-
(int) $page,
75+
$page,
7176
$length,
7277
null,
7378
$search,

src/Grid/Gally/Search/SearchDriver.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,24 @@ public function __construct(
3939

4040
public function getDataSource(array $configuration, Parameters $parameters): DataSourceInterface
4141
{
42-
if (!array_key_exists('class', $configuration)) {
42+
if (!\array_key_exists('class', $configuration)) {
4343
throw new \InvalidArgumentException('"class" must be configured.');
4444
}
4545

4646
/** @var ObjectManager $manager */
4747
$manager = $this->managerRegistry->getManagerForClass($configuration['class']);
4848

4949
/** @var ProductRepositoryInterface $repository */
50-
$repository = $manager->getRepository($configuration['class']);
50+
$repository = $manager->getRepository($configuration['class']); // @phpstan-ignore-line
5151

5252
$method = $configuration['repository']['method'];
5353
$arguments = isset($configuration['repository']['arguments']) ? array_values($configuration['repository']['arguments']) : [];
5454

55-
return new SearchDataSource($repository->$method(...$arguments), $this->searchManager, $this->catalogProvider, $this->eventDispatcher);
55+
return new SearchDataSource(
56+
$repository->{$method}(...$arguments),
57+
$this->searchManager,
58+
$this->catalogProvider,
59+
$this->eventDispatcher
60+
);
5661
}
5762
}

src/Indexer/CategoryIndexer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ private function formatTaxon(TaxonInterface $taxon, TaxonTranslationInterface $t
128128
'level' => $taxon->getLevel() + 1 - $menuTaxon->getLevel(),
129129
'path' => $this->pathCache[$taxon->getCode()],
130130
'name' => $translation->getName(),
131+
'slug' => $translation->getSlug(),
131132
];
132133
}
133134
}

src/Indexer/ProductIndexer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ private function formatProduct(ProductInterface $product, ChannelInterface $chan
100100
'sku' => [$product->getCode()],
101101
'name' => [$product->getTranslation($locale->getCode())->getName()],
102102
'description' => [$product->getTranslation($locale->getCode())->getDescription()],
103+
'slug' => [$product->getTranslation($locale->getCode())->getSlug()],
103104
'image' => ['' !== $this->formatMedia($product) ? $this->formatMedia($product) : null],
104105
'price' => $this->formatPrice($variant, $channel),
105106
'stock' => [

src/Indexer/Provider/SourceFieldProvider.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class SourceFieldProvider implements ProviderInterface
3333
/** @var LocalizedCatalog[] */
3434
private array $localizedCatalogs = [];
3535

36-
/** @var \Gally\Sdk\Entity\Metadata[] */
36+
/** @var Metadata[] */
3737
private array $metadataCache = [];
3838

3939
public function __construct(
@@ -59,6 +59,22 @@ public function provide(): iterable
5959
foreach ($this->productOptionRepository->findAll() as $productOption) {
6060
yield $this->buildSourceField('product', $productOption, 'select');
6161
}
62+
63+
$staticSourceField = [
64+
'product' => ['slug' => 'text'],
65+
'category' => ['slug' => 'text'],
66+
];
67+
68+
// Static source field
69+
foreach ($staticSourceField as $entity => $fields) {
70+
foreach ($fields as $code => $type) {
71+
if (!\array_key_exists($entity, $this->metadataCache)) {
72+
$this->metadataCache[$entity] = new Metadata($entity);
73+
}
74+
75+
yield new SourceField($this->metadataCache[$entity], $code, $this->getGallyType($type), $code, []);
76+
}
77+
}
6278
}
6379

6480
public function buildSourceField(
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
/**
3+
* DISCLAIMER
4+
*
5+
* Do not edit or add to this file if you wish to upgrade Gally to newer versions in the future.
6+
*
7+
* @package Gally
8+
* @author Stephan Hochdörfer <S.Hochdoerfer@bitexpert.de>, Gally Team <elasticsuite@smile.fr>
9+
* @copyright 2022-present Smile
10+
* @license Open Software License v. 3.0 (OSL-3.0)
11+
*/
12+
13+
declare(strict_types=1);
14+
15+
namespace Gally\SyliusPlugin\Migrations;
16+
17+
use Doctrine\DBAL\Schema\Schema;
18+
use Doctrine\Migrations\AbstractMigration;
19+
20+
final class Version20250725112500 extends AbstractMigration
21+
{
22+
public function getDescription(): string
23+
{
24+
return "Add autocomplete columns in 'sylius_channel' table.";
25+
}
26+
27+
public function up(Schema $schema): void
28+
{
29+
$this->addSql('ALTER TABLE sylius_channel ADD gally_autocomplete_product_max_size INT NOT NULL DEFAULT 6, ADD gally_autocomplete_category_max_size INT NOT NULL DEFAULT 6, ADD gally_autocomplete_attribute_max_size INT NOT NULL DEFAULT 6');
30+
}
31+
32+
public function down(Schema $schema): void
33+
{
34+
$this->addSql('ALTER TABLE sylius_channel DROP gally_autocomplete_product_max_size, DROP gally_autocomplete_category_max_size, DROP gally_autocomplete_attribute_max_size');
35+
}
36+
}

0 commit comments

Comments
 (0)