View source
<?php
namespace Drupal\search_api\Query;
use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\search_api\Display\DisplayPluginManagerInterface;
use Drupal\search_api\Event\QueryPreExecuteEvent;
use Drupal\search_api\Event\ProcessingResultsEvent;
use Drupal\search_api\Event\SearchApiEvents;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\ParseMode\ParseModeInterface;
use Drupal\search_api\ParseMode\ParseModePluginManager;
use Drupal\search_api\SearchApiException;
use Drupal\search_api\Utility\QueryHelperInterface;
class Query implements QueryInterface, RefinableCacheableDependencyInterface {
use StringTranslationTrait;
use RefinableCacheableDependencyTrait;
use DependencySerializationTrait {
__sleep as traitSleep;
__wakeup as traitWakeup;
}
protected $index;
protected $indexId;
protected $results;
protected $searchId;
protected $parseMode;
protected $processingLevel = self::PROCESSING_FULL;
protected $languages;
protected $keys;
protected $origKeys;
protected $fields;
protected $conditionGroup;
protected $sorts = [];
protected $aborted;
protected $options;
protected $tags = [];
protected $preExecuteRan = FALSE;
protected $executed = FALSE;
protected $moduleHandler;
protected $eventDispatcher;
protected $parseModeManager;
protected $displayPluginManager;
protected $queryHelper;
protected $originalQuery;
public function __construct(IndexInterface $index, array $options = []) {
if (!$index
->status()) {
$index_label = $index
->label();
throw new SearchApiException("Can't search on index '{$index_label}' which is disabled.");
}
$this->index = $index;
$this->results = new ResultSet($this);
$this->options = $options;
$this->conditionGroup = $this
->createConditionGroup('AND');
}
public static function create(IndexInterface $index, array $options = []) {
return new static($index, $options);
}
public function getModuleHandler() {
return $this->moduleHandler ?: \Drupal::moduleHandler();
}
public function setModuleHandler(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
return $this;
}
public function getEventDispatcher() {
return $this->eventDispatcher ?: \Drupal::service('event_dispatcher');
}
public function setEventDispatcher(ContainerAwareEventDispatcher $event_dispatcher) {
$this->eventDispatcher = $event_dispatcher;
return $this;
}
public function getParseModeManager() {
return $this->parseModeManager ?: \Drupal::service('plugin.manager.search_api.parse_mode');
}
public function setParseModeManager(ParseModePluginManager $parse_mode_manager) {
$this->parseModeManager = $parse_mode_manager;
return $this;
}
public function getDisplayPluginManager() {
return $this->displayPluginManager ?: \Drupal::service('plugin.manager.search_api.display');
}
public function setDisplayPluginManager(DisplayPluginManagerInterface $display_plugin_manager) {
$this->displayPluginManager = $display_plugin_manager;
return $this;
}
public function getQueryHelper() {
return $this->queryHelper ?: \Drupal::service('search_api.query_helper');
}
public function setQueryHelper(QueryHelperInterface $query_helper) {
$this->queryHelper = $query_helper;
return $this;
}
public function getSearchId($generate = TRUE) {
if ($generate && !isset($this->searchId)) {
static $num = 0;
$this->searchId = 'search_' . ++$num;
}
return $this->searchId;
}
public function setSearchId($search_id) {
$this->searchId = $search_id;
return $this;
}
public function getDisplayPlugin() {
$display_manager = $this
->getDisplayPluginManager();
if (isset($this->searchId) && $display_manager
->hasDefinition($this->searchId)) {
return $display_manager
->createInstance($this->searchId);
}
return NULL;
}
public function getParseMode() {
if (!$this->parseMode) {
$this->parseMode = $this
->getParseModeManager()
->createInstance('terms');
}
return $this->parseMode;
}
public function setParseMode(ParseModeInterface $parse_mode) {
$this->parseMode = $parse_mode;
if (is_scalar($this->origKeys)) {
$this->keys = $parse_mode
->parseInput($this->origKeys);
}
return $this;
}
public function getLanguages() {
return $this->languages;
}
public function setLanguages(array $languages = NULL) {
$this->languages = $languages !== NULL ? array_values($languages) : NULL;
return $this;
}
public function createConditionGroup($conjunction = 'AND', array $tags = []) {
return new ConditionGroup($conjunction, $tags);
}
public function keys($keys = NULL) {
$this->origKeys = $keys;
if (is_scalar($keys)) {
$this->keys = $this
->getParseMode()
->parseInput("{$keys}");
}
else {
$this->keys = $keys;
}
return $this;
}
public function setFulltextFields(array $fields = NULL) {
$this->fields = $fields;
return $this;
}
public function addConditionGroup(ConditionGroupInterface $condition_group) {
$this->conditionGroup
->addConditionGroup($condition_group);
return $this;
}
public function addCondition($field, $value, $operator = '=') {
$this->conditionGroup
->addCondition($field, $value, $operator);
return $this;
}
public function sort($field, $order = self::SORT_ASC) {
$order = strtoupper(trim($order));
$order = $order == self::SORT_DESC ? self::SORT_DESC : self::SORT_ASC;
if (!isset($this->sorts[$field])) {
$this->sorts[$field] = $order;
}
return $this;
}
public function range($offset = NULL, $limit = NULL) {
$this->options['offset'] = $offset;
$this->options['limit'] = $limit;
return $this;
}
public function getProcessingLevel() {
return $this->processingLevel;
}
public function setProcessingLevel($level) {
$this->processingLevel = $level;
return $this;
}
public function abort($error_message = NULL) {
$this->aborted = $error_message ?? TRUE;
}
public function wasAborted() {
return $this->aborted !== NULL;
}
public function getAbortMessage() {
return !is_bool($this->aborted) ? $this->aborted : NULL;
}
public function execute() {
if ($this
->hasExecuted()) {
return $this->results;
}
$this->executed = TRUE;
if ($this
->shouldAbort()) {
return $this->results;
}
$this
->preExecute();
if ($this
->shouldAbort()) {
return $this->results;
}
$this->index
->getServerInstance()
->search($this);
$this
->postExecute();
return $this->results;
}
protected function shouldAbort() {
if (!$this
->wasAborted() && $this->languages !== []) {
return FALSE;
}
if (!$this->originalQuery) {
$this->originalQuery = clone $this;
}
$this
->postExecute();
return TRUE;
}
public function preExecute() {
if (!$this->preExecuteRan) {
$this->originalQuery = clone $this;
$this->originalQuery->executed = FALSE;
$this->preExecuteRan = TRUE;
if ($this->processingLevel == self::PROCESSING_NONE) {
return;
}
$this->index
->preprocessSearchQuery($this);
$event_base_name = SearchApiEvents::QUERY_PRE_EXECUTE;
$event = new QueryPreExecuteEvent($this);
$this
->getEventDispatcher()
->dispatch($event_base_name, $event);
$hooks = [
'search_api_query',
];
foreach ($this->tags as $tag) {
$hooks[] = "search_api_query_{$tag}";
$event_name = "{$event_base_name}.{$tag}";
$event = new QueryPreExecuteEvent($this);
$this
->getEventDispatcher()
->dispatch($event_name, $event);
}
$description = 'This hook is deprecated in search_api:8.x-1.14 and is removed from search_api:2.0.0. Please use the "search_api.query_pre_execute" event instead. See https://www.drupal.org/node/3059866';
$this
->getModuleHandler()
->alterDeprecated($description, $hooks, $this);
}
}
public function postExecute() {
if ($this->processingLevel == self::PROCESSING_NONE) {
return;
}
$this->index
->postprocessSearchResults($this->results);
$event_base_name = SearchApiEvents::PROCESSING_RESULTS;
$event = new ProcessingResultsEvent($this->results);
$this
->getEventDispatcher()
->dispatch($event_base_name, $event);
$this->results = $event
->getResults();
$hooks = [
'search_api_results',
];
foreach ($this->tags as $tag) {
$hooks[] = "search_api_results_{$tag}";
$event = new ProcessingResultsEvent($this->results);
$this
->getEventDispatcher()
->dispatch("{$event_base_name}.{$tag}", $event);
$this->results = $event
->getResults();
}
$description = 'This hook is deprecated in search_api:8.x-1.14 and is removed from search_api:2.0.0. Please use the "search_api.processing_results" event instead. See https://www.drupal.org/node/3059866';
$this
->getModuleHandler()
->alterDeprecated($description, $hooks, $this->results);
$this
->getQueryHelper()
->addResults($this->results);
}
public function hasExecuted() {
return $this->executed;
}
public function getResults() {
return $this->results;
}
public function getIndex() {
return $this->index;
}
public function &getKeys() {
return $this->keys;
}
public function getOriginalKeys() {
return $this->origKeys;
}
public function &getFulltextFields() {
return $this->fields;
}
public function getConditionGroup() {
return $this->conditionGroup;
}
public function &getSorts() {
return $this->sorts;
}
public function getOption($name, $default = NULL) {
return array_key_exists($name, $this->options) ? $this->options[$name] : $default;
}
public function setOption($name, $value) {
$old = $this
->getOption($name);
$this->options[$name] = $value;
return $old;
}
public function &getOptions() {
return $this->options;
}
public function addTag($tag) {
$this->tags[$tag] = $tag;
return $this;
}
public function hasTag($tag) {
return isset($this->tags[$tag]);
}
public function hasAllTags() {
return !array_diff_key(array_flip(func_get_args()), $this->tags);
}
public function hasAnyTag() {
return (bool) array_intersect_key(array_flip(func_get_args()), $this->tags);
}
public function &getTags() {
return $this->tags;
}
public function getOriginalQuery() {
return $this->originalQuery ?: clone $this;
}
public function getCacheContexts() {
$contexts = $this->cacheContexts;
foreach ($this
->getIndex()
->getDatasources() as $datasource) {
$contexts = Cache::mergeContexts($datasource
->getListCacheContexts(), $contexts);
}
return $contexts;
}
public function getCacheTags() {
$tags = $this->cacheTags;
$index_tags = $this
->getIndex()
->getCacheTagsToInvalidate();
$tags = Cache::mergeTags($index_tags, $tags);
return $tags;
}
public function getCacheMaxAge() {
return $this->cacheMaxAge;
}
public function __clone() {
$this->results = $this
->getResults()
->getCloneForQuery($this);
$this->conditionGroup = clone $this->conditionGroup;
if ($this->originalQuery) {
$this->originalQuery = clone $this->originalQuery;
}
if ($this->parseMode) {
$this->parseMode = clone $this->parseMode;
}
}
public function __sleep() {
$this->indexId = $this->index
->id();
$keys = $this
->traitSleep();
return array_diff($keys, [
'index',
]);
}
public function __wakeup() {
if (!isset($this->index) && !empty($this->indexId) && \Drupal::hasContainer() && \Drupal::getContainer()
->has('entity_type.manager')) {
$this->index = \Drupal::entityTypeManager()
->getStorage('search_api_index')
->load($this->indexId);
$this->indexId = NULL;
}
$this
->traitWakeup();
}
public function __toString() {
$ret = 'Index: ' . $this->index
->id() . "\n";
$ret .= 'Keys: ' . str_replace("\n", "\n ", var_export($this->origKeys, TRUE)) . "\n";
if (isset($this->keys)) {
$ret .= 'Parsed keys: ' . str_replace("\n", "\n ", var_export($this->keys, TRUE)) . "\n";
$ret .= 'Searched fields: ' . (isset($this->fields) ? implode(', ', $this->fields) : '[ALL]') . "\n";
}
if (isset($this->languages)) {
$ret .= 'Searched languages: ' . implode(', ', $this->languages) . "\n";
}
if ($conditions = (string) $this->conditionGroup) {
$conditions = str_replace("\n", "\n ", $conditions);
$ret .= "Conditions:\n {$conditions}\n";
}
if ($this->sorts) {
$sorts = [];
foreach ($this->sorts as $field => $order) {
$sorts[] = "{$field} {$order}";
}
$ret .= 'Sorting: ' . implode(', ', $sorts) . "\n";
}
$options = $this
->sanitizeOptions($this->options);
$options = str_replace("\n", "\n ", var_export($options, TRUE));
$ret .= 'Options: ' . $options . "\n";
return $ret;
}
protected function sanitizeOptions(array $options) {
foreach ($options as $key => $value) {
if (is_object($value)) {
$options[$key] = 'object (' . get_class($value) . ')';
}
elseif (is_array($value)) {
$options[$key] = $this
->sanitizeOptions($value);
}
}
return $options;
}
}