Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.12% covered (success)
94.12%
32 / 34
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
QueryBuilder
94.12% covered (success)
94.12%
32 / 34
66.67% covered (warning)
66.67%
4 / 6
15.05
0.00% covered (danger)
0.00%
0 / 1
 build
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 abstractQueryToString
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 queryGroupToString
94.44% covered (success)
94.44%
17 / 18
0.00% covered (danger)
0.00%
0 / 1
7.01
 queryToString
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getLuceneHelper
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setLuceneHelper
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * Summon QueryBuilder.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2010.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 * @category VuFind
24 * @package  Search
25 * @author   Andrew S. Nagy <vufind-tech@lists.sourceforge.net>
26 * @author   David Maus <maus@hab.de>
27 * @author   Demian Katz <demian.katz@villanova.edu>
28 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
29 * @link     https://vufind.org
30 */
31
32namespace VuFindSearch\Backend\Summon;
33
34use VuFindSearch\Backend\Solr\LuceneSyntaxHelper;
35use VuFindSearch\ParamBag;
36use VuFindSearch\Query\AbstractQuery;
37use VuFindSearch\Query\Query;
38use VuFindSearch\Query\QueryGroup;
39
40use function count;
41
42/**
43 * Summon QueryBuilder.
44 *
45 * @category VuFind
46 * @package  Search
47 * @author   Andrew S. Nagy <vufind-tech@lists.sourceforge.net>
48 * @author   David Maus <maus@hab.de>
49 * @author   Demian Katz <demian.katz@villanova.edu>
50 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
51 * @link     https://vufind.org
52 */
53class QueryBuilder
54{
55    /**
56     * Lucene syntax helper
57     *
58     * @var LuceneSyntaxHelper
59     */
60    protected $luceneHelper = null;
61
62    /// Public API
63
64    /**
65     * Return Summon search parameters based on a user query and params.
66     *
67     * @param AbstractQuery $query  User query
68     * @param ?ParamBag     $params Search backend parameters
69     *
70     * @return ParamBag
71     *
72     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
73     */
74    public function build(AbstractQuery $query, ?ParamBag $params = null)
75    {
76        // Build base query
77        $queryStr = $this->abstractQueryToString($query);
78
79        // Send back results
80        $newParams = new ParamBag();
81        $newParams->set('query', $queryStr);
82        return $newParams;
83    }
84
85    /// Internal API
86
87    /**
88     * Convert an AbstractQuery object to a query string.
89     *
90     * @param AbstractQuery $query Query to convert
91     *
92     * @return string
93     */
94    protected function abstractQueryToString(AbstractQuery $query)
95    {
96        if ($query instanceof Query) {
97            return $this->queryToString($query);
98        } else {
99            return $this->queryGroupToString($query);
100        }
101    }
102
103    /**
104     * Convert a QueryGroup object to a query string.
105     *
106     * @param QueryGroup $query QueryGroup to convert
107     *
108     * @return string
109     */
110    protected function queryGroupToString(QueryGroup $query)
111    {
112        $groups = $excludes = [];
113
114        foreach ($query->getQueries() as $params) {
115            // Advanced Search
116            if ($params instanceof QueryGroup) {
117                $thisGroup = [];
118                // Process each search group
119                foreach ($params->getQueries() as $group) {
120                    // Build this group individually as a basic search
121                    $thisGroup[] = $this->abstractQueryToString($group);
122                }
123                // Is this an exclusion (NOT) group or a normal group?
124                if ($params->isNegated()) {
125                    $excludes[] = implode(' OR ', $thisGroup);
126                } else {
127                    $groups[]
128                        = implode(' ' . $params->getOperator() . ' ', $thisGroup);
129                }
130            } else {
131                // Basic Search
132                $groups[] = $this->queryToString($params);
133            }
134        }
135
136        // Put our advanced search together
137        $queryStr = '';
138        if (count($groups) > 0) {
139            $queryStr
140                .= '(' . implode(') ' . $query->getOperator() . ' (', $groups) . ')';
141        }
142        // and concatenate exclusion after that
143        if (count($excludes) > 0) {
144            $queryStr .= ' NOT ((' . implode(') OR (', $excludes) . '))';
145        }
146
147        return $queryStr;
148    }
149
150    /**
151     * Convert a single Query object to a query string.
152     *
153     * @param Query $query Query to convert
154     *
155     * @return string
156     */
157    protected function queryToString(Query $query)
158    {
159        // Clean and validate input:
160        $index = $query->getHandler();
161        $lookfor = $query->getString();
162
163        // Force boolean operators to uppercase if we are in a
164        // case-insensitive mode:
165        $lookfor = $this->getLuceneHelper()
166            ->capitalizeCaseInsensitiveBooleans($lookfor);
167
168        // Prepend the index name, unless it's the special "AllFields"
169        // index:
170        return ($index != 'AllFields') ? "{$index}:($lookfor)" : $lookfor;
171    }
172
173    /**
174     * Get Lucene syntax helper
175     *
176     * @return LuceneSyntaxHelper
177     */
178    public function getLuceneHelper()
179    {
180        if (null === $this->luceneHelper) {
181            $this->luceneHelper = new LuceneSyntaxHelper();
182        }
183        return $this->luceneHelper;
184    }
185
186    /**
187     * Set Lucene syntax helper
188     *
189     * @param LuceneSyntaxHelper $helper Lucene syntax helper
190     *
191     * @return void
192     */
193    public function setLuceneHelper(LuceneSyntaxHelper $helper)
194    {
195        $this->luceneHelper = $helper;
196    }
197}