PageRenderTime 58ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/lib/Zend/Search/Lucene/Search/Query/Boolean.php

https://bitbucket.org/yoander/mtrack
PHP | 815 lines | 440 code | 125 blank | 250 comment | 112 complexity | ced81a6ad5d68d0a9db69b83cd716afd MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Search_Lucene
  17. * @subpackage Search
  18. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id: Boolean.php 23775 2011-03-01 17:25:24Z ralph $
  21. */
  22. /** Zend_Search_Lucene_Search_Query */
  23. require_once 'Zend/Search/Lucene/Search/Query.php';
  24. /**
  25. * @category Zend
  26. * @package Zend_Search_Lucene
  27. * @subpackage Search
  28. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  29. * @license http://framework.zend.com/license/new-bsd New BSD License
  30. */
  31. class Zend_Search_Lucene_Search_Query_Boolean extends Zend_Search_Lucene_Search_Query
  32. {
  33. /**
  34. * Subqueries
  35. * Array of Zend_Search_Lucene_Search_Query
  36. *
  37. * @var array
  38. */
  39. private $_subqueries = array();
  40. /**
  41. * Subqueries signs.
  42. * If true then subquery is required.
  43. * If false then subquery is prohibited.
  44. * If null then subquery is neither prohibited, nor required
  45. *
  46. * If array is null then all subqueries are required
  47. *
  48. * @var array
  49. */
  50. private $_signs = array();
  51. /**
  52. * Result vector.
  53. *
  54. * @var array
  55. */
  56. private $_resVector = null;
  57. /**
  58. * A score factor based on the fraction of all query subqueries
  59. * that a document contains.
  60. * float for conjunction queries
  61. * array of float for non conjunction queries
  62. *
  63. * @var mixed
  64. */
  65. private $_coord = null;
  66. /**
  67. * Class constructor. Create a new Boolean query object.
  68. *
  69. * if $signs array is omitted then all subqueries are required
  70. * it differs from addSubquery() behavior, but should never be used
  71. *
  72. * @param array $subqueries Array of Zend_Search_Search_Query objects
  73. * @param array $signs Array of signs. Sign is boolean|null.
  74. * @return void
  75. */
  76. public function __construct($subqueries = null, $signs = null)
  77. {
  78. if (is_array($subqueries)) {
  79. $this->_subqueries = $subqueries;
  80. $this->_signs = null;
  81. // Check if all subqueries are required
  82. if (is_array($signs)) {
  83. foreach ($signs as $sign ) {
  84. if ($sign !== true) {
  85. $this->_signs = $signs;
  86. break;
  87. }
  88. }
  89. }
  90. }
  91. }
  92. /**
  93. * Add a $subquery (Zend_Search_Lucene_Search_Query) to this query.
  94. *
  95. * The sign is specified as:
  96. * TRUE - subquery is required
  97. * FALSE - subquery is prohibited
  98. * NULL - subquery is neither prohibited, nor required
  99. *
  100. * @param Zend_Search_Lucene_Search_Query $subquery
  101. * @param boolean|null $sign
  102. * @return void
  103. */
  104. public function addSubquery(Zend_Search_Lucene_Search_Query $subquery, $sign=null) {
  105. if ($sign !== true || $this->_signs !== null) { // Skip, if all subqueries are required
  106. if ($this->_signs === null) { // Check, If all previous subqueries are required
  107. $this->_signs = array();
  108. foreach ($this->_subqueries as $prevSubquery) {
  109. $this->_signs[] = true;
  110. }
  111. }
  112. $this->_signs[] = $sign;
  113. }
  114. $this->_subqueries[] = $subquery;
  115. }
  116. /**
  117. * Re-write queries into primitive queries
  118. *
  119. * @param Zend_Search_Lucene_Interface $index
  120. * @return Zend_Search_Lucene_Search_Query
  121. */
  122. public function rewrite(Zend_Search_Lucene_Interface $index)
  123. {
  124. $query = new Zend_Search_Lucene_Search_Query_Boolean();
  125. $query->setBoost($this->getBoost());
  126. foreach ($this->_subqueries as $subqueryId => $subquery) {
  127. $query->addSubquery($subquery->rewrite($index),
  128. ($this->_signs === null)? true : $this->_signs[$subqueryId]);
  129. }
  130. return $query;
  131. }
  132. /**
  133. * Optimize query in the context of specified index
  134. *
  135. * @param Zend_Search_Lucene_Interface $index
  136. * @return Zend_Search_Lucene_Search_Query
  137. */
  138. public function optimize(Zend_Search_Lucene_Interface $index)
  139. {
  140. $subqueries = array();
  141. $signs = array();
  142. // Optimize all subqueries
  143. foreach ($this->_subqueries as $id => $subquery) {
  144. $subqueries[] = $subquery->optimize($index);
  145. $signs[] = ($this->_signs === null)? true : $this->_signs[$id];
  146. }
  147. // Remove insignificant subqueries
  148. foreach ($subqueries as $id => $subquery) {
  149. if ($subquery instanceof Zend_Search_Lucene_Search_Query_Insignificant) {
  150. // Insignificant subquery has to be removed anyway
  151. unset($subqueries[$id]);
  152. unset($signs[$id]);
  153. }
  154. }
  155. if (count($subqueries) == 0) {
  156. // Boolean query doesn't has non-insignificant subqueries
  157. require_once 'Zend/Search/Lucene/Search/Query/Insignificant.php';
  158. return new Zend_Search_Lucene_Search_Query_Insignificant();
  159. }
  160. // Check if all non-insignificant subqueries are prohibited
  161. $allProhibited = true;
  162. foreach ($signs as $sign) {
  163. if ($sign !== false) {
  164. $allProhibited = false;
  165. break;
  166. }
  167. }
  168. if ($allProhibited) {
  169. require_once 'Zend/Search/Lucene/Search/Query/Insignificant.php';
  170. return new Zend_Search_Lucene_Search_Query_Insignificant();
  171. }
  172. // Check for empty subqueries
  173. foreach ($subqueries as $id => $subquery) {
  174. if ($subquery instanceof Zend_Search_Lucene_Search_Query_Empty) {
  175. if ($signs[$id] === true) {
  176. // Matching is required, but is actually empty
  177. require_once 'Zend/Search/Lucene/Search/Query/Empty.php';
  178. return new Zend_Search_Lucene_Search_Query_Empty();
  179. } else {
  180. // Matching is optional or prohibited, but is empty
  181. // Remove it from subqueries and signs list
  182. unset($subqueries[$id]);
  183. unset($signs[$id]);
  184. }
  185. }
  186. }
  187. // Check, if reduced subqueries list is empty
  188. if (count($subqueries) == 0) {
  189. require_once 'Zend/Search/Lucene/Search/Query/Empty.php';
  190. return new Zend_Search_Lucene_Search_Query_Empty();
  191. }
  192. // Check if all non-empty subqueries are prohibited
  193. $allProhibited = true;
  194. foreach ($signs as $sign) {
  195. if ($sign !== false) {
  196. $allProhibited = false;
  197. break;
  198. }
  199. }
  200. if ($allProhibited) {
  201. require_once 'Zend/Search/Lucene/Search/Query/Empty.php';
  202. return new Zend_Search_Lucene_Search_Query_Empty();
  203. }
  204. // Check, if reduced subqueries list has only one entry
  205. if (count($subqueries) == 1) {
  206. // It's a query with only one required or optional clause
  207. // (it's already checked, that it's not a prohibited clause)
  208. if ($this->getBoost() == 1) {
  209. return reset($subqueries);
  210. }
  211. $optimizedQuery = clone reset($subqueries);
  212. $optimizedQuery->setBoost($optimizedQuery->getBoost()*$this->getBoost());
  213. return $optimizedQuery;
  214. }
  215. // Prepare first candidate for optimized query
  216. $optimizedQuery = new Zend_Search_Lucene_Search_Query_Boolean($subqueries, $signs);
  217. $optimizedQuery->setBoost($this->getBoost());
  218. $terms = array();
  219. $tsigns = array();
  220. $boostFactors = array();
  221. // Try to decompose term and multi-term subqueries
  222. foreach ($subqueries as $id => $subquery) {
  223. if ($subquery instanceof Zend_Search_Lucene_Search_Query_Term) {
  224. $terms[] = $subquery->getTerm();
  225. $tsigns[] = $signs[$id];
  226. $boostFactors[] = $subquery->getBoost();
  227. // remove subquery from a subqueries list
  228. unset($subqueries[$id]);
  229. unset($signs[$id]);
  230. } else if ($subquery instanceof Zend_Search_Lucene_Search_Query_MultiTerm) {
  231. $subTerms = $subquery->getTerms();
  232. $subSigns = $subquery->getSigns();
  233. if ($signs[$id] === true) {
  234. // It's a required multi-term subquery.
  235. // Something like '... +(+term1 -term2 term3 ...) ...'
  236. // Multi-term required subquery can be decomposed only if it contains
  237. // required terms and doesn't contain prohibited terms:
  238. // ... +(+term1 term2 ...) ... => ... +term1 term2 ...
  239. //
  240. // Check this
  241. $hasRequired = false;
  242. $hasProhibited = false;
  243. if ($subSigns === null) {
  244. // All subterms are required
  245. $hasRequired = true;
  246. } else {
  247. foreach ($subSigns as $sign) {
  248. if ($sign === true) {
  249. $hasRequired = true;
  250. } else if ($sign === false) {
  251. $hasProhibited = true;
  252. break;
  253. }
  254. }
  255. }
  256. // Continue if subquery has prohibited terms or doesn't have required terms
  257. if ($hasProhibited || !$hasRequired) {
  258. continue;
  259. }
  260. foreach ($subTerms as $termId => $term) {
  261. $terms[] = $term;
  262. $tsigns[] = ($subSigns === null)? true : $subSigns[$termId];
  263. $boostFactors[] = $subquery->getBoost();
  264. }
  265. // remove subquery from a subqueries list
  266. unset($subqueries[$id]);
  267. unset($signs[$id]);
  268. } else { // $signs[$id] === null || $signs[$id] === false
  269. // It's an optional or prohibited multi-term subquery.
  270. // Something like '... (+term1 -term2 term3 ...) ...'
  271. // or
  272. // something like '... -(+term1 -term2 term3 ...) ...'
  273. // Multi-term optional and required subqueries can be decomposed
  274. // only if all terms are optional.
  275. //
  276. // Check if all terms are optional.
  277. $onlyOptional = true;
  278. if ($subSigns === null) {
  279. // All subterms are required
  280. $onlyOptional = false;
  281. } else {
  282. foreach ($subSigns as $sign) {
  283. if ($sign !== null) {
  284. $onlyOptional = false;
  285. break;
  286. }
  287. }
  288. }
  289. // Continue if non-optional terms are presented in this multi-term subquery
  290. if (!$onlyOptional) {
  291. continue;
  292. }
  293. foreach ($subTerms as $termId => $term) {
  294. $terms[] = $term;
  295. $tsigns[] = ($signs[$id] === null)? null /* optional */ :
  296. false /* prohibited */;
  297. $boostFactors[] = $subquery->getBoost();
  298. }
  299. // remove subquery from a subqueries list
  300. unset($subqueries[$id]);
  301. unset($signs[$id]);
  302. }
  303. }
  304. }
  305. // Check, if there are no decomposed subqueries
  306. if (count($terms) == 0 ) {
  307. // return prepared candidate
  308. return $optimizedQuery;
  309. }
  310. // Check, if all subqueries have been decomposed and all terms has the same boost factor
  311. if (count($subqueries) == 0 && count(array_unique($boostFactors)) == 1) {
  312. require_once 'Zend/Search/Lucene/Search/Query/MultiTerm.php';
  313. $optimizedQuery = new Zend_Search_Lucene_Search_Query_MultiTerm($terms, $tsigns);
  314. $optimizedQuery->setBoost(reset($boostFactors)*$this->getBoost());
  315. return $optimizedQuery;
  316. }
  317. // This boolean query can't be transformed to Term/MultiTerm query and still contains
  318. // several subqueries
  319. // Separate prohibited terms
  320. $prohibitedTerms = array();
  321. foreach ($terms as $id => $term) {
  322. if ($tsigns[$id] === false) {
  323. $prohibitedTerms[] = $term;
  324. unset($terms[$id]);
  325. unset($tsigns[$id]);
  326. unset($boostFactors[$id]);
  327. }
  328. }
  329. if (count($terms) == 1) {
  330. require_once 'Zend/Search/Lucene/Search/Query/Term.php';
  331. $clause = new Zend_Search_Lucene_Search_Query_Term(reset($terms));
  332. $clause->setBoost(reset($boostFactors));
  333. $subqueries[] = $clause;
  334. $signs[] = reset($tsigns);
  335. // Clear terms list
  336. $terms = array();
  337. } else if (count($terms) > 1 && count(array_unique($boostFactors)) == 1) {
  338. require_once 'Zend/Search/Lucene/Search/Query/MultiTerm.php';
  339. $clause = new Zend_Search_Lucene_Search_Query_MultiTerm($terms, $tsigns);
  340. $clause->setBoost(reset($boostFactors));
  341. $subqueries[] = $clause;
  342. // Clause sign is 'required' if clause contains required terms. 'Optional' otherwise.
  343. $signs[] = (in_array(true, $tsigns))? true : null;
  344. // Clear terms list
  345. $terms = array();
  346. }
  347. if (count($prohibitedTerms) == 1) {
  348. // (boost factors are not significant for prohibited clauses)
  349. require_once 'Zend/Search/Lucene/Search/Query/Term.php';
  350. $subqueries[] = new Zend_Search_Lucene_Search_Query_Term(reset($prohibitedTerms));
  351. $signs[] = false;
  352. // Clear prohibited terms list
  353. $prohibitedTerms = array();
  354. } else if (count($prohibitedTerms) > 1) {
  355. // prepare signs array
  356. $prohibitedSigns = array();
  357. foreach ($prohibitedTerms as $id => $term) {
  358. // all prohibited term are grouped as optional into multi-term query
  359. $prohibitedSigns[$id] = null;
  360. }
  361. // (boost factors are not significant for prohibited clauses)
  362. require_once 'Zend/Search/Lucene/Search/Query/MultiTerm.php';
  363. $subqueries[] = new Zend_Search_Lucene_Search_Query_MultiTerm($prohibitedTerms, $prohibitedSigns);
  364. // Clause sign is 'prohibited'
  365. $signs[] = false;
  366. // Clear terms list
  367. $prohibitedTerms = array();
  368. }
  369. /** @todo Group terms with the same boost factors together */
  370. // Check, that all terms are processed
  371. // Replace candidate for optimized query
  372. if (count($terms) == 0 && count($prohibitedTerms) == 0) {
  373. $optimizedQuery = new Zend_Search_Lucene_Search_Query_Boolean($subqueries, $signs);
  374. $optimizedQuery->setBoost($this->getBoost());
  375. }
  376. return $optimizedQuery;
  377. }
  378. /**
  379. * Returns subqueries
  380. *
  381. * @return array
  382. */
  383. public function getSubqueries()
  384. {
  385. return $this->_subqueries;
  386. }
  387. /**
  388. * Return subqueries signs
  389. *
  390. * @return array
  391. */
  392. public function getSigns()
  393. {
  394. return $this->_signs;
  395. }
  396. /**
  397. * Constructs an appropriate Weight implementation for this query.
  398. *
  399. * @param Zend_Search_Lucene_Interface $reader
  400. * @return Zend_Search_Lucene_Search_Weight
  401. */
  402. public function createWeight(Zend_Search_Lucene_Interface $reader)
  403. {
  404. require_once 'Zend/Search/Lucene/Search/Weight/Boolean.php';
  405. $this->_weight = new Zend_Search_Lucene_Search_Weight_Boolean($this, $reader);
  406. return $this->_weight;
  407. }
  408. /**
  409. * Calculate result vector for Conjunction query
  410. * (like '<subquery1> AND <subquery2> AND <subquery3>')
  411. */
  412. private function _calculateConjunctionResult()
  413. {
  414. $this->_resVector = null;
  415. if (count($this->_subqueries) == 0) {
  416. $this->_resVector = array();
  417. }
  418. $resVectors = array();
  419. $resVectorsSizes = array();
  420. $resVectorsIds = array(); // is used to prevent arrays comparison
  421. foreach ($this->_subqueries as $subqueryId => $subquery) {
  422. $resVectors[] = $subquery->matchedDocs();
  423. $resVectorsSizes[] = count(end($resVectors));
  424. $resVectorsIds[] = $subqueryId;
  425. }
  426. // sort resvectors in order of subquery cardinality increasing
  427. array_multisort($resVectorsSizes, SORT_ASC, SORT_NUMERIC,
  428. $resVectorsIds, SORT_ASC, SORT_NUMERIC,
  429. $resVectors);
  430. foreach ($resVectors as $nextResVector) {
  431. if($this->_resVector === null) {
  432. $this->_resVector = $nextResVector;
  433. } else {
  434. //$this->_resVector = array_intersect_key($this->_resVector, $nextResVector);
  435. /**
  436. * This code is used as workaround for array_intersect_key() slowness problem.
  437. */
  438. $updatedVector = array();
  439. foreach ($this->_resVector as $id => $value) {
  440. if (isset($nextResVector[$id])) {
  441. $updatedVector[$id] = $value;
  442. }
  443. }
  444. $this->_resVector = $updatedVector;
  445. }
  446. if (count($this->_resVector) == 0) {
  447. // Empty result set, we don't need to check other terms
  448. break;
  449. }
  450. }
  451. // ksort($this->_resVector, SORT_NUMERIC);
  452. // Used algorithm doesn't change elements order
  453. }
  454. /**
  455. * Calculate result vector for non Conjunction query
  456. * (like '<subquery1> AND <subquery2> AND NOT <subquery3> OR <subquery4>')
  457. */
  458. private function _calculateNonConjunctionResult()
  459. {
  460. $requiredVectors = array();
  461. $requiredVectorsSizes = array();
  462. $requiredVectorsIds = array(); // is used to prevent arrays comparison
  463. $optional = array();
  464. foreach ($this->_subqueries as $subqueryId => $subquery) {
  465. if ($this->_signs[$subqueryId] === true) {
  466. // required
  467. $requiredVectors[] = $subquery->matchedDocs();
  468. $requiredVectorsSizes[] = count(end($requiredVectors));
  469. $requiredVectorsIds[] = $subqueryId;
  470. } elseif ($this->_signs[$subqueryId] === false) {
  471. // prohibited
  472. // Do nothing. matchedDocs() may include non-matching id's
  473. // Calculating prohibited vector may take significant time, but do not affect the result
  474. // Skipped.
  475. } else {
  476. // neither required, nor prohibited
  477. // array union
  478. $optional += $subquery->matchedDocs();
  479. }
  480. }
  481. // sort resvectors in order of subquery cardinality increasing
  482. array_multisort($requiredVectorsSizes, SORT_ASC, SORT_NUMERIC,
  483. $requiredVectorsIds, SORT_ASC, SORT_NUMERIC,
  484. $requiredVectors);
  485. $required = null;
  486. foreach ($requiredVectors as $nextResVector) {
  487. if($required === null) {
  488. $required = $nextResVector;
  489. } else {
  490. //$required = array_intersect_key($required, $nextResVector);
  491. /**
  492. * This code is used as workaround for array_intersect_key() slowness problem.
  493. */
  494. $updatedVector = array();
  495. foreach ($required as $id => $value) {
  496. if (isset($nextResVector[$id])) {
  497. $updatedVector[$id] = $value;
  498. }
  499. }
  500. $required = $updatedVector;
  501. }
  502. if (count($required) == 0) {
  503. // Empty result set, we don't need to check other terms
  504. break;
  505. }
  506. }
  507. if ($required !== null) {
  508. $this->_resVector = &$required;
  509. } else {
  510. $this->_resVector = &$optional;
  511. }
  512. ksort($this->_resVector, SORT_NUMERIC);
  513. }
  514. /**
  515. * Score calculator for conjunction queries (all subqueries are required)
  516. *
  517. * @param integer $docId
  518. * @param Zend_Search_Lucene_Interface $reader
  519. * @return float
  520. */
  521. public function _conjunctionScore($docId, Zend_Search_Lucene_Interface $reader)
  522. {
  523. if ($this->_coord === null) {
  524. $this->_coord = $reader->getSimilarity()->coord(count($this->_subqueries),
  525. count($this->_subqueries) );
  526. }
  527. $score = 0;
  528. foreach ($this->_subqueries as $subquery) {
  529. $subscore = $subquery->score($docId, $reader);
  530. if ($subscore == 0) {
  531. return 0;
  532. }
  533. $score += $subquery->score($docId, $reader) * $this->_coord;
  534. }
  535. return $score * $this->_coord * $this->getBoost();
  536. }
  537. /**
  538. * Score calculator for non conjunction queries (not all subqueries are required)
  539. *
  540. * @param integer $docId
  541. * @param Zend_Search_Lucene_Interface $reader
  542. * @return float
  543. */
  544. public function _nonConjunctionScore($docId, Zend_Search_Lucene_Interface $reader)
  545. {
  546. if ($this->_coord === null) {
  547. $this->_coord = array();
  548. $maxCoord = 0;
  549. foreach ($this->_signs as $sign) {
  550. if ($sign !== false /* not prohibited */) {
  551. $maxCoord++;
  552. }
  553. }
  554. for ($count = 0; $count <= $maxCoord; $count++) {
  555. $this->_coord[$count] = $reader->getSimilarity()->coord($count, $maxCoord);
  556. }
  557. }
  558. $score = 0;
  559. $matchedSubqueries = 0;
  560. foreach ($this->_subqueries as $subqueryId => $subquery) {
  561. $subscore = $subquery->score($docId, $reader);
  562. // Prohibited
  563. if ($this->_signs[$subqueryId] === false && $subscore != 0) {
  564. return 0;
  565. }
  566. // is required, but doen't match
  567. if ($this->_signs[$subqueryId] === true && $subscore == 0) {
  568. return 0;
  569. }
  570. if ($subscore != 0) {
  571. $matchedSubqueries++;
  572. $score += $subscore;
  573. }
  574. }
  575. return $score * $this->_coord[$matchedSubqueries] * $this->getBoost();
  576. }
  577. /**
  578. * Execute query in context of index reader
  579. * It also initializes necessary internal structures
  580. *
  581. * @param Zend_Search_Lucene_Interface $reader
  582. * @param Zend_Search_Lucene_Index_DocsFilter|null $docsFilter
  583. */
  584. public function execute(Zend_Search_Lucene_Interface $reader, $docsFilter = null)
  585. {
  586. // Initialize weight if it's not done yet
  587. $this->_initWeight($reader);
  588. if ($docsFilter === null) {
  589. // Create local documents filter if it's not provided by upper query
  590. require_once 'Zend/Search/Lucene/Index/DocsFilter.php';
  591. $docsFilter = new Zend_Search_Lucene_Index_DocsFilter();
  592. }
  593. foreach ($this->_subqueries as $subqueryId => $subquery) {
  594. if ($this->_signs == null || $this->_signs[$subqueryId] === true) {
  595. // Subquery is required
  596. $subquery->execute($reader, $docsFilter);
  597. } else {
  598. $subquery->execute($reader);
  599. }
  600. }
  601. if ($this->_signs === null) {
  602. $this->_calculateConjunctionResult();
  603. } else {
  604. $this->_calculateNonConjunctionResult();
  605. }
  606. }
  607. /**
  608. * Get document ids likely matching the query
  609. *
  610. * It's an array with document ids as keys (performance considerations)
  611. *
  612. * @return array
  613. */
  614. public function matchedDocs()
  615. {
  616. return $this->_resVector;
  617. }
  618. /**
  619. * Score specified document
  620. *
  621. * @param integer $docId
  622. * @param Zend_Search_Lucene_Interface $reader
  623. * @return float
  624. */
  625. public function score($docId, Zend_Search_Lucene_Interface $reader)
  626. {
  627. if (isset($this->_resVector[$docId])) {
  628. if ($this->_signs === null) {
  629. return $this->_conjunctionScore($docId, $reader);
  630. } else {
  631. return $this->_nonConjunctionScore($docId, $reader);
  632. }
  633. } else {
  634. return 0;
  635. }
  636. }
  637. /**
  638. * Return query terms
  639. *
  640. * @return array
  641. */
  642. public function getQueryTerms()
  643. {
  644. $terms = array();
  645. foreach ($this->_subqueries as $id => $subquery) {
  646. if ($this->_signs === null || $this->_signs[$id] !== false) {
  647. $terms = array_merge($terms, $subquery->getQueryTerms());
  648. }
  649. }
  650. return $terms;
  651. }
  652. /**
  653. * Query specific matches highlighting
  654. *
  655. * @param Zend_Search_Lucene_Search_Highlighter_Interface $highlighter Highlighter object (also contains doc for highlighting)
  656. */
  657. protected function _highlightMatches(Zend_Search_Lucene_Search_Highlighter_Interface $highlighter)
  658. {
  659. foreach ($this->_subqueries as $id => $subquery) {
  660. if ($this->_signs === null || $this->_signs[$id] !== false) {
  661. $subquery->_highlightMatches($highlighter);
  662. }
  663. }
  664. }
  665. /**
  666. * Print a query
  667. *
  668. * @return string
  669. */
  670. public function __toString()
  671. {
  672. // It's used only for query visualisation, so we don't care about characters escaping
  673. $query = '';
  674. foreach ($this->_subqueries as $id => $subquery) {
  675. if ($id != 0) {
  676. $query .= ' ';
  677. }
  678. if ($this->_signs === null || $this->_signs[$id] === true) {
  679. $query .= '+';
  680. } else if ($this->_signs[$id] === false) {
  681. $query .= '-';
  682. }
  683. $query .= '(' . $subquery->__toString() . ')';
  684. }
  685. if ($this->getBoost() != 1) {
  686. $query = '(' . $query . ')^' . round($this->getBoost(), 4);
  687. }
  688. return $query;
  689. }
  690. }