1 package OpenILS::WWW::EGCatLoader;
2 use strict; use warnings;
5 use Digest::MD5 qw(md5_hex);
6 use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN 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';
17 my %cache; # proc-level cache
20 my($class, $apache, $ctx) = @_;
22 my $self = bless({}, ref($class) || $class);
24 $self->apache($apache);
28 OpenILS::Utils::CStoreEditor->init; # just in case
29 $self->editor(new_editor());
35 # current Apache2::RequestRec;
37 my($self, $apache) = @_;
38 $self->{apache} = $apache if $apache;
39 return $self->{apache};
42 # runtime / template context
45 $self->{ctx} = $ctx if $ctx;
51 my($self, $editor) = @_;
52 $self->{editor} = $editor if $editor;
53 return $self->{editor};
59 $self->{cgi} = $cgi if $cgi;
64 # load common data, then load page data
69 my $stat = $self->load_common;
70 return $stat unless $stat == Apache2::Const::OK;
72 my $path = $self->apache->path_info;
74 return $self->load_home if $path =~ /opac\/home/;
75 return $self->load_login if $path =~ /opac\/login/;
76 return $self->load_logout if $path =~ /opac\/logout/;
77 return $self->load_rresults if $path =~ /opac\/results/;
78 return $self->load_record if $path =~ /opac\/record/;
80 # ----------------------------------------------------------------
81 # These pages require authentication
82 # ----------------------------------------------------------------
83 return Apache2::Const::FORBIDDEN unless $self->cgi->https;
84 return $self->load_logout unless $self->editor->requestor;
86 return $self->load_place_hold if $path =~ /opac\/place_hold/;
87 return $self->load_myopac_holds if $path =~ /opac\/myopac\/holds/;
88 return $self->load_myopac_circs if $path =~ /opac\/myopac\/circs/;
89 return $self->load_myopac_fines if $path =~ /opac\/myopac\/fines/;
90 return $self->load_myopac if $path =~ /opac\/myopac/;
91 # ----------------------------------------------------------------
93 return Apache2::Const::OK;
96 # general purpose utility functions added to the environment
99 my $e = $self->editor;
100 my $ctx = $self->ctx;
102 $cache{map} = {}; # public object maps
103 $cache{list} = {}; # public object lists
105 # fetch-on-demand-and-cache subs for commonly used public data
106 my @public_classes = qw/ccs aout/;
108 for my $hint (@public_classes) {
111 $Fieldmapper::fieldmap->{$_}->{hint} eq $hint
112 } keys %{ $Fieldmapper::fieldmap };
114 $class =~ s/Fieldmapper:://o;
118 my $list_key = $hint . '_list';
119 my $find_key = "find_$hint";
121 $ctx->{$list_key} = sub {
122 my $method = "retrieve_all_$class";
123 $cache{list}{$hint} = $e->$method() unless $cache{list}{$hint};
124 return $cache{list}{$hint};
127 $cache{map}{$hint} = {};
129 $ctx->{$find_key} = sub {
131 return $cache{map}{$hint}{$id} if $cache{map}{$hint}{$id};
132 ($cache{map}{$hint}{$id}) = grep { $_->id == $id } @{$ctx->{$list_key}->()};
133 return $cache{map}{$hint}{$id};
138 $ctx->{aou_tree} = sub {
140 # fetch the org unit tree
141 unless($cache{aou_tree}) {
142 my $tree = $e->search_actor_org_unit([
143 { parent_ou => undef},
145 flesh_fields => {aou => ['children']},
146 order_by => {aou => 'name'}
150 # flesh the org unit type for each org unit
151 # and simultaneously set the id => aou map cache
155 $node->ou_type( $ctx->{find_aout}->($node->ou_type) );
156 $cache{map}{aou}{$node->id} = $node;
157 flesh_aout($_, $ctx) foreach @{$node->children};
159 flesh_aout($tree, $ctx);
161 $cache{aou_tree} = $tree;
164 return $cache{aou_tree};
167 # Add a special handler for the tree-shaped org unit cache
168 $cache{map}{aou} = {};
169 $ctx->{find_aou} = sub {
171 $ctx->{aou_tree}->(); # force the org tree to load
172 return $cache{map}{aou}{$org_id};
175 # turns an ISO date into something TT can understand
176 $ctx->{parse_datetime} = sub {
178 $date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($date));
180 "%0.2d:%0.2d:%0.2d %0.2d-%0.2d-%0.4d",
194 # user_status : hash of user circ numbers
198 my $e = $self->editor;
199 my $ctx = $self->ctx;
201 if($e->authtoken($self->cgi->cookie('ses'))) {
205 $ctx->{authtoken} = $e->authtoken;
206 $ctx->{user} = $e->requestor;
207 $ctx->{user_stats} = $U->simplereq(
209 'open-ils.actor.user.opac.vital_stats',
210 $e->authtoken, $e->requestor->id);
214 return $self->load_logout;
218 return Apache2::Const::OK;
223 $self->ctx->{page} = 'home';
224 return Apache2::Const::OK;
230 my $cgi = $self->cgi;
232 $self->ctx->{page} = 'login';
234 my $username = $cgi->param('username');
235 my $password = $cgi->param('password');
237 return Apache2::Const::OK unless $username and $password;
239 my $seed = $U->simplereq(
241 'open-ils.auth.authenticate.init',
244 my $response = $U->simplereq(
246 'open-ils.auth.authenticate.complete',
247 { username => $username,
248 password => md5_hex($seed . md5_hex($password)),
253 # XXX check event, redirect as necessary
255 my $home = $self->apache->unparsed_uri;
256 $home =~ s/\/login/\/home/;
258 $self->apache->print(
260 -url => $cgi->param('origin') || $home,
261 -cookie => $cgi->cookie(
265 -value => $response->{payload}->{authtoken},
266 -expires => CORE::time + $response->{payload}->{authtime}
271 return Apache2::Const::REDIRECT;
277 my $url = 'http://' . $self->apache->hostname . $self->ctx->{base_path} . "/opac/home";
279 $self->apache->print(
280 $self->cgi->redirect(
282 -cookie => $self->cgi->cookie(
291 return Apache2::Const::REDIRECT;
297 # records : list of bre's and copy-count objects
300 my $cgi = $self->cgi;
301 my $ctx = $self->ctx;
302 my $e = $self->editor;
304 $ctx->{page} = 'rresult';
305 my $page = $cgi->param('page') || 0;
306 my $facet = $cgi->param('facet');
307 my $query = $cgi->param('query');
308 my $limit = $cgi->param('limit') || 10; # XXX user settings
309 my $args = {limit => $limit, offset => $page * $limit};
310 $query = "$query $facet" if $facet;
314 $results = $U->simplereq(
316 'open-ils.search.biblio.multiclass.query.staff',
321 $logger->error("multiclass search error: $err");
322 $results = {count => 0, ids => []};
325 my $rec_ids = [map { $_->[0] } @{$results->{ids}}];
327 $ctx->{records} = [];
328 $ctx->{search_facets} = {};
329 $ctx->{page_size} = $limit;
330 $ctx->{hit_count} = $results->{count};
332 return Apache2::Const::OK if @$rec_ids == 0;
334 my $cstore1 = OpenSRF::AppSession->create('open-ils.cstore');
335 my $bre_req = $cstore1->request(
336 'open-ils.cstore.direct.biblio.record_entry.search', {id => $rec_ids});
338 my $search = OpenSRF::AppSession->create('open-ils.search');
339 my $facet_req = $search->request('open-ils.search.facet_cache.retrieve', $results->{facet_key}, 10);
341 unless($cache{cmf}) {
342 $cache{cmf} = $e->search_config_metabib_field({id => {'!=' => undef}});
343 $ctx->{metabib_field} = $cache{cmf};
344 #$cache{cmc} = $e->search_config_metabib_class({name => {'!=' => undef}});
345 #$ctx->{metabib_class} = $cache{cmc};
349 while(my $resp = $bre_req->recv) {
350 my $bre = $resp->content;
352 # XXX farm out to multiple cstore sessions before loop, then collect after
353 my $copy_counts = $e->json_query(
354 {from => ['asset.record_copy_count', 1, $bre->id, 0]})->[0];
359 marc_xml => XML::LibXML->new->parse_string($bre->marc),
360 copy_counts => $copy_counts
367 # shove recs into context in search results order
368 for my $rec_id (@$rec_ids) {
371 grep { $_->{bre}->id == $rec_id } @data
375 my $facets = $facet_req->gather(1);
377 for my $cmf_id (keys %$facets) { # quick-n-dirty
378 my ($cmf) = grep { $_->id eq $cmf_id } @{$cache{cmf}};
379 $facets->{$cmf_id} = {cmf => $cmf, data => $facets->{$cmf_id}};
381 $ctx->{search_facets} = $facets;
383 return Apache2::Const::OK;
387 # record : bre object
390 $self->ctx->{page} = 'record';
392 my $rec_id = $self->ctx->{page_args}->[0]
393 or return Apache2::Const::HTTP_BAD_REQUEST;
395 $self->ctx->{record} = $self->editor->retrieve_biblio_record_entry([
400 bre => ['call_numbers'],
401 acn => ['copies'] # limit, paging, etc.
406 $self->ctx->{marc_xml} = XML::LibXML->new->parse_string($self->ctx->{record}->marc);
408 return Apache2::Const::OK;
412 # user : au object, fleshed
415 $self->ctx->{page} = 'myopac';
417 $self->ctx->{user} = $self->editor->retrieve_actor_user([
418 $self->ctx->{user}->id,
428 return Apache2::Const::OK;
431 sub load_myopac_holds {
433 my $e = $self->editor;
434 my $ctx = $self->ctx;
436 my $limit = $self->cgi->param('limit') || 10;
437 my $offset = $self->cgi->param('offset') || 0;
439 my $circ = OpenSRF::AppSession->create('open-ils.circ');
440 my $hold_ids = $circ->request(
441 'open-ils.circ.holds.id_list.retrieve',
446 $hold_ids = [ grep { defined $_ } @$hold_ids[$offset..($offset + $limit - 1)] ];
448 my $req = $circ->request(
449 'open-ils.circ.hold.details.batch.retrieve',
453 suppress_notices => 1,
454 suppress_transits => 1,
456 suppress_patron_details => 1,
461 # any requests we can fire off here?
464 while(my $resp = $req->recv) {
465 my $hold = $resp->content;
466 push(@{$ctx->{holds}}, {
468 marc_xml => XML::LibXML->new->parse_string($hold->{bre}->marc)
473 return Apache2::Const::OK;
476 sub load_place_hold {
478 my $ctx = $self->ctx;
479 my $e = $self->editor;
480 $self->ctx->{page} = 'place_hold';
482 $ctx->{hold_target} = $self->cgi->param('hold_target');
483 $ctx->{hold_type} = $self->cgi->param('hold_type');
484 $ctx->{default_pickup_lib} = $e->requestor->home_ou; # XXX staff
486 if($ctx->{hold_type} eq 'T') {
487 $ctx->{record} = $e->retrieve_biblio_record_entry($ctx->{hold_target});
491 $ctx->{marc_xml} = XML::LibXML->new->parse_string($ctx->{record}->marc);
493 if(my $pickup_lib = $self->cgi->param('pickup_lib')) {
496 patronid => $e->requestor->id,
497 titleid => $ctx->{hold_target}, # XXX
498 pickup_lib => $pickup_lib,
502 my $allowed = $U->simplereq(
504 'open-ils.circ.title_hold.is_possible',
508 if($allowed->{success} == 1) {
509 my $hold = Fieldmapper::action::hold_request->new;
511 $hold->pickup_lib($pickup_lib);
512 $hold->requestor($e->requestor->id);
513 $hold->usr($e->requestor->id); # XXX staff
514 $hold->target($ctx->{hold_target});
515 $hold->hold_type($ctx->{hold_type});
516 # frozen, expired, etc..
518 my $stat = $U->simplereq(
520 'open-ils.circ.holds.create',
524 if($stat and $stat > 0) {
525 $ctx->{hold_success} = 1;
527 $ctx->{hold_failed} = 1; # XXX process the events, etc
531 # place the hold and deliver results
534 return Apache2::Const::OK;
538 sub load_myopac_circs {
540 my $e = $self->editor;
541 my $ctx = $self->ctx;
544 my $limit = $self->cgi->param('limit') || 10;
545 my $offset = $self->cgi->param('offset') || 0;
547 my $circ_data = $U->simplereq(
549 'open-ils.actor.user.checked_out',
554 my @circ_ids = ( @{$circ_data->{overdue}}, @{$circ_data->{out}} );
555 @circ_ids = grep { defined $_ } @circ_ids[0..($offset + $limit - 1)];
557 return Apache2::Const::OK unless @circ_ids;
559 my $cstore = OpenSRF::AppSession->create('open-ils.cstore');
560 my $req = $cstore->request(
561 'open-ils.cstore.direct.action.circulation.search',
566 circ => ['target_copy'],
567 acp => ['call_number'],
574 while(my $resp = $req->recv) {
575 my $circ = $resp->content;
578 marc_xml => ($circ->target_copy->call_number->id == -1) ?
579 undef : # pre-cat copy, use the dummy title/author instead
580 XML::LibXML->new->parse_string($circ->target_copy->call_number->record->marc),
584 # make sure the final list is in the correct order
585 for my $id (@circ_ids) {
588 (grep { $_->{circ}->id == $id } @circs)
592 return Apache2::Const::OK;
595 sub load_myopac_fines {
597 my $e = $self->editor;
598 my $ctx = $self->ctx;
599 $ctx->{transactions} = [];
601 my $limit = $self->cgi->param('limit') || 10;
602 my $offset = $self->cgi->param('offset') || 0;
604 my $cstore = OpenSRF::AppSession->create('open-ils.cstore');
606 # TODO: This should really use a ML call, but the existing calls
607 # return an excessive amount of data and don't offer streaming
609 my $req = $cstore->request(
610 'open-ils.cstore.direct.money.open_billable_transaction_summary.search',
612 usr => $e->requestor->id,
613 balance_owed => {'!=' => 0}
618 mobts => ['circulation', 'grocery'],
621 circ => ['target_copy'],
622 acp => ['call_number'],
625 order_by => { mobts => 'xact_start' },
631 while(my $resp = $req->recv) {
632 my $mobts = $resp->content;
633 my $circ = $mobts->circulation;
636 if($mobts->grocery) {
637 my @billings = sort { $a->billing_ts cmp $b->billing_ts } @{$mobts->grocery->billings};
638 $last_billing = pop(@billings);
642 @{$ctx->{transactions}},
645 last_grocery_billing => $last_billing,
646 marc_xml => ($mobts->xact_type ne 'circulation' or $circ->target_copy->call_number->id == -1) ?
648 XML::LibXML->new->parse_string($circ->target_copy->call_number->record->marc),
653 return Apache2::Const::OK;