Proof of concept Template-Toolkit OPAC
authorBill Erickson <berick@esilibrary.com>
Tue, 18 Jan 2011 14:59:16 +0000 (09:59 -0500)
committerBill Erickson <berick@esilibrary.com>
Tue, 18 Jan 2011 14:59:16 +0000 (09:59 -0500)
13 files changed:
Open-ILS/src/perlmods/OpenILS/WWW/EGCatLoader.pm [new file with mode: 0644]
Open-ILS/src/perlmods/OpenILS/WWW/EGWeb.pm
Open-ILS/web/templates/default/opac/base.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/opac/common.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/opac/home.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/opac/login.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/opac/marc_attrs.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/opac/myopac.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/opac/place_hold.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/opac/rdetail.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/opac/records.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/opac/results.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/opac/welcome.tt2 [new file with mode: 0644]

diff --git a/Open-ILS/src/perlmods/OpenILS/WWW/EGCatLoader.pm b/Open-ILS/src/perlmods/OpenILS/WWW/EGCatLoader.pm
new file mode 100644 (file)
index 0000000..18afc95
--- /dev/null
@@ -0,0 +1,405 @@
+package OpenILS::WWW::EGCatLoader;
+use strict; use warnings;
+use CGI;
+use XML::LibXML;
+use Digest::MD5 qw(md5_hex);
+use Apache2::Const -compile => qw(OK DECLINED HTTP_INTERNAL_SERVER_ERROR REDIRECT);
+use OpenSRF::AppSession;
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Application::AppUtils;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+my $U = 'OpenILS::Application::AppUtils';
+
+sub new {
+    my($class, $apache, $ctx) = @_;
+
+    my $self = bless({}, ref($class) || $class);
+
+    $self->apache($apache);
+    $self->ctx($ctx);
+    $self->cgi(CGI->new);
+
+    OpenILS::Utils::CStoreEditor->init; # just in case
+    $self->editor(new_editor());
+
+    return $self;
+}
+
+
+# current Apache2::RequestRec;
+sub apache {
+    my($self, $apache) = @_;
+    $self->{apache} = $apache if $apache;
+    return $self->{apache};
+}
+
+# runtime context
+sub ctx {
+    my($self, $ctx) = @_;
+    $self->{ctx} = $ctx if $ctx;
+    return $self->{ctx};
+}
+
+# cstore editor
+sub editor {
+    my($self, $editor) = @_;
+    $self->{editor} = $editor if $editor;
+    return $self->{editor};
+}
+
+# CGI handle
+sub cgi {
+    my($self, $cgi) = @_;
+    $self->{cgi} = $cgi if $cgi;
+    return $self->{cgi};
+}
+
+
+# load common data, then load page data
+sub load {
+    my $self = shift;
+
+    my $path = $self->apache->path_info;
+    $self->load_helpers;
+
+    my $stat = $self->load_common;
+    return $stat unless $stat == Apache2::Const::OK;
+
+    return $self->load_home if $path =~ /opac\/home/;
+    return $self->load_login if $path =~ /opac\/login/;
+    return $self->load_logout if $path =~ /opac\/logout/;
+    return $self->load_rresults if $path =~ /opac\/results/;
+    return $self->load_rdetail if $path =~ /opac\/rdetail/;
+    return $self->load_myopac if $path =~ /opac\/myopac/;
+    return $self->load_place_hold if $path =~ /opac\/place_hold/;
+
+    return Apache2::Const::OK;
+}
+
+# general purpose utility functions added to the environment
+# context additions: 
+#   find_org_unit : function(id) => aou object
+#   org_tree : function(id) => aou object, top of tree, fleshed
+my $cached_org_tree;
+my %org_unit_map;
+sub load_helpers {
+    my $self = shift;
+
+    # pull the org unit from the cached org tree
+    $self->ctx->{find_org_unit} = sub {
+        my $org_id = shift;
+        return undef unless defined $org_id;
+        return $org_unit_map{$org_id} if defined $org_unit_map{$org_id};
+        my $tree = shift || $self->ctx->{org_tree}->();
+        return $org_unit_map{$org_id} = $tree if $tree->id == $org_id;
+        for my $child (@{$tree->children}) {
+            my $node = $self->ctx->{find_org_unit}->($org_id, $child);
+            return $node if $node;
+        }
+        return undef;
+    };
+
+    $self->ctx->{org_tree} = sub {
+        unless($cached_org_tree) {
+            $cached_org_tree = $self->editor->search_actor_org_unit([
+                           {   parent_ou => undef},
+                           {   flesh            => -1,
+                                   flesh_fields    => {aou =>  ['children', 'ou_type']},
+                                   order_by        => {aou => 'name'}
+                           }
+                   ])->[0];
+        }
+        return $cached_org_tree;
+    }
+}
+
+# context additions: 
+#   authtoken : string
+#   user : au object
+#   user_status : hash of user circ numbers
+sub load_common {
+    my $self = shift;
+
+    my $e = $self->editor;
+    my $ctx = $self->ctx;
+
+    if($e->authtoken($self->cgi->cookie('ses'))) {
+
+        if($e->checkauth) {
+
+            $ctx->{authtoken} = $e->authtoken;
+            $ctx->{user} = $e->requestor;
+            $ctx->{user_stats} = $U->simplereq(
+                'open-ils.actor', 
+                'open-ils.actor.user.opac.vital_stats', 
+                $e->authtoken, $e->requestor->id);
+
+        } else {
+
+            return $self->load_logout;
+        }
+    }
+
+    return Apache2::Const::OK;
+}
+
+sub load_home {
+    my $self = shift;
+    $self->ctx->{page} = 'home';
+    return Apache2::Const::OK;
+}
+
+
+sub load_login {
+    my $self = shift;
+    my $cgi = $self->cgi;
+
+    $self->ctx->{page} = 'login';
+
+    my $username = $cgi->param('username');
+    my $password = $cgi->param('password');
+
+    return Apache2::Const::OK unless $username and $password;
+
+       my $seed = $U->simplereq(
+        'open-ils.auth', 
+               'open-ils.auth.authenticate.init',
+        $username);
+
+       my $response = $U->simplereq(
+        'open-ils.auth', 
+               'open-ils.auth.authenticate.complete', 
+               {       username => $username, 
+                       password => md5_hex($seed . md5_hex($password)), 
+                       type => 'opac' 
+        }
+    );
+
+    # XXX check event, redirect as necessary
+
+    my $home = $self->apache->unparsed_uri;
+    $home =~ s/\/login/\/home/;
+
+    $self->apache->print(
+        $cgi->redirect(
+            -url => $cgi->param('origin') || $home,
+            -cookie => $cgi->cookie(
+                -name => 'ses',
+                -path => '/',
+                -value => $response->{payload}->{authtoken},
+                -expires => CORE::time + $response->{payload}->{authtime}
+            )
+        )
+    );
+
+    return Apache2::Const::REDIRECT;
+}
+
+sub load_logout {
+    my $self = shift;
+
+    my $path = $self->apache->uri;
+    $path =~ s/(\/[^\/]+$)/\/home/;
+    my $url = 'http://' . $self->apache->hostname . "$path";
+
+    $self->apache->print(
+        $self->cgi->redirect(
+            -url => $url,
+            -cookie => $self->cgi->cookie(
+                -name => 'ses',
+                -path => '/',
+                -value => '',
+                -expires => '-1h'
+            )
+        )
+    );
+
+    return Apache2::Const::REDIRECT;
+}
+
+# context additions: 
+#   page_size
+#   hit_count
+#   records : list of bre's and copy-count objects
+my $cmf_cache;
+my $cmc_cache;
+sub load_rresults {
+    my $self = shift;
+
+    my $cgi = $self->cgi;
+    my $ctx = $self->ctx;
+    my $e = $self->editor;
+
+    $ctx->{page} = 'rresult';
+
+    unless($cmf_cache) {
+        $cmf_cache = $e->search_config_metabib_field({id => {'!=' => undef}});
+        $cmc_cache = $e->search_config_metabib_class({name => {'!=' => undef}});
+        $ctx->{metabib_field} = $cmf_cache;
+        $ctx->{metabib_class} = $cmc_cache;
+    }
+
+
+    my $page = $cgi->param('page') || 0;
+    my $query = $cgi->param('query');
+    my $limit = $cgi->param('limit') || 10; # XXX user settings
+
+    my $args = {limit => $limit, offset => $page * $limit}; 
+    my $results = $U->simplereq('open-ils.search',
+        'open-ils.search.biblio.multiclass.query.staff', $args, $query, 1);
+
+    my $search = OpenSRF::AppSession->create('open-ils.search');
+    my $facet_req = $search->request('open-ils.search.facet_cache.retrieve', $results->{facet_key}, 10);
+
+    my $rec_ids = [map { $_->[0] } @{$results->{ids}}];
+
+    $ctx->{page_size} = $limit;
+    $ctx->{hit_count} = $results->{count};
+    
+    my $cstore1 = OpenSRF::AppSession->create('open-ils.cstore');
+
+    my $bre_req = $cstore1->request(
+        'open-ils.cstore.direct.biblio.record_entry.search', {id => $rec_ids});
+
+    my @data;
+    while(my $resp = $bre_req->recv) {
+        my $bre = $resp->content; 
+
+        my $copy_counts = $e->json_query(
+            {from => ['asset.record_copy_count', 1, $bre->id, 0]})->[0];
+
+        push(@data,
+            {
+                bre => $bre,
+                marc_xml => XML::LibXML->new->parse_string($bre->marc),
+                copy_counts => $copy_counts
+            }
+        );
+    }
+
+    $cstore1->kill_me;
+
+    # shove recs into context in search results order
+    $ctx->{records} = [];
+    for my $rec_id (@$rec_ids) { 
+        push(
+            @{$ctx->{records}},
+            grep { $_->{bre}->id == $rec_id } @data
+        );
+    }
+
+    my $facets = $facet_req->gather(1);
+
+    for my $cmf_id (keys %$facets) {  # quick-n-dirty
+        my ($cmf) = grep { $_->id eq $cmf_id } @$cmf_cache;
+        $facets->{$cmf->label} = $facets->{$cmf_id};
+        delete $facets->{$cmf_id};
+    }
+    $ctx->{search_facets} = $facets;
+
+    return Apache2::Const::OK;
+}
+
+# context additions: 
+#   record : bre object
+sub load_rdetail {
+    my $self = shift;
+
+    $self->ctx->{record} = $self->editor->retrieve_biblio_record_entry([
+        $self->cgi->param('record'),
+        {
+            flesh => 2, 
+            flesh_fields => {
+                bre => ['call_numbers'],
+                acn => ['copies'] # limit, paging, etc.
+            }
+        }
+    ]);
+
+    $self->ctx->{marc_xml} = XML::LibXML->new->parse_string($self->ctx->{record}->marc);
+
+    return Apache2::Const::OK;
+}
+
+# context additions: 
+#   user : au object, fleshed
+sub load_myopac {
+    my $self = shift;
+
+    $self->ctx->{user} = $self->editor->retrieve_actor_user([
+        $self->ctx->{user}->id,
+        {
+            flesh => 1,
+            flesh_fields => {
+                au => ['card']
+                # ...
+            }
+        }
+    ]);
+
+    return Apache2::Const::OK;
+}
+
+# context additions: 
+sub load_place_hold {
+    my $self = shift;
+    my $ctx = $self->ctx;
+    my $e = $self->editor;
+
+    $ctx->{hold_target} = $self->cgi->param('hold_target');
+    $ctx->{hold_type} = $self->cgi->param('hold_type');
+    $ctx->{default_pickup_lib} = $e->requestor->home_ou; # XXX staff
+
+    if($ctx->{hold_type} eq 'T') {
+        $ctx->{record} = $e->retrieve_biblio_record_entry($ctx->{hold_target});
+    }
+    # ...
+
+    $ctx->{marc_xml} = XML::LibXML->new->parse_string($ctx->{record}->marc);
+
+    if(my $pickup_lib = $self->cgi->param('pickup_lib')) {
+
+        my $args = {
+            patronid => $e->requestor->id,
+            titleid => $ctx->{hold_target}, # XXX
+            pickup_lib => $pickup_lib,
+            depth => 0, # XXX
+        };
+
+        my $allowed = $U->simplereq(
+            'open-ils.circ',
+            'open-ils.circ.title_hold.is_possible',
+            $e->authtoken, $args
+        );
+
+        if($allowed->{success} == 1) {
+            my $hold = Fieldmapper::action::hold_request->new;
+
+            $hold->pickup_lib($pickup_lib);
+            $hold->requestor($e->requestor->id);
+            $hold->usr($e->requestor->id); # XXX staff
+            $hold->target($ctx->{hold_target});
+            $hold->hold_type($ctx->{hold_type});
+            # frozen, expired, etc..
+
+            my $stat = $U->simplereq(
+                'open-ils.circ',
+                'open-ils.circ.holds.create',
+                $e->authtoken, $hold
+            );
+
+            if($stat and $stat > 0) {
+                $ctx->{hold_success} = 1;
+            } else {
+                $ctx->{hold_failed} = 1; # XXX process the events, etc
+            }
+        }
+
+        # place the hold and deliver results
+    }
+
+    return Apache2::Const::OK;
+}
+
+1;
index 42eb6ff..50de099 100644 (file)
@@ -7,6 +7,7 @@ use File::stat;
 use Apache2::Const -compile => qw(OK DECLINED HTTP_INTERNAL_SERVER_ERROR);
 use Apache2::Log;
 use OpenSRF::EX qw(:try);
