368 lines
14 KiB
Plaintext
368 lines
14 KiB
Plaintext
<?php
|
|
|
|
/**
|
|
* @file
|
|
* Contains facets.module.
|
|
*/
|
|
|
|
use Drupal\Component\Utility\Html;
|
|
use Drupal\Core\Breadcrumb\Breadcrumb;
|
|
use Drupal\Core\Form\FormStateInterface;
|
|
use Drupal\Core\Link;
|
|
use Drupal\Core\Routing\RouteMatchInterface;
|
|
use Drupal\Core\Url;
|
|
use Drupal\facets\Entity\Facet;
|
|
use Drupal\facets\Entity\FacetSource;
|
|
use Drupal\facets\FacetInterface;
|
|
use Drupal\views\Entity\View;
|
|
use Drupal\Core\Entity\EntityInterface;
|
|
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
|
|
use Drupal\Core\Logger\RfcLogLevel;
|
|
|
|
/**
|
|
* Implements hook_help().
|
|
*/
|
|
function facets_help($route_name, RouteMatchInterface $route_match) {
|
|
switch ($route_name) {
|
|
// Main module help for the facets module.
|
|
case 'help.page.facets':
|
|
$output = '';
|
|
$output .= '<h3>' . t('About') . '</h3>';
|
|
$output .= '<p>' . t('Facets test') . '</p>';
|
|
return $output;
|
|
|
|
case 'entity.facets_facet.collection':
|
|
$output = '';
|
|
$output .= '<p>' . t('Below is a list of facets grouped by facetsources they are associated with. A facetsource is the instance where the facet does the actual filtering, for example a View on a Search API index.') . '</p>';
|
|
$output .= '<p>' . t('The facets weight can be changed with drag and drop within the same facet source. Although you can drag and drop a facet under any facet source, this change will not be performed on save.') . '</p>';
|
|
return $output;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_theme().
|
|
*/
|
|
function facets_theme($existing, $type, $theme, $path) {
|
|
return [
|
|
'facets_result_item' => [
|
|
'variables' => [
|
|
'facet' => NULL,
|
|
'raw_value' => '',
|
|
'value' => '',
|
|
'show_count' => FALSE,
|
|
'count' => NULL,
|
|
'is_active' => FALSE,
|
|
],
|
|
],
|
|
'facets_item_list' => [
|
|
'variables' => [
|
|
'facet' => NULL,
|
|
'items' => [],
|
|
'title' => '',
|
|
'list_type' => 'ul',
|
|
'wrapper_attributes' => [],
|
|
'attributes' => [],
|
|
'empty' => NULL,
|
|
'context' => [],
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Implements hook_entity_presave().
|
|
*
|
|
* We implement this to make sure that a facet gets removed on view updates, so
|
|
* we don't get broken facet blocks.
|
|
*/
|
|
function facets_entity_presave(EntityInterface $entity) {
|
|
// Make sure that we only react on view entities with changed displays.
|
|
if ($entity instanceof View && !empty($entity->original)) {
|
|
if ($entity->original->get('display') != $entity->get('display')) {
|
|
|
|
/** @var \Drupal\facets\FacetSource\FacetSourcePluginManager $facet_source_plugin_manager */
|
|
$facet_source_plugin_manager = \Drupal::getContainer()
|
|
->get('plugin.manager.facets.facet_source');
|
|
$definitions = $facet_source_plugin_manager->getDefinitions();
|
|
|
|
// Setup an array of sources that are deleted.
|
|
$sources = [];
|
|
foreach ($entity->original->get('display') as $k => $display) {
|
|
// Check if the current display is also a facet source plugin and that
|
|
// is removed from the view. We use the double underscore here to make
|
|
// sure that we use core convention of "plugin:derived_plugin".
|
|
$facets_source_plugin_id = 'search_api:views_' . $display['display_plugin'] . '__' . $entity->id() . '__' . $display['id'];
|
|
if (array_key_exists($facets_source_plugin_id, $definitions) && !array_key_exists($k, $entity->get('display'))) {
|
|
$entity_id = str_replace(':', '__', $facets_source_plugin_id);
|
|
$source_entity = FacetSource::load($entity_id);
|
|
$sources[] = $facets_source_plugin_id;
|
|
if (!is_null($source_entity)) {
|
|
$source_entity->delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop over all deleted sources and delete the facets that were linked to
|
|
// that source.
|
|
if (count($sources) > 0) {
|
|
/** @var \Drupal\facets\FacetManager\DefaultFacetManager $fm */
|
|
$fm = \Drupal::getContainer()->get('facets.manager');
|
|
foreach ($sources as $source) {
|
|
$facets = $fm->getFacetsByFacetSourceId($source);
|
|
foreach ($facets as $facet) {
|
|
$facet->delete();
|
|
}
|
|
}
|
|
}
|
|
$facet_source_plugin_manager->clearCachedDefinitions();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Implements hook_preprocess_block().
|
|
*
|
|
* Adds a class for the widget to the facet block to allow for more specific
|
|
* styling.
|
|
*/
|
|
function facets_preprocess_block(&$variables) {
|
|
if ($variables['configuration']['provider'] == 'facets') {
|
|
// Hide the block if it's empty.
|
|
if (!empty($variables['elements']['content'][0]['#attributes']['class']) && in_array('facet-hidden', $variables['elements']['content'][0]['#attributes']['class'])) {
|
|
// Add the Drupal class for hiding this for everyone, including screen
|
|
// readers. See hidden.module.css in the core system module.
|
|
$variables['attributes']['class'][] = 'hidden';
|
|
}
|
|
if (!empty($variables['derivative_plugin_id'])) {
|
|
$facet = Facet::load($variables['derivative_plugin_id']);
|
|
$variables['attributes']['class'][] = 'block-facet--' . Html::cleanCssIdentifier($facet->getWidget()['type']);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_entity_predelete().
|
|
*
|
|
* We implement this hook to make sure that facet source plugins are cleared
|
|
* when a view is deleted. It also deletes facets that are created on those
|
|
* plugins.
|
|
*/
|
|
function facets_entity_predelete(EntityInterface $entity) {
|
|
if ($entity instanceof View) {
|
|
$facet_source_plugin_manager = \Drupal::getContainer()
|
|
->get('plugin.manager.facets.facet_source');
|
|
|
|
$definitions = $facet_source_plugin_manager->getDefinitions();
|
|
|
|
if (!is_array($definitions)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($definitions as $plugin_id => $definition) {
|
|
if (strpos($plugin_id, 'search_api:' . $entity->id() . '__') !== FALSE) {
|
|
try {
|
|
$facetManager = \Drupal::getContainer()->get('facets.manager');
|
|
}
|
|
catch (ServiceNotFoundException $e) {
|
|
\Drupal::logger('facets')->log(RfcLogLevel::DEBUG, 'Facet manager not found on trying to delete a view.');
|
|
return;
|
|
}
|
|
|
|
$facets = $facetManager->getFacetsByFacetSourceId($plugin_id);
|
|
foreach ($facets as $facet) {
|
|
$facet->delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear cached plugin definitions for facet source to make sure we don't
|
|
// show stale data.
|
|
$facet_source_plugin_manager->clearCachedDefinitions();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prepares variables for facets item list templates.
|
|
*
|
|
* Default template: facets-item-list.html.twig.
|
|
*
|
|
* @param array $variables
|
|
* An associative array containing:
|
|
* - items: An array of items to be displayed in the list. Each item can be
|
|
* either a string or a render array. If #type, #theme, or #markup
|
|
* properties are not specified for child render arrays, they will be
|
|
* inherited from the parent list, allowing callers to specify larger
|
|
* nested lists without having to explicitly specify and repeat the
|
|
* render properties for all nested child lists.
|
|
* - title: A title to be prepended to the list.
|
|
* - list_type: The type of list to return (e.g. "ul", "ol").
|
|
* - wrapper_attributes: HTML attributes to be applied to the list wrapper.
|
|
*
|
|
* @see https://www.drupal.org/node/1842756
|
|
*/
|
|
function facets_preprocess_facets_item_list(array &$variables) {
|
|
if ($variables['facet'] !== NULL && $variables['facet']->get('show_title') === TRUE) {
|
|
$variables['title'] = $variables['facet']->label();
|
|
}
|
|
template_preprocess_item_list($variables);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_system_breadcrumb_alter().
|
|
*/
|
|
function facets_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context) {
|
|
/** @var \Drupal\facets\FacetSource\FacetSourcePluginManager $facet_source_manager */
|
|
$facet_source_manager = \Drupal::service('plugin.manager.facets.facet_source');
|
|
|
|
/** @var \Drupal\facets\FacetManager\DefaultFacetManager $facet_manager */
|
|
$facet_manager = \Drupal::service('facets.manager');
|
|
|
|
/** @var \Drupal\Core\Entity\EntityTypeManager $entity_type_manager */
|
|
$entity_type_manager = \Drupal::service('entity_type.manager');
|
|
|
|
/** @var \Drupal\Core\Entity\EntityStorageInterface $facet_source_storage */
|
|
$facet_source_storage = $entity_type_manager->getStorage('facets_facet_source');
|
|
|
|
$facet_sources_definitions = $facet_source_manager->getDefinitions();
|
|
|
|
$facets_url_generator = \Drupal::service('facets.utility.url_generator');
|
|
|
|
// No facet sources found, so don't do anything.
|
|
if (empty($facet_sources_definitions)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($facet_sources_definitions as $definition) {
|
|
/** @var \Drupal\facets\FacetSource\FacetSourcePluginBase $facet_source_plugin */
|
|
$facetsource_id = $definition['id'];
|
|
$facet_source_plugin = $facet_source_manager->createInstance($facetsource_id);
|
|
|
|
// If the current facet source is not being rendered, don't do anything with
|
|
// these facet sources.
|
|
if (!$facet_source_plugin->isRenderedInCurrentRequest()) {
|
|
continue;
|
|
}
|
|
|
|
$source_id = str_replace(':', '__', $facetsource_id);
|
|
/** @var \Drupal\facets\FacetSourceInterface $facet_source */
|
|
$facet_source = $facet_source_storage->load($source_id);
|
|
|
|
// If the facet source is not loaded, or the facet source doesn't have
|
|
// breadcrumbs enabled, don't do anything.
|
|
if (!($facet_source && !empty($facet_source->getBreadcrumbSettings()['active']))) {
|
|
continue;
|
|
}
|
|
|
|
// Add the required cacheability metadata.
|
|
$breadcrumb->addCacheContexts(['url']);
|
|
$breadcrumb->addCacheableDependency($facet_source);
|
|
|
|
// Process the facets if they are not already processed.
|
|
$facet_manager->processFacets($facetsource_id);
|
|
$facets = $facet_manager->getFacetsByFacetSourceId($facetsource_id);
|
|
|
|
// Sort facets by weight.
|
|
uasort($facets, function (FacetInterface $a, FacetInterface $b) {
|
|
return (int) $a->getWeight() - $b->getWeight();
|
|
});
|
|
|
|
/** @var \Drupal\facets\UrlProcessor\UrlProcessorPluginManager $url_processor_manager */
|
|
$url_processor_manager = \Drupal::service('plugin.manager.facets.url_processor');
|
|
|
|
// Get active facets and results to use them at building the crumbs.
|
|
$active_results = [];
|
|
$active_facets = [];
|
|
foreach ($facets as $facet) {
|
|
if (count($facet->getActiveItems()) > 0) {
|
|
// Add the facet as a cacheable dependency.
|
|
$breadcrumb->addCacheableDependency($facet);
|
|
/** @var \Drupal\facets\UrlProcessor\UrlProcessorInterface $url_processor */
|
|
$url_processor = $url_processor_manager->createInstance($facet_source->getUrlProcessorName(), ['facet' => $facet]);
|
|
$facet_manager->build($facet);
|
|
|
|
foreach ($facet->getResults() as $result) {
|
|
if ($result->isActive() || $result->hasActiveChildren()) {
|
|
// Clone the result so we can mark it as inactive to be added to the
|
|
// url parameters when calling buildUrls.
|
|
$cloned_result = clone $result;
|
|
$cloned_result->setActiveState(FALSE);
|
|
$active_results[$facet->id()][] = $cloned_result;
|
|
}
|
|
}
|
|
if (!empty($active_results[$facet->getUrlAlias()])) {
|
|
$url_processor->buildUrls($facet, $active_results[$facet->getUrlAlias()]);
|
|
}
|
|
$active_facets[$facet->id()] = $facet;
|
|
}
|
|
}
|
|
|
|
// @todo find a better way to construct the url for a crumb maybe url
|
|
// processor will have a function to get params for a result
|
|
// without all the other request parameters; with this we could implement:
|
|
// @see https://www.drupal.org/node/2861586
|
|
// @todo handle not grouped facets.
|
|
/** @var \Drupal\facets\Result\ResultInterface[] $facet_results */
|
|
foreach ($active_results as $facet_id => $facet_results) {
|
|
$facet_used_result[$facet_id] = [];
|
|
$facet_crumb_items = [];
|
|
|
|
// Because we can't get the desired display value trough a url processor
|
|
// method we iterate each result url and remove the facet params that
|
|
// haven't been used on previous crumbs.
|
|
foreach ($facet_results as $res) {
|
|
$facet_used_result[$facet_id][] = $res->getRawValue();
|
|
$facet_crumb_items[] = $res->getDisplayValue();
|
|
}
|
|
|
|
sort($facet_crumb_items);
|
|
|
|
$facet_url = $facets_url_generator->getUrl($facet_used_result, FALSE);
|
|
if (!empty($facet_source->getBreadcrumbSettings()['before'])) {
|
|
$crumb_text = $active_facets[$facet_id]->label() . ': ' . implode(', ', $facet_crumb_items);
|
|
}
|
|
else {
|
|
$crumb_text = implode(', ', $facet_crumb_items);
|
|
};
|
|
$link = Link::fromTextAndUrl($crumb_text, $facet_url);
|
|
$breadcrumb->addLink($link);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_language_switch_links_alter().
|
|
*/
|
|
function facets_language_switch_links_alter(array &$links, $type, Url $url) {
|
|
/** @var \Drupal\facets\LanguageSwitcherLinksAlterer $alterer */
|
|
$alterer = \Drupal::service('facets.language_switcher_links_alterer');
|
|
$alterer->alter($links, $type, $url);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_form_FORM_ID_alter().
|
|
*/
|
|
function facets_form_facets_facet_form_alter(&$form, FormStateInterface $form_state, $form_id) {
|
|
$facet_sources = [];
|
|
foreach (\Drupal::service('plugin.manager.facets.facet_source')->getDefinitions() as $facet_source_id => $definition) {
|
|
$facet_sources[$definition['id']] = !empty($definition['label']) ? $definition['label'] : $facet_source_id;
|
|
}
|
|
|
|
if (count($facet_sources) == 0) {
|
|
unset($form['actions']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_theme_suggestions_HOOK().
|
|
*/
|
|
function facets_theme_suggestions_facets_result_item(array $variables) {
|
|
$suggestions = [];
|
|
$facet = $variables['facet'];
|
|
if ($facet instanceof FacetInterface) {
|
|
$suggestions[] = 'facets_result_item__' . $facet->getWidget()['type'];
|
|
$suggestions[] = 'facets_result_item__' . $facet->getWidget()['type'] . '__' . $facet->id();
|
|
}
|
|
return $suggestions;
|
|
}
|