PageRenderTime 75ms CodeModel.GetById 1ms app.highlight 60ms RepoModel.GetById 1ms app.codeStats 1ms

/zf/library/Zend/Search/Lucene/Search/Query/Boolean.php

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