+use OpenILS::Utils::CStoreEditor;
 
 use constant OILS_HTTP_COOKIE_SKIN => 'oils:skin';
 use constant OILS_HTTP_COOKIE_THEME => 'oils:theme';
@@ -32,16 +33,21 @@ sub handler {
     check_web_config($r); # option to disable this
     my $ctx = load_context($r);
     my $base = $ctx->{base_path};
+
+    $r->content_type('text/html; encoding=utf8');
+    my $stat = run_context_loader($r, $ctx);
+    return $stat unless $stat == Apache2::Const::OK;
+
     my($template, $page_args, $as_xml) = find_template($r, $base, $ctx);
     return Apache2::Const::DECLINED unless $template;
 
     $template = $ctx->{skin} . "/$template";
     $ctx->{page_args} = $page_args;
-    $r->content_type('text/html; encoding=utf8');
 
     my $tt = Template->new({
         OUTPUT => ($as_xml) ?  sub { parse_as_xml($r, $ctx, @_); } : $r,
         INCLUDE_PATH => $ctx->{template_paths},
+        DEBUG => $ctx->{debug_template}
     });
 
     unless($tt->process($template, {ctx => $ctx})) {
@@ -52,6 +58,30 @@ sub handler {
     return Apache2::Const::OK;
 }
 
+
+sub run_context_loader {
+    my $r = shift;
+    my $ctx = shift;
+
+    my $stat = Apache2::Const::OK;
+
+    my $loader = $r->dir_config('OILSWebContextLoader');
+    return $stat unless $loader;
+
+    eval {
+        $loader->use;
+        $stat = $loader->new($r, $ctx)->load;
+    };
+
+    if($@) {
+        $r->log->error("Context Loader error: $@");
+        return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    $r->log->warn("context loader resulted in status $stat");
+    return $stat;
+}
+
 sub parse_as_xml {
     my $r = shift;
     my $ctx = shift;
@@ -65,7 +95,7 @@ sub parse_as_xml {
         $data = $ctx->{final_dtd} . "\n" . $data;
         $success = 1;
     } otherwise {
-       my $e = shift;
+           my $e = shift;
         my $err = "Invalid XML: $e";
         $r->log->error($err);
         $r->content_type('text/plain; encoding=utf8');
@@ -79,7 +109,8 @@ sub parse_as_xml {
 sub load_context {
     my $r = shift;
     my $cgi = CGI->new;
-    my $ctx = $web_config->{ctx};
+    my $ctx = {}; # new context for each page load
+    $ctx->{$_} = $web_config->{base_ctx}->{$_} for keys %{$web_config->{base_ctx}};
     $ctx->{hostname} = $r->hostname;
     $ctx->{base_url} = $cgi->url(-base => 1);
     $ctx->{skin} = $cgi->cookie(OILS_HTTP_COOKIE_SKIN) || 'default';
@@ -192,6 +223,7 @@ sub parse_config {
     $ctx->{base_path} = (ref $data->{base_path}) ? '' : $data->{base_path};
     $ctx->{template_paths} = [];
     $ctx->{force_valid_xml} = ($data->{force_valid_xml} =~ /true/io) ? 1 : 0;
+    $ctx->{debug_template} = ($data->{debug_template} =~ /true/io) ? 1 : 0;
     $ctx->{default_template_extension} = $data->{default_template_extension} || 'tt2';
     $ctx->{web_dir} = $data->{web_dir};
 
@@ -217,7 +249,7 @@ sub parse_config {
         }
     }
 
-    return {ctx => $ctx, handlers => $handlers};
+    return {base_ctx => $ctx, handlers => $handlers};
 }
 
 package PathConfig;
diff --git a/Open-ILS/web/templates/default/opac/base.tt2 b/Open-ILS/web/templates/default/opac/base.tt2
new file mode 100644 (file)
index 0000000..6d54607
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns='http://www.w3.org/1999/xhtml' lang='[% ctx.locale %]' xml:lang='[% ctx.locale %]'>
+    <head>
+        <title>[% ctx.page_title %]</title>
+        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+        [% BLOCK html_head; END; # provide a default that can be overridden %]
+        [% PROCESS html_head %]
+    </head>
+    <body>
+        [% content %] 
+    </body>
+</html>
diff --git a/Open-ILS/web/templates/default/opac/common.tt2 b/Open-ILS/web/templates/default/opac/common.tt2
new file mode 100644 (file)
index 0000000..0500725
--- /dev/null
@@ -0,0 +1,27 @@
+[% 
+    # Org Unit Selector Widget : 
+    #   PROCESS build_org_selector id='selector-id' name='selector-name'
+    BLOCK build_org_selector;
+        first_run = 0;
+        IF !org_unit;
+            org_unit = ctx.org_tree;
+            first_run = 1;
+%]
+    <select id='[% id %]' name='[% name %]'>
+    [% END %]
+        <option value='[% org_unit.id %]' [% IF org_unit.id == value %] selected='selected' [% END %]>
+            [% 
+                pad = org_unit.ou_type.depth * 4;
+                FOR idx IN [0..pad]; '&nbsp;'; END;
+                org_unit.name;
+            %]
+        </option>
+        [% FOR child IN org_unit.children; PROCESS build_org_selector org_unit = child; END %]
+    [% IF first_run %]
+    </select>
+    [% END %]
+[% END %]
+
+[% PROCESS 'default/opac/marc_attrs.tt2' %]
+
+
diff --git a/Open-ILS/web/templates/default/opac/home.tt2 b/Open-ILS/web/templates/default/opac/home.tt2
new file mode 100644 (file)
index 0000000..d1c2e05
--- /dev/null
@@ -0,0 +1,22 @@
+[% ctx.page_title = "Home" %]
+
+[% BLOCK html_head %]
+<style>
+    #home_div { text-align: center; width: 100%; margin-top: 30px;}
+</style>
+[% END %]
+
+[% WRAPPER "default/opac/base.tt2" %]
+
+<div id='home_div'>
+    <img src='/images/eg_logo.jpg'/>
+    <br/><br/>
+    <form action='./results' method='GET'>
+        <input type='text' name='query' value='[% query %]'/>
+        <input type='submit'/>
+        <input type='hidden' name='page' value='0'/>
+    </form>
+</div>
+
+
+[% END %]
diff --git a/Open-ILS/web/templates/default/opac/login.tt2 b/Open-ILS/web/templates/default/opac/login.tt2
new file mode 100644 (file)
index 0000000..a6e8b14
--- /dev/null
@@ -0,0 +1,32 @@
+[% BLOCK html_head %]
+<style>
+</style>
+[% END %]
+
+[% 
+    USE CGI;
+    WRAPPER "default/opac/base.tt2"; 
+    ctx.page_title = "Login";
+%]
+
+<div style='width:400px; text-align:center; border: 1px solid #888'>
+    <form method='POST'>
+        <table>
+            <tr>
+                <td>Username or Barcode</td>
+                <td><input name='username' type='text'/></td>
+            </tr>
+            <tr>
+                <td>Password</td>
+                <td><input name='password' type='password'/></td>
+            </tr>
+            <tr>
+                <td colspan='2' style='text-align:center'>
+                    <input type='submit'/>
+                </td>
+            </tr>
+        </table>
+        <input type='hidden' name='origin' value='[% CGI.param('origin') %]'/>
+    </form>
+</div>
+[% END %]
diff --git a/Open-ILS/web/templates/default/opac/marc_attrs.tt2 b/Open-ILS/web/templates/default/opac/marc_attrs.tt2
new file mode 100644 (file)
index 0000000..9cdad34
--- /dev/null
@@ -0,0 +1,17 @@
+[% 
+    # Extract MARC fields from XML
+    #   get_marc_attrs( { marc_xml => doc } )
+    BLOCK get_marc_attrs;
+        xml = args.marc_xml;
+        args.isbn = xml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
+        args.upc = xml.findnodes('//*[@tag="024"]/*[@code="a"]').textContent;
+        args.issn = xml.findnodes('//*[@tag="022"]/*[@code="a"]').textContent;
+        args.title = xml.findnodes('//*[@tag="245"]/*[@code="a"]').textContent;
+        args.author = xml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
+        args.publisher = xml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
+        args.pubdate = xml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
+
+        # clean up the ISBN
+        args.isbn_clean = args.isbn.replace('\ .*', '');
+    END;
+%]
diff --git a/Open-ILS/web/templates/default/opac/myopac.tt2 b/Open-ILS/web/templates/default/opac/myopac.tt2
new file mode 100644 (file)
index 0000000..fc8c74c
--- /dev/null
@@ -0,0 +1,44 @@
+[% BLOCK html_head %]
+<style>
+    table { width: 100%; text-align: center; padding: 20px; margin-top: 30px; }
+    table { border-collapse: collapse; }
+    table { padding: 3px; border-bottom: 1px solid #ddd; text-align: left;}
+    table tr:nth-child(odd) { background-color:#ded; }
+</style>
+[% END %]
+
+[% 
+    WRAPPER "default/opac/base.tt2"; 
+    ctx.page_title = "My Account";
+%]
+
+<table>
+    <tr>
+        <td>First Name</td>
+        <td>[% ctx.user.first_given_name %]</td>
+    </tr>
+    <tr>
+        <td>Middle Name</td>
+        <td>[% ctx.user.second_given_name %]</td>
+    </tr>
+    <tr>
+        <td>Last Name</td>
+        <td>[% ctx.user.family_name %]</td>
+    </tr>
+    <tr>
+        <td>Library Card</td>
+        <td>[% ctx.user.card.barcode %]</td>
+    </tr>
+    <tr>
+        <td>Email Address</td>
+        <td>[% ctx.user.email %]</td>
+    </tr>
+    <tr>
+        <td>Phone</td>
+        <td>[% ctx.user.day_phone %]</td>
+    </tr>
+</table>
+
+
+
+[% END %]
diff --git a/Open-ILS/web/templates/default/opac/place_hold.tt2 b/Open-ILS/web/templates/default/opac/place_hold.tt2
new file mode 100644 (file)
index 0000000..b92315b
--- /dev/null
@@ -0,0 +1,32 @@
+[% BLOCK html_head %]
+<style>
+</style>
+[% END %]
+
+[% 
+    USE CGI;
+    PROCESS "default/opac/common.tt2";
+    WRAPPER "default/opac/base.tt2"; 
+    ctx.page_title = "Place Hold";
+    attrs = {marc_xml => ctx.marc_xml};
+    PROCESS get_marc_attrs args=attrs;
+%]
+
+
+<div>
+    <div>Placing hold on [% attrs.title %], by [% attrs.author %]</div>
+    [% IF ctx.hold_success %] 
+        <div>Succeeded</div>
+    [% ELSIF ctx.hold_failed %]
+        <div>Failed...</div>
+    [% ELSE %]
+    <form action='place_hold' method='POST'>
+        Choose a pickup Library [% PROCESS build_org_selector name='pickup_lib' value=ctx.default_pickup_lib %]
+        <input type='Submit'/>
+        <input type='hidden' name='hold_target' value='[% CGI.param('hold_target') %]'/>
+        <input type='hidden' name='hold_type' value='[% CGI.param('hold_type') %]'/>
+    </form>
+    [% END %]
+</div>
+
+[% END %]
diff --git a/Open-ILS/web/templates/default/opac/rdetail.tt2 b/Open-ILS/web/templates/default/opac/rdetail.tt2
new file mode 100644 (file)
index 0000000..a4966fd
--- /dev/null
@@ -0,0 +1,80 @@
+[% BLOCK html_head %]
+<style>
+    table { width: 100%; padding: 20px; margin-top: 30px; }
+    table { border-collapse: collapse; }
+    table td { padding: 3px; border-bottom: 1px solid #ddd; text-align: left;}
+    table th { padding: 3px; border-bottom: 1px solid #ddd; text-align: left;}
+    table tr:nth-child(even) { background-color:#ded; }
+    #record_table td { padding-left: 15px; padding-right: 15px; }
+</style>
+[% END %]
+
+[% 
+    WRAPPER "default/opac/base.tt2"; 
+    PROCESS "default/opac/common.tt2";
+    ctx.page_title = "Details";
+    record = ctx.record;
+    attrs = {marc_xml => ctx.marc_xml};
+    PROCESS get_marc_attrs args=attrs;
+%]
+
+<div id='detail_div'>
+    <table id='record_table' style='width:auto'>
+        <tr>
+            <td rowspan='10' style='width:55px; vertical-align:top; padding-right:4px;'>
+                <img width='50' height='70' src='/opac/extras/ac/jacket/small/[% attrs.isbn_clean || attrs.upc %]'/>
+            </td>
+        </tr>
+        [% IF attrs.title %]<tr><td>Title</td><td>[% attrs.title %]</td></tr>[% END %]
+        [% IF attrs.author %]<tr><td>Author</td><td><a href='results?query=au:[% attrs.author | uri %]'>[% attrs.author %]</a></td></tr>[% END %]
+        [% IF attrs.isbn %]<tr><td>ISBN</td><td>[% attrs.isbn %]</td></tr>[% END %]
+        [% IF attrs.issn %]<tr><td>ISSN</td><td>[% attrs.issn %]</td></tr>[% END %]
+        [% IF attrs.upc %]<tr><td>UPC</td><td>[% attrs.upc %]</td></tr>[% END %]
+        [% IF attrs.pubdate %]<tr><td>Publication Date</td><td>[% attrs.pubdate %]</td></tr>[% END %]
+        [% IF attrs.publisher %]<tr><td>Publishere</td><td>[% attrs.publisher %]</td></tr>[% END %]
+        <tr>
+            <td>Subjects</td>
+            <td>
+            [% FOR node IN marc_xml.findnodes('//*[@tag="650"]') %]
+                [% 
+                    s0 = node.childNodes.0.textContent;
+                    s1 = node.childNodes.1.textContent;
+                %]
+                [% IF s0 %]
+                    <a href='results?query=su:[% s0 | url %]'>[% s0 %]</a>
+                    [% IF s1 %]
+                    <span>--</span>
+                    <a href='results?query=su:[% s1 | url %]'>[% s1 %]</a>
+                    [% END %]
+                    <br/>
+                [% END %]
+            [% END %]
+            </td>
+        </tr>
+    </table>
+    <table id='copy_table'>
+        <thead>
+            <tr>
+                <th>Owning Lib</th>
+                <th>Call Number</th>
+                <th>Barcode</th>
+                <th>Status</th>
+            </tr>
+        </thead>
+        <tbody>
+        [% FOR acn IN record.call_numbers %]
+            [% FOR acp IN acn.copies %]
+                <tr>
+                    <td>[% ctx.find_org_unit(acn.owning_lib).shortname %]</td>
+                    <td>[% acn.label %]</td>
+                    <td>[% acp.barcode %]</td>
+                    <td>[% acp.status %]</td>
+                </tr>
+            [% END %]
+        [% END %]
+        </tbody>
+    </table>
+</div>
+
+
+[% END %]
diff --git a/Open-ILS/web/templates/default/opac/records.tt2 b/Open-ILS/web/templates/default/opac/records.tt2
new file mode 100644 (file)
index 0000000..7d53268
--- /dev/null
@@ -0,0 +1,31 @@
+[% WRAPPER "default/opac/base.tt2" %]
+[% ctx.page_title = "Results" %]
+[% page = ctx.cgi.param('page') || 0; %]
+<a href='?page=[% page - 1 %]'>Prev</a>  <a href='?page=[% page + 1 %]'>Next</a>
+<br/>
+
+<table>
+[%
+    e = ctx.editor;
+    idx = 0;
+    WHILE idx < 10;
+        id = idx + page * 10;
+        attrs = e.search_acq_lineitem_attr({lineitem => id});
+        isbn = '';
+        FOR attr IN attrs; 
+            IF attr.attr_name == 'isbn';
+                isbn = attr.attr_value;
+                LAST;
+            END;
+        END;
+        idx = idx + 1;
+%]
+
+<tr>
+    <td><img width='65' height='90' src='/opac/extras/ac/jacket/small/[% isbn %]'/></td>
+    <td>[% FOR attr IN attrs; attr.attr_value _ ' / '; END; %]</td>
+</tr>
+
+    [% END %]
+</table>
+[% END %]
diff --git a/Open-ILS/web/templates/default/opac/results.tt2 b/Open-ILS/web/templates/default/opac/results.tt2
new file mode 100644 (file)
index 0000000..8704eeb
--- /dev/null
@@ -0,0 +1,102 @@
+[% BLOCK html_head %]
+<style>
+    #body_table { width: 100%; margin-top: 20px; }
+    #left_block { width: 15%; vertical-align: top; }
+    #right_block { width: auto; vertical-align: top;}
+    #record_table { border-collapse: collapse; width:100%; }
+    #record_table td { padding: 3px; border-bottom: 1px solid #ddd; }
+    #record_table tr:nth-child(odd) { background-color:#ded; }
+    #form_div { text-align: center; width: 100%; margin-top: 10px;}
+    #links_div { margin-bottom: 10px; padding: 5px;}
+</style>
+[% END %]
+
+[% 
+    USE CGI;
+    USE POSIX;
+    WRAPPER "default/opac/base.tt2"; 
+    PROCESS "default/opac/common.tt2";
+    ctx.page_title = "Results";
+    page = CGI.param('page') || 0; 
+    query = CGI.param('query');
+    page_count = POSIX.ceil(ctx.hit_count / ctx.page_size);
+%]
+
+<div id='form_div'>
+    <form action='./results' method='GET'>
+        <input type='text' name='query' size='50' value='[% query %]'/>
+        <input type='submit'/>
+        <input type='hidden' name='page' value='0'/>
+    </form>
+</div>
+
+<table id='body_table'>
+    <tr>
+        <td id='left_block'>
+                [% IF ctx.user; %]
+                    <div id='links_div'>
+                        <div><a href='home'>Home</a></div>
+                        <div><a href='myopac'>Account</a></div>
+                        <div><a href='logout'>Logout</a></div>
+                    </div>
+                    <hr/>
+                    <table>
+                        <tr><td colspan='2' style='border-bottom:1px solid #9A9'>Signed in as [% ctx.user.usrname %]</td></tr>
+                        <tr><td>Total Holds</td><td>[% ctx.user_stats.holds.total %]</td></tr>
+                        <tr><td>Ready Holds</td><td>[% ctx.user_stats.holds.ready %]</td></tr>
+                        <tr><td>Items Out</td><td>[% ctx.user_stats.checkouts.out %]</td></tr>
+                        <tr><td>Fines</td><td>$[% ctx.user_stats.fines.balance_owed %]</td></tr>
+                    </table>
+                [% ELSE %]
+                    [% 
+                        login = CGI.url("-path" => 1).replace('^http:', 'https:').replace('/results','/login');
+                        origin = CGI.url("-absolute" => 1, "-path" => 1, "-query" => 1) | uri 
+                    %]
+                    <a href='[% login _ '?origin=' _ origin %]'>Login</a>
+                [% END %]
+            </div>
+            <div>
+                [% FOR facet_type IN ctx.search_facets.keys %]
+                    <b>[% facet_type %]</b>
+                    <ul>
+                        [% FOR facet IN ctx.search_facets.$facet_type.keys %]
+                            <li>[% facet %] / [% ctx.search_facets.$facet_type.$facet %]</li>
+                        [% END %]
+                    </ul>
+                [% END %]
+            </div>
+        </td>
+        <td id='right_block'>
+            <div>
+                <span>Hits: [% ctx.hit_count %] / Page [% page + 1 %] of [% page_count %]</span>
+                <a [% IF page > 0 %] href='?page=[% page - 1 %]&query=[% query | uri %]' [% END %]>Prev</a>  
+                <a [% IF (page + 1) < page_count %] href='?page=[% page + 1 %]&query=[% query | uri %]' [% END %]>Next</a>
+            </div>
+            <table id='record_table'>
+                [%
+                FOR rec IN ctx.records;
+                    attrs = {marc_xml => rec.marc_xml};
+                    PROCESS get_marc_attrs args=attrs;
+                %]
+                <tr>
+                    <td style='width:52px;height:72px'>
+                        [% IF attrs.isbn %]
+                        <img width='50' height='70' src='/opac/extras/ac/jacket/small/[% attrs.isbn_clean || attrs.upc %]'/>
+                        [% END %]
+                    </td>
+                    <td width='auto'>
+                        <div>
+                            <a href='rdetail?record=[% rec.bre.id %]'>[% attrs.title %]</a>
+                            <span style='padding-left:10px;'>[% rec.copy_counts.available %] / [% rec.copy_counts.visible %]</span>
+                        </div>
+                        <div>[% attrs.author %]</div>
+                        <div>[% attrs.isbn || attrs.issn || attrs.upc %] [% attrs.publisher %] [% attrs.pubdate %]</div>
+                    </td>
+                </tr>
+                [% END %]
+            </table>
+        </td>
+    </tr>
+
+</table>
+[% END %]
diff --git a/Open-ILS/web/templates/default/opac/welcome.tt2 b/Open-ILS/web/templates/default/opac/welcome.tt2
new file mode 100644 (file)
index 0000000..85d19ac
--- /dev/null
@@ -0,0 +1,27 @@
+<div style='position:absolute; top:0px; right:0px; border-left:1px solid #9A9; border-bottom:1px solid #9A9; padding: 10px; background:#ded'>
+    [% IF ctx.user; %]
+        <table>
+            <tr>
+                <td colspan='2' style='border-bottom:1px solid #9A9'>Welcome, [% ctx.user.usrname %]!</td>
+            </tr>
+            <tr>
+                <td>Total Holds</td><td>[% ctx.user_stats.holds.total %]</td>
+            </tr>
+            <tr>
+                <td>Ready Holds</td><td>[% ctx.user_stats.holds.ready %]</td>
+            </tr>
+            <tr>
+                <td>Items Out</td><td>[% ctx.user_stats.checkouts.out %]</td>
+            </tr>
+            <tr>
+                <td>Fines</td><td>$[% ctx.user_stats.fines.balance_owed %]</td>
+            </tr>
+            <tr>
+                <td><a href='logout'>Logout</a></td>
+                <td><a href='myopac'>Account</a></td>
+            </tr>
+        </table>
+    [% ELSE %]
+        <a href='[% ctx.base_url.replace('^http:', 'https:') _ ctx.base_path _ '/opac/login' %]'>Login</a>
+    [% END %]
+</div>