Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
8.93% covered (danger)
8.93%
5 / 56
0.00% covered (danger)
0.00%
0 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
Connector
8.93% covered (danger)
8.93%
5 / 56
0.00% covered (danger)
0.00%
0 / 13
497.09
0.00% covered (danger)
0.00%
0 / 1
 __construct
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
3.21
 init
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 query
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
72
 send
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 ping
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 record
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 search
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 session
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 settings
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 show
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 stat
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 termlist
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 bytarget
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * Central class for connecting to Pazpar2 resources used by VuFind.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2011.
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  Connection
25 * @author   Chris Hallberg <challber@villanova.edu>
26 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
27 * @link     https://vufind.org/wiki/development:architecture Wiki
28 */
29
30namespace VuFindSearch\Backend\Pazpar2;
31
32use Laminas\Http\Client;
33use Laminas\Http\Request;
34use VuFindSearch\Backend\Exception\HttpErrorException;
35use VuFindSearch\ParamBag;
36
37use function sprintf;
38
39/**
40 * Central class for connecting to resources used by VuFind.
41 *
42 * @category VuFind
43 * @package  Connection
44 * @author   Chris Hallberg <challber@villanova.edu>
45 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
46 * @link     https://vufind.org/wiki/development:architecture Wiki
47 */
48class Connector implements \Laminas\Log\LoggerAwareInterface
49{
50    use \VuFind\Log\LoggerAwareTrait;
51
52    /**
53     * Base url for searches
54     *
55     * @var string
56     */
57    protected $base;
58
59    /**
60     * The HTTP_Request object used for REST transactions
61     *
62     * @var Client
63     */
64    protected $client;
65
66    /**
67     * Session ID
68     *
69     * @var string
70     */
71    protected $session = false;
72
73    /**
74     * Constructor
75     *
76     * @param string $base     Base URL for Pazpar2
77     * @param Client $client   An HTTP client object
78     * @param bool   $autoInit Should we auto-initialize the Pazpar2 connection?
79     */
80    public function __construct($base, Client $client, $autoInit = false)
81    {
82        $this->base = $base;
83        if (empty($this->base)) {
84            throw new \Exception('Missing Pazpar2 base URL.');
85        }
86
87        $this->client = $client;
88        $this->client->setMethod(Request::METHOD_GET);  // always use GET
89
90        if ($autoInit) {
91            $this->init();
92        }
93    }
94
95    /**
96     * Initializes a session. Returns session ID to be used in subsequent requests.
97     * Adds session to the base
98     *
99     * @return session id
100     */
101    public function init()
102    {
103        $this->session = false; // clear any existing session
104        $session = $this->query('init');
105        if (!isset($session->session)) {
106            throw new \Exception('Session initialization failed.');
107        }
108        $this->session = $session->session;
109        return $session;
110    }
111
112    /**
113     * Requests and receives information from pazpar
114     *
115     * @param string   $command the command to be executed
116     * @param ParamBag $data    optional extra data
117     *
118     * @return SimpleXMLElement Response
119     */
120    protected function query($command, ParamBag $data = null)
121    {
122        // If we don't have a session as long as we're not being explicit
123        if (!$this->session && $command !== 'init') {
124            $this->init();
125        }
126
127        // Don't change input when manipulating parameters:
128        $params = (null === $data) ? new ParamBag() : clone $data;
129
130        // Add session and command:
131        if ($this->session) {
132            $params->set('session', $this->session);
133        }
134        $params->set('command', $command);
135
136        $this->client->setUri($this->base . '?' . implode('&', $params->request()));
137        $xmlStr = $this->send($this->client);
138        $xml = simplexml_load_string($xmlStr);
139
140        // If our session has expired, start a new session
141        if (
142            $command !== 'init'
143            && $xml->session == $this->session && isset($this->session)
144        ) {
145            $this->init();
146            return $this->query($command, $data);
147        }
148        return $xml;
149    }
150
151    /**
152     * Send a request and return the response.
153     *
154     * @param Client $client Prepare HTTP client
155     *
156     * @return string Response body
157     *
158     * @throws \VuFindSearch\Backend\Exception\RemoteErrorException  Server
159     * signaled a server error (HTTP 5xx)
160     * @throws \VuFindSearch\Backend\Exception\RequestErrorException Server
161     * signaled a client error (HTTP 4xx)
162     */
163    protected function send(Client $client)
164    {
165        $this->debug(
166            sprintf('=> %s %s', $client->getMethod(), $client->getUri())
167        );
168
169        $time     = microtime(true);
170        $response = $client->send();
171        $time     = microtime(true) - $time;
172
173        $this->debug(
174            sprintf(
175                '<= %s %s',
176                $response->getStatusCode(),
177                $response->getReasonPhrase()
178            ),
179            ['time' => $time]
180        );
181
182        if (!$response->isSuccess()) {
183            throw HttpErrorException::createFromResponse($response);
184        }
185        return $response->getBody();
186    }
187
188    /**
189     * Keeps a session alive. An idle session will time out after one minute.
190     * The ping command can be used to keep the session alive absent other activity.
191     * It is suggested that any browser client have a simple alarm handler
192     * which sends a ping every 50 seconds or so once a session has been initialized
193     *
194     * @return void
195     */
196    public function ping()
197    {
198        $this->query('ping');
199    }
200
201    /**
202     * Retrieves a detailed record.
203     * Unlike the show command, this command returns
204     * metadata records before merging takes place.
205     *
206     * @param string $id array of options as described above
207     *
208     * @return associative array of XML data
209     */
210    public function record($id)
211    {
212        return $this->query('record', new ParamBag(['id' => $id]));
213    }
214
215    /**
216     * Launches a search.
217     *
218     * Option (default):
219     *  - query     : search string ('')
220     *  - filter    : setting+operator+args pairs, such as 'pz:id=4|17, pz:id~3'
221     *  - limit     : Narrows the search by one or more fields (typically facets)
222     *                as name=arg1|arg2| pairs separated by comma (none)
223     *  - startrecs : int (0)
224     *  - maxrecs   : int (100)
225     *
226     * TODO: Make the array more useful to get the correct format?
227     *
228     * @param ParamBag $options array of options as described above
229     *
230     * @return associative array of XML data
231     */
232    public function search(ParamBag $options = null)
233    {
234        return $this->query('search', $options);
235    }
236
237    /**
238     * Return session id
239     *
240     * @return session id
241     */
242    public function session()
243    {
244        return $this->session;
245    }
246
247    /**
248     * Applies settings to this session
249     * Each setting parameter has the form name[target]=value
250     *
251     * TODO: Make the array more useful to get the correct format?
252     *
253     * @param string $settings settings to be sets
254     *
255     * @return bool Success/failure status
256     */
257    public function settings($settings = false)
258    {
259        if ($settings === false) {
260            return false;
261        }
262        $set = $this->query('settings', $settings);
263        return $set->status == 'OK';
264    }
265
266    /**
267     * Proper alias of results
268     *
269     * Options (default):
270     *  - start : int (0)
271     *  - num   : int (20)
272     *  - block : 1 = wait until enough records are found (0)
273     *  - sort  : column:1 [increasing] or 0 [decreasing] (none)
274     *
275     * @param ParamBag $options array of options as described above
276     *
277     * @return array Associative array of XML data
278     */
279    public function show(ParamBag $options = null)
280    {
281        return $this->query('show', $options);
282    }
283
284    /**
285     * Provides status information about an ongoing search.
286     *
287     * @return associative array of XML data
288     */
289    public function stat()
290    {
291        return $this->query('stat');
292    }
293
294    /**
295     * Retrieves term list(s).
296     *
297     * Options (default):
298     *  - name : comma-separated list of termlist names (all termlists)
299     *  - num  : maximum number of entries to return (15)
300     *
301     * @param ParamBag $options array of options as described above
302     *
303     * @return array Associative array of XML data
304     */
305    public function termlist(ParamBag $options = null)
306    {
307        return $this->query('termlist', $options);
308    }
309
310    /**
311     * Returns information about the status of each active client.
312     *
313     * @param string $id client id
314     *
315     * @return array Associative array of XML data
316     */
317    public function bytarget($id)
318    {
319        return $this->query('bytarget', new ParamBag(['id' => $id]));
320    }
321}