Newer
Older
Joris Vercammen
committed
namespace Drupal\facets\Plugin\facets\url_processor;
Hoi Sing Edison Wong
committed
use Drupal\Core\Cache\UnchangingCacheableDependencyTrait;
Jimmy Henderickx
committed
use Drupal\Core\Entity\EntityTypeManagerInterface;
Merlin Axel Rutz
committed
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\facets\Event\ActiveFiltersParsed;
Abhishek Vishwakarma
committed
use Drupal\facets\Event\QueryStringCreated;
use Drupal\facets\Event\UrlCreated;
use Drupal\facets\FacetInterface;
Joris Vercammen
committed
use Drupal\facets\UrlProcessor\UrlProcessorPluginBase;
Markus Kalkbrenner
committed
use Drupal\facets\Utility\FacetsUrlGenerator;
Abhishek Vishwakarma
committed
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
Joris Vercammen
committed
* Query string URL processor.
Joris Vercammen
committed
* @FacetsUrlProcessor(
Joris Vercammen
committed
* label = @Translation("Query string"),
Girish G
committed
* description = @Translation("Query string is the default Facets URL processor, and uses GET parameters, for example ?f[0]=brand:drupal&f[1]=color:blue")
Joris Vercammen
committed
class QueryString extends UrlProcessorPluginBase {
Hoi Sing Edison Wong
committed
use UnchangingCacheableDependencyTrait;
Joris Vercammen
committed
/**
* A string of how to represent the facet in the url.
*
* @var string
*/
protected $urlAlias;
Joris Vercammen
committed
Abhishek Vishwakarma
committed
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
Markus Kalkbrenner
committed
/**
* The URL generator.
*
* @var \Drupal\facets\Utility\FacetsUrlGenerator
*/
protected $urlGenerator;
Joris Vercammen
committed
/**
* {@inheritdoc}
*/
Markus Kalkbrenner
committed
public function __construct(array $configuration, $plugin_id, $plugin_definition, Request $request, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $eventDispatcher, FacetsUrlGenerator $urlGenerator) {
Jimmy Henderickx
committed
parent::__construct($configuration, $plugin_id, $plugin_definition, $request, $entity_type_manager);
Abhishek Vishwakarma
committed
$this->eventDispatcher = $eventDispatcher;
Markus Kalkbrenner
committed
$this->urlGenerator = $urlGenerator;
$this->initializeActiveFilters();
}
Abhishek Vishwakarma
committed
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
Jibran Ijaz
committed
$container->get('request_stack')->getCurrentRequest(),
Abhishek Vishwakarma
committed
$container->get('entity_type.manager'),
Markus Kalkbrenner
committed
$container->get('event_dispatcher'),
$container->get('facets.utility.url_generator')
Abhishek Vishwakarma
committed
);
}
Joris Vercammen
committed
/**
* {@inheritdoc}
*/
Joris Vercammen
committed
public function buildUrls(FacetInterface $facet, array $results) {
Joris Vercammen
committed
// No results are found for this facet, so don't try to create urls.
Joris Vercammen
committed
return [];
// First get the current list of get parameters.
$get_params = $this->request->query;
// When adding/removing a filter the number of pages may have changed,
// possibly resulting in an invalid page parameter.
Andrey Postnikov
committed
if ($get_params->has('page')) {
$current_page = $get_params->get('page');
$get_params->remove('page');
}
// Set the url alias from the facet object.
$this->urlAlias = $facet->getUrlAlias();
Markus Kalkbrenner
committed
// In case of a view page display, the facet source has a path, If the
// source is a block, the path is null.
Chris Jansen
committed
$facet_source_path = $facet->getFacetSource()->getPath();
$request = $this->getRequestByFacetSourcePath($facet_source_path);
$requestUrl = $this->getUrlForRequest($facet_source_path, $request);
Adrian Rollett
committed
Jimmy Henderickx
committed
$original_filter_params = [];
foreach ($this->getActiveFilters() as $facet_id => $values) {
$values = array_filter($values, static function ($it) {
return $it !== NULL;
});
foreach ($values as $value) {
$original_filter_params[] = $this->getUrlAliasByFacetId($facet_id, $facet->getFacetSourceId()) . $this->getSeparator() . $value;
Jimmy Henderickx
committed
}
}
Joris Vercammen
committed
/** @var \Drupal\facets\Result\ResultInterface[] $results */
Joris Vercammen
committed
foreach ($results as &$result) {
Jimmy Henderickx
committed
// Reset the URL for each result.
Adrian Rollett
committed
$url = clone $requestUrl;
Jimmy Henderickx
committed
José Manuel Rodríguez Vélez
committed
// Sets the url for children.
if ($children = $result->getChildren()) {
$this->buildUrls($facet, $children);
}
Markus Kalkbrenner
committed
$filter_missing = '';
Joris Vercammen
committed
if ($result->getRawValue() === NULL) {
$filter_string = NULL;
}
Markus Kalkbrenner
committed
elseif ($result->isMissing()) {
$filter_missing = $this->urlAlias . $this->getSeparator() . '!(';
$filter_string = $filter_missing . implode($this->getDelimiter(), $result->getMissingFilters()) . ')';
}
Joris Vercammen
committed
else {
$filter_string = $this->urlAlias . $this->getSeparator() . $result->getRawValue();
}
$result_get_params = clone $get_params;
Jimmy Henderickx
committed
$filter_params = $original_filter_params;
Jimmy Henderickx
committed
Joris Vercammen
committed
// If the value is active, remove the filter string from the parameters.
if ($result->isActive()) {
foreach ($filter_params as $key => $filter_param) {
Markus Kalkbrenner
committed
if ($filter_param === $filter_string || ($filter_missing && str_starts_with($filter_param, $filter_missing))) {
unset($filter_params[$key]);
}
}
Markus Kalkbrenner
committed
if ($facet->getUseHierarchy() && !$result->isMissing()) {
Markus Kalkbrenner
committed
$id = $result->getRawValue();
// Disable child filters.
foreach ($facet->getHierarchyInstance()->getNestedChildIds($id) as $child_id) {
$filter_params = array_diff($filter_params, [$this->urlAlias . $this->getSeparator() . $child_id]);
}
if ($facet->getEnableParentWhenChildGetsDisabled()) {
// Enable parent id again if exists.
$parent_ids = $facet->getHierarchyInstance()->getParentIds($id);
if (isset($parent_ids[0]) && $parent_ids[0]) {
// Get the parents children.
$child_ids = $facet->getHierarchyInstance()->getNestedChildIds($parent_ids[0]);
// Check if there are active siblings.
$active_sibling = FALSE;
if ($child_ids) {
foreach ($results as $result2) {
if ($result2->isActive() && $result2->getRawValue() != $id && in_array($result2->getRawValue(), $child_ids)) {
$active_sibling = TRUE;
continue;
}
Joris Vercammen
committed
}
}
Markus Kalkbrenner
committed
if (!$active_sibling) {
$filter_params[] = $this->urlAlias . $this->getSeparator() . $parent_ids[0];
}
Joris Vercammen
committed
}
Jimmy Henderickx
committed
}
}
Joris Vercammen
committed
}
// If the value is not active, add the filter string.
else {
Joris Vercammen
committed
if ($filter_string !== NULL) {
$filter_params[] = $filter_string;
}
Jimmy Henderickx
committed
Markus Kalkbrenner
committed
$parents_and_child_ids = [];
Jimmy Henderickx
committed
if ($facet->getUseHierarchy()) {
$parent_ids = $facet->getHierarchyInstance()->getParentIds($result->getRawValue());
$child_ids = $facet->getHierarchyInstance()->getNestedChildIds($result->getRawValue());
$parents_and_child_ids = array_merge($parent_ids, $child_ids);
Markus Kalkbrenner
committed
if (!$facet->getKeepHierarchyParentsActive()) {
// If hierarchy is active, unset parent trail and every child when
// building the enable-link to ensure those are not enabled anymore.
foreach ($parents_and_child_ids as $id) {
$filter_params = array_diff($filter_params, [$this->urlAlias . $this->getSeparator() . $id]);
}
Jimmy Henderickx
committed
}
}
Markus Kalkbrenner
committed
Joris Vercammen
committed
// Exclude currently active results from the filter params if we are in
// the show_only_one_result mode.
if ($facet->getShowOnlyOneResult()) {
foreach ($results as $result2) {
if ($result2->isActive()) {
Markus Kalkbrenner
committed
$id = $result2->getRawValue();
if (!in_array($id, $parents_and_child_ids)) {
$active_filter_string = $this->urlAlias . $this->getSeparator() . $id;
foreach ($filter_params as $key2 => $filter_param2) {
if ($filter_param2 == $active_filter_string) {
unset($filter_params[$key2]);
}
Joris Vercammen
committed
}
}
}
}
}
Abhishek Vishwakarma
committed
// Allow other modules to alter the result url query string built.
Megha kundar
committed
$event = new QueryStringCreated($result_get_params, $filter_params, $result, $this->activeFilters, $facet);
$this->eventDispatcher->dispatch($event);
Megha kundar
committed
$filter_params = $event->getFilterParameters();
Abhishek Vishwakarma
committed
Joris Vercammen
committed
asort($filter_params, \SORT_NATURAL);
$result_get_params->set($this->filterKey, array_values($filter_params));
if ($result_get_params->all() !== [$this->filterKey => []]) {
$new_url_params = $result_get_params->all();
if (empty($new_url_params[$this->filterKey])) {
unset($new_url_params[$this->filterKey]);
}
// Facet links should be page-less.
// See https://www.drupal.org/node/2898189.
unset($new_url_params['page']);
Markus Kalkbrenner
committed
// Remove core wrapper format (e.g. render-as-ajax-response) parameters.
Markus Kalkbrenner
committed
// @todo ajax review
Merlin Axel Rutz
committed
unset($new_url_params[MainContentViewSubscriber::WRAPPER_FORMAT]);
// Set the new url parameters.
Adrian Rollett
committed
$url->setOption('query', $new_url_params);
Joris Vercammen
committed
// Allow other modules to alter the result url built.
$event = new UrlCreated($url, $result, $facet);
$this->eventDispatcher->dispatch($event);
$result->setUrl($event->getUrl());
Antonio De Marco
committed
// Restore page parameter again. See https://www.drupal.org/node/2726455.
Andrey Postnikov
committed
if (isset($current_page)) {
$get_params->set('page', $current_page);
}
Joris Vercammen
committed
return $results;
Chris Jansen
committed
/**
* Gets a request object based on the facet source path.
*
* If the facet's source has a path, we construct a request object based on
* that path, as it may be different than the current request's. This method
* statically caches the request object based on the facet source path so that
Hoi Sing Edison Wong
committed
* subsequent calls to this processor do not recreate the same request object.
Chris Jansen
committed
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
*
* @param string $facet_source_path
* The facet source path.
*
* @return \Symfony\Component\HttpFoundation\Request
* The request.
*/
protected function getRequestByFacetSourcePath($facet_source_path) {
$requestsByPath = &drupal_static(__CLASS__ . __FUNCTION__, []);
if (!$facet_source_path) {
return $this->request;
}
if (array_key_exists($facet_source_path, $requestsByPath)) {
return $requestsByPath[$facet_source_path];
}
$request = Request::create($facet_source_path);
$request->attributes->set('_format', $this->request->get('_format'));
$requestsByPath[$facet_source_path] = $request;
return $request;
}
/**
* Gets the URL object for a request.
*
Markus Kalkbrenner
committed
* This method delegates to the URL generator service. But we keep it for
* backward compatibility for custom implementations that extend this class.
Chris Jansen
committed
*
* @param string $facet_source_path
* The facet source path.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
*
* @return \Drupal\Core\Url
* The URL.
*/
protected function getUrlForRequest($facet_source_path, Request $request) {
Markus Kalkbrenner
committed
return $this->urlGenerator->getUrlForRequest($request, $facet_source_path);
Chris Jansen
committed
}
Joris Vercammen
committed
/**
Jimmy Henderickx
committed
* Initializes the active filters from the request query.
Jimmy Henderickx
committed
* Get all the filters that are active by checking the request query and store
* them in activeFilters which is an array where key is the facet id and value
* is an array of raw values.
*/
protected function initializeActiveFilters() {
Markus Kalkbrenner
committed
if ($this->request->isXmlHttpRequest()) {
$url_parameters = $this->request->request;
}
else {
$url_parameters = $this->request->query;
}
Ide Braakman
committed
$active_params = $url_parameters->all()[$this->filterKey] ?? "";
Jimmy Henderickx
committed
$facet_source_id = $this->configuration['facet']->getFacetSourceId();
Joris Vercammen
committed
// When an invalid parameter is passed in the url, we can't do anything.
if (!is_array($active_params)) {
return;
}
$active_filters = [];
// Explode the active params on the separator.
foreach ($active_params as $param) {
Mattias Michaux
committed
$explosion = explode($this->getSeparator(), $param);
Jimmy Henderickx
committed
$url_alias = array_shift($explosion);
if ($facet_id = $this->getFacetIdByUrlAlias($url_alias, $facet_source_id)) {
$value = '';
while (count($explosion) > 0) {
$value .= array_shift($explosion);
if (count($explosion) > 0) {
$value .= $this->getSeparator();
}
}
if (!isset($active_filters[$facet_id])) {
$active_filters[$facet_id] = [$value];
}
else {
$active_filters[$facet_id][] = $value;
Joris Vercammen
committed
}
Jimmy Henderickx
committed
}
}
// Allow other modules to alter the parsed active filters.
Markus Kalkbrenner
committed
$event = new ActiveFiltersParsed($facet_source_id, $active_filters, $url_parameters, $this->filterKey);
$this->eventDispatcher->dispatch($event);
$this->activeFilters = $event->getActiveFilters();
Jimmy Henderickx
committed
}
/**
* Gets the facet id from the url alias & facet source id.
*
* @param string $url_alias
* The url alias.
* @param string $facet_source_id
* The facet source id.
*
* @return bool|string
* Either the facet id, or FALSE if that can't be loaded.
*/
protected function getFacetIdByUrlAlias($url_alias, $facet_source_id) {
$mapping = &drupal_static(__FUNCTION__);
if (!isset($mapping[$facet_source_id][$url_alias])) {
$storage = $this->entityTypeManager->getStorage('facets_facet');
$facet = current($storage->loadByProperties(
[
'url_alias' => $url_alias,
'facet_source_id' => $facet_source_id,
]
));
Jimmy Henderickx
committed
if (!$facet) {
return NULL;
}
$mapping[$facet_source_id][$url_alias] = $facet->id();
}
return $mapping[$facet_source_id][$url_alias];
}
/**
* Gets the url alias from the facet id & facet source id.
*
* @param string $facet_id
* The facet id.
* @param string $facet_source_id
* The facet source id.
*
* @return bool|string
* Either the url alias, or FALSE if that can't be loaded.
*/
protected function getUrlAliasByFacetId($facet_id, $facet_source_id) {
$mapping = &drupal_static(__FUNCTION__);
if (!isset($mapping[$facet_source_id][$facet_id])) {
$storage = $this->entityTypeManager->getStorage('facets_facet');
$facet = current($storage->loadByProperties(
[
'id' => $facet_id,
'facet_source_id' => $facet_source_id,
]
));
Jimmy Henderickx
committed
if (!$facet) {
return FALSE;
Jimmy Henderickx
committed
$mapping[$facet_source_id][$facet_id] = $facet->getUrlAlias();
Jimmy Henderickx
committed
return $mapping[$facet_source_id][$facet_id];
Hoi Sing Edison Wong
committed
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return ['url.query_args'];
}