Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.30% covered (success)
97.30%
36 / 37
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
QueryBuilder
97.30% covered (success)
97.30%
36 / 37
80.00% covered (warning)
80.00%
4 / 5
15
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 build
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 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%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3/**
4 * WorldCat 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\WorldCat;
33
34use VuFindSearch\ParamBag;
35use VuFindSearch\Query\AbstractQuery;
36use VuFindSearch\Query\Query;
37use VuFindSearch\Query\QueryGroup;
38
39use function count;
40
41/**
42 * WorldCat QueryBuilder.
43 *
44 * @category VuFind
45 * @package  Search
46 * @author   Andrew S. Nagy <vufind-tech@lists.sourceforge.net>
47 * @author   David Maus <maus@hab.de>
48 * @author   Demian Katz <demian.katz@villanova.edu>
49 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
50 * @link     https://vufind.org
51 */
52class QueryBuilder
53{
54    /**
55     * OCLC code to exclude from results
56     *
57     * @var string
58     */
59    protected $oclcCodeToExclude;
60
61    /// Public API
62
63    /**
64     * Constructor
65     *
66     * @param string $exclude OCLC code to exclude from results
67     */
68    public function __construct($exclude = null)
69    {
70        $this->oclcCodeToExclude = $exclude;
71    }
72
73    /**
74     * Return WorldCat search parameters based on a user query and params.
75     *
76     * @param AbstractQuery $query  User query
77     * @param ?ParamBag     $params Search backend parameters
78     *
79     * @return ParamBag
80     *
81     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
82     */
83    public function build(AbstractQuery $query, ?ParamBag $params = null)
84    {
85        // Build base query
86        $queryStr = $this->abstractQueryToString($query);
87
88        // Exclude current library from results (if applicable)
89        if (null !== $this->oclcCodeToExclude) {
90            $queryStr .= ' not srw.li all "' . $this->oclcCodeToExclude . '"';
91        }
92
93        // Send back results
94        $newParams = new ParamBag();
95        $newParams->set('query', $queryStr);
96        return $newParams;
97    }
98
99    /// Internal API
100
101    /**
102     * Convert an AbstractQuery object to a query string.
103     *
104     * @param AbstractQuery $query Query to convert
105     *
106     * @return string
107     */
108    protected function abstractQueryToString(AbstractQuery $query)
109    {
110        if ($query instanceof Query) {
111            return $this->queryToString($query);
112        } else {
113            return $this->queryGroupToString($query);
114        }
115    }
116
117    /**
118     * Convert a QueryGroup object to a query string.
119     *
120     * @param QueryGroup $query QueryGroup to convert
121     *
122     * @return string
123     */
124    protected function queryGroupToString(QueryGroup $query)
125    {
126        $groups = $excludes = [];
127
128        foreach ($query->getQueries() as $params) {
129            // Advanced Search
130            if ($params instanceof QueryGroup) {
131                $thisGroup = [];
132                // Process each search group
133                foreach ($params->getQueries() as $group) {
134                    // Build this group individually as a basic search
135                    $thisGroup[] = $this->abstractQueryToString($group);
136                }
137                // Is this an exclusion (NOT) group or a normal group?
138                if ($params->isNegated()) {
139                    $excludes[] = implode(' OR ', $thisGroup);
140                } else {
141                    $groups[]
142                        = implode(' ' . $params->getOperator() . ' ', $thisGroup);
143                }
144            } else {
145                // Basic Search
146                $groups[] = $this->queryToString($params);
147            }
148        }
149
150        // Put our advanced search together
151        $queryStr = '';
152        if (count($groups) > 0) {
153            $queryStr
154                .= '(' . implode(') ' . $query->getOperator() . ' (', $groups) . ')';
155        }
156        // and concatenate exclusion after that
157        if (count($excludes) > 0) {
158            $queryStr .= ' NOT ((' . implode(') OR (', $excludes) . '))';
159        }
160
161        return $queryStr;
162    }
163
164    /**
165     * Convert a single Query object to a query string.
166     *
167     * @param Query $query Query to convert
168     *
169     * @return string
170     */
171    protected function queryToString(Query $query)
172    {
173        // Clean and validate input:
174        $index = $query->getHandler();
175        if (empty($index)) {
176            // No handler?  Just accept query string as-is; no modifications needed.
177            return $query->getString();
178        }
179        $lookfor = str_replace('"', '', $query->getString());
180
181        // The index may contain multiple parts -- we want to search all listed index
182        // fields:
183        $index = explode(':', $index);
184        $clauses = [];
185        foreach ($index as $currentIndex) {
186            $clauses[] = "{$currentIndex} all \"{$lookfor}\"";
187        }
188
189        return '(' . implode(' OR ', $clauses) . ')';
190    }
191}