]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/WWW/EGCatLoader.pm
show some holds
[working/Evergreen.git] / Open-ILS / src / perlmods / OpenILS / WWW / EGCatLoader.pm
1 package OpenILS::WWW::EGCatLoader;
2 use strict; use warnings;
3 use CGI;
4 use XML::LibXML;
5 use Digest::MD5 qw(md5_hex);
6 use Apache2::Const -compile => qw(OK DECLINED HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
7 use OpenSRF::AppSession;
8 use OpenSRF::EX qw/:try/;
9 use OpenSRF::Utils qw/:datetime/;
10 use OpenSRF::Utils::Logger qw/$logger/;
11 use OpenILS::Application::AppUtils;
12 use OpenILS::Utils::CStoreEditor qw/:funcs/;
13 use OpenILS::Utils::Fieldmapper;
14 use DateTime::Format::ISO8601;
15 my $U = 'OpenILS::Application::AppUtils';
16
17 my %cache; # proc-level cache
18
19 sub new {
20     my($class, $apache, $ctx) = @_;
21
22     my $self = bless({}, ref($class) || $class);
23
24     $self->apache($apache);
25     $self->ctx($ctx);
26     $self->cgi(CGI->new);
27
28     OpenILS::Utils::CStoreEditor->init; # just in case
29     $self->editor(new_editor());
30
31     return $self;
32 }
33
34
35 # current Apache2::RequestRec;
36 sub apache {
37     my($self, $apache) = @_;
38     $self->{apache} = $apache if $apache;
39     return $self->{apache};
40 }
41
42 # runtime / template context
43 sub ctx {
44     my($self, $ctx) = @_;
45     $self->{ctx} = $ctx if $ctx;
46     return $self->{ctx};
47 }
48
49 # cstore editor
50 sub editor {
51     my($self, $editor) = @_;
52     $self->{editor} = $editor if $editor;
53     return $self->{editor};
54 }
55
56 # CGI handle
57 sub cgi {
58     my($self, $cgi) = @_;
59     $self->{cgi} = $cgi if $cgi;
60     return $self->{cgi};
61 }
62
63
64 # load common data, then load page data
65 sub load {
66     my $self = shift;
67
68     $self->load_helpers;
69     my $stat = $self->load_common;
70     return $stat unless $stat == Apache2::Const::OK;
71
72     my $path = $self->apache->path_info;
73     return $self->load_home if $path =~ /opac\/home/;
74     return $self->load_login if $path =~ /opac\/login/;
75     return $self->load_logout if $path =~ /opac\/logout/;
76     return $self->load_rresults if $path =~ /opac\/results/;
77     return $self->load_record if $path =~ /opac\/record/;
78     return $self->load_place_hold if $path =~ /opac\/place_hold/;
79
80     # XXX here is where we need to add a check for session timeout, either that
81     # or have load_* check something...
82
83     return $self->load_myopac_holds if $path =~ /opac\/myopac\/holds/;
84     return $self->load_myopac_circs if $path =~ /opac\/myopac\/circs/;
85     return $self->load_myopac if $path =~ /opac\/myopac/;
86
87     return Apache2::Const::OK;
88 }
89
90 # general purpose utility functions added to the environment
91 sub load_helpers {
92     my $self = shift;
93     my $e = $self->editor;
94     my $ctx = $self->ctx;
95
96     $cache{map} = {}; # public object maps
97     $cache{list} = {}; # public object lists
98
99     # fetch-on-demand-and-cache subs for commonly used public data
100     my @public_classes = qw/ccs aout/;
101
102     for my $hint (@public_classes) {
103
104         my ($class) = grep {
105             $Fieldmapper::fieldmap->{$_}->{hint} eq $hint
106         } keys %{ $Fieldmapper::fieldmap };
107
108             $class =~ s/Fieldmapper:://o;
109             $class =~ s/::/_/g;
110
111         # copy statuses
112         my $list_key = $hint . '_list';
113         my $find_key = "find_$hint";
114
115         $ctx->{$list_key} = sub {
116             my $method = "retrieve_all_$class";
117             $cache{list}{$hint} = $e->$method() unless $cache{list}{$hint};
118             return $cache{list}{$hint};
119         };
120     
121         $cache{map}{$hint} = {};
122
123         $ctx->{$find_key} = sub {
124             my $id = shift;
125             return $cache{map}{$hint}{$id} if $cache{map}{$hint}{$id}; 
126             ($cache{map}{$hint}{$id}) = grep { $_->id == $id } @{$ctx->{$list_key}->()};
127             return $cache{map}{$hint}{$id};
128         };
129
130     }
131
132     $ctx->{aou_tree} = sub {
133
134         # fetch the org unit tree
135         unless($cache{aou_tree}) {
136             my $tree = $e->search_actor_org_unit([
137                             {   parent_ou => undef},
138                             {   flesh            => -1,
139                                     flesh_fields    => {aou =>  ['children']},
140                                     order_by        => {aou => 'name'}
141                             }
142                     ])->[0];
143
144             # flesh the org unit type for each org unit
145             # and simultaneously set the id => aou map cache
146             sub flesh_aout {
147                 my $node = shift;
148                 my $ctx = shift;
149                 $node->ou_type( $ctx->{find_aout}->($node->ou_type) );
150                 $cache{map}{aou}{$node->id} = $node;
151                 flesh_aout($_, $ctx) foreach @{$node->children};
152             };
153             flesh_aout($tree, $ctx);
154
155             $cache{aou_tree} = $tree;
156         }
157
158         return $cache{aou_tree};
159     };
160
161     # Add a special handler for the tree-shaped org unit cache
162     $cache{map}{aou} = {};
163     $ctx->{find_aou} = sub {
164         my $org_id = shift;
165         $ctx->{aou_tree}->(); # force the org tree to load
166         return $cache{map}{aou}{$org_id};
167     };
168
169     # turns an ISO date into something TT can understand
170     $ctx->{parse_datetime} = sub {
171         my $date = shift;
172         $date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($date));
173         return sprintf(
174             "%0.2d:%0.2d:%0.2d %0.2d-%0.2d-%0.4d",
175             $date->hour,
176             $date->minute,
177             $date->second,
178             $date->day,
179             $date->month,
180             $date->year
181         );
182     }
183 }
184
185 # context additions: 
186 #   authtoken : string
187 #   user : au object
188 #   user_status : hash of user circ numbers
189 sub load_common {
190     my $self = shift;
191
192     my $e = $self->editor;
193     my $ctx = $self->ctx;
194
195     if($e->authtoken($self->cgi->cookie('ses'))) {
196
197         if($e->checkauth) {
198
199             $ctx->{authtoken} = $e->authtoken;
200             $ctx->{user} = $e->requestor;
201             $ctx->{user_stats} = $U->simplereq(
202                 'open-ils.actor', 
203                 'open-ils.actor.user.opac.vital_stats', 
204                 $e->authtoken, $e->requestor->id);
205
206         } else {
207
208             return $self->load_logout;
209         }
210     }
211
212     return Apache2::Const::OK;
213 }
214
215 sub load_home {
216     my $self = shift;
217     $self->ctx->{page} = 'home';
218     return Apache2::Const::OK;
219 }
220
221
222 sub load_login {
223     my $self = shift;
224     my $cgi = $self->cgi;
225
226     $self->ctx->{page} = 'login';
227
228     my $username = $cgi->param('username');
229     my $password = $cgi->param('password');
230
231     return Apache2::Const::OK unless $username and $password;
232
233         my $seed = $U->simplereq(
234         'open-ils.auth', 
235                 'open-ils.auth.authenticate.init',
236         $username);
237
238         my $response = $U->simplereq(
239         'open-ils.auth', 
240                 'open-ils.auth.authenticate.complete', 
241                 {       username => $username, 
242                         password => md5_hex($seed . md5_hex($password)), 
243                         type => 'opac' 
244         }
245     );
246
247     # XXX check event, redirect as necessary
248
249     my $home = $self->apache->unparsed_uri;
250     $home =~ s/\/login/\/home/;
251
252     $self->apache->print(
253         $cgi->redirect(
254             -url => $cgi->param('origin') || $home,
255             -cookie => $cgi->cookie(
256                 -name => 'ses',
257                 -path => '/',
258                 -secure => 1,
259                 -value => $response->{payload}->{authtoken},
260                 -expires => CORE::time + $response->{payload}->{authtime}
261             )
262         )
263     );
264
265     return Apache2::Const::REDIRECT;
266 }
267
268 sub load_logout {
269     my $self = shift;
270
271     my $path = $self->apache->uri;
272     $path =~ s/(\/[^\/]+$)/\/home/;
273     my $url = 'http://' . $self->apache->hostname . "$path";
274
275     $self->apache->print(
276         $self->cgi->redirect(
277             -url => $url,
278             -cookie => $self->cgi->cookie(
279                 -name => 'ses',
280                 -path => '/',
281                 -value => '',
282                 -expires => '-1h'
283             )
284         )
285     );
286
287     return Apache2::Const::REDIRECT;
288 }
289
290 # context additions: 
291 #   page_size
292 #   hit_count
293 #   records : list of bre's and copy-count objects
294 sub load_rresults {
295     my $self = shift;
296     my $cgi = $self->cgi;
297     my $ctx = $self->ctx;
298     my $e = $self->editor;
299
300     $ctx->{page} = 'rresult';
301     my $page = $cgi->param('page') || 0;
302     my $facet = $cgi->param('facet');
303     my $query = $cgi->param('query');
304     my $limit = $cgi->param('limit') || 10; # XXX user settings
305     my $args = {limit => $limit, offset => $page * $limit}; 
306     $query = "$query $facet" if $facet;
307     my $results;
308
309     try {
310         $results = $U->simplereq(
311             'open-ils.search',
312             'open-ils.search.biblio.multiclass.query.staff', 
313             $args, $query, 1);
314
315     } catch Error with {
316         my $err = shift;
317         $logger->error("multiclass search error: $err");
318         $results = {count => 0, ids => []};
319     };
320
321     my $rec_ids = [map { $_->[0] } @{$results->{ids}}];
322
323     $ctx->{records} = [];
324     $ctx->{search_facets} = {};
325     $ctx->{page_size} = $limit;
326     $ctx->{hit_count} = $results->{count};
327
328     return Apache2::Const::OK if @$rec_ids == 0;
329
330     my $cstore1 = OpenSRF::AppSession->create('open-ils.cstore');
331     my $bre_req = $cstore1->request(
332         'open-ils.cstore.direct.biblio.record_entry.search', {id => $rec_ids});
333
334     my $search = OpenSRF::AppSession->create('open-ils.search');
335     my $facet_req = $search->request('open-ils.search.facet_cache.retrieve', $results->{facet_key}, 10);
336
337     unless($cache{cmf}) {
338         $cache{cmf} = $e->search_config_metabib_field({id => {'!=' => undef}});
339         $ctx->{metabib_field} = $cache{cmf};
340         #$cache{cmc} = $e->search_config_metabib_class({name => {'!=' => undef}});
341         #$ctx->{metabib_class} = $cache{cmc};
342     }
343
344     my @data;
345     while(my $resp = $bre_req->recv) {
346         my $bre = $resp->content; 
347
348         # XXX farm out to multiple cstore sessions before loop, then collect after
349         my $copy_counts = $e->json_query(
350             {from => ['asset.record_copy_count', 1, $bre->id, 0]})->[0];
351
352         push(@data,
353             {
354                 bre => $bre,
355                 marc_xml => XML::LibXML->new->parse_string($bre->marc),
356                 copy_counts => $copy_counts
357             }
358         );
359     }
360
361     $cstore1->kill_me;
362
363     # shove recs into context in search results order
364     for my $rec_id (@$rec_ids) { 
365         push(
366             @{$ctx->{records}},
367             grep { $_->{bre}->id == $rec_id } @data
368         );
369     }
370
371     my $facets = $facet_req->gather(1);
372
373     for my $cmf_id (keys %$facets) {  # quick-n-dirty
374         my ($cmf) = grep { $_->id eq $cmf_id } @{$cache{cmf}};
375         $facets->{$cmf_id} = {cmf => $cmf, data => $facets->{$cmf_id}};
376     }
377     $ctx->{search_facets} = $facets;
378
379     return Apache2::Const::OK;
380 }
381
382 # context additions: 
383 #   record : bre object
384 sub load_record {
385     my $self = shift;
386     $self->ctx->{page} = 'record';
387
388     my $rec_id = $self->ctx->{page_args}->[0]
389         or return Apache2::Const::HTTP_BAD_REQUEST;
390
391     $self->ctx->{record} = $self->editor->retrieve_biblio_record_entry([
392         $rec_id,
393         {
394             flesh => 2, 
395             flesh_fields => {
396                 bre => ['call_numbers'],
397                 acn => ['copies'] # limit, paging, etc.
398             }
399         }
400     ]);
401
402     $self->ctx->{marc_xml} = XML::LibXML->new->parse_string($self->ctx->{record}->marc);
403
404     return Apache2::Const::OK;
405 }
406
407 # context additions: 
408 #   user : au object, fleshed
409 sub load_myopac {
410     my $self = shift;
411     $self->ctx->{page} = 'myopac';
412
413     $self->ctx->{user} = $self->editor->retrieve_actor_user([
414         $self->ctx->{user}->id,
415         {
416             flesh => 1,
417             flesh_fields => {
418                 au => ['card']
419                 # ...
420             }
421         }
422     ]);
423
424     return Apache2::Const::OK;
425 }
426
427 sub load_myopac_holds {
428     my $self = shift;
429     my $e = $self->editor;
430     my $ctx = $self->ctx;
431
432     my $limit = $self->cgi->param('limit') || 10;
433     my $offset = $self->cgi->param('offset') || 0;
434
435     my $circ = OpenSRF::AppSession->create('open-ils.circ');
436     my $hold_ids = $circ->request(
437         'open-ils.circ.holds.id_list.retrieve', 
438         $e->authtoken, 
439         $e->requestor->id
440     )->gather(1);
441
442     $hold_ids = [ grep { defined $_ } @$hold_ids[$offset..($offset + $limit - 1)] ];
443
444     my $req = $circ->request(
445         'open-ils.circ.hold.details.batch.retrieve', 
446         $e->authtoken, 
447         $hold_ids,
448         {
449             suppress_notices => 1,
450             suppress_transits => 1,
451             suppress_mvr => 1,
452             suppress_patron_details => 1,
453             include_bre => 1
454         }
455     );
456
457     # any requests we can fire off here?
458     
459     $ctx->{holds} = []; 
460     while(my $resp = $req->recv) {
461         my $hold = $resp->content;
462         push(@{$ctx->{holds}}, {
463             hold => $hold,
464             marc_xml => XML::LibXML->new->parse_string($hold->{bre}->marc)
465         });
466     }
467
468     $circ->kill_me;
469     return Apache2::Const::OK;
470 }
471
472 sub load_place_hold {
473     my $self = shift;
474     my $ctx = $self->ctx;
475     my $e = $self->editor;
476     $self->ctx->{page} = 'place_hold';
477
478     $ctx->{hold_target} = $self->cgi->param('hold_target');
479     $ctx->{hold_type} = $self->cgi->param('hold_type');
480     $ctx->{default_pickup_lib} = $e->requestor->home_ou; # XXX staff
481
482     if($ctx->{hold_type} eq 'T') {
483         $ctx->{record} = $e->retrieve_biblio_record_entry($ctx->{hold_target});
484     }
485     # ...
486
487     $ctx->{marc_xml} = XML::LibXML->new->parse_string($ctx->{record}->marc);
488
489     if(my $pickup_lib = $self->cgi->param('pickup_lib')) {
490
491         my $args = {
492             patronid => $e->requestor->id,
493             titleid => $ctx->{hold_target}, # XXX
494             pickup_lib => $pickup_lib,
495             depth => 0, # XXX
496         };
497
498         my $allowed = $U->simplereq(
499             'open-ils.circ',
500             'open-ils.circ.title_hold.is_possible',
501             $e->authtoken, $args
502         );
503
504         if($allowed->{success} == 1) {
505             my $hold = Fieldmapper::action::hold_request->new;
506
507             $hold->pickup_lib($pickup_lib);
508             $hold->requestor($e->requestor->id);
509             $hold->usr($e->requestor->id); # XXX staff
510             $hold->target($ctx->{hold_target});
511             $hold->hold_type($ctx->{hold_type});
512             # frozen, expired, etc..
513
514             my $stat = $U->simplereq(
515                 'open-ils.circ',
516                 'open-ils.circ.holds.create',
517                 $e->authtoken, $hold
518             );
519
520             if($stat and $stat > 0) {
521                 $ctx->{hold_success} = 1;
522             } else {
523                 $ctx->{hold_failed} = 1; # XXX process the events, etc
524             }
525         }
526
527         # place the hold and deliver results
528     }
529
530     return Apache2::Const::OK;
531 }
532
533
534 sub load_myopac_circs {
535     my $self = shift;
536     my $e = $self->editor;
537     my $ctx = $self->ctx;
538     $ctx->{circs} = [];
539
540     my $limit = $self->cgi->param('limit') || 10;
541     my $offset = $self->cgi->param('offset') || 0;
542
543     my $circ_data = $U->simplereq(
544         'open-ils.actor', 
545         'open-ils.actor.user.checked_out',
546         $e->authtoken, 
547         $e->requestor->id
548     );
549
550     my @circ_ids =  ( @{$circ_data->{overdue}}, @{$circ_data->{out}} );
551     @circ_ids = grep { defined $_ } @circ_ids[0..($offset + $limit - 1)];
552
553     return Apache2::Const::OK unless @circ_ids;
554
555     my $cstore = OpenSRF::AppSession->create('open-ils.cstore');
556     my $req = $cstore->request(
557         'open-ils.cstore.direct.action.circulation.search', 
558         {id => \@circ_ids},
559         {
560             flesh => 3,
561             flesh_fields => {
562                 circ => ['target_copy'],
563                 acp => ['call_number'],
564                 acn => ['record']
565             }
566         }
567     );
568
569     my @circs;
570     while(my $resp = $req->recv) {
571         my $circ = $resp->content;
572         push(@circs, {
573             circ => $circ, 
574             marc_xml => ($circ->target_copy->call_number->id == -1) ? 
575                 undef :  # pre-cat copy, use the dummy title/author instead
576                 XML::LibXML->new->parse_string($circ->target_copy->call_number->record->marc),
577         });
578     }
579
580     # make sure the final list is in the correct order
581     for my $id (@circ_ids) {
582         push(
583             @{$ctx->{circs}}, 
584             (grep { $_->{circ}->id == $id } @circs)
585         );
586     }
587
588     return Apache2::Const::OK;
589 }
590
591
592
593 1;