Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
78.72% |
307 / 390 |
|
58.72% |
64 / 109 |
CRAP | |
0.00% |
0 / 1 |
DefaultRecord | |
78.72% |
307 / 390 |
|
58.72% |
64 / 109 |
635.09 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getAccessRestrictions | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAllSubjectHeadings | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
getAllRecordLinks | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAuthorDataFields | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
6 | |||
getAwards | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getBibliographyNotes | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getBreadcrumb | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCallNumber | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getCallNumbers | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCleanDOI | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getCleanISBN | |
81.82% |
9 / 11 |
|
0.00% |
0 / 1 |
5.15 | |||
getCleanISBNs | |
95.45% |
21 / 22 |
|
0.00% |
0 / 1 |
10 | |||
getCleanISSN | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 | |||
getCleanOCLCNum | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
getCleanUPC | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
getCleanNBN | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCleanISMN | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCorporateAuthors | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCorporateAuthorsRoles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDateSpan | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDeduplicatedAuthors | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
9 | |||
getEdition | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getFindingAids | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getFormats | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getGeneralNotes | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRawAuthorHighlights | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPrimaryAuthorsWithHighlighting | |
50.00% |
6 / 12 |
|
0.00% |
0 / 1 |
4.12 | |||
getLastIndexed | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSnippetCaption | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getHighlightedSnippet | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getHighlightedTitle | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getInstitutions | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getBuildings | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getISBNs | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getISSNs | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getLanguages | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRawLCCN | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getLCCN | |
50.00% |
4 / 8 |
|
0.00% |
0 / 1 |
4.12 | |||
getNewerTitles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getOCLC | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getOpenUrlFormat | |
78.57% |
11 / 14 |
|
0.00% |
0 / 1 |
9.80 | |||
getCoinsID | |
42.86% |
3 / 7 |
|
0.00% |
0 / 1 |
9.66 | |||
getDefaultOpenUrlParams | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
getBookOpenUrlParams | |
94.44% |
17 / 18 |
|
0.00% |
0 / 1 |
5.00 | |||
getArticleOpenUrlParams | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
2 | |||
getUnknownFormatOpenUrlParams | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
getJournalOpenUrlParams | |
77.78% |
7 / 9 |
|
0.00% |
0 / 1 |
3.10 | |||
getOpenUrl | |
92.31% |
12 / 13 |
|
0.00% |
0 / 1 |
6.02 | |||
getCoinsOpenUrl | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPhysicalDescriptions | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPlacesOfPublication | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPlayingTimes | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPreviousTitles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPrimaryAuthor | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getPrimaryAuthors | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPrimaryAuthorsRoles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getProductionCredits | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPublicationDates | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getHumanReadablePublicationDates | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPublicationDetails | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
4 | |||
getPublicationFrequency | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPublishers | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRealTimeHistory | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRealTimeHoldings | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRelationshipNotes | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSecondaryAuthors | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSecondaryAuthorsRoles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSeries | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getShortTitle | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSource | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSubtitle | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSystemDetails | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSummary | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getTargetAudienceNotes | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getThumbnail | |
73.33% |
22 / 30 |
|
0.00% |
0 / 1 |
16.20 | |||
getTitle | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getTitleSection | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTitleStatement | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTOC | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getHierarchicalPlaceNames | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUPC | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getUuids | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCleanUuid | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
getURLs | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getHierarchyTopID | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getHierarchyTopTitle | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getContainingCollections | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isCollection | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getHierarchyTrees | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getHierarchyType | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUniqueID | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
getXML | |
90.62% |
29 / 32 |
|
0.00% |
0 / 1 |
10.08 | |||
getSupportedCitationFormats | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getContainerTitle | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getContainerVolume | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getContainerIssue | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getContainerStartPage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getContainerEndPage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getContainerReference | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSortTitle | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSchemaOrgFormatsArray | |
28.57% |
6 / 21 |
|
0.00% |
0 / 1 |
46.44 | |||
getSchemaOrgFormats | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDedupData | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getChildRecordCount | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getContainerRecordID | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGeoLocation | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDisplayCoordinates | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCoordinateLabels | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | /** |
4 | * Default model for records |
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 RecordDrivers |
25 | * @author Demian Katz <demian.katz@villanova.edu> |
26 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
27 | * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki |
28 | */ |
29 | |
30 | namespace VuFind\RecordDriver; |
31 | |
32 | use VuFind\View\Helper\Root\RecordLinker; |
33 | use VuFindCode\ISBN; |
34 | |
35 | use function count; |
36 | use function in_array; |
37 | use function is_array; |
38 | use function strlen; |
39 | |
40 | /** |
41 | * Default model for records |
42 | * |
43 | * @category VuFind |
44 | * @package RecordDrivers |
45 | * @author Demian Katz <demian.katz@villanova.edu> |
46 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
47 | * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki |
48 | * |
49 | * @SuppressWarnings(PHPMD.ExcessivePublicCount) |
50 | */ |
51 | class DefaultRecord extends AbstractBase |
52 | { |
53 | /** |
54 | * Should we highlight fields in search results? |
55 | * |
56 | * @var bool |
57 | */ |
58 | protected $highlight = false; |
59 | |
60 | /** |
61 | * Constructor |
62 | * |
63 | * @param \Laminas\Config\Config $mainConfig VuFind main configuration (omit |
64 | * for built-in defaults) |
65 | * @param \Laminas\Config\Config $recordConfig Record-specific configuration |
66 | * file (omit to use $mainConfig as $recordConfig) |
67 | * @param \Laminas\Config\Config $searchSettings Search-specific configuration |
68 | * file |
69 | */ |
70 | public function __construct( |
71 | $mainConfig = null, |
72 | $recordConfig = null, |
73 | $searchSettings = null |
74 | ) { |
75 | // Turn on highlighting as needed: |
76 | $this->highlight = $searchSettings->General->highlighting ?? false; |
77 | |
78 | parent::__construct($mainConfig, $recordConfig); |
79 | } |
80 | |
81 | /** |
82 | * Get access restriction notes for the record. |
83 | * |
84 | * @return array |
85 | */ |
86 | public function getAccessRestrictions() |
87 | { |
88 | // Not currently stored in the default index schema |
89 | return []; |
90 | } |
91 | |
92 | /** |
93 | * Get all subject headings associated with this record. Each heading is |
94 | * returned as an array of chunks, increasing from least specific to most |
95 | * specific. |
96 | * |
97 | * @param bool $extended Whether to return a keyed array with the following |
98 | * keys: |
99 | * - heading: the actual subject heading chunks |
100 | * - type: heading type |
101 | * - source: source vocabulary |
102 | * |
103 | * @return array |
104 | */ |
105 | public function getAllSubjectHeadings($extended = false) |
106 | { |
107 | $headings = []; |
108 | foreach (['topic', 'geographic', 'genre', 'era'] as $field) { |
109 | if (isset($this->fields[$field])) { |
110 | $headings = array_merge($headings, $this->fields[$field]); |
111 | } |
112 | } |
113 | |
114 | // The default index schema doesn't currently store subject headings in a |
115 | // broken-down format, so we'll just send each value as a single chunk. |
116 | // Other record drivers (i.e. SolrMarc) can offer this data in a more |
117 | // granular format. |
118 | $callback = function ($i) use ($extended) { |
119 | return $extended |
120 | ? ['heading' => [$i], 'type' => '', 'source' => ''] |
121 | : [$i]; |
122 | }; |
123 | return array_map($callback, array_unique($headings)); |
124 | } |
125 | |
126 | /** |
127 | * Get all record links related to the current record. Each link is returned as |
128 | * array. |
129 | * NB: to use this method you must override it. |
130 | * Format: |
131 | * <code> |
132 | * array( |
133 | * array( |
134 | * 'title' => label_for_title |
135 | * 'value' => link_name |
136 | * 'link' => link_URI |
137 | * ), |
138 | * ... |
139 | * ) |
140 | * </code> |
141 | * |
142 | * @return null|array |
143 | */ |
144 | public function getAllRecordLinks() |
145 | { |
146 | return null; |
147 | } |
148 | |
149 | /** |
150 | * Get Author Information with Associated Data Fields |
151 | * |
152 | * @param string $index The author index [primary, corporate, or secondary] |
153 | * used to construct a method name for retrieving author data (e.g. |
154 | * getPrimaryAuthors). |
155 | * @param array $dataFields An array of fields to used to construct method |
156 | * names for retrieving author-related data (e.g., if you pass 'role' the |
157 | * data method will be similar to getPrimaryAuthorsRoles). This value will also |
158 | * be used as a key associated with each author in the resulting data array. |
159 | * |
160 | * @return array |
161 | */ |
162 | public function getAuthorDataFields($index, $dataFields = []) |
163 | { |
164 | $data = $dataFieldValues = []; |
165 | |
166 | // Collect author data |
167 | $authorMethod = sprintf('get%sAuthors', ucfirst($index)); |
168 | $authors = $this->tryMethod($authorMethod, [], []); |
169 | |
170 | // Collect attribute data |
171 | foreach ($dataFields as $field) { |
172 | $fieldMethod = $authorMethod . ucfirst($field) . 's'; |
173 | $dataFieldValues[$field] = $this->tryMethod($fieldMethod, [], []); |
174 | } |
175 | |
176 | // Match up author and attribute data (this assumes that the attribute |
177 | // arrays have the same indices as the author array; i.e. $author[$i] |
178 | // has $dataFieldValues[$attribute][$i]. |
179 | foreach ($authors as $i => $author) { |
180 | if (!isset($data[$author])) { |
181 | $data[$author] = []; |
182 | } |
183 | |
184 | foreach ($dataFieldValues as $field => $dataFieldValue) { |
185 | if (!empty($dataFieldValue[$i])) { |
186 | $data[$author][$field][] = $dataFieldValue[$i]; |
187 | } |
188 | } |
189 | } |
190 | |
191 | return $data; |
192 | } |
193 | |
194 | /** |
195 | * Get award notes for the record. |
196 | * |
197 | * @return array |
198 | */ |
199 | public function getAwards() |
200 | { |
201 | // Not currently stored in the default index schema |
202 | return []; |
203 | } |
204 | |
205 | /** |
206 | * Get notes on bibliography content. |
207 | * |
208 | * @return array |
209 | */ |
210 | public function getBibliographyNotes() |
211 | { |
212 | // Not currently stored in the default index schema |
213 | return []; |
214 | } |
215 | |
216 | /** |
217 | * Get text that can be displayed to represent this record in |
218 | * breadcrumbs. |
219 | * |
220 | * @return string Breadcrumb text to represent this record. |
221 | */ |
222 | public function getBreadcrumb() |
223 | { |
224 | return $this->getShortTitle(); |
225 | } |
226 | |
227 | /** |
228 | * Get the first call number associated with the record (empty string if none). |
229 | * |
230 | * @return string |
231 | */ |
232 | public function getCallNumber() |
233 | { |
234 | $all = $this->getCallNumbers(); |
235 | return $all[0] ?? ''; |
236 | } |
237 | |
238 | /** |
239 | * Get all call numbers associated with the record. |
240 | * |
241 | * @return array |
242 | */ |
243 | public function getCallNumbers() |
244 | { |
245 | return (array)($this->fields['callnumber-raw'] ?? []); |
246 | } |
247 | |
248 | /** |
249 | * Return the first valid DOI found in the record (false if none). |
250 | * |
251 | * @return mixed |
252 | */ |
253 | public function getCleanDOI() |
254 | { |
255 | $field = 'doi_str_mv'; |
256 | return !empty($this->fields[$field][0]) ? $this->fields[$field][0] : false; |
257 | } |
258 | |
259 | /** |
260 | * Return the first valid ISBN found in the record (favoring ISBN-10 over |
261 | * ISBN-13 when possible). |
262 | * |
263 | * @return mixed |
264 | */ |
265 | public function getCleanISBN() |
266 | { |
267 | // Get all the ISBNs and initialize the return value: |
268 | $isbns = $this->getISBNs(); |
269 | $isbn13 = false; |
270 | |
271 | // Loop through the ISBNs: |
272 | foreach ($isbns as $isbn) { |
273 | // Strip off any unwanted notes: |
274 | if ($pos = strpos($isbn, ' ')) { |
275 | $isbn = substr($isbn, 0, $pos); |
276 | } |
277 | |
278 | // If we find an ISBN-10, return it immediately; otherwise, if we find |
279 | // an ISBN-13, save it if it is the first one encountered. |
280 | $isbnObj = new ISBN($isbn); |
281 | if ($isbn10 = $isbnObj->get10()) { |
282 | return $isbn10; |
283 | } |
284 | if (!$isbn13) { |
285 | $isbn13 = $isbnObj->get13(); |
286 | } |
287 | } |
288 | return $isbn13; |
289 | } |
290 | |
291 | /** |
292 | * Return all ISBNs found in the record. |
293 | * |
294 | * @param string $mode Mode for returning ISBNs: |
295 | * - 'only10' returns only ISBN-10s |
296 | * - 'prefer10' returns ISBN-10s if available, otherwise ISBN-13s (default) |
297 | * - 'normalize13' returns ISBN-13s, normalizing ISBN-10s to ISBN-13s |
298 | * @param bool $filterInvalid Whether to filter out invalid ISBNs |
299 | * |
300 | * @return array |
301 | */ |
302 | public function getCleanISBNs( |
303 | string $mode = 'prefer10', |
304 | bool $filterInvalid = true |
305 | ): array { |
306 | $isbns = $this->getISBNs(); |
307 | $all = $tens = $thirteens = $invalid = []; |
308 | foreach ($isbns as $isbn) { |
309 | // Strip off any unwanted notes: |
310 | if ($pos = strpos($isbn, ' ')) { |
311 | $isbn = substr($isbn, 0, $pos); |
312 | } |
313 | $isbnObj = new ISBN($isbn); |
314 | if ($isbnObj->isValid()) { |
315 | if ($isbn10 = $isbnObj->get10()) { |
316 | $normalized |
317 | = $mode === 'normalize13' ? $isbnObj->get13() : $isbn10; |
318 | $tens[] = $normalized; |
319 | $all[] = $normalized; |
320 | } elseif ($isbn13 = $isbnObj->get13()) { |
321 | $thirteens[] = $isbn13; |
322 | $all[] = $isbn13; |
323 | } |
324 | } elseif (!$filterInvalid) { |
325 | $invalid[] = $isbn; |
326 | $all[] = $isbn; |
327 | } |
328 | } |
329 | if ($mode === 'only10') { |
330 | return array_merge($tens, $invalid); |
331 | } |
332 | return $mode === 'prefer10' |
333 | ? array_merge($tens, $thirteens, $invalid) : $all; |
334 | } |
335 | |
336 | /** |
337 | * Get just the base portion of the first listed ISSN (or false if no ISSNs). |
338 | * |
339 | * @return mixed |
340 | */ |
341 | public function getCleanISSN() |
342 | { |
343 | $issns = $this->getISSNs(); |
344 | if (empty($issns)) { |
345 | return false; |
346 | } |
347 | $issn = $issns[0]; |
348 | if ($pos = strpos($issn, ' ')) { |
349 | $issn = substr($issn, 0, $pos); |
350 | } |
351 | return $issn; |
352 | } |
353 | |
354 | /** |
355 | * Get just the first listed OCLC Number (or false if none available). |
356 | * |
357 | * @return mixed |
358 | */ |
359 | public function getCleanOCLCNum() |
360 | { |
361 | $nums = $this->getOCLC(); |
362 | return empty($nums) ? false : $nums[0]; |
363 | } |
364 | |
365 | /** |
366 | * Get just the first listed UPC Number (or false if none available). |
367 | * |
368 | * @return mixed |
369 | */ |
370 | public function getCleanUPC() |
371 | { |
372 | $nums = $this->getUPC(); |
373 | return empty($nums) ? false : $nums[0]; |
374 | } |
375 | |
376 | /** |
377 | * Get just the first listed national bibliography number (or false if none |
378 | * available). |
379 | * |
380 | * @return mixed |
381 | */ |
382 | public function getCleanNBN() |
383 | { |
384 | return false; |
385 | } |
386 | |
387 | /** |
388 | * Get just the base portion of the first listed ISMN (or false if no ISSMs). |
389 | * |
390 | * @return mixed |
391 | */ |
392 | public function getCleanISMN() |
393 | { |
394 | return false; |
395 | } |
396 | |
397 | /** |
398 | * Get the main corporate authors (if any) for the record. |
399 | * |
400 | * @return array |
401 | */ |
402 | public function getCorporateAuthors() |
403 | { |
404 | return (array)($this->fields['author_corporate'] ?? []); |
405 | } |
406 | |
407 | /** |
408 | * Get an array of all main corporate authors roles. |
409 | * |
410 | * @return array |
411 | */ |
412 | public function getCorporateAuthorsRoles() |
413 | { |
414 | return (array)($this->fields['author_corporate_role'] ?? []); |
415 | } |
416 | |
417 | /** |
418 | * Get the date coverage for a record which spans a period of time (i.e. a |
419 | * journal). Use getPublicationDates for publication dates of particular |
420 | * monographic items. |
421 | * |
422 | * @return array |
423 | */ |
424 | public function getDateSpan() |
425 | { |
426 | return (array)($this->fields['dateSpan'] ?? []); |
427 | } |
428 | |
429 | /** |
430 | * Deduplicate author information into associative array with main/corporate/ |
431 | * secondary keys. |
432 | * |
433 | * @param array $dataFields An array of extra data fields to retrieve (see |
434 | * getAuthorDataFields) |
435 | * |
436 | * @return array |
437 | */ |
438 | public function getDeduplicatedAuthors($dataFields = ['role']) |
439 | { |
440 | $authors = []; |
441 | foreach (['primary', 'secondary', 'corporate'] as $type) { |
442 | $authors[$type] = $this->getAuthorDataFields($type, $dataFields); |
443 | } |
444 | |
445 | // deduplicate |
446 | $dedup = function (&$array1, &$array2) { |
447 | if (!empty($array1) && !empty($array2)) { |
448 | $keys = array_keys($array1); |
449 | foreach ($keys as $author) { |
450 | if (isset($array2[$author])) { |
451 | $array1[$author] = array_merge_recursive( |
452 | $array1[$author], |
453 | $array2[$author] |
454 | ); |
455 | unset($array2[$author]); |
456 | } |
457 | } |
458 | } |
459 | }; |
460 | |
461 | $dedup($authors['primary'], $authors['corporate']); |
462 | $dedup($authors['secondary'], $authors['corporate']); |
463 | $dedup($authors['primary'], $authors['secondary']); |
464 | |
465 | $dedup_data = function (&$array) { |
466 | foreach ($array as $author => $data) { |
467 | foreach ($data as $field => $values) { |
468 | if (is_array($values)) { |
469 | $array[$author][$field] = array_unique($values); |
470 | } |
471 | } |
472 | } |
473 | }; |
474 | |
475 | $dedup_data($authors['primary']); |
476 | $dedup_data($authors['secondary']); |
477 | $dedup_data($authors['corporate']); |
478 | |
479 | return $authors; |
480 | } |
481 | |
482 | /** |
483 | * Get the edition of the current record. |
484 | * |
485 | * @return string |
486 | */ |
487 | public function getEdition() |
488 | { |
489 | return $this->fields['edition'] ?? ''; |
490 | } |
491 | |
492 | /** |
493 | * Get notes on finding aids related to the record. |
494 | * |
495 | * @return array |
496 | */ |
497 | public function getFindingAids() |
498 | { |
499 | // Not currently stored in the default index schema |
500 | return []; |
501 | } |
502 | |
503 | /** |
504 | * Get an array of all the formats associated with the record. |
505 | * |
506 | * @return array |
507 | */ |
508 | public function getFormats() |
509 | { |
510 | return (array)($this->fields['format'] ?? []); |
511 | } |
512 | |
513 | /** |
514 | * Get general notes on the record. |
515 | * |
516 | * @return array |
517 | */ |
518 | public function getGeneralNotes() |
519 | { |
520 | // Not currently stored in the default index schema |
521 | return []; |
522 | } |
523 | |
524 | /** |
525 | * Get highlighted author data, if available. |
526 | * |
527 | * @return array |
528 | */ |
529 | public function getRawAuthorHighlights() |
530 | { |
531 | // Not supported by default. |
532 | return []; |
533 | } |
534 | |
535 | /** |
536 | * Get primary author information with highlights applied (if applicable) |
537 | * |
538 | * @return array |
539 | */ |
540 | public function getPrimaryAuthorsWithHighlighting() |
541 | { |
542 | $highlights = []; |
543 | // Create a map of de-highlighted values => highlighted values. |
544 | foreach ($this->getRawAuthorHighlights() as $current) { |
545 | $dehighlighted = str_replace( |
546 | ['{{{{START_HILITE}}}}', '{{{{END_HILITE}}}}'], |
547 | '', |
548 | $current |
549 | ); |
550 | $highlights[$dehighlighted] = $current; |
551 | } |
552 | |
553 | // replace unhighlighted authors with highlighted versions where |
554 | // applicable: |
555 | $authors = []; |
556 | foreach ($this->getPrimaryAuthors() as $author) { |
557 | $authors[] = $highlights[$author] ?? $author; |
558 | } |
559 | return $authors; |
560 | } |
561 | |
562 | /** |
563 | * Get a string representing the last date that the record was indexed. |
564 | * |
565 | * @return string |
566 | */ |
567 | public function getLastIndexed() |
568 | { |
569 | return $this->fields['last_indexed'] ?? ''; |
570 | } |
571 | |
572 | /** |
573 | * Given a field name, return an appropriate caption. |
574 | * |
575 | * @param string $field Field name |
576 | * |
577 | * @return mixed Caption if found, false if none available. |
578 | * |
579 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
580 | */ |
581 | public function getSnippetCaption($field) |
582 | { |
583 | // Not supported by default. |
584 | return false; |
585 | } |
586 | |
587 | /** |
588 | * Pick one line from the highlighted text (if any) to use as a snippet. |
589 | * |
590 | * @return mixed False if no snippet found, otherwise associative array |
591 | * with 'snippet' and 'caption' keys. |
592 | */ |
593 | public function getHighlightedSnippet() |
594 | { |
595 | // Not supported by default. |
596 | return false; |
597 | } |
598 | |
599 | /** |
600 | * Get a highlighted title string, if available. |
601 | * |
602 | * @return string |
603 | */ |
604 | public function getHighlightedTitle() |
605 | { |
606 | // Not supported by default. |
607 | return ''; |
608 | } |
609 | |
610 | /** |
611 | * Get the institutions holding the record. |
612 | * |
613 | * @return array |
614 | */ |
615 | public function getInstitutions() |
616 | { |
617 | return (array)($this->fields['institution'] ?? []); |
618 | } |
619 | |
620 | /** |
621 | * Get the buildings containing the record. |
622 | * |
623 | * @return array |
624 | */ |
625 | public function getBuildings() |
626 | { |
627 | return (array)($this->fields['building'] ?? []); |
628 | } |
629 | |
630 | /** |
631 | * Get an array of all ISBNs associated with the record (may be empty). |
632 | * |
633 | * @return array |
634 | */ |
635 | public function getISBNs() |
636 | { |
637 | return (array)($this->fields['isbn'] ?? []); |
638 | } |
639 | |
640 | /** |
641 | * Get an array of all ISSNs associated with the record (may be empty). |
642 | * |
643 | * @return array |
644 | */ |
645 | public function getISSNs() |
646 | { |
647 | return (array)($this->fields['issn'] ?? []); |
648 | } |
649 | |
650 | /** |
651 | * Get an array of all the languages associated with the record. |
652 | * |
653 | * @return array |
654 | */ |
655 | public function getLanguages() |
656 | { |
657 | return (array)($this->fields['language'] ?? []); |
658 | } |
659 | |
660 | /** |
661 | * Get a raw, unnormalized LCCN. (See getLCCN for normalization). |
662 | * |
663 | * @return string |
664 | */ |
665 | protected function getRawLCCN() |
666 | { |
667 | // Get LCCN from Index |
668 | return $this->fields['lccn'] ?? ''; |
669 | } |
670 | |
671 | /** |
672 | * Get a LCCN, normalised according to info:lccn |
673 | * |
674 | * @return string |
675 | */ |
676 | public function getLCCN() |
677 | { |
678 | // Remove all blanks. |
679 | $raw = preg_replace('{[ \t]+}', '', $this->getRawLCCN()); |
680 | |
681 | // If there is a forward slash (/) in the string, remove it, and remove all |
682 | // characters to the right of the forward slash. |
683 | if (strpos($raw, '/') > 0) { |
684 | $tmpArray = explode('/', $raw); |
685 | $raw = $tmpArray[0]; |
686 | } |
687 | /* If there is a hyphen in the string: |
688 | a. Remove it. |
689 | b. Inspect the substring following (to the right of) the (removed) |
690 | hyphen. Then (and assuming that steps 1 and 2 have been carried out): |
691 | i. All these characters should be digits, and there should be |
692 | six or less. |
693 | ii. If the length of the substring is less than 6, left-fill the |
694 | substring with zeros until the length is six. |
695 | */ |
696 | if (strpos($raw, '-') > 0) { |
697 | // haven't checked for i. above. If they aren't all digits, there is |
698 | // nothing that can be done, so might as well leave it. |
699 | $tmpArray = explode('-', $raw); |
700 | $raw = $tmpArray[0] . str_pad($tmpArray[1], 6, '0', STR_PAD_LEFT); |
701 | } |
702 | return $raw; |
703 | } |
704 | |
705 | /** |
706 | * Get an array of newer titles for the record. |
707 | * |
708 | * @return array |
709 | */ |
710 | public function getNewerTitles() |
711 | { |
712 | return (array)($this->fields['title_new'] ?? []); |
713 | } |
714 | |
715 | /** |
716 | * Get the OCLC number(s) of the record. |
717 | * |
718 | * @return array |
719 | */ |
720 | public function getOCLC() |
721 | { |
722 | return (array)($this->fields['oclc_num'] ?? []); |
723 | } |
724 | |
725 | /** |
726 | * Support method for getOpenUrl() -- pick the OpenURL format. |
727 | * |
728 | * @return string |
729 | */ |
730 | protected function getOpenUrlFormat() |
731 | { |
732 | // If we have multiple formats, Book, Journal and Article are most |
733 | // important... |
734 | $formats = $this->getFormats(); |
735 | if (in_array('Book', $formats) || in_array('eBook', $formats)) { |
736 | return 'Book'; |
737 | } elseif (in_array('Article', $formats)) { |
738 | return 'Article'; |
739 | } elseif (in_array('Journal', $formats)) { |
740 | return 'Journal'; |
741 | } elseif (strlen($this->getCleanISSN()) > 0) { |
742 | // If the record has an ISSN and we have not already |
743 | // decided it is an Article, we'll treat it as a Book |
744 | // if it has an ISBN and is therefore likely part of a |
745 | // monographic series. Otherwise, we'll treat it as a |
746 | // Journal. |
747 | // Anecdotally, some link resolvers do not return correct |
748 | // results when given both ISBN and ISSN for a member of a |
749 | // monographic series. |
750 | return strlen($this->getCleanISBN()) > 0 ? 'Book' : 'Journal'; |
751 | } elseif (isset($formats[0])) { |
752 | return $formats[0]; |
753 | } elseif (strlen($this->getCleanISBN()) > 0) { |
754 | // Last ditch. Note that this is last by intention; if the |
755 | // record has a format set and also has an ISBN, we don't |
756 | // necessarily want to send the ISBN, as it may be a game |
757 | // or a DVD that wouldn't typically be found in OpenURL |
758 | // knowledgebases. |
759 | return 'Book'; |
760 | } |
761 | return 'UnknownFormat'; |
762 | } |
763 | |
764 | /** |
765 | * Get the COinS identifier. |
766 | * |
767 | * @return string |
768 | */ |
769 | protected function getCoinsID() |
770 | { |
771 | // Get the COinS ID -- it should be in the OpenURL section of config.ini, |
772 | // but we'll also check the COinS section for compatibility with legacy |
773 | // configurations (this moved between the RC2 and 1.0 releases). |
774 | if ( |
775 | isset($this->mainConfig->OpenURL->rfr_id) |
776 | && !empty($this->mainConfig->OpenURL->rfr_id) |
777 | ) { |
778 | return $this->mainConfig->OpenURL->rfr_id; |
779 | } |
780 | if ( |
781 | isset($this->mainConfig->COinS->identifier) |
782 | && !empty($this->mainConfig->COinS->identifier) |
783 | ) { |
784 | return $this->mainConfig->COinS->identifier; |
785 | } |
786 | return 'vufind.svn.sourceforge.net'; |
787 | } |
788 | |
789 | /** |
790 | * Get default OpenURL parameters. |
791 | * |
792 | * @return array |
793 | */ |
794 | protected function getDefaultOpenUrlParams() |
795 | { |
796 | // Get a representative publication date: |
797 | $pubDate = $this->getPublicationDates(); |
798 | $pubDate = empty($pubDate) ? '' : $pubDate[0]; |
799 | |
800 | // Start an array of OpenURL parameters: |
801 | return [ |
802 | 'url_ver' => 'Z39.88-2004', |
803 | 'ctx_ver' => 'Z39.88-2004', |
804 | 'ctx_enc' => 'info:ofi/enc:UTF-8', |
805 | 'rfr_id' => 'info:sid/' . $this->getCoinsID() . ':generator', |
806 | 'rft.title' => $this->getTitle(), |
807 | 'rft.date' => $pubDate, |
808 | ]; |
809 | } |
810 | |
811 | /** |
812 | * Get OpenURL parameters for a book. |
813 | * |
814 | * @return array |
815 | */ |
816 | protected function getBookOpenUrlParams() |
817 | { |
818 | $params = $this->getDefaultOpenUrlParams(); |
819 | $params['rft_val_fmt'] = 'info:ofi/fmt:kev:mtx:book'; |
820 | $params['rft.genre'] = 'book'; |
821 | $params['rft.btitle'] = $params['rft.title']; |
822 | $series = $this->getSeries(); |
823 | if (count($series) > 0) { |
824 | // Handle both possible return formats of getSeries: |
825 | $params['rft.series'] = is_array($series[0]) ? |
826 | $series[0]['name'] : $series[0]; |
827 | } |
828 | $params['rft.au'] = $this->getPrimaryAuthor(); |
829 | $publishers = $this->getPublishers(); |
830 | if (count($publishers) > 0) { |
831 | $params['rft.pub'] = $publishers[0]; |
832 | } |
833 | $placesOfPublication = $this->getPlacesOfPublication(); |
834 | if (count($placesOfPublication) > 0) { |
835 | $params['rft.place'] = $placesOfPublication[0]; |
836 | } |
837 | $params['rft.edition'] = $this->getEdition(); |
838 | $params['rft.isbn'] = (string)$this->getCleanISBN(); |
839 | return $params; |
840 | } |
841 | |
842 | /** |
843 | * Get OpenURL parameters for an article. |
844 | * |
845 | * @return array |
846 | */ |
847 | protected function getArticleOpenUrlParams() |
848 | { |
849 | $params = $this->getDefaultOpenUrlParams(); |
850 | $params['rft_val_fmt'] = 'info:ofi/fmt:kev:mtx:journal'; |
851 | $params['rft.genre'] = 'article'; |
852 | $params['rft.issn'] = (string)$this->getCleanISSN(); |
853 | // an article may have also an ISBN: |
854 | $params['rft.isbn'] = (string)$this->getCleanISBN(); |
855 | $params['rft.volume'] = $this->getContainerVolume(); |
856 | $params['rft.issue'] = $this->getContainerIssue(); |
857 | $params['rft.spage'] = $this->getContainerStartPage(); |
858 | // unset default title -- we only want jtitle/atitle here: |
859 | unset($params['rft.title']); |
860 | $params['rft.jtitle'] = $this->getContainerTitle(); |
861 | $params['rft.atitle'] = $this->getTitle(); |
862 | $params['rft.au'] = $this->getPrimaryAuthor(); |
863 | |
864 | $params['rft.format'] = 'Article'; |
865 | $langs = $this->getLanguages(); |
866 | if (count($langs) > 0) { |
867 | $params['rft.language'] = $langs[0]; |
868 | } |
869 | return $params; |
870 | } |
871 | |
872 | /** |
873 | * Get OpenURL parameters for an unknown format. |
874 | * |
875 | * @param string $format Name of format |
876 | * |
877 | * @return array |
878 | */ |
879 | protected function getUnknownFormatOpenUrlParams($format = 'UnknownFormat') |
880 | { |
881 | $params = $this->getDefaultOpenUrlParams(); |
882 | $params['rft_val_fmt'] = 'info:ofi/fmt:kev:mtx:dc'; |
883 | $params['rft.creator'] = $this->getPrimaryAuthor(); |
884 | $publishers = $this->getPublishers(); |
885 | if (count($publishers) > 0) { |
886 | $params['rft.pub'] = $publishers[0]; |
887 | } |
888 | $params['rft.format'] = $format; |
889 | $langs = $this->getLanguages(); |
890 | if (count($langs) > 0) { |
891 | $params['rft.language'] = $langs[0]; |
892 | } |
893 | return $params; |
894 | } |
895 | |
896 | /** |
897 | * Get OpenURL parameters for a journal. |
898 | * |
899 | * @return array |
900 | */ |
901 | protected function getJournalOpenUrlParams() |
902 | { |
903 | $params = $this->getUnknownFormatOpenUrlParams('Journal'); |
904 | /* This is probably the most technically correct way to represent |
905 | * a journal run as an OpenURL; however, it doesn't work well with |
906 | * Zotero, so it is currently commented out -- instead, we just add |
907 | * some extra fields and to the "unknown format" case. |
908 | $params['rft_val_fmt'] = 'info:ofi/fmt:kev:mtx:journal'; |
909 | $params['rft.genre'] = 'journal'; |
910 | $params['rft.jtitle'] = $params['rft.title']; |
911 | $params['rft.issn'] = $this->getCleanISSN(); |
912 | $params['rft.au'] = $this->getPrimaryAuthor(); |
913 | */ |
914 | $params['rft.issn'] = (string)$this->getCleanISSN(); |
915 | |
916 | // Including a date in a title-level Journal OpenURL may be too |
917 | // limiting -- in some link resolvers, it may cause the exclusion |
918 | // of databases if they do not cover the exact date provided! |
919 | unset($params['rft.date']); |
920 | |
921 | // If we're working with the SFX or Alma resolver, we should add a |
922 | // special parameter to ensure that electronic holdings links |
923 | // are shown even though no specific date or issue is specified: |
924 | $resolver = strtolower($this->mainConfig->OpenURL->resolver ?? ''); |
925 | if ('sfx' === $resolver) { |
926 | $params['sfx.ignore_date_threshold'] = 1; |
927 | } elseif ('alma' === $resolver) { |
928 | $params['u.ignore_date_coverage'] = 'true'; |
929 | } |
930 | return $params; |
931 | } |
932 | |
933 | /** |
934 | * Get the OpenURL parameters to represent this record (useful for the |
935 | * title attribute of a COinS span tag). |
936 | * |
937 | * @param bool $overrideSupportsOpenUrl Flag to override checking |
938 | * supportsOpenUrl() (default is false) |
939 | * |
940 | * @return string OpenURL parameters. |
941 | */ |
942 | public function getOpenUrl($overrideSupportsOpenUrl = false) |
943 | { |
944 | // stop here if this record does not support OpenURLs |
945 | if (!$overrideSupportsOpenUrl && !$this->supportsOpenUrl()) { |
946 | return false; |
947 | } |
948 | |
949 | // Set up parameters based on the format of the record: |
950 | $format = $this->getOpenUrlFormat(); |
951 | $method = "get{$format}OpenUrlParams"; |
952 | if (method_exists($this, $method)) { |
953 | $params = $this->$method(); |
954 | } else { |
955 | $params = $this->getUnknownFormatOpenUrlParams($format); |
956 | } |
957 | |
958 | // Assemble the URL: |
959 | $query = []; |
960 | foreach ($params as $key => $value) { |
961 | $value = (array)$value; |
962 | foreach ($value as $sub) { |
963 | $query[] = urlencode($key) . '=' . urlencode($sub); |
964 | } |
965 | } |
966 | return implode('&', $query); |
967 | } |
968 | |
969 | /** |
970 | * Get the OpenURL parameters to represent this record for COinS even if |
971 | * supportsOpenUrl() is false for this RecordDriver. |
972 | * |
973 | * @return string OpenURL parameters. |
974 | */ |
975 | public function getCoinsOpenUrl() |
976 | { |
977 | return $this->getOpenUrl($this->supportsCoinsOpenUrl()); |
978 | } |
979 | |
980 | /** |
981 | * Get an array of physical descriptions of the item. |
982 | * |
983 | * @return array |
984 | */ |
985 | public function getPhysicalDescriptions() |
986 | { |
987 | return (array)($this->fields['physical'] ?? []); |
988 | } |
989 | |
990 | /** |
991 | * Get the item's place of publication. |
992 | * |
993 | * @return array |
994 | */ |
995 | public function getPlacesOfPublication() |
996 | { |
997 | // Not currently stored in the default index schema |
998 | return []; |
999 | } |
1000 | |
1001 | /** |
1002 | * Get an array of playing times for the record (if applicable). |
1003 | * |
1004 | * @return array |
1005 | */ |
1006 | public function getPlayingTimes() |
1007 | { |
1008 | // Not currently stored in the default index schema |
1009 | return []; |
1010 | } |
1011 | |
1012 | /** |
1013 | * Get an array of previous titles for the record. |
1014 | * |
1015 | * @return array |
1016 | */ |
1017 | public function getPreviousTitles() |
1018 | { |
1019 | return (array)($this->fields['title_old'] ?? []); |
1020 | } |
1021 | |
1022 | /** |
1023 | * Get the main author of the record. |
1024 | * |
1025 | * @return string |
1026 | */ |
1027 | public function getPrimaryAuthor() |
1028 | { |
1029 | $authors = $this->getPrimaryAuthors(); |
1030 | return $authors[0] ?? ''; |
1031 | } |
1032 | |
1033 | /** |
1034 | * Get the main authors of the record. |
1035 | * |
1036 | * @return array |
1037 | */ |
1038 | public function getPrimaryAuthors() |
1039 | { |
1040 | return (array)($this->fields['author'] ?? []); |
1041 | } |
1042 | |
1043 | /** |
1044 | * Get an array of all main authors roles (complementing |
1045 | * getSecondaryAuthorsRoles()). |
1046 | * |
1047 | * @return array |
1048 | */ |
1049 | public function getPrimaryAuthorsRoles() |
1050 | { |
1051 | return (array)($this->fields['author_role'] ?? []); |
1052 | } |
1053 | |
1054 | /** |
1055 | * Get credits of people involved in production of the item. |
1056 | * |
1057 | * @return array |
1058 | */ |
1059 | public function getProductionCredits() |
1060 | { |
1061 | // Not currently stored in the default index schema |
1062 | return []; |
1063 | } |
1064 | |
1065 | /** |
1066 | * Get the publication dates of the record. See also getDateSpan(). |
1067 | * |
1068 | * @return array |
1069 | */ |
1070 | public function getPublicationDates() |
1071 | { |
1072 | return (array)($this->fields['publishDate'] ?? []); |
1073 | } |
1074 | |
1075 | /** |
1076 | * Get human readable publication dates for display purposes (may not be suitable |
1077 | * for computer processing -- use getPublicationDates() for that). |
1078 | * |
1079 | * @return array |
1080 | */ |
1081 | public function getHumanReadablePublicationDates() |
1082 | { |
1083 | return $this->getPublicationDates(); |
1084 | } |
1085 | |
1086 | /** |
1087 | * Get an array of publication detail lines combining information from |
1088 | * getPublicationDates(), getPublishers() and getPlacesOfPublication(). |
1089 | * |
1090 | * @return array |
1091 | */ |
1092 | public function getPublicationDetails() |
1093 | { |
1094 | $places = $this->getPlacesOfPublication(); |
1095 | $names = $this->getPublishers(); |
1096 | $dates = $this->getHumanReadablePublicationDates(); |
1097 | |
1098 | $i = 0; |
1099 | $retval = []; |
1100 | while (isset($places[$i]) || isset($names[$i]) || isset($dates[$i])) { |
1101 | // Build objects to represent each set of data; these will |
1102 | // transform seamlessly into strings in the view layer. |
1103 | $retval[] = new Response\PublicationDetails( |
1104 | $places[$i] ?? '', |
1105 | $names[$i] ?? '', |
1106 | $dates[$i] ?? '' |
1107 | ); |
1108 | $i++; |
1109 | } |
1110 | |
1111 | return $retval; |
1112 | } |
1113 | |
1114 | /** |
1115 | * Get an array of publication frequency information. |
1116 | * |
1117 | * @return array |
1118 | */ |
1119 | public function getPublicationFrequency() |
1120 | { |
1121 | // Not currently stored in the default index schema |
1122 | return []; |
1123 | } |
1124 | |
1125 | /** |
1126 | * Get the publishers of the record. |
1127 | * |
1128 | * @return array |
1129 | */ |
1130 | public function getPublishers() |
1131 | { |
1132 | return (array)($this->fields['publisher'] ?? []); |
1133 | } |
1134 | |
1135 | /** |
1136 | * Get an array of information about record history, obtained in real-time |
1137 | * from the ILS. |
1138 | * |
1139 | * @return array |
1140 | */ |
1141 | public function getRealTimeHistory() |
1142 | { |
1143 | // Not supported by the default index schema -- implement in child classes. |
1144 | return []; |
1145 | } |
1146 | |
1147 | /** |
1148 | * Get an array of information about record holdings, obtained in real-time |
1149 | * from the ILS. |
1150 | * |
1151 | * @return array |
1152 | */ |
1153 | public function getRealTimeHoldings() |
1154 | { |
1155 | // Not supported by the default index schema -- implement in child classes. |
1156 | return ['holdings' => []]; |
1157 | } |
1158 | |
1159 | /** |
1160 | * Get an array of strings describing relationships to other items. |
1161 | * |
1162 | * @return array |
1163 | */ |
1164 | public function getRelationshipNotes() |
1165 | { |
1166 | // Not currently stored in the default index schema |
1167 | return []; |
1168 | } |
1169 | |
1170 | /** |
1171 | * Get an array of all secondary authors (complementing getPrimaryAuthors()). |
1172 | * |
1173 | * @return array |
1174 | */ |
1175 | public function getSecondaryAuthors() |
1176 | { |
1177 | return (array)($this->fields['author2'] ?? []); |
1178 | } |
1179 | |
1180 | /** |
1181 | * Get an array of all secondary authors roles (complementing |
1182 | * getPrimaryAuthorsRoles()). |
1183 | * |
1184 | * @return array |
1185 | */ |
1186 | public function getSecondaryAuthorsRoles() |
1187 | { |
1188 | return (array)($this->fields['author2_role'] ?? []); |
1189 | } |
1190 | |
1191 | /** |
1192 | * Get an array of all series names containing the record. Array entries may |
1193 | * be either the name string, or an associative array with 'name' and 'number' |
1194 | * keys. |
1195 | * |
1196 | * @return array |
1197 | */ |
1198 | public function getSeries() |
1199 | { |
1200 | // Only use the contents of the series2 field if the series field is empty |
1201 | return !empty($this->fields['series']) |
1202 | ? (array)$this->fields['series'] |
1203 | : (array)($this->fields['series2'] ?? []); |
1204 | } |
1205 | |
1206 | /** |
1207 | * Get the short (pre-subtitle) title of the record. |
1208 | * |
1209 | * @return string |
1210 | */ |
1211 | public function getShortTitle() |
1212 | { |
1213 | return $this->fields['title_short'] ?? ''; |
1214 | } |
1215 | |
1216 | /** |
1217 | * Get the item's source. |
1218 | * |
1219 | * @return string |
1220 | */ |
1221 | public function getSource() |
1222 | { |
1223 | // Not supported in base class: |
1224 | return ''; |
1225 | } |
1226 | |
1227 | /** |
1228 | * Get the subtitle of the record. |
1229 | * |
1230 | * @return string |
1231 | */ |
1232 | public function getSubtitle() |
1233 | { |
1234 | return $this->fields['title_sub'] ?? ''; |
1235 | } |
1236 | |
1237 | /** |
1238 | * Get an array of technical details on the item represented by the record. |
1239 | * |
1240 | * @return array |
1241 | */ |
1242 | public function getSystemDetails() |
1243 | { |
1244 | // Not currently stored in the default index schema |
1245 | return []; |
1246 | } |
1247 | |
1248 | /** |
1249 | * Get an array of summary strings for the record. |
1250 | * |
1251 | * @return array |
1252 | */ |
1253 | public function getSummary() |
1254 | { |
1255 | // We need to return an array, so if we have a description, turn it into an |
1256 | // array (it should be a flat string according to the default schema, but we |
1257 | // might as well support the array case just to be on the safe side: |
1258 | return (array)($this->fields['description'] ?? []); |
1259 | } |
1260 | |
1261 | /** |
1262 | * Get an array of note about the record's target audience. |
1263 | * |
1264 | * @return array |
1265 | */ |
1266 | public function getTargetAudienceNotes() |
1267 | { |
1268 | // Not currently stored in the default index schema |
1269 | return []; |
1270 | } |
1271 | |
1272 | /** |
1273 | * Returns one of three things: a full URL to a thumbnail preview of the record |
1274 | * if an image is available in an external system; an array of parameters to |
1275 | * send to VuFind's internal cover generator if no fixed URL exists; or false |
1276 | * if no thumbnail can be generated. |
1277 | * |
1278 | * @param string $size Size of thumbnail (small, medium or large -- small is |
1279 | * default). |
1280 | * |
1281 | * @return string|array|bool |
1282 | */ |
1283 | public function getThumbnail($size = 'small') |
1284 | { |
1285 | if (!empty($this->fields['thumbnail'])) { |
1286 | return $this->fields['thumbnail']; |
1287 | } |
1288 | $arr = [ |
1289 | 'author' => mb_substr($this->getPrimaryAuthor(), 0, 300, 'utf-8'), |
1290 | 'callnumber' => $this->getCallNumber(), |
1291 | 'size' => $size, |
1292 | 'title' => mb_substr($this->getTitle(), 0, 300, 'utf-8'), |
1293 | 'recordid' => $this->getUniqueID(), |
1294 | 'source' => $this->getSourceIdentifier(), |
1295 | ]; |
1296 | $isbns = $this->getCleanISBNs(); |
1297 | if (!empty($isbns)) { |
1298 | $arr['isbns'] = $isbns; |
1299 | } |
1300 | if ($issn = $this->getCleanISSN()) { |
1301 | $arr['issn'] = $issn; |
1302 | } |
1303 | if ($oclc = $this->getCleanOCLCNum()) { |
1304 | $arr['oclc'] = $oclc; |
1305 | } |
1306 | if ($upc = $this->getCleanUPC()) { |
1307 | $arr['upc'] = $upc; |
1308 | } |
1309 | if ($nbn = $this->getCleanNBN()) { |
1310 | $arr['nbn'] = $nbn['nbn']; |
1311 | } |
1312 | if ($ismn = $this->getCleanISMN()) { |
1313 | $arr['ismn'] = $ismn; |
1314 | } |
1315 | if ($uuid = $this->getCleanUuid()) { |
1316 | $arr['uuid'] = $uuid; |
1317 | } |
1318 | |
1319 | // If an ILS driver has injected extra details, check for IDs in there |
1320 | // to fill gaps: |
1321 | if ($ilsDetails = $this->getExtraDetail('ils_details')) { |
1322 | $idTypes = ['isbn', 'issn', 'oclc', 'upc', 'nbn', 'ismn', 'uuid']; |
1323 | foreach ($idTypes as $key) { |
1324 | if (!isset($arr[$key]) && isset($ilsDetails[$key])) { |
1325 | $arr[$key] = $ilsDetails[$key]; |
1326 | } |
1327 | } |
1328 | } |
1329 | return $arr; |
1330 | } |
1331 | |
1332 | /** |
1333 | * Get the full title of the record. |
1334 | * |
1335 | * @return string |
1336 | */ |
1337 | public function getTitle() |
1338 | { |
1339 | return $this->fields['title'] ?? ''; |
1340 | } |
1341 | |
1342 | /** |
1343 | * Get the text of the part/section portion of the title. |
1344 | * |
1345 | * @return string |
1346 | */ |
1347 | public function getTitleSection() |
1348 | { |
1349 | // Not currently stored in the default index schema |
1350 | return null; |
1351 | } |
1352 | |
1353 | /** |
1354 | * Get the statement of responsibility that goes with the title (i.e. "by John |
1355 | * Smith"). |
1356 | * |
1357 | * @return string |
1358 | */ |
1359 | public function getTitleStatement() |
1360 | { |
1361 | // Not currently stored in the default index schema |
1362 | return null; |
1363 | } |
1364 | |
1365 | /** |
1366 | * Get an array of lines from the table of contents. |
1367 | * |
1368 | * @return array |
1369 | */ |
1370 | public function getTOC() |
1371 | { |
1372 | return (array)($this->fields['contents'] ?? []); |
1373 | } |
1374 | |
1375 | /** |
1376 | * Get hierarchical place names |
1377 | * |
1378 | * @return array |
1379 | */ |
1380 | public function getHierarchicalPlaceNames() |
1381 | { |
1382 | // Not currently stored in the default index schema |
1383 | return []; |
1384 | } |
1385 | |
1386 | /** |
1387 | * Get the UPC number(s) of the record. |
1388 | * |
1389 | * @return array |
1390 | */ |
1391 | public function getUPC() |
1392 | { |
1393 | return (array)($this->fields['upc_str_mv'] ?? []); |
1394 | } |
1395 | |
1396 | /** |
1397 | * Get UUIDs (Universally unique identifier). These are commonly used in, for |
1398 | * example, digital library or repository systems and can be a useful match |
1399 | * point with third party systems. |
1400 | * |
1401 | * @return array |
1402 | */ |
1403 | public function getUuids() |
1404 | { |
1405 | return (array)($this->fields['uuid_str_mv'] ?? []); |
1406 | } |
1407 | |
1408 | /** |
1409 | * Get just the first listed UUID (Universally unique identifier), or false if |
1410 | * none available. |
1411 | * |
1412 | * @return mixed |
1413 | */ |
1414 | public function getCleanUuid() |
1415 | { |
1416 | $uuids = $this->getUuids(); |
1417 | return empty($uuids) ? false : $uuids[0]; |
1418 | } |
1419 | |
1420 | /** |
1421 | * Return an array of associative URL arrays with one or more of the following |
1422 | * keys: |
1423 | * |
1424 | * <li> |
1425 | * <ul>desc: URL description text to display (optional)</ul> |
1426 | * <ul>url: fully-formed URL (required if 'route' is absent)</ul> |
1427 | * <ul>route: VuFind route to build URL with (required if 'url' is absent)</ul> |
1428 | * <ul>routeParams: Parameters for route (optional)</ul> |
1429 | * <ul>queryString: Query params to append after building route (optional)</ul> |
1430 | * </li> |
1431 | * |
1432 | * @return array |
1433 | */ |
1434 | public function getURLs() |
1435 | { |
1436 | $filter = function ($url) { |
1437 | return ['url' => $url]; |
1438 | }; |
1439 | return array_map($filter, (array)($this->fields['url'] ?? [])); |
1440 | } |
1441 | |
1442 | /** |
1443 | * Get the hierarchy_top_id(s) associated with this item (empty if none). |
1444 | * |
1445 | * @return array |
1446 | */ |
1447 | public function getHierarchyTopID() |
1448 | { |
1449 | // Unsupported by default: |
1450 | return []; |
1451 | } |
1452 | |
1453 | /** |
1454 | * Get the absolute parent title(s) associated with this item (empty if none). |
1455 | * |
1456 | * @return array |
1457 | */ |
1458 | public function getHierarchyTopTitle() |
1459 | { |
1460 | // Unsupported by default: |
1461 | return []; |
1462 | } |
1463 | |
1464 | /** |
1465 | * Get an associative array (id => title) of collections containing this record. |
1466 | * |
1467 | * @return array |
1468 | */ |
1469 | public function getContainingCollections() |
1470 | { |
1471 | // Unsupported by default: |
1472 | return []; |
1473 | } |
1474 | |
1475 | /** |
1476 | * Get the value of whether or not this is a collection level record |
1477 | * |
1478 | * NOTE: \VuFind\Hierarchy\TreeDataFormatter\AbstractBase::isCollection() |
1479 | * duplicates some of this logic. |
1480 | * |
1481 | * @return bool |
1482 | */ |
1483 | public function isCollection() |
1484 | { |
1485 | // Unsupported by default: |
1486 | return false; |
1487 | } |
1488 | |
1489 | /** |
1490 | * Get a list of hierarchy trees containing this record. |
1491 | * |
1492 | * @param string $hierarchyID The hierarchy to get the tree for |
1493 | * |
1494 | * @return mixed An associative array of hierarchy trees on success |
1495 | * (id => title), false if no hierarchies found |
1496 | * |
1497 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1498 | */ |
1499 | public function getHierarchyTrees($hierarchyID = false) |
1500 | { |
1501 | // Unsupported by default: |
1502 | return false; |
1503 | } |
1504 | |
1505 | /** |
1506 | * Get the Hierarchy Type (false if none) |
1507 | * |
1508 | * @return string|bool |
1509 | */ |
1510 | public function getHierarchyType() |
1511 | { |
1512 | // Unsupported by default: |
1513 | return false; |
1514 | } |
1515 | |
1516 | /** |
1517 | * Return the unique identifier of this record within the index; |
1518 | * useful for retrieving additional information (like tags and user |
1519 | * comments) from the external MySQL database. |
1520 | * |
1521 | * @return string Unique identifier. |
1522 | */ |
1523 | public function getUniqueID() |
1524 | { |
1525 | if (!isset($this->fields['id'])) { |
1526 | throw new \Exception('ID not set!'); |
1527 | } |
1528 | return $this->fields['id']; |
1529 | } |
1530 | |
1531 | /** |
1532 | * Return an XML representation of the record using the specified format. |
1533 | * Return false if the format is unsupported. |
1534 | * |
1535 | * @param string $format Name of format to use (corresponds with |
1536 | * OAI-PMH metadataPrefix parameter). |
1537 | * @param string $baseUrl Base URL of host containing VuFind (optional; |
1538 | * may be used to inject record URLs into XML when appropriate). |
1539 | * @param RecordLinker $linker Record linker helper (optional; may be used to |
1540 | * inject record URLs into XML when appropriate). |
1541 | * |
1542 | * @return mixed XML, or false if format unsupported. |
1543 | */ |
1544 | public function getXML($format, $baseUrl = null, $linker = null) |
1545 | { |
1546 | // For OAI-PMH Dublin Core, produce the necessary XML: |
1547 | if ($format == 'oai_dc') { |
1548 | $dc = 'http://purl.org/dc/elements/1.1/'; |
1549 | $xml = new \SimpleXMLElement( |
1550 | '<oai_dc:dc ' |
1551 | . 'xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/" ' |
1552 | . 'xmlns:dc="' . $dc . '" ' |
1553 | . 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' |
1554 | . 'xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai_dc/ ' |
1555 | . 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd" />' |
1556 | ); |
1557 | $xml->addChild('title', htmlspecialchars($this->getTitle()), $dc); |
1558 | $authors = $this->getDeduplicatedAuthors(); |
1559 | foreach ($authors as $list) { |
1560 | foreach (array_keys($list) as $author) { |
1561 | $xml->addChild('creator', htmlspecialchars($author), $dc); |
1562 | } |
1563 | } |
1564 | foreach ($this->getLanguages() as $lang) { |
1565 | $xml->addChild('language', htmlspecialchars($lang), $dc); |
1566 | } |
1567 | foreach ($this->getPublishers() as $pub) { |
1568 | $xml->addChild('publisher', htmlspecialchars($pub), $dc); |
1569 | } |
1570 | foreach ($this->getPublicationDates() as $date) { |
1571 | $xml->addChild('date', htmlspecialchars($date), $dc); |
1572 | } |
1573 | foreach ($this->getAllSubjectHeadings() as $subj) { |
1574 | $xml->addChild( |
1575 | 'subject', |
1576 | htmlspecialchars(implode(' -- ', $subj)), |
1577 | $dc |
1578 | ); |
1579 | } |
1580 | if (null !== $baseUrl && null !== $linker) { |
1581 | $url = $baseUrl . $linker->getUrl($this); |
1582 | $xml->addChild('identifier', $url, $dc); |
1583 | } |
1584 | |
1585 | return $xml->asXml(); |
1586 | } |
1587 | |
1588 | // Unsupported format: |
1589 | return false; |
1590 | } |
1591 | |
1592 | /** |
1593 | * Get an array of strings representing citation formats supported |
1594 | * by this record's data (empty if none). For possible legal values, |
1595 | * see /application/themes/root/helpers/Citation.php, getCitation() |
1596 | * method. |
1597 | * |
1598 | * @return array Strings representing citation formats. |
1599 | */ |
1600 | protected function getSupportedCitationFormats() |
1601 | { |
1602 | return ['APA', 'Chicago', 'MLA']; |
1603 | } |
1604 | |
1605 | /** |
1606 | * Get the title of the item that contains this record (i.e. MARC 773s of a |
1607 | * journal). |
1608 | * |
1609 | * @return string |
1610 | */ |
1611 | public function getContainerTitle() |
1612 | { |
1613 | return $this->fields['container_title'] ?? ''; |
1614 | } |
1615 | |
1616 | /** |
1617 | * Get the volume of the item that contains this record (i.e. MARC 773v of a |
1618 | * journal). |
1619 | * |
1620 | * @return string |
1621 | */ |
1622 | public function getContainerVolume() |
1623 | { |
1624 | return $this->fields['container_volume'] ?? ''; |
1625 | } |
1626 | |
1627 | /** |
1628 | * Get the issue of the item that contains this record (i.e. MARC 773l of a |
1629 | * journal). |
1630 | * |
1631 | * @return string |
1632 | */ |
1633 | public function getContainerIssue() |
1634 | { |
1635 | return $this->fields['container_issue'] ?? ''; |
1636 | } |
1637 | |
1638 | /** |
1639 | * Get the start page of the item that contains this record (i.e. MARC 773q of a |
1640 | * journal). |
1641 | * |
1642 | * @return string |
1643 | */ |
1644 | public function getContainerStartPage() |
1645 | { |
1646 | return $this->fields['container_start_page'] ?? ''; |
1647 | } |
1648 | |
1649 | /** |
1650 | * Get the end page of the item that contains this record. |
1651 | * |
1652 | * @return string |
1653 | */ |
1654 | public function getContainerEndPage() |
1655 | { |
1656 | // Not supported by the default index schema -- implement in child classes. |
1657 | return ''; |
1658 | } |
1659 | |
1660 | /** |
1661 | * Get a full, free-form reference to the context of the item that contains this |
1662 | * record (i.e. volume, year, issue, pages). |
1663 | * |
1664 | * @return string |
1665 | */ |
1666 | public function getContainerReference() |
1667 | { |
1668 | return $this->fields['container_reference'] ?? ''; |
1669 | } |
1670 | |
1671 | /** |
1672 | * Get a sortable title for the record (i.e. no leading articles). |
1673 | * |
1674 | * @return string |
1675 | */ |
1676 | public function getSortTitle() |
1677 | { |
1678 | return $this->fields['title_sort'] ?? parent::getSortTitle(); |
1679 | } |
1680 | |
1681 | /** |
1682 | * Get schema.org type mapping, an array of sub-types of |
1683 | * http://schema.org/CreativeWork, defaulting to CreativeWork |
1684 | * itself if nothing else matches. |
1685 | * |
1686 | * @return array |
1687 | */ |
1688 | public function getSchemaOrgFormatsArray() |
1689 | { |
1690 | $types = []; |
1691 | foreach ($this->getFormats() as $format) { |
1692 | switch ($format) { |
1693 | case 'Book': |
1694 | case 'eBook': |
1695 | $types['Book'] = 1; |
1696 | break; |
1697 | case 'Video': |
1698 | case 'VHS': |
1699 | $types['Movie'] = 1; |
1700 | break; |
1701 | case 'Photo': |
1702 | $types['Photograph'] = 1; |
1703 | break; |
1704 | case 'Map': |
1705 | $types['Map'] = 1; |
1706 | break; |
1707 | case 'Audio': |
1708 | $types['MusicAlbum'] = 1; |
1709 | break; |
1710 | default: |
1711 | $types['CreativeWork'] = 1; |
1712 | } |
1713 | } |
1714 | return array_keys($types); |
1715 | } |
1716 | |
1717 | /** |
1718 | * Get schema.org type mapping, expected to be a space-delimited string of |
1719 | * sub-types of http://schema.org/CreativeWork, defaulting to CreativeWork |
1720 | * itself if nothing else matches. |
1721 | * |
1722 | * @return string |
1723 | */ |
1724 | public function getSchemaOrgFormats() |
1725 | { |
1726 | return implode(' ', $this->getSchemaOrgFormatsArray()); |
1727 | } |
1728 | |
1729 | /** |
1730 | * Get information on records deduplicated with this one |
1731 | * |
1732 | * @return array Array keyed by source id containing record id |
1733 | */ |
1734 | public function getDedupData() |
1735 | { |
1736 | return $this->fields['dedup_data'] ?? []; |
1737 | } |
1738 | |
1739 | /** |
1740 | * Get the number of child records belonging to this record |
1741 | * |
1742 | * @return int Number of records |
1743 | */ |
1744 | public function getChildRecordCount() |
1745 | { |
1746 | // Unsupported by default |
1747 | return 0; |
1748 | } |
1749 | |
1750 | /** |
1751 | * Get the container record id. |
1752 | * |
1753 | * @return string Container record id (empty string if none) |
1754 | */ |
1755 | public function getContainerRecordID() |
1756 | { |
1757 | // Unsupported by default |
1758 | return ''; |
1759 | } |
1760 | |
1761 | /** |
1762 | * Get the bbox-geo variable. |
1763 | * |
1764 | * @return array |
1765 | */ |
1766 | public function getGeoLocation() |
1767 | { |
1768 | return (array)($this->fields['long_lat'] ?? []); |
1769 | } |
1770 | |
1771 | /** |
1772 | * Get the map display (lat/lon) coordinates |
1773 | * |
1774 | * @return array |
1775 | */ |
1776 | public function getDisplayCoordinates() |
1777 | { |
1778 | return (array)($this->fields['long_lat_display'] ?? []); |
1779 | } |
1780 | |
1781 | /** |
1782 | * Get the map display (lat/lon) labels |
1783 | * |
1784 | * @return array |
1785 | */ |
1786 | public function getCoordinateLabels() |
1787 | { |
1788 | return (array)($this->fields['long_lat_label'] ?? []); |
1789 | } |
1790 | } |