LP#1749475: OPAC email/print record improvements
authorMike Rylander <mrylander@gmail.com>
Thu, 1 Nov 2018 21:56:09 +0000 (17:56 -0400)
committerGalen Charlton <gmc@equinoxinitiative.org>
Mon, 14 Sep 2020 13:42:41 +0000 (09:42 -0400)
This commit provides new functionality for controlling and delivering print
and email bibliographic record export options.

Staff will be able to adjust existing and create new print and email formats
using the Action/Trigger infrastructure and the new Event Definition Group
capability.

Patrons will be able to choose from one of several formats, as configured by
staff, in which to receive one or more bibliographic records.  Print and email
preview is made available for individual records via the existing links on the
record detail page, and for lists of records via new entry points on both the
Basket (anonymous list) and My Lists interfaces.

Signed-off-by: Mike Rylander <mrylander@gmail.com>
Signed-off-by: Dan Wells <dbw2@calvin.edu>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Signed-off-by: Michele Morgan <mmorgan@noblenet.org>
21 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/eg2/src/app/staff/admin/basic-admin-page.component.ts
Open-ILS/src/eg2/src/app/staff/admin/server/admin-server-splash.component.html
Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Reactor/SendEmail.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Container.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
Open-ILS/src/sql/Pg/400.schema.action_trigger.sql
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.AT-def-groups.sql [new file with mode: 0644]
Open-ILS/src/templates/opac/parts/anon_list.tt2
Open-ILS/src/templates/opac/parts/cart.tt2
Open-ILS/src/templates/opac/parts/record/summary.tt2
Open-ILS/src/templates/opac/record/email.tt2
Open-ILS/src/templates/opac/record/email_preview.tt2 [new file with mode: 0644]
Open-ILS/src/templates/opac/record/print.tt2
Open-ILS/src/templates/opac/record/print_preview.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/admin/server/t_splash.tt2

index e5c945d..23b951f 100644 (file)
@@ -1476,6 +1476,62 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                </permacrud>
        </class>
 
+       <class id="atevdefg" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="action_trigger::event_def_group" oils_persist:tablename="action_trigger.event_def_group" reporter:label="Trigger Event Definition Group" oils_persist:restrict_primary="100">
+               <fields oils_persist:primary="id" oils_persist:sequence="action_trigger.event_def_group_id_seq">
+                       <field reporter:label="Definition ID" name="id" reporter:datatype="id"/>
+                       <field reporter:label="Enabled" name="active" reporter:datatype="bool"/>
+                       <field reporter:label="Owning Library" name="owner"  reporter:datatype="org_unit"/>
+                       <field reporter:label="Hook" name="hook"  reporter:datatype="link"/>
+                       <field reporter:label="Name" name="name"  reporter:datatype="text"/>
+                       <field reporter:label="Members" name="members" oils_persist:virtual="true" reporter:datatype="link"/>
+               </fields>
+               <links>
+                       <link field="owner" reltype="has_a" key="id" map="" class="aou"/>
+                       <link field="hook" reltype="has_a" key="key" map="" class="ath"/>
+                       <link field="members" reltype="has_many" key="grp" map="" class="atevdefgm"/>
+               </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <create permission="ADMIN_TRIGGER_EVENT_DEF CREATE_TRIGGER_EVENT_DEF" context_field="owner"/>
+                               <retrieve permission="ADMIN_TRIGGER_EVENT_DEF VIEW_TRIGGER_EVENT_DEF" context_field="owner"/>
+                               <update permission="ADMIN_TRIGGER_EVENT_DEF UPDATE_TRIGGER_EVENT_DEF" context_field="owner"/>
+                               <delete permission="ADMIN_TRIGGER_EVENT_DEF DELETE_TRIGGER_EVENT_DEF" context_field="owner"/>
+                       </actions>
+               </permacrud>
+       </class>
+
+       <class id="atevdefgm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="action_trigger::event_def_group_member" oils_persist:tablename="action_trigger.event_def_group_member" reporter:label="Trigger Event Definition Group Member">
+               <fields oils_persist:primary="id" oils_persist:sequence="action_trigger.event_def_group_member_id_seq">
+                       <field reporter:label="Definition ID" name="id" reporter:datatype="id"/>
+                       <field reporter:label="Name" name="name"  reporter:datatype="text"/>
+                       <field reporter:label="Group" name="grp"  reporter:datatype="link"/>
+                       <field reporter:label="Definition" name="event_def"  reporter:datatype="link"/>
+                       <field reporter:label="Sortable" name="sortable" reporter:datatype="bool"/>
+                       <field reporter:label="Include Holdings" name="holdings" reporter:datatype="bool"/>
+                       <field reporter:label="Externally Processed" name="external" reporter:datatype="bool"/>
+               </fields>
+               <links>
+                       <link field="grp" reltype="has_a" key="id" map="" class="atevdefg"/>
+                       <link field="event_def" reltype="has_a" key="id" map="" class="atevdef"/>
+               </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <create permission="ADMIN_TRIGGER_EVENT_DEF CREATE_TRIGGER_EVENT_DEF">
+                    <context link="grp" field="owner"/>
+                </create>
+                               <retrieve permission="ADMIN_TRIGGER_EVENT_DEF VIEW_TRIGGER_EVENT_DEF">
+                    <context link="grp" field="owner"/>
+                </retrieve>
+                               <update permission="ADMIN_TRIGGER_EVENT_DEF UPDATE_TRIGGER_EVENT_DEF">
+                    <context link="grp" field="owner"/>
+                </update>
+                               <delete permission="ADMIN_TRIGGER_EVENT_DEF DELETE_TRIGGER_EVENT_DEF">
+                    <context link="grp" field="owner"/>
+                </delete>
+                       </actions>
+               </permacrud>
+       </class>
+
        <class id="atev" controller="open-ils.cstore" oils_obj:fieldmapper="action_trigger::event" oils_persist:tablename="action_trigger.event" reporter:label="Trigger Event Entry">
                <fields oils_persist:primary="id" oils_persist:sequence="action_trigger.event_id_seq">
                        <field reporter:label="Event ID" name="id" reporter:datatype="id"/>
