facets.module in Facets 8
Contains facets.module.
File
facets.moduleView source
<?php
/**
* @file
* Contains facets.module.
*/
use Drupal\Component\Utility\Html;
use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
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) {
if ($a
->getWeight() == $b
->getWeight()) {
return 0;
}
return $a
->getWeight() < $b
->getWeight() ? -1 : 1;
});
/** @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;
}
Functions
Name | Description |
---|---|
facets_entity_predelete | Implements hook_entity_predelete(). |
facets_entity_presave | Implements hook_entity_presave(). |
facets_form_facets_facet_form_alter | Implements hook_form_FORM_ID_alter(). |
facets_help | Implements hook_help(). |
facets_language_switch_links_alter | Implements hook_language_switch_links_alter(). |
facets_preprocess_block | Implements hook_preprocess_block(). |
facets_preprocess_facets_item_list | Prepares variables for facets item list templates. |
facets_system_breadcrumb_alter | Implements hook_system_breadcrumb_alter(). |
facets_theme | Implements hook_theme(). |
facets_theme_suggestions_facets_result_item | Implements hook_theme_suggestions_HOOK(). |