' . t('About') . ''; $output .= '

' . t('Facets test') . '

'; return $output; case 'entity.facets_facet.collection': $output = ''; $output .= '

' . 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.') . '

'; $output .= '

' . 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.') . '

'; 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' => [], ], ], 'facets_views_plugin' => [ 'variables' => [ 'content' => [], ], ], ]; } /** * 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) { /** @var FacetInterface $facet */ $facet = $variables['facet']; if ($facet !== NULL) { if ($facet->get('show_title') === TRUE) { $variables['title'] = $facet->label(); } if (Settings::get('facets_debug_cacheable_metadata', FALSE) && $facet->getFacetSource() instanceof SearchApiFacetSourceInterface) { $variables['cache_hash'] = substr(base_convert(hash('sha256', uniqid(time())), 16, 36), 0, 6); $variables['cache_contexts'] = implode(', ', $facet->getCacheContexts()); $variables['cache_tags'] = implode(', ', $facet->getCacheTags()); $variables['cache_max_age'] = $facet->getCacheMaxAge() . ' seconds'; } } 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; }