]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/WWW/Exporter.pm
2fc4462cabf1916727de3fed3cdfffdc40efc841
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / WWW / Exporter.pm
1 package OpenILS::WWW::Exporter;
2 use strict;
3 use warnings;
4 use bytes;
5
6 use Apache2::Log;
7 use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND :log);
8 use APR::Const    -compile => qw(:error SUCCESS);
9 use APR::Table;
10
11 use Apache2::RequestRec ();
12 use Apache2::RequestIO ();
13 use Apache2::RequestUtil;
14 use CGI;
15 use Data::Dumper;
16 use Text::CSV;
17
18 use OpenSRF::EX qw(:try);
19 use OpenSRF::Utils qw/:datetime/;
20 use OpenSRF::Utils::Cache;
21 use OpenSRF::System;
22 use OpenSRF::AppSession;
23 use XML::LibXML;
24 use XML::LibXSLT;
25
26 use Encode;
27 use Unicode::Normalize;
28 use OpenILS::Utils::Fieldmapper;
29 use OpenSRF::Utils::Logger qw/$logger/;
30
31 use MARC::Record;
32 use MARC::File::XML ( BinaryEncoding => 'UTF-8' );
33
34 use UNIVERSAL::require;
35
36 our @formats = qw/USMARC UNIMARC XML BRE/;
37
38 # set the bootstrap config and template include directory when
39 # this module is loaded
40 my $bootstrap;
41
42 sub import {
43         my $self = shift;
44         $bootstrap = shift;
45 }
46
47
48 sub child_init {
49         OpenSRF::System->bootstrap_client( config_file => $bootstrap );
50         return Apache2::Const::OK;
51 }
52
53 sub handler {
54         my $r = shift;
55         my $cgi = new CGI;
56
57         # find some IDs ...
58         my @records;
59
60         @records = map { $_ ? ($_) : () } $cgi->param('id');
61
62         if (!@records) { # try for a file
63                 my $file = $cgi->param('idfile');
64                 if ($file) {
65                         my $col = $cgi->param('idcolumn') || 0;
66                         my $csv = new Text::CSV;
67
68                         while (<$file>) {
69                                 $csv->parse($_);
70                                 my @data = $csv->fields;
71                                 my $id = $data[$col];
72                                 $id =~ s/\D+//o;
73                                 next unless ($id);
74                                 push @records, $id;
75                         }
76                 }
77         }
78
79         if (!@records) { # try pathinfo
80                 my $path_rec = $cgi->path_info();
81                 if ($path_rec) {
82                         @records = map { $_ ? ($_) : () } split '/', $path_rec;
83                 }
84         }
85
86         my $ses = OpenSRF::AppSession->create('open-ils.cstore');
87
88         # still no records ...
89         my $container = $cgi->param('containerid');
90         if ($container) {
91                 my $bucket = $ses->request( 'open-ils.cstore.direct.container.biblio_record_entry_bucket.retrieve', $container )->gather(1);
92         unless($bucket) {
93             $r->log->error("No such bucket $container"); 
94             $logger->error("No such bucket $container"); 
95             return Apache2::Const::NOT_FOUND;
96         }
97         if ($bucket->pub !~ /t|1/oi) {
98                 my $authid = $cgi->cookie('ses') || $cgi->param('ses');
99                 my $auth = verify_login($authid);
100                     if (!$auth) {
101                             return 403;
102                 }
103         }
104                 my $recs = $ses->request( 'open-ils.cstore.direct.container.biblio_record_entry_bucket_item.search.atomic', { bucket => $container } )->gather(1);
105                 @records = map { ($_->target_biblio_record_entry) } @$recs;
106         }
107
108         return show_template($r) unless (@records);
109
110         my $type = $cgi->param('rectype') || 'biblio';
111         if ($type ne 'biblio' && $type ne 'authority') {
112                 return 400;
113         }
114
115         my $tcn_v = 'tcn_value';
116         my $tcn_s = 'tcn_source';
117
118         my $holdings = $cgi->param('holdings') if ($type eq 'biblio');
119         my $location = $cgi->param('location') || 'gaaagpl'; # just because...
120
121         my $format = $cgi->param('format') || 'USMARC';
122         $format = uc($format);
123
124         my $encoding = $cgi->param('encoding') || 'UTF-8';
125         $encoding = uc($encoding);
126
127         my $filename = $cgi->param('filename') || "export.$type.$encoding.$format";
128
129         binmode(STDOUT, ':raw') if ($encoding ne 'UTF-8');
130         binmode(STDOUT, ':utf8') if ($encoding eq 'UTF-8');
131
132         if (!grep { uc($format) eq $_ } @formats) {
133                 return 400;
134         }
135
136         if ($format ne 'XML') {
137                 my $ftype = 'MARC::File::' . $format;
138                 $ftype->require;
139         }
140
141
142         $r->headers_out->set("Content-Disposition" => "inline; filename=$filename");
143
144         if (uc($format) eq 'XML') {
145                 $r->content_type('application/xml');
146         } else {
147                 $r->content_type('application/octet-stream');
148         }
149
150         $r->print( <<"  HEADER" ) if (uc($format) eq 'XML');
151 <?xml version="1.0" encoding="$encoding"?>
152 <collection xmlns='http://www.loc.gov/MARC21/slim'>
153         HEADER
154
155         my %orgs;
156         my %shelves;
157         my %statuses;
158
159         my $flesh = {};
160         if ($holdings) {
161
162                 my $req = $ses->request( 'open-ils.cstore.direct.actor.org_unit.search', { id => { '!=' => undef } } );
163
164                 while (my $o = $req->recv) {
165                         next if ($req->failed);
166                         $o = $o->content;
167                         last unless ($o);
168                         $orgs{$o->id} = $o;
169                 }
170                 $req->finish;
171
172                 $req = $ses->request( 'open-ils.cstore.direct.asset.copy_location.search', { id => { '!=' => undef } } );
173
174                 while (my $s = $req->recv) {
175                         next if ($req->failed);
176                         $s = $s->content;
177                         last unless ($s);
178                         $shelves{$s->id} = $s;
179                 }
180                 $req->finish;
181
182                 $req = $ses->request( 'open-ils.cstore.direct.config.copy_status.search', { id => { '!=' => undef } } );
183
184                 while (my $s = $req->recv) {
185                         next if ($req->failed);
186                         $s = $s->content;
187                         last unless ($s);
188                         $statuses{$s->id} = $s;
189                 }
190                 $req->finish;
191
192                 $flesh = { flesh => 2, flesh_fields => { bre => [ 'call_numbers' ], acn => [ 'copies' ] } };
193         }
194
195         for my $i ( @records ) {
196                 my $bib;
197                 try {
198                         local $SIG{ALRM} = sub { die "TIMEOUT\n" };
199                         alarm(1);
200                         $bib = $ses->request( "open-ils.cstore.direct.$type.record_entry.retrieve", $i, $flesh )->gather(1);
201                         alarm(0);
202                 } otherwise {
203                         warn "\n!!!!!! Timed out trying to read record $i\n";
204                 };
205                 alarm(0);
206
207                 next unless $bib;
208
209                 if (uc($format) eq 'BRE') {
210                         $r->print( OpenSRF::Utils::JSON->perl2JSON($bib) . "\n" );
211                         next;
212                 }
213
214                 try {
215
216                         my $req = MARC::Record->new_from_xml( $bib->marc, $encoding, $format );
217                         $req->encoding($encoding) if ($encoding eq 'UTF-8');
218
219                         if ($holdings) {
220                                 $req->delete_field( $_ ) for ($req->field('852')); # remove any legacy 852s
221                                 my $cn_list = $bib->call_numbers;
222                                 if ($cn_list && @$cn_list) {
223
224                                         my $cp_list = [ map { @{ $_->copies } } @$cn_list ];
225                                         if ($cp_list && @$cp_list) {
226
227                                                 my %cn_map;
228                                                 push @{$cn_map{$_->call_number}}, $_ for (@$cp_list);
229                                         
230                                                 for my $cn ( @$cn_list ) {
231                                                         my $cn_map_list = $cn_map{$cn->id};
232         
233                                                         for my $cp ( @$cn_map_list ) {
234                                         
235                                                                 $req->append_fields(
236                                                                         MARC::Field->new(
237                                                                                 852, '4', '', 
238                                                                                 a => $location,
239                                                                                 b => $orgs{$cn->owning_lib}->shortname,
240                                                                                 b => $orgs{$cp->circ_lib}->shortname,
241                                                                                 c => $shelves{$cp->location}->name,
242                                                                                 j => $cn->label,
243                                                                                 ($cp->circ_modifier ? ( g => $cp->circ_modifier ) : ()),
244                                                                                 p => $cp->barcode,
245                                                                                 ($cp->price ? ( y => $cp->price ) : ()),
246                                                                                 ($cp->copy_number ? ( t => $cp->copy_number ) : ()),
247                                                                                 ($cp->ref eq 't' ? ( x => 'reference' ) : ( x => 'nonreference' )),
248                                                                                 ($cp->holdable eq 'f' ? ( x => 'unholdable' ) : ( x => 'holdable' )),
249                                                                                 ($cp->circulate eq 'f' ? ( x => 'noncirculating' ) : ( x => 'circulating' )),
250                                                                                 ($cp->opac_visible eq 'f' ? ( x => 'hidden' ) : ( x => 'visible' )),
251                                                                                 z => $statuses{$cp->status}->name,
252                                                                         )
253                                                                 );
254
255                                                         }
256                                                 }
257                                         }
258                                 }
259                         }
260
261                         if (uc($format) eq 'XML') {
262                                 my $x = $req->as_xml_record;
263                                 $x =~ s/^<\?xml version="1.0" encoding="UTF-8"\?>//o;
264                                 $r->print($x);
265                         } elsif (uc($format) eq 'UNIMARC') {
266                                 $r->print($req->as_usmarc);
267                         } elsif (uc($format) eq 'USMARC') {
268                                 $r->print($req->as_usmarc);
269                         }
270
271             $r->rflush();
272
273                 } otherwise {
274                         my $e = shift;
275                         warn "\n$e\n";
276                 };
277
278         }
279
280         $r->print("</collection>\n") if ($format eq 'XML');
281
282         return Apache2::Const::OK;
283
284 }
285
286 sub verify_login {
287         my $auth_token = shift;
288         return undef unless $auth_token;
289
290         my $user = OpenSRF::AppSession
291                 ->create("open-ils.auth")
292                 ->request( "open-ils.auth.session.retrieve", $auth_token )
293                 ->gather(1);
294
295         if (ref($user) eq 'HASH' && $user->{ilsevent} == 1001) {
296                 return undef;
297         }
298
299         return $user if ref($user);
300         return undef;
301 }
302
303 sub show_template {
304         my $r = shift;
305
306         $r->content_type('text/html');
307         $r->print(<<HTML);
308
309 <html>
310         <head>
311                 <title>Record Export</title>
312         </head>
313         <body>
314                 <form method="POST" enctype="multipart/form-data">
315                         Use field number <input type="text" size="2" maxlength="2" name="idcolumn" value="0"/> (starting from 0)
316                         from CSV file <input type="file" name="idfile"/>
317                         <br/><br/> <b>or</b> <br/><br/>
318                         Record ID <input type="text" size="12" maxlength="12" name="id"/>
319                         <br/><br/> Record Type:
320                         <select name="rectype">
321                                 <option value="biblio">Bibliographic Records</option>
322                                 <option value="authority">Authority Records</option>
323                         </select>
324                         <br/> Record Format:
325                         <select name="format">
326                                 <option value="USMARC">MARC21</option>
327                                 <option value="UNIMARC">UNIMARC</option>
328                                 <option value="XML">MARC XML</option>
329                                 <option value="BRE">Evergreen BRE</option>
330                         </select>
331                         <br/> Record Encoding:
332                         <select name="encoding">
333                                 <option value="UTF-8">UTF-8</option>
334                                 <option value="MARC8">MARC8</option>
335                         </select>
336                         <br/> Include holdings in Bibliographic Records:
337                         <input type="checkbox" name="holdings" value="1">
338                         <br/><br/><input type="submit" value="Retrieve Records"/>
339                 </form>
340         </body>
341 </html>
342
343 HTML
344
345         return Apache2::Const::OK;
346 }
347
348 1;