1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Colligator; |
4
|
|
|
|
5
|
|
|
use Colligator\Events\Marc21RecordImported; |
6
|
|
|
use Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement; |
7
|
|
|
use Event; |
8
|
|
|
use Scriptotek\Marc\Record as MarcRecord; |
9
|
|
|
use Scriptotek\SimpleMarcParser\BibliographicRecord; |
10
|
|
|
use Scriptotek\SimpleMarcParser\HoldingsRecord; |
11
|
|
|
use Scriptotek\SimpleMarcParser\Parser as MarcParser; |
12
|
|
|
use Scriptotek\SimpleMarcParser\ParserException; |
13
|
|
|
|
14
|
|
|
class Marc21Importer |
15
|
|
|
{ |
16
|
|
|
public $record; |
17
|
|
|
public $parser; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Create a new job instance. |
21
|
|
|
* |
22
|
|
|
* @param $record |
23
|
|
|
*/ |
24
|
|
|
public function __construct(MarcParser $parser = null, DescriptionScraper $scraper) |
25
|
|
|
{ |
26
|
|
|
$this->parser = $parser ?: new MarcParser(); |
27
|
|
|
$this->scraper = $scraper ?: new DescriptionScraper(); |
28
|
|
|
} |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Parse using SimpleMarcParser and separate bibliographic and holdings. |
32
|
|
|
* |
33
|
|
|
* @param QuiteSimpleXMLElement $data |
34
|
|
|
* |
35
|
|
|
* @return array |
36
|
|
|
*/ |
37
|
|
|
public function parseRecord(QuiteSimpleXMLElement $data) |
38
|
|
|
{ |
39
|
|
|
$data->registerXPathNamespaces([ |
40
|
|
|
'marc' => 'http://www.loc.gov/MARC21/slim' |
41
|
|
|
]); |
42
|
|
|
$biblio = null; |
43
|
|
|
$holdings = []; |
44
|
|
|
foreach ($data->xpath('.//marc:record') as $rec) { |
45
|
|
|
$parsed = $this->parser->parse($rec); |
46
|
|
|
if ($parsed instanceof BibliographicRecord) { |
47
|
|
|
$biblio = $parsed->toArray(); |
48
|
|
|
} elseif ($parsed instanceof HoldingsRecord) { |
49
|
|
|
$holdings[] = $parsed->toArray(); |
50
|
|
|
} |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
if (!count($holdings)) { |
54
|
|
|
// Oh, hello Alma... |
55
|
|
|
$q = $data->first('.//marc:record')->asXML(); |
56
|
|
|
|
57
|
|
|
$rec = MarcRecord::fromString($q); |
58
|
|
|
|
59
|
|
|
$itemMap = [ |
60
|
|
|
'x' => 'location', // OBS: 1030310 |
61
|
|
|
'y' => 'shelvinglocation', // OBS: k00475 |
62
|
|
|
'b' => 'barcode', |
63
|
|
|
'z' => 'callcode', |
64
|
|
|
'a' => 'id', |
65
|
|
|
'd' => 'due_back_date', |
66
|
|
|
'p' => 'process_type', |
67
|
|
|
's' => 'item_status', |
68
|
|
|
'n' => 'public_note', |
69
|
|
|
]; |
70
|
|
|
|
71
|
|
|
foreach ($rec->getFields('909') as $field) { |
72
|
|
|
$holding = []; |
73
|
|
|
|
74
|
|
|
foreach ($itemMap as $c => $f) { |
75
|
|
|
$sf = $field->getSubfield($c); |
76
|
|
|
if ($sf) { |
77
|
|
|
$holding[$f] = $sf->getData(); |
78
|
|
|
} |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
$sf = $field->getSubfield('s'); |
82
|
|
|
if ($sf) { |
83
|
|
|
$sft = $sf->getData(); |
84
|
|
|
if ($sft) { |
85
|
|
|
$holding['circulation_status'] = 'Available'; |
86
|
|
|
} else { |
87
|
|
|
$holding['circulation_status'] = 'Unavailable'; |
88
|
|
|
} |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
$holdings[] = $holding; |
92
|
|
|
} |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
return [$biblio, $holdings]; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Fixes wrong vocabulary codes returned by the Bibsys SRU service. |
100
|
|
|
* Bibsys strips dashes in 648, 650, 655, but not in 651, so we have |
101
|
|
|
* to do that ourselves. This affects 'no-ubo-mn' and 'no-ubo-mr'. |
102
|
|
|
*/ |
103
|
|
|
public function fixVocabularyCode($code) |
104
|
|
|
{ |
105
|
|
|
return str_replace('-', '', $code); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* @param array $biblio |
110
|
|
|
* @param array $holdings |
111
|
|
|
* |
112
|
|
|
* @return null|Document |
113
|
|
|
*/ |
114
|
|
|
public function importParsedRecord(array $biblio, array $holdings = []) |
115
|
|
|
{ |
116
|
|
|
// Convert Carbon date objects to ISO8601 strings |
117
|
|
|
if (isset($biblio['created'])) { |
118
|
|
|
$biblio['created'] = $biblio['created']->toIso8601String(); |
119
|
|
|
} |
120
|
|
|
if (isset($biblio['modified'])) { |
121
|
|
|
$biblio['modified'] = $biblio['modified']->toIso8601String(); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
// Find existing Document or create a new one |
125
|
|
|
$doc = Document::firstOrNew(['bibsys_id' => $biblio['id']]); |
126
|
|
|
|
127
|
|
|
// Update Document |
128
|
|
|
$doc->bibliographic = $biblio; |
129
|
|
|
$doc->holdings = $holdings; |
130
|
|
|
|
131
|
|
|
// Extract description from bibliographic record if no description exists |
132
|
|
|
if (isset($biblio['description']) && is_null($doc->description)) { |
133
|
|
|
$this->scraper->updateDocument($doc, $biblio['description']); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
if (!$doc->save()) { |
137
|
|
|
$this->error("Document $biblio->id could not be saved!"); |
138
|
|
|
|
139
|
|
|
return; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
// Check other form |
143
|
|
|
$other_id = array_get($biblio, 'other_form.id'); |
144
|
|
|
if (!empty($other_id)) { |
|
|
|
|
145
|
|
|
|
146
|
|
|
// @TODO: https://github.com/scriptotek/colligator-backend/issues/34 |
147
|
|
|
// Alma uses 776, but the IDs can't be looked up. Need to investigate! |
148
|
|
|
// Example: 990824196984702204 (NZ: 990824196984702201), having |
|
|
|
|
149
|
|
|
// 776 0_ $t The Earth after us : what legacy will humans leave in the rocks? $w 991234552274702201 |
150
|
|
|
|
151
|
|
|
// $doc2 = Document::where('bibsys_id', '=', $other_id)->first(); |
|
|
|
|
152
|
|
|
// if (is_null($doc2)) { |
|
|
|
|
153
|
|
|
// $record = \SruClient::first('bs.objektid=' . $other_id); |
|
|
|
|
154
|
|
|
// \Log::debug('Importing related record ' . $other_id); |
|
|
|
|
155
|
|
|
// if (is_null($record)) { |
|
|
|
|
156
|
|
|
// die('uh oh'); |
|
|
|
|
157
|
|
|
// } else { |
|
|
|
|
158
|
|
|
// $this->import($record->data); |
|
|
|
|
159
|
|
|
// } |
160
|
|
|
// } |
161
|
|
|
|
162
|
|
|
// @TODO: Add a separate jobb that updates e-books weekly or so.. |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
// Sync subjects |
166
|
|
|
$subject_ids = []; |
167
|
|
|
foreach ($biblio['subjects'] as $value) { |
168
|
|
|
$value['vocabulary'] = $this->fixVocabularyCode($value['vocabulary']); |
169
|
|
|
$subject = Subject::lookup($value['vocabulary'], $value['term'], $value['type']); |
170
|
|
|
if (is_null($subject)) { |
171
|
|
|
$subject = Subject::create($value); |
172
|
|
|
} |
173
|
|
|
$subject_ids[] = $subject->id; |
174
|
|
|
} |
175
|
|
|
$doc->subjects()->sync($subject_ids); |
176
|
|
|
|
177
|
|
|
// Sync genres |
178
|
|
|
$genre_ids = []; |
179
|
|
|
foreach ($biblio['genres'] as $value) { |
180
|
|
|
$genre = Genre::lookup($value['vocabulary'], $value['term']); |
181
|
|
|
if (is_null($genre)) { |
182
|
|
|
$genre = Genre::create($value); |
183
|
|
|
} |
184
|
|
|
$genre_ids[] = $genre->id; |
185
|
|
|
} |
186
|
|
|
$doc->genres()->sync($genre_ids); |
187
|
|
|
|
188
|
|
|
// Extract cover from bibliographic record if no local cover exists |
189
|
|
|
if (isset($biblio['cover_image']) && is_null($doc->cover)) { |
190
|
|
|
try { |
191
|
|
|
$doc->storeCover($biblio['cover_image']); |
192
|
|
|
} catch (\ErrorException $e) { |
|
|
|
|
193
|
|
|
\Log::error('Failed to store cover: ' . $biblio['cover_image']); |
194
|
|
|
} |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
return $doc; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Execute the job. |
202
|
|
|
* @param QuiteSimpleXMLElement $record |
203
|
|
|
* @return int|null |
204
|
|
|
*/ |
205
|
|
|
public function import(QuiteSimpleXMLElement $record) |
206
|
|
|
{ |
207
|
|
|
try { |
208
|
|
|
list($biblio, $holdings) = $this->parseRecord($record); |
209
|
|
|
} catch (ParserException $e) { |
210
|
|
|
$this->error('Failed to parse MARC record. Error "' . $e->getMessage() . '" in: ' . $e->getFile() . ':' . $e->getLine() . "\nStack trace:\n" . $e->getTraceAsString()); |
211
|
|
|
|
212
|
|
|
return null; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
$doc = $this->importParsedRecord($biblio, $holdings); |
216
|
|
|
|
217
|
|
|
\Log::debug('[Marc21Importer] Imported ' . $doc->bibsys_id . ' as ' . $doc->id); |
218
|
|
|
|
219
|
|
|
if (!is_null($doc)) { |
220
|
|
|
Event::fire(new Marc21RecordImported($doc->id)); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
return $doc->id; |
224
|
|
|
} |
225
|
|
|
} |
226
|
|
|
|
This check looks for the bodies of
if
statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.These
if
bodies can be removed. If you have an empty if but statements in theelse
branch, consider inverting the condition.could be turned into
This is much more concise to read.