toolshed-8.x-1.x-dev/modules/toolshed_search/src/Plugin/views/filter/EntityBundleFilter.php
modules/toolshed_search/src/Plugin/views/filter/EntityBundleFilter.php
<?php
namespace Drupal\toolshed_search\Plugin\views\filter;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SortArray;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\search_api\Plugin\views\filter\SearchApiFilterTrait;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\filter\InOperator;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* A Search API datasource filter with more exposed filter options.
*
* @ViewsFilter("toolshed_entity_bundle_filter")
*/
class EntityBundleFilter extends InOperator {
use SearchApiFilterTrait;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* The entity type bundle info service.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected EntityTypeBundleInfoInterface $entityBundleInfo;
/**
* Constructs a custom variant of a Toolshed entity bundle filter.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_bundle_info
* The core bundle info service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_bundle_info) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeManager = $entity_type_manager;
$this->entityBundleInfo = $entity_bundle_info;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('entity_type.bundle.info')
);
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$options = NULL): void {
parent::init($view, $display, $options);
$this->operator = strtolower($this->options["operator"]);
}
/**
* {@inheritdoc}
*/
public function getValueOptions(): array {
if (!isset($this->valueOptions)) {
$this->valueOptions = $this->exposedInfo()['bundle_labels'] ?? [];
// Get all the available bundles from the datasources.
foreach ($this->getIndex()->getDatasources() as $datasource) {
if (!($entityTypeId = $datasource->getEntityTypeId())) {
continue;
}
foreach ($datasource->getBundles() as $bundleId => $bundleLabel) {
$key = "$entityTypeId:$bundleId";
if (empty($this->valueOptions[$key])) {
$this->valueOptions[$key] = $bundleLabel;
}
}
}
}
return $this->valueOptions;
}
/**
* {@inheritdoc}
*/
protected function defineOptions(): array {
$options = parent::defineOptions();
$options['operator'] = ['default' => 'in'];
$options['value'] = ['default' => []];
// Setup additional exposed filter options. The new options help control
// who the entity bundle selection will appear when exposed.
$exposeOpts = &$options['expose']['contains'];
$exposeOpts['select_type'] = ['default' => 'checkbox'];
$exposeOpts['bundle_labels'] = ['default' => []];
return $options;
}
/**
* {@inheritdoc}
*/
public function defaultExposeOptions(): void {
parent::defaultExposeOptions();
$this->options['expose']['select_type'] = 'checkbox';
$this->options['expose']['bundle_labels'] = [];
}
/**
* Display the filter on the administrative summary.
*/
public function adminSummary(): string|object {
return $this->operator . ' [' . implode(', ', $this->value) . ']';
}
/**
* Get the operatiors supported for filtering.
*
* @return array
* An array of operators that this filter uses, with the query operation
* as the key, and information about the operation as the value.
*/
public function operators(): array {
return [
'in' => [
'title' => $this->t('Is one of'),
'short' => $this->t('in'),
'short_single' => '=',
'values' => 1,
],
'not in' => [
'title' => $this->t('Is not one of'),
'short' => $this->t('not in'),
'short_single' => '<>',
'values' => 1,
],
];
}
/**
* {@inheritdoc}
*/
protected function valueForm(&$form, FormStateInterface $form_state): void {
parent::valueForm($form, $form_state);
$bundles = [];
foreach ($this->getIndex()->getDatasources() as $datasource) {
$entityTypeId = $datasource->getEntityTypeId();
$entityType = $entityTypeId ? $this->entityTypeManager->getDefinition($entityTypeId, FALSE) : NULL;
if ($entityType) {
$entityLabel = Html::escape($entityType->getLabel());
$bundles[$entityLabel] = [];
foreach ($datasource->getBundles() as $key => $bundleLabel) {
$bundles[$entityLabel]["$entityTypeId:$key"] = $bundleLabel;
}
}
}
// List all bundles available to the search index, grouped by entity types.
$form['value'] = [
'#type' => 'select',
'#title' => $this->t('Include entity bundles'),
'#multiple' => TRUE,
'#options' => $bundles,
'#default_value' => $this->value ?? [],
];
}
/**
* {@inheritdoc}
*/
public function buildExposeForm(&$form, FormStateInterface $form_state): void {
parent::buildExposeForm($form, $form_state);
$form['expose']['select_type'] = [
'#type' => 'select',
'#title' => $this->t('Selection type'),
'#options' => [
'checkbox' => $this->t('Check boxes/radio buttons'),
'select' => $this->t('Select list'),
],
'#default_value' => $this->options['expose']['select_type'],
];
// Create a table to allow the assignment of labels for use on exposed
// forms. Show all labels, but only the entity types selected for in the
// values options will be available.
$form['expose']['bundle_labels'] = [
'#type' => 'table',
'#tree' => TRUE,
'#header' => [
'bundle' => $this->t('Entity bundle'),
'label' => $this->t('Label'),
'weight' => $this->t('Sort weight'),
],
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'bundle-weight',
],
],
];
$availBundles = [];
$entityLabels = [];
foreach ($this->getIndex()->getDatasources() as $datasource) {
if ($entityTypeId = $datasource->getEntityTypeId()) {
if ($entityType = $this->entityTypeManager->getDefinition($entityTypeId, FALSE)) {
foreach ($datasource->getBundles() as $bundle => $label) {
$key = "{$entityTypeId}:{$bundle}";
$entityLabels[$key] = $label . ' (' . $entityType->getLabel() . ')';
$availBundles[$key] = $label;
}
}
}
}
$defaultLabels = $this->options['expose']['bundle_labels'];
$table = &$form['expose']['bundle_labels'];
foreach ($defaultLabels + $availBundles as $bundleKey => $label) {
if (!isset($availBundles[$bundleKey])) {
continue;
}
$table[$bundleKey] = [
'#attributes' => ['class' => ['draggable']],
'bundle' => [
'#plain_text' => $entityLabels[$bundleKey],
],
'label' => [
'#type' => 'textfield',
'#placeholder' => $availBundles[$bundleKey],
'#default_value' => @$defaultLabels[$bundleKey] ?: '',
],
'weight' => [
'#type' => 'weight',
'#default_value' => 0,
'#attributes' => ['class' => ['bundle-weight']],
],
];
}
}
/**
* {@inheritdoc}
*/
public function submitExposeForm($form, FormStateInterface $form_state): void {
$parents = $form['expose']['bundle_labels']['#parents'];
$values = $form_state->getValue($parents);
uasort($values, SortArray::sortByWeightElement(...));
$bundleLabels = [];
foreach ($values as $key => $info) {
$title = trim($info['label']);
$bundleLabels[$key] = $title ?: FALSE;
}
// Set the cleaned up bundle labels.
$form_state->setValue($parents, $bundleLabels);
parent::submitExposeForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function acceptExposedInput($input): bool {
if (empty($this->options['exposed'])) {
return TRUE;
}
$filterKey = $this->options['expose']['identifier'];
$value = $input[$filterKey];
if (is_array($value)) {
$value = array_filter($value);
}
else {
$value = empty($value) ? [] : [$value => $value];
}
// If not empty, then change the query value to the options set here.
// If this is empty fallback to what was set for the filter options.
if ($value) {
$this->value = $value;
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function buildExposedForm(&$form, FormStateInterface $form_state): void {
if (empty($this->options['exposed'])) {
return;
}
$exposeOpts = $this->options['expose'];
$filterKey = $exposeOpts['identifier'];
$element = [
'#options' => [],
'#required' => $exposeOpts['required'] ?? FALSE,
];
// Determine which form element to use based on the 'select_type' option,
// and if multiple values are allowed.
if ($exposeOpts['select_type'] === 'checkbox') {
if (empty($exposeOpts['multiple'])) {
$element['#type'] = 'radios';
if (!$element['#required']) {
$element['#options'][0] = $this->t('Any');
$element['#default_value'] = 0;
}
}
else {
$element['#type'] = 'checkboxes';
$element['#default_value'] = [];
}
}
else {
$element['#type'] = 'select';
$element['#empty_option'] = $this->t('- Any -');
$element['#multiple'] = $exposeOpts['multiple'] ?? FALSE;
}
// If limited selection / values then iterate only the bundle options
// available. Otherwise, list all values allowed for this Search API index.
$element['#options'] = $this->options['value']
? array_intersect_key($this->getValueOptions(), $this->options['value'])
: $this->getValueOptions();
$form[$filterKey] = $element;
}
/**
* {@inheritdoc}
*/
public function query(): void {
if ($this->value) {
$this->ensureMyTable();
$op = $this->operator;
if (count($this->value) === 1) {
$op = $this->operators()[$this->operator]['short_single'] ?? $this->operator;
}
$group = $this->options['group'];
$field = "$this->tableAlias.$this->realField";
/** @var \Drupal\search_api\Plugin\views\query\SearchApiQuery $query */
$query = $this->query;
$query->addWhere($group, $field, $this->value, $op);
}
}
}
