| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013 | <?php/** * @file * Contains SearchApiQueryInterface and SearchApiQuery. *//** * Interface representing a search query on an Search API index. * * Methods not returning something else will return the object itself, so calls * can be chained. */interface SearchApiQueryInterface {  /**   * Constructs a new search query.   *   * @param SearchApiIndex $index   *   The index the query should be executed on.   * @param array $options   *   Associative array of options configuring this query. Recognized options   *   are:   *   - conjunction: The type of conjunction to use for this query - either   *     'AND' or 'OR'. 'AND' by default. This only influences the search keys,   *     filters will always use AND by default.   *   - 'parse mode': The mode with which to parse the $keys variable, if it   *     is set and not already an array. See SearchApiQuery::parseModes() for   *     recognized parse modes.   *   - languages: The languages to search for, as an array of language IDs.   *     If not specified, all languages will be searched. Language-neutral   *     content (LANGUAGE_NONE) is always searched.   *   - offset: The position of the first returned search results relative to   *     the whole result in the index.   *   - limit: The maximum number of search results to return. -1 means no   *     limit.   *   - 'filter class': Can be used to change the SearchApiQueryFilterInterface   *     implementation to use.   *   - 'search id': A string that will be used as the identifier when storing   *     this search in the Search API's static cache.   *   - 'skip result count': If present and set to TRUE, the result's   *     "result count" key will not be needed. Service classes can check for   *     this option to possibly avoid executing expensive operations to compute   *     the result count in cases where it is not needed.   *   - search_api_access_account: The account which will be used for entity   *     access checks, if available and enabled for the index.   *   - search_api_bypass_access: If set to TRUE, entity access checks will be   *     skipped, even if enabled for the index.   *   All options are optional. Third-party modules might define and use other   *   options not listed here.   *   * @throws SearchApiException   *   If a search on that index (or with those options) won't be possible.   */  public function __construct(SearchApiIndex $index, array $options = array());  /**   * Retrieves the parse modes supported by this query class.   *   * @return array   *   An associative array of parse modes recognized by objects of this class.   *   The keys are the parse modes' ids, values are associative arrays   *   containing the following entries:   *   - name: The translated name of the parse mode.   *   - description: (optional) A translated text describing the parse mode.   */  public function parseModes();  /**   * Creates a new filter to use with this query object.   *   * @param string $conjunction   *   The conjunction to use for the filter - either 'AND' or 'OR'.   * @param $tags   *   (Optional) An arbitrary set of tags. Can be used to identify this filter   *   down the line if necessary. This is primarily used by the facet system   *   to support OR facet queries.   *   * @return SearchApiQueryFilterInterface   *   A filter object that is set to use the specified conjunction.   */  public function createFilter($conjunction = 'AND', $tags = array());  /**   * Sets the keys to search for.   *   * If this method is not called on the query before execution, this will be a   * filter-only query.   *   * @param array|string|null $keys   *   A string with the unparsed search keys, or NULL to use no search keys.   *   * @return SearchApiQueryInterface   *   The called object.   */  public function keys($keys = NULL);  /**   * Sets the fields that will be searched for the search keys.   *   * If this is not called, all fulltext fields will be searched.   *   * @param array $fields   *   An array containing fulltext fields that should be searched.   *   * @return SearchApiQueryInterface   *   The called object.   *   * @throws SearchApiException   *   If one of the fields isn't of type "text".   */  // @todo Allow calling with NULL.  public function fields(array $fields);  /**   * Adds a subfilter to this query's filter.   *   * @param SearchApiQueryFilterInterface $filter   *   A SearchApiQueryFilter object that should be added as a subfilter.   *   * @return SearchApiQueryInterface   *   The called object.   */  public function filter(SearchApiQueryFilterInterface $filter);  /**   * Adds a new ($field $operator $value) condition filter.   *   * @param string $field   *   The field to filter on, e.g. 'title'.   * @param mixed $value   *   The value the field should have (or be related to by the operator).   * @param string $operator   *   The operator to use for checking the constraint. The following operators   *   are supported for primitive types: "=", "<>", "<", "<=", ">=", ">". They   *   have the same semantics as the corresponding SQL operators.   *   If $field is a fulltext field, $operator can only be "=" or "<>", which   *   are in this case interpreted as "contains" or "doesn't contain",   *   respectively.   *   If $value is NULL, $operator also can only be "=" or "<>", meaning the   *   field must have no or some value, respectively.   *   * @return SearchApiQueryInterface   *   The called object.   */  public function condition($field, $value, $operator = '=');  /**   * Adds a sort directive to this search query.   *   * If no sort is manually set, the results will be sorted descending by   * relevance.   *   * @param string $field   *   The field to sort by. The special fields 'search_api_relevance' (sort by   *   relevance) and 'search_api_id' (sort by item id) may be used.   * @param string $order   *   The order to sort items in - either 'ASC' or 'DESC'.   *   * @return SearchApiQueryInterface   *   The called object.   *   * @throws SearchApiException   *   If the field is multi-valued or of a fulltext type.   */  public function sort($field, $order = 'ASC');  /**   * Adds a range of results to return.   *   * This will be saved in the query's options. If called without parameters,   * this will remove all range restrictions previously set.   *   * @param int|null $offset   *   The zero-based offset of the first result returned.   * @param int|null $limit   *   The number of results to return.   *   * @return SearchApiQueryInterface   *   The called object.   */  public function range($offset = NULL, $limit = NULL);  /**   * Executes this search query.   *   * @return array   *   An associative array containing the search results. The following keys   *   are standardized:   *   - 'result count': The overall number of results for this query, without   *     range restrictions. Might be approximated, for large numbers, or   *     skipped entirely if the "skip result count" option was set on this   *     query.   *   - results: An array of results, ordered as specified. The array keys are   *     the items' IDs, values are arrays containing the following keys:   *     - id: The item's ID.   *     - score: A float measuring how well the item fits the search.   *     - fields: (optional) If set, an array containing some field values   *       already ready-to-use. This allows search engines (or postprocessors)   *       to store extracted fields so other modules don't have to extract them   *       again. This fields should always be checked by modules that want to   *       use field contents of the result items.   *     - entity: (optional) If set, the fully loaded result item. This field   *       should always be used by modules using search results, to avoid   *       duplicate item loads.   *     - excerpt: (optional) If set, an HTML text containing highlighted   *       portions of the fulltext that match the query.   *   - warnings: A numeric array of translated warning messages that may be   *     displayed to the user.   *   - ignored: A numeric array of search keys that were ignored for this   *     search (e.g., because of being too short or stop words).   *   - performance: An associative array with the time taken (as floats, in   *     seconds) for specific parts of the search execution:   *     - complete: The complete runtime of the query.   *     - hooks: Hook invocations and other client-side preprocessing.   *     - preprocessing: Preprocessing of the service class.   *     - execution: The actual query to the search server, in whatever form.   *     - postprocessing: Preparing the results for returning.   *   Additional metadata may be returned in other keys. Only 'result count'   *   and 'result' always have to be set, all other entries are optional.   */  public function execute();  /**   * Prepares the query object for the search.   *   * This method should always be called by execute() and contain all necessary   * operations before the query is passed to the server's search() method.   */  public function preExecute();  /**   * Postprocesses the search results before they are returned.   *   * This method should always be called by execute() and contain all necessary   * operations after the results are returned from the server.   *   * @param array $results   *   The results returned by the server, which may be altered. The data   *   structure is the same as returned by execute().   */  public function postExecute(array &$results);  /**   * Retrieves the index associated with this search.   *   * @return SearchApiIndex   *   The search index this query should be executed on.   */  public function getIndex();  /**   * Retrieves the search keys for this query.   *   * @return array|string|null   *   This object's search keys - either a string or an array specifying a   *   complex search expression.   *   An array will contain a '#conjunction' key specifying the conjunction   *   type, and search strings or nested expression arrays at numeric keys.   *   Additionally, a '#negation' key might be present, which means – unless it   *   maps to a FALSE value – that the search keys contained in that array   *   should be negated, i.e. not be present in returned results. The negation   *   works on the whole array, not on each contained term individually – i.e.,   *   with the "AND" conjunction and negation, only results that contain all   *   the terms in the array should be excluded; with the "OR" conjunction and   *   negation, all results containing one or more of the terms in the array   *   should be excluded.   *   * @see keys()   */  public function &getKeys();  /**   * Retrieves the unparsed search keys for this query as originally entered.   *   * @return array|string|null   *   The unprocessed search keys, exactly as passed to this object. Has the   *   same format as the return value of getKeys().   *   * @see keys()   */  public function getOriginalKeys();  /**   * Retrieves the fulltext fields that will be searched for the search keys.   *   * @return array   *   An array containing the fields that should be searched for the search   *   keys.   *   * @see fields()   */  public function &getFields();  /**   * Retrieves the filter object associated with this search query.   *   * @return SearchApiQueryFilterInterface   *   This object's associated filter object.   */  public function getFilter();  /**   * Retrieves the sorts set for this query.   *   * @return array   *   An array specifying the sort order for this query. Array keys are the   *   field names in order of importance, the values are the respective order   *   in which to sort the results according to the field.   *   * @see sort()   */  public function &getSort();  /**   * Retrieves an option set on this search query.   *   * @param string $name   *   The name of an option.   * @param mixed $default   *   The value to return if the specified option is not set.   *   * @return mixed   *   The value of the option with the specified name, if set. NULL otherwise.   */  public function getOption($name, $default = NULL);  /**   * Sets an option for this search query.   *   * @param string $name   *   The name of an option.   * @param mixed $value   *   The new value of the option.   *   * @return mixed   *   The option's previous value.   */  public function setOption($name, $value);  /**   * Retrieves all options set for this search query.   *   * The return value is a reference to the options so they can also be altered   * this way.   *   * @return array   *   An associative array of query options.   */  public function &getOptions();}/** * Provides a standard implementation of the SearchApiQueryInterface. */class SearchApiQuery implements SearchApiQueryInterface {  /**   * The index this query will use.   *   * @var SearchApiIndex   */  protected $index;  /**   * The index's machine name.   *   * used during serialization to avoid serializing the whole index object.   *   * @var string   */  protected $index_id;  /**   * The search keys. If NULL, this will be a filter-only search.   *   * @var mixed   */  protected $keys;  /**   * The unprocessed search keys, as passed to the keys() method.   *   * @var mixed   */  protected $orig_keys;  /**   * The fields that will be searched for the keys.   *   * @var array   */  protected $fields;  /**   * The search filter associated with this query.   *   * @var SearchApiQueryFilterInterface   */  protected $filter;  /**   * The sort associated with this query.   *   * @var array   */  protected $sort;  /**   * Search options configuring this query.   *   * @var array   */  protected $options;  /**   * Flag for whether preExecute() was already called for this query.   *   * @var bool   */  protected $pre_execute = FALSE;  /**   * {@inheritdoc}   */  public function __construct(SearchApiIndex $index, array $options = array()) {    if (empty($index->options['fields'])) {      throw new SearchApiException(t("Can't search an index which hasn't got any fields defined."));    }    if (empty($index->enabled)) {      throw new SearchApiException(t("Can't search a disabled index."));    }    if (isset($options['parse mode'])) {      $modes = $this->parseModes();      if (!isset($modes[$options['parse mode']])) {        throw new SearchApiException(t('Unknown parse mode: @mode.', array('@mode' => $options['parse mode'])));      }    }    $this->index = $index;    $this->options = $options + array(      'conjunction' => 'AND',      'parse mode' => 'terms',      'filter class' => 'SearchApiQueryFilter',      'search id' => __CLASS__,    );    $this->filter = $this->createFilter('AND');    $this->sort = array();  }  /**   * {@inheritdoc}   */  public function parseModes() {    $modes['direct'] = array(      'name' => t('Direct query'),      'description' => t("Don't parse the query, just hand it to the search server unaltered. " .          "Might fail if the query contains syntax errors in regard to the specific server's query syntax."),    );    $modes['single'] = array(      'name' => t('Single term'),      'description' => t('The query is interpreted as a single keyword, maybe containing spaces or special characters.'),    );    $modes['terms'] = array(      'name' => t('Multiple terms'),      'description' => t('The query is interpreted as multiple keywords seperated by spaces. ' .          'Keywords containing spaces may be "quoted". Quoted keywords must still be seperated by spaces.'),    );    // @todo Add fourth mode for complicated expressions, e.g.: »"vanilla ice" OR (love NOT hate)«    return $modes;  }  /**   * {@inheritdoc}   */  protected function parseKeys($keys, $mode) {    if ($keys === NULL || is_array($keys)) {      return $keys;    }    $keys = '' . $keys;    switch ($mode) {      case 'direct':        return $keys;      case 'single':        return array('#conjunction' => $this->options['conjunction'], $keys);      case 'terms':        $ret = preg_split('/\s+/u', $keys);        $quoted = FALSE;        $str = '';        foreach ($ret as $k => $v) {          if (!$v) {            continue;          }          if ($quoted) {            if (substr($v, -1) == '"') {              $v = substr($v, 0, -1);              $str .= ' ' . $v;              $ret[$k] = $str;              $quoted = FALSE;            }            else {              $str .= ' ' . $v;              unset($ret[$k]);            }          }          elseif ($v[0] == '"') {            $len = strlen($v);            if ($len > 1 && $v[$len-1] == '"') {              $ret[$k] = substr($v, 1, -1);            }            else {              $str = substr($v, 1);              $quoted = TRUE;              unset($ret[$k]);            }          }        }        if ($quoted) {          $ret[] = $str;        }        $ret['#conjunction'] = $this->options['conjunction'];        return array_filter($ret);    }  }  /**   * {@inheritdoc}   */  public function createFilter($conjunction = 'AND', $tags = array()) {    $filter_class = $this->options['filter class'];    return new $filter_class($conjunction, $tags);  }  /**   * {@inheritdoc}   */  public function keys($keys = NULL) {    $this->orig_keys = $keys;    if (isset($keys)) {      $this->keys = $this->parseKeys($keys, $this->options['parse mode']);    }    else {      $this->keys = NULL;    }    return $this;  }  /**   * {@inheritdoc}   */  public function fields(array $fields) {    $fulltext_fields = $this->index->getFulltextFields();    foreach (array_diff($fields, $fulltext_fields) as $field) {      throw new SearchApiException(t('Trying to search on field @field which is no indexed fulltext field.', array('@field' => $field)));    }    $this->fields = $fields;    return $this;  }  /**   * {@inheritdoc}   */  public function filter(SearchApiQueryFilterInterface $filter) {    $this->filter->filter($filter);    return $this;  }  /**   * {@inheritdoc}   */  public function condition($field, $value, $operator = '=') {    $this->filter->condition($field, $value, $operator);    return $this;  }  /**   * {@inheritdoc}   */  public function sort($field, $order = 'ASC') {    $fields = $this->index->options['fields'];    $fields += array(      'search_api_relevance' => array('type' => 'decimal'),      'search_api_id' => array('type' => 'integer'),    );    if (empty($fields[$field])) {      throw new SearchApiException(t('Trying to sort on unknown field @field.', array('@field' => $field)));    }    $type = $fields[$field]['type'];    if (search_api_is_list_type($type) || search_api_is_text_type($type)) {      throw new SearchApiException(t('Trying to sort on field @field of illegal type @type.', array('@field' => $field, '@type' => $type)));    }    $order = strtoupper(trim($order)) == 'DESC' ? 'DESC' : 'ASC';    $this->sort[$field] = $order;    return $this;  }  /**   * {@inheritdoc}   */  public function range($offset = NULL, $limit = NULL) {    $this->options['offset'] = $offset;    $this->options['limit'] = $limit;    return $this;  }  /**   * {@inheritdoc}   */  public function execute() {    $start = microtime(TRUE);    // Prepare the query for execution by the server.    $this->preExecute();    $pre_search = microtime(TRUE);    // Execute query.    $response = $this->index->server()->search($this);    $post_search = microtime(TRUE);    // Postprocess the search results.    $this->postExecute($response);    $end = microtime(TRUE);    $response['performance']['complete'] = $end - $start;    $response['performance']['hooks'] = $response['performance']['complete'] - ($post_search - $pre_search);    // Store search for later retrieval for facets, etc.    search_api_current_search(NULL, $this, $response);    return $response;  }  /**   * Adds language filters for the query.   *   * Internal helper function.   *   * @param array $languages   *   The languages for which results should be returned.   *   * @throws SearchApiException   *   If there was a logical error in the combination of filters and languages.   */  protected function addLanguages(array $languages) {    if (array_search(LANGUAGE_NONE, $languages) === FALSE) {      $languages[] = LANGUAGE_NONE;    }    $languages = drupal_map_assoc($languages);    $langs_to_add = $languages;    $filters = $this->filter->getFilters();    while ($filters && $langs_to_add) {      $filter = array_shift($filters);      if (is_array($filter)) {        if ($filter[0] == 'search_api_language' && $filter[2] == '=') {          $lang = $filter[1];          if (isset($languages[$lang])) {            unset($langs_to_add[$lang]);          }          else {            throw new SearchApiException(t('Impossible combination of filters and languages. There is a filter for "@language", but allowed languages are: "@languages".', array('@language' => $lang, '@languages' => implode('", "', $languages))));          }        }      }      else {        if ($filter->getConjunction() == 'AND') {          $filters += $filter->getFilters();        }      }    }    if ($langs_to_add) {      if (count($langs_to_add) == 1) {        $this->condition('search_api_language', reset($langs_to_add));      }      else {        $filter = $this->createFilter('OR');        foreach ($langs_to_add as $lang) {          $filter->condition('search_api_language', $lang);        }        $this->filter($filter);      }    }  }  /**   * {@inheritdoc}   */  public function preExecute() {    // Make sure to only execute this once per query.    if (!$this->pre_execute) {      $this->pre_execute = TRUE;      // Add filter for languages.      if (isset($this->options['languages'])) {        $this->addLanguages($this->options['languages']);      }      // Add fulltext fields, unless set      if ($this->fields === NULL) {        $this->fields = $this->index->getFulltextFields();      }      // Preprocess query.      $this->index->preprocessSearchQuery($this);      // Let modules alter the query.      drupal_alter('search_api_query', $this);    }  }  /**   * {@inheritdoc}   */  public function postExecute(array &$results) {    // Postprocess results.    $this->index->postprocessSearchResults($results, $this);  }  /**   * {@inheritdoc}   */  public function getIndex() {    return $this->index;  }  /**   * {@inheritdoc}   */  public function &getKeys() {    return $this->keys;  }  /**   * {@inheritdoc}   */  public function getOriginalKeys() {    return $this->orig_keys;  }  /**   * {@inheritdoc}   */  public function &getFields() {    return $this->fields;  }  /**   * {@inheritdoc}   */  public function getFilter() {    return $this->filter;  }  /**   * {@inheritdoc}   */  public function &getSort() {    return $this->sort;  }  /**   * {@inheritdoc}   */  public function getOption($name, $default = NULL) {    return array_key_exists($name, $this->options) ? $this->options[$name] : $default;  }  /**   * {@inheritdoc}   */  public function setOption($name, $value) {    $old = $this->getOption($name);    $this->options[$name] = $value;    return $old;  }  /**   * {@inheritdoc}   */  public function &getOptions() {    return $this->options;  }  /**   * Implements the magic __sleep() method to avoid serializing the index.   */  public function __sleep() {    $this->index_id = $this->index->machine_name;    $keys = get_object_vars($this);    unset($keys['index']);    return array_keys($keys);  }  /**   * Implements the magic __wakeup() method to reload the query's index.   */  public function __wakeup() {    if (!isset($this->index) && !empty($this->index_id)) {      $this->index = search_api_index_load($this->index_id);      unset($this->index_id);    }  }  /**   * Implements the magic __clone() method to clone the filter, too.   */  public function __clone() {    $this->filter = clone $this->filter;  }}/** * Represents a filter on a search query. * * Filters apply conditions on one or more fields with a specific conjunction * (AND or OR) and may contain nested filters. */interface SearchApiQueryFilterInterface {  /**   * Constructs a new filter that uses the specified conjunction.   *   * @param string $conjunction   *   (optional) The conjunction to use for this filter - either 'AND' or 'OR'.   * @param array $tags   *   (optional) An arbitrary set of tags. Can be used to identify this filter   *   down the line if necessary. This is primarily used by the facet system   *   to support OR facet queries.   */  public function __construct($conjunction = 'AND', array $tags = array());  /**   * Sets this filter's conjunction.   *   * @param string $conjunction   *   The conjunction to use for this filter - either 'AND' or 'OR'.   *   * @return SearchApiQueryFilterInterface   *   The called object.   */  public function setConjunction($conjunction);  /**   * Adds a subfilter.   *   * @param SearchApiQueryFilterInterface $filter   *   A SearchApiQueryFilterInterface object that should be added as a   *   subfilter.   *   * @return SearchApiQueryFilterInterface   *   The called object.   */  public function filter(SearchApiQueryFilterInterface $filter);  /**   * Adds a new ($field $operator $value) condition.   *   * @param string $field   *   The field to filter on, e.g. 'title'.   * @param mixed $value   *   The value the field should have (or be related to by the operator).   * @param string $operator   *   The operator to use for checking the constraint. The following operators   *   are supported for primitive types: "=", "<>", "<", "<=", ">=", ">". They   *   have the same semantics as the corresponding SQL operators.   *   If $field is a fulltext field, $operator can only be "=" or "<>", which   *   are in this case interpreted as "contains" or "doesn't contain",   *   respectively.   *   If $value is NULL, $operator also can only be "=" or "<>", meaning the   *   field must have no or some value, respectively.   *   * @return SearchApiQueryFilterInterface   *   The called object.   */  public function condition($field, $value, $operator = '=');  /**   * Retrieves the conjunction used by this filter.   *   * @return string   *   The conjunction used by this filter - either 'AND' or 'OR'.   */  public function getConjunction();  /**   * Return all conditions and nested filters contained in this filter.   *   * @return array   *   An array containing this filter's subfilters. Each of these is either an   *   array (field, value, operator), or another SearchApiFilter object.   */  public function &getFilters();  /**   * Checks whether a certain tag was set on this filter.   *   * @param string $tag   *   A tag to check for.   *   * @return bool   *   TRUE if the tag was set for this filter, FALSE otherwise.   */  public function hasTag($tag);  /**   * Retrieves the tags set on this filter.   *   * @return array   *   The tags associated with this filter, as both the array keys and values.   */  public function &getTags();}/** * Provides a standard implementation of SearchApiQueryFilterInterface. */class SearchApiQueryFilter implements SearchApiQueryFilterInterface {  /**   * Array containing subfilters.   *   * Each of these is either an array (field, value, operator), or another   * SearchApiFilter object.   *   * @var array   */  protected $filters;  /**   * String specifying this filter's conjunction ('AND' or 'OR').   *   * @var string   */  protected $conjunction;  /**   * {@inheritdoc}   */  public function __construct($conjunction = 'AND', array $tags = array()) {    $this->setConjunction($conjunction);    $this->filters = array();    $this->tags = drupal_map_assoc($tags);  }  /**   * {@inheritdoc}   */  public function setConjunction($conjunction) {    $this->conjunction = strtoupper(trim($conjunction)) == 'OR' ? 'OR' : 'AND';    return $this;  }  /**   * {@inheritdoc}   */  public function filter(SearchApiQueryFilterInterface $filter) {    $this->filters[] = $filter;    return $this;  }  /**   * {@inheritdoc}   */  public function condition($field, $value, $operator = '=') {    $this->filters[] = array($field, $value, $operator);    return $this;  }  /**   * {@inheritdoc}   */  public function getConjunction() {    return $this->conjunction;  }  /**   * {@inheritdoc}   */  public function &getFilters() {    return $this->filters;  }  /**   * {@inheritdoc}   */  public function hasTag($tag) {    return isset($this->tags[$tag]);  }  /**   * {@inheritdoc}   */  public function &getTags() {    return $this->tags;  }  /**   * Implements the magic __clone() method to clone nested filters, too.   */  public function __clone() {    foreach ($this->filters as $i => $filter) {      if (is_object($filter)) {        $this->filters[$i] = clone $filter;      }    }  }}
 |