index 1c9b4c8..6a43a62 100644 (file)
@@ -56,10 +56,10 @@ export class BasicAdminPageComponent implements OnInit {
         // the prefix because that will cause it to double-up.
         // e.g. eg.grid.acq.acq.cancel_reason
         this.persistKeyPfx = this.route.snapshot.parent.url[0].path;
-        const selfPrefixers = ['acq', 'booking'];
+        const selfPrefixers = ['acq', 'action_trigger', 'booking'];
         if (selfPrefixers.indexOf(this.persistKeyPfx) > -1) {
-            // ACQ is a special case, because unlike 'server', 'local',
-            // 'workstation', the schema ('acq') is the root of the path.
+            // selfPrefixers, unlike 'server', 'local', and
+            // 'workstation', are the root of the path.
             this.persistKeyPfx = '';
         } else {
             this.configLinkBasePath += '/' + this.persistKeyPfx;
index de399d6..849c2ea 100644 (file)
       routerLink="/staff/admin/server/config/z3950_index_field_map"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Z39.50 Servers"  
       routerLink="/staff/admin/server/config/z3950_source"></eg-link-table-link>
+    <eg-link-table-link i18n-label label="Event Defintion Groups"
+      routerLink="/staff/admin/server/action_trigger/event_def_group"></eg-link-table-link>
+    <eg-link-table-link i18n-label label="Event Defintion Group Members"
+      routerLink="/staff/admin/server/action_trigger/event_def_group_member"></eg-link-table-link>
   </eg-link-table>
 </div>
index b07440e..f2d7d1a 100644 (file)
@@ -10,6 +10,8 @@ use OpenSRF::Utils::SettingsClient;
 use OpenILS::Utils::CStoreEditor q/:funcs/;
 use OpenSRF::Utils::Cache;
 use Encode;
+use Email::Send;
+use Email::Simple;
 
 use OpenSRF::Utils::Logger qw/:logger/;
 
@@ -1833,12 +1835,77 @@ sub biblio_record_to_marc_html {
 }
 
 __PACKAGE__->register_method(
+    method    => "send_event_email_output",
+    api_name  => "open-ils.search.biblio.record.email.send_output",
+);
+sub send_event_email_output {
+    my($self, $client, $auth, $event_id, $capkey, $capanswer) = @_;
+    return undef unless $event_id;
+
+    my $captcha_pass = 0;
+    my $real_answer;
+    if ($capkey) {
+        $real_answer = $cache->get_cache(md5_hex($capkey));
+        $captcha_pass++ if ($real_answer eq $capanswer);
+    }
+
+    my $e = new_editor(authtoken => $auth);
+    return $e->die_event unless $captcha_pass || $e->checkauth;
+
+    my $event = $e->retrieve_action_trigger_event([$event_id,{flesh => 1, flesh_fields => { atev => ['template_output']}}]);
+    return undef unless ($event and $event->template_output);
+
+    my $smtp = OpenSRF::Utils::SettingsClient
+        ->new
+        ->config_value('email_notify', 'smtp_server');
+
+    my $sender = Email::Send->new({mailer => 'SMTP'});
+    $sender->mailer_args([Host => $smtp]);
+
+    my $stat;
+    my $err;
+
+    my $email = Email::Simple->new($event->template_output->data);
+
+    for my $hfield (qw/From To Subject Bcc Cc Reply-To Sender/) {
+        my @headers = $email->header($hfield);
+        $email->header_set($hfield => map { encode("MIME-Header", $_) } @headers) if ($headers[0]);
+    }
+
+    $email->header_set('MIME-Version' => '1.0');
+    $email->header_set('Content-Type' => "text/plain; charset=UTF-8");
+    $email->header_set('Content-Transfer-Encoding' => '8bit');
+
+    try {
+        $stat = $sender->send($email);
+    } catch Error with {
+        $err = $stat = shift;
+        $logger->error("resend_at_email: Email failed with error: $err");
+    };
+
+    return undef;
+}
+
+__PACKAGE__->register_method(
+    method    => "format_biblio_record_entry",
+    api_name  => "open-ils.search.biblio.record.print.preview",
+);
+
+__PACKAGE__->register_method(
+    method    => "format_biblio_record_entry",
+    api_name  => "open-ils.search.biblio.record.email.preview",
+);
+
+__PACKAGE__->register_method(
     method    => "format_biblio_record_entry",
     api_name  => "open-ils.search.biblio.record.print",
     signature => {
         desc   => 'Returns a printable version of the specified bib record',
         params => [
             { desc => 'Biblio record entry ID or array of IDs', type => 'number' },
+            { desc => 'Context library for holdings, if applicable' => 'number' },
+            { desc => 'Sort order, if applicable' => 'string' },
+            { desc => 'Definition Group Member id' => 'number' },
         ],
         return => {
             desc => q/An action_trigger.event object or error event./,
@@ -1854,6 +1921,13 @@ __PACKAGE__->register_method(
         params => [
             { desc => 'Authentication token',  type => 'string'},
             { desc => 'Biblio record entry ID or array of IDs', type => 'number' },
+            { desc => 'Context library for holdings, if applicable' => 'number' },
+            { desc => 'Sort order, if applicable' => 'string' },
+            { desc => 'Sort direction, if applicable' => 'string' },
+            { desc => 'Definition Group Member id' => 'number' },
+            { desc => 'Whether to bypass auth due to captcha' => 'bool' },
+            { desc => 'Email address, if none for the user' => 'string' },
+            { desc => 'Subject, if customized' => 'string' },
         ],
         return => {
             desc => q/Undefined on success, otherwise an error event./,
@@ -1863,25 +1937,44 @@ __PACKAGE__->register_method(
 );
 
 sub format_biblio_record_entry {
-    my($self, $conn, $arg1, $arg2) = @_;
+    my($self, $conn, $arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $captcha_pass, $email, $subject) = @_;
 
     my $for_print = ($self->api_name =~ /print/);
     my $for_email = ($self->api_name =~ /email/);
+    my $preview = ($self->api_name =~ /preview/);
 
-    my $e; my $auth; my $bib_id; my $context_org;
+    my $e; my $auth; my $bib_id; my $context_org; my $holdings_context; my $bib_sort; my $group_member; my $type = 'brief'; my $sort_dir;
 
     if ($for_print) {
         $bib_id = $arg1;
         $context_org = $arg2 || $U->get_org_tree->id;
+        $holdings_context = $context_org;
+        $bib_sort = $arg3 || 'author';
+        $sort_dir = $arg4 || 'ascending';
+        $group_member = $arg5;
         $e = new_editor(xact => 1);
     } elsif ($for_email) {
         $auth = $arg1;
         $bib_id = $arg2;
+        $bib_sort = $arg4 || 'author';
+        $sort_dir = $arg5 || 'ascending';
+        $group_member = $arg6;
         $e = new_editor(authtoken => $auth, xact => 1);
-        return $e->die_event unless $e->checkauth;
-        $context_org = $e->requestor->home_ou;
+        return $e->die_event unless $captcha_pass || $e->checkauth;
+        $holdings_context = $arg3 || $U->get_org_tree->id;
+        $context_org = $e->requestor ? $e->requestor->home_ou : $arg3;
+        $email ||= $e->requestor ? $e->requestor->email : '';
+    }
+
+    if ($group_member) {
+        $group_member = $e->retrieve_action_trigger_event_def_group_member($group_member);
+        if ($group_member and $U->is_true($group_member->holdings)) {
+            $type = 'full';
+        }
     }
 
+    $holdings_context = $e->retrieve_actor_org_unit($holdings_context);
+
     my $bib_ids;
     if (ref $bib_id ne 'ARRAY') {
         $bib_ids = [ $bib_id ];
@@ -1893,7 +1986,7 @@ sub format_biblio_record_entry {
     $bucket->btype('temp');
     $bucket->name('format_biblio_record_entry ' . $U->create_uuid_string);
     if ($for_email) {
-        $bucket->owner($e->requestor) 
+        $bucket->owner($e->requestor || 1
     } else {
         $bucket->owner(1);
     }
@@ -1911,13 +2004,26 @@ sub format_biblio_record_entry {
 
     $e->commit;
 
+    my $usr_data = {
+        type        => $type,
+        email       => $email,
+        subject     => $subject,
+        context_org => $holdings_context->shortname,
+        sort_by     => $bib_sort,
+        sort_dir    => $sort_dir,
+        preview     => $preview
+    };
+
     if ($for_print) {
 
-        return $U->fire_object_event(undef, 'biblio.format.record_entry.print', [ $bucket ], $context_org);
+        return $U->fire_object_event(undef, 'biblio.format.record_entry.print', [ $bucket ], $context_org, undef, [ $usr_data ]);
 
     } elsif ($for_email) {
 
-        $U->create_events_for_hook('biblio.format.record_entry.email', $bucket, $context_org, undef, undef, 1);
+        return $U->fire_object_event(undef, 'biblio.format.record_entry.email', [ $bucket ], $context_org, undef, [ $usr_data ])
+            if ($preview);
+
+        $U->create_events_for_hook('biblio.format.record_entry.email', $bucket, $context_org, undef, $usr_data, 1);
     }
 
     return undef;
index bf6b67e..f011417 100644 (file)
@@ -418,7 +418,7 @@ $_TT_helpers = {
                 $unapi_args->{flesh}, 
                 $unapi_args->{site}, 
                 $unapi_args->{depth}, 
-                $unapi_args->{flesh_depth}, 
+                $unapi_args->{flesh_limit}, 
             ]
         };
 
@@ -427,6 +427,99 @@ $_TT_helpers = {
         return $_TT_helpers->{xml_doc}->($unapi->[0]->{'unapi.bre'});
     },
 
+    # input: list of bib bucket items; output: sorted list of unapi_bre objects
+    sort_bucket_unapi_bre => sub {
+        my ($list, $unapi_args, $sortby, $sortdir) = @_;
+        #$logger->info("sort_bucket_unapi_bre unapi_bre params: " . join(', ', map { "$_: $$unapi_args{$_}" } keys(%$unapi_args)));
+        my @sorted_list;
+        for my $i (@$list) {
+            my $xml = $_TT_helpers->{unapi_bre}->($i->target_biblio_record_entry, $unapi_args);
+            if ($xml) {
+                my $bib = { xml => $xml, id => $i->target_biblio_record_entry };
+
+                $$bib{title} = '';
+                for my $part ($xml->findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]')) {
+                    $$bib{title} = $$bib{title} . $part->textContent;
+                }
+                $$bib{titlesort} = lc(substr($$bib{title}, $xml->findnodes('//*[@tag="245"]')->get_node(1)->getAttribute('ind2')))
+                    if ($$bib{title});
+
+                $$bib{authorsort} = $$bib{author} = $xml->findnodes('//*[@tag="100"]/*[@code="a"]')->to_literal_delimited(' ');
+                $$bib{authorsort} = lc($$bib{authorsort});
+                $$bib{item_type} = $xml->findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]')->get_node(1)->getAttribute('coded-value');
+                my $p = $xml->findnodes('//*[@tag="260" or @tag="264"]/*[@code="b"]')->get_node(1);
+                $$bib{publisher} = $p ? $p->textContent : '';
+                $$bib{pubdatesort} = $$bib{pubdate} = $xml->findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="date1"]')->get_node(1)->textContent;
+                $$bib{pubdatesort} = lc($$bib{pubdatesort});
+                $$bib{isbn} = $xml->findnodes('//*[@tag="020"]/*[@code="a"]')->to_literal_delimited(', ');
+                $$bib{issn} = $xml->findnodes('//*[@tag="022"]/*[@code="a"]')->to_literal_delimited(', ');
+                $$bib{upc} = $xml->findnodes('//*[@tag="024"]/*[@code="a"]')->to_literal_delimited(', ');
+
+                $$bib{holdings} = [];
+
+                for my $vol ($xml->findnodes('//*[local-name()="volume" and @deleted="false" and @opac_visible="true"]')) {
+                    my $vol_data = {};
+                    $$vol_data{prefix_sort} = $vol->findnodes('.//*[local-name()="call_number_prefix"]')->get_node(1)->getAttribute('label_sortkey');
+                    $$vol_data{prefix} = $vol->findnodes('.//*[local-name()="call_number_prefix"]')->get_node(1)->getAttribute('label');
+                    $$vol_data{callnumber} = $vol->getAttribute('label');
+                    $$vol_data{callnumber_sort} = $vol->getAttribute('label_sortkey');
+                    $$vol_data{suffix_sort} = $vol->findnodes('.//*[local-name()="call_number_suffix"]')->get_node(1)->getAttribute('label_sortkey');
+                    $$vol_data{suffix} = $vol->findnodes('.//*[local-name()="call_number_suffix"]')->get_node(1)->getAttribute('label');
+                    #$logger->info("sort_bucket_unapi_bre found volume: " . join(', ', map { "$_: $$vol_data{$_}" } keys(%$vol_data)));
+
+                    my @copies;
+                    for my $cp ($vol->findnodes('.//*[local-name()="copy" and @deleted="false"]')) {
+                        my $cp_data = {%$vol_data};
+                        my $l = $cp->findnodes('.//*[local-name()="location" and @opac_visible="true"]')->get_node(1);
+                        next unless ($l);
+                        $$cp_data{location} = $l->textContent;
+
+                        my $s = $cp->findnodes('.//*[local-name()="status" and @opac_visible="true"]')->get_node(1);
+                        next unless ($s);
+                        $$cp_data{status_label} = $s->textContent;
+                        $$cp_data{status_id} = $s->getAttribute('ident');
+
+                        my $c = $cp->findnodes('.//*[local-name()="circ_lib" and @opac_visible="true"]')->get_node(1);
+                        next unless ($c);
+                        $$cp_data{circ_lib} = $c->getAttribute('name');
+
+                        $$cp_data{barcode} = $cp->getAttribute('barcode');
+
+                        $$cp_data{parts} = '';
+                        for my $mp ($cp->findnodes('.//*[local-name()="monograph_part"]')) {
+                            $$cp_data{parts} .= ', ' if $$cp_data{parts};
+                            $$cp_data{parts} .= $mp->textContent;
+                        }
+                        push @copies, $cp_data;
+                        #$logger->info("sort_bucket_unapi_bre found copy: " . join(', ', map { "$_: $$cp_data{$_}" } keys(%$cp_data)));
+                    }
+                    if (@copies) {
+                        push @{$$bib{holdings}}, @copies;
+                    }
+                }
+
+                # sort 'em!
+                $$bib{holdings} = [ sort {
+                    $$a{circ_lib}     cmp $$b{circ_lib} ||
+                    $$a{location}     cmp $$b{location} ||
+                    $$a{prefix_sort}  cmp $$b{prefix_sort} ||
+                    $$a{callnumber_sort}   cmp $$b{callnumber_sort} ||
+                    $$a{suffix_sort}  cmp $$b{suffix_sort} ||
+                    ($$a{status_id} == 0 ? -1 : 0) ||
+                    ($$a{status_id} == 7 ? -1 : 0) ||
+                    $$a{status_label} cmp $$b{status_label};
+                } @{$$bib{holdings}} ];
+
+                push @sorted_list, $bib;
+            }
+        }
+
+        if ($sortdir =~ /^d/) {
+            return [ sort { $$b{$sortby.'sort'} cmp $$a{$sortby.'sort'} } @sorted_list ];
+        }
+        return [ sort { $$a{$sortby.'sort'} cmp $$b{$sortby.'sort'} } @sorted_list ];
+    },
+
     # escapes quotes in csv string values
     escape_csv => sub {
         my $string = shift;
index 8f7a321..880e95b 100644 (file)
@@ -49,6 +49,10 @@ sub handler {
 
     my $text = encode_utf8($self->run_TT($env));
     return 0 if (!$text);
+    if ($$env{user_data} && ref($$env{user_data}) =~ /HASH/ && $$env{user_data}{preview}) {
+        $logger->info("SendEmail Reactor: success in preview mode, not sending email");
+        return 1;
+    }
 
     my $sender = Email::Send->new({mailer => 'SMTP'});
     $sender->mailer_args([Host => $smtp]);
index c458848..c214c1a 100644 (file)
@@ -12,6 +12,7 @@ use OpenSRF::Utils::Logger qw/$logger/;
 use OpenILS::Application::AppUtils;
 use OpenILS::Utils::CStoreEditor qw/:funcs/;
 use OpenILS::Utils::Fieldmapper;
+use OpenSRF::Utils::Cache;
 use DateTime::Format::ISO8601;
 use CGI qw(:all -utf8);
 use Time::HiRes;
@@ -133,6 +134,9 @@ sub load {
         $path =~ /opac\/my(opac\/lists|list)/ ||
         $path =~ m!opac/api/mylist!;
 
+    my $org_unit = $self->ctx->{physical_loc} || $self->cgi->param('context_org') || $self->_get_search_lib;
+    $self->ctx->{selected_print_email_loc} = $org_unit;
+
     return $self->load_api_mylist_retrieve if $path =~ m|opac/api/mylist/retrieve|;
     return $self->load_api_mylist_add if $path =~ m|opac/api/mylist/add|;
     return $self->load_api_mylist_delete if $path =~ m|opac/api/mylist/delete|;
@@ -145,6 +149,7 @@ sub load {
 
     return $self->load_library if $path =~ m|opac/library|;
     return $self->load_rresults if $path =~ m|opac/results|;
+    return $self->load_print_or_email_preview('print') if $path =~ m|opac/record/print_preview|;
     return $self->load_print_record if $path =~ m|opac/record/print|;
     return $self->load_record if $path =~ m|opac/record/\d|;
     return $self->load_cnbrowse if $path =~ m|opac/cnbrowse|;
@@ -184,11 +189,36 @@ sub load {
     }
 
     if ($path =~ m|opac/sms_cn| and !$self->editor->requestor) {
-        my $org_unit = $self->ctx->{physical_loc} || $self->cgi->param('loc') || $self->ctx->{aou_tree}->()->id;
         my $skip_sms_auth = $self->ctx->{get_org_setting}->($org_unit, 'sms.disable_authentication_requirement.callnumbers');
         return $self->load_sms_cn if $skip_sms_auth;
     }
 
+    if (!$self->editor->requestor && $path =~ m|opac/record/email|) {
+        if ($self->ctx->{get_org_setting}->($org_unit, 'opac.email_record.allow_without_login')) {
+            my $cache = OpenSRF::Utils::Cache->new('global');
+
+            if ($path !~ m|preview|) { # the real thing!
+                $logger->info("not preview");
+                my $cap_key = $self->ctx->{cap}->{key} = $self->cgi->param('capkey');
+                $logger->info("got cap_key $cap_key");
+                if ($cap_key) {
+                    my $cap_answer = $self->ctx->{cap_answer} = $self->cgi->param('capanswer');
+                    my $real_answer = $self->ctx->{real_answer} = $cache->get_cache(md5_hex($cap_key));
+                    $logger->info("got answers $cap_answer $real_answer");
+                    return $self->load_email_record(1) if ( $cap_answer eq $real_answer );
+                }
+            }
+
+            my $captcha = {};
+            $$captcha{key} = time() . $$ . rand();
+            $$captcha{left} = int(rand(10));
+            $$captcha{right} = int(rand(10));
+            $cache->put_cache(md5_hex($$captcha{key}), $$captcha{left} + $$captcha{right});
+            $self->ctx->{captcha} = $captcha;
+            return $self->load_print_or_email_preview('email', 1) if $path =~ m|opac/record/email_preview|;
+        }
+    }
+
     # ----------------------------------------------------------------
     #  Everything below here requires authentication
     # ----------------------------------------------------------------
@@ -202,7 +232,9 @@ sub load {
         (undef, $self->ctx->{mylist}) = $self->fetch_mylist;
     }
     $self->load_simple("mylist/email") if $path =~ m|opac/mylist/email|;
+    return $self->load_print_or_email_preview('email') if $path =~ m|opac/mylist/doemail_preview|;
     return $self->load_mylist_email if $path =~ m|opac/mylist/doemail|;
+    return $self->load_print_or_email_preview('email') if $path =~ m|opac/record/email_preview|;
     return $self->load_email_record if $path =~ m|opac/record/email|;
 
     return $self->load_place_hold if $path =~ m|opac/place_hold|;
index 4065e24..3c78ea2 100644 (file)
@@ -2907,10 +2907,42 @@ sub load_myopac_bookbag_update {
         return $self->generic_redirect($url);
 
     } elsif ($action eq 'print') {
-        my $temp_cache_key = $self->_stash_record_list_in_anon_cache(@selected_item);
+        my ($incoming_sort,$sort_dir) = $self->_get_bookbag_sort_params('sort');
+        $sort_dir = $self->cgi->param('sort_dir') if $self->cgi->param('sort_dir');
+        if (!$incoming_sort) {
+            ($incoming_sort,$sort_dir) = $self->_get_bookbag_sort_params('anonsort');
+        }
+        if (!$incoming_sort) {
+            $incoming_sort = 'author';
+        }
+
+        $incoming_sort =~ s/sort.*$//;
+
+        $self->ctx->{sort} = $incoming_sort;
+        $self->ctx->{sort_dir} = $sort_dir;
+
+        my $items = $self->editor->search_container_biblio_record_entry_bucket_item({id=>\@selected_item});
+        my @bib_ids = map { $_->target_biblio_record_entry } @$items;
+        my $temp_cache_key = $self->_stash_record_list_in_anon_cache(@bib_ids);
         return $self->load_mylist_print($temp_cache_key);
     } elsif ($action eq 'email') {
-        my $temp_cache_key = $self->_stash_record_list_in_anon_cache(@selected_item);
+        my ($incoming_sort,$sort_dir) = $self->_get_bookbag_sort_params('sort');
+        $sort_dir = $self->cgi->param('sort_dir') if $self->cgi->param('sort_dir');
+        if (!$incoming_sort) {
+            ($incoming_sort,$sort_dir) = $self->_get_bookbag_sort_params('anonsort');
+        }
+        if (!$incoming_sort) {
+            $incoming_sort = 'author';
+        }
+
+        $incoming_sort =~ s/sort.*$//;
+
+        $self->ctx->{sort} = $incoming_sort;
+        $self->ctx->{sort_dir} = $sort_dir;
+
+        my $items = $self->editor->search_container_biblio_record_entry_bucket_item({id=>\@selected_item});
+        my @bib_ids = map { $_->target_biblio_record_entry } @$items;
+        my $temp_cache_key = $self->_stash_record_list_in_anon_cache(@bib_ids);
         return $self->load_mylist_email($temp_cache_key);
     } else {
 
index 6b07672..89ccdd0 100644 (file)
@@ -205,7 +205,7 @@ sub load_mylist_print {
     }
 
     my $url = sprintf(
-        "%s://%s%s/record/print/%s",
+        "%s://%s%s/record/print_preview/%s",
         $self->ctx->{proto},
         $self->ctx->{hostname},
         $self->ctx->{opac_root},
@@ -215,8 +215,12 @@ sub load_mylist_print {
     my $redirect = $self->cgi->param('redirect_to');
     $url .= '?redirect_to=' . uri_escape_utf8($redirect);
     my $clear_cart = $self->cgi->param('clear_cart');
-    $url .= '&is_list=1';
     $url .= '&clear_cart=1' if $clear_cart;
+    my $sort = $self->cgi->param('sort') || $self->cgi->param('anonsort');
+    my $sort_dir = $self->cgi->param('sort_dir');
+    $url .= '&sort='.$sort if $sort;
+    $url .= '&sort_dir='.$sort_dir if $sort_dir;
+    $url .= '&is_list=1';
 
     return $self->generic_redirect($url);
 }
@@ -231,7 +235,7 @@ sub load_mylist_email {
     }
 
     my $url = sprintf(
-        "%s://%s%s/record/email/%s",
+        "%s://%s%s/record/email_preview/%s",
         $self->ctx->{proto},
         $self->ctx->{hostname},
         $self->ctx->{opac_root},
@@ -241,8 +245,12 @@ sub load_mylist_email {
     my $redirect = $self->cgi->param('redirect_to');
     $url .= '?redirect_to=' . uri_escape_utf8($redirect);
     my $clear_cart = $self->cgi->param('clear_cart');
-    $url .= '&is_list=1';
     $url .= '&clear_cart=1' if $clear_cart;
+    my $sort = $self->cgi->param('sort') || $self->cgi->param('anonsort');
+    my $sort_dir = $self->cgi->param('sort_dir');
+    $url .= '&sort='.$sort if $sort;
+    $url .= '&sort_dir='.$sort_dir if $sort_dir;
+    $url .= '&is_list=1';
 
     return $self->generic_redirect($url);
 }
@@ -279,10 +287,16 @@ sub load_mylist_move {
         return $self->load_myopac_bookbag_update('place_hold', undef, @rec_ids);
     }
     if ($action eq 'print') {
+        if ($self->cgi->param('entire_list')) {
+            @rec_ids = @$list;
+        }
         my $temp_cache_key = $self->_stash_record_list_in_anon_cache(@rec_ids);
         return $self->load_mylist_print($temp_cache_key);
     }
     if ($action eq 'email') {
+        if ($self->cgi->param('entire_list')) {
+            @rec_ids = @$list;
+        }
         my $temp_cache_key = $self->_stash_record_list_in_anon_cache(@rec_ids);
         return $self->load_mylist_email($temp_cache_key);
     }
index bc3972b..5032716 100644 (file)
@@ -507,13 +507,30 @@ sub get_hold_copy_summary {
     $search->kill_me;
 }
 
-sub load_print_record {
+sub load_print_or_email_preview {
     my $self = shift;
+    my $type = shift;
+    my $captcha_pass = shift;
+
+    my $ctx = $self->ctx;
+    my $e = new_editor(xact => 1);
+    my $old_event = $self->cgi->param('old_event');
+    if ($old_event) {
+        $old_event = $e->retrieve_action_trigger_event([
+            $old_event,
+            {flesh => 1, flesh_fields => { atev => ['template_output'] }}
+        ]);
+        $e->delete_action_trigger_event($old_event) if ($old_event);
+        $e->delete_action_trigger_event_output($old_event->template_output) if ($old_event && $old_event->template_output);
+        $e->commit;
+    }
 
-    my $rec_or_list_id = $self->ctx->{page_args}->[0]
+    my $rec_or_list_id = $ctx->{page_args}->[0]
         or return Apache2::Const::HTTP_BAD_REQUEST;
 
-    my $is_list = $self->cgi->param('is_list');
+    $ctx->{bre_id} = $rec_or_list_id;
+
+    my $is_list = $ctx->{is_list} = $self->cgi->param('is_list');
     my $list;
     if ($is_list) {
 
@@ -533,12 +550,88 @@ sub load_print_record {
         };
     } else {
         $list = $rec_or_list_id;
-        $self->{ctx}->{bre_id} = $rec_or_list_id;
+        $ctx->{bre_id} = $rec_or_list_id;
     }
 
-    $self->{ctx}->{printable_record} = $U->simplereq(
-        'open-ils.search',
-        'open-ils.search.biblio.record.print', $list);
+    $ctx->{sortable} = (ref($list) && @$list > 1);
+
+    my $group = $type eq 'print' ? 1 : 2;
+
+    $ctx->{formats} = $self->editor->search_action_trigger_event_def_group_member([{grp => $group},{order_by => { atevdefgm => 'name'}}]);
+    $ctx->{format} = $self->cgi->param('format') || $ctx->{formats}[0]->id;
+    if ($type eq 'email') {
+        $ctx->{email} = $self->cgi->param('email') || ($ctx->{user} ? $ctx->{user}->email : '');
+        $ctx->{subject} = $self->cgi->param('subject');
+    }
+
+    my $context_org = $self->cgi->param('context_org');
+    if ($context_org) {
+        $context_org = $self->ctx->{get_aou}->($context_org);
+    }
+
+    if (!$context_org) {
+        $context_org = $self->ctx->{get_aou}->($self->_get_search_lib()) ||
+            $self->ctx->{aou_tree}->();
+    }
+
+    $ctx->{context_org} = $context_org->id;
+
+    my ($incoming_sort,$sort_dir) = $self->_get_bookbag_sort_params('sort');
+    $sort_dir = $self->cgi->param('sort_dir') if $self->cgi->param('sort_dir');
+    if (!$incoming_sort) {
+        ($incoming_sort,$sort_dir) = $self->_get_bookbag_sort_params('anonsort');
+    }
+    if (!$incoming_sort) {
+        $incoming_sort = 'author';
+    }
+
+    $incoming_sort =~ s/sort.*$//;
+
+    $incoming_sort = 'author'
+        unless (grep {$_ eq $incoming_sort} qw/title author pubdate/);
+
+    $ctx->{sort} = $incoming_sort;
+    $ctx->{sort_dir} = $sort_dir;
+
+    my $method = "open-ils.search.biblio.record.$type.preview";
+    my @args = (
+        $list,
+        $ctx->{context_org},
+        $ctx->{sort},
+        $ctx->{sort_dir},
+        $ctx->{format},
+        $captcha_pass,
+        $ctx->{email},
+        $ctx->{subject}
+    );
+
+    unshift(@args, $ctx->{authtoken}) if ($type eq 'email');
+
+    $ctx->{preview_record} = $U->simplereq(
+        'open-ils.search', $method, @args);
+
+    $ctx->{'redirect_to'} = $self->cgi->param('redirect_to') || $self->cgi->referer;
+
+    return Apache2::Const::OK;
+}
+
+sub load_print_record {
+    my $self = shift;
+
+    my $event_id = $self->ctx->{page_args}->[0]
+        or return Apache2::Const::HTTP_BAD_REQUEST;
+
+    my $event = $self->editor->retrieve_action_trigger_event([
+        $event_id,
+        {flesh => 1, flesh_fields => { atev => ['template_output'] }}
+    ]);
+
+    return Apache2::Const::HTTP_BAD_REQUEST
+        unless ($event and $event->template_output and $event->template_output->data);
+
+    $self->ctx->{bre_id} = $self->cgi->param('bre_id');
+    $self->ctx->{is_list} = $self->cgi->param('is_list');
+    $self->ctx->{print_data} = $event->template_output->data;
 
     if ($self->cgi->param('clear_cart')) {
         $self->clear_anon_cache;
@@ -550,37 +643,40 @@ sub load_print_record {
 
 sub load_email_record {
     my $self = shift;
+    my $captcha_pass = shift;
 
-    my $rec_or_list_id = $self->ctx->{page_args}->[0]
+    my $event_id = $self->ctx->{page_args}->[0]
         or return Apache2::Const::HTTP_BAD_REQUEST;
 
-    my $is_list = $self->cgi->param('is_list');
-    my $list;
-    if ($is_list) {
+    my $e = new_editor(xact => 1, authtoken => $self->ctx->{authtoken});
+    return Apache2::Const::HTTP_BAD_REQUEST
+        unless $captcha_pass || $e->checkauth;
 
-        $list = $U->simplereq(
-            'open-ils.actor',
-            'open-ils.actor.anon_cache.get_value',
-            $rec_or_list_id, (ref $self)->CART_CACHE_MYLIST);
+    my $event = $e->retrieve_action_trigger_event([
+        $event_id,
+        {flesh => 1, flesh_fields => { atev => ['template_output'] }}
+    ]);
 
-        if(!$list) {
-            $list = [];
-        }
+    return Apache2::Const::HTTP_BAD_REQUEST
+        unless ($event and $event->template_output and $event->template_output->data);
 
-        {   # sanitize
-            no warnings qw/numeric/;
-            $list = [map { int $_ } @$list];
-            $list = [grep { $_ > 0} @$list];
-        };
-    } else {
-        $list = $rec_or_list_id;
-        $self->{ctx}->{bre_id} = $rec_or_list_id;
-    }
+    $self->ctx->{email} = $self->cgi->param('email');
+    $self->ctx->{subject} = $self->cgi->param('subject');
+    $self->ctx->{bre_id} = $self->cgi->param('bre_id');
+    $self->ctx->{is_list} = $self->cgi->param('is_list');
+    $self->ctx->{print_data} = $event->template_output->data;
 
     $U->simplereq(
         'open-ils.search',
-        'open-ils.search.biblio.record.email', 
-        $self->ctx->{authtoken}, $list);
+        'open-ils.search.biblio.record.email.send_output',
+        $self->ctx->{authtoken}, $event_id,
+        $self->ctx->{cap}->{key}, $self->ctx->{cap_answer});
+
+    # Move the output to async so it can't be used in a resend attack
+    $event->async_output($event->template_output->id);
+    $event->clear_template_output;
+    $e->update_action_trigger_event($event);
+    $e->commit;
 
     if ($self->cgi->param('clear_cart')) {
         $self->clear_anon_cache;
index 9f990a3..8a0c213 100644 (file)
@@ -291,6 +291,33 @@ CREATE TABLE action_trigger.event_params (
     CONSTRAINT event_params_event_def_param_once UNIQUE (event_def,param)
 );
 
+CREATE TABLE action_trigger.event_def_group (
+    id      SERIAL  PRIMARY KEY,
+    owner   INT     NOT NULL REFERENCES actor.org_unit (id)
+                        ON DELETE RESTRICT ON UPDATE CASCADE
+                        DEFERRABLE INITIALLY DEFERRED,
+    hook    TEXT    NOT NULL REFERENCES action_trigger.hook (key)
+                        ON DELETE RESTRICT ON UPDATE CASCADE
+                        DEFERRABLE INITIALLY DEFERRED,
+    active  BOOL    NOT NULL DEFAULT TRUE,
+    name    TEXT    NOT NULL
+);
+SELECT SETVAL('action_trigger.event_def_group_id_seq'::TEXT, 100, TRUE);
+
+CREATE TABLE action_trigger.event_def_group_member (
+    id          SERIAL  PRIMARY KEY,
+    grp         INT     NOT NULL REFERENCES action_trigger.event_def_group (id)
+                            ON DELETE CASCADE ON UPDATE CASCADE
+                            DEFERRABLE INITIALLY DEFERRED,
+    event_def   INT     NOT NULL REFERENCES action_trigger.event_definition (id)
+                            ON DELETE RESTRICT ON UPDATE CASCADE
+                            DEFERRABLE INITIALLY DEFERRED,
+    sortable    BOOL    NOT NULL DEFAULT TRUE,
+    holdings    BOOL    NOT NULL DEFAULT FALSE,
+    external    BOOL    NOT NULL DEFAULT FALSE,
+    name        TEXT    NOT NULL
+);
+
 CREATE OR REPLACE FUNCTION action_trigger.purge_events() RETURNS VOID AS $_$
 /**
   * Deleting expired events without simultaneously deleting their outputs
index e203f28..3eab0bd 100644 (file)
@@ -12023,39 +12023,47 @@ INSERT INTO action_trigger.event_definition (
 $$
 [%- USE date -%]
 [%- SET user = target.0.owner -%]
-To: [%- params.recipient_email || user.email %]
+To: [%- params.recipient_email || user_data.0.email || user.email %]
 From: [%- params.sender_email || default_sender %]
 Date: [%- date.format(date.now, '%a, %d %b %Y %T -0000', gmt => 1) %]
-Subject: Bibliographic Records
+Subject: [%- user_data.0.subject || 'Bibliographic Records' %]
 Auto-Submitted: auto-generated
 
-[% FOR cbreb IN target %]
-[% FOR item IN cbreb.items;
-    bre_id = item.target_biblio_record_entry;
+[% FOR cbreb IN target;
 
-    bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'});
-    title = '';
-    FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
-        title = title _ part.textContent;
+    flesh_list = '{mra';
+    IF user_data.0.type == 'full';
+        flesh_list = flesh_list _ ',holdings_xml,acp';
+        IF params.holdings_limit;
+            flimit = 'acn=>' _ params.holdings_limit _ ',acp=>' _ params.holdings_limit;
+        END;
     END;
+    flesh_list = flesh_list _ '}';
+
+    item_list = helpers.sort_bucket_unapi_bre(cbreb.items,{flesh => flesh_list, site => user_data.0.context_org, flesh_limit => flimit}, user_data.0.sort_by, user_data.0.sort_dir);
+
+FOR item IN item_list %]
+
+[% loop.count %]/[% loop.size %].  Bib ID# [% item.id %]
+[% IF item.isbn %]ISBN: [% item.isbn _ "\n" %][% END -%]
+[% IF item.issn %]ISSN: [% item.issn _ "\n" %][% END -%]
+[% IF item.upc  %]UPC:  [% item.upc _ "\n" %][% END -%]
+Title: [% item.title %]
+[% IF item.author %]Author: [% item.author _ "\n" %][% END -%]
+Publication Info: [% item.publisher %] [% item.pubdate %]
+Item Type: [% item.item_type %]
+[% IF user_data.0.type == 'full' && item.holdings.size == 0 %]
+ * No items for this record at the selected location
+[% END %]
+[% FOR cp IN item.holdings -%]
+ * Library: [% cp.circ_lib %]
+   Location: [% cp.location %]
+   Call Number: [% cp.prefix _ ' ' _ cp.callnumber _ ' ' _ cp.suffix %]
+[% IF cp.parts %]   Parts: [% cp.parts _ "\n" %][% END -%]
+   Status: [% cp.status_label %]
+   Barcode: [% cp.barcode %]
 
-    author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
-    item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
-    publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
-    pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
-    isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
-    issn = bibxml.findnodes('//*[@tag="022"]/*[@code="a"]').textContent;
-    upc = bibxml.findnodes('//*[@tag="024"]/*[@code="a"]').textContent;
-%]
-
-[% loop.count %]/[% loop.size %].  Bib ID# [% bre_id %] 
-[% IF isbn %]ISBN: [% isbn _ "\n" %][% END -%]
-[% IF issn %]ISSN: [% issn _ "\n" %][% END -%]
-[% IF upc  %]UPC:  [% upc _ "\n" %] [% END -%]
-Title: [% title %]
-Author: [% author %]
-Publication Info: [% publisher %] [% pubdate %]
-Item Type: [% item_type %]
+[% END %]
 
 [% END %]
 [% END %]
 <div>
     <style> li { padding: 8px; margin 5px; }</style>
     <ol>
-    [% FOR cbreb IN target %]
-    [% FOR item IN cbreb.items;
-        bre_id = item.target_biblio_record_entry;
-
-        bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'});
-        title = '';
-        FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
-            title = title _ part.textContent;
-        END;
+    [% FOR cbreb IN target;
 
-        author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
-        item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
-        publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
-        pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
-        isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
-        %]
+    flesh_list = '{mra';
+    IF user_data.0.type == 'full';
+        flesh_list = flesh_list _ ',holdings_xml,acp';
+        IF params.holdings_limit;
+            flimit = 'acn=>' _ params.holdings_limit _ ',acp=>' _ params.holdings_limit;
+        END;
+    END;
+    flesh_list = flesh_list _ '}';
 
+    item_list = helpers.sort_bucket_unapi_bre(cbreb.items,{flesh => flesh_list, site => user_data.0.context_org, flesh_limit => flimit}, user_data.0.sort_by, user_data.0.sort_dir);
+    FOR item IN item_list %]
         <li>
-            Bib ID# [% bre_id %] ISBN: [% isbn %]<br />
-            Title: [% title %]<br />
-            Author: [% author %]<br />
-            Publication Info: [% publisher %] [% pubdate %]<br/>
-            Item Type: [% item_type %]
+            Bib ID# [% item.id %]<br />
+            [% IF item.isbn %]ISBN: [% item.isbn %]<br />[% END %]
+            [% IF item.issn %]ISSN: [% item.issn %]<br />[% END %]
+            [% IF item.upc  %]UPC:  [% item.upc %]<br />[% END %]
+            Title: [% item.title %]<br />
+[% IF item.author %]            Author: [% item.author %]<br />[% END -%]
+            Publication Info: [% item.publisher %] [% item.pubdate %]<br/>
+            Item Type: [% item.item_type %]
+            <ul>
+            [% IF user_data.0.type == 'full' && item.holdings.size == 0 %]
+                <li>No items for this record at the selected location</li>
+            [% END %]
+            [% FOR cp IN item.holdings -%]
+                <li>
+                    Library: [% cp.circ_lib %]<br/>
+                    Location: [% cp.location %]<br/>
+                    Call Number: [% cp.prefix _ ' ' _ cp.callnumber _ ' ' _ cp.suffix %]<br/>
+                    [% IF cp.parts %]Parts: [% cp.parts %]<br/>[% END %]
+                    Status: [% cp.status_label %]<br/>
+                    Barcode: [% cp.barcode %]
+                </li>
+            [% END %]
+            </ul>
         </li>
     [% END %]
     [% END %]
@@ -19813,6 +19835,35 @@ VALUES (
     )
 );
 
+INSERT into config.org_unit_setting_type
+( name, grp, label, description, datatype, fm_class ) VALUES
+( 'opac.email_record.allow_without_login', 'opac',
+    oils_i18n_gettext('opac.email_record.allow_without_login',
+        'Allow record emailing without login',
+        'coust', 'label'),
+    oils_i18n_gettext('opac.email_record.allow_without_login',
+        'Instead of forcing a patron to log in in order to email the details of a record, just challenge them with a simple catpcha.',
+        'coust', 'description'),
+    'bool', null)
+;
+
+INSERT INTO action_trigger.event_def_group (id, owner, hook, name)
+    VALUES (1, 1, 'biblio.format.record_entry.print','Print Record(s)');
+
+INSERT INTO action_trigger.event_def_group_member (grp, name, event_def)
+    SELECT 1, 'Brief', id FROM action_trigger.event_definition WHERE hook = 'biblio.format.record_entry.print';
+
+INSERT INTO action_trigger.event_def_group_member (grp, name, holdings, event_def)
+    SELECT 1, 'Full', TRUE, id FROM action_trigger.event_definition WHERE hook = 'biblio.format.record_entry.print';
+
+INSERT INTO action_trigger.event_def_group (id, owner, hook, name)
+    VALUES (2,1,'biblio.format.record_entry.email','Email Record(s)');
+
+INSERT INTO action_trigger.event_def_group_member (grp, name, event_def)
+    SELECT 2, 'Brief', id FROM action_trigger.event_definition WHERE hook = 'biblio.format.record_entry.email';
+
+INSERT INTO action_trigger.event_def_group_member (grp, name, holdings, event_def)
+    SELECT 2, 'Full', TRUE, id FROM action_trigger.event_definition WHERE hook = 'biblio.format.record_entry.email';
 
 INSERT into config.org_unit_setting_type (name, label, description, datatype) 
 VALUES ( 
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.AT-def-groups.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.AT-def-groups.sql
new file mode 100644 (file)
index 0000000..573aabb
--- /dev/null
@@ -0,0 +1,160 @@
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT into config.org_unit_setting_type
+( name, grp, label, description, datatype, fm_class ) VALUES
+( 'opac.email_record.allow_without_login', 'opac',
+    oils_i18n_gettext('opac.email_record.allow_without_login',
+        'Allow record emailing without login',
+        'coust', 'label'),
+    oils_i18n_gettext('opac.email_record.allow_without_login',
+        'Instead of forcing a patron to log in in order to email the details of a record, just challenge them with a simple catpcha.',
+        'coust', 'description'),
+    'bool', null)
+;
+
+CREATE TABLE action_trigger.event_def_group (
+    id      SERIAL  PRIMARY KEY,
+    owner   INT     NOT NULL REFERENCES actor.org_unit (id)
+                        ON DELETE RESTRICT ON UPDATE CASCADE
+                        DEFERRABLE INITIALLY DEFERRED,
+    hook    TEXT    NOT NULL REFERENCES action_trigger.hook (key)
+                        ON DELETE RESTRICT ON UPDATE CASCADE
+                        DEFERRABLE INITIALLY DEFERRED,
+    active  BOOL    NOT NULL DEFAULT TRUE,
+    name    TEXT    NOT NULL
+);
+SELECT SETVAL('action_trigger.event_def_group_id_seq'::TEXT, 100, TRUE);
+
+CREATE TABLE action_trigger.event_def_group_member (
+    id          SERIAL  PRIMARY KEY,
+    grp         INT     NOT NULL REFERENCES action_trigger.event_def_group (id)
+                            ON DELETE CASCADE ON UPDATE CASCADE
+                            DEFERRABLE INITIALLY DEFERRED,
+    event_def   INT     NOT NULL REFERENCES action_trigger.event_definition (id)
+                            ON DELETE RESTRICT ON UPDATE CASCADE
+                            DEFERRABLE INITIALLY DEFERRED,
+    sortable    BOOL    NOT NULL DEFAULT TRUE,
+    holdings    BOOL    NOT NULL DEFAULT FALSE,
+    external    BOOL    NOT NULL DEFAULT FALSE,
+    name        TEXT    NOT NULL
+);
+
+INSERT INTO action_trigger.event_def_group (id, owner, hook, name)
+    VALUES (1, 1, 'biblio.format.record_entry.print','Print Record(s)');
+
+INSERT INTO action_trigger.event_def_group_member (grp, name, event_def)
+    SELECT 1, 'Brief', id FROM action_trigger.event_definition WHERE hook = 'biblio.format.record_entry.print';
+
+INSERT INTO action_trigger.event_def_group_member (grp, name, holdings, event_def)
+    SELECT 1, 'Full', TRUE, id FROM action_trigger.event_definition WHERE hook = 'biblio.format.record_entry.print';
+
+INSERT INTO action_trigger.event_def_group (id, owner, hook, name)
+    VALUES (2,1,'biblio.format.record_entry.email','Email Record(s)');
+
+INSERT INTO action_trigger.event_def_group_member (grp, name, event_def)
+    SELECT 2, 'Brief', id FROM action_trigger.event_definition WHERE hook = 'biblio.format.record_entry.email';
+
+INSERT INTO action_trigger.event_def_group_member (grp, name, holdings, event_def)
+    SELECT 2, 'Full', TRUE, id FROM action_trigger.event_definition WHERE hook = 'biblio.format.record_entry.email';
+
+UPDATE action_trigger.event_definition SET template = $$
+[%- USE date -%]
+[%- SET user = target.0.owner -%]
+To: [%- params.recipient_email || user_data.0.email || user.email %]
+From: [%- params.sender_email || default_sender %]
+Date: [%- date.format(date.now, '%a, %d %b %Y %T -0000', gmt => 1) %]
+Subject: [%- user_data.0.subject || 'Bibliographic Records' %]
+Auto-Submitted: auto-generated
+
+[% FOR cbreb IN target;
+
+    flesh_list = '{mra';
+    IF user_data.0.type == 'full';
+        flesh_list = flesh_list _ ',holdings_xml,acp';
+        IF params.holdings_limit;
+            flimit = 'acn=>' _ params.holdings_limit _ ',acp=>' _ params.holdings_limit;
+        END;
+    END;
+    flesh_list = flesh_list _ '}';
+
+    item_list = helpers.sort_bucket_unapi_bre(cbreb.items,{flesh => flesh_list, site => user_data.0.context_org, flesh_limit => flimit}, user_data.0.sort_by, user_data.0.sort_dir);
+
+FOR item IN item_list %]
+
+[% loop.count %]/[% loop.size %].  Bib ID# [% item.id %]
+[% IF item.isbn %]ISBN: [% item.isbn _ "\n" %][% END -%]
+[% IF item.issn %]ISSN: [% item.issn _ "\n" %][% END -%]
+[% IF item.upc  %]UPC:  [% item.upc _ "\n" %][% END -%]
+Title: [% item.title %]
+[% IF item.author %]Author: [% item.author _ "\n" %][% END -%]
+Publication Info: [% item.publisher %] [% item.pubdate %]
+Item Type: [% item.item_type %]
+[% IF user_data.0.type == 'full' && item.holdings.size == 0 %]
+ * No items for this record at the selected location
+[% END %]
+[% FOR cp IN item.holdings -%]
+ * Library: [% cp.circ_lib %]
+   Location: [% cp.location %]
+   Call Number: [% cp.prefix _ ' ' _ cp.callnumber _ ' ' _ cp.suffix %]
+[% IF cp.parts %]   Parts: [% cp.parts _ "\n" %][% END -%]
+   Status: [% cp.status_label %]
+   Barcode: [% cp.barcode %]
+[% END %]
+
+[% END %]
+[% END %]
+$$ WHERE hook = 'biblio.format.record_entry.email';
+
+UPDATE action_trigger.event_definition SET template = $$
+<div>
+    <style> li { padding: 8px; margin 5px; }</style>
+    <ol>
+    [% FOR cbreb IN target;
+
+    flesh_list = '{mra';
+    IF user_data.0.type == 'full';
+        flesh_list = flesh_list _ ',holdings_xml,acp';
+        IF params.holdings_limit;
+            flimit = 'acn=>' _ params.holdings_limit _ ',acp=>' _ params.holdings_limit;
+        END;
+    END;
+    flesh_list = flesh_list _ '}';
+
+    item_list = helpers.sort_bucket_unapi_bre(cbreb.items,{flesh => flesh_list, site => user_data.0.context_org, flesh_limit => flimit}, user_data.0.sort_by, user_data.0.sort_dir);
+    FOR item IN item_list %]
+        <li>
+            Bib ID# [% item.id %]<br />
+            [% IF item.isbn %]ISBN: [% item.isbn %]<br />[% END %]
+            [% IF item.issn %]ISSN: [% item.issn %]<br />[% END %]
+            [% IF item.upc  %]UPC:  [% item.upc %]<br />[% END %]
+            Title: [% item.title %]<br />
+[% IF item.author %]            Author: [% item.author %]<br />[% END -%]
+            Publication Info: [% item.publisher %] [% item.pubdate %]<br/>
+            Item Type: [% item.item_type %]
+            <ul>
+            [% IF user_data.0.type == 'full' && item.holdings.size == 0 %]
+                <li>No items for this record at the selected location</li>
+            [% END %]
+            [% FOR cp IN item.holdings -%]
+                <li>
+                    Library: [% cp.circ_lib %]<br/>
+                    Location: [% cp.location %]<br/>
+                    Call Number: [% cp.prefix _ ' ' _ cp.callnumber _ ' ' _ cp.suffix %]<br/>
+                    [% IF cp.parts %]Parts: [% cp.parts %]<br/>[% END %]
+                    Status: [% cp.status_label %]<br/>
+                    Barcode: [% cp.barcode %]
+                </li>
+            [% END %]
+            </ul>
+        </li>
+    [% END %]
+    [% END %]
+    </ol>
+</div>
+$$ WHERE hook = 'biblio.format.record_entry.print';
+
+COMMIT;
+
index ed0b546..afe60fa 100644 (file)
@@ -13,6 +13,7 @@
             </form>
         </div>
         <form action="[% mkurl(ctx.opac_root _ '/mylist/move') %]" method="post">
+        <input type="hidden" name="anonsort" value="[% CGI.param('anonsort') %]" />
         <input type="hidden" name="orig_referrer" value="[% CGI.referer | html %]" />
         <input type="hidden" name="redirect_to" value="[% mkurl('', {}, ['list_none_selected', 'cart_none_selected']) %]" />
         <div class="bbag-action" style="clear:both;">
index 8451413..9a97f36 100644 (file)
@@ -4,8 +4,8 @@
       <option value="">[% l('-- Basket Actions --') %]</option>
       <option value="[% mkurl(ctx.opac_root _ '/mylist', {}) %]">[% l('View Basket') %]</option>
       <option value="[% mkurl(ctx.opac_root _ '/mylist/move', { action => 'place_hold', entire_list => 1 }) %]">[% l('Place Holds') %]</option>
-      <option value="[% mkurl(ctx.opac_root _ '/mylist/print', {}) %]">[% l('Print Title Details') %]</option>
-      <option value="[% mkurl(ctx.opac_root _ '/mylist/email', {}) %]">[% l('Email Title Details') %]</option>
+      <option value="[% mkurl(ctx.opac_root _ '/mylist/move', { action => 'print', entire_list => 1}) %]">[% l('Print Title Details') %]</option>
+      <option value="[% mkurl(ctx.opac_root _ '/mylist/move', { action => 'email', entire_list => 1}) %]">[% l('Email Title Details') %]</option>
       [% IF !ctx.is_browser_staff %]
       <option value="[% mkurl(ctx.opac_root _ '/myopac/lists', { move_cart_by_default => 1, from_basket => 1 }) %]">[% l('Add Basket to Saved List') %]</option>
       [% END %]
index fb6f84a..be248ab 100644 (file)
             </div>
                 <div class="rdetail_aux_utils">
                     <img src="[% ctx.media_prefix %]/images/clipboard.png[% ctx.cache_key %]" alt="[% l('Print / Email Actions Image') %]" />
-                    <a href="[% mkurl(ctx.opac_root _ '/record/print/' _ ctx.bre_id) %]" class="no-dec" rel="nofollow" vocab="">[% l('Print') %]</a> /
-                    <a href="[% mkurl(ctx.opac_root _ '/record/email/' _ ctx.bre_id) %]" class="no-dec" rel="nofollow" vocab="">[% l('Email') %]</a>
+                    <a href="[% mkurl(ctx.opac_root _ '/record/print_preview/' _ ctx.bre_id) %]" class="no-dec" rel="nofollow" vocab="">[% l('Print') %]</a> /
+                    <a href="[% mkurl(ctx.opac_root _ '/record/email_preview/' _ ctx.bre_id) %]" class="no-dec" rel="nofollow" vocab="">[% l('Email') %]</a>
                 </div>
                 [%- IF ctx.refworks.enabled == 'true' %]
                     [%- INCLUDE 'opac/parts/record/refworks.tt2' %]
index 88cf88c..f086b99 100644 (file)
@@ -8,11 +8,10 @@
     <div id="content-wrapper" class="content-wrapper-record-page">
         <div id='main-content'>
             <br/>
-            [% IF ctx.user.email %]
-            <h2 class='success'>[% l('Your email has been queued for delivery to [_1]', ctx.user.email ) %]</h2>
-            [% ELSE %]
+            <h2 class='success'>[% l('Your email has been queued for delivery to [_1]', ctx.email ) %]</h2>
+            [% IF ctx.user  && !ctx.user.email %]
             <h2 class='error'>
-                [% here_link_text = "<a href=\"${ctx.opac_root}/myopac/update_email?return_to_referer=1\">";
+                [% here_link_text = "<a href=\"${ctx.opac_root}/myopac/update_email\">";
                    here_link_text = here_link_text _ l("here");
                    here_link_text = here_link_text _ "</a>";
                    l('Your account does not currently have an email address set. Set your email address [_1]', here_link_text) %]
diff --git a/Open-ILS/src/templates/opac/record/email_preview.tt2 b/Open-ILS/src/templates/opac/record/email_preview.tt2
new file mode 100644 (file)
index 0000000..9698091
--- /dev/null
@@ -0,0 +1,111 @@
+[%- PROCESS "opac/parts/header.tt2";
+    WRAPPER "opac/parts/base.tt2";
+    INCLUDE "opac/parts/topnav.tt2";
+    ctx.page_title = l("Email Record Preview");
+    PROCESS "opac/parts/org_selector.tt2";
+-%]
+    <h2 class="sr-only">[% l('Email Record Preview') %]</h2>
+    [% INCLUDE "opac/parts/searchbar.tt2" %]
+    <br class="clear-both" />
+    <div id="content-wrapper" class="content-wrapper-record-page">
+
+    <form id="previewForm" method="POST" action="[% mkurl('',{},['locg','format','sort','sort_dir','context_org','bre_id','is_list']) %]">
+        <input type="hidden" name="old_event" value="[% ctx.preview_record.id %]"/>
+        <input type="hidden" name="bre_id" value="[% ctx.bre_id %]"/>
+        <input type="hidden" name="locg" value="[% ctx.selected_print_email_loc %]"/>
+        <input type="hidden" name="is_list" value="[% ctx.is_list %]"/>
+        <input type="hidden" name="redirect_to" value="[% ctx.redirect_to | html %]"/>
+
+        <div class="searchbar">
+            <label for="email">[% l('Email Address: ') %]
+                <input id="email" type="text" name="email" value="[% ctx.email || ctx.user.email %]"/>
+            </label>
+        </div>
+
+        <div class="searchbar">
+            <label for="subject">[% l('Custom Email Subject: ') %]
+                <input id="subject" type="text" name="subject" value="[% ctx.subject || '' %]"/>
+            </label>
+        </div>
+
+        <div class="searchbar">
+            <label for="formats">[% l('Format: ') %]
+                <select id="formats" name="format">
+                    [% FOR f IN ctx.formats %]
+                        [% IF !ctx.format_obj; ctx.format_obj = f; END %]
+                        <option [% IF f.id == ctx.format; ctx.format_obj = f; 'selected="selected"'; END %] value="[% f.id %]">[% f.name | html %]</option>
+                    [% END %]
+                </select>
+            </label>
+        </div>
+
+        [% IF ctx.is_list == '1' %]
+        <div class="searchbar">
+            <label for="sortby">[% l('Sort by: ') %]
+                <select id="sortby" name="sort">
+                    <option [% IF ctx.sort == 'author'; 'selected="selected"'; END %] value="author">[% l('Author') %]</option>
+                    <option [% IF ctx.sort == 'title'; 'selected="selected"'; END %] value="title">[% l('Title') %]</option>
+                    <option [% IF ctx.sort == 'pubdate'; 'selected="selected"'; END %] value="pubdate">[% l('Publication Date') %]</option>
+                </select>
+                <select id="sort_dir" name="sort_dir">
+                    <option [% IF ctx.sort_dir == 'ascending'; 'selected="selected"'; END %] value="ascending">[% l('Ascending') %]</option>
+                    <option [% IF ctx.sort_dir == 'descending'; 'selected="selected"'; END %] value="descending">[% l('Descending') %]</option>
+                </select>
+            </label>
+        </div>
+        [% END %]
+
+        [% IF ctx.format_obj.holdings == 't' %]
+        <div class="searchbar">
+            <label for="context_org">[% l('Holdings Library: ') %]
+                [% INCLUDE build_org_selector id='context_org' name='context_org' value=ctx.selected_print_email_loc %]</br>
+            </label>
+        </div>
+        [% END %]
+
+        <br/>
+        <input type="submit" class="opac-button" value="[% l("Update") %]" />
+        <br/>
+    </form>
+
+        <hr/>
+
+    <form id="emailForm" method="POST" action="[% mkurl('../email/' _ ctx.preview_record.id,{},['locg','old_event','email','format','sort','bre_id','is_list']) %]">
+        <input type="hidden" name="old_event" value="[% ctx.preview_record.id %]"/>
+        <input type="hidden" name="bre_id" value="[% ctx.bre_id %]"/>
+        <input type="hidden" name="locg" value="[% ctx.selected_print_email_loc %]"/>
+        <input type="hidden" name="is_list" value="[% ctx.is_list %]"/>
+        <input type="hidden" name="redirect_to" value="[% ctx.redirect_to | html %]"/>
+        <input type="hidden" name="email" value="[% ctx.email || ctx.user.email %]"/>
+
+        [% IF ctx.preview_record.template_output %]
+            [% IF ctx.captcha.key %]
+            <div class="searchbar">
+                <p>[% l('Please prove you are not a robot by answering the following addition problem:') %]</p>
+                <p>[% ctx.captcha.left %] + [% ctx.captcha.right %] = <input type="text" name="capanswer"/></p>
+                <input type="hidden" name="capkey" value="[% ctx.captcha.key %]"/>
+            </div>
+            [% END %]
+            <input type="submit" class="opac-button" value="[% l("Email Now") %]"/> |
+            <a class="opac-button" href="[% ctx.redirect_to | html %]">[% l("Return") %]</a>
+            <br/>
+            <pre>[% ctx.preview_record.template_output.data %]</pre>
+        [% ELSE %]
+            <div class="print-error">
+                [% l(
+                    'Error previwing record: [_1]',
+                        (ctx.preview_record.textcode ? ctx.preview_record.textcode _ ' / ' _ ctx.preview_record.desc : 0) ||
+                        ctx.preview_record.error_output.data ||
+                        l('No record data returned from server')
+                    ) | html %]
+            </div>
+            <hr />
+            <div>
+                <a class="opac-button" href="[% ctx.redirect_to | html %]">[% l("Return") %]</a>
+            </div>
+        [% END %]
+        <br class="clear-both" />
+    </form>
+    </div>
+[%- END %]
index 26c3543..c1b55a4 100644 (file)
@@ -6,18 +6,21 @@
         <style type="text/css" media="print">.noprint {display: none}</style>
     </head>
     <body onload="if (document.getElementById('printable-record')) window.print();">
-        [% IF ctx.printable_record.template_output %]
-        <tt id="printable-record">
-            [% ctx.printable_record.template_output.data %]
-        </tt>
+        <div class='noprint'>
+            [% IF ctx.redirect_to %]
+            <p>[ <a href="[% ctx.redirect_to | html %]">[% l("Return") %]</a> ] </p>
+            [% ELSE %]
+            <p>[ <a href="[% mkurl(ctx.opac_root  _ '/record/' _ ctx.bre_id) %]">[% l("Back to Record") %]</a> ]</p>
+            [% END %]
+            <hr />
+        </div>
+        [% IF ctx.print_data %]
+        <div id="printable-record">
+            [% ctx.print_data %]
+        </div>
         [% ELSE %]
         <div class="noprint print-error">
-            [% l(
-                'Error printing record: [_1]',
-                    (ctx.printable_record.textcode ? ctx.printable_record.textcode _ ' / ' _ ctx.printable_record.desc : 0) ||
-                    ctx.printable_record.error_output.data ||
-                    l('No record data returned from server')
-                ) | html %]
+            [% l( 'Error printing record: [_1]', l('No record data returned from server')) | html %]
         </div>
         [% END %]
         <div class='noprint'>
diff --git a/Open-ILS/src/templates/opac/record/print_preview.tt2 b/Open-ILS/src/templates/opac/record/print_preview.tt2
new file mode 100644 (file)
index 0000000..8fc5839
--- /dev/null
@@ -0,0 +1,82 @@
+[%- PROCESS "opac/parts/header.tt2";
+    WRAPPER "opac/parts/base.tt2";
+    INCLUDE "opac/parts/topnav.tt2";
+    ctx.page_title = l("Print Record Preview");
+    PROCESS "opac/parts/org_selector.tt2";
+-%]
+    <h2 class="sr-only">[% l('Print Record Preview') %]</h2>
+    [% INCLUDE "opac/parts/searchbar.tt2" %]
+    <br class="clear-both" />
+    <div id="content-wrapper" class="content-wrapper-record-page">
+
+    <form id="previewForm" action="[% mkurl('',{},['locg','format','sort','sort_by','context_org','bre_id','is_list']) %]">
+        <input type="hidden" name="old_event" value="[% ctx.preview_record.id %]"/>
+        <input type="hidden" name="bre_id" value="[% ctx.bre_id %]"/>
+        <input type="hidden" name="locg" value="[% ctx.selected_print_email_loc %]"/>
+        <input type="hidden" name="is_list" value="[% ctx.is_list %]"/>
+        <input type="hidden" name="redirect_to" value="[% ctx.redirect_to | html %]"/>
+
+        <div class="searchbar">
+            <label for="formats">[% l('Format: ') %]
+                <select id="formats" name="format">
+                    [% FOR f IN ctx.formats %]
+                        [% IF !ctx.format_obj; ctx.format_obj = f; END %]
+                        <option [% IF f.id == ctx.format; ctx.format_obj = f; 'selected="selected"'; END %] value="[% f.id %]">[% f.name | html %]</option>
+                    [% END %]
+                </select>
+            </label>
+        </div>
+
+        [% IF ctx.is_list == '1' %]
+        <div class="searchbar">
+            <label for="sortby">[% l('Sort by: ') %]
+                <select id="sortby" name="sort">
+                    <option [% IF ctx.sort == 'author'; 'selected="selected"'; END %] value="author">[% l('Author') %]</option>
+                    <option [% IF ctx.sort == 'title'; 'selected="selected"'; END %] value="title">[% l('Title') %]</option>
+                    <option [% IF ctx.sort == 'pubdate'; 'selected="selected"'; END %] value="pubdate">[% l('Publication Date') %]</option>
+                </select>
+                <select id="sort_dir" name="sort_dir">
+                    <option [% IF ctx.sort_dir == 'ascending'; 'selected="selected"'; END %] value="ascending">[% l('Ascending') %]</option>
+                    <option [% IF ctx.sort_dir == 'descending'; 'selected="selected"'; END %] value="descending">[% l('Descending') %]</option>
+                </select>
+            </label>
+        </div>
+        [% END %]
+
+        [% IF ctx.format_obj.holdings == 't' %]
+        <div class="searchbar">
+            <label for="context_org">[% l('Holdings Library: ') %]
+                [% INCLUDE build_org_selector id='context_org' name='context_org' value=ctx.selected_print_email_loc %]</br>
+            </label>
+        </div>
+        [% END %]
+
+        <br/>
+
+        <input type="submit" class="opac-button" value="[% l("Update") %]" />
+        <br/>
+        <hr/>
+       
+        [% IF ctx.preview_record.template_output %]
+            <a class="opac-button" href="[% mkurl('../print/' _ ctx.preview_record.id, {redirect_to => ctx.redirect_to}) %]">[% l("Print Now") %]</a> |
+            <a class="opac-button" href="[% ctx.redirect_to | html %]">[% l("Return") %]</a>
+            <br/>
+            <div>[% ctx.preview_record.template_output.data %]</div>
+        [% ELSE %]
+            <div class="print-error">
+                [% l(
+                    'Error previwing record: [_1]',
+                        (ctx.preview_record.textcode ? ctx.preview_record.textcode _ ' / ' _ ctx.preview_record.desc : 0) ||
+                        ctx.preview_record.error_output.data ||
+                        l('No record data returned from server')
+                    ) | html %]
+            </div>
+            <hr />
+            <div>
+                <a class="opac-button" href="[% ctx.redirect_to | html %]">[% l("Return") %]</a>
+            </div>
+        [% END %]
+        <br class="clear-both" />
+    </form>
+    </div>
+[%- END %]
index 1377308..c85b06b 100644 (file)
@@ -27,6 +27,8 @@
     ,[ l('Circulation Modifiers'), "./admin/server/config/circ_modifier" ]
     ,[ l('Circulation Recurring Fine Rules'), "./admin/server/config/rule_recurring_fine" ]
     ,[ l('Custom Org Unit Trees'), "./admin/server/actor/org_unit_custom_tree" ]
+    ,[ l('Event Definition Groups'), "/eg2/staff/admin/server/action_trigger/event_def_group" ]
+    ,[ l('Event Definition Group Members'), "/eg2/staff/admin/server/action_trigger/event_def_group_member" ]
     ,[ l('Floating Groups'), "./admin/server/config/floating_groups" ]
     ,[ l('Global Flags'), "./admin/server/config/global_flag" ]
     ,[ l('Hard Due Date Changes'), "./admin/server/config/hard_due_date" ]