]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm
Merge remote-tracking branch 'eg-working/user/dbs/lp894052-version-upgrade'
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / WWW / EGCatLoader / Util.pm
1 package OpenILS::WWW::EGCatLoader;
2 use strict; use warnings;
3 use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
4 use OpenSRF::Utils::Logger qw/$logger/;
5 use OpenILS::Utils::CStoreEditor qw/:funcs/;
6 use OpenILS::Utils::Fieldmapper;
7 use OpenILS::Application::AppUtils;
8 use OpenSRF::MultiSession;
9 my $U = 'OpenILS::Application::AppUtils';
10
11 my $ro_object_subs; # cached subs
12 our %cache = ( # cached data
13     map => {aou => {}}, # others added dynamically as needed
14     list => {},
15     search => {},
16     org_settings => {}
17 );
18
19 sub init_ro_object_cache {
20     my $self = shift;
21     my $e = $self->editor;
22     my $ctx = $self->ctx;
23
24     # reset org unit setting cache on each page load to avoid the 
25     # requirement of reloading apache with each org-setting change
26     $cache{org_settings} = {};
27
28     if($ro_object_subs) {
29         # subs have been built.  insert into the context then move along.
30         $ctx->{$_} = $ro_object_subs->{$_} for keys %$ro_object_subs;
31         return;
32     }
33
34     # make all "field_safe" classes accesible by default in the template context
35     my @classes = grep {
36         ($Fieldmapper::fieldmap->{$_}->{field_safe} || '') =~ /true/i
37     } keys %{ $Fieldmapper::fieldmap };
38
39     for my $class (@classes) {
40
41         my $hint = $Fieldmapper::fieldmap->{$class}->{hint};
42         next if $hint eq 'aou'; # handled separately
43
44         my $ident_field =  $Fieldmapper::fieldmap->{$class}->{identity};
45         (my $eclass = $class) =~ s/Fieldmapper:://o;
46         $eclass =~ s/::/_/g;
47
48         my $list_key = "${hint}_list";
49         my $get_key = "get_$hint";
50         my $search_key = "search_$hint";
51
52         # Retrieve the full set of objects with class $hint
53         $ro_object_subs->{$list_key} = sub {
54             my $method = "retrieve_all_$eclass";
55             $cache{list}{$hint} = $e->$method() unless $cache{list}{$hint};
56             return $cache{list}{$hint};
57         };
58     
59         # locate object of class $hint with Ident field $id
60         $cache{map}{$hint} = {};
61         $ro_object_subs->{$get_key} = sub {
62             my $id = shift;
63             return $cache{map}{$hint}{$id} if $cache{map}{$hint}{$id}; 
64             ($cache{map}{$hint}{$id}) = grep { $_->$ident_field eq $id } @{$ro_object_subs->{$list_key}->()};
65             return $cache{map}{$hint}{$id};
66         };
67
68         # search for objects of class $hint where field=value
69         $cache{search}{$hint} = {};
70         $ro_object_subs->{$search_key} = sub {
71             my ($field, $val) = @_;
72             my $method = "search_$eclass";
73             $cache{search}{$hint}{$field} = {} unless $cache{search}{$hint}{$field};
74             $cache{search}{$hint}{$field}{$val} = $e->$method({$field => $val}) 
75                 unless $cache{search}{$hint}{$field}{$val};
76             return $cache{search}{$hint}{$field}{$val};
77         };
78     }
79
80     $ro_object_subs->{aou_tree} = sub {
81
82         # fetch the org unit tree
83         unless($cache{aou_tree}) {
84             my $tree = $e->search_actor_org_unit([
85                             {   parent_ou => undef},
86                             {   flesh            => -1,
87                                     flesh_fields    => {aou =>  ['children']},
88                                     order_by        => {aou => 'name'}
89                             }
90                     ])->[0];
91
92             # flesh the org unit type for each org unit
93             # and simultaneously set the id => aou map cache
94             sub flesh_aout {
95                 my $node = shift;
96                 my $ro_object_subs = shift;
97                 $node->ou_type( $ro_object_subs->{get_aout}->($node->ou_type) );
98                 $cache{map}{aou}{$node->id} = $node;
99                 flesh_aout($_, $ro_object_subs) foreach @{$node->children};
100             };
101             flesh_aout($tree, $ro_object_subs);
102
103             $cache{aou_tree} = $tree;
104         }
105
106         return $cache{aou_tree};
107     };
108
109     # Add a special handler for the tree-shaped org unit cache
110     $ro_object_subs->{get_aou} = sub {
111         my $org_id = shift;
112         return undef unless defined $org_id;
113         $ro_object_subs->{aou_tree}->(); # force the org tree to load
114         return $cache{map}{aou}{$org_id};
115     };
116
117     # Returns a flat list of aou objects.  often easier to manage than a tree.
118     $ro_object_subs->{aou_list} = sub {
119         $ro_object_subs->{aou_tree}->(); # force the org tree to load
120         return [ values %{$cache{map}{aou}} ];
121     };
122
123
124     # turns an ISO date into something TT can understand
125     $ro_object_subs->{parse_datetime} = sub {
126         my $date = shift;
127         $date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($date));
128         return sprintf(
129             "%0.2d:%0.2d:%0.2d %0.2d-%0.2d-%0.4d",
130             $date->hour,
131             $date->minute,
132             $date->second,
133             $date->day,
134             $date->month,
135             $date->year
136         );
137     };
138
139     # retrieve and cache org unit setting values
140     $ro_object_subs->{get_org_setting} = sub {
141         my($org_id, $setting) = @_;
142
143         $cache{org_settings}{$org_id} = {} 
144             unless $cache{org_settings}{$org_id};
145
146         $cache{org_settings}{$org_id}{$setting} = 
147             $U->ou_ancestor_setting_value($org_id, $setting)
148                 unless exists $cache{org_settings}{$org_id}{$setting};
149
150         return $cache{org_settings}{$org_id}{$setting};
151     };
152
153     $ctx->{$_} = $ro_object_subs->{$_} for keys %$ro_object_subs;
154 }
155
156 sub generic_redirect {
157     my $self = shift;
158     my $url = shift;
159     my $cookie = shift; # can be an array of cgi.cookie's
160
161     $self->apache->print(
162         $self->cgi->redirect(
163             -url => $url || 
164                 $self->cgi->param('redirect_to') || 
165                 $self->ctx->{referer} || 
166                 $self->ctx->{home_page},
167             -cookie => $cookie
168         )
169     );
170
171     return Apache2::Const::REDIRECT;
172 }
173
174 sub get_records_and_facets {
175     my ($self, $rec_ids, $facet_key, $unapi_args) = @_;
176
177     $unapi_args ||= {};
178     $unapi_args->{site} ||= $self->ctx->{aou_tree}->()->shortname;
179     $unapi_args->{depth} ||= $self->ctx->{aou_tree}->()->ou_type->depth;
180     $unapi_args->{flesh_depth} ||= 5;
181
182     my @data;
183     my $ses = OpenSRF::MultiSession->new(
184         app => 'open-ils.cstore',
185         cap => 10, # XXX config
186         success_handler => sub {
187             my($self, $req) = @_;
188             my $data = $req->{response}->[0]->content;
189             my $xml = XML::LibXML->new->parse_string($data->{'unapi.bre'})->documentElement;
190
191             # Protect against legacy invalid MARCXML that might not have a 901c
192             my $bre_id;
193             my $bre_id_nodes =  $xml->find('*[@tag="901"]/*[@code="c"]');
194             if ($bre_id_nodes) {
195                 $bre_id =  $bre_id_nodes->[0]->textContent;
196             } else {
197                 $logger->warn("Missing 901 subfield 'c' in " . $xml->toString());
198             }
199             push(@data, {id => $bre_id, marc_xml => $xml});
200         }
201     );
202
203     $ses->request(
204         'open-ils.cstore.json_query',
205          {from => [
206             'unapi.bre', $_, 'marcxml','record', 
207             $unapi_args->{flesh}, 
208             $unapi_args->{site}, 
209             $unapi_args->{depth}, 
210             $unapi_args->{flesh_depth}, 
211         ]}
212     ) for @$rec_ids;
213
214     # collect the facet data
215     my $search = OpenSRF::AppSession->create('open-ils.search');
216     my $facet_req = $search->request(
217         'open-ils.search.facet_cache.retrieve', $facet_key, 10
218     ) if $facet_key;
219
220     # gather up the unapi recs
221     $ses->session_wait(1);
222
223     my $facets = {};
224     if ($facet_key) {
225         my $tmp_facets = $facet_req->gather(1);
226         for my $cmf_id (keys %$tmp_facets) {
227
228             # sort highest to lowest match count
229             my @entries;
230             my $entries = $tmp_facets->{$cmf_id};
231             for my $ent (keys %$entries) {
232                 push(@entries, {value => $ent, count => $$entries{$ent}});
233             };
234             @entries = sort { $b->{count} <=> $a->{count} } @entries;
235             $facets->{$cmf_id} = {
236                 cmf => $self->ctx->{get_cmf}->($cmf_id),
237                 data => \@entries
238             }
239         }
240     } else {
241         $facets = undef;
242     }
243
244     $search->kill_me;
245
246     return ($facets, @data);
247 }
248
249 # TODO: blend this code w/ ^-- get_records_and_facets
250 sub fetch_marc_xml_by_id {
251     my ($self, $id_list) = @_;
252     $id_list = [$id_list] unless ref($id_list);
253
254     {
255         no warnings qw/numeric/;
256         $id_list = [map { int $_ } @$id_list];
257         $id_list = [grep { $_ > 0} @$id_list];
258     };
259
260     return {} if scalar(@$id_list) < 1;
261
262     # I'm just sure there needs to be some more efficient way to get all of
263     # this.
264     my $results = $self->editor->json_query({
265         "select" => {"bre" => ["id", "marc"]},
266         "from" => {"bre" => {}},
267         "where" => {"id" => $id_list}
268     }, {substream => 1}) or return $self->editor->die_event;
269
270     my $marc_xml = {};
271     for my $r (@$results) {
272         $marc_xml->{$r->{"id"}} =
273             (new XML::LibXML)->parse_string($r->{"marc"});
274     }
275
276     return $marc_xml;
277 }
278
279 sub _get_search_lib {
280     my $self = shift;
281
282     # loc param takes precedence
283     my $loc = $self->cgi->param('loc');
284     return $loc if $loc;
285
286     if ($self->ctx->{user}) {
287         # See if the user has a search library preference
288         my $lset = $self->editor->search_actor_user_setting({
289             usr => $self->ctx->{user}->id, 
290             name => 'opac.default_search_location'
291         })->[0];
292         return OpenSRF::Utils::JSON->JSON2perl($lset->value) if $lset;
293
294         # Otherwise return the user's home library
295         return $self->ctx->{user}->home_ou;
296     }
297
298     if ($self->cgi->param('physical_loc')) {
299         return $self->cgi->param('physical_loc');
300     }
301
302     return $self->ctx->{aou_tree}->()->id;
303 }
304
305 1;