use OpenILS::WWW::FlatFielder;
use OpenILS::WWW::PhoneList ('@sysconfdir@/opensrf_core.xml');
+# Pass second argument of '1' to disable template caching.
+use OpenILS::WWW::PrintTemplate ('/openils/conf/opensrf_core.xml', 0);
+
# - Uncomment the following 2 lines to make use of the IP redirection code
# - The IP file should to contain a map with the following format:
# - actor.org_unit.shortname <start_ip> <end_ip>
</LocationMatch>
</IfModule>
+<Location /print_template>
+ SetHandler perl-script
+ PerlHandler OpenILS::WWW::PrintTemplate
+ Options +ExecCGI
+ PerlSendHeader On
+ Require all granted
+</Location>
+
<Location /IDL2js>
</links>
</class>
+ <class id="cpt" controller="open-ils.cstore open-ils.pcrud"
+ oils_obj:fieldmapper="config::print_template"
+ oils_persist:tablename="config.print_template"
+ reporter:label="Workstation Setting Type">
+ <fields oils_persist:primary="name">
+ <field name="id" reporter:datatype="id" reporter:selector="label"/>
+ <field name="name" reporter:datatype="text" oils_obj:required="true"/>
+ <field name="label" reporter:datatype="text" oils_obj:required="true" oils_persist:i18n="true"/>
+ <field reporter:label="Owner" name="owner" oils_obj:required="true" reporter:datatype="link"/>
+ <field reporter:label="Locale" name="locale" reporter:datatype="link"/>
+ <field name="content_type" reporter:datatype="text"/>
+ <field name="template" reporter:datatype="text" oils_obj:required="true"/>
+ </fields>
+ <links>
+ <link field="owner" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="locale" reltype="has_a" key="id" map="" class="i18n_l"/>
+ </links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <create permission="ADMIN_PRINT_TEMPLATE" context_field="owner"/>
+ <retrieve permission="STAFF_LOGIN" context_field="owner"/>
+ <update permission="ADMIN_PRINT_TEMPLATE" context_field="owner"/>
+ <delete permission="ADMIN_PRINT_TEMPLATE" context_field="owner"/>
+ </actions>
+ </permacrud>
+ </class>
+
+
<!-- ********************************************************************************************************************* -->
</IDL>
import {Injectable, EventEmitter, TemplateRef} from '@angular/core';
+import {tap} from 'rxjs/operators';
import {StoreService} from '@eg/core/store.service';
+import {LocaleService} from '@eg/core/locale.service';
+import {AuthService} from '@eg/core/auth.service';
+
+declare var js2JSON: (jsThing: any) => string;
+
+const PRINT_TEMPLATE_PATH = '/print_template';
export interface PrintRequest {
template?: TemplateRef<any>;
+ templateName?: string;
contextData?: any;
text?: string;
printContext: string;
showDialog?: boolean;
}
+export interface PrintTemplateResponse {
+ contentType: string;
+ content: string;
+}
+
@Injectable()
export class PrintService {
onPrintRequest$: EventEmitter<PrintRequest>;
- constructor(private store: StoreService) {
+ constructor(
+ private locale: LocaleService,
+ private auth: AuthService,
+ private store: StoreService
+ ) {
this.onPrintRequest$ = new EventEmitter<PrintRequest>();
}
this.print(req);
}
}
+
+ compileRemoteTemplate(printReq: PrintRequest): Promise<PrintTemplateResponse> {
+
+ const formData: FormData = new FormData();
+
+ formData.append('ses', this.auth.token());
+ formData.append('template_name', printReq.templateName);
+ formData.append('template_data', js2JSON(printReq.contextData));
+ formData.append('locale', this.locale.currentLocaleCode());
+
+ return new Promise((resolve, reject) => {
+ const xhttp = new XMLHttpRequest();
+ xhttp.onreadystatechange = function() {
+ if (this.readyState === 4) {
+ if (this.status === 200) {
+ resolve({
+ content: xhttp.responseText,
+ contentType: this.getResponseHeader('content-type')
+ });
+ } else {
+ reject('Error compiling print template');
+ }
+ }
+ };
+ xhttp.open('POST', PRINT_TEMPLATE_PATH, true);
+ xhttp.send(formData);
+ });
+
+ }
}
<h4>PCRUD auto flesh and FormatService detection</h4>
<div *ngIf="aMetarecord">Fingerprint: {{aMetarecord}}</div>
+<h4>Test Server Print Template</h4>
+<button class="btn btn-info" (click)="testServerPrint()">GO</button>
+
.then(txt => this.toast.success(txt));
}, 4000);
}
+
+ testServerPrint() {
+
+ // Note these values can be IDL objects or plain hashes.
+ const patron = this.idl.create('au');
+ const address = this.idl.create('aua');
+ patron.first_given_name('Crosby');
+ patron.second_given_name('Stills');
+ patron.family_name('Nash');
+ address.street1('123 Pineapple Road');
+ address.street2('Apt #4');
+ address.city('Bahama');
+ address.state('NC');
+ address.post_code('555444');
+
+ const templateData = {
+ patron: patron,
+ address: address
+ }
+
+ // NOTE: eventually this will be baked into the print service.
+ this.printer.compileRemoteTemplate({
+ templateName: 'address-label',
+ contextData: templateData,
+ printContext: 'default'
+ }).then(
+ response => {
+ console.log(response.contentType);
+ console.log(response.content);
+ this.printer.print({
+ printContext: 'default',
+ contentType: response.contentType,
+ text: response.content,
+ showDialog: true
+ });
+ }
+ );
+ }
}
use OpenILS::Application::Actor::Friends;
use OpenILS::Application::Actor::Stage;
use OpenILS::Application::Actor::Settings;
+use OpenILS::Application::Actor::PrintTemplate;
use OpenILS::Utils::CStoreEditor qw/:funcs/;
use OpenILS::Utils::Penalty;
--- /dev/null
+package OpenILS::WWW::PrintTemplate;
+use strict; use warnings;
+use Apache2::Const -compile =>
+ qw(OK FORBIDDEN NOT_FOUND HTTP_INTERNAL_SERVER_ERROR HTTP_BAD_REQUEST);
+use Apache2::RequestRec;
+use CGI;
+use OpenSRF::Utils::JSON;
+use OpenSRF::System;
+use OpenSRF::Utils::SettingsClient;
+use OpenILS::Utils::CStoreEditor q/:funcs/;
+use OpenSRF::Utils::Logger q/$logger/;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+my $bs_config;
+my $disable_cache;
+sub import {
+ $bs_config = shift;
+ $disable_cache = shift;
+}
+
+my $init_complete = 0;
+sub child_init {
+ $init_complete = 1;
+
+ OpenSRF::System->bootstrap_client(config_file => $bs_config);
+ OpenILS::Utils::CStoreEditor->init; # just in case
+ return Apache2::Const::OK;
+}
+
+
+sub handler {
+ my $r = shift;
+ my $cgi = CGI->new;
+
+ child_init() unless $init_complete;
+
+ my $auth = $cgi->param('ses') ||
+ $cgi->cookie('eg.auth.token') || $cgi->cookie('ses');
+
+ my $e = new_editor(authtoken => $auth);
+
+ # Requires staff login
+ return Apache2::Const::FORBIDDEN
+ unless $e->checkauth && $e->requestor->wsid;
+
+ # Let pcrud handle the authz
+ $e->personality('open-ils.pcrud');
+
+ my $owner = $e->requestor->ws_ou;
+ my $locale = $cgi->param('locale') || 'en-US';
+ my $template_name = $cgi->param('template_name');
+ my $template_data = $cgi->param('template_data');
+
+ return Apache2::Const::FORBIDDEN unless $template_name;
+
+ my $template = find_template($e, $template_name, $locale, $owner)
+ or return Apache2::Const::NOT_FOUND;
+
+ my $output = '';
+ my $tt = Template->new;
+ my $tmpl = $template->template;
+ my $data;
+
+ eval { $data = OpenSRF::Utils::JSON->JSON2perl($template_data); };
+ if ($@) {
+ $logger->error("Invalid JSON in template compilation: $template_data");
+ return Apache2::Const::HTTP_BAD_REQUEST;
+ }
+
+ my $stat = $tt->process(\$tmpl, {template_data => $data}, \$output);
+
+ if ($stat) { # OK
+
+ $r->content_type($template->content_type . '; encoding=utf8');
+ $r->print($output);
+ return Apache2::Const::OK;
+
+ } else {
+
+ (my $error = $tt->error) =~ s/\n/ /og;
+ $logger->error("Error processing Trigger template: $error");
+ return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+ }
+}
+
+# Find the template closest to the specific org unit owner.
+my %template_cache;
+sub find_template {
+ my ($e, $name, $locale, $owner) = @_;
+
+ return $template_cache{$owner}{$name}{$locale}
+ if $template_cache{$owner} &&
+ $template_cache{$owner}{$name} &&
+ $template_cache{$owner}{$name}{$locale};
+
+ while ($owner) {
+ my ($org) = $U->fetch_org_unit($owner); # internally cached
+
+ my $template = $e->search_config_print_template({
+ name => $name,
+ locale => $locale,
+ owner => $org->id
+ })->[0];
+
+ if ($template) {
+ if (!$disable_cache) {
+ $template_cache{$owner} = {} unless $template_cache{$owner};
+ $template_cache{$owner}{$name} = {}
+ unless $template_cache{$owner}{$name};
+ $template_cache{$owner}{$name}{$locale} = $template;
+ }
+
+ return $template;
+ }
+
+ $owner = $org->parent_ou;
+ }
+
+ return undef;
+}
+
+1;
--- /dev/null
+
+BEGIN;
+
+/*
+No longer have to deliver print templates to the client or store as workstatoin settings
+-- trade-off is print data is now sent to the server
+Can define per-locale templates.
+immune to future technology changes
+Angular template modification requires recompiling the Angular build.
+https://metacpan.org/pod/HTML::Restrict
+security concerns of staff modifing templates directly since
+they execute in the borwser context.
+offline caveat
+*/
+
+-- SELECT evergreen.upgrade_deps_block_check('TODO', :eg_version);
+
+CREATE TABLE config.print_template (
+ id SERIAL PRIMARY KEY,
+ name TEXT NOT NULL, -- programatic name
+ label TEXT NOT NULL, -- i18n
+ owner INT NOT NULL REFERENCES actor.org_unit (id),
+ locale TEXT REFERENCES config.i18n_locale(code)
+ ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ content_type TEXT NOT NULL DEFAULT 'text/html',
+ template TEXT NOT NULL,
+ CONSTRAINT name_once_per_lib UNIQUE (owner, name),
+ CONSTRAINT label_once_per_lib UNIQUE (owner, label)
+);
+
+INSERT INTO config.print_template (id, name, locale, owner, label, template)
+VALUES (
+ 1, 'address-label', 'en-US',
+ (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
+ oils_i18n_gettext(1, 'Test Template', 'cpt', 'label'),
+$TEMPLATE$
+[%-
+ SET patron = template_data.patron;
+ SET addr = template_data.address;
+-%]
+<div style="font-size:.7em;">
+ <div>
+ [% patron.first_given_name %]
+ [% patron.second_given_name %]
+ [% patron.family_name %]
+ </div>
+ <div>[% addr.street1 %]</div>
+ [% IF addr.street2 %]<div>[% addr.street2 %]</div>[% END %]
+ <div>
+ [% addr.city %], [% addr.state %] [% addr.post_code %]
+ </div>
+</div>
+$TEMPLATE$
+);
+
+-- TODO: add print template permission
+
+-- Allow for 1k stock templates
+SELECT SETVAL('config.print_template_id_seq'::TEXT, 1000);
+
+COMMIT;
+