12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112 |
- <?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. Also, if
- * the search server supports the "search_api_random_sort" feature, the
- * "search_api_random" special field can be used to sort randomly.
- * @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. The format of the array is
- * field IDs (as used by the Search API internally) mapped to either the
- * raw value of the field (scalar or array value), or an associative
- * array with the following keys:
- * - #value: The raw field value.
- * - #sanitize_callback: The callback to use for sanitizing the field
- * value for HTML output, or FALSE to state that the field value is
- * already sanitized.
- * In the simple form, it's assumed the field value should be sanitized
- * with check_plain().
- * - 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 'results' always have to be set, all other entries are optional.
- *
- * @throws SearchApiException
- * If an error prevented the search from completing.
- */
- 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.
- *
- * @throws SearchApiException
- * If any error occurred during the preparation of the query.
- */
- 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 separated by spaces. ' .
- 'Keywords containing spaces may be "quoted". Quoted keywords must still be separated 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 ($this->getIndex()->server()->supportsFeature('search_api_random_sort')) {
- $fields['search_api_random'] = 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);
- // Let modules alter the results.
- drupal_alter('search_api_results', $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;
- }
- /**
- * Implements the magic __toString() method to simplify debugging.
- */
- public function __toString() {
- $ret = 'Index: ' . $this->index->machine_name . "\n";
- $ret .= 'Keys: ' . str_replace("\n", "\n ", var_export($this->orig_keys, 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 ($filter = (string) $this->filter) {
- $filter = str_replace("\n", "\n ", $filter);
- $ret .= "Filters:\n $filter\n";
- }
- if ($this->sort) {
- $sort = array();
- foreach ($this->sort as $field => $order) {
- $sort[] = "$field $order";
- }
- $ret .= 'Sorting: ' . implode(', ', $sort) . "\n";
- }
- $options = $this->sanitizeOptions($this->options);
- $options = str_replace("\n", "\n ", var_export($options, TRUE));
- $ret .= 'Options: ' . $options . "\n";
- return $ret;
- }
- /**
- * Sanitizes an array of options in a way that plays nice with var_export().
- *
- * @param array $options
- * An array of options.
- *
- * @return array
- * The sanitized options.
- */
- 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;
- }
- }
- /**
- * 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 a
- * condition, represented as a numerically indexed array with the arguments
- * of a previous SearchApiQueryFilterInterface::condition() call (field,
- * value, operator); or a nested filter, represented by a
- * SearchApiQueryFilterInterface filter 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() {
- // Tags can sometimes be NULL for old serialized query filter objects.
- if (!isset($this->tags)) {
- $this->tags = array();
- }
- 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;
- }
- }
- }
- /**
- * Implements the magic __toString() method to simplify debugging.
- */
- public function __toString() {
- // Special case for a single, nested filter:
- if (count($this->filters) == 1 && is_object($this->filters[0])) {
- return (string) $this->filters[0];
- }
- $ret = array();
- foreach ($this->filters as $filter) {
- if (is_object($filter)) {
- $ret[] = "[\n " . str_replace("\n", "\n ", (string) $filter) . "\n ]";
- }
- else {
- $ret[] = "$filter[0] $filter[2] " . str_replace("\n", "\n ", var_export($filter[1], TRUE));
- }
- }
- return $ret ? ' ' . implode("\n{$this->conjunction}\n ", $ret) : '';
- }
- }
|