Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.57% covered (success)
90.57%
48 / 53
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
MarcReaderTrait
90.57% covered (success)
90.57%
48 / 53
75.00% covered (warning)
75.00%
6 / 8
27.61
0.00% covered (danger)
0.00%
0 / 1
 getRawMarcData
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 getMarcReader
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getFieldArray
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 getFirstFieldValue
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getPublicationInfo
80.00% covered (warning)
80.00%
16 / 20
0.00% covered (danger)
0.00%
0 / 1
9.65
 getSubfield
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSubfields
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSubfieldArray
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2
3/**
4 * Functions for reading MARC records.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2017.
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  RecordDrivers
25 * @author   Demian Katz <demian.katz@villanova.edu>
26 * @author   Ere Maijala <ere.maijala@helsinki.fi>
27 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
28 * @link     https://vufind.org/wiki/development:plugins:record_drivers Wiki
29 */
30
31namespace VuFind\RecordDriver\Feature;
32
33use function array_key_exists;
34use function count;
35use function in_array;
36use function is_array;
37
38/**
39 * Functions for reading MARC records.
40 *
41 * Assumption: raw MARC data can be found in $this->fields['fullrecord'].
42 *
43 * Assumption: VuFind config available as $this->mainConfig
44 *
45 * @category VuFind
46 * @package  RecordDrivers
47 * @author   Demian Katz <demian.katz@villanova.edu>
48 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
49 * @link     https://vufind.org/wiki/development:plugins:record_drivers Wiki
50 */
51trait MarcReaderTrait
52{
53    /**
54     * MARC reader class to use.
55     *
56     * @var string
57     */
58    protected $marcReaderClass = \VuFind\Marc\MarcReader::class;
59
60    /**
61     * MARC reader. Access only via getMarcReader() as this is initialized lazily.
62     */
63    protected $lazyMarcReader = null;
64
65    /**
66     * Retrieve the raw MARC data for this record; note that format may vary
67     * depending on what was indexed (e.g. XML vs. binary MARC).
68     *
69     * @return string
70     */
71    public function getRawMarcData()
72    {
73        // Set preferred MARC field from config or default, if it's not existing
74        $preferredMarcFields = $this->mainConfig->Record->preferredMarcFields
75            ?? 'fullrecord';
76        $preferredMarcFieldArray = explode(',', $preferredMarcFields);
77        $preferredMarcField = 'fullrecord';
78        foreach ($preferredMarcFieldArray as $testField) {
79            if (array_key_exists($testField, $this->fields)) {
80                $preferredMarcField = $testField;
81                break;
82            }
83        }
84        if (empty($this->fields[$preferredMarcField])) {
85            throw new \Exception('Missing MARC data in record ' . $this->getUniqueId());
86        }
87        return trim($this->fields[$preferredMarcField]);
88    }
89
90    /**
91     * Get access to the MarcReader object.
92     *
93     * @return \VuFind\Marc\MarcReader
94     */
95    public function getMarcReader()
96    {
97        if (null === $this->lazyMarcReader) {
98            $this->lazyMarcReader = new $this->marcReaderClass(
99                $this->getRawMarcData()
100            );
101        }
102
103        return $this->lazyMarcReader;
104    }
105
106    /**
107     * Return an array of all values extracted from the specified field/subfield
108     * combination. If multiple subfields are specified and $concat is true, they
109     * will be concatenated together in the order listed -- each entry in the array
110     * will correspond with a single MARC field. If $concat is false, the return
111     * array will contain separate entries for separate subfields.
112     *
113     * @param string $field     The MARC field number to read
114     * @param array  $subfields The MARC subfield codes to read
115     * @param bool   $concat    Should we concatenate subfields?
116     * @param string $separator Separator string (used only when $concat === true)
117     *
118     * @return array
119     */
120    protected function getFieldArray(
121        $field,
122        $subfields = null,
123        $concat = true,
124        $separator = ' '
125    ) {
126        // Default to subfield a if nothing is specified.
127        if (!is_array($subfields)) {
128            $subfields = ['a'];
129        }
130        return $this->getMarcReader()->getFieldsSubfields(
131            $field,
132            $subfields,
133            $concat ? $separator : null
134        );
135    }
136
137    /**
138     * Get the first value matching the specified MARC field and subfields.
139     * If multiple subfields are specified, they will be concatenated together.
140     *
141     * @param string $field     The MARC field to read
142     * @param array  $subfields The MARC subfield codes to read
143     *
144     * @return string
145     */
146    protected function getFirstFieldValue($field, $subfields = null)
147    {
148        $matches = $this->getFieldArray($field, $subfields);
149        return $matches[0] ?? '';
150    }
151
152    /**
153     * Get the item's publication information
154     *
155     * @param string $subfield The subfield to retrieve ('a' = location, 'c' = date)
156     *
157     * @return array
158     */
159    protected function getPublicationInfo($subfield = 'a')
160    {
161        // Get string separator for publication information:
162        $separator = $this->mainConfig->Record->marcPublicationInfoSeparator ?? ' ';
163
164        // First check old-style 260 field:
165        $results = $this->getFieldArray('260', [$subfield], true, $separator);
166
167        // Now track down relevant RDA-style 264 fields; we only care about
168        // copyright and publication places (and ignore copyright places if
169        // publication places are present). This behavior is designed to be
170        // consistent with default SolrMarc handling of names/dates.
171        $pubResults = $copyResults = [];
172
173        $fields = $this->getMarcReader()->getFields('264', [$subfield]);
174        foreach ($fields as $currentField) {
175            $currentVal = $this
176                ->getSubfieldArray($currentField, [$subfield], true, $separator);
177            if (!empty($currentVal)) {
178                switch ($currentField['i2']) {
179                    case '1':
180                        $pubResults = array_merge($pubResults, $currentVal);
181                        break;
182                    case '4':
183                        $copyResults = array_merge($copyResults, $currentVal);
184                        break;
185                }
186            }
187        }
188        $replace260 = $this->mainConfig->Record->replaceMarc260 ?? false;
189        if (count($pubResults) > 0) {
190            return $replace260 ? $pubResults : array_merge($results, $pubResults);
191        } elseif (count($copyResults) > 0) {
192            return $replace260 ? $copyResults : array_merge($results, $copyResults);
193        }
194
195        return $results;
196    }
197
198    /**
199     * Return first subfield with the given code in the provided MARC field
200     *
201     * @param array  $field    Result from MarcReader::getFields
202     * @param string $subfield The MARC subfield code to get
203     *
204     * @return string
205     */
206    protected function getSubfield($field, $subfield)
207    {
208        return $this->getMarcReader()->getSubfield($field, $subfield);
209    }
210
211    /**
212     * Return all subfields with the given code in the provided MARC field
213     *
214     * @param array  $field    Result from MarcReader::getFields
215     * @param string $subfield The MARC subfield code to get
216     *
217     * @return array
218     */
219    protected function getSubfields($field, $subfield)
220    {
221        return $this->getMarcReader()->getSubfields($field, $subfield);
222    }
223
224    /**
225     * Return an array of non-empty subfield values found in the provided MARC
226     * field. If $concat is true, the array will contain either zero or one
227     * entries (empty array if no subfields found, subfield values concatenated
228     * together in specified order if found). If concat is false, the array
229     * will contain a separate entry for each subfield value found.
230     *
231     * @param array  $currentField Result from MarcReader::getFields
232     * @param array  $subfields    The MARC subfield codes to read
233     * @param bool   $concat       Should we concatenate subfields?
234     * @param string $separator    Separator string (used only when $concat === true)
235     *
236     * @return array
237     */
238    protected function getSubfieldArray(
239        $currentField,
240        $subfields,
241        $concat = true,
242        $separator = ' '
243    ) {
244        // Start building a line of text for the current field
245        $matches = [];
246
247        // Loop through all subfields, collecting results that match the filter;
248        // note that it is important to retain the original MARC order here!
249        foreach ($currentField['subfields'] as $currentSubfield) {
250            if (in_array($currentSubfield['code'], $subfields)) {
251                // Grab the current subfield value and act on it if it is non-empty:
252                $data = trim($currentSubfield['data']);
253                if (!empty($data)) {
254                    $matches[] = $data;
255                }
256            }
257        }
258
259        // Send back the data in a different format depending on $concat mode:
260        return $concat && $matches ? [implode($separator, $matches)] : $matches;
261    }
262}