facets-8.x-1.x-dev/src/FacetManager/DefaultFacetManager.php

src/FacetManager/DefaultFacetManager.php
<?php

namespace Drupal\facets\FacetManager;

use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\CurrentRouteMatch;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\facets\Event\PostBuildFacet;
use Drupal\facets\Exception\InvalidProcessorException;
use Drupal\facets\FacetInterface;
use Drupal\facets\FacetSource\FacetSourcePluginManager;
use Drupal\facets\Plugin\facets\facet_source\SearchApiDisplay;
use Drupal\facets\Processor\BuildProcessorInterface;
use Drupal\facets\Processor\PostQueryProcessorInterface;
use Drupal\facets\Processor\PreQueryProcessorInterface;
use Drupal\facets\Processor\ProcessorInterface;
use Drupal\facets\Processor\ProcessorPluginManager;
use Drupal\facets\QueryType\QueryTypePluginManager;

/**
 * The facet manager.
 *
 * The manager is responsible for interactions with the Search backend, such as
 * altering the query, it is also responsible for executing and building the
 * facet. It is also responsible for running the processors.
 */
class DefaultFacetManager {

  use StringTranslationTrait;

  /**
   * The query type plugin manager.
   *
   * @var \Drupal\facets\QueryType\QueryTypePluginManager
   *   The query type plugin manager.
   */
  protected $queryTypePluginManager;

  /**
   * The facet source plugin manager.
   *
   * @var \Drupal\facets\FacetSource\FacetSourcePluginManager
   */
  protected $facetSourcePluginManager;

  /**
   * The processor plugin manager.
   *
   * @var \Drupal\facets\Processor\ProcessorPluginManager
   */
  protected $processorPluginManager;

  /**
   * An array of facets that are being rendered.
   *
   * @var \Drupal\facets\FacetInterface[]
   *
   * @see \Drupal\facets\FacetInterface
   * @see \Drupal\facets\Entity\Facet
   */
  protected $facets = [];

  /**
   * The entity storage for facets.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface|object
   */
  protected $facetStorage;

  /**
   * A static cache of already processed facets.
   *
   * @var \Drupal\facets\FacetInterface[]
   */
  protected $processedFacets = [];

  /**
   * A static cache of already built facets.
   *
   * @var \Drupal\facets\FacetInterface[]
   */
  protected $builtFacets = [];

  /**
   * The current route match.
   *
   * @var \Drupal\Core\Routing\CurrentRouteMatch
   */
  private $routeMatch;

  /**
   * Constructs a new instance of the DefaultFacetManager.
   *
   * @param \Drupal\facets\QueryType\QueryTypePluginManager $query_type_plugin_manager
   *   The query type plugin manager.
   * @param \Drupal\facets\FacetSource\FacetSourcePluginManager $facet_source_manager
   *   The facet source plugin manager.
   * @param \Drupal\facets\Processor\ProcessorPluginManager $processor_plugin_manager
   *   The processor plugin manager.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type plugin manager.
   * @param \Drupal\Core\Routing\CurrentRouteMatch $route_match
   *   The current route match.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function __construct(QueryTypePluginManager $query_type_plugin_manager, FacetSourcePluginManager $facet_source_manager, ProcessorPluginManager $processor_plugin_manager, EntityTypeManagerInterface $entity_type_manager, CurrentRouteMatch $route_match) {
    $this->queryTypePluginManager = $query_type_plugin_manager;
    $this->facetSourcePluginManager = $facet_source_manager;
    $this->processorPluginManager = $processor_plugin_manager;
    $this->facetStorage = $entity_type_manager->getStorage('facets_facet');
    $this->routeMatch = $route_match;
  }

  /**
   * Allows the backend to add facet queries to its native query object.
   *
   * This method is called by the implementing module to initialize the facet
   * display process.
   *
   * @param mixed $query
   *   The backend's native query object.
   * @param string $facetsource_id
   *   The facet source ID to process.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  public function alterQuery(&$query, $facetsource_id) {
    $query_is_cacheable = $query instanceof RefinableCacheableDependencyInterface;
    /** @var \Drupal\facets\FacetInterface[] $facets */
    $facets = $this->getFacetsByFacetSourceId($facetsource_id);

