Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
66.28% covered (warning)
66.28%
114 / 172
23.94% covered (danger)
23.94%
17 / 71
CRAP
0.00% covered (danger)
0.00%
0 / 1
ComposedDriver
66.28% covered (warning)
66.28%
114 / 172
23.94% covered (danger)
23.94%
17 / 71
408.53
0.00% covered (danger)
0.00%
0 / 1
 init
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 cancelHolds
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 cancelILLRequests
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 cancelStorageRetrievalRequests
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 changePassword
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 checkILLRequestIsValid
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 checkRequestIsValid
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 checkStorageRetrievalRequestIsValid
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 findReserves
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAccountBlocks
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCancelHoldDetails
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCancelHoldLink
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCancelILLRequestDetails
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCancelStorageRetrievalRequestDetails
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getConfig
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getConsortialHoldings
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getCourses
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDefaultPickUpLocation
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDepartments
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFunds
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHoldDefaultRequiredDate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHolding
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getHoldLink
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getILLPickupLibraries
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getILLPickupLocations
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getInstructors
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMyFines
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMyHolds
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMyILLRequests
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMyProfile
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMyStorageRetrievalRequests
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMyTransactionHistory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMyTransactions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getNewItems
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOfflineMode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPickUpLocations
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getProxiedUsers
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPurchaseHistory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRenewDetails
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRequestBlocks
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRequestGroups
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getStatus
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStatuses
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSuppressedAuthorityRecords
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSuppressedRecords
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUrlsForRecord
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasHoldings
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 loginIsHidden
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 patronLogin
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 placeHold
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 placeILLRequest
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 placeStorageRetrievalRequest
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 purgeTransactionHistory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 renewMyItems
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 renewMyItemsLink
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 supportsMethod
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 updateHolds
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLoginDrivers
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDefaultLoginDriver
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDefaultRequestGroup
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __call
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 callDriverMethod
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getMainDriverNameForMethod
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 defaultCall
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 mergeSingleArrayResults
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 combineArraysOfAssociativeArrays
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
2
 combineMultipleArraysOfAssociativeArrays
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
4
 extractResultSubfields
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 mergeInSubfields
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 mergeAssociativeArrays
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 extractKey
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3/**
4 * Composed Driver.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Hebis Verbundzentrale 2023.
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  ILSdrivers
25 * @author   Thomas Wagener <wagener@hebis.uni-frankfurt.de>
26 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
27 * @link     https://vufind.org/wiki/development:plugins:ils_drivers Wiki
28 */
29
30namespace VuFind\ILS\Driver;
31
32use VuFind\Date\DateException;
33use VuFind\Exception\ILS as ILSException;
34
35use function call_user_func_array;
36use function count;
37use function func_get_args;
38use function in_array;
39
40/**
41 * Composed Driver.
42 *
43 * ILS Driver for VuFind to use multiple drivers for different tasks and
44 * combine their results.
45 *
46 * @category VuFind
47 * @package  ILSdrivers
48 * @author   Thomas Wagener <wagener@hebis.uni-frankfurt.de>
49 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
50 * @link     https://vufind.org/wiki/development:plugins:ils_drivers Wiki
51 */
52class ComposedDriver extends AbstractMultiDriver
53{
54    /**
55     * Name of the main driver
56     *
57     * @var string
58     */
59    protected $mainDriver;
60
61    /**
62     * Initialize the driver.
63     *
64     * Validate configuration and perform all resource-intensive tasks needed to
65     * make the driver active.
66     *
67     * @throws ILSException
68     * @return void
69     */
70    public function init()
71    {
72        parent::init();
73        if (!($this->mainDriver = $this->config['General']['main_driver'] ?? false)) {
74            throw new ILSException('Main driver needs to be set.');
75        }
76    }
77
78    /**
79     * Cancel Holds
80     *
81     * Attempts to Cancel a hold or recall on a particular item. The
82     * data in $cancelDetails['details'] is determined by getCancelHoldDetails().
83     *
84     * @param array $cancelDetails An array of item and patron data
85     *
86     * @return array               An array of data on each request including
87     * whether or not it was successful and a system message (if available)
88     */
89    public function cancelHolds($cancelDetails)
90    {
91        return $this->defaultCall('cancelHolds', func_get_args());
92    }
93
94    /**
95     * Cancel ILL Requests
96     *
97     * Attempts to Cancel an ILL request on a particular item. The
98     * data in $cancelDetails['details'] is determined by
99     * getCancelILLRequestDetails().
100     *
101     * @param array $cancelDetails An array of item and patron data
102     *
103     * @return array               An array of data on each request including
104     * whether or not it was successful and a system message (if available)
105     */
106    public function cancelILLRequests($cancelDetails)
107    {
108        return $this->defaultCall('cancelILLRequests', func_get_args());
109    }
110
111    /**
112     * Cancel Call Slips
113     *
114     * Attempts to Cancel a call slip on a particular item. The
115     * data in $cancelDetails['details'] is determined by
116     * getCancelStorageRetrievalRequestDetails().
117     *
118     * @param array $cancelDetails An array of item and patron data
119     *
120     * @return array               An array of data on each request including
121     * whether or not it was successful and a system message (if available)
122     */
123    public function cancelStorageRetrievalRequests($cancelDetails)
124    {
125        return $this->defaultCall('cancelStorageRetrievalRequests', func_get_args());
126    }
127
128    /**
129     * Change Password
130     *
131     * Attempts to change patron password (PIN code)
132     *
133     * @param array $details An array of patron id and old and new password
134     *
135     * @return mixed An array of data on the request including
136     * whether or not it was successful and a system message (if available)
137     */
138    public function changePassword($details)
139    {
140        return $this->defaultCall('changePassword', func_get_args());
141    }
142
143    /**
144     * Check whether an ILL request is valid
145     *
146     * This is responsible for determining if an item is requestable
147     *
148     * @param string $id     The Bib ID
149     * @param array  $data   An Array of item data
150     * @param array  $patron An array of patron data
151     *
152     * @return mixed An array of data on the request including
153     * whether or not it is valid and a status message. Alternatively a boolean
154     * true if request is valid, false if not.
155     */
156    public function checkILLRequestIsValid($id, $data, $patron)
157    {
158        return $this->defaultCall('checkILLRequestIsValid', func_get_args());
159    }
160
161    /**
162     * Check whether a hold or recall request is valid
163     *
164     * This is responsible for determining if an item is requestable
165     *
166     * @param string $id     The Bib ID
167     * @param array  $data   An Array of item data
168     * @param array  $patron An array of patron data
169     *
170     * @return mixed An array of data on the request including
171     * whether or not it is valid and a status message. Alternatively a boolean
172     * true if request is valid, false if not.
173     */
174    public function checkRequestIsValid($id, $data, $patron)
175    {
176        return $this->defaultCall('checkRequestIsValid', func_get_args());
177    }
178
179    /**
180     * Check whether a storage retrieval request is valid
181     *
182     * This is responsible for determining if an item is requestable
183     *
184     * @param string $id     The Bib ID
185     * @param array  $data   An Array of item data
186     * @param array  $patron An array of patron data
187     *
188     * @return mixed An array of data on the request including
189     * whether or not it is valid and a status message. Alternatively a boolean
190     * true if request is valid, false if not.
191     */
192    public function checkStorageRetrievalRequestIsValid($id, $data, $patron)
193    {
194        return $this->defaultCall('checkStorageRetrievalRequestIsValid', func_get_args());
195    }
196
197    /**
198     * Find Reserves
199     *
200     * Obtain information on course reserves.
201     *
202     * @param string $course ID from getCourses (empty string to match all)
203     * @param string $inst   ID from getInstructors (empty string to match all)
204     * @param string $dept   ID from getDepartments (empty string to match all)
205     *
206     * @return mixed An array of associative arrays representing reserve items
207     */
208    public function findReserves($course, $inst, $dept)
209    {
210        return $this->defaultCall('findReserves', func_get_args());
211    }
212
213    /**
214     * Check whether the patron has any blocks on their account.
215     *
216     * @param array $patron Patron data from patronLogin().
217     *
218     * @return mixed A boolean false if no blocks are in place and an array
219     * of block reasons if blocks are in place
220     */
221    public function getAccountBlocks($patron)
222    {
223        return $this->defaultCall('getAccountBlocks', func_get_args());
224    }
225
226    /**
227     * Get Cancel Hold Details
228     *
229     * In order to cancel a hold, the ILS requires some information on the hold.
230     * This function returns the required information, which is then submitted
231     * as form data in Hold.php. This value is then extracted by the CancelHolds
232     * function.
233     *
234     * @param array $hold   A single hold array from getMyHolds
235     * @param array $patron Patron information from patronLogin
236     *
237     * @return string Data for use in a form field
238     */
239    public function getCancelHoldDetails($hold, $patron = [])
240    {
241        return $this->defaultCall('getCancelHoldDetails', func_get_args());
242    }
243
244    /**
245     * Get Cancel Hold Link
246     *
247     * @param array $holdDetails Hold Details
248     * @param array $patron      Patron
249     *
250     * @return string URL to native OPAC
251     */
252    public function getCancelHoldLink($holdDetails, $patron)
253    {
254        return $this->defaultCall('getCancelHoldLink', func_get_args());
255    }
256
257    /**
258     * Get Cancel ILL Request Details
259     *
260     * In order to cancel an ILL request, the ILS requires some information on the
261     * request. This function returns the required information, which is then
262     * submitted as form data. This value is then extracted by the CancelILLRequests
263     * function.
264     *
265     * @param array $details An array of item data
266     *
267     * @return string Data for use in a form field
268     */
269    public function getCancelILLRequestDetails($details)
270    {
271        return $this->defaultCall('getCancelILLRequestDetails', func_get_args());
272    }
273
274    /**
275     * Get Cancel Call Slip Details
276     *
277     * In order to cancel a call slip, the ILS requires some information on it.
278     * This function returns the required information, which is then submitted
279     * as form data. This value is then extracted by the
280     * CancelStorageRetrievalRequests function.
281     *
282     * @param array $details An array of item data
283     *
284     * @return string Data for use in a form field
285     */
286    public function getCancelStorageRetrievalRequestDetails($details)
287    {
288        return $this->defaultCall('getCancelStorageRetrievalRequestDetails', func_get_args());
289    }
290
291    /**
292     * Function which specifies renew, hold and cancel settings.
293     *
294     * @param string $function The name of the feature to be checked
295     * @param array  $params   Optional feature-specific parameters (array)
296     *
297     * @return array An array with key-value pairs.
298     */
299    public function getConfig($function, $params = null)
300    {
301        return $this->defaultCall('getConfig', func_get_args());
302    }
303
304    /**
305     * Get Consortial Holdings
306     *
307     * This is responsible for retrieving the holding information of a certain
308     * consortial record.
309     *
310     * @param string $id     The record id to retrieve the holdings for
311     * @param array  $patron Patron data
312     * @param array  $ids    The (consortial) source records for the record id
313     *
314     * @return array         On success, an associative array with the following
315     *  keys: id, availability (boolean), status, location, reserve, callnumber,
316     *  duedate, number, barcode.
317     * @throws ILSException
318     * @throws DateException
319     */
320    public function getConsortialHoldings($id, $patron, $ids)
321    {
322        return $this->combineArraysOfAssociativeArrays(
323            'getConsortialHoldings',
324            func_get_args(),
325            ['holdings', 'electronic_holdings']
326        );
327    }
328
329    /**
330     * Get Courses
331     *
332     * Obtain a list of courses for use in limiting the reserves list.
333     *
334     * @return array An associative array with key = ID, value = name.
335     */
336    public function getCourses()
337    {
338        return $this->defaultCall('getCourses', func_get_args());
339    }
340
341    /**
342     * Get Default Pick Up Location
343     *
344     * Returns the default pick up location
345     *
346     * @param array $patron      Patron information returned by the patronLogin
347     * method.
348     * @param array $holdDetails Optional array, only passed in when getting a list
349     * in the context of placing a hold; contains most of the same values passed to
350     * placeHold, minus the patron data. May be used to limit the pickup options
351     * or may be ignored.
352     *
353     * @return string A location ID
354     */
355    public function getDefaultPickUpLocation($patron = false, $holdDetails = null)
356    {
357        return $this->defaultCall('getDefaultPickUpLocation', func_get_args());
358    }
359
360    /**
361     * Get Departments
362     *
363     * Obtain a list of departments for use in limiting the reserves list.
364     *
365     * @return array An associative array with key = dept. ID, value = dept. name.
366     */
367    public function getDepartments()
368    {
369        return $this->defaultCall('getDepartments', func_get_args());
370    }
371
372    /**
373     * Get Funds
374     *
375     * Return a list of funds which may be used to limit the getNewItems list.
376     *
377     * @throws ILSException
378     * @return array An associative array with key = fund ID, value = fund name.
379     */
380    public function getFunds()
381    {
382        return $this->defaultCall('getFunds', func_get_args());
383    }
384
385    /**
386     * Get Default "Hold Required By" Date (as Unix timestamp) or null if unsupported
387     *
388     * @param array $patron   Patron information returned by the patronLogin method.
389     * @param array $holdInfo Contains most of the same values passed to
390     * placeHold, minus the patron data.
391     *
392     * @return int|null
393     */
394    public function getHoldDefaultRequiredDate($patron, $holdInfo)
395    {
396        return $this->defaultCall('getHoldDefaultRequiredDate', func_get_args());
397    }
398
399    /**
400     * Get Holding
401     *
402     * This is responsible for retrieving the holding information of a certain
403     * record.
404     *
405     * @param string $id      The record id to retrieve the holdings for
406     * @param array  $patron  Patron data
407     * @param array  $options Extra options (not currently used)
408     *
409     * @return array         On success, an associative array with the following
410     * keys: id, availability (boolean), status, location, reserve, callnumber,
411     * duedate, number, barcode.
412     *
413     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
414     */
415    public function getHolding($id, array $patron = null, array $options = [])
416    {
417        return $this->combineArraysOfAssociativeArrays(
418            'getHolding',
419            func_get_args(),
420            ['holdings', 'electronic_holdings']
421        );
422    }
423
424    /**
425     * Get Hold Link
426     *
427     * The goal for this method is to return a URL to a "place hold" web page on
428     * the ILS OPAC. This is used for ILSs that do not support an API or method
429     * to place Holds.
430     *
431     * @param string $id      The id of the bib record
432     * @param array  $details Item details from getHoldings return array
433     *
434     * @return string         URL to ILS's OPAC's place hold screen.
435     */
436    public function getHoldLink($id, $details)
437    {
438        return $this->defaultCall('getHoldLink', func_get_args());
439    }
440
441    /**
442     * Get ILL Pickup Libraries
443     *
444     * This is responsible for getting information on the possible pickup libraries
445     *
446     * @param string $id     Record ID
447     * @param array  $patron Patron
448     *
449     * @return bool|array False if request not allowed, or an array of associative
450     * arrays with libraries.
451     */
452    public function getILLPickupLibraries($id, $patron)
453    {
454        return $this->defaultCall('getILLPickupLibraries', func_get_args());
455    }
456
457    /**
458     * Get ILL Pickup Locations
459     *
460     * This is responsible for getting a list of possible pickup locations for a
461     * library
462     *
463     * @param string $id        Record ID
464     * @param string $pickupLib Pickup library ID
465     * @param array  $patron    Patron
466     *
467     * @return bool|array False if request not allowed, or an array of
468     * locations.
469     */
470    public function getILLPickupLocations($id, $pickupLib, $patron)
471    {
472        return $this->defaultCall('getILLPickupLocations', func_get_args());
473    }
474
475    /**
476     * Get Instructors
477     *
478     * Obtain a list of instructors for use in limiting the reserves list.
479     *
480     * @return array An associative array with key = ID, value = name.
481     */
482    public function getInstructors()
483    {
484        return $this->defaultCall('getInstructors', func_get_args());
485    }
486
487    /**
488     * Get Patron Fines
489     *
490     * This is responsible for retrieving all fines by a specific patron.
491     *
492     * @param array $patron The patron array from patronLogin
493     *
494     * @return mixed        Array of the patron's fines on success.
495     */
496    public function getMyFines($patron)
497    {
498        return $this->combineArraysOfAssociativeArrays('getMyFines', func_get_args());
499    }
500
501    /**
502     * Get Patron Holds
503     *
504     * This is responsible for retrieving all holds by a specific patron.
505     *
506     * @param array $patron The patron array from patronLogin
507     *
508     * @return mixed      Array of the patron's holds
509     */
510    public function getMyHolds($patron)
511    {
512        return $this->combineArraysOfAssociativeArrays('getMyHolds', func_get_args());
513    }
514
515    /**
516     * Get Patron ILL Requests
517     *
518     * This is responsible for retrieving all ILL Requests by a specific patron.
519     *
520     * @param array $patron The patron array from patronLogin
521     *
522     * @return mixed      Array of the patron's ILL requests
523     */
524    public function getMyILLRequests($patron)
525    {
526        return $this->combineArraysOfAssociativeArrays('getMyILLRequests', func_get_args());
527    }
528
529    /**
530     * Get Patron Profile
531     *
532     * This is responsible for retrieving the profile for a specific patron.
533     *
534     * @param array $patron The patron array
535     *
536     * @return mixed Array of the patron's profile data
537     */
538    public function getMyProfile($patron)
539    {
540        return $this->mergeSingleArrayResults('getMyProfile', func_get_args());
541    }
542
543    /**
544     * Get Patron Call Slips
545     *
546     * This is responsible for retrieving all call slips by a specific patron.
547     *
548     * @param array $patron The patron array from patronLogin
549     *
550     * @return mixed      Array of the patron's holds
551     */
552    public function getMyStorageRetrievalRequests($patron)
553    {
554        return $this->combineArraysOfAssociativeArrays('getMyStorageRetrievalRequests', func_get_args());
555    }
556
557    /**
558     * Get Patron Loan History
559     *
560     * @param array $user   The patron array from patronLogin
561     * @param array $params Parameters
562     *
563     * @throws DateException
564     * @throws ILSException
565     * @return array      Array of the patron's historic loans on success.
566     */
567    public function getMyTransactionHistory($user, $params = null)
568    {
569        return $this->combineArraysOfAssociativeArrays('getMyTransactionHistory', func_get_args());
570    }
571
572    /**
573     * Get Patron Transactions
574     *
575     * This is responsible for retrieving all transactions (i.e. checked out items)
576     * by a specific patron.
577     *
578     * @param array $patron The patron array from patronLogin
579     *
580     * @return mixed        Array of the patron's transactions on success.
581     */
582    public function getMyTransactions($patron)
583    {
584        return $this->combineArraysOfAssociativeArrays('getMyTransactions', func_get_args(), ['records']);
585    }
586
587    /**
588     * Get New Items
589     *
590     * Retrieve the IDs of items recently added to the catalog.
591     *
592     * @param int $page    Page number of results to retrieve (counting starts at 1)
593     * @param int $limit   The size of each page of results to retrieve
594     * @param int $daysOld The maximum age of records to retrieve in days (max. 30)
595     * @param int $fundId  optional fund ID to use for limiting results (use a value
596     * returned by getFunds, or exclude for no limit); note that "fund" may be a
597     * misnomer - if funds are not an appropriate way to limit your new item
598     * results, you can return a different set of values from getFunds. The
599     * important thing is that this parameter supports an ID returned by getFunds,
600     * whatever that may mean.
601     *
602     * @return array       Associative array with 'count' and 'results' keys
603     */
604    public function getNewItems($page, $limit, $daysOld, $fundId = null)
605    {
606        return $this->defaultCall('getNewItems', func_get_args());
607    }
608
609    /**
610     * Get Offline Mode
611     *
612     * This is responsible for returning the offline mode
613     *
614     * @return string "ils-offline" for systems where the main ILS is offline,
615     * "ils-none" for systems which do not use an ILS
616     */
617    public function getOfflineMode()
618    {
619        return $this->defaultCall('getOfflineMode', func_get_args());
620    }
621
622    /**
623     * Get Pick Up Locations
624     *
625     * This is responsible get a list of valid library locations for holds / recall
626     * retrieval
627     *
628     * @param array $patron      Patron information returned by the patronLogin
629     * method.
630     * @param array $holdDetails Optional array, only passed in when getting a list
631     * in the context of placing or editing a hold. When placing a hold, it contains
632     * most of the same values passed to placeHold, minus the patron data. When
633     * editing a hold it contains all the hold information returned by getMyHolds.
634     * May be used to limit the pickup options or may be ignored. The driver must
635     * not add new options to the return array based on this data or other areas of
636     * VuFind may behave incorrectly.
637     *
638     * @return array        An array of associative arrays with locationID and
639     * locationDisplay keys
640     */
641    public function getPickUpLocations($patron = false, $holdDetails = null)
642    {
643        return $this->defaultCall('getPickUpLocations', func_get_args());
644    }
645
646    /**
647     * Get list of users for whom the provided patron is a proxy.
648     *
649     * @param array $patron The patron array with username and password
650     *
651     * @return array
652     */
653    public function getProxiedUsers($patron)
654    {
655        return $this->defaultCall('getProxiedUsers', func_get_args());
656    }
657
658    /**
659     * Get Purchase History
660     *
661     * This is responsible for retrieving the acquisitions history data for the
662     * specific record (usually recently received issues of a serial).
663     *
664     * @param string $id The record id to retrieve the info for
665     *
666     * @throws ILSException
667     * @return array     An array with the acquisitions data on success.
668     */
669    public function getPurchaseHistory($id)
670    {
671        return $this->combineArraysOfAssociativeArrays('getPurchaseHistory', func_get_args());
672    }
673
674    /**
675     * Get Renew Details
676     *
677     * In order to renew an item, the ILS requires information on the item and
678     * patron. This function returns the information as a string which is then used
679     * as submitted form data in checkedOut.php. This value is then extracted by
680     * the RenewMyItems function.
681     *
682     * @param array $checkoutDetails An array of item data
683     *
684     * @return string Data for use in a form field
685     */
686    public function getRenewDetails($checkoutDetails)
687    {
688        return $this->defaultCall('getRenewDetails', func_get_args());
689    }
690
691    /**
692     * Check whether the patron is blocked from placing requests (holds/ILL/SRR).
693     *
694     * @param array $patron Patron data from patronLogin().
695     *
696     * @return mixed A boolean false if no blocks are in place and an array
697     * of block reasons if blocks are in place
698     */
699    public function getRequestBlocks($patron)
700    {
701        return $this->defaultCall('getRequestBlocks', func_get_args());
702    }
703
704    /**
705     * Get request groups
706     *
707     * @param int   $id          BIB ID
708     * @param array $patron      Patron information returned by the patronLogin
709     * method.
710     * @param array $holdDetails Optional array, only passed in when getting a list
711     * in the context of placing a hold; contains most of the same values passed to
712     * placeHold, minus the patron data. May be used to limit the request group
713     * options or may be ignored.
714     *
715     * @return array  An array of associative arrays with requestGroupId and
716     * name keys
717     */
718    public function getRequestGroups($id, $patron, $holdDetails = null)
719    {
720        return $this->defaultCall('getRequestGroups', func_get_args());
721    }
722
723    /**
724     * Get Status
725     *
726     * This is responsible for retrieving the status information of a certain
727     * record.
728     *
729     * @param string $id The record id to retrieve the holdings for
730     *
731     * @throws ILSException
732     * @return mixed     On success, an associative array with the following keys:
733     * id, availability (boolean), status, location, reserve, callnumber.
734     */
735    public function getStatus($id)
736    {
737        return $this->combineArraysOfAssociativeArrays('getStatus', [$id]);
738    }
739
740    /**
741     * Get Statuses
742     *
743     * This is responsible for retrieving the status information for a
744     * collection of records.
745     *
746     * @param array $ids The array of record ids to retrieve the status for
747     *
748     * @throws ILSException
749     * @return array     An array of getStatus() return values on success.
750     */
751    public function getStatuses($ids)
752    {
753        return $this->combineMultipleArraysOfAssociativeArrays('getStatuses', [$ids], 'id');
754    }
755
756    /**
757     * Get suppressed authority records
758     *
759     * @return array ID numbers of suppressed authority records in the system.
760     */
761    public function getSuppressedAuthorityRecords()
762    {
763        return $this->defaultCall('getSuppressedAuthorityRecords', func_get_args());
764    }
765
766    /**
767     * Get suppressed records.
768     *
769     * @throws ILSException
770     * @return array ID numbers of suppressed records in the system.
771     */
772    public function getSuppressedRecords()
773    {
774        return $this->defaultCall('getSuppressedRecords', func_get_args());
775    }
776
777    /**
778     * Provide an array of URL data (in the same format returned by the record
779     * driver's getURLs method) for the specified bibliographic record.
780     *
781     * @param string $id Bibliographic record ID
782     *
783     * @return array
784     */
785    public function getUrlsForRecord($id)
786    {
787        return $this->defaultCall('getUrlsForRecord', func_get_args());
788    }
789
790    /**
791     * Has Holdings
792     *
793     * This is responsible for determining if holdings exist for a particular
794     * bibliographic id
795     *
796     * @param string $id The record id to retrieve the holdings for
797     *
798     * @return bool True if holdings exist, False if they do not
799     *
800     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
801     */
802    public function hasHoldings($id)
803    {
804        return $this->defaultCall('hasHoldings', func_get_args());
805    }
806
807    /**
808     * Get Hidden Login Mode
809     *
810     * This is responsible for indicating whether login should be hidden.
811     *
812     * @return bool true if the login should be hidden, false if not
813     */
814    public function loginIsHidden()
815    {
816        return $this->defaultCall('loginIsHidden', func_get_args());
817    }
818
819    /**
820     * Patron Login
821     *
822     * This is responsible for authenticating a patron against the catalog.
823     *
824     * @param string $username The patron barcode
825     * @param string $password The patron password
826     *
827     * @throws ILSException
828     * @return mixed           Associative array of patron info on successful login,
829     * null on unsuccessful login.
830     */
831    public function patronLogin($username, $password)
832    {
833        return $this->defaultCall('patronLogin', func_get_args());
834    }
835
836    /**
837     * Place Hold
838     *
839     * Attempts to place a hold or recall on a particular item and returns
840     * an array with result details
841     *
842     * @param array $holdDetails An array of item and patron data
843     *
844     * @return mixed An array of data on the request including
845     * whether or not it was successful and a system message (if available)
846     */
847    public function placeHold($holdDetails)
848    {
849        return $this->defaultCall('placeHold', func_get_args());
850    }
851
852    /**
853     * Place ILL Request
854     *
855     * Attempts to place an ILL request on a particular item and returns
856     * an array with result details (or throws an exception on failure of support
857     * classes)
858     *
859     * @param array $details An array of item and patron data
860     *
861     * @return mixed An array of data on the request including
862     * whether or not it was successful and a system message (if available)
863     */
864    public function placeILLRequest($details)
865    {
866        return $this->defaultCall('placeILLRequest', func_get_args());
867    }
868
869    /**
870     * Place Storage Retrieval Request
871     *
872     * Attempts to place a storage retrieval request on a particular item and returns
873     * an array with result details
874     *
875     * @param array $details An array of item and patron data
876     *
877     * @return mixed An array of data on the request including
878     * whether or not it was successful and a system message (if available)
879     */
880    public function placeStorageRetrievalRequest($details)
881    {
882        return $this->defaultCall('placeStorageRetrievalRequest', func_get_args());
883    }
884
885    /**
886     * Purge Patron Transaction History
887     *
888     * @param array  $patron The patron array from patronLogin
889     * @param ?array $ids    IDs to purge, or null for all
890     *
891     * @throws ILSException
892     * @return array Associative array of the results
893     */
894    public function purgeTransactionHistory(array $patron, ?array $ids)
895    {
896        return $this->defaultCall('purgeTransactionHistory', func_get_args());
897    }
898
899    /**
900     * Renew My Items
901     *
902     * Function for attempting to renew a patron's items. The data in
903     * $renewDetails['details'] is determined by getRenewDetails().
904     *
905     * @param array $renewDetails An array of data required for renewing items
906     * including the Patron ID and an array of renewal IDS
907     *
908     * @return array An array of renewal information keyed by item ID
909     */
910    public function renewMyItems($renewDetails)
911    {
912        return $this->defaultCall('renewMyItems', func_get_args());
913    }
914
915    /**
916     * Renew My Items Link
917     *
918     * @param array $checkedOutDetails Checked Out Details
919     *
920     * @return string Url to a native OPAC
921     */
922    public function renewMyItemsLink($checkedOutDetails)
923    {
924        return $this->defaultCall('renewMyItemsLink', func_get_args());
925    }
926
927    /**
928     * Helper method to determine whether or not a certain method can be
929     * called on this driver. Required method for any smart drivers.
930     *
931     * @param string $method The name of the called method.
932     * @param array  $params Array of passed parameters.
933     *
934     * @return bool True if the method can be called with the given parameters,
935     * false otherwise.
936     */
937    public function supportsMethod($method, $params)
938    {
939        $driverName = $this->config[$method]['main_driver'] ?? $this->mainDriver;
940        $driver = $this->getDriver($driverName);
941        return $driver && $this->driverSupportsMethod($driver, $method, $params);
942    }
943
944    /**
945     * Update holds
946     *
947     * This is responsible for changing the status of hold requests
948     *
949     * @param array $holdsDetails The details identifying the holds
950     * @param array $fields       An associative array of fields to be updated
951     * @param array $patron       Patron array
952     *
953     * @return array Associative array of the results
954     */
955    public function updateHolds($holdsDetails, $fields, $patron)
956    {
957        return $this->defaultCall('updateHolds', func_get_args());
958    }
959
960    /**
961     * Get available login targets (drivers enabled for login)
962     *
963     * @return string[] Source ID's
964     */
965    public function getLoginDrivers()
966    {
967        return [$this->mainDriver];
968    }
969
970    /**
971     * Get default login driver
972     *
973     * @return string Default login driver or empty string
974     */
975    public function getDefaultLoginDriver()
976    {
977        return $this->mainDriver;
978    }
979
980    /**
981     * Get Default Request Group
982     *
983     * Returns the default request group
984     *
985     * @param array $patron      Patron information returned by the patronLogin
986     * method.
987     * @param array $holdDetails Optional array, only passed in when getting a list
988     * in the context of placing a hold; contains most of the same values passed to
989     * placeHold, minus the patron data. May be used to limit the request group
990     * options or may be ignored.
991     *
992     * @return string A location ID
993     */
994    public function getDefaultRequestGroup($patron, $holdDetails = null)
995    {
996        return $this->defaultCall('getDefaultRequestGroup', func_get_args());
997    }
998
999    /**
1000     * Default method -- pass along calls to the driver if a source can be determined
1001     * and a driver is available. Throws ILSException otherwise.
1002     *
1003     * @param string $methodName The name of the called method
1004     * @param array  $params     Array of passed parameters
1005     *
1006     * @throws ILSException
1007     * @return mixed             Varies by method
1008     */
1009    public function __call($methodName, $params)
1010    {
1011        return $this->defaultCall($methodName, $params);
1012    }
1013
1014    /**
1015     * Calling a function of a driver
1016     *
1017     * @param string $driverName Name of the driver on which the method is called
1018     * @param string $method     Name of the method
1019     * @param array  $params     Parameters
1020     *
1021     * @return mixed
1022     */
1023    protected function callDriverMethod($driverName, $method, $params)
1024    {
1025        $driver = $this->getDriver($driverName);
1026        return call_user_func_array([$driver, $method], $params);
1027    }
1028
1029    /**
1030     * Determines which driver should be used for the specified method
1031     *
1032     * @param $method string name of the method
1033     *
1034     * @return string
1035     */
1036    protected function getMainDriverNameForMethod($method)
1037    {
1038        $driverName = $this->config[$method]['main_driver'] ?? $this->mainDriver;
1039        return $driverName;
1040    }
1041
1042    /**
1043     * Simply calls the method for the specified main driver
1044     *
1045     * @param string $methodName Name of the method to be called
1046     * @param array  $params     Arguments for the method call
1047     *
1048     * @return mixed
1049     */
1050    protected function defaultCall($methodName, $params)
1051    {
1052        if ($this->supportsMethod($methodName, $params)) {
1053            $driverName = $this->getMainDriverNameForMethod($methodName);
1054            return $this->callDriverMethod($driverName, $methodName, $params);
1055        }
1056        throw new ILSException('Method "' . $methodName . '" is not supported.');
1057    }
1058
1059    /**
1060     * Used for methods that return associative arrays. Calls the method for the main and support drivers and merges
1061     * the results. Only uses the specified support fields of the support drivers.
1062     *
1063     * @param string $methodName Name of the method to be called
1064     * @param array  $params     Arguments for the method call
1065     *
1066     * @return array
1067     */
1068    protected function mergeSingleArrayResults($methodName, $params)
1069    {
1070        $methodConfig = $this->config[$methodName] ?? [];
1071
1072        // get main results
1073        $mainDriverName = $this->getMainDriverNameForMethod($methodName);
1074        $mainResult = $this->callDriverMethod($mainDriverName, $methodName, $params);
1075
1076        $supportConfig = $methodConfig['support_drivers'] ?? [];
1077        $supportDriverNames = array_keys($supportConfig) ?? [];
1078
1079        // get support results
1080        $supportResults = array_map(function ($driverName) use ($methodName, $params, $supportConfig) {
1081            $supportKeys = explode(',', $supportConfig[$driverName] ?? '');
1082            return array_intersect_key(
1083                $this->callDriverMethod($driverName, $methodName, $params),
1084                array_flip($supportKeys)
1085            );
1086        }, $supportDriverNames);
1087
1088        // merge results
1089        return array_merge($mainResult, ...$supportResults);
1090    }
1091
1092    /**
1093     * Used for methods where the result is a list of items. Calls the method for
1094     * the main driver and all support drivers. Then adds specified fields of the
1095     * support drivers to the main driver's result.
1096     *
1097     * @param $methodName              string Name of the method to be called
1098     * @param $params                  array  Arguments for the method call
1099     * @param $optionalResultSubfields array  Keys of possible result subfields
1100     *
1101     * @return mixed
1102     */
1103    protected function combineArraysOfAssociativeArrays($methodName, $params, $optionalResultSubfields = [])
1104    {
1105        $methodConfig = $this->config[$methodName] ?? [];
1106
1107        // get main results
1108        $mainDriverName = $this->getMainDriverNameForMethod($methodName);
1109        $mainResult = $this->callDriverMethod($mainDriverName, $methodName, $params);
1110
1111        if (!empty($mergeKeys = $methodConfig['merge_keys'] ?? [])) {
1112            $supportConfig = $methodConfig['support_drivers'] ?? [];
1113            $supportDriverNames = array_keys($supportConfig) ?? [];
1114
1115            // get support results
1116            $supportResult = array_map(
1117                function ($driverName) use (
1118                    $params,
1119                    $mergeKeys,
1120                    $methodName,
1121                    $supportConfig,
1122                    $optionalResultSubfields
1123                ) {
1124                    $result = $this->callDriverMethod($driverName, $methodName, $params);
1125                    $result = $this->extractResultSubfields($result, $optionalResultSubfields);
1126                    $mergeKey = $mergeKeys[$driverName];
1127                    // extract support keys
1128                    $supportEntry = array_map(
1129                        function ($fullEntry) use ($mergeKey, $supportConfig, $driverName) {
1130                            $usedKeys = array_merge([$mergeKey], explode(',', $supportConfig[$driverName]));
1131                            return array_intersect_key($fullEntry, array_flip($usedKeys));
1132                        },
1133                        $result
1134                    );
1135                    return $this->extractKey($supportEntry, $mergeKey);
1136                },
1137                array_combine($supportDriverNames, $supportDriverNames)
1138            );
1139
1140            // merge results
1141            $mainResult = $this->mergeInSubfields($mainResult, $supportResult, $mergeKeys, $optionalResultSubfields);
1142        }
1143        return $mainResult;
1144    }
1145
1146    /**
1147     * Used for methods where the result is a list of lists of items. Calls the method for
1148     * the main driver and all support drivers. Then adds specified fields of the
1149     * support drivers to the main driver's result.
1150     *
1151     * @param $methodName              string Name of the method to be called
1152     * @param $params                  array  Arguments for the method
1153     * @param $baseMergeKey            string Key to match arrays on the first level
1154     * @param $optionalResultSubfields array  Keys of possible result subfields
1155     *
1156     * @return mixed
1157     */
1158    protected function combineMultipleArraysOfAssociativeArrays(
1159        $methodName,
1160        $params,
1161        $baseMergeKey,
1162        $optionalResultSubfields = []
1163    ) {
1164        $methodConfig = $this->config[$methodName] ?? [];
1165
1166        // get main results
1167        $mainDriverName = $this->getMainDriverNameForMethod($methodName);
1168        $mainResult = $this->callDriverMethod($mainDriverName, $methodName, $params);
1169        $subMergeKeys = $methodConfig['merge_keys'] ?? [];
1170
1171        if (!empty($subMergeKeys)) {
1172            $supportConfig = $methodConfig['support_drivers'] ?? [];
1173            $supportDriverNames = array_keys($supportConfig) ?? [];
1174
1175            // get support results
1176            $supportResults = array_map(
1177                function ($driverName) use (
1178                    $params,
1179                    $baseMergeKey,
1180                    $subMergeKeys,
1181                    $methodName,
1182                    $supportConfig,
1183                    $optionalResultSubfields
1184                ) {
1185                    $results = $this->callDriverMethod($driverName, $methodName, $params);
1186                    return array_map(
1187                        function ($result) use (
1188                            $baseMergeKey,
1189                            $subMergeKeys,
1190                            $supportConfig,
1191                            $driverName,
1192                            $optionalResultSubfields
1193                        ) {
1194                            $result = $this->extractResultSubfields($result, $optionalResultSubfields);
1195                            $subMergeKey = $subMergeKeys[$driverName];
1196                            // extract support keys
1197                            $supportEntry = array_map(
1198                                function ($fullEntry) use ($subMergeKey, $supportConfig, $driverName, $baseMergeKey) {
1199                                    $usedKeys = array_merge(
1200                                        [$baseMergeKey, $subMergeKey],
1201                                        explode(',', $supportConfig[$driverName])
1202                                    );
1203                                    return array_intersect_key($fullEntry, array_flip($usedKeys));
1204                                },
1205                                $result
1206                            );
1207                            return  $this->extractKey($supportEntry, $subMergeKey);
1208                        },
1209                        $results
1210                    );
1211                },
1212                array_combine($supportDriverNames, $supportDriverNames)
1213            );
1214
1215            // merge all single results
1216            $res = [];
1217            for ($i = 0; $i < count($mainResult); $i++) {
1218                if ($baseMergeValue = $mainResult[$i][0][$baseMergeKey] ?? false) {
1219                    $supportResult = array_map(function ($supportResult) use ($baseMergeKey, $baseMergeValue) {
1220                        return current(array_filter(
1221                            $supportResult,
1222                            function ($entry) use ($baseMergeKey, $baseMergeValue) {
1223                                return ($entry[array_keys($entry)[0]][$baseMergeKey] ?? null) === $baseMergeValue;
1224                            }
1225                        ));
1226                    }, $supportResults);
1227                    $res[] = $this->mergeInSubfields(
1228                        $mainResult[$i],
1229                        $supportResult,
1230                        $subMergeKeys,
1231                        $optionalResultSubfields
1232                    );
1233                }
1234            }
1235            $mainResult = $res;
1236        }
1237        return $mainResult;
1238    }
1239
1240    /**
1241     * Extracts results from support drivers where the result can be split into named subfields.
1242     *
1243     * @param $result                  array Result of a support driver
1244     * @param $optionalResultSubfields array Keys of possible result subfields
1245     *
1246     * @return array
1247     */
1248    protected function extractResultSubfields($result, $optionalResultSubfields)
1249    {
1250        $includesSubfields = false;
1251        foreach ($optionalResultSubfields as $subfield) {
1252            $includesSubfields |= in_array($subfield, array_keys($result));
1253        }
1254        if ($includesSubfields) {
1255            $tmpResult = [];
1256            foreach ($optionalResultSubfields as $key) {
1257                $tmpResult = array_merge($tmpResult, $result[$key] ?? []);
1258            }
1259            $result = $tmpResult;
1260        }
1261        return $result;
1262    }
1263
1264    /**
1265     * Merges results where the result can be split into named subfields.
1266     *
1267     * @param $mainResult              array Result of the main driver
1268     * @param $supportResults          array Result of a support driver
1269     * @param $mergeKeys               array Merge keys
1270     * @param $optionalResultSubfields array Keys of possible result subfields
1271     *
1272     * @return array
1273     */
1274    protected function mergeInSubfields($mainResult, $supportResults, $mergeKeys, $optionalResultSubfields)
1275    {
1276        $includesSubfields = false;
1277        foreach ($optionalResultSubfields as $subfield) {
1278            $includesSubfields |= in_array($subfield, array_keys($mainResult));
1279        }
1280        if ($includesSubfields) {
1281            foreach ($optionalResultSubfields as $key) {
1282                $mainResult[$key] = $this->mergeAssociativeArrays($mainResult[$key], $supportResults, $mergeKeys);
1283            }
1284        } else {
1285            $mainResult = $this->mergeAssociativeArrays($mainResult, $supportResults, $mergeKeys);
1286        }
1287        return $mainResult;
1288    }
1289
1290    /**
1291     * Merges results of the main and the support drivers on the specified key
1292     *
1293     * @param array $mainResult     Result of main driver
1294     * @param array $supportResults Results of support drivers
1295     * @param array $mergeKeys      Key on which the results are merged
1296     *
1297     * @return array
1298     */
1299    protected function mergeAssociativeArrays($mainResult, $supportResults, $mergeKeys)
1300    {
1301        $res = [];
1302        foreach ($mainResult as $mainEntry) {
1303            foreach ($supportResults as $driverName => $supportResult) {
1304                $mergeKey = $mergeKeys[$driverName] ?? null;
1305                if ($mergeKey !== null && $mainEntry[$mergeKey]) {
1306                    // merge entries that match on $mergeKey
1307                    $supportEntry = $supportResult[$mainEntry[$mergeKey]] ?? [];
1308                    if (!empty($supportEntry)) {
1309                        $mainEntry = array_merge($supportEntry, $mainEntry);
1310                    }
1311                }
1312            }
1313            $res[] = $mainEntry;
1314        }
1315        return $res;
1316    }
1317
1318    /**
1319     * Takes an array of item as input and creates an associative
1320     * array using specified fields of the items as key
1321     *
1322     * @param array  $data Array of items
1323     * @param string $key  Items field to be used as key
1324     *
1325     * @return array
1326     */
1327    protected function extractKey($data, $key)
1328    {
1329        $res = [];
1330        foreach ($data as $entry) {
1331            if (!empty($entry[$key])) {
1332                $res[$entry[$key]] = $entry;
1333            }
1334        }
1335        return $res;
1336    }
1337}