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