LP1825851 Server managed/processed print templates
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / WWW / PrintTemplate.pm
1 package OpenILS::WWW::PrintTemplate;
2 use strict; use warnings;
3 use Apache2::Const -compile => 
4     qw(OK FORBIDDEN NOT_FOUND HTTP_INTERNAL_SERVER_ERROR HTTP_BAD_REQUEST);
5 use Apache2::RequestRec;
6 use CGI;
7 use HTML::Defang;
8 use DateTime;
9 use DateTime::Format::ISO8601;
10 use Unicode::Normalize;
11 use OpenSRF::Utils::JSON;
12 use OpenSRF::System;
13 use OpenSRF::Utils::SettingsClient;
14 use OpenILS::Utils::CStoreEditor q/:funcs/;
15 use OpenSRF::Utils::Logger q/$logger/;
16 use OpenILS::Application::AppUtils;
17 use OpenILS::Utils::DateTime qw/:datetime/;
18
19 my $U = 'OpenILS::Application::AppUtils';
20 my $helpers;
21
22 my $bs_config;
23 my $enable_cache; # Enable process-level template caching
24 sub import {
25     $bs_config = shift;
26     $enable_cache = shift;
27 }
28
29 my $init_complete = 0;
30 sub child_init {
31     $init_complete = 1;
32
33     OpenSRF::System->bootstrap_client(config_file => $bs_config);
34     OpenILS::Utils::CStoreEditor->init;
35     return Apache2::Const::OK;
36 }
37
38 # HTML scrubber
39 # https://metacpan.org/pod/HTML::Defang
40 my $defang = HTML::Defang->new;
41
42 sub handler {
43     my $r = shift;
44     my $cgi = CGI->new;
45
46     child_init() unless $init_complete;
47
48     my $auth = $cgi->param('ses') || 
49         $cgi->cookie('eg.auth.token') || $cgi->cookie('ses');
50
51     my $e = new_editor(authtoken => $auth);
52
53     # Requires staff login
54     return Apache2::Const::FORBIDDEN 
55         unless $e->checkauth && $e->requestor->wsid;
56
57     # Let pcrud handle the authz
58     $e->personality('open-ils.pcrud');
59
60     my $tmpl_owner = $cgi->param('template_owner') || $e->requestor->ws_ou;
61     my $tmpl_locale = $cgi->param('template_locale') || 'en-US';
62     my $tmpl_id = $cgi->param('template_id');
63     my $tmpl_name = $cgi->param('template_name');
64     my $tmpl_data = $cgi->param('template_data');
65     my $client_timezone = $cgi->param('client_timezone');
66
67     return Apache2::Const::HTTP_BAD_REQUEST unless $tmpl_name || $tmpl_id;
68
69     my $template = 
70         find_template($e, $tmpl_id, $tmpl_name, $tmpl_locale, $tmpl_owner)
71         or return Apache2::Const::NOT_FOUND;
72
73     my $data;
74     eval { $data = OpenSRF::Utils::JSON->JSON2perl($tmpl_data); };
75     if ($@) {
76         $logger->error("Invalid JSON in template compilation: $tmpl_data");
77         return Apache2::Const::HTTP_BAD_REQUEST;
78     }
79
80     my ($staff_org) = $U->fetch_org_unit($e->requestor->ws_ou);
81
82     my $output = '';
83     my $tt = Template->new;
84     my $tmpl = $template->template;
85
86     my $context = {
87         template_locale => $tmpl_locale,
88         client_timezone => $client_timezone,
89         staff => $e->requestor,
90         staff_org => $staff_org,
91         staff_org_timezone => get_org_timezone($e, $staff_org->id),
92         helpers => $helpers,
93         template_data => $data
94     };
95
96     my $stat = $tt->process(\$tmpl, $context, \$output);
97
98     if ($stat) { # OK
99         my $ctype = $template->content_type;
100         if ($ctype eq 'text/html') {
101             $output = $defang->defang($output); # Scrub the HTML
102         }
103         # TODO
104         # client current expects content type to only contain type.
105         # $r->content_type("$ctype; encoding=utf8");
106         $r->content_type($ctype);
107         $r->print($output);
108         return Apache2::Const::OK;
109
110     } else {
111
112         (my $error = $tt->error) =~ s/\n/ /og;
113         $logger->error("Error processing print template: $error");
114         return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
115     }
116 }
117
118 my %org_timezone_cache;
119 sub get_org_timezone {
120     my ($e, $org_id) = @_;
121
122     if (!$org_timezone_cache{$org_id}) {
123
124         # open-ils.auth call required since our $e is in pcrud mode.
125         my $value = $U->simplereq(
126             'open-ils.actor',
127             'open-ils.actor.ou_setting.ancestor_default', 
128             $org_id, 'lib.timezone');
129
130         $org_timezone_cache{$org_id} = $value ? $value->{value} : 
131             DateTime->now(time_zone => 'local')->time_zone->name;
132     }
133
134     return $org_timezone_cache{$org_id};
135 }
136
137
138 # Find the template closest to the specific org unit owner.
139 my %template_cache;
140 sub find_template {
141     my ($e, $template_id, $name, $locale, $owner) = @_;
142
143     if ($template_id) {
144         # Requesting by ID, generally used for testing, 
145         # always pulls the latest value and ignores the active flag
146         return $e->retrieve_config_print_template($template_id);
147     }
148
149     return  $template_cache{$owner}{$name}{$locale}
150         if  $enable_cache &&
151             $template_cache{$owner} && 
152             $template_cache{$owner}{$name} &&
153             $template_cache{$owner}{$name}{$locale};
154
155     while ($owner) {
156         my ($org) = $U->fetch_org_unit($owner); # cached in AppUtils
157         
158         my $template = $e->search_config_print_template({
159             name => $name, 
160             locale => $locale, 
161             owner => $org->id,
162             active => 't'
163         })->[0];
164
165         if ($template) {
166
167             if ($enable_cache) {
168                 $template_cache{$owner} = {} unless $template_cache{$owner};
169                 $template_cache{$owner}{$name} = {} 
170                     unless $template_cache{$owner}{$name};
171                 $template_cache{$owner}{$name}{$locale} = $template;
172             }
173
174             return $template;
175         }
176
177         $owner = $org->parent_ou;
178     }
179
180     return undef;
181 }
182
183 # Utility / helper functions passed into every template
184
185 $helpers = {
186
187     # turns a date w/ optional timezone modifier into something 
188     # TT can understand
189     format_date => sub {
190         my $date = shift;
191         my $tz = shift;
192
193         $date = DateTime::Format::ISO8601->new->parse_datetime(clean_ISO8601($date));
194         $date->set_time_zone($tz) if $tz;
195
196         return sprintf(
197             "%0.2d:%0.2d:%0.2d %0.2d-%0.2d-%0.4d",
198             $date->hour,
199             $date->minute,
200             $date->second,
201             $date->day,
202             $date->month,
203             $date->year
204         );
205     },
206
207     current_date => sub {
208         my $tz = shift || 'local';
209         my $date = DateTime->now(time_zone => $tz);
210         return $helpers->{format_date}->($date);
211     }
212 };
213
214
215
216
217 1;