]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/WWW/Vandelay.pm
LP 2061136 follow-up: ng lint --fix
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / WWW / Vandelay.pm
1 package OpenILS::WWW::Vandelay;
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 AUTH_REQUIRED FORBIDDEN HTTP_UNAUTHORIZED HTTP_REQUEST_ENTITY_TOO_LARGE HTTP_INTERNAL_SERVER_ERROR :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 use GD;
18
19 use OpenSRF::EX qw(:try);
20 use OpenSRF::Utils::Cache;
21 use OpenSRF::System;
22 use OpenSRF::AppSession;
23 use XML::LibXML;
24
25 use OpenILS::Utils::Fieldmapper;
26 use OpenSRF::Utils::Logger qw/$logger/;
27
28 use MARC::Record;
29 use MARC::File::XML ( BinaryEncoding => 'UTF-8' );
30
31 use MIME::Base64;
32 use Digest::MD5 qw/md5_hex/;
33 use OpenSRF::Utils::SettingsClient;
34
35 use UNIVERSAL::require;
36
37 our @formats = qw/USMARC UNIMARC XML BRE/;
38 my $MAX_FILE_SIZE = 10737418240; #10G
39 my $MAX_JACKET_SIZE = 10737418240; #10G
40 my $FILE_READ_SIZE = 4096;
41
42 # set the bootstrap config and template include directory when
43 # this module is loaded
44 my $bootstrap;
45
46 sub import {
47         my $self = shift;
48         $bootstrap = shift;
49 }
50
51
52 sub child_init {
53         OpenSRF::System->bootstrap_client( config_file => $bootstrap );
54         return Apache2::Const::OK;
55 }
56
57 sub spool_marc {
58     my $r = shift;
59     my $cgi = new CGI;
60
61     my $auth = $cgi->param('ses') || $cgi->cookie('ses') || $cgi->cookie('eg.auth.token');
62     if ($auth =~ /^"(.+)"$/) {
63         $auth = $1;
64     }
65
66     unless(verify_login($auth)) {
67         $logger->error("authentication failed on vandelay record import: $auth");
68         return Apache2::Const::FORBIDDEN;
69     }
70
71     my $data_fingerprint = '';
72     my $purpose = $cgi->param('purpose') || '';
73     my $infile = $cgi->param('marc_upload') || '';
74     my $bib_source = $cgi->param('bib_source') || '';
75
76     $logger->debug("purpose = $purpose, infile = $infile, bib_source = $bib_source");
77
78     my $conf = OpenSRF::Utils::SettingsClient->new;
79     my $dir = $conf->config_value(
80         apps => 'open-ils.vandelay' => app_settings => databases => 'importer');
81
82     unless(-w $dir) {
83         $logger->error("We need some place to store our MARC files");
84         return Apache2::Const::FORBIDDEN;
85     }
86
87     if($infile and -e $infile) {
88         my ($total_bytes, $buf, $bytes) = (0);
89         $data_fingerprint = md5_hex(time."$$".rand());
90         my $outfile = "$dir/$data_fingerprint.mrc";
91
92         unless(open(OUTFILE, ">$outfile")) {
93             $logger->error("unable to open MARC file [$outfile] for writing: $@");
94             return Apache2::Const::FORBIDDEN;
95         }
96
97         while($bytes = sysread($infile, $buf, $FILE_READ_SIZE)) {
98             $total_bytes += $bytes;
99             if($total_bytes >= $MAX_FILE_SIZE) {
100                 close(OUTFILE);
101                 unlink $outfile;
102                 $logger->error("import exceeded upload size: $MAX_FILE_SIZE");
103                 return Apache2::Const::FORBIDDEN;
104             }
105             print OUTFILE $buf;
106         }
107
108         close(OUTFILE);
109
110         OpenSRF::Utils::Cache->new->put_cache(
111             'vandelay_import_spool_' . $data_fingerprint,
112             {   purpose => $purpose, 
113                 path => $outfile,
114                 bib_source => $bib_source,
115             }
116         );
117     }
118
119     $logger->info("uploaded MARC batch with key $data_fingerprint");
120     $r->content_type('text/plain; charset=utf-8');
121     print "$data_fingerprint";
122     return Apache2::Const::OK;
123 }
124
125 sub spool_jacket {
126     my $r = shift;
127     my $cgi = new CGI;
128
129     my $auth = $cgi->param('ses') || $cgi->cookie('ses') || $cgi->cookie('eg.auth.token');
130     if ($auth =~ /^"(.+)"$/) {
131         $auth = $1;
132     }
133     my $user = verify_login($auth);
134     my $perm_check = verify_permission($auth, $user, $user->ws_ou, ['UPLOAD_COVER_IMAGE']);
135
136     unless($user) {
137         $logger->error("spool_jacket: authentication failed on jacket image import: $auth");
138         print '"session not found"';
139         return Apache2::Const::OK;
140     }
141     unless($perm_check) {
142         $logger->error("spool_jacket: authorization failed on jacket image import: $auth");
143         print '"permission denied"';
144         return Apache2::Const::OK;
145     }
146
147     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
148     my $compression_flag = $ses->request( 'open-ils.cstore.direct.config.global_flag.retrieve', 'opac.cover_upload_compression' )->gather(1);
149     my $compression_level = ($compression_flag && OpenILS::Application::AppUtils->is_true($compression_flag->enabled)) ? $compression_flag->value : -1;
150     if ($compression_level < -1 || $compression_level > 9) {
151         $r->content_type('text/plain; charset=utf-8');
152         print '"invalid compression level"';
153         return Apache2::Const::OK;
154     }
155     $logger->debug("spool_jacket: PNG compression set to $compression_level");
156
157     my $max_jacket_size = OpenILS::Application::AppUtils->ou_ancestor_setting_value($user->ws_ou, 'opac.cover_upload_max_file_size') || $MAX_JACKET_SIZE;
158
159     my $infile = $cgi->param('jacket_upload') || '';
160     my $bib_record = $cgi->param('bib_record') || '';
161     unless ($bib_record =~ /^-?\d+$/) {
162         $logger->error("spool_jacket: passed bib_record = $bib_record");
163         $r->content_type('text/plain; charset=utf-8');
164         print '"bib not found"';
165         return Apache2::Const::OK;
166     }
167
168     $logger->debug("infile = $infile, bib_record = $bib_record");
169
170     my $conf = OpenSRF::Utils::SettingsClient->new;
171     my $dir = $conf->config_value(
172         apps => 'open-ils.vandelay' => app_settings => databases => 'jackets');
173
174     unless(-w $dir) { # FIXME: good or bad idea to fallback to /openils/var/web/opac/extracs/ac if opensrf.xml is not updated?
175         $logger->error("spool_jacket: We need some place to store our jacket files");
176         print '"jacket location not configured"';
177         return Apache2::Const::OK;
178     }
179
180     if($infile and -e $infile) {
181         my $memcache = OpenSRF::Utils::Cache->new('global');
182
183         my ($total_bytes, $buf, $bytes) = (0);
184         my $outfile_large = "$dir/jacket/large/r/$bib_record";
185         my $outfile_medium = "$dir/jacket/medium/r/$bib_record";
186         my $outfile_small = "$dir/jacket/small/r/$bib_record";
187
188         unless(open(OUTFILE_LARGE, ">$outfile_large.temp")) {
189             $logger->error("spool_jacket: unable to open jacket file [$outfile_large.temp] for writing: $@");
190             return Apache2::Const::FORBIDDEN;
191             print '"unable to open file for writing"';
192             return Apache2::Const::OK;
193         }
194
195         while($bytes = sysread($infile, $buf, $FILE_READ_SIZE)) {
196             $total_bytes += $bytes;
197             if($total_bytes >= $max_jacket_size) {
198                 close(OUTFILE_LARGE);
199                 unlink $outfile_large . ".temp";
200                 $logger->error("spool_jacket: import exceeded upload size: $max_jacket_size");
201                 print '"file too large"';
202                 return Apache2::Const::OK;
203             }
204             print OUTFILE_LARGE $buf;
205         }
206
207         close(OUTFILE_LARGE);
208
209         my $image;
210         eval { $image = GD::Image->newFromPng("$outfile_large.temp"); };
211         eval { $image = $image || GD::Image->newFromJpeg("$outfile_large.temp"); };
212         eval { $image = $image || GD::Image->newFromGif("$outfile_large.temp"); };
213         eval { $image = $image || GD::Image->newFromXpm("$outfile_large.temp"); };
214         eval { $image = $image || GD::Image->newFromWBMP("$outfile_large.temp"); };
215         eval { $image = $image || GD::Image->newFromXbm("$outfile_large.temp"); };
216         eval { $image = $image || GD::Image->newFromGd("$outfile_large.temp"); };
217         eval { $image = $image || GD::Image->newFromGd2("$outfile_large.temp"); };
218         unless ($image) {
219             unlink $outfile_large . ".temp";
220             $logger->error("spool_jacket: unable to parse $outfile_large.temp");
221             $r->content_type('text/plain; charset=utf-8');
222             print '"parse error"';
223             return Apache2::Const::OK;
224         }
225
226         my ($image_width, $image_height) = $image->getBounds();
227
228         #### resizing for small
229
230         my $target_width = 55; # FIXME: get these from settings, but for now, using customer desired width and aspect ratio observed from OpenLibrary
231         my $target_height = 91;
232
233         my $width_ratio = $target_width / $image_width;
234         my $height_ratio = $target_height / $image_height;
235
236         my $best_ratio = $width_ratio < $height_ratio ? $width_ratio : $height_ratio;
237
238         my ($new_width, $new_height) = ($image_width * $best_ratio, $image_height * $best_ratio);
239
240         my $new_image = $image->copyScaleInterpolated($new_width, $new_height);
241
242         unless(open(OUTFILE_SMALL, ">$outfile_small.temp")) {
243             $logger->error("spool_jacket: unable to open jacket file [$outfile_small.temp] for writing: $@");
244             print '"unable to open file for writing"';
245             return Apache2::Const::OK;
246         }
247         print OUTFILE_SMALL $new_image->png($compression_level);
248         close(OUTFILE_SMALL);
249
250         #### resizing for medium
251
252         $target_width = 120;
253         $target_height = 200;
254
255         $width_ratio = $target_width / $image_width;
256         $height_ratio = $target_height / $image_height;
257
258         $best_ratio = $width_ratio < $height_ratio ? $width_ratio : $height_ratio;
259
260         ($new_width, $new_height) = ($image_width * $best_ratio, $image_height * $best_ratio);
261
262         $new_image = $image->copyScaleInterpolated($new_width, $new_height);
263
264         unless(open(OUTFILE_MEDIUM, ">$outfile_medium.temp")) {
265             $logger->error("spool_jacket: unable to open jacket file [$outfile_medium.temp] for writing: $@");
266             print '"unable to open file for writing"';
267             return Apache2::Const::OK;
268         }
269         print OUTFILE_MEDIUM $new_image->png($compression_level);
270         close(OUTFILE_MEDIUM);
271
272         #### resizing for large
273
274         $target_width = 475;
275         $target_height = 787;
276
277         $width_ratio = $target_width / $image_width;
278         $height_ratio = $target_height / $image_height;
279
280         $best_ratio = $width_ratio < $height_ratio ? $width_ratio : $height_ratio;
281
282         ($new_width, $new_height) = ($image_width * $best_ratio, $image_height * $best_ratio);
283
284         $new_image = $image->copyScaleInterpolated($new_width, $new_height);
285
286         unless(open(OUTFILE_LARGE, ">$outfile_large.temp")) {
287             $logger->error("spool_jacket: unable to open jacket file [$outfile_large.temp] for writing: $@");
288             print "'unable to open file for writing'\n";
289             return Apache2::Const::OK;
290         }
291         print OUTFILE_LARGE $new_image->png($compression_level);
292         close(OUTFILE_LARGE);
293
294         #### renaming temp files to final images
295
296         rename "$outfile_small.temp", $outfile_small;
297         rename "$outfile_medium.temp", $outfile_medium;
298         rename "$outfile_large.temp", $outfile_large;
299
300         #### clearing memcache
301
302         my @jacket_sizes = ('large','medium','small');
303         foreach my $size (@jacket_sizes) {
304             my $key = "ac.jacket.$size.record_$bib_record";
305             $memcache->delete_cache($key);
306         }
307
308     } else {
309         $logger->error("spool_jacket: image not uploaded? check form action and encoding");
310         print '"upload error"';
311         return Apache2::Const::OK;
312     }
313
314     $logger->info("spool_jacket: uploaded jacket file for record $bib_record");
315     $r->content_type('text/plain; charset=utf-8');
316     print "1";
317     return Apache2::Const::OK;
318 }
319
320 sub verify_login {
321         my $auth_token = shift;
322         return undef unless $auth_token;
323
324         my $user = OpenSRF::AppSession
325                 ->create("open-ils.auth")
326                 ->request( "open-ils.auth.session.retrieve", $auth_token )
327                 ->gather(1);
328
329         if (ref($user) eq 'HASH' && $user->{ilsevent} == 1001) {
330                 return undef;
331         }
332
333         return $user if ref($user);
334         return undef;
335 }
336
337 sub verify_permission { # FIXME: could refactor these verify_ subs in WWW/
338     my ($token, $user, $org_unit, $permissions) = @_;
339
340     my $failures = OpenSRF::AppSession
341         ->create('open-ils.actor')
342         ->request('open-ils.actor.user.perm.check', $token, $user->id, $org_unit, $permissions)
343         ->gather(1);
344
345     return !scalar(@$failures);
346 }
347
348 1;