Added islandora_advanced_search module.
This commit is contained in:
commit
698cbcad8b
52 changed files with 4329 additions and 0 deletions
394
src/Plugin/Block/AdvancedSearchBlock.php
Normal file
394
src/Plugin/Block/AdvancedSearchBlock.php
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\islandora_advanced_search\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Form\FormBuilderInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\search_api\Display\DisplayPluginManager;
|
||||
use Drupal\views\Entity\View;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Provides an Islandora Advanced Search block.
|
||||
*
|
||||
* @Block(
|
||||
* id = "islandora_advanced_search_block",
|
||||
* deriver = "Drupal\islandora_advanced_search\Plugin\Block\AdvancedSearchBlockDeriver",
|
||||
* admin_label = @Translation("Islandora Advanced Search"),
|
||||
* category = @Translation("Islandora"),
|
||||
* )
|
||||
*/
|
||||
class AdvancedSearchBlock extends BlockBase implements ContainerFactoryPluginInterface {
|
||||
use ViewAndDisplayIdentifiersTrait;
|
||||
|
||||
// CSS classes used to bind table-drag behavior to.
|
||||
const WEIGHT_FIELD_CLASS = 'field-weight';
|
||||
const DISPLAY_FIELD_CLASS = 'field-display';
|
||||
|
||||
// Regions in the table which denote if a given field
|
||||
// is visible in the Advanced Search Form or not.
|
||||
const REGION_VISIBLE = 'visible';
|
||||
const REGION_HIDDEN = 'hidden';
|
||||
|
||||
// Keys for settings.
|
||||
const SETTING_FIELDS = 'fields';
|
||||
const SETTING_CONTEXTUAL_FILTER = 'context_filter';
|
||||
|
||||
/**
|
||||
* The display plugin manager.
|
||||
*
|
||||
* @var \Drupal\search_api\Display\DisplayPluginManager
|
||||
*/
|
||||
protected $displayPluginManager;
|
||||
|
||||
/**
|
||||
* The clone of the current request object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* The view this block affects.
|
||||
*
|
||||
* @var \Drupal\views\Entity\View
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* The view display this block affects.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $display;
|
||||
|
||||
/**
|
||||
* Form Builder.
|
||||
*
|
||||
* @var \Drupal\Core\Form\FormBuilderInterface
|
||||
*/
|
||||
protected $formBuilder;
|
||||
|
||||
/**
|
||||
* Construct a AdvancedSearchBlock instance.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param string $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\search_api\Display\DisplayPluginManager $display_plugin_manager
|
||||
* The display plugin manager.
|
||||
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
|
||||
* The form builder service used to build the search form.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* A request object for the current request.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, DisplayPluginManager $display_plugin_manager, FormBuilderInterface $form_builder, Request $request) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->displayPluginManager = $display_plugin_manager;
|
||||
list($view_id, $display_id) = preg_split('/__/', $this->getDerivativeId(), 2);
|
||||
$this->view = View::Load($view_id);
|
||||
$this->display = $this->view->getDisplay($display_id);
|
||||
$this->formBuilder = $form_builder;
|
||||
$this->request = clone $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('plugin.manager.search_api.display'),
|
||||
$container->get('form_builder'),
|
||||
$container->get('request_stack')->getMasterRequest()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
self::SETTING_FIELDS => [],
|
||||
self::SETTING_CONTEXTUAL_FILTER => NULL,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fields which can be enabled / disabled for display in the search form.
|
||||
*
|
||||
* @return \Drupal\search_api\Item\FieldInterface[]
|
||||
* The $fields sorted by label.
|
||||
*/
|
||||
protected function getFields() {
|
||||
$fields = $this->getIndex()->getFields();
|
||||
// First pass sort on label, secondary sort will be used
|
||||
// when looking at existing configuration for this block.
|
||||
uasort($fields, function ($a, $b) {
|
||||
return strcmp($a->getLabel(), $b->getLabel());
|
||||
});
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get regions of table to display.
|
||||
*
|
||||
* @return array
|
||||
* The properties of each region used for building the table of fields.
|
||||
*/
|
||||
protected function getRegions() {
|
||||
// Classes for select fields like 'weight' and 'display' are hard-coded
|
||||
// and used in js/islandora-advanced-search.admin.js.
|
||||
return [
|
||||
'visible' => [
|
||||
'title' => $this->t('Visible'),
|
||||
'invisible' => TRUE,
|
||||
'message' => $this->t('No search field is visible.'),
|
||||
'weight' => self::WEIGHT_FIELD_CLASS . '-visible',
|
||||
'display' => self::DISPLAY_FIELD_CLASS . '-visible',
|
||||
],
|
||||
'hidden' => [
|
||||
'title' => $this->t('Hidden'),
|
||||
'invisible' => FALSE,
|
||||
'message' => $this->t('No search field is hidden.'),
|
||||
'weight' => self::WEIGHT_FIELD_CLASS . '-hidden',
|
||||
'display' => self::DISPLAY_FIELD_CLASS . '-hidden',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for field display derived from the available regions.
|
||||
*
|
||||
* @return array
|
||||
* Display select field options.
|
||||
*/
|
||||
protected function getDisplayOptions() {
|
||||
$options = [];
|
||||
foreach ($this->getRegions() as $region => $settings) {
|
||||
$options[$region] = $settings['title'];
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockForm($form, FormStateInterface $form_state) {
|
||||
// At most we will have one row per field.
|
||||
$fields = $this->getFields();
|
||||
$weight_delta = round(count($fields) / 2);
|
||||
|
||||
// Group each field into a region given our current configuration.
|
||||
$visible_fields = $this->configuration[self::SETTING_FIELDS];
|
||||
$regions = $this->getRegions();
|
||||
$display_options = $this->getDisplayOptions();
|
||||
|
||||
// Field rows are grouped by the region in which they are displayed.
|
||||
$field_rows = array_fill_keys(array_keys($regions), []);
|
||||
foreach ($fields as $field) {
|
||||
// If a field exists in the blocks configuration than it is 'visible' and
|
||||
// its weight is equivalent to its order in the configuration,
|
||||
// i.e. its index.
|
||||
$identifier = $field->getFieldIdentifier();
|
||||
$weight = array_search($identifier, $visible_fields);
|
||||
$visible = $weight !== FALSE;
|
||||
$region = $visible ? self::REGION_VISIBLE : self::REGION_HIDDEN;
|
||||
$field_rows[$region][$identifier] = [
|
||||
'#attributes' => [
|
||||
'class' => ['draggable'],
|
||||
],
|
||||
'label' => ['#plain_text' => $field->getLabel()],
|
||||
'identifier' => ['#plain_text' => $identifier],
|
||||
'weight' => [
|
||||
'#type' => 'weight',
|
||||
'#title' => $this->t('Weight'),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $visible ? $weight : 0,
|
||||
'#delta' => $weight_delta,
|
||||
'#attributes' => [
|
||||
'class' => [self::WEIGHT_FIELD_CLASS, $regions[$region]['weight']],
|
||||
],
|
||||
],
|
||||
'display' => [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Display'),
|
||||
'#title_display' => 'invisible',
|
||||
'#options' => $display_options,
|
||||
'#default_value' => $region,
|
||||
'#attributes' => [
|
||||
'class' => [self::DISPLAY_FIELD_CLASS, $regions[$region]['display']],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
// Sort the visible rows by their weight.
|
||||
uasort($field_rows[self::REGION_VISIBLE], function ($a, $b) {
|
||||
$a = $a['weight']['#default_value'];
|
||||
$b = $b['weight']['#default_value'];
|
||||
if ($a == $b) {
|
||||
return 0;
|
||||
}
|
||||
return ($a < $b) ? -1 : 1;
|
||||
});
|
||||
|
||||
// Build Rows.
|
||||
$rows = [];
|
||||
$table_drag = [];
|
||||
foreach ($regions as $region => $properties) {
|
||||
$rows += [
|
||||
// Conditionally display region title as a row.
|
||||
"region-$region" => $properties['invisible'] ? NULL : [
|
||||
'#attributes' => [
|
||||
'class' => ['region-title', "region-title-$region"],
|
||||
],
|
||||
'label' => [
|
||||
'#plain_text' => $properties['title'],
|
||||
'#wrapper_attributes' => [
|
||||
'colspan' => 4,
|
||||
],
|
||||
],
|
||||
],
|
||||
// Will dynamically display if the region has fields or not controlled
|
||||
// by Drupal behaviors in js/islandora-advanced-search.admin.js.
|
||||
"region-$region-message" => [
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'region-message',
|
||||
"region-$region-message",
|
||||
empty($field_rows[$region]) ? 'region-empty' : 'region-populated',
|
||||
],
|
||||
],
|
||||
'message' => [
|
||||
'#markup' => '<em>' . $properties['message'] . '</em>',
|
||||
'#wrapper_attributes' => [
|
||||
'colspan' => 4,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Include field rows in this region.
|
||||
$rows += $field_rows[$region];
|
||||
|
||||
// Configure order by weight field in region.
|
||||
$table_drag[] = [
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => self::WEIGHT_FIELD_CLASS,
|
||||
'subgroup' => $properties['weight'],
|
||||
'source' => self::WEIGHT_FIELD_CLASS,
|
||||
];
|
||||
|
||||
// Configure drag action for display field in region.
|
||||
$table_drag[] = [
|
||||
'action' => 'match',
|
||||
'relationship' => 'sibling',
|
||||
'group' => self::DISPLAY_FIELD_CLASS,
|
||||
'subgroup' => $properties['display'],
|
||||
'source' => self::DISPLAY_FIELD_CLASS,
|
||||
];
|
||||
}
|
||||
|
||||
$form[self::SETTING_FIELDS] = [
|
||||
'#type' => 'table',
|
||||
'#attributes' => [
|
||||
// Identifier is hard-coded and used in
|
||||
// js/islandora-advanced-search.admin.js.
|
||||
'id' => 'advanced-search-fields',
|
||||
],
|
||||
'#header' => [
|
||||
$this->t('Label'),
|
||||
$this->t('Field'),
|
||||
$this->t('Weight'),
|
||||
$this->t('Display'),
|
||||
],
|
||||
'#empty' => $this->t('No search fields, please check search index configuration.'),
|
||||
'#tabledrag' => $table_drag,
|
||||
] + $rows;
|
||||
|
||||
// If there is contextual filters associated with the display that means
|
||||
// we can filter on collection / sub-collection. Allow the user to choose
|
||||
// which filters collections.
|
||||
$id = NULL;
|
||||
$field = NULL;
|
||||
$options = [];
|
||||
if (isset($this->display['display_options']['arguments'])) {
|
||||
foreach ($this->display['display_options']['arguments'] as $context_filter) {
|
||||
$id = $context_filter['id'];
|
||||
$field = $context_filter['field'];
|
||||
if (isset($fields[$field])) {
|
||||
$options[$id] = $fields[$field]->getLabel() . ':' . $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count($options) > 1) {
|
||||
$form[self::SETTING_CONTEXTUAL_FILTER] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Context Filter'),
|
||||
'#description' => $this->t('If more than one <strong>Context Filter</strong> is defined, specify which is used to <strong>include</strong> only <strong>direct children</strong> of the Collection as it will disabled to allow recursive searching.'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $this->configuration[self::SETTING_CONTEXTUAL_FILTER],
|
||||
'#multiple' => FALSE,
|
||||
'#required' => TRUE,
|
||||
'#size' => count($options) + 1,
|
||||
];
|
||||
}
|
||||
$form['#attributes']['class'][] = 'clearfix';
|
||||
$form['#attached']['library'][] = 'islandora_advanced_search/advanced.search.admin';
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockSubmit($form, FormStateInterface $form_state) {
|
||||
$values = $form_state->getValues();
|
||||
$fields = array_filter($values[self::SETTING_FIELDS], function ($field) {
|
||||
return $field['display'] == 'visible';
|
||||
});
|
||||
uasort($fields, '\Drupal\Component\Utility\SortArray::sortByWeightElement');
|
||||
$this->configuration[self::SETTING_FIELDS] = array_keys($fields);
|
||||
if (isset($values[self::SETTING_CONTEXTUAL_FILTER])) {
|
||||
$this->configuration[self::SETTING_CONTEXTUAL_FILTER] = $values[self::SETTING_CONTEXTUAL_FILTER];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$fields = $this->getIndex()->getFields();
|
||||
$configured_fields = [];
|
||||
foreach ($this->configuration[self::SETTING_FIELDS] as $identifier) {
|
||||
$configured_fields[$identifier] = $fields[$identifier];
|
||||
}
|
||||
return $this->formBuilder->getForm('Drupal\islandora_advanced_search\Form\AdvancedSearchForm', $configured_fields, $this->configuration[self::SETTING_CONTEXTUAL_FILTER]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
// The block cannot be cached, because it must always match the current
|
||||
// search results.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Search Index.
|
||||
*/
|
||||
protected function getIndex() {
|
||||
$id = $this->getDerivativeId();
|
||||
return $this->displayPluginManager->createInstance("views_{$this->display['display_plugin']}:{$id}")->getIndex();
|
||||
}
|
||||
|
||||
}
|
||||
91
src/Plugin/Block/AdvancedSearchBlockDeriver.php
Normal file
91
src/Plugin/Block/AdvancedSearchBlockDeriver.php
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\islandora_advanced_search\Plugin\Block;
|
||||
|
||||
use Drupal\Component\Plugin\PluginBase;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* This deriver creates a block for every search_api.display.
|
||||
*/
|
||||
class AdvancedSearchBlockDeriver implements ContainerDeriverInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* List of derivative definitions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $derivatives = [];
|
||||
|
||||
/**
|
||||
* The entity storage for the view.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The display manager for the search_api.
|
||||
*
|
||||
* @var \Drupal\search_api\Display\DisplayPluginManager
|
||||
*/
|
||||
protected $displayPluginManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
$deriver = new static($container, $base_plugin_id);
|
||||
$deriver->storage = $container->get('entity_type.manager')->getStorage('view');
|
||||
$deriver->displayPluginManager = $container->get('plugin.manager.search_api.display');
|
||||
return $deriver;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
|
||||
$derivatives = $this->getDerivativeDefinitions($base_plugin_definition);
|
||||
return isset($derivatives[$derivative_id]) ? $derivatives[$derivative_id] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
$base_plugin_id = $base_plugin_definition['id'];
|
||||
|
||||
if (!isset($this->derivatives[$base_plugin_id])) {
|
||||
$plugin_derivatives = [];
|
||||
|
||||
foreach ($this->displayPluginManager->getDefinitions() as $display_definition) {
|
||||
$view_id = $display_definition['view_id'];
|
||||
$view_display = $display_definition['view_display'];
|
||||
// The derived block needs both the view / display identifiers to
|
||||
// construct the pager.
|
||||
$machine_name = "${view_id}__${view_display}";
|
||||
|
||||
/** @var \Drupal\views\ViewEntityInterface $view */
|
||||
$view = $this->storage->load($view_id);
|
||||
$display = $view->getDisplay($view_display);
|
||||
|
||||
$plugin_derivatives[$machine_name] = [
|
||||
'id' => $base_plugin_id . PluginBase::DERIVATIVE_SEPARATOR . $machine_name,
|
||||
'label' => $this->t('Advanced Search'),
|
||||
'admin_label' => $this->t(':view: Advanced Search for :display', [
|
||||
':view' => $view->label(),
|
||||
':display' => $display['display_title'],
|
||||
]),
|
||||
] + $base_plugin_definition;
|
||||
}
|
||||
|
||||
$this->derivatives[$base_plugin_id] = $plugin_derivatives;
|
||||
}
|
||||
return $this->derivatives[$base_plugin_id];
|
||||
}
|
||||
|
||||
}
|
||||
314
src/Plugin/Block/SearchResultsPagerBlock.php
Normal file
314
src/Plugin/Block/SearchResultsPagerBlock.php
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\islandora_advanced_search\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Render\Markup;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\islandora_advanced_search\AdvancedSearchQuery;
|
||||
use Drupal\views\Entity\View;
|
||||
use Drupal\views\Plugin\views\pager\SqlBase;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Provides a 'AjaxViewBlock' block.
|
||||
*
|
||||
* @Block(
|
||||
* id = "islandora_advanced_search_result_pager",
|
||||
* deriver = "Drupal\islandora_advanced_search\Plugin\Block\SearchResultsPagerBlockDeriver",
|
||||
* admin_label = @Translation("Search Results Pager"),
|
||||
* category = @Translation("Islandora"),
|
||||
* )
|
||||
*/
|
||||
class SearchResultsPagerBlock extends BlockBase implements ContainerFactoryPluginInterface {
|
||||
use ViewAndDisplayIdentifiersTrait;
|
||||
|
||||
/**
|
||||
* The clone of the current request object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* Construct a FacetBlock instance.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param string $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* A request object for the current request.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, Request $request) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->request = clone $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('request_stack')->getMasterRequest()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$id = $this->getDerivativeId();
|
||||
list($view_id, $display_id) = $this->getViewAndDisplayIdentifiers();
|
||||
$view = View::Load($view_id);
|
||||
$view_executable = $view->getExecutable();
|
||||
$view_executable->setDisplay($display_id);
|
||||
// Allow advanced search to alter the query.
|
||||
$advanced_search_query = new AdvancedSearchQuery();
|
||||
$advanced_search_query->alterView($this->request, $view_executable, $display_id);
|
||||
$view_executable->execute();
|
||||
$pager = $view_executable->getPager();
|
||||
$exposed_input = $view_executable->getExposedInput();
|
||||
$query_parameters = $this->request->query->all();
|
||||
$build = [
|
||||
'#attached' => [
|
||||
'drupalSettings' => [
|
||||
'islandora_advanced_search_pager_views_ajax' => [
|
||||
$id => [
|
||||
'view_id' => $view_id,
|
||||
'current_display_id' => $display_id,
|
||||
'ajax_path' => '/views/ajax',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'#attributes' => [
|
||||
'class' => ['islandora_advanced_search_result_pager'],
|
||||
'data-drupal-pager-id' => $id,
|
||||
],
|
||||
'result_summary' => $this->buildResultsSummary($view_executable),
|
||||
'container' => [
|
||||
'#prefix' => '<div class="pager__group">',
|
||||
'#suffix' => '</div>',
|
||||
'results_per_page_links' => $this->buildResultsPerPageLinks($pager, $query_parameters),
|
||||
'display_links' => $this->buildDisplayLinks($query_parameters),
|
||||
'sort_by' => $this->buildSortByForm($view_executable->sort, $query_parameters),
|
||||
'pager' => array_merge($pager->render($exposed_input), ['#wrapper_attributes' => ['class' => ['container']]]),
|
||||
],
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the results summary portion of the pager.
|
||||
*
|
||||
* @param Drupal\views\ViewExecutable $view_executable
|
||||
* The view to build the summary for.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array that represents the current page, and number of
|
||||
* results in the view.
|
||||
*/
|
||||
protected function buildResultsSummary(ViewExecutable $view_executable) {
|
||||
$current_page = (int) $view_executable->getCurrentPage() + 1;
|
||||
$per_page = (int) $view_executable->getItemsPerPage();
|
||||
$total = isset($view_executable->total_rows) ? $view_executable->total_rows : count($view_executable->result);
|
||||
// If there is no result the "start" and "current_record_count" should be
|
||||
// equal to 0. To have the same calculation logic, we use a "start offset"
|
||||
// to handle all the cases.
|
||||
$start_offset = empty($total) ? 0 : 1;
|
||||
if ($per_page === 0) {
|
||||
$start = $start_offset;
|
||||
$end = $total;
|
||||
}
|
||||
else {
|
||||
$total_count = $current_page * $per_page;
|
||||
if ($total_count > $total) {
|
||||
$total_count = $total;
|
||||
}
|
||||
$start = ($current_page - 1) * $per_page + $start_offset;
|
||||
$end = $total_count;
|
||||
}
|
||||
if (!empty($total)) {
|
||||
// Return as render array.
|
||||
return [
|
||||
'#prefix' => '<div class="pager__summary">',
|
||||
'#suffix' => '</div>',
|
||||
'#markup' => $this->t('Displaying @start - @end of @total', [
|
||||
'@start' => $start,
|
||||
'@end' => $end,
|
||||
'@total' => $total,
|
||||
]),
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the results per page portion of the pager.
|
||||
*
|
||||
* @param Drupal\views\Plugin\views\pager\SqlBase $pager
|
||||
* The pager for the view.
|
||||
* @param array $query_parameters
|
||||
* The query parameters used to change the number of results per page.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array representing the results per page portion of pager.
|
||||
*/
|
||||
protected function buildResultsPerPageLinks(SqlBase $pager, array $query_parameters) {
|
||||
$active_items_per_page = $query_parameters['items_per_page'] ?? $pager->options['items_per_page'];
|
||||
$items_per_page_options = array_map(function ($value) {
|
||||
return trim($value);
|
||||
}, explode(',', $pager->options['expose']['items_per_page_options']));
|
||||
$items = [];
|
||||
foreach ($items_per_page_options as $items_per_page) {
|
||||
$url = Url::fromRoute('<current>', [], [
|
||||
// When changing the number of items displayed always return the user
|
||||
// to the first page.
|
||||
'query' => array_merge($query_parameters, [
|
||||
'items_per_page' => $items_per_page,
|
||||
'page' => 0,
|
||||
]),
|
||||
'absolute' => TRUE,
|
||||
]);
|
||||
$active = $items_per_page == $active_items_per_page;
|
||||
$items[] = [
|
||||
'#type' => 'link',
|
||||
'#url' => $url,
|
||||
'#title' => $items_per_page,
|
||||
'#attributes' => [
|
||||
'class' => $active ? ['pager__link', 'pager__link--is-active'] : ['pager__link'],
|
||||
],
|
||||
'#wrapper_attributes' => [
|
||||
'class' => $active ? ['pager__item', 'is-active'] : ['pager__item'],
|
||||
],
|
||||
];
|
||||
}
|
||||
return [
|
||||
'#theme' => 'item_list',
|
||||
'#title' => $this->t('Results per page'),
|
||||
'#list_type' => 'ul',
|
||||
'#items' => $items,
|
||||
'#attributes' => [],
|
||||
'#wrapper_attributes' => ['class' => ['pager__results', 'container']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the display links portion of the pager (list/grid).
|
||||
*
|
||||
* @param array $query_parameters
|
||||
* The query parameters used to change the display format.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array representing the display links portion of pager.
|
||||
*/
|
||||
protected function buildDisplayLinks(array $query_parameters) {
|
||||
$active_display = $query_parameters['display'] ?? 'list';
|
||||
$display_options = [
|
||||
'list' => [
|
||||
'icon' => 'fa-list',
|
||||
'title' => $this->t('List'),
|
||||
],
|
||||
'grid' => [
|
||||
'icon' => 'fa-th',
|
||||
'title' => $this->t('Grid'),
|
||||
],
|
||||
];
|
||||
$items = [];
|
||||
foreach ($display_options as $display => $options) {
|
||||
$url = Url::fromRoute('<current>', [], [
|
||||
'query' => array_merge($query_parameters, ['display' => $display]),
|
||||
'absolute' => TRUE,
|
||||
]);
|
||||
$text = "<i class='fa {$options['icon']}' aria-hidden='true'> </i>{$options['title']}";
|
||||
$active = $active_display == $display;
|
||||
$items[] = [
|
||||
'#type' => 'link',
|
||||
'#url' => $url,
|
||||
'#title' => Markup::create($text),
|
||||
'#attributes' => [
|
||||
'class' => $active ? ['pager__link', 'pager__link--is-active'] : ['pager__link'],
|
||||
],
|
||||
'#wrapper_attributes' => [
|
||||
'class' => $active ? ['pager__item', 'is-active'] : ['pager__item'],
|
||||
],
|
||||
];
|
||||
}
|
||||
return [
|
||||
'#theme' => 'item_list',
|
||||
'#list_type' => 'ul',
|
||||
'#items' => $items,
|
||||
'#attributes' => [],
|
||||
'#wrapper_attributes' => ['class' => ['pager__display', 'container']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the sort by portion of the pager.
|
||||
*
|
||||
* @param array $sort_criteria
|
||||
* The search fields which can be sorted.
|
||||
* @param array $query_parameters
|
||||
* The query parameters used to change the display format.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array representing the sort by portion of pager.
|
||||
*/
|
||||
protected function buildSortByForm(array $sort_criteria, array $query_parameters) {
|
||||
$default_order = $query_parameters['sort_order'] ?? 'ASC';
|
||||
$default_sort_by = $query_parameters['sort_by'] ?? 'search_api_relevance';
|
||||
$default_value = $default_sort_by . '_' . strtolower($default_order);
|
||||
$options = [];
|
||||
$options_attributes = [];
|
||||
// Not sure if this will work without defining a sort per direction.
|
||||
foreach ($sort_criteria as $sort) {
|
||||
if ($sort->options['exposed'] == TRUE) {
|
||||
$id = $sort->options['id'];
|
||||
// Label should be translated via views already.
|
||||
$label = $sort->options['expose']['label'];
|
||||
$asc = "{$id}_asc";
|
||||
$desc = "{$id}_desc";
|
||||
$options[$asc] = "{$label} ↑";
|
||||
$options[$desc] = "{$label} ↓";
|
||||
$options_attributes[$asc] = [
|
||||
'data-sort_by' => $id,
|
||||
'data-sort_order' => 'ASC',
|
||||
];
|
||||
$options_attributes[$desc] = [
|
||||
'data-sort_by' => $id,
|
||||
'data-sort_order' => 'DESC',
|
||||
];
|
||||
}
|
||||
}
|
||||
return [
|
||||
'#type' => 'select',
|
||||
'#title' => 'Sort',
|
||||
'#title_display' => 'invisible',
|
||||
'#options' => $options,
|
||||
'#options_attributes' => $options_attributes,
|
||||
'#attributes' => ['autocomplete' => 'off'],
|
||||
'#wrapper_attributes' => ['class' => ['pager__sort', 'container']],
|
||||
'#name' => 'order',
|
||||
'#value' => $default_value,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
// The block cannot be cached, because it must always match the current
|
||||
// search results.
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
91
src/Plugin/Block/SearchResultsPagerBlockDeriver.php
Normal file
91
src/Plugin/Block/SearchResultsPagerBlockDeriver.php
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\islandora_advanced_search\Plugin\Block;
|
||||
|
||||
use Drupal\Component\Plugin\PluginBase;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* This deriver creates a block for every search_api.display.
|
||||
*/
|
||||
class SearchResultsPagerBlockDeriver implements ContainerDeriverInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* List of derivative definitions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $derivatives = [];
|
||||
|
||||
/**
|
||||
* The entity storage used for the view.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The display manager for the search_api.
|
||||
*
|
||||
* @var \Drupal\search_api\Display\DisplayPluginManager
|
||||
*/
|
||||
protected $displayPluginManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
$deriver = new static($container, $base_plugin_id);
|
||||
$deriver->storage = $container->get('entity_type.manager')->getStorage('view');
|
||||
$deriver->displayPluginManager = $container->get('plugin.manager.search_api.display');
|
||||
return $deriver;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
|
||||
$derivatives = $this->getDerivativeDefinitions($base_plugin_definition);
|
||||
return isset($derivatives[$derivative_id]) ? $derivatives[$derivative_id] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
$base_plugin_id = $base_plugin_definition['id'];
|
||||
|
||||
if (!isset($this->derivatives[$base_plugin_id])) {
|
||||
$plugin_derivatives = [];
|
||||
|
||||
foreach ($this->displayPluginManager->getDefinitions() as $display_definition) {
|
||||
$view_id = $display_definition['view_id'];
|
||||
$view_display = $display_definition['view_display'];
|
||||
// The derived block needs both the view / display identifiers
|
||||
// to construct the pager.
|
||||
$machine_name = "${view_id}__${view_display}";
|
||||
|
||||
/** @var \Drupal\views\ViewEntityInterface $view */
|
||||
$view = $this->storage->load($view_id);
|
||||
$display = $view->getDisplay($view_display);
|
||||
|
||||
$plugin_derivatives[$machine_name] = [
|
||||
'id' => $base_plugin_id . PluginBase::DERIVATIVE_SEPARATOR . $machine_name,
|
||||
'label' => $this->t('Search Results Pager'),
|
||||
'admin_label' => $this->t(':view: Pager for :display', [
|
||||
':view' => $view->label(),
|
||||
':display' => $display['display_title'],
|
||||
]),
|
||||
] + $base_plugin_definition;
|
||||
}
|
||||
|
||||
$this->derivatives[$base_plugin_id] = $plugin_derivatives;
|
||||
}
|
||||
return $this->derivatives[$base_plugin_id];
|
||||
}
|
||||
|
||||
}
|
||||
30
src/Plugin/Block/ViewAndDisplayIdentifiersTrait.php
Normal file
30
src/Plugin/Block/ViewAndDisplayIdentifiersTrait.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\islandora_advanced_search\Plugin\Block;
|
||||
|
||||
/**
|
||||
* Gets the view and display identifiers used to create this block.
|
||||
*
|
||||
* @see Drupal\Component\Plugin\Discovery\DiscoveryInterface
|
||||
*/
|
||||
trait ViewAndDisplayIdentifiersTrait {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
abstract public function getDerivativeId();
|
||||
|
||||
/**
|
||||
* Gets the View and View Display identifiers used to derive this block.
|
||||
*
|
||||
* @return string[]
|
||||
* Returns an array of two strings where the first is the View identifier
|
||||
* and the second is the View Display identifier associated with the view
|
||||
* used to derive this block.
|
||||
*/
|
||||
public function getViewAndDisplayIdentifiers() {
|
||||
$id = $this->getDerivativeId();
|
||||
return preg_split('/__/', $id, 2);
|
||||
}
|
||||
|
||||
}
|
||||
63
src/Plugin/Field/FieldFormatter/EntityReferenceCountFormatter.php
Executable file
63
src/Plugin/Field/FieldFormatter/EntityReferenceCountFormatter.php
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\islandora_advanced_search\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'entity reference ID' formatter.
|
||||
*
|
||||
* @FieldFormatter(
|
||||
* id = "entity_reference_url_title",
|
||||
* label = @Translation("Children Entity Count, Label."),
|
||||
* description = @Translation("Children Entity Count, Label."),
|
||||
* field_types = {
|
||||
* "entity_reference"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class EntityReferenceCountFormatter extends EntityReferenceFormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return [
|
||||
'label' => 'Items in Collection',
|
||||
] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$elements['separator'] = [
|
||||
'#title' => $this->t("Text to appear next to the children's count"),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => $this->getSetting('label'),
|
||||
];
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsSummary(): array {
|
||||
$summary = [];
|
||||
$summary[] = $this->getSetting('label') ? 'Label : ' . $this->getSetting('label') : $this->t('No label');
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode): array {
|
||||
$element = [];
|
||||
$total_items = count($this->getEntitiesToView($items, $langcode));
|
||||
$element[] = ['#markup' => "<span class='collection_children__total_count'>{$total_items}<span>" . ' ' . $this->formatPlural($total_items, '1 Item in Collection', '@count Items in Collection')];
|
||||
return $element;
|
||||
}
|
||||
|
||||
}
|
||||
84
src/Plugin/facets/widget/IncludeExcludeLinksWidget.php
Normal file
84
src/Plugin/facets/widget/IncludeExcludeLinksWidget.php
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\islandora_advanced_search\Plugin\facets\widget;
|
||||
|
||||
use Drupal\facets\Plugin\facets\widget\LinksWidget;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\facets\Result\ResultInterface;
|
||||
|
||||
/**
|
||||
* The links widget.
|
||||
*
|
||||
* @FacetsWidget(
|
||||
* id = "include_exclude_links",
|
||||
* label = @Translation("List of links that allow the user to include / exclude facets."),
|
||||
* description = @Translation("A simple widget that shows a list of +/- links."),
|
||||
* )
|
||||
*/
|
||||
class IncludeExcludeLinksWidget extends LinksWidget {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function prepareLink(ResultInterface $result) {
|
||||
$facet = $result->getFacet();
|
||||
$facet_source_id = $facet->getFacetSourceId();
|
||||
$facet_manager = \Drupal::service('facets.manager');
|
||||
$facets = $facet_manager->getFacetsByFacetSourceId($facet_source_id);
|
||||
$raw_value = $result->getRawValue();
|
||||
$count = $result->getCount();
|
||||
$url = $result->getUrl();
|
||||
$exclude_facet = $this->getExcludeFacet($facet, $facets);
|
||||
$exclude_result = $this->getExcludeResult($exclude_facet, $raw_value);
|
||||
$exclude_url = $exclude_result ? $exclude_result->getUrl() : NULL;
|
||||
return [
|
||||
'#theme' => 'facets_result_item',
|
||||
'#is_active' => $result->isActive(),
|
||||
'#value' => [
|
||||
'text' => (new Link($result->getDisplayValue(), $url))->toRenderable(),
|
||||
'include' => (new Link(' ', $url))->toRenderable() + [
|
||||
'#attributes' => [
|
||||
'class' => ['facet-item__include', 'fa', 'fa-plus'],
|
||||
],
|
||||
],
|
||||
'exclude' => $exclude_url ? (new Link(' ', $exclude_url))->toRenderable() + [
|
||||
'#attributes' => [
|
||||
'class' => ['facet-item__exclude', 'fa', 'fa-minus'],
|
||||
],
|
||||
] : NULL,
|
||||
],
|
||||
'#show_count' => $this->getConfiguration()['show_numbers'] && ($count !== NULL),
|
||||
'#count' => $count,
|
||||
'#facet' => $facet,
|
||||
'#raw_value' => $raw_value,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for the excluded facet version of the included facet.
|
||||
*/
|
||||
protected function getExcludeResult($facet, $raw_value) {
|
||||
if ($facet) {
|
||||
foreach ($facet->getResults() as $result) {
|
||||
if ($result->getRawValue() === $raw_value) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for the excluded facet version of the included facet.
|
||||
*/
|
||||
protected function getExcludeFacet($include, $facets) {
|
||||
$field_identifier = $include->getFieldIdentifier();
|
||||
foreach ($facets as $facet) {
|
||||
if ($field_identifier === $facet->getFieldIdentifier() && $facet->getExclude()) {
|
||||
return $facet;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
42
src/Plugin/facets_summary/processor/ResetRemovePage.php
Normal file
42
src/Plugin/facets_summary/processor/ResetRemovePage.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\islandora_advanced_search\Plugin\facets_summary\processor;
|
||||
|
||||
use Drupal\facets_summary\FacetsSummaryInterface;
|
||||
use Drupal\facets_summary\Processor\BuildProcessorInterface;
|
||||
|
||||
/**
|
||||
* Reset should also remove the page query attribute.
|
||||
*
|
||||
* @SummaryProcessor(
|
||||
* id = "reset_remove_page",
|
||||
* label = @Translation("Remove page from query when resetting facets/query."),
|
||||
* description = @Translation("Remove page from query when resetting facets/query."),
|
||||
* stages = {
|
||||
* "build" = 45
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ResetRemovePage extends ShowSearchQueryProcessor implements BuildProcessorInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(FacetsSummaryInterface $facets_summary, array $build, array $facets) {
|
||||
// This processor is weighted to occur after the reset facets link.
|
||||
// Which leaves two cases:
|
||||
// - No facets selected so no reset link (we must add one).
|
||||
// - Reset link exists at the top of the list (we must remove the
|
||||
// search term from the link as well).
|
||||
$reset_index = $this->getResetLinkIndex($build);
|
||||
if ($reset_index !== NULL) {
|
||||
$reset = &$build['#items'][$reset_index];
|
||||
// Remove query from reset url as well.
|
||||
$query_params = $reset['#url']->getOption('query');
|
||||
unset($query_params['page']);
|
||||
$reset['#url']->setOption('query', $query_params);
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\islandora_advanced_search\Plugin\facets_summary\processor;
|
||||
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\facets_summary\FacetsSummaryInterface;
|
||||
use Drupal\facets_summary\Processor\BuildProcessorInterface;
|
||||
use Drupal\facets_summary\Processor\ProcessorPluginBase;
|
||||
|
||||
/**
|
||||
* Provides a processor that shows the search query.
|
||||
*
|
||||
* @SummaryProcessor(
|
||||
* id = "show_active_excluded_facets",
|
||||
* label = @Translation("Show active excluded facets."),
|
||||
* description = @Translation("When checked, negated facets will appear in the summary."),
|
||||
* stages = {
|
||||
* "build" = 20
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ShowActiveExcludedFacets extends ProcessorPluginBase implements BuildProcessorInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(FacetsSummaryInterface $facets_summary, array $build, array $facets) {
|
||||
$request = \Drupal::request();
|
||||
$query_params = $request->query->all();
|
||||
foreach ($facets as $facet) {
|
||||
if ($facet->getExclude()) {
|
||||
$url_alias = $facet->getUrlAlias();
|
||||
$filter_key = $facet->getFacetSourceConfig()->getFilterKey() ?: 'f';
|
||||
$active_items = $facet->getActiveItems();
|
||||
foreach ($active_items as $active_item) {
|
||||
$url = Url::createFromRequest($request);
|
||||
$modified_query_params = $query_params;
|
||||
$modified_query_params[$filter_key] = array_filter($query_params[$filter_key], function ($query_param) use ($url_alias, $active_item) {
|
||||
$pos = strpos($query_param, ':');
|
||||
$alias = substr($query_param, 0, $pos);
|
||||
$value = substr($query_param, $pos + 1);
|
||||
return !($alias == $url_alias && $value == $active_item);
|
||||
});
|
||||
$url->setOption('query', $modified_query_params);
|
||||
$item = [
|
||||
'#theme' => 'facets_result_item__summary',
|
||||
'#value' => $active_item,
|
||||
// We do not have counts for excluded facets...
|
||||
'#show_count' => FALSE,
|
||||
// Do not know the count.
|
||||
'#count' => 0,
|
||||
'#is_active' => TRUE,
|
||||
'#facet' => $facet,
|
||||
'#raw_value' => $active_item,
|
||||
];
|
||||
$item = (new Link($item, $url))->toRenderable();
|
||||
$item['#wrapper_attributes'] = [
|
||||
'class' => [
|
||||
'facet-summary-item--facet',
|
||||
'facet-summary-item--exclude',
|
||||
],
|
||||
];
|
||||
$build['#items'][] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
89
src/Plugin/facets_summary/processor/ShowActiveFacets.php
Normal file
89
src/Plugin/facets_summary/processor/ShowActiveFacets.php
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\islandora_advanced_search\Plugin\facets_summary\processor;
|
||||
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\facets_summary\FacetsSummaryInterface;
|
||||
use Drupal\facets_summary\Processor\BuildProcessorInterface;
|
||||
use Drupal\facets_summary\Processor\ProcessorPluginBase;
|
||||
use Drupal\facets\FacetInterface;
|
||||
use Drupal\facets\Result\ResultInterface;
|
||||
|
||||
/**
|
||||
* Provides a processor that shows the search query.
|
||||
*
|
||||
* @SummaryProcessor(
|
||||
* id = "show_active_facets",
|
||||
* label = @Translation("Shows active hidden facets."),
|
||||
* description = @Translation("When checked, undoes 'hide_active_items_processor', etc."),
|
||||
* stages = {
|
||||
* "build" = 20
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ShowActiveFacets extends ProcessorPluginBase implements BuildProcessorInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(FacetsSummaryInterface $facets_summary, array $build, array $facets) {
|
||||
// Rebuild list of results, add back ones that have been removed.
|
||||
$facet_manager = \Drupal::service('facets.manager');
|
||||
$facet_source_id = $facets_summary->getFacetSourceId();
|
||||
$facet_manager->updateResults($facet_source_id);
|
||||
$facet_manager->processFacets($facet_source_id);
|
||||
$facets_config = $facets_summary->getFacets();
|
||||
foreach ($facets as $facet) {
|
||||
$processors = $facet->getProcessors();
|
||||
/** @var \Drupal\facets\Processor\BuildProcessorInterface $url_handler */
|
||||
$url_handler = $processors['url_processor_handler'];
|
||||
$results = $url_handler->build($facet, $facet->getResults());
|
||||
foreach ($results as $result) {
|
||||
if ($result->isActive() && $this->resultMissing($facet, $result, $build['#items'])) {
|
||||
$item = [
|
||||
'#theme' => 'facets_result_item__summary',
|
||||
'#value' => $result->getDisplayValue(),
|
||||
'#show_count' => $facets_config[$facet->id()]['show_count'],
|
||||
'#count' => $result->getCount(),
|
||||
'#is_active' => TRUE,
|
||||
'#facet' => $result->getFacet(),
|
||||
'#raw_value' => $result->getRawValue(),
|
||||
];
|
||||
$item = (new Link($item, $result->getUrl()))->toRenderable();
|
||||
$item['#wrapper_attributes'] = [
|
||||
'class' => [
|
||||
'facet-summary-item--facet',
|
||||
],
|
||||
];
|
||||
$build['#items'][] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the results are missing for the given facet.
|
||||
*
|
||||
* @param \Drupal\facets\FacetInterface $facet
|
||||
* The facet to check.
|
||||
* @param \Drupal\facets\Result\ResultInterface $result
|
||||
* The result of the facet to check.
|
||||
* @param array $items
|
||||
* The already completed render array of facets to check against.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the result is missing FALSE otherwise.
|
||||
*/
|
||||
protected function resultMissing(FacetInterface $facet, ResultInterface $result, array $items) {
|
||||
foreach ($items as $item) {
|
||||
$item_facet = $item['#title']['#facet'];
|
||||
$raw_value = $item['#title']['#raw_value'];
|
||||
if ($item_facet === $facet && $raw_value === $result->getRawValue()) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
70
src/Plugin/facets_summary/processor/ShowMissingFacets.php
Normal file
70
src/Plugin/facets_summary/processor/ShowMissingFacets.php
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\islandora_advanced_search\Plugin\facets_summary\processor;
|
||||
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\facets_summary\FacetsSummaryInterface;
|
||||
use Drupal\facets_summary\Processor\BuildProcessorInterface;
|
||||
use Drupal\facets_summary\Processor\ProcessorPluginBase;
|
||||
|
||||
/**
|
||||
* Provides a processor that shows the search query.
|
||||
*
|
||||
* @SummaryProcessor(
|
||||
* id = "show_missing_facets",
|
||||
* label = @Translation("Shows facets from the url that are missing from the results."),
|
||||
* description = @Translation("When checked, show facets not included in the solr result but specified in the URL."),
|
||||
* stages = {
|
||||
* "build" = 20
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ShowMissingFacets extends ProcessorPluginBase implements BuildProcessorInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(FacetsSummaryInterface $facets_summary, array $build, array $facets) {
|
||||
$request = \Drupal::request();
|
||||
$query_params = $request->query->all();
|
||||
foreach ($facets as $facet) {
|
||||
if (!$facet->getExclude() && empty($facet->getResults())) {
|
||||
$url_alias = $facet->getUrlAlias();
|
||||
$filter_key = $facet->getFacetSourceConfig()->getFilterKey() ?: 'f';
|
||||
$active_items = $facet->getActiveItems();
|
||||
foreach ($active_items as $active_item) {
|
||||
$url = Url::createFromRequest($request);
|
||||
$modified_query_params = $query_params;
|
||||
$modified_query_params[$filter_key] = array_filter($query_params[$filter_key], function ($query_param) use ($url_alias, $active_item) {
|
||||
$pos = strpos($query_param, ':');
|
||||
$alias = substr($query_param, 0, $pos);
|
||||
$value = substr($query_param, $pos + 1);
|
||||
return !($alias == $url_alias && $value == $active_item);
|
||||
});
|
||||
$url->setOption('query', $modified_query_params);
|
||||
$item = [
|
||||
'#theme' => 'facets_result_item__summary',
|
||||
'#value' => $active_item,
|
||||
// We do not have counts for missing facets...
|
||||
'#show_count' => FALSE,
|
||||
// Do not know the count.
|
||||
'#count' => 0,
|
||||
'#is_active' => TRUE,
|
||||
'#facet' => $facet,
|
||||
'#raw_value' => $active_item,
|
||||
];
|
||||
$item = (new Link($item, $url))->toRenderable();
|
||||
$item['#wrapper_attributes'] = [
|
||||
'class' => [
|
||||
'facet-summary-item--facet',
|
||||
],
|
||||
];
|
||||
$build['#items'][] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
101
src/Plugin/facets_summary/processor/ShowSearchQueryProcessor.php
Normal file
101
src/Plugin/facets_summary/processor/ShowSearchQueryProcessor.php
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\islandora_advanced_search\Plugin\facets_summary\processor;
|
||||
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\facets_summary\FacetsSummaryInterface;
|
||||
use Drupal\facets_summary\Processor\BuildProcessorInterface;
|
||||
use Drupal\facets_summary\Processor\ProcessorPluginBase;
|
||||
|
||||
/**
|
||||
* Provides a processor that shows the search query.
|
||||
*
|
||||
* @SummaryProcessor(
|
||||
* id = "show_search_query",
|
||||
* label = @Translation("Show the current search query"),
|
||||
* description = @Translation("When checked, this facet will show the search query."),
|
||||
* stages = {
|
||||
* "build" = 40
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ShowSearchQueryProcessor extends ProcessorPluginBase implements BuildProcessorInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(FacetsSummaryInterface $facets_summary, array $build, array $facets) {
|
||||
$request = \Drupal::request();
|
||||
$query_params = $request->query->all();
|
||||
if (!empty($query_params['search_api_fulltext'])) {
|
||||
$text = $query_params['search_api_fulltext'];
|
||||
unset($query_params['search_api_fulltext']);
|
||||
$url = Url::createFromRequest($request);
|
||||
$url->setOption('query', $query_params);
|
||||
$item = [
|
||||
'#theme' => 'facets_result_item__summary',
|
||||
'#is_active' => FALSE,
|
||||
'#value' => $text,
|
||||
'#show_count' => FALSE,
|
||||
];
|
||||
$item = Link::fromTextAndUrl($item, $url)->toRenderable();
|
||||
$item['#wrapper_attributes'] = [
|
||||
'class' => [
|
||||
'facet-summary-item--query',
|
||||
],
|
||||
];
|
||||
// This processor is weighted to occur after the reset facets link.
|
||||
// Which leaves two cases:
|
||||
// - No facets selected so no reset link (we must add one).
|
||||
// - Reset link exists at the top of the list (we must remove the search
|
||||
// term from the link as well).
|
||||
$reset_index = $this->getResetLinkIndex($build);
|
||||
if ($reset_index !== NULL) {
|
||||
$reset = $build['#items'][$reset_index];
|
||||
// Remove query from reset url as well.
|
||||
$query_params = $reset['#url']->getOption('query');
|
||||
unset($query_params['search_api_fulltext']);
|
||||
$reset['#url']->setOption('query', $query_params);
|
||||
array_splice($build['#items'], $reset_index + 1, 0, [$item]);
|
||||
}
|
||||
else {
|
||||
array_unshift($build['#items'], $item);
|
||||
$text = $this->t('Reset');
|
||||
if (isset($facets_summary->getProcessorConfigs()['reset_facets']['settings']['link_text'])) {
|
||||
$text = $facets_summary->getProcessorConfigs()['reset_facets']['settings']['link_text'];
|
||||
}
|
||||
$reset = Link::fromTextAndUrl($text, $url)->toRenderable();
|
||||
$reset['#wrapper_attributes'] = [
|
||||
'class' => [
|
||||
'facet-summary-item--clear',
|
||||
],
|
||||
];
|
||||
array_unshift($build['#items'], $reset);
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index in the $build render array of the reset link.
|
||||
*
|
||||
* @param array $build
|
||||
* The render array of the FacetSummary block.
|
||||
*
|
||||
* @return mixed|null
|
||||
* The index of the reset link the $build render array.
|
||||
*/
|
||||
protected function getResetLinkIndex(array $build) {
|
||||
if (isset($build['#items'])) {
|
||||
foreach ($build['#items'] as $index => $item) {
|
||||
if (isset($item['#wrapper_attributes']['class']) && in_array('facet-summary-item--clear', $item['#wrapper_attributes']['class'])) {
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue