5f0797226c41e78d055a8cd43d0506544297abaa
[Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Trigger / Reactor.pm
1 package OpenILS::Application::Trigger::Reactor;
2 use strict; use warnings;
3 use Encode qw/ encode /;
4 use Template;
5 use DateTime;
6 use DateTime::Format::ISO8601;
7 use Unicode::Normalize;
8 use XML::LibXML;
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 my $U = 'OpenILS::Application::AppUtils';
14
15 sub fourty_two { return 42 }
16 sub NOOP_True  { return  1 }
17 sub NOOP_False { return  0 }
18
19
20 # To be used in two places within $_TT_helpers.  Without putting the code out
21 # here, we can't really reuse it within that structure.
22 sub get_li_attr {
23     my $name = shift or return;     # the first arg is always the name
24     my ($type, $attr) = (scalar(@_) == 1) ? (undef, $_[0]) : @_;
25     # if the next is the last, it's the attributes, otherwise type
26     # use Data::Dumper; $logger->warn("get_li_attr: " . Dumper($attr));
27     ($name and @$attr) or return;
28     my $length;
29     $name =~ s/^(\D+)_(\d+)$/$1/ and $length = $2;
30     foreach (@$attr) {
31         $_->attr_name eq $name or next;
32         next if $length and $length != length($_->attr_value);
33         return $_->attr_value if (! $type) or $type eq $_->attr_type;
34     }
35     return;
36 }
37
38 # helper functions inserted into the TT environment
39 my $_TT_helpers = {
40
41     # turns a date into something TT can understand
42     format_date => sub {
43         my $date = shift;
44         $date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($date));
45         return sprintf(
46             "%0.2d:%0.2d:%0.2d %0.2d-%0.2d-%0.4d",
47             $date->hour,
48             $date->minute,
49             $date->second,
50             $date->day,
51             $date->month,
52             $date->year
53         );
54     },
55
56     # escapes a string for inclusion in an XML document.  escapes &, <, and > characters
57     escape_xml => sub {
58         my $str = shift;
59         $str =~ s/&/&amp;/sog;
60         $str =~ s/</&lt;/sog;
61         $str =~ s/>/&gt;/sog;
62         return $str;
63     },
64
65     escape_json => sub {
66         my $str = shift;
67         $str =~ s/([\x{0080}-\x{fffd}])/sprintf('\u%0.4x',ord($1))/sgoe;
68         return $str;
69     },
70
71     # encode email headers in UTF-8, per RFC2231
72     escape_email_header => sub {
73         my $str = shift;
74         $str = encode("MIME-Header", $str);
75         return $str;
76     },
77
78     # strip non-ASCII characters after splitting base characters and diacritics
79     # least common denominator for EDIFACT messages using the UNOB character set
80     force_jedi_unob => sub {
81         my $str = shift;
82         $str = NFD($str);
83         $str =~ s/[\x{0080}-\x{fffd}]//g;
84         return $str;
85     },
86
87     # returns the calculated user locale
88     get_user_locale => sub { 
89         my $user_id = shift;
90         return $U->get_user_locale($user_id);
91     },
92
93     # returns the calculated copy price
94     get_copy_price => sub {
95         my $copy_id = shift;
96         return $U->get_copy_price(new_editor(xact=>1), $copy_id);
97     },
98
99     # given a copy, returns the title and author in a hash
100     get_copy_bib_basics => sub {
101         my $copy_id = shift;
102         my $copy = new_editor(xact=>1)->retrieve_asset_copy([
103             $copy_id,
104             {
105                 flesh => 2,
106                 flesh_fields => {
107                     acp => ['call_number'],
108                     acn => ['record']
109                 }
110             }
111         ]);
112         if($copy->call_number->id == -1) {
113             return {
114                 title  => $copy->dummy_title,
115                 author => $copy->dummy_author,
116             };
117         } else {
118             my $mvr = $U->record_to_mvr($copy->call_number->record);
119             return {
120                 title  => $mvr->title,
121                 author => $mvr->author
122             };
123         }
124     },
125
126     # returns the org unit setting value
127     get_org_setting => sub {
128         my($org_id, $setting) = @_;
129         return $U->ou_ancestor_setting_value($org_id, $setting);
130     },
131
132     # This basically greps/maps out ths isbn string values, but also promotes the first isbn-13 to the
133     # front of the line (so that the EDI translator takes it as primary) if there is one.
134     get_li_isbns => sub {
135         my $attrs = shift;
136         my @isbns;
137         my $primary;
138         foreach (@$attrs) {
139             $_->attr_name eq 'isbn' or next;
140             my $val = $_->attr_value;
141             if (! $primary and length($val) == 13) {
142                 $primary = $val;
143             } else {
144                 push @isbns, $val;
145             }
146         }
147         $primary and unshift @isbns, $primary;
148         $logger->debug("get_li_isbns returning isbns: " . join(', ', @isbns));
149         return @isbns;
150     },
151
152     # helpers.get_li_attr('isbn_13', li.attributes)
153     # returns matching line item attribute, or undef
154     get_li_attr => \&get_li_attr,
155
156     get_li_attr_jedi => sub {
157         my $value = get_li_attr(@_);
158         if ($value) {
159             # Here we can add any number of special case transformations to
160             # avoid problems with the EDI translator (or bad JSON).
161
162             # The ? character, if in the final position of a string, breaks
163             # the translator. + or ' or : could be problematic, too.
164             if ($value =~ /[\?\+':]$/) {
165                 chop $value;
166             }
167
168             # Make sure any double quotation marks are escaped.
169             $value =~ s/"/\\"/g;
170
171             # What the heck, get rid of [ ] too (although I couldn't get them
172             # to cause any problems for me.
173             $value =~ s/[\[\]]//g;
174         }
175
176         return $value;
177     },
178
179     get_queued_bib_attr => sub {
180         my $name = shift or return;     # the first arg is always the name
181         my ($attr) = @_;
182         # use Data::Dumper; $logger->warn("get_queued_bib_attr: " . Dumper($attr));
183         ($name and @$attr) or return;
184
185         my $query = {
186             select => {'vqbrad' => ['id']},
187             from => 'vqbrad',
188             where => {code => $name}
189         };
190
191         my $def_ids = new_editor()->json_query($query);
192         @$def_ids or return;
193
194         my $length;
195         $name =~ s/^(\D+)_(\d+)$/$1/ and $length = $2;
196         foreach (@$attr) {
197             $_->field eq @{$def_ids}[0]->{id} or next;
198             next if $length and $length != length($_->attr_value);
199             return $_->attr_value;
200         }
201         return;
202     },
203
204     get_queued_auth_attr => sub {
205         my $name = shift or return;     # the first arg is always the name
206         my ($attr) = @_;
207         # use Data::Dumper; $logger->warn("get_queued_auth_attr: " . Dumper($attr));
208         ($name and @$attr) or return;
209
210         my $query = {
211             select => {'vqarad' => ['id']},
212             from => 'vqarad',
213             where => {code => $name}
214         };
215
216         my $def_ids = new_editor()->json_query($query);
217         @$def_ids or return;
218
219         my $length;
220         $name =~ s/^(\D+)_(\d+)$/$1/ and $length = $2;
221         foreach (@$attr) {
222             $_->field eq @{$def_ids}[0]->{id} or next;
223             next if $length and $length != length($_->attr_value);
224             return $_->attr_value;
225         }
226         return;
227     },
228
229     csv_datum => sub {
230         my ($str) = @_;
231
232         if ($str =~ /\,/ || $str =~ /"/) {
233             $str =~ s/"/""/g;
234             $str = '"' . $str . '"';
235         }
236
237         return $str;
238     },
239
240
241     bre_open_hold_count => sub {
242         my $bre_id = shift;
243         return 0 unless $bre_id;
244         return $U->simplereq(
245             'open-ils.circ',
246             'open-ils.circ.bre.holds.count', $bre_id);
247     },
248
249     xml_doc => sub {
250         my ($str) = @_;
251         return $str ? (new XML::LibXML)->parse_string($str) : undef;
252     }
253
254 };
255
256
257 # processes templates.  Returns template output on success, undef on error
258 sub run_TT {
259     my $self = shift;
260     my $env = shift;
261     my $nostore = shift;
262     return undef unless $env->{template};
263
264     my $error;
265     my $output = '';
266     my $tt = Template->new;
267     # my $tt = Template->new(ENCODING => 'utf8');   # ??
268     $env->{helpers} = $_TT_helpers;
269
270     unless( $tt->process(\$env->{template}, $env, \$output) ) {
271         $output = undef;
272         ($error = $tt->error) =~ s/\n/ /og;
273         $logger->error("Error processing Trigger template: $error");
274     }
275
276     if ( $error or (!$nostore && $output) ) {
277         my $t_o = Fieldmapper::action_trigger::event_output->new;
278         $t_o->data( ($error) ? $error : $output );
279         $t_o->is_error( ($error) ? 't' : 'f' );
280         $logger->info("trigger: writing " . length($t_o->data) . " bytes to template output");
281
282         $env->{EventProcessor}->editor->xact_begin;
283         $t_o = $env->{EventProcessor}->editor->create_action_trigger_event_output( $t_o );
284
285         my $state = (ref $$env{event} eq 'ARRAY') ? $$env{event}->[0]->state : $env->{event}->state;
286         my $key = ($error) ? 'error_output' : 'template_output';
287         $env->{EventProcessor}->update_state( $state, { $key => $t_o->id } );
288     }
289         
290     return $output;
291 }
292
293
294 1;