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