commercetools-8.x-1.2-alpha1/src/Plugin/Block/CommercetoolsProductFiltersBlockBase.php
src/Plugin/Block/CommercetoolsProductFiltersBlockBase.php
<?php
declare(strict_types=1);
namespace Drupal\commercetools\Plugin\Block;
use Drupal\commercetools\CommercetoolsService;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Html;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\InsertCommand;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a base block implementation for product filters blocks.
*/
abstract class CommercetoolsProductFiltersBlockBase extends CommercetoolsCatalogActionBlockBase {
/**
* The commercetools service.
*
* @var \Drupal\commercetools\CommercetoolsService
*/
protected CommercetoolsService $ct;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->ct = $container->get('commercetools');
return $instance;
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state): array {
$form = parent::blockForm($form, $form_state);
$productTypes = $this->ct->getProductsTypes();
$customFiltersID = Html::getUniqueId('custom-filters-table');
$form['customize_filters_enabled'] = [
'#type' => 'checkbox',
'#title' => $this->t('Customize Filters'),
'#default_value' => $this->configuration['customize_filters_enabled'] ?? NULL,
];
$form['customize_filters'] = [
'#type' => 'details',
'#title' => $this->t('Customize Filters'),
'#open' => TRUE,
'#attributes' => ['id' => [$customFiltersID]],
'#states' => [
'visible' => [
':input[name="settings[customize_filters_enabled]"]' => ['checked' => TRUE],
],
],
'filters' => [
'#type' => 'table',
'#header' => [
$this->t('Filter type'),
$this->t('Filter name'),
$this->t('Filter display label'),
$this->t('Type'),
$this->t('Path'),
$this->t('Enabled'),
$this->t('Actions'),
],
],
];
foreach ($productTypes as $productTypeKey => $productType) {
foreach ($productType['attributeDefinitions'] as $attributeKey => $attribute) {
$productTypeNamesPerAttribute[$attributeKey][] = $productType['name'];
$rowConfigs = $this->configuration['customize_filters'][$attributeKey] ?? [];
$form['customize_filters']['filters'][$attributeKey] = $this->buildFilterRow('attribute', $rowConfigs, [
'details' => [
'#markup' => new FormattableMarkup('<div>@attrName</div><div class="form-item__description">@productTypeNames</div>', [
'@attrName' => $attribute['label'],
'@productTypeNames' => implode(', ', $productTypeNamesPerAttribute[$attributeKey]),
]),
],
'label' => ['#default_value' => $rowConfigs['label'] ?? $attribute['label']],
'widget_type' => [
'#options' => [
'facet' => $this->t('Facet'),
'facet_count' => $this->t('Facet with count'),
],
'#default_value' => $rowConfigs['widget_type'] ?? NULL,
],
'path' => [
'#disabled' => TRUE,
'#value' => $this->ct->getAttributePath($productTypeKey, $attributeKey),
],
]);
}
}
// Global filters.
$filterConfig = $this->configuration['customize_filters']['show_in_stock_filter'] ?? [];
$form['customize_filters']['filters']['show_in_stock_filter'] = $this->buildFilterRow('global', $filterConfig, [
'details' => ['#markup' => $this->t('Show In Stock Filter')],
'label' => ['#default_value' => $filterConfig['label'] ?? $this->t('In Stock')],
'widget_type' => [
'#options' => [
'checkbox' => $this->t('Checkbox'),
],
],
'path' => [
'#disabled' => TRUE,
'#value' => 'variants.availability.isOnStock',
],
]);
$filterConfig = $this->configuration['customize_filters']['price_range_filter'] ?? [];
$form['customize_filters']['filters']['price_range_filter'] = $this->buildFilterRow('global', $filterConfig, [
'details' => ['#markup' => $this->t('Price Range Filter')],
'label' => ['#default_value' => $filterConfig['label'] ?? $this->t('Price Range')],
'widget_type' => [
'#options' => [
'custom' => $this->t('Custom'),
],
],
'path' => [
'#disabled' => TRUE,
'#value' => 'variants.price.centAmount',
],
]);
// Custom filters.
$formStorage = $form_state->getStorage();
$customFilters = array_filter($this->configuration['customize_filters'] ?? [], function ($filter) {
return $filter['type'] === 'custom';
});
$formStorage['custom_filters'] = $formStorage['custom_filters'] ?? $customFilters;
$form_state->setStorage($formStorage);
foreach ($formStorage['custom_filters'] as $key => $filterConfig) {
$form['customize_filters']['filters'][$key] = $this->buildFilterRow('custom', $filterConfig, [
'details' => ['#markup' => $key],
'enabled' => [
'#default_value' => $filterConfig['enabled'] ?? TRUE,
],
'actions' => [
'#type' => 'actions',
'remove' => [
'#type' => 'submit',
'#value' => $this->t('Remove'),
'#limit_validation_errors' => [],
'#submit' => [[$this, 'filterActionSubmit']],
'#ajax' => [
'callback' => [$this, 'filterActionAjax'],
'wrapper' => $customFiltersID,
'effect' => 'fade',
],
'#name' => "remove:{$key}",
],
],
]);
}
$form['customize_filters']['add_filter'] = [
'#type' => 'submit',
'#value' => $this->t('Add custom filter'),
'#limit_validation_errors' => [],
'#submit' => [[$this, 'filterActionSubmit']],
'#name' => 'add',
'#ajax' => [
'callback' => [$this, 'filterActionAjax'],
'wrapper' => $customFiltersID,
'effect' => 'fade',
],
];
return $form;
}
/**
* Main method for building a filter form.
*/
protected function buildFilterRow($filterType, $configs, $extra_settings = []): array {
$extra_settings = $extra_settings + [
'type' => [],
'details' => [],
'label' => [],
'widget_type' => [],
'path' => [],
'enabled' => [],
'actions' => [],
];
return [
'type' => $extra_settings['type'] + [
'#type' => 'select',
'#options' => [
'global' => $this->t('Global'),
'custom' => $this->t('Custom filter'),
'attribute' => $this->t('Product attribute'),
],
'#default_value' => $filterType,
'#disabled' => TRUE,
],
'details' => $extra_settings['details'],
'label' => $extra_settings['label'] + [
'#type' => 'textfield',
'#default_value' => $configs['label'] ?? NULL,
'#required' => TRUE,
],
'widget_type' => $extra_settings['widget_type'] + [
'#type' => 'select',
'#options' => [
'facet' => $this->t('Facet'),
'facet_count' => $this->t('Facet with count'),
'checkbox' => $this->t('Checkbox'),
'textfield' => $this->t('Textfield'),
'separator' => $this->t('Separator'),
],
'#default_value' => $configs['widget_type'] ?? NULL,
],
'path' => $extra_settings['path'] + [
'#type' => 'textfield',
'#default_value' => $configs['path'] ?? NULL,
'#required' => TRUE,
],
'enabled' => $extra_settings['enabled'] + [
'#type' => 'checkbox',
'#default_value' => $configs['enabled'] ?? FALSE,
],
'actions' => $extra_settings['actions'],
];
}
/**
* Ajax callback function for managing custom filters.
*/
public function filterActionAjax(array &$form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new InsertCommand(NULL, $form['settings']['customize_filters']));
return $response;
}
/**
* Submit method for managing custom filters.
*/
public function filterActionSubmit(array &$form, FormStateInterface $form_state) {
$button = $form_state->getTriggeringElement();
$formStorage = $form_state->getStorage();
if ($button['#name'] === 'add') {
if (empty($formStorage['custom_filters'])) {
$index = 0;
}
else {
[, $index] = explode('_', array_key_last($formStorage['custom_filters']));
$index++;
}
$formStorage['custom_filters']["custom_{$index}"] = [];
}
else {
[, $key] = explode(':', $button['#name']);
unset($formStorage['custom_filters'][$key]);
}
$form_state->setStorage($formStorage);
$form_state->setRebuild();
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
$this->configuration['customize_filters_enabled'] = $form_state->getValue('customize_filters_enabled');
$this->configuration['customize_filters'] = $this->configuration['customize_filters_enabled']
? $form_state->getValue('customize_filters')['filters']
: NULL;
parent::blockSubmit($form, $form_state);
}
/**
* Get enabled filters.
*/
protected function getEnabledFilters(): array {
$filters = [];
if (!empty($this->configuration['customize_filters_enabled'])) {
foreach ($this->configuration['customize_filters'] as $filter) {
if (!$filter['enabled']) {
continue;
}
$filter['path'] = $this->ct->localizePath($filter['path']);
if (in_array($filter['widget_type'], ['facet', 'facet_count'])) {
$filter['graphql'] = $this->ct->buildFacetGraphql($filter['path'], $filter['widget_type'] === 'facet_count');
}
if ($filter['widget_type'] === 'custom') {
$filter['graphql'] = $this->ct->buildRangeFacetGraphql($filter['path']);
}
if ($filter['type'] === 'attribute') {
[,, $attributeKey] = explode('.', $filter['path']);
$filter['attributeKey'] = $attributeKey;
}
$filters[] = $filter;
}
}
else {
$supportedTypes = ['enum', 'ltext', 'lenum'];
$filterPaths = [];
foreach ($this->ct->getProductsTypes() as $productTypeKey => $productType) {
foreach ($productType['attributeDefinitions'] as $attributeKey => $attribute) {
if (empty($attribute['isSearchable']) || !in_array($attribute['type'], $supportedTypes)) {
continue;
}
$path = $this->ct->localizePath($this->ct->getAttributePath($productTypeKey, $attributeKey));
if (in_array($path, $filterPaths)) {
continue;
}
$filters[] = [
'type' => 'attribute',
'label' => $attribute['label'],
'widget_type' => 'facet_count',
'path' => $path,
'graphql' => $this->ct->buildFacetGraphql($path, TRUE),
'attributeKey' => $attributeKey,
];
$filterPaths[] = $path;
}
}
$filters[] = [
'type' => 'attribute',
'label' => $this->t('In Stock'),
'widget_type' => 'checkbox',
'path' => 'variants.availability.isOnStock',
];
$filters[] = [
'type' => 'attribute',
'label' => $this->t('Price Range'),
'widget_type' => 'custom',
'path' => CommercetoolsService::CT_VARIANTS_PRICE_AMOUNT,
'graphql' => $this->ct->buildRangeFacetGraphql(CommercetoolsService::CT_VARIANTS_PRICE_AMOUNT),
];
}
return $filters;
}
}
