query.inc 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929
  1. <?php
  2. /**
  3. * Interface representing a search query on an Search API index.
  4. *
  5. * Methods not returning something else will return the object itself, so calls
  6. * can be chained.
  7. */
  8. interface SearchApiQueryInterface {
  9. /**
  10. * Constructs a new search query.
  11. *
  12. * @param SearchApiIndex $index
  13. * The index the query should be executed on.
  14. * @param array $options
  15. * Associative array of options configuring this query. Recognized options
  16. * are:
  17. * - conjunction: The type of conjunction to use for this query - either
  18. * 'AND' or 'OR'. 'AND' by default. This only influences the search keys,
  19. * filters will always use AND by default.
  20. * - 'parse mode': The mode with which to parse the $keys variable, if it
  21. * is set and not already an array. See SearchApiQuery::parseModes() for
  22. * recognized parse modes.
  23. * - languages: The languages to search for, as an array of language IDs.
  24. * If not specified, all languages will be searched. Language-neutral
  25. * content (LANGUAGE_NONE) is always searched.
  26. * - offset: The position of the first returned search results relative to
  27. * the whole result in the index.
  28. * - limit: The maximum number of search results to return. -1 means no
  29. * limit.
  30. * - 'filter class': Can be used to change the SearchApiQueryFilterInterface
  31. * implementation to use.
  32. * - 'search id': A string that will be used as the identifier when storing
  33. * this search in the Search API's static cache.
  34. * - search_api_access_account: The account which will be used for entity
  35. * access checks, if available and enabled for the index.
  36. * - search_api_bypass_access: If set to TRUE, entity access checks will be
  37. * skipped, even if enabled for the index.
  38. * All options are optional. Third-party modules might define and use other
  39. * options not listed here.
  40. *
  41. * @throws SearchApiException
  42. * If a search on that index (or with those options) won't be possible.
  43. */
  44. public function __construct(SearchApiIndex $index, array $options = array());
  45. /**
  46. * Retrieves the parse modes supported by this query class.
  47. *
  48. * @return array
  49. * An associative array of parse modes recognized by objects of this class.
  50. * The keys are the parse modes' ids, values are associative arrays
  51. * containing the following entries:
  52. * - name: The translated name of the parse mode.
  53. * - description: (optional) A translated text describing the parse mode.
  54. */
  55. public function parseModes();
  56. /**
  57. * Creates a new filter to use with this query object.
  58. *
  59. * @param string $conjunction
  60. * The conjunction to use for the filter - either 'AND' or 'OR'.
  61. *
  62. * @return SearchApiQueryFilterInterface
  63. * A filter object that is set to use the specified conjunction.
  64. */
  65. public function createFilter($conjunction = 'AND');
  66. /**
  67. * Sets the keys to search for.
  68. *
  69. * If this method is not called on the query before execution, this will be a
  70. * filter-only query.
  71. *
  72. * @param array|string|null $keys
  73. * A string with the unparsed search keys, or NULL to use no search keys.
  74. *
  75. * @return SearchApiQueryInterface
  76. * The called object.
  77. */
  78. public function keys($keys = NULL);
  79. /**
  80. * Sets the fields that will be searched for the search keys.
  81. *
  82. * If this is not called, all fulltext fields will be searched.
  83. *
  84. * @param array $fields
  85. * An array containing fulltext fields that should be searched.
  86. *
  87. * @return SearchApiQueryInterface
  88. * The called object.
  89. *
  90. * @throws SearchApiException
  91. * If one of the fields isn't of type "text".
  92. */
  93. // @todo Allow calling with NULL.
  94. public function fields(array $fields);
  95. /**
  96. * Adds a subfilter to this query's filter.
  97. *
  98. * @param SearchApiQueryFilterInterface $filter
  99. * A SearchApiQueryFilter object that should be added as a subfilter.
  100. *
  101. * @return SearchApiQueryInterface
  102. * The called object.
  103. */
  104. public function filter(SearchApiQueryFilterInterface $filter);
  105. /**
  106. * Adds a new ($field $operator $value) condition filter.
  107. *
  108. * @param string $field
  109. * The field to filter on, e.g. 'title'.
  110. * @param mixed $value
  111. * The value the field should have (or be related to by the operator).
  112. * @param string $operator
  113. * The operator to use for checking the constraint. The following operators
  114. * are supported for primitive types: "=", "<>", "<", "<=", ">=", ">". They
  115. * have the same semantics as the corresponding SQL operators.
  116. * If $field is a fulltext field, $operator can only be "=" or "<>", which
  117. * are in this case interpreted as "contains" or "doesn't contain",
  118. * respectively.
  119. * If $value is NULL, $operator also can only be "=" or "<>", meaning the
  120. * field must have no or some value, respectively.
  121. *
  122. * @return SearchApiQueryInterface
  123. * The called object.
  124. */
  125. public function condition($field, $value, $operator = '=');
  126. /**
  127. * Adds a sort directive to this search query.
  128. *
  129. * If no sort is manually set, the results will be sorted descending by
  130. * relevance.
  131. *
  132. * @param string $field
  133. * The field to sort by. The special fields 'search_api_relevance' (sort by
  134. * relevance) and 'search_api_id' (sort by item id) may be used.
  135. * @param string $order
  136. * The order to sort items in - either 'ASC' or 'DESC'.
  137. *
  138. * @return SearchApiQueryInterface
  139. * The called object.
  140. *
  141. * @throws SearchApiException
  142. * If the field is multi-valued or of a fulltext type.
  143. */
  144. public function sort($field, $order = 'ASC');
  145. /**
  146. * Adds a range of results to return.
  147. *
  148. * This will be saved in the query's options. If called without parameters,
  149. * this will remove all range restrictions previously set.
  150. *
  151. * @param int|null $offset
  152. * The zero-based offset of the first result returned.
  153. * @param int|null $limit
  154. * The number of results to return.
  155. *
  156. * @return SearchApiQueryInterface
  157. * The called object.
  158. */
  159. public function range($offset = NULL, $limit = NULL);
  160. /**
  161. * Executes this search query.
  162. *
  163. * @return array
  164. * An associative array containing the search results. The following keys
  165. * are standardized:
  166. * - 'result count': The overall number of results for this query, without
  167. * range restrictions. Might be approximated, for large numbers.
  168. * - results: An array of results, ordered as specified. The array keys are
  169. * the items' IDs, values are arrays containing the following keys:
  170. * - id: The item's ID.
  171. * - score: A float measuring how well the item fits the search.
  172. * - fields: (optional) If set, an array containing some field values
  173. * already ready-to-use. This allows search engines (or postprocessors)
  174. * to store extracted fields so other modules don't have to extract them
  175. * again. This fields should always be checked by modules that want to
  176. * use field contents of the result items.
  177. * - entity: (optional) If set, the fully loaded result item. This field
  178. * should always be used by modules using search results, to avoid
  179. * duplicate item loads.
  180. * - excerpt: (optional) If set, an HTML text containing highlighted
  181. * portions of the fulltext that match the query.
  182. * - warnings: A numeric array of translated warning messages that may be
  183. * displayed to the user.
  184. * - ignored: A numeric array of search keys that were ignored for this
  185. * search (e.g., because of being too short or stop words).
  186. * - performance: An associative array with the time taken (as floats, in
  187. * seconds) for specific parts of the search execution:
  188. * - complete: The complete runtime of the query.
  189. * - hooks: Hook invocations and other client-side preprocessing.
  190. * - preprocessing: Preprocessing of the service class.
  191. * - execution: The actual query to the search server, in whatever form.
  192. * - postprocessing: Preparing the results for returning.
  193. * Additional metadata may be returned in other keys. Only 'result count'
  194. * and 'result' always have to be set, all other entries are optional.
  195. */
  196. public function execute();
  197. /**
  198. * Prepares the query object for the search.
  199. *
  200. * This method should always be called by execute() and contain all necessary
  201. * operations before the query is passed to the server's search() method.
  202. */
  203. public function preExecute();
  204. /**
  205. * Postprocesses the search results before they are returned.
  206. *
  207. * This method should always be called by execute() and contain all necessary
  208. * operations after the results are returned from the server.
  209. *
  210. * @param array $results
  211. * The results returned by the server, which may be altered. The data
  212. * structure is the same as returned by execute().
  213. */
  214. public function postExecute(array &$results);
  215. /**
  216. * Retrieves the index associated with this search.
  217. *
  218. * @return SearchApiIndex
  219. * The search index this query should be executed on.
  220. */
  221. public function getIndex();
  222. /**
  223. * Retrieves the search keys for this query.
  224. *
  225. * @return array|string|null
  226. * This object's search keys - either a string or an array specifying a
  227. * complex search expression.
  228. * An array will contain a '#conjunction' key specifying the conjunction
  229. * type, and search strings or nested expression arrays at numeric keys.
  230. * Additionally, a '#negation' key might be present, which means – unless it
  231. * maps to a FALSE value – that the search keys contained in that array
  232. * should be negated, i.e. not be present in returned results. The negation
  233. * works on the whole array, not on each contained term individually – i.e.,
  234. * with the "AND" conjunction and negation, only results that contain all
  235. * the terms in the array should be excluded; with the "OR" conjunction and
  236. * negation, all results containing one or more of the terms in the array
  237. * should be excluded.
  238. *
  239. * @see keys()
  240. */
  241. public function &getKeys();
  242. /**
  243. * Retrieves the unparsed search keys for this query as originally entered.
  244. *
  245. * @return array|string|null
  246. * The unprocessed search keys, exactly as passed to this object. Has the
  247. * same format as the return value of getKeys().
  248. *
  249. * @see keys()
  250. */
  251. public function getOriginalKeys();
  252. /**
  253. * Retrieves the fulltext fields that will be searched for the search keys.
  254. *
  255. * @return array
  256. * An array containing the fields that should be searched for the search
  257. * keys.
  258. *
  259. * @see fields()
  260. */
  261. public function &getFields();
  262. /**
  263. * Retrieves the filter object associated with this search query.
  264. *
  265. * @return SearchApiQueryFilterInterface
  266. * This object's associated filter object.
  267. */
  268. public function getFilter();
  269. /**
  270. * Retrieves the sorts set for this query.
  271. *
  272. * @return array
  273. * An array specifying the sort order for this query. Array keys are the
  274. * field names in order of importance, the values are the respective order
  275. * in which to sort the results according to the field.
  276. *
  277. * @see sort()
  278. */
  279. public function &getSort();
  280. /**
  281. * Retrieves an option set on this search query.
  282. *
  283. * @param string $name
  284. * The name of an option.
  285. * @param mixed $default
  286. * The value to return if the specified option is not set.
  287. *
  288. * @return mixed
  289. * The value of the option with the specified name, if set. NULL otherwise.
  290. */
  291. public function getOption($name, $default = NULL);
  292. /**
  293. * Sets an option for this search query.
  294. *
  295. * @param string $name
  296. * The name of an option.
  297. * @param mixed $value
  298. * The new value of the option.
  299. *
  300. * @return The option's previous value.
  301. */
  302. public function setOption($name, $value);
  303. /**
  304. * Retrieves all options set for this search query.
  305. *
  306. * The return value is a reference to the options so they can also be altered
  307. * this way.
  308. *
  309. * @return array
  310. * An associative array of query options.
  311. */
  312. public function &getOptions();
  313. }
  314. /**
  315. * Provides a standard implementation of the SearchApiQueryInterface.
  316. */
  317. class SearchApiQuery implements SearchApiQueryInterface {
  318. /**
  319. * The index.
  320. *
  321. * @var SearchApiIndex
  322. */
  323. protected $index;
  324. /**
  325. * The search keys. If NULL, this will be a filter-only search.
  326. *
  327. * @var mixed
  328. */
  329. protected $keys;
  330. /**
  331. * The unprocessed search keys, as passed to the keys() method.
  332. *
  333. * @var mixed
  334. */
  335. protected $orig_keys;
  336. /**
  337. * The fields that will be searched for the keys.
  338. *
  339. * @var array
  340. */
  341. protected $fields;
  342. /**
  343. * The search filter associated with this query.
  344. *
  345. * @var SearchApiQueryFilterInterface
  346. */
  347. protected $filter;
  348. /**
  349. * The sort associated with this query.
  350. *
  351. * @var array
  352. */
  353. protected $sort;
  354. /**
  355. * Search options configuring this query.
  356. *
  357. * @var array
  358. */
  359. protected $options;
  360. /**
  361. * Flag for whether preExecute() was already called for this query.
  362. *
  363. * @var bool
  364. */
  365. protected $pre_execute = FALSE;
  366. /**
  367. * {@inheritdoc}
  368. */
  369. public function __construct(SearchApiIndex $index, array $options = array()) {
  370. if (empty($index->options['fields'])) {
  371. throw new SearchApiException(t("Can't search an index which hasn't got any fields defined."));
  372. }
  373. if (empty($index->enabled)) {
  374. throw new SearchApiException(t("Can't search a disabled index."));
  375. }
  376. if (isset($options['parse mode'])) {
  377. $modes = $this->parseModes();
  378. if (!isset($modes[$options['parse mode']])) {
  379. throw new SearchApiException(t('Unknown parse mode: @mode.', array('@mode' => $options['parse mode'])));
  380. }
  381. }
  382. $this->index = $index;
  383. $this->options = $options + array(
  384. 'conjunction' => 'AND',
  385. 'parse mode' => 'terms',
  386. 'filter class' => 'SearchApiQueryFilter',
  387. 'search id' => __CLASS__,
  388. );
  389. $this->filter = $this->createFilter('AND');
  390. $this->sort = array();
  391. }
  392. /**
  393. * {@inheritdoc}
  394. */
  395. public function parseModes() {
  396. $modes['direct'] = array(
  397. 'name' => t('Direct query'),
  398. 'description' => t("Don't parse the query, just hand it to the search server unaltered. " .
  399. "Might fail if the query contains syntax errors in regard to the specific server's query syntax."),
  400. );
  401. $modes['single'] = array(
  402. 'name' => t('Single term'),
  403. 'description' => t('The query is interpreted as a single keyword, maybe containing spaces or special characters.'),
  404. );
  405. $modes['terms'] = array(
  406. 'name' => t('Multiple terms'),
  407. 'description' => t('The query is interpreted as multiple keywords seperated by spaces. ' .
  408. 'Keywords containing spaces may be "quoted". Quoted keywords must still be seperated by spaces.'),
  409. );
  410. // @todo Add fourth mode for complicated expressions, e.g.: »"vanilla ice" OR (love NOT hate)«
  411. return $modes;
  412. }
  413. /**
  414. * {@inheritdoc}
  415. */
  416. protected function parseKeys($keys, $mode) {
  417. if ($keys === NULL || is_array($keys)) {
  418. return $keys;
  419. }
  420. $keys = '' . $keys;
  421. switch ($mode) {
  422. case 'direct':
  423. return $keys;
  424. case 'single':
  425. return array('#conjunction' => $this->options['conjunction'], $keys);
  426. case 'terms':
  427. $ret = preg_split('/\s+/u', $keys);
  428. $quoted = FALSE;
  429. $str = '';
  430. foreach ($ret as $k => $v) {
  431. if (!$v) {
  432. continue;
  433. }
  434. if ($quoted) {
  435. if (substr($v, -1) == '"') {
  436. $v = substr($v, 0, -1);
  437. $str .= ' ' . $v;
  438. $ret[$k] = $str;
  439. $quoted = FALSE;
  440. }
  441. else {
  442. $str .= ' ' . $v;
  443. unset($ret[$k]);
  444. }
  445. }
  446. elseif ($v[0] == '"') {
  447. $len = strlen($v);
  448. if ($len > 1 && $v[$len-1] == '"') {
  449. $ret[$k] = substr($v, 1, -1);
  450. }
  451. else {
  452. $str = substr($v, 1);
  453. $quoted = TRUE;
  454. unset($ret[$k]);
  455. }
  456. }
  457. }
  458. if ($quoted) {
  459. $ret[] = $str;
  460. }
  461. $ret['#conjunction'] = $this->options['conjunction'];
  462. return array_filter($ret);
  463. }
  464. }
  465. /**
  466. * {@inheritdoc}
  467. */
  468. public function createFilter($conjunction = 'AND') {
  469. $filter_class = $this->options['filter class'];
  470. return new $filter_class($conjunction);
  471. }
  472. /**
  473. * {@inheritdoc}
  474. */
  475. public function keys($keys = NULL) {
  476. $this->orig_keys = $keys;
  477. if (isset($keys)) {
  478. $this->keys = $this->parseKeys($keys, $this->options['parse mode']);
  479. }
  480. else {
  481. $this->keys = NULL;
  482. }
  483. return $this;
  484. }
  485. /**
  486. * {@inheritdoc}
  487. */
  488. public function fields(array $fields) {
  489. $fulltext_fields = $this->index->getFulltextFields();
  490. foreach (array_diff($fields, $fulltext_fields) as $field) {
  491. throw new SearchApiException(t('Trying to search on field @field which is no indexed fulltext field.', array('@field' => $field)));
  492. }
  493. $this->fields = $fields;
  494. return $this;
  495. }
  496. /**
  497. * {@inheritdoc}
  498. */
  499. public function filter(SearchApiQueryFilterInterface $filter) {
  500. $this->filter->filter($filter);
  501. return $this;
  502. }
  503. /**
  504. * {@inheritdoc}
  505. */
  506. public function condition($field, $value, $operator = '=') {
  507. $this->filter->condition($field, $value, $operator);
  508. return $this;
  509. }
  510. /**
  511. * {@inheritdoc}
  512. */
  513. public function sort($field, $order = 'ASC') {
  514. $fields = $this->index->options['fields'];
  515. $fields += array(
  516. 'search_api_relevance' => array('type' => 'decimal'),
  517. 'search_api_id' => array('type' => 'integer'),
  518. );
  519. if (empty($fields[$field])) {
  520. throw new SearchApiException(t('Trying to sort on unknown field @field.', array('@field' => $field)));
  521. }
  522. $type = $fields[$field]['type'];
  523. if (search_api_is_list_type($type) || search_api_is_text_type($type)) {
  524. throw new SearchApiException(t('Trying to sort on field @field of illegal type @type.', array('@field' => $field, '@type' => $type)));
  525. }
  526. $order = strtoupper(trim($order)) == 'DESC' ? 'DESC' : 'ASC';
  527. $this->sort[$field] = $order;
  528. return $this;
  529. }
  530. /**
  531. * {@inheritdoc}
  532. */
  533. public function range($offset = NULL, $limit = NULL) {
  534. $this->options['offset'] = $offset;
  535. $this->options['limit'] = $limit;
  536. return $this;
  537. }
  538. /**
  539. * {@inheritdoc}
  540. */
  541. public function execute() {
  542. $start = microtime(TRUE);
  543. // Prepare the query for execution by the server.
  544. $this->preExecute();
  545. $pre_search = microtime(TRUE);
  546. // Execute query.
  547. $response = $this->index->server()->search($this);
  548. $post_search = microtime(TRUE);
  549. // Postprocess the search results.
  550. $this->postExecute($response);
  551. $end = microtime(TRUE);
  552. $response['performance']['complete'] = $end - $start;
  553. $response['performance']['hooks'] = $response['performance']['complete'] - ($post_search - $pre_search);
  554. // Store search for later retrieval for facets, etc.
  555. search_api_current_search(NULL, $this, $response);
  556. return $response;
  557. }
  558. /**
  559. * Adds language filters for the query.
  560. *
  561. * Internal helper function.
  562. *
  563. * @param array $languages
  564. * The languages for which results should be returned.
  565. */
  566. protected function addLanguages(array $languages) {
  567. if (array_search(LANGUAGE_NONE, $languages) === FALSE) {
  568. $languages[] = LANGUAGE_NONE;
  569. }
  570. $languages = drupal_map_assoc($languages);
  571. $langs_to_add = $languages;
  572. $filters = $this->filter->getFilters();
  573. while ($filters && $langs_to_add) {
  574. $filter = array_shift($filters);
  575. if (is_array($filter)) {
  576. if ($filter[0] == 'search_api_language' && $filter[2] == '=') {
  577. $lang = $filter[1];
  578. if (isset($languages[$lang])) {
  579. unset($langs_to_add[$lang]);
  580. }
  581. else {
  582. 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))));
  583. }
  584. }
  585. }
  586. else {
  587. if ($filter->getConjunction() == 'AND') {
  588. $filters += $filter->getFilters();
  589. }
  590. }
  591. }
  592. if ($langs_to_add) {
  593. if (count($langs_to_add) == 1) {
  594. $this->condition('search_api_language', reset($langs_to_add));
  595. }
  596. else {
  597. $filter = $this->createFilter('OR');
  598. foreach ($langs_to_add as $lang) {
  599. $filter->condition('search_api_language', $lang);
  600. }
  601. $this->filter($filter);
  602. }
  603. }
  604. }
  605. /**
  606. * {@inheritdoc}
  607. */
  608. public function preExecute() {
  609. // Make sure to only execute this once per query.
  610. if (!$this->pre_execute) {
  611. $this->pre_execute = TRUE;
  612. // Add filter for languages.
  613. if (isset($this->options['languages'])) {
  614. $this->addLanguages($this->options['languages']);
  615. }
  616. // Add fulltext fields, unless set
  617. if ($this->fields === NULL) {
  618. $this->fields = $this->index->getFulltextFields();
  619. }
  620. // Preprocess query.
  621. $this->index->preprocessSearchQuery($this);
  622. // Let modules alter the query.
  623. drupal_alter('search_api_query', $this);
  624. }
  625. }
  626. /**
  627. * {@inheritdoc}
  628. */
  629. public function postExecute(array &$results) {
  630. // Postprocess results.
  631. $this->index->postprocessSearchResults($results, $this);
  632. }
  633. /**
  634. * {@inheritdoc}
  635. */
  636. public function getIndex() {
  637. return $this->index;
  638. }
  639. /**
  640. * {@inheritdoc}
  641. */
  642. public function &getKeys() {
  643. return $this->keys;
  644. }
  645. /**
  646. * {@inheritdoc}
  647. */
  648. public function getOriginalKeys() {
  649. return $this->orig_keys;
  650. }
  651. /**
  652. * {@inheritdoc}
  653. */
  654. public function &getFields() {
  655. return $this->fields;
  656. }
  657. /**
  658. * {@inheritdoc}
  659. */
  660. public function getFilter() {
  661. return $this->filter;
  662. }
  663. /**
  664. * {@inheritdoc}
  665. */
  666. public function &getSort() {
  667. return $this->sort;
  668. }
  669. /**
  670. * {@inheritdoc}
  671. */
  672. public function getOption($name, $default = NULL) {
  673. return array_key_exists($name, $this->options) ? $this->options[$name] : $default;
  674. }
  675. /**
  676. * {@inheritdoc}
  677. */
  678. public function setOption($name, $value) {
  679. $old = $this->getOption($name);
  680. $this->options[$name] = $value;
  681. return $old;
  682. }
  683. /**
  684. * {@inheritdoc}
  685. */
  686. public function &getOptions() {
  687. return $this->options;
  688. }
  689. /**
  690. * Implements the magic __sleep() method to avoid serializing the index.
  691. */
  692. public function __sleep() {
  693. $this->index_id = $this->index->machine_name;
  694. $keys = get_object_vars($this);
  695. unset($keys['index']);
  696. return array_keys($keys);
  697. }
  698. /**
  699. * Implements the magic __wakeup() method to reload the query's index.
  700. */
  701. public function __wakeup() {
  702. if (!isset($this->index) && !empty($this->index_id)) {
  703. $this->index = search_api_index_load($this->index_id);
  704. unset($this->index_id);
  705. }
  706. }
  707. }
  708. /**
  709. * Represents a filter on a search query.
  710. *
  711. * Filters apply conditions on one or more fields with a specific conjunction
  712. * (AND or OR) and may contain nested filters.
  713. */
  714. interface SearchApiQueryFilterInterface {
  715. /**
  716. * Constructs a new filter that uses the specified conjunction.
  717. *
  718. * @param string $conjunction
  719. * The conjunction to use for this filter - either 'AND' or 'OR'.
  720. */
  721. public function __construct($conjunction = 'AND');
  722. /**
  723. * Sets this filter's conjunction.
  724. *
  725. * @param string $conjunction
  726. * The conjunction to use for this filter - either 'AND' or 'OR'.
  727. *
  728. * @return SearchApiQueryFilterInterface
  729. * The called object.
  730. */
  731. public function setConjunction($conjunction);
  732. /**
  733. * Adds a subfilter.
  734. *
  735. * @param SearchApiQueryFilterInterface $filter
  736. * A SearchApiQueryFilterInterface object that should be added as a
  737. * subfilter.
  738. *
  739. * @return SearchApiQueryFilterInterface
  740. * The called object.
  741. */
  742. public function filter(SearchApiQueryFilterInterface $filter);
  743. /**
  744. * Adds a new ($field $operator $value) condition.
  745. *
  746. * @param string $field
  747. * The field to filter on, e.g. 'title'.
  748. * @param mixed $value
  749. * The value the field should have (or be related to by the operator).
  750. * @param string $operator
  751. * The operator to use for checking the constraint. The following operators
  752. * are supported for primitive types: "=", "<>", "<", "<=", ">=", ">". They
  753. * have the same semantics as the corresponding SQL operators.
  754. * If $field is a fulltext field, $operator can only be "=" or "<>", which
  755. * are in this case interpreted as "contains" or "doesn't contain",
  756. * respectively.
  757. * If $value is NULL, $operator also can only be "=" or "<>", meaning the
  758. * field must have no or some value, respectively.
  759. *
  760. * @return SearchApiQueryFilterInterface
  761. * The called object.
  762. */
  763. public function condition($field, $value, $operator = '=');
  764. /**
  765. * Retrieves the conjunction used by this filter.
  766. *
  767. * @return string
  768. * The conjunction used by this filter - either 'AND' or 'OR'.
  769. */
  770. public function getConjunction();
  771. /**
  772. * Return all conditions and nested filters contained in this filter.
  773. *
  774. * @return array
  775. * An array containing this filter's subfilters. Each of these is either an
  776. * array (field, value, operator), or another SearchApiFilter object.
  777. */
  778. public function &getFilters();
  779. }
  780. /**
  781. * Provides a standard implementation of SearchApiQueryFilterInterface.
  782. */
  783. class SearchApiQueryFilter implements SearchApiQueryFilterInterface {
  784. /**
  785. * Array containing subfilters.
  786. *
  787. * Each of these is either an array (field, value, operator), or another
  788. * SearchApiFilter object.
  789. *
  790. * @var array
  791. */
  792. protected $filters;
  793. /**
  794. * String specifying this filter's conjunction ('AND' or 'OR').
  795. *
  796. * @var string
  797. */
  798. protected $conjunction;
  799. /**
  800. * {@inheritdoc}
  801. */
  802. public function __construct($conjunction = 'AND') {
  803. $this->setConjunction($conjunction);
  804. $this->filters = array();
  805. }
  806. /**
  807. * {@inheritdoc}
  808. */
  809. public function setConjunction($conjunction) {
  810. $this->conjunction = strtoupper(trim($conjunction)) == 'OR' ? 'OR' : 'AND';
  811. return $this;
  812. }
  813. /**
  814. * {@inheritdoc}
  815. */
  816. public function filter(SearchApiQueryFilterInterface $filter) {
  817. $this->filters[] = $filter;
  818. return $this;
  819. }
  820. /**
  821. * {@inheritdoc}
  822. */
  823. public function condition($field, $value, $operator = '=') {
  824. $this->filters[] = array($field, $value, $operator);
  825. return $this;
  826. }
  827. /**
  828. * {@inheritdoc}
  829. */
  830. public function getConjunction() {
  831. return $this->conjunction;
  832. }
  833. /**
  834. * {@inheritdoc}
  835. */
  836. public function &getFilters() {
  837. return $this->filters;
  838. }
  839. }