    foreach ($facets as $facet) {
      $processors = $facet->getProcessors();

      if (isset($processors['dependent_processor'])) {
        $conditions = $processors['dependent_processor']->getConfiguration();

        $enabled_conditions = [];
        foreach ($conditions as $facet_id => $condition) {
          if (empty($condition['enable'])) {
            continue;
          }
          $enabled_conditions[$facet_id] = $condition;
        }

        foreach ($enabled_conditions as $facet_id => $condition_settings) {
          if (!isset($facets[$facet_id]) || !$processors['dependent_processor']->isConditionMet($condition_settings, $facets[$facet_id])) {
            // The conditions are not met anymore, remove the active items.
            $facet->setActiveItems([]);

            // Remove the query parameter from other facets.
            foreach ($facets as $other_facet) {
              /** @var \Drupal\facets\UrlProcessor\UrlProcessorInterface $urlProcessor */
              $urlProcessor = $other_facet->getProcessors()['url_processor_handler']->getProcessor();
              $active_filters = $urlProcessor->getActiveFilters();
              unset($active_filters[$facet->id()]);
              $urlProcessor->setActiveFilters($active_filters);
            }
            // Add "dependend facet" cacheabillity to make sure that whenever
            // its preferences will change, for instance to "negate", query
            // results cache will take it to consideration.
            if ($query_is_cacheable) {
              $query->addCacheableDependency($facet);
            }
            // Don't convert this facet's active items into query conditions.
            // Continue with the next facet.
            continue(2);
          }
        }
      }

      /** @var \Drupal\facets\QueryType\QueryTypeInterface $query_type_plugin */
      $query_type_plugin = $this->queryTypePluginManager->createInstance(
        $facet->getQueryType(),
        [
          'query' => $query,
          'facet' => $facet,
        ]
      );
      $query_type_plugin->execute();
      // Merge cache metadata that gathered from facet and its processors.
      if ($query_is_cacheable) {
        if ($query->hasTag('alter_cache_metadata')) {
          $facet_source = $facet->getFacetSource();
          if ($facet_source instanceof SearchApiDisplay) {
            // Avoid a loop when saving a view. The Search API cache plugin for
            // views "preExecutes" a query to collect cache metadata from
            // modules that alter this query. Our SearchApiDisplay must not ask
            // the view for its cache metadata at this point which is in a
            // random state.
            $facet_source->setDisplayEditInProgress(TRUE);
          }
        }

        $query->addCacheableDependency($facet);
      }
    }
  }

  /**
   * Returns enabled facets for the searcher associated with this FacetManager.
   *
   * @return \Drupal\facets\FacetInterface[]
   *   An array of enabled facets.
   */
  public function getEnabledFacets() {
    return $this->facetStorage->loadMultiple();
  }

  /**
   * Returns currently rendered facets filtered by facetsource ID.
   *
   * @param string $facetsource_id
   *   The facetsource ID to filter by.
   *
   * @return \Drupal\facets\FacetInterface[]
   *   An array of enabled facets.
   *
   * @throws \Drupal\facets\Exception\InvalidProcessorException
   */
  public function getFacetsByFacetSourceId($facetsource_id) {
    // Immediately initialize the facets.
    $this->initFacets();
    $facets = [];
    foreach ($this->facets as $facet) {
      if ($facet->getFacetSourceId() === $facetsource_id) {
        $facets[$facet->id()] = $facet;
      }
    }
    return $facets;
  }

  /**
   * Initializes facet builds, sets the breadcrumb trail.
   *
   * Facets are built via FacetsFacetProcessor objects. Facets only need to be
   * processed, or built, once the FacetsFacetManager::processed semaphore is
   * set when this method is called ensuring that facets are built only once
   * regardless of how many times this method is called.
   *
   * @param string|null $facetsource_id
   *   The facetsource if of the currently processed facet.
   *
   * @throws \Drupal\facets\Exception\InvalidProcessorException
   *   Thrown when one of the defined processors is invalid.
   */
  public function processFacets($facetsource_id = NULL) {
    if ($facetsource_id === NULL) {
      foreach ($this->facets as $facet) {
        $current_facetsource_id = $facet->getFacetSourceId();
        $this->processFacets($current_facetsource_id);
      }
    }

    $unprocessedFacets = array_filter($this->facets, function ($item) use ($facetsource_id) {
      return $item->getFacetSourceId() === $facetsource_id && !isset($this->processedFacets[$facetsource_id][$item->id()]);
    });

    // All facets were already processed on a previous run, so no need to do so
    // again.
    if (count($unprocessedFacets) === 0) {
      return;
    }

    $this->updateResults($facetsource_id);

    foreach ($unprocessedFacets as $facet) {
      $processor_configs = $facet->getProcessorConfigs();
      foreach ($facet->getProcessorsByStage(ProcessorInterface::STAGE_POST_QUERY) as $processor) {
        $processor_config = $processor_configs[$processor->getPluginDefinition()['id']]['settings'];
        $processor_config['facet'] = $facet;
        /** @var \Drupal\facets\Processor\PostQueryProcessorInterface $post_query_processor */
        $post_query_processor = $this->processorPluginManager->createInstance($processor->getPluginDefinition()['id'], $processor_config);
        if (!$post_query_processor instanceof PostQueryProcessorInterface) {
          throw new InvalidProcessorException("The processor {$processor->getPluginDefinition()['id']} has a post_query definition but doesn't implement the required PostQueryProcessor interface");
        }
        $post_query_processor->postQuery($facet);
      }

      $this->processedFacets[$facetsource_id][$facet->id()] = $facet;
    }
  }

  /**
   * Initializes enabled facets.
   *
   * In this method all pre-query processors get called and their contents are
   * executed.
   *
   * @throws \Drupal\facets\Exception\InvalidProcessorException
   *   Thrown if one of the pre query processors is invalid.
   */
  protected function initFacets() {
    if (count($this->facets) > 0) {
      return;
    }

    $this->facets = $this->getEnabledFacets();
    foreach ($this->facets as $facet) {
      foreach ($facet->getProcessorsByStage(ProcessorInterface::STAGE_PRE_QUERY) as $processor) {
        /** @var \Drupal\facets\Processor\PreQueryProcessorInterface $pre_query_processor */
        $pre_query_processor = $facet->getProcessors()[$processor->getPluginDefinition()['id']];
        if (!$pre_query_processor instanceof PreQueryProcessorInterface) {
          throw new InvalidProcessorException("The processor {$processor->getPluginDefinition()['id']} has a pre_query definition but doesn't implement the required PreQueryProcessorInterface interface");
        }
        $pre_query_processor->preQuery($facet);
      }
    }
  }

  /**
   * Builds a facet.
   *
   * This method delegates to the relevant plugins in Build stage, the
   * processors that implement the  BuildProcessorInterface enabled on this
   * facet will run.
   *
   * @param \Drupal\facets\FacetInterface $facet
   *   The facet we should build.
   *
   * @return \Drupal\facets\FacetInterface
   *   The built Facet.
   *
   * @throws \Drupal\facets\Exception\InvalidProcessorException
   *   Throws an exception when an invalid processor is linked to the facet.
   */
  protected function processBuild(FacetInterface $facet) {
    if (!isset($this->builtFacets[$facet->getFacetSourceId()][$facet->id()])) {
      // Immediately initialize the facets if they are not initiated yet.
      $this->initFacets();

      // It might be that the facet received here, is not the same as the
      // already loaded facets in the FacetManager.
      // For that reason, get the facet from the already loaded facets in the
      // static cache.
      $facet = $this->facets[$facet->id()];

      // For clarity, process facets is called each build.
      // The first facet therefor will trigger the processing. Note that
      // processing is done only once, so repeatedly calling this method will
      // not trigger the processing more than once.
      $this->processFacets($facet->getFacetSourceId());

      // Get the current results from the facets and let all processors that
      // trigger on the build step do their build processing.
      // @see \Drupal\facets\Processor\BuildProcessorInterface.
      // @see \Drupal\facets\Processor\SortProcessorInterface.
      $results = $facet->getResults();

      foreach ($facet->getProcessorsByStage(ProcessorInterface::STAGE_BUILD) as $processor) {
        if (!$processor instanceof BuildProcessorInterface) {
          throw new InvalidProcessorException("The processor {$processor->getPluginDefinition()['id']} has a build definition but doesn't implement the required BuildProcessorInterface interface");
        }
        $results = $processor->build($facet, $results);
      }

      // Trigger sort stage.
      $active_sort_processors = [];
      foreach ($facet->getProcessorsByStage(ProcessorInterface::STAGE_SORT) as $processor) {
        $active_sort_processors[] = $processor;
      }

      // Sort the actual results if we have enabled sort processors.
      if (!empty($active_sort_processors)) {
        $results = $this->sortFacetResults($active_sort_processors, $results);
      }

      $facet->setResults($results);

      $eventDispatcher = \Drupal::service('event_dispatcher');
      $event = new PostBuildFacet($facet);
      $eventDispatcher->dispatch($event);

      $this->builtFacets[$facet->getFacetSourceId()][$facet->id()] = $event->getFacet();
    }

    return $this->builtFacets[$facet->getFacetSourceId()][$facet->id()];
  }

  /**
   * Builds a facet and returns it as a renderable array.
   *
   * This method delegates to the relevant plugins to render a facet, it calls
   * out to a widget plugin to do the actual rendering when results are found.
   * When no results are found it calls out to the correct empty result plugin
   * to build a render array. Renderable array will include all facet plugins
   * cache metadata that were used to build this facet.
   *
   * Before doing any rendering, the processors that implement the
   * BuildProcessorInterface enabled on this facet will run.
   *
   * @param \Drupal\facets\FacetInterface $facet
   *   The facet we should build.
   *
   * @return array
   *   Facet render arrays.
   *
   * @throws \Drupal\facets\Exception\InvalidProcessorException
   *   Throws an exception when an invalid processor is linked to the facet.
   */
  public function build(FacetInterface $facet) {
    if ($facet->getOnlyVisibleWhenFacetSourceIsVisible()) {
      // Block rendering and processing should be stopped when the facet source
      // is not available on the page. Returning an empty array here is enough
      // to halt all further processing.
      $facet_source = $facet->getFacetSource();
      if (is_null($facet_source) || !$facet_source->isRenderedInCurrentRequest()) {
        $build = [];
        $cacheableMetadata = new CacheableMetadata();
        $cacheableMetadata->addCacheableDependency($facet_source);
        $cacheableMetadata->applyTo($build);
        return $build;
      }
    }

    $built_facet = $this->processBuild($facet);
    // The build process might have returned a previously built and statically
    // cached instance of the facet object. So we need to ensure that the cache
    // metadata is updated on the outer object, too.
    $facet->addCacheableDependency($built_facet);

    // We include this build even if empty, it may contain attached libraries.
    /** @var \Drupal\facets\Widget\WidgetPluginInterface $widget */
    $widget = $built_facet->getWidgetInstance();
    $build = $widget->build($built_facet);
    // No results behavior handling. Return a custom text or false depending on
    // settings.
    if (empty($built_facet->getResults())) {
      $empty_behavior = $built_facet->getEmptyBehavior();
      switch ($empty_behavior['behavior'] ?? '') {
        case 'text':
          // @codingStandardsIgnoreStart
          $text = $this->t($empty_behavior['text'] ?? '');
          // @codingStandardsIgnoreEnd
          return [
            [
              0 => $build,
              '#type' => 'container',
              '#attributes' => [
                'data-drupal-facet-id' => $built_facet->id(),
                'class' => ['facet-empty'],
              ],
              'empty_text' => [
                '#type' => 'processed_text',
                '#text' => $text,
                '#format' => $empty_behavior['text_format'] ?? 'plain_text',
              ],
            ],
          ];

        case 'none':
          return [];

        case 'empty':
        default:
          return [$build];
      }
    }

    return [$build];
  }

  /**
   * Updates all facets of a given facet source with the raw results.
   *
   * @param string $facetsource_id
   *   The facet source ID of the currently processed facet.
   *
   * @throws \Drupal\facets\Exception\InvalidProcessorException
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  public function updateResults($facetsource_id) {
    $facets = $this->getFacetsByFacetSourceId($facetsource_id);
    if ($facets) {
      // Clear the caches of processed results.
      unset($this->processedFacets[$facetsource_id]);
      unset($this->builtFacets[$facetsource_id]);

      /** @var \Drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source_plugin */
      $facet_source_plugin = $this->facetSourcePluginManager->createInstance($facetsource_id);
      $facet_source_plugin->fillFacetsWithResults($facets);
    }
  }

  /**
   * Returns one of the processed facets.
   *
   * Returns one of the processed facets, this is a facet with filled results.
   * Keep in mind that if you want to have the facet's build processor executed,
   * call returnBuiltFacet() instead.
   *
   * @param \Drupal\facets\FacetInterface $facet
   *   The facet to process with a collected plugins cache metadata.
   *
   * @return \Drupal\facets\FacetInterface|null
   *   The updated facet if it exists, NULL otherwise.
   *
   * @throws \Drupal\facets\Exception\InvalidProcessorException
   */
  public function returnProcessedFacet(FacetInterface $facet) {
    $this->processFacets($facet->getFacetSourceId());
    return !empty($this->facets[$facet->id()]) ? $this->facets[$facet->id()] : NULL;
  }

  /**
   * Returns one of the built facets.
   *
   * @param \Drupal\facets\FacetInterface $facet
   *   The facet to process.
   *
   * @return \Drupal\facets\FacetInterface
   *   The built Facet object with a collected plugins cache metadata.
   */
  public function returnBuiltFacet(FacetInterface $facet) {
    return $this->processBuild($facet);
  }

  /**
   * Sort the facet results, and recurse to children to do the same.
   *
   * @param \Drupal\facets\Processor\SortProcessorInterface[] $active_sort_processors
   *   An array of sort processors.
   * @param \Drupal\facets\Result\ResultInterface[] $results
   *   An array of results.
   *
   * @return \Drupal\facets\Result\ResultInterface[]
   *   A sorted array of results.
   */
  protected function sortFacetResults(array $active_sort_processors, array $results) {
    uasort($results, function ($a, $b) use ($active_sort_processors) {
      $return = 0;
      foreach ($active_sort_processors as $sort_processor) {
        if ($return = $sort_processor->sortResults($a, $b)) {
          if ($sort_processor->getConfiguration()['sort'] == 'DESC') {
            $return *= -1;
          }
          break;
        }
      }
      return $return;
    });

    // Loop over the results and see if they have any children, if they do, fire
    // a request to this same method again with the children.
    foreach ($results as &$result) {
      if (!empty($result->getChildren())) {
        $children = $this->sortFacetResults($active_sort_processors, $result->getChildren());
        $result->setChildren($children);
      }
    }

    return $results;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc