Merge branch 'master' of git.evergreen-ils.org:Evergreen into dbs/ttopac-master-merge
authorDan Scott <dan@coffeecode.net>
Fri, 19 Aug 2011 19:39:12 +0000 (15:39 -0400)
committerDan Scott <dan@coffeecode.net>
Fri, 19 Aug 2011 19:39:12 +0000 (15:39 -0400)
577 files changed:
Open-ILS/examples/apache/eg_vhost.conf
Open-ILS/examples/fm_IDL.xml
Open-ILS/examples/oils_web.xml.example
Open-ILS/src/Makefile.am
Open-ILS/src/perlmods/MANIFEST
Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Container.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb/CGI_utf8.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb/I18NFilter.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/WWW/Redirect.pm
Open-ILS/src/perlmods/lib/Template/Plugin/ResolverResolver.pm [new file with mode: 0644]
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.data.opac_payment_history_age_limit.sql [new file with mode: 0644]
Open-ILS/src/support-scripts/test-scripts/unapi_bench.pl [new file with mode: 0755]
Open-ILS/src/templates/base.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/common/claim_dialog.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/common/final_claim_dialog.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/common/info.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/common/inv_dialog.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/common/jubgrid.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/common/li_table.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/common/li_table_pager.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/common/notes.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/financial/claim_eligible.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/financial/list_currency_types.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/financial/list_funding_sources.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/financial/list_funds.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/financial/view_fund.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/financial/view_funding_source.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/financial/view_provider.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/invoice/view.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/lineitem/findbib.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/lineitem/history.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/lineitem/related.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/lineitem/search.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/lineitem/worksheet.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/picklist/bib_search.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/picklist/brief_record.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/picklist/from_bib.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/picklist/list.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/picklist/upload.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/picklist/user_request.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/picklist/view.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/po/create.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/po/edi_messages.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/po/events.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/po/history.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/po/item_table.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/po/search.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/po/view.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/receiving/process.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/search/unified.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/acq/settings/li_attr.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/actor/user/register.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/actor/user/register_table.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/actor/user/trigger_events.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/base.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/booking/capture.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/booking/pickup.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/booking/pull_list.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/booking/reservation.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/booking/return.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/cat/authority/list.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/circ/selfcheck/audio_config.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/circ/selfcheck/banner.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/circ/selfcheck/circ_page.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/circ/selfcheck/fines.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/circ/selfcheck/holds_page.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/circ/selfcheck/main.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/circ/selfcheck/patron_login.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/circ/selfcheck/payment.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/circ/selfcheck/summary.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/acq/cancel_reason.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/acq/claim_event_type.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/acq/claim_policy.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/acq/claim_policy_action.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/acq/claim_type.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/acq/distribution_formula.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/acq/edi_account.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/acq/exchange_rate.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/acq/fund_tag.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/acq/invoice_item_type.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/acq/invoice_payment_method.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/acq/lineitem_alert.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/acq/lineitem_marc_attr_def.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/acq/provider.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/action/survey.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/action/survey/edit.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/action_trigger/event_definition.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/action_trigger/event_definition_data.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/asset/copy_location_order.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/asset/copy_template.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/biblio/monograph_part.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/booking/resource.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/booking/resource_attr.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/booking/resource_attr_map.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/booking/resource_attr_value.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/booking/resource_type.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/cat/authority/browse_axis.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/cat/authority/browse_axis_authority_field_map.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/cat/authority/control_set.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/cat/authority/control_set_authority_field.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/cat/authority/control_set_bib_field.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/cat/authority/thesaurus.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/acn_prefix.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/acn_suffix.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/actor_sip_fields.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/asset_sip_fields.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/barcode_completion.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/billing_type.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/circ_matrix_matchpoint.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/circ_matrix_weights.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/circ_modifier.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/coded_value_map.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/global_flag.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/hard_due_date.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/hard_due_date_values.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/hold_matrix_matchpoint.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/hold_matrix_weights.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/idl_field_doc.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/metabib_field.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/org_unit_setting_type.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/record_attr_definition.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/rule_age_hold_protect.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/rule_circ_duration.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/rule_max_fine.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/rule_recurring_fine.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/standing_penalty.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/usr_setting_type.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/weight_assoc.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/config/z3950_source.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/permission/grp_penalty_threshold.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/vandelay/match_set.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/conify/global/vandelay/match_set_tree.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/footer.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/header.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/menu.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/advanced.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/cnbrowse.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/home.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/login.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/mylist.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/circ_history.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/circs.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/hold_history.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/holds.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/holds/edit.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/lists.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/main.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/main_pay.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/main_payment_form.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/main_payments.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/prefs.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/prefs_notify.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/prefs_settings.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/receipt_email.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/receipt_print.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/update_email.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/update_password.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/myopac/update_username.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/advanced/expert.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/advanced/global_row.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/advanced/numeric.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/advanced/search.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/anon_list.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/base.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/chilifresh.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/coded_value_selector.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/config.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/filtersort.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/footer.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/header.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/hold_error_messages.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/homesearch.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/js.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/login/form.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/login/help.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/login/password_hint.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/misc_util.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/myopac/base.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/myopac/main_base.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/myopac/main_refund_policy.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/myopac/prefs_base.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/myopac/prefs_hints.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/org_selector.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/place_hold.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/printnav.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/qtype_selector.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/record/authors.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/record/awards.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/record/body.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/record/cn_details.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/record/cnbrowse.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/record/copyinfo.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/record/extras.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/record/issues.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/record/refworks.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/record/series.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/record/subjects.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/record/summary.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/record/summaryplus.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/result/lowhits.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/result/lowhits_purchase.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/result/paginate.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/result/table.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/searchbar.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/tips.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/topnav.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/topnav_links.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/parts/topnav_logo.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/place_hold.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/record.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/opac/results.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/serial/list_item.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/serial/list_stream.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/serial/list_subscription.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/serial/print_routing_list_users.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/serial/subscription.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/serial/subscription/caption_and_pattern.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/serial/subscription/distribution.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/serial/subscription/issuance.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/vandelay/inc/attrs.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/vandelay/inc/export.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/vandelay/inc/import_errors.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/vandelay/inc/item_attrs.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/vandelay/inc/marchtml.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/vandelay/inc/matches.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/vandelay/inc/profiles.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/vandelay/inc/progress.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/vandelay/inc/queue.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/vandelay/inc/queueselect.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/vandelay/inc/toolbar.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/vandelay/inc/upload.tt2 [new file with mode: 0644]
Open-ILS/src/templates/default/vandelay/vandelay.tt2 [new file with mode: 0644]
Open-ILS/src/templates/login.tt2 [new file with mode: 0644]
Open-ILS/web/css/skin/default/opac/contentslider.css [new file with mode: 0644]
Open-ILS/web/css/skin/default/opac/semiauto.css [new file with mode: 0644]
Open-ILS/web/css/skin/default/opac/style.css [new file with mode: 0644]
Open-ILS/web/images/KCLS_logo_horiz.gif [new file with mode: 0644]
Open-ILS/web/images/acct-btn-hover.png [new file with mode: 0644]
Open-ILS/web/images/acct-btn.png [new file with mode: 0644]
Open-ILS/web/images/acct_checked_out_off.gif [new file with mode: 0644]
Open-ILS/web/images/acct_checked_out_on.gif [new file with mode: 0644]
Open-ILS/web/images/acct_favs_off.gif [new file with mode: 0644]
Open-ILS/web/images/acct_favs_on.gif [new file with mode: 0644]
Open-ILS/web/images/acct_fines_off.jpg [new file with mode: 0644]
Open-ILS/web/images/acct_fines_on.jpg [new file with mode: 0644]
Open-ILS/web/images/acct_holds_off.gif [new file with mode: 0644]
Open-ILS/web/images/acct_holds_on.gif [new file with mode: 0644]
Open-ILS/web/images/acct_lists_off.gif [new file with mode: 0644]
Open-ILS/web/images/acct_lists_on.gif [new file with mode: 0644]
Open-ILS/web/images/acct_payments_off.jpg [new file with mode: 0644]
Open-ILS/web/images/acct_payments_on.jpg [new file with mode: 0644]
Open-ILS/web/images/acct_prefs_off.gif [new file with mode: 0644]
Open-ILS/web/images/acct_prefs_on.gif [new file with mode: 0644]
Open-ILS/web/images/acct_sum_fines_bl.png [new file with mode: 0644]
Open-ILS/web/images/acct_sum_fines_br.png [new file with mode: 0644]
Open-ILS/web/images/acct_sum_fines_tl.png [new file with mode: 0644]
Open-ILS/web/images/acct_sum_fines_tr.png [new file with mode: 0644]
Open-ILS/web/images/acct_summary_off.gif [new file with mode: 0644]
Open-ILS/web/images/acct_summary_on.gif [new file with mode: 0644]
Open-ILS/web/images/add_mylist.gif [new file with mode: 0644]
Open-ILS/web/images/add_mylist_sel.gif [new file with mode: 0644]
Open-ILS/web/images/add_mylist_sel.kcls.gif [new file with mode: 0644]
Open-ILS/web/images/add_search_row_btn.gif [new file with mode: 0644]
Open-ILS/web/images/adv_row_close_btn.png [new file with mode: 0644]
Open-ILS/web/images/adv_search.png [new file with mode: 0644]
Open-ILS/web/images/adv_search_hover.png [new file with mode: 0644]
Open-ILS/web/images/adv_search_off.gif [new file with mode: 0644]
Open-ILS/web/images/adv_search_on.gif [new file with mode: 0644]
Open-ILS/web/images/another_search.png [new file with mode: 0644]
Open-ILS/web/images/another_search_hover.png [new file with mode: 0644]
Open-ILS/web/images/arrow-down.gif [new file with mode: 0644]
Open-ILS/web/images/arrow-right.gif [new file with mode: 0644]
Open-ILS/web/images/arrow-right.png [new file with mode: 0644]
Open-ILS/web/images/asknow_available.gif [new file with mode: 0644]
Open-ILS/web/images/banner-bg.png [new file with mode: 0644]
Open-ILS/web/images/banner1.jpg [new file with mode: 0644]
Open-ILS/web/images/btnCancel.png [new file with mode: 0644]
Open-ILS/web/images/btnSubmit.png [new file with mode: 0644]
Open-ILS/web/images/button-bg.png [new file with mode: 0644]
Open-ILS/web/images/cancel_btn.gif [new file with mode: 0644]
Open-ILS/web/images/cd-small.png [new file with mode: 0644]
Open-ILS/web/images/clipboard.png [new file with mode: 0644]
Open-ILS/web/images/dash-corner-left1.png [new file with mode: 0644]
Open-ILS/web/images/dash-corner-left2.png [new file with mode: 0644]
Open-ILS/web/images/dash-corner-mid1.png [new file with mode: 0644]
Open-ILS/web/images/dash-corner-mid2.png [new file with mode: 0644]
Open-ILS/web/images/dash-corner-right1.png [new file with mode: 0644]
Open-ILS/web/images/dash-corner-right2.png [new file with mode: 0644]
Open-ILS/web/images/dash-divider.jpg [new file with mode: 0644]
Open-ILS/web/images/dropdown-hover.gif [new file with mode: 0644]
Open-ILS/web/images/dropdown.gif [new file with mode: 0644]
Open-ILS/web/images/expert_search_off.gif [new file with mode: 0644]
Open-ILS/web/images/expert_search_on.gif [new file with mode: 0644]
Open-ILS/web/images/faqs-btn.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/item_type/a.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/item_type/c.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/item_type/d.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/item_type/e.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/item_type/f.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/item_type/g.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/item_type/i.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/item_type/j.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/item_type/k.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/item_type/m.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/item_type/o.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/item_type/p.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/item_type/r.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/item_type/t.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/2.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/5.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/a.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/b.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/c.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/d.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/e.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/f.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/h.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/i.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/j.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/k.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/l.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/m.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/media_downloadableebook.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/media_electronicgames.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/media_evideo.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/media_onlinejournal.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/media_player.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/media_podcasts.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/media_streamingaudio.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/media_streamingvideo.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/media_vhs.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/n.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/o.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/p.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/q.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/r.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/s.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/t.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/v.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/w.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/x.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/y.png [new file with mode: 0644]
Open-ILS/web/images/format_icons/mattype/z.png [new file with mode: 0644]
Open-ILS/web/images/go-btn-hover.png [new file with mode: 0644]
Open-ILS/web/images/go-btn.png [new file with mode: 0644]
Open-ILS/web/images/go_but_long.gif [new file with mode: 0644]
Open-ILS/web/images/golive.jpg [new file with mode: 0644]
Open-ILS/web/images/gray-arrow.png [new file with mode: 0644]
Open-ILS/web/images/green_check.png [new file with mode: 0644]
Open-ILS/web/images/header_left.gif [new file with mode: 0644]
Open-ILS/web/images/header_right.gif [new file with mode: 0644]
Open-ILS/web/images/hp-links-left.jpg [new file with mode: 0644]
Open-ILS/web/images/hp-links-mid.jpg [new file with mode: 0644]
Open-ILS/web/images/hp-links-right.jpg [new file with mode: 0644]
Open-ILS/web/images/locations.jpg [new file with mode: 0644]
Open-ILS/web/images/login-bg.jpg [new file with mode: 0644]
Open-ILS/web/images/login-bg2.jpg [new file with mode: 0644]
Open-ILS/web/images/login-box-bg.jpg [new file with mode: 0644]
Open-ILS/web/images/login-btn-hover.png [new file with mode: 0644]
Open-ILS/web/images/login-btn.gif [new file with mode: 0644]
Open-ILS/web/images/login-btn.png [new file with mode: 0644]
Open-ILS/web/images/login-btn2.png [new file with mode: 0644]
Open-ILS/web/images/logout-btn-hover.png [new file with mode: 0644]
Open-ILS/web/images/logout-btn.png [new file with mode: 0644]
Open-ILS/web/images/minus_sign.png [new file with mode: 0644]
Open-ILS/web/images/num_search_off.gif [new file with mode: 0644]
Open-ILS/web/images/num_search_on.gif [new file with mode: 0644]
Open-ILS/web/images/one_land.gif [new file with mode: 0644]
Open-ILS/web/images/pay-fines-btn-hover.png [new file with mode: 0644]
Open-ILS/web/images/pay-fines-btn.png [new file with mode: 0644]
Open-ILS/web/images/pay_fines_btn.gif [new file with mode: 0644]
Open-ILS/web/images/place_hold.gif [new file with mode: 0644]
Open-ILS/web/images/plus_sign.png [new file with mode: 0644]
Open-ILS/web/images/projectedmedia.jpg [new file with mode: 0644]
Open-ILS/web/images/question-mark.png [new file with mode: 0644]
Open-ILS/web/images/questions.png [new file with mode: 0644]
Open-ILS/web/images/rdetail_arrow.png [new file with mode: 0644]
Open-ILS/web/images/reset_form_btn.gif [new file with mode: 0644]
Open-ILS/web/images/reviews.gif [new file with mode: 0644]
Open-ILS/web/images/save-btn.png [new file with mode: 0644]
Open-ILS/web/images/save_btn.gif [new file with mode: 0644]
Open-ILS/web/images/search_btn.gif [new file with mode: 0644]
Open-ILS/web/images/small-rss.png [new file with mode: 0644]
Open-ILS/web/images/starz.png [new file with mode: 0644]
Open-ILS/web/images/sub_checked_hist_off.jpg [new file with mode: 0644]
Open-ILS/web/images/sub_checked_hist_on.jpg [new file with mode: 0644]
Open-ILS/web/images/sub_checked_out_off.jpg [new file with mode: 0644]
Open-ILS/web/images/sub_checked_out_on.jpg [new file with mode: 0644]
Open-ILS/web/images/sub_holds_hist_off.jpg [new file with mode: 0644]
Open-ILS/web/images/sub_holds_hist_on.jpg [new file with mode: 0644]
Open-ILS/web/images/sub_holds_off.jpg [new file with mode: 0644]
Open-ILS/web/images/sub_holds_on.jpg [new file with mode: 0644]
Open-ILS/web/images/sub_prefs_info_off.jpg [new file with mode: 0644]
Open-ILS/web/images/sub_prefs_info_on.jpg [new file with mode: 0644]
Open-ILS/web/images/sub_prefs_notify_off.jpg [new file with mode: 0644]
Open-ILS/web/images/sub_prefs_notify_on.jpg [new file with mode: 0644]
Open-ILS/web/images/sub_prefs_search_off.jpg [new file with mode: 0644]
Open-ILS/web/images/sub_prefs_search_on.jpg [new file with mode: 0644]
Open-ILS/web/images/submit_btn.gif [new file with mode: 0644]
Open-ILS/web/images/tool_back.gif [new file with mode: 0644]
Open-ILS/web/images/tool_back.png [new file with mode: 0644]
Open-ILS/web/images/tool_font.gif [new file with mode: 0644]
Open-ILS/web/images/tool_forward.gif [new file with mode: 0644]
Open-ILS/web/images/tool_forward.png [new file with mode: 0644]
Open-ILS/web/images/tool_help.gif [new file with mode: 0644]
Open-ILS/web/images/tool_help.png [new file with mode: 0644]
Open-ILS/web/images/tool_home.gif [new file with mode: 0644]
Open-ILS/web/images/tool_home.png [new file with mode: 0644]
Open-ILS/web/images/tool_mail.gif [new file with mode: 0644]
Open-ILS/web/images/tool_print.gif [new file with mode: 0644]
Open-ILS/web/images/tool_print.png [new file with mode: 0644]
Open-ILS/web/images/utils-corner-left.png [new file with mode: 0644]
Open-ILS/web/images/utils-corner-mid.png [new file with mode: 0644]
Open-ILS/web/images/utils-corner-right.jpg [new file with mode: 0644]
Open-ILS/web/images/utils-corner-right.png [new file with mode: 0644]
Open-ILS/web/images/utils-corner.jpg [new file with mode: 0644]
Open-ILS/web/images/view_my_list.png [new file with mode: 0644]
Open-ILS/web/images/view_my_list_hover.png [new file with mode: 0644]
Open-ILS/web/js/ui/default/opac/simple.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/opac/staff.js [new file with mode: 0644]
Open-ILS/web/opac/skin/default/xml/footer.xml
Open-ILS/web/templates/base.tt2 [deleted file]
Open-ILS/web/templates/default/acq/common/claim_dialog.tt2 [deleted file]
Open-ILS/web/templates/default/acq/common/final_claim_dialog.tt2 [deleted file]
Open-ILS/web/templates/default/acq/common/info.tt2 [deleted file]
Open-ILS/web/templates/default/acq/common/inv_dialog.tt2 [deleted file]
Open-ILS/web/templates/default/acq/common/jubgrid.tt2 [deleted file]
Open-ILS/web/templates/default/acq/common/li_table.tt2 [deleted file]
Open-ILS/web/templates/default/acq/common/li_table_pager.tt2 [deleted file]
Open-ILS/web/templates/default/acq/common/notes.tt2 [deleted file]
Open-ILS/web/templates/default/acq/financial/claim_eligible.tt2 [deleted file]
Open-ILS/web/templates/default/acq/financial/list_currency_types.tt2 [deleted file]
Open-ILS/web/templates/default/acq/financial/list_funding_sources.tt2 [deleted file]
Open-ILS/web/templates/default/acq/financial/list_funds.tt2 [deleted file]
Open-ILS/web/templates/default/acq/financial/view_fund.tt2 [deleted file]
Open-ILS/web/templates/default/acq/financial/view_funding_source.tt2 [deleted file]
Open-ILS/web/templates/default/acq/financial/view_provider.tt2 [deleted file]
Open-ILS/web/templates/default/acq/invoice/view.tt2 [deleted file]
Open-ILS/web/templates/default/acq/lineitem/findbib.tt2 [deleted file]
Open-ILS/web/templates/default/acq/lineitem/history.tt2 [deleted file]
Open-ILS/web/templates/default/acq/lineitem/related.tt2 [deleted file]
Open-ILS/web/templates/default/acq/lineitem/search.tt2 [deleted file]
Open-ILS/web/templates/default/acq/lineitem/worksheet.tt2 [deleted file]
Open-ILS/web/templates/default/acq/picklist/bib_search.tt2 [deleted file]
Open-ILS/web/templates/default/acq/picklist/brief_record.tt2 [deleted file]
Open-ILS/web/templates/default/acq/picklist/from_bib.tt2 [deleted file]
Open-ILS/web/templates/default/acq/picklist/list.tt2 [deleted file]
Open-ILS/web/templates/default/acq/picklist/upload.tt2 [deleted file]
Open-ILS/web/templates/default/acq/picklist/user_request.tt2 [deleted file]
Open-ILS/web/templates/default/acq/picklist/view.tt2 [deleted file]
Open-ILS/web/templates/default/acq/po/create.tt2 [deleted file]
Open-ILS/web/templates/default/acq/po/edi_messages.tt2 [deleted file]
Open-ILS/web/templates/default/acq/po/events.tt2 [deleted file]
Open-ILS/web/templates/default/acq/po/history.tt2 [deleted file]
Open-ILS/web/templates/default/acq/po/item_table.tt2 [deleted file]
Open-ILS/web/templates/default/acq/po/search.tt2 [deleted file]
Open-ILS/web/templates/default/acq/po/view.tt2 [deleted file]
Open-ILS/web/templates/default/acq/receiving/process.tt2 [deleted file]
Open-ILS/web/templates/default/acq/search/unified.tt2 [deleted file]
Open-ILS/web/templates/default/acq/settings/li_attr.tt2 [deleted file]
Open-ILS/web/templates/default/actor/user/register.tt2 [deleted file]
Open-ILS/web/templates/default/actor/user/register_table.tt2 [deleted file]
Open-ILS/web/templates/default/actor/user/trigger_events.tt2 [deleted file]
Open-ILS/web/templates/default/base.tt2 [deleted file]
Open-ILS/web/templates/default/booking/capture.tt2 [deleted file]
Open-ILS/web/templates/default/booking/pickup.tt2 [deleted file]
Open-ILS/web/templates/default/booking/pull_list.tt2 [deleted file]
Open-ILS/web/templates/default/booking/reservation.tt2 [deleted file]
Open-ILS/web/templates/default/booking/return.tt2 [deleted file]
Open-ILS/web/templates/default/cat/authority/list.tt2 [deleted file]
Open-ILS/web/templates/default/circ/selfcheck/audio_config.tt2 [deleted file]
Open-ILS/web/templates/default/circ/selfcheck/banner.tt2 [deleted file]
Open-ILS/web/templates/default/circ/selfcheck/circ_page.tt2 [deleted file]
Open-ILS/web/templates/default/circ/selfcheck/fines.tt2 [deleted file]
Open-ILS/web/templates/default/circ/selfcheck/holds_page.tt2 [deleted file]
Open-ILS/web/templates/default/circ/selfcheck/main.tt2 [deleted file]
Open-ILS/web/templates/default/circ/selfcheck/patron_login.tt2 [deleted file]
Open-ILS/web/templates/default/circ/selfcheck/payment.tt2 [deleted file]
Open-ILS/web/templates/default/circ/selfcheck/summary.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/acq/cancel_reason.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/acq/claim_event_type.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/acq/claim_policy.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/acq/claim_policy_action.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/acq/claim_type.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/acq/distribution_formula.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/acq/edi_account.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/acq/exchange_rate.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/acq/fund_tag.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/acq/invoice_item_type.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/acq/invoice_payment_method.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/acq/lineitem_alert.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/acq/lineitem_marc_attr_def.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/acq/provider.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/action/survey.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/action/survey/edit.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/action_trigger/event_definition.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/action_trigger/event_definition_data.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/asset/copy_location_order.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/asset/copy_template.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/biblio/monograph_part.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/booking/resource.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/booking/resource_attr.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/booking/resource_attr_map.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/booking/resource_attr_value.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/booking/resource_type.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/cat/authority/browse_axis.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/cat/authority/browse_axis_authority_field_map.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/cat/authority/control_set.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/cat/authority/control_set_authority_field.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/cat/authority/control_set_bib_field.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/cat/authority/thesaurus.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/acn_prefix.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/acn_suffix.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/actor_sip_fields.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/asset_sip_fields.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/barcode_completion.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/billing_type.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/circ_matrix_matchpoint.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/circ_matrix_weights.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/circ_modifier.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/coded_value_map.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/global_flag.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/hard_due_date.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/hard_due_date_values.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/hold_matrix_matchpoint.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/hold_matrix_weights.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/idl_field_doc.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/metabib_field.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/org_unit_setting_type.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/record_attr_definition.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/rule_age_hold_protect.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/rule_circ_duration.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/rule_max_fine.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/rule_recurring_fine.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/standing_penalty.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/usr_setting_type.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/weight_assoc.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/config/z3950_source.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/permission/grp_penalty_threshold.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/vandelay/match_set.tt2 [deleted file]
Open-ILS/web/templates/default/conify/global/vandelay/match_set_tree.tt2 [deleted file]
Open-ILS/web/templates/default/footer.tt2 [deleted file]
Open-ILS/web/templates/default/header.tt2 [deleted file]
Open-ILS/web/templates/default/menu.tt2 [deleted file]
Open-ILS/web/templates/default/serial/list_item.tt2 [deleted file]
Open-ILS/web/templates/default/serial/list_stream.tt2 [deleted file]
Open-ILS/web/templates/default/serial/list_subscription.tt2 [deleted file]
Open-ILS/web/templates/default/serial/print_routing_list_users.tt2 [deleted file]
Open-ILS/web/templates/default/serial/subscription.tt2 [deleted file]
Open-ILS/web/templates/default/serial/subscription/caption_and_pattern.tt2 [deleted file]
Open-ILS/web/templates/default/serial/subscription/distribution.tt2 [deleted file]
Open-ILS/web/templates/default/serial/subscription/issuance.tt2 [deleted file]
Open-ILS/web/templates/default/vandelay/inc/attrs.tt2 [deleted file]
Open-ILS/web/templates/default/vandelay/inc/export.tt2 [deleted file]
Open-ILS/web/templates/default/vandelay/inc/import_errors.tt2 [deleted file]
Open-ILS/web/templates/default/vandelay/inc/item_attrs.tt2 [deleted file]
Open-ILS/web/templates/default/vandelay/inc/marchtml.tt2 [deleted file]
Open-ILS/web/templates/default/vandelay/inc/matches.tt2 [deleted file]
Open-ILS/web/templates/default/vandelay/inc/profiles.tt2 [deleted file]
Open-ILS/web/templates/default/vandelay/inc/progress.tt2 [deleted file]
Open-ILS/web/templates/default/vandelay/inc/queue.tt2 [deleted file]
Open-ILS/web/templates/default/vandelay/inc/queueselect.tt2 [deleted file]
Open-ILS/web/templates/default/vandelay/inc/toolbar.tt2 [deleted file]
Open-ILS/web/templates/default/vandelay/inc/upload.tt2 [deleted file]
Open-ILS/web/templates/default/vandelay/vandelay.tt2 [deleted file]
Open-ILS/web/templates/login.tt2 [deleted file]
Open-ILS/xul/staff_client/server/patron/holds.js

index 7fea97c..48a259c 100644 (file)
@@ -20,6 +20,8 @@ RedirectMatch 301 ^/$ /opac/en-US/skin/default/xml/index.xml
 #    # OILSRedirectDepth defaults to the depth of the branch that the OPAC was directed to
 #    #PerlSetVar OILSRedirectDepth "0"
 #    #PerlSetVar OILSRedirectLocale "en-US"
+#    # Use the template-toolkit opac
+#    #PerlSetVar OILSRedirectTpac "true"
 #    allow from all
 #</LocationMatch>
 
@@ -114,16 +116,16 @@ RewriteRule . - [E=locale:%1]
     #SetEnv OILS_CHILIFRESH_ACCOUNT
     #SetEnv OILS_CHILIFRESH_PROFILE
     #SetEnv OILS_CHILIFRESH_URL http://chilifresh.com/on-site/js/evergreen.js
+    #SetEnv OILS_CHILIFRESH_HTTPS_URL https://secure.chilifresh.com/on-site/js/evergreen.js
 
     # Specify the initial script URL for Novelist (containing account credentials, etc.)
     #SetEnv OILS_NOVELIST_URL
-    
+    #
 
     # Uncomment to force SSL any time a patron is logged in.  This protects 
     # authentication tokens.  Left commented out for backwards compat for now.
     #SetEnv OILS_OPAC_FORCE_LOGIN_SSL 1
 
-
     # If set, the skin uses the combined JS file at $SKINDIR/js/combined.js
     #SetEnv OILS_OPAC_COMBINED_JS 1
 
@@ -169,26 +171,6 @@ RewriteRule . - [E=locale:%1]
 
 </Location>
 
-<Location /js/>
-    # ----------------------------------------------------------------------------------
-    # Some mod_deflate fun
-    # ----------------------------------------------------------------------------------
-    <IfModule mod_deflate.c>
-        SetOutputFilter DEFLATE
-
-        BrowserMatch ^Mozilla/4 gzip-only-text/html
-        BrowserMatch ^Mozilla/4\.0[678] no-gzip
-        BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
-
-        SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
-
-        <IfModule mod_headers.c>
-            Header append Vary User-Agent env=!dont-vary
-        </IfModule>
-    </IfModule>
-
-</Location>
-
 # ----------------------------------------------------------------------------------
 # Force SSL on the OPAC's "My Account" page
 # ----------------------------------------------------------------------------------
@@ -554,7 +536,47 @@ RewriteRule ^/openurl$ ${openurl:%1} [NE,PT]
     Options +ExecCGI
     PerlSendHeader On
     allow from all
+    <IfModule mod_deflate.c>
+        SetOutputFilter DEFLATE
+        BrowserMatch ^Mozilla/4 gzip-only-text/html
+        BrowserMatch ^Mozilla/4\.0[678] no-gzip
+        BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
+        SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
+        <IfModule mod_headers.c>
+            Header append Cache-Control "public"
+            Header append Vary User-Agent env=!dont-vary
+        </IfModule>
+    </IfModule>
 </Location>
+<LocationMatch ^/(images|css|js)/>
+    # should pick up the default expire time from eg.conf...
+    <IfModule mod_deflate.c>
+        SetOutputFilter DEFLATE
+        BrowserMatch ^Mozilla/4 gzip-only-text/html
+        BrowserMatch ^Mozilla/4\.0[678] no-gzip
+        BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
+        SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
+        <IfModule mod_headers.c>
+            Header append Cache-Control "public"
+            Header append Vary User-Agent env=!dont-vary
+        </IfModule>
+    </IfModule>
+</LocationMatch>
+<Location /eg/opac>
+    PerlSetVar OILSWebContextLoader "OpenILS::WWW::EGCatLoader"
+    # Expire the HTML quickly since we're loading dynamic data for each page
+    ExpiresActive On
+    ExpiresByType text/html "access plus 5 seconds"
+    
+    # For use with embedded Content Cafe content
+    #SetEnv OILS_CONTENT_CAFE_USER 123
+    #SetEnv OILS_CONTENT_CAFE_PASS 456
+    # Consider copying/moving other added content configs 
+    # (e.g. NOVELIST) into here or to an outer container shared by
+    # both /opac and /eg/opac since some are used in both places
+</Location>
+
+
 # Note: the template processor will decline handling anything it does not
 # have an explicit configuration for, which means it will fall back to 
 # Apache to serve the file.  However, in the interest of speed, go ahead 
index 7b621a9..9b224df 100644 (file)
@@ -2529,6 +2529,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <field name="xact" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field name="grocery" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field name="circulation" oils_persist:virtual="true" reporter:datatype="link"/>
+                       <field name="reservation" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field name="billing_location" reporter:datatype="link"/>
                </fields>
                <links>
@@ -2536,6 +2537,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <link field="xact" reltype="might_have" key="id" map="" class="mbt"/>
                        <link field="circulation" reltype="might_have" key="id" map="" class="circ"/>
                        <link field="grocery" reltype="might_have" key="id" map="" class="mg"/>
+                       <link field="reservation" reltype="might_have" key="id" map="" class="bresv"/>
                        <link field="billing_location" reltype="has_a" key="id" map="" class="aou"/>
                </links>
        </class>
index 60f1573..9961490 100644 (file)
@@ -9,8 +9,24 @@
         files have the following filename extension -->
     <default_template_extension>tt2</default_template_extension>
 
-    <!-- media_prefix can be a remote server.  
-         E.g. <media_prefix>http://static.example.com/media</media_prefix> -->
+    <!-- Media Prefix.  Allows static files to be served from an alternate domain/server
+
+            Examples:
+
+                # local URL path
+                <media_prefix>/media</media_prefix> 
+
+                # server w/ path.  
+                <media_prefix>static.example.com/media</media_prefix> 
+
+                ===
+                In the first 2 examples, the request protocol (http vs https) will
+                match the protocol of the current page
+                ===
+
+                # full-qualified with static protocol
+                <media_prefix>http://static.example.com/media</media_prefix> 
+     -->
     <media_prefix/>
 
     <!-- If set to true, all output will be parsed as XML before delivery to the client.  
         XML parsing adds overhead, so this should only be used for debugging -->
     <force_valid_xml>false</force_valid_xml>
 
+    <!-- Turn on template-toolkit debugging, which reports on undefined blocks, macros, etc. -->
+    <debug_template>false</debug_template>
+
+    <!-- 
+        Supported locales.  Locales with no message catalog will use the native template strings.
+        All locales will fall back to native strings when a given string is not in the catalog
+    -->
+    <locales>
+        <en_US/>
+        <en_CA>/openils/var/data/locale/messages.en_CA.po</en_CA>
+        <fr_CA>/openils/var/data/locale/messages.fr_CA.po</fr_CA>
+    </locales>
+
     <!-- Where templates can be found.  Paths will be checked in the order entered here.
          It's possible to override individual or sets of templates by putting them into
          a path in front of the default template path -->
     <template_paths>
-        <!-- XXX we should really move these out of the default web root -->
-        <path>/openils/var/web/templates</path>
+        <path>/openils/var/templates</path>
     </template_paths>
 
     <handlers>
index c7c2a62..98b40e5 100644 (file)
@@ -160,6 +160,9 @@ uninstall-hook:
 ilscore-install:
        @echo $@
        $(MKDIR_P) $(DESTDIR)$(TEMPLATEDIR)
+       cp -r @srcdir@/templates/base.tt2 $(DESTDIR)$(TEMPLATEDIR)
+       cp -r @srcdir@/templates/login.tt2 $(DESTDIR)$(TEMPLATEDIR)
+       cp -r @srcdir@/templates/default $(DESTDIR)$(TEMPLATEDIR)
        cp -r @srcdir@/templates/marc $(DESTDIR)$(TEMPLATEDIR)
        cp -r @srcdir@/templates/password-reset $(DESTDIR)$(TEMPLATEDIR)
        @echo "Installing string templates to $(DESTDIR)$(TEMPLATEDIR)"
index c581a01..9f30b97 100644 (file)
@@ -126,6 +126,7 @@ lib/OpenILS/SIP/Transaction.pm
 lib/OpenILS/SIP/Transaction/Checkin.pm
 lib/OpenILS/SIP/Transaction/Checkout.pm
 lib/OpenILS/SIP/Transaction/Renew.pm
+lib/Template/Plugin/ResolverResolver.pm
 lib/OpenILS/Template/Plugin/Unicode.pm
 lib/OpenILS/Template/Plugin/WebSession.pm
 lib/OpenILS/Template/Plugin/WebUtils.pm
index 1f0fa93..c6eef0e 100644 (file)
@@ -1820,39 +1820,31 @@ __PACKAGE__->register_method(
 );
        
 sub hold_request_count {
-       my( $self, $client, $login_session, $userid ) = @_;
-
-       my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
-               $login_session, $userid, 'VIEW_HOLD' );
-       return $evt if $evt;
-       
-
-       my $holds = $apputils->simple_scalar_request(
-                       "open-ils.cstore",
-                       "open-ils.cstore.direct.action.hold_request.search.atomic",
-                       { 
-                               usr => $userid,
-                               fulfillment_time => {"=" => undef },
-                               cancel_time => undef,
-                       }
-       );
+       my( $self, $client, $authtoken, $user_id ) = @_;
+    my $e = new_editor(authtoken => $authtoken);
+    return $e->event unless $e->checkauth;
 
-       my @ready;
-       for my $h (@$holds) {
-               next unless $h->capture_time and $h->current_copy;
+    $user_id = $e->requestor->id unless defined $user_id;
 
-               my $copy = $apputils->simple_scalar_request(
-                       "open-ils.cstore",
-                       "open-ils.cstore.direct.asset.copy.retrieve",
-                       $h->current_copy
-               );
+    if($e->requestor->id ne $user_id) {
+        my $user = $e->retrieve_actor_user($user_id);
+        return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
+    }
 
-               if ($copy and $copy->status == 8) {
-                       push @ready, $h;
-               }
-       }
+    my $holds = $e->json_query({
+        select => {ahr => ['shelf_time']},
+        from => 'ahr',
+        where => {
+            usr => $user_id,
+            fulfillment_time => {"=" => undef },
+            cancel_time => undef,
+        }
+    });
 
-       return { total => scalar(@$holds), ready => scalar(@ready) };
+       return { 
+        total => scalar(@$holds), 
+        ready => scalar(grep { $_->{shelf_time} } @$holds) 
+    };
 }
 
 __PACKAGE__->register_method(
index 8b29701..d21a8cc 100644 (file)
@@ -1818,5 +1818,61 @@ sub create_circ_chain_summary {
     return $obj;
 }
 
+
+# Returns "mra" attribute key/value pairs for a set of bre's
+# Takes a list of bre IDs, returns a hash of hashes,
+# {bre_id1 => {key1 => {code => value1, label => label1}, ...}...}
+my $ccvm_cache;
+sub get_bre_attrs {
+    my ($class, $bre_ids, $e) = @_;
+    $e = $e || OpenILS::Utils::CStoreEditor->new;
+
+    my $attrs = {};
+    return $attrs unless defined $bre_ids;
+    $bre_ids = [$bre_ids] unless ref $bre_ids;
+
+    my $mra = $e->json_query({
+        select => {
+            mra => [
+                {
+                    column => 'id',
+                    alias => 'bre'
+                }, {
+                    column => 'attrs',
+                    transform => 'each',
+                    result_field => 'key',
+                    alias => 'key'
+                },{
+                    column => 'attrs',
+                    transform => 'each',
+                    result_field => 'value',
+                    alias => 'value'
+                }
+            ]
+        },
+        from => 'mra',
+        where => {id => $bre_ids}
+    });
+
+    return $attrs unless $mra;
+
+    $ccvm_cache = $ccvm_cache || $e->search_config_coded_value_map({id => {'!=' => undef}});
+
+    for my $id (@$bre_ids) {
+        $attrs->{$id} = {};
+        for my $mra (grep { $_->{bre} eq $id } @$mra) {
+            my $ctype = $mra->{key};
+            my $code = $mra->{value};
+            $attrs->{$id}->{$ctype} = {code => $code};
+            if($code) {
+                my ($ccvm) = grep { $_->ctype eq $ctype and $_->code eq $code } @$ccvm_cache;
+                $attrs->{$id}->{$ctype}->{label} = $ccvm->value if $ccvm;
+            }
+        }
+    }
+
+    return $attrs;
+}
+
 1;
 
index cab3edd..584b4c7 100644 (file)
@@ -3373,15 +3373,15 @@ __PACKAGE__->register_method(
                 'available at the library where the user is placing the hold (or, alternatively, '.
                 'at the pickup library) to encourage bypassing the hold placement and just '      .
                 'checking out the item.' ,
-        params => {
+        params => [
             { desc => 'Authentication Token', type => 'string' },
             { desc => 'Method Arguments.  Options include: hold_type, hold_target, org_unit.  ' 
                     . 'hold_type is the hold type code (T, V, C, M, ...).  '
                     . 'hold_target is the identifier of the hold target object.  ' 
                     . 'org_unit is org unit ID.', 
               type => 'object' 
-            },
-        },
+            }
+        ],
         return => { 
             desc => q/Result hash like { "copy" : copy_id, "location" : location_name }, empty hash on misses, event on error./,
             type => 'object' 
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
new file mode 100644 (file)
index 0000000..a9d0565
--- /dev/null
@@ -0,0 +1,350 @@
+package OpenILS::WWW::EGCatLoader;
+use strict; use warnings;
+use XML::LibXML;
+use URI::Escape;
+use Digest::MD5 qw(md5_hex);
+use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
+use OpenSRF::AppSession;
+use OpenSRF::EX qw/:try/;
+use OpenSRF::Utils qw/:datetime/;
+use OpenSRF::Utils::JSON;
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Application::AppUtils;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use DateTime::Format::ISO8601;
+use CGI qw(:all -utf8);
+
+# EGCatLoader sub-modules 
+use OpenILS::WWW::EGCatLoader::Util;
+use OpenILS::WWW::EGCatLoader::Account;
+use OpenILS::WWW::EGCatLoader::Search;
+use OpenILS::WWW::EGCatLoader::Record;
+use OpenILS::WWW::EGCatLoader::Container;
+
+my $U = 'OpenILS::Application::AppUtils';
+
+use constant COOKIE_SES => 'ses';
+use constant COOKIE_ORIG_LOC => 'eg_orig_loc';
+
+sub new {
+    my($class, $apache, $ctx) = @_;
+
+    my $self = bless({}, ref($class) || $class);
+
+    $self->apache($apache);
+    $self->ctx($ctx);
+    $self->cgi(new CGI);
+
+    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 / template 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};
+}
+
+
+# -----------------------------------------------------------------------------
+# Perform initial setup, load common data, then load page data
+# -----------------------------------------------------------------------------
+sub load {
+    my $self = shift;
+
+    $self->init_ro_object_cache;
+
+    my $stat = $self->load_common;
+    return $stat unless $stat == Apache2::Const::OK;
+
+    my $path = $self->apache->path_info;
+
+    (undef, $self->ctx->{mylist}) = $self->fetch_mylist unless
+        $path =~ /opac\/my(opac\/lists|list)/;
+
+    return $self->load_simple("home") if $path =~ m|opac/home|;
+    return $self->load_simple("advanced") if
+        $path =~ m:opac/(advanced|numeric|expert):;
+
+    return $self->load_rresults if $path =~ m|opac/results|;
+    return $self->load_record if $path =~ m|opac/record|;
+    return $self->load_cnbrowse if $path =~ m|opac/cnbrowse|;
+
+    return $self->load_mylist_add if $path =~ m|opac/mylist/add|;
+    return $self->load_mylist_move if $path =~ m|opac/mylist/move|;
+    return $self->load_mylist if $path =~ m|opac/mylist|;
+    return $self->load_cache_clear if $path =~ m|opac/cache/clear|;
+
+    # ----------------------------------------------------------------
+    # Logout and login require SSL
+    # ----------------------------------------------------------------
+    if($path =~ m|opac/login|) {
+        return $self->redirect_ssl unless $self->cgi->https;
+        return $self->load_login unless $self->editor->requestor; # already logged in?
+
+        # This will be less confusing to users than to be shown a login form
+        # when they're already logged in.
+        return $self->generic_redirect(
+            sprintf(
+                "https://%s%s/myopac/main",
+                $self->apache->hostname, $self->ctx->{opac_root}
+            )
+        );
+    }
+
+    if($path =~ m|opac/logout|) {
+        #return Apache2::Const::FORBIDDEN unless $self->cgi->https; 
+        $self->apache->log->warn("catloader: logout called in non-secure context from " . 
+            ($self->ctx->{referer} || '<no referer>')) unless $self->cgi->https;
+        return $self->load_logout;
+    }
+
+    # ----------------------------------------------------------------
+    #  Everything below here requires SSL + authentication
+    # ----------------------------------------------------------------
+    return $self->redirect_auth
+        unless $self->cgi->https and $self->editor->requestor;
+
+    return $self->load_place_hold if $path =~ m|opac/place_hold|;
+    return $self->load_myopac_holds if $path =~ m|opac/myopac/holds|;
+    return $self->load_myopac_circs if $path =~ m|opac/myopac/circs|;
+    return $self->load_myopac_payment_form if $path =~ m|opac/myopac/main_payment_form|;
+    return $self->load_myopac_payments if $path =~ m|opac/myopac/main_payments|;
+    return $self->load_myopac_pay if $path =~ m|opac/myopac/main_pay|;
+    return $self->load_myopac_main if $path =~ m|opac/myopac/main|;
+    return $self->load_myopac_receipt_email if $path =~ m|opac/myopac/receipt_email|;
+    return $self->load_myopac_receipt_print if $path =~ m|opac/myopac/receipt_print|;
+    return $self->load_myopac_update_email if $path =~ m|opac/myopac/update_email|;
+    return $self->load_myopac_update_password if $path =~ m|opac/myopac/update_password|;
+    return $self->load_myopac_update_username if $path =~ m|opac/myopac/update_username|;
+    return $self->load_myopac_bookbags if $path =~ m|opac/myopac/lists|;
+    return $self->load_myopac_bookbag_update if $path =~ m|opac/myopac/list/update|;
+    return $self->load_myopac_circ_history if $path =~ m|opac/myopac/circ_history|;
+    return $self->load_myopac_hold_history if $path =~ m|opac/myopac/hold_history|;
+    return $self->load_myopac_prefs_notify if $path =~ m|opac/myopac/prefs_notify|;
+    return $self->load_myopac_prefs_settings if $path =~ m|opac/myopac/prefs_settings|;
+    return $self->load_myopac_prefs if $path =~ m|opac/myopac/prefs|;
+
+    return Apache2::Const::OK;
+}
+
+
+# -----------------------------------------------------------------------------
+# Redirect to SSL equivalent of a given page
+# -----------------------------------------------------------------------------
+sub redirect_ssl {
+    my $self = shift;
+    my $new_page = sprintf('https://%s%s', $self->apache->hostname, $self->apache->unparsed_uri);
+    return $self->generic_redirect($new_page);
+}
+
+# -----------------------------------------------------------------------------
+# If an authnticated resource is requested w/o auth, redirect to the login page,
+# then return to the originally requrested resource upon successful login.
+# -----------------------------------------------------------------------------
+sub redirect_auth {
+    my $self = shift;
+    my $login_page = sprintf('https://%s%s/login', $self->apache->hostname, $self->ctx->{opac_root});
+    my $redirect_to = uri_escape($self->apache->unparsed_uri);
+    return $self->generic_redirect("$login_page?redirect_to=$redirect_to");
+}
+
+# -----------------------------------------------------------------------------
+# Fall-through for loading a basic page
+# -----------------------------------------------------------------------------
+sub load_simple {
+    my ($self, $page) = @_;
+    $self->ctx->{page} = $page;
+
+    if (my $patron_barcode = $self->cgi->param("patron_barcode")) {
+        # Special CGI variable from staff client; propagate henceforth as cookie
+        $self->apache->headers_out->add(
+            "Set-Cookie" => $self->cgi->cookie(
+                -name => "patron_barcode",
+                -path => "/",
+                -secure => 1,
+                -value => $patron_barcode,
+                -expires => undef
+            )
+        );
+    }
+    return Apache2::Const::OK;
+}
+
+# -----------------------------------------------------------------------------
+# Tests to see if the user is authenticated and sets some common context values
+# -----------------------------------------------------------------------------
+sub load_common {
+    my $self = shift;
+
+    my $e = $self->editor;
+    my $ctx = $self->ctx;
+
+    $ctx->{referer} = $self->cgi->referer;
+    $ctx->{path_info} = $self->cgi->path_info;
+    $ctx->{full_path} = $ctx->{base_path} . $self->cgi->path_info;
+    $ctx->{unparsed_uri} = $self->apache->unparsed_uri;
+    $ctx->{opac_root} = $ctx->{base_path} . "/opac"; # absolute base url
+    $ctx->{is_staff} = ($self->apache->headers_in->get('User-Agent') =~ /oils_xulrunner/);
+    $ctx->{orig_loc} = $self->get_orig_loc;
+
+    # capture some commonly accessed pages
+    $ctx->{home_page} = 'http://' . $self->apache->hostname . $self->ctx->{opac_root} . "/home";
+    $ctx->{logout_page} = 'https://' . $self->apache->hostname . $self->ctx->{opac_root} . "/logout";
+
+    if($e->authtoken($self->cgi->cookie(COOKIE_SES))) {
+
+        if($e->checkauth) {
+
+            $ctx->{authtoken} = $e->authtoken;
+            $ctx->{authtime} = $e->authtime;
+            $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 {
+
+            # if we encounter a stale authtoken, call load_logout 
+            # to clean up the cookie, then redirect the user to the
+            # originally requested page
+            return $self->load_logout($self->apache->unparsed_uri);
+        }
+    }
+
+    return Apache2::Const::OK;
+}
+
+# orig_loc (i.e. "original location") passed in as a URL 
+# param will replace any existing orig_loc stored as a cookie.
+sub get_orig_loc {
+    my $self = shift;
+
+    if(my $orig_loc = $self->cgi->param('orig_loc')) {
+        $self->apache->headers_out->add(
+            "Set-Cookie" => $self->cgi->cookie(
+                -name => COOKIE_ORIG_LOC,
+                -path => $self->ctx->{base_path},
+                -value => $orig_loc,
+                -expires => undef
+            )
+        );
+        return $orig_loc;
+    }
+
+    return $self->cgi->cookie(COOKIE_ORIG_LOC);
+}
+
+
+
+# -----------------------------------------------------------------------------
+# Log in and redirect to the redirect_to URL (or home)
+# -----------------------------------------------------------------------------
+sub load_login {
+    my $self = shift;
+    my $cgi = $self->cgi;
+    my $ctx = $self->ctx;
+
+    $ctx->{page} = 'login';
+
+    my $username = $cgi->param('username');
+    my $password = $cgi->param('password');
+    my $org_unit = $cgi->param('loc') || $ctx->{aou_tree}->()->id;
+    my $persist = $cgi->param('persist');
+
+    # initial log form only
+    return Apache2::Const::OK unless $username and $password;
+
+       my $seed = $U->simplereq(
+        'open-ils.auth', 
+               'open-ils.auth.authenticate.init', $username);
+
+    my $args = {       
+        username => $username, 
+        password => md5_hex($seed . md5_hex($password)), 
+        type => ($persist) ? 'persist' : 'opac' 
+    };
+
+    my $bc_regex = $ctx->{get_org_setting}->($org_unit, 'opac.barcode_regex');
+
+    $args->{barcode} = delete $args->{username} 
+        if $bc_regex and ($username =~ /$bc_regex/);
+
+       my $response = $U->simplereq(
+        'open-ils.auth', 'open-ils.auth.authenticate.complete', $args);
+
+    if($U->event_code($response)) { 
+        # login failed, report the reason to the template
+        $ctx->{login_failed_event} = $response;
+        return Apache2::Const::OK;
+    }
+
+    # login succeeded, redirect as necessary
+
+    my $acct = $self->apache->unparsed_uri;
+    $acct =~ s|/login|/myopac/main|;
+
+    return $self->generic_redirect(
+        $cgi->param('redirect_to') || $acct,
+        $cgi->cookie(
+            -name => COOKIE_SES,
+            -path => '/',
+            -secure => 1,
+            -value => $response->{payload}->{authtoken},
+            -expires => ($persist) ? CORE::time + $response->{payload}->{authtime} : undef
+        )
+    );
+}
+
+# -----------------------------------------------------------------------------
+# Log out and redirect to the home page
+# -----------------------------------------------------------------------------
+sub load_logout {
+    my $self = shift;
+    my $redirect_to = shift;
+
+    # If the user was adding anyting to an anonymous cache 
+    # while logged in, go ahead and clear it out.
+    $self->clear_anon_cache;
+
+    return $self->generic_redirect(
+        $redirect_to || $self->ctx->{home_page},
+        $self->cgi->cookie(
+            -name => COOKIE_SES,
+            -path => '/',
+            -value => '',
+            -expires => '-1h'
+        )
+    );
+}
+
+1;
+
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
new file mode 100644 (file)
index 0000000..698f467
--- /dev/null
@@ -0,0 +1,1253 @@
+package OpenILS::WWW::EGCatLoader;
+use strict; use warnings;
+use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+use OpenILS::Event;
+use OpenSRF::Utils::JSON;
+use Data::Dumper;
+$Data::Dumper::Indent = 0;
+use DateTime;
+my $U = 'OpenILS::Application::AppUtils';
+
+sub prepare_extended_user_info {
+    my $self = shift;
+    my @extra_flesh = @_;
+
+    $self->ctx->{user} = $self->editor->retrieve_actor_user([
+        $self->ctx->{user}->id,
+        {
+            flesh => 1,
+            flesh_fields => {
+                au => [qw/card home_ou addresses ident_type billing_address/, @extra_flesh]
+                # ...
+            }
+        }
+    ]) or return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+
+    return;
+}
+
+# Given an event returned by a failed attempt to create a hold, do we have
+# permission to override?  XXX Should the permission check be scoped to a
+# given org_unit context?
+sub test_could_override {
+    my ($self) = @_;
+    my $event = $self->ctx->{"hold_failed_event"};
+
+    return 0 unless $event;
+    return 1 if $self->editor->allowed($event . ".override");
+    return 1 if $event->{"fail_part"} and
+        $self->editor->allowed($event->{"fail_part"} . ".override");
+    return 0;
+}
+
+# Find out whether we care that local copies are available
+sub local_avail_concern {
+    my ($self, $allowed, $hold_target, $hold_type, $pickup_lib) = @_;
+
+    my $would_block = $self->ctx->{get_org_setting}->
+        ($pickup_lib, "circ.holds.hold_has_copy_at.block");
+    my $would_alert = (
+        $self->ctx->{get_org_setting}->
+            ($pickup_lib, "circ.holds.hold_has_copy_at.alert") and
+                not $self->cgi->param("override")
+    ) unless $would_block;
+
+    if ($allowed->{"success"} and ($would_block or $would_alert)) {
+        my $args = {
+            "hold_target" => $hold_target,
+            "hold_type" => $hold_type,
+            "org_unit" => $pickup_lib
+        };
+        my $local_avail = $U->simplereq(
+            "open-ils.circ",
+            "open-ils.circ.hold.has_copy_at", $self->editor->authtoken, $args
+        );
+        $logger->info(
+            "copy availability information for " . Dumper($args) .
+            " is " . Dumper($local_avail)
+        );
+        if (%$local_avail) { # if hash not empty
+            $self->ctx->{hold_copy_available} = $local_avail;
+            return ($would_block, $would_alert);
+        }
+    }
+
+    return (0, 0);
+}
+
+# context additions: 
+#   user : au object, fleshed
+sub load_myopac_prefs {
+    my $self = shift;
+    return $self->prepare_extended_user_info || Apache2::Const::OK;
+}
+
+sub load_myopac_prefs_notify {
+    my $self = shift;
+    my $e = $self->editor;
+
+    my $user_prefs = $self->fetch_optin_prefs;
+    $user_prefs = $self->update_optin_prefs($user_prefs)
+        if $self->cgi->request_method eq 'POST';
+
+    $self->ctx->{opt_in_settings} = $user_prefs; 
+
+    return Apache2::Const::OK;
+}
+
+sub fetch_optin_prefs {
+    my $self = shift;
+    my $e = $self->editor;
+
+    # fetch all of the opt-in settings the user has access to
+    # XXX: user's should in theory have options to opt-in to notices
+    # for remote locations, but that opens the door for a large
+    # set of generally un-used opt-ins.. needs discussion
+    my $opt_ins =  $U->simplereq(
+        'open-ils.actor',
+        'open-ils.actor.event_def.opt_in.settings.atomic',
+        $e->authtoken, $e->requestor->home_ou);
+
+    # fetch user setting values for each of the opt-in settings
+    my $user_set = $U->simplereq(
+        'open-ils.actor',
+        'open-ils.actor.patron.settings.retrieve',
+        $e->authtoken, 
+        $e->requestor->id, 
+        [map {$_->name} @$opt_ins]
+    );
+
+    return [map { {cust => $_, value => $user_set->{$_->name} } } @$opt_ins];
+}
+
+sub update_optin_prefs {
+    my $self = shift;
+    my $user_prefs = shift;
+    my $e = $self->editor;
+    my @settings = $self->cgi->param('setting');
+    my %newsets;
+
+    # apply now-true settings
+    for my $applied (@settings) {
+        # see if setting is already applied to this user
+        next if grep { $_->{cust}->name eq $applied and $_->{value} } @$user_prefs;
+        $newsets{$applied} = OpenSRF::Utils::JSON->true;
+    }
+
+    # remove now-false settings
+    for my $pref (grep { $_->{value} } @$user_prefs) {
+        $newsets{$pref->{cust}->name} = undef 
+            unless grep { $_ eq $pref->{cust}->name } @settings;
+    }
+
+    $U->simplereq(
+        'open-ils.actor',
+        'open-ils.actor.patron.settings.update',
+        $e->authtoken, $e->requestor->id, \%newsets);
+
+    # update the local prefs to match reality
+    for my $pref (@$user_prefs) {
+        $pref->{value} = $newsets{$pref->{cust}->name} 
+            if exists $newsets{$pref->{cust}->name};
+    }
+
+    return $user_prefs;
+}
+
+sub _load_user_with_prefs {
+    my $self = shift;
+    my $stat = $self->prepare_extended_user_info('settings');
+    return $stat if $stat; # not-OK
+
+    $self->ctx->{user_setting_map} = {
+        map { $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) } 
+            @{$self->ctx->{user}->settings}
+    };
+
+    return undef;
+}
+
+sub load_myopac_prefs_settings {
+    my $self = shift;
+
+    my $stat = $self->_load_user_with_prefs;
+    return $stat if $stat;
+
+    return Apache2::Const::OK
+        unless $self->cgi->request_method eq 'POST';
+
+    # some setting values from the form don't match the 
+    # required value/format for the db, so they have to be 
+    # individually translated.
+
+    my %settings;
+    my $set_map = $self->ctx->{user_setting_map};
+
+    my $key = 'opac.hits_per_page';
+    my $val = $self->cgi->param($key);
+    $settings{$key}= $val unless $$set_map{$key} eq $val;
+
+    my $now = DateTime->now->strftime('%F');
+    for $key (qw/history.circ.retention_start history.hold.retention_start/) {
+        $val = $self->cgi->param($key);
+        if($val and $val eq 'on') {
+            # Set the start time to 'now' unless a start time already exists for the user
+            $settings{$key} = $now unless $$set_map{$key};
+        } else {
+            # clear the start time if one previously existed for the user
+            $settings{$key} = undef if $$set_map{$key};
+        }
+    }
+    
+    # Send the modified settings off to be saved
+    $U->simplereq(
+        'open-ils.actor', 
+        'open-ils.actor.patron.settings.update',
+        $self->editor->authtoken, undef, \%settings);
+
+    # re-fetch user prefs 
+    $self->ctx->{updated_user_settings} = \%settings;
+    return $self->_load_user_with_prefs || Apache2::Const::OK;
+}
+
+sub fetch_user_holds {
+    my $self = shift;
+    my $hold_ids = shift;
+    my $ids_only = shift;
+    my $flesh = shift;
+    my $available = shift;
+    my $limit = shift;
+    my $offset = shift;
+
+    my $e = $self->editor;
+
+    if(!$hold_ids) {
+        my $circ = OpenSRF::AppSession->create('open-ils.circ');
+
+        $hold_ids = $circ->request(
+            'open-ils.circ.holds.id_list.retrieve.authoritative', 
+            $e->authtoken, 
+            $e->requestor->id
+        )->gather(1);
+        $circ->kill_me;
+    
+        $hold_ids = [ grep { defined $_ } @$hold_ids[$offset..($offset + $limit - 1)] ] if $limit or $offset;
+    }
+
+
+    return $hold_ids if $ids_only or @$hold_ids == 0;
+
+    my $args = {
+        suppress_notices => 1,
+        suppress_transits => 1,
+        suppress_mvr => 1,
+        suppress_patron_details => 1,
+        include_bre => $flesh ? 1 : 0
+    };
+
+    # ----------------------------------------------------------------
+    # Collect holds in batches of $batch_size for faster retrieval
+
+    my $batch_size = 8;
+    my $batch_idx = 0;
+    my $mk_req_batch = sub {
+        my @ses;
+        my $top_idx = $batch_idx + $batch_size;
+        while($batch_idx < $top_idx) {
+            my $hold_id = $hold_ids->[$batch_idx++];
+            last unless $hold_id;
+            my $ses = OpenSRF::AppSession->create('open-ils.circ');
+            my $req = $ses->request(
+                'open-ils.circ.hold.details.retrieve', 
+                $e->authtoken, $hold_id, $args);
+            push(@ses, {ses => $ses, req => $req});
+        }
+        return @ses;
+    };
+
+    my $first = 1;
+    my(@collected, @holds, @ses);
+
+    while(1) {
+        @ses = $mk_req_batch->() if $first;
+        last if $first and not @ses;
+
+        if(@collected) {
+            # If desired by the caller, filter any holds that are not available.
+            if ($available) {
+                @collected = grep { $_->{hold}->{status} == 4 } @collected;
+            }
+            while(my $blob = pop(@collected)) {
+                $blob->{marc_xml} = XML::LibXML->new->parse_string($blob->{hold}->{bre}->marc) if $flesh;
+                push(@holds, $blob);
+            }
+        }
+
+        for my $req_data (@ses) {
+            push(@collected, {hold => $req_data->{req}->gather(1)});
+            $req_data->{ses}->kill_me;
+        }
+
+        @ses = $mk_req_batch->();
+        last unless @collected or @ses;
+        $first = 0;
+    }
+
+    # put the holds back into the original server sort order
+    my @sorted;
+    for my $id (@$hold_ids) {
+        push @sorted, grep { $_->{hold}->{hold}->id == $id } @holds;
+    }
+
+    return \@sorted;
+}
+
+sub handle_hold_update {
+    my $self = shift;
+    my $action = shift;
+    my $hold_ids = shift;
+    my $e = $self->editor;
+    my $url;
+
+    my @hold_ids = ($hold_ids) ? @$hold_ids : $self->cgi->param('hold_id'); # for non-_all actions
+    @hold_ids = @{$self->fetch_user_holds(undef, 1)} if $action =~ /_all/;
+
+    my $circ = OpenSRF::AppSession->create('open-ils.circ');
+
+    if($action =~ /cancel/) {
+
+        for my $hold_id (@hold_ids) {
+            my $resp = $circ->request(
+                'open-ils.circ.hold.cancel', $e->authtoken, $hold_id, 6 )->gather(1); # 6 == patron-cancelled-via-opac
+        }
+
+    } elsif ($action =~ /activate|suspend/) {
+        
+        my $vlist = [];
+        for my $hold_id (@hold_ids) {
+            my $vals = {id => $hold_id};
+
+            if($action =~ /activate/) {
+                $vals->{frozen} = 'f';
+                $vals->{thaw_date} = undef;
+
+            } elsif($action =~ /suspend/) {
+                $vals->{frozen} = 't';
+                # $vals->{thaw_date} = TODO;
+            }
+            push(@$vlist, $vals);
+        }
+
+        $circ->request('open-ils.circ.hold.update.batch.atomic', $e->authtoken, undef, $vlist)->gather(1);
+    } elsif ($action eq 'edit') {
+
+        my @vals = map {
+            my $val = {"id" => $_};
+            $val->{"frozen"} = $self->cgi->param("frozen");
+            $val->{"pickup_lib"} = $self->cgi->param("pickup_lib");
+
+            for my $field (qw/expire_time thaw_date/) {
+                # XXX TODO make this support other date formats, not just
+                # MM/DD/YYYY.
+                next unless $self->cgi->param($field) =~
+                    m:^(\d{2})/(\d{2})/(\d{4})$:;
+                $val->{$field} = "$3-$1-$2";
+            }
+            $val;
+        } @hold_ids;
+
+        $circ->request(
+            'open-ils.circ.hold.update.batch.atomic',
+            $e->authtoken, undef, \@vals
+        )->gather(1);   # LFW XXX test for failure
+        $url = 'https://' . $self->apache->hostname . $self->ctx->{opac_root} . '/myopac/holds';
+    }
+
+    $circ->kill_me;
+    return defined($url) ? $self->generic_redirect($url) : undef;
+}
+
+sub load_myopac_holds {
+    my $self = shift;
+    my $e = $self->editor;
+    my $ctx = $self->ctx;
+    
+    my $limit = $self->cgi->param('limit') || 0;
+    my $offset = $self->cgi->param('offset') || 0;
+    my $action = $self->cgi->param('action') || '';
+    my $hold_id = $self->cgi->param('id');
+    my $available = int($self->cgi->param('available') || 0);
+
+    my $hold_handle_result;
+    $hold_handle_result = $self->handle_hold_update($action) if $action;
+
+    $ctx->{holds} = $self->fetch_user_holds($hold_id ? [$hold_id] : undef, 0, 1, $available, $limit, $offset);
+
+    return defined($hold_handle_result) ? $hold_handle_result : Apache2::Const::OK;
+}
+
+sub load_place_hold {
+    my $self = shift;
+    my $ctx = $self->ctx;
+    my $gos = $ctx->{get_org_setting};
+    my $e = $self->editor;
+    my $cgi = $self->cgi;
+    $self->ctx->{page} = 'place_hold';
+
+    $ctx->{hold_target} = $cgi->param('hold_target');
+    $ctx->{hold_type} = $cgi->param('hold_type');
+
+    $ctx->{default_pickup_lib} = $e->requestor->home_ou; # unless changed below
+
+    if (my $bc = $self->cgi->cookie("patron_barcode")) {
+        # passed in from staff client
+        $ctx->{patron_recipient} = $U->simplereq(
+            "open-ils.actor", "open-ils.actor.user.fleshed.retrieve_by_barcode",
+            $self->editor->authtoken, $bc
+        ) or return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+
+        $ctx->{default_pickup_lib} = $ctx->{patron_recipient}->home_ou;
+    }
+
+    my $request_lib = $e->requestor->ws_ou;
+
+    # XXX check for failure of the retrieve_* methods called below, and
+    # possibly replace all the if,elsif with a dispatch table (meh, elegance)
+
+    my $target_field;
+    if ($ctx->{hold_type} eq 'T') {
+        $target_field = "titleid";
+        $ctx->{record} = $e->retrieve_biblio_record_entry($ctx->{hold_target});
+    } elsif ($ctx->{hold_type} eq 'V') {
+        $target_field = "volume_id";
+        my $vol = $e->retrieve_asset_call_number([
+            $ctx->{hold_target}, {
+                "flesh" => 1,
+                "flesh_fields" => {"acn" => ["record"]}
+            }
+        ]);
+        $ctx->{record} = $vol->record;
+    } elsif ($ctx->{hold_type} eq 'C') {
+        $target_field = "copy_id";
+        my $copy = $e->retrieve_asset_copy([
+            $ctx->{hold_target}, {
+                "flesh" => 2,
+                "flesh_fields" => {
+                    "acn" => ["record"],
+                    "acp" => ["call_number"]
+                }
+            }
+        ]);
+        $ctx->{record} = $copy->call_number->record;
+    } elsif ($ctx->{hold_type} eq 'I') {
+        $target_field = "issuanceid";
+        my $iss = $e->retrieve_serial_issuance([
+            $ctx->{hold_target}, {
+                "flesh" => 2,
+                "flesh_fields" => {
+                    "siss" => ["subscription"], "ssub" => ["record_entry"]
+                }
+            }
+        ]);
+        $ctx->{record} = $iss->subscription->record_entry;
+    }
+    # ...
+
+    $ctx->{marc_xml} = XML::LibXML->new->parse_string($ctx->{record}->marc);
+
+    if (my $pickup_lib = $cgi->param('pickup_lib')) {
+        my $requestor = $e->requestor->id;
+        my $usr; 
+
+        if ((not $ctx->{"is_staff"}) or
+            ($cgi->param("hold_usr_is_requestor"))) {
+            $usr = $requestor;
+        } else {
+            my $actor = create OpenSRF::AppSession("open-ils.actor");
+            $usr = $actor->request(
+                "open-ils.actor.user.retrieve_id_by_barcode_or_username",
+                $e->authtoken, $cgi->param("hold_usr")
+            )->gather(1);
+
+            if (defined $U->event_code($usr)) {
+                $ctx->{hold_failed} = 1;
+                $ctx->{hold_failed_event} = $usr;
+            }
+            $actor->kill_me;
+        }
+
+        my $args = {
+            patronid => $usr,
+            $target_field => $ctx->{"hold_target"},
+            pickup_lib => $pickup_lib,
+            hold_type => $ctx->{"hold_type"},
+            depth => 0, # XXX
+        };
+
+        my $allowed = $U->simplereq(
+            'open-ils.circ',
+            'open-ils.circ.title_hold.is_possible',
+            $e->authtoken, $args
+        );
+
+        $logger->info('hold permit result ' . OpenSRF::Utils::JSON->perl2JSON($allowed));
+
+        my ($local_block, $local_alert) = $self->local_avail_concern(
+            $allowed, $args->{$target_field}, $args->{hold_type}, $pickup_lib
+        );
+
+        # Give the original CGI params back to the user in case they
+        # want to try to override something.
+        $ctx->{orig_params} = $cgi->Vars;
+
+        if ($local_block) {
+            $ctx->{hold_failed} = 1;
+            $ctx->{hold_local_block} = 1;
+        } elsif ($local_alert) {
+            $ctx->{hold_failed} = 1;
+            $ctx->{hold_local_alert} = 1;
+        } elsif ($allowed->{success}) {
+            my $hold = Fieldmapper::action::hold_request->new;
+
+            $hold->pickup_lib($pickup_lib);
+            $hold->requestor($requestor);
+            $hold->usr($usr);
+            $hold->target($ctx->{hold_target});
+            $hold->hold_type($ctx->{hold_type});
+            # frozen, expired, etc..
+
+            my $method =  "open-ils.circ.holds.create";
+            $method .= ".override" if $cgi->param("override");
+
+            my $stat = $U->simplereq(
+                "open-ils.circ", $method, $e->authtoken, $hold
+            );
+
+            # The following did not cover all the possible return values of
+            # open-ils.circ.holds.create
+            #if($stat and $stat > 0) {
+            if ($stat and (not ref $stat) and $stat > 0) {
+                # if successful, return the user to the requesting page
+                $self->apache->log->info(
+                    "Redirecting back to " . $cgi->param('redirect_to')
+                );
+
+                # We also clear the patron_barcode (from the staff client)
+                # cookie at this point (otherwise it haunts the staff user
+                # later). XXX todo make sure this is best; also see that
+                # template when staff mode calls xulG.opac_hold_placed()
+                return $self->generic_redirect(
+                    undef,
+                    $self->cgi->cookie(
+                        -name => "patron_barcode",
+                        -path => "/",
+                        -secure => 1,
+                        -value => "",
+                        -expires => "-1h"
+                    )
+                );
+
+            } else {
+                $ctx->{hold_failed} = 1;
+
+                delete $ctx->{orig_params}{submit};
+
+                if (ref $stat eq 'ARRAY') {
+                    $ctx->{hold_failed_event} = shift @$stat;
+                } elsif (defined $U->event_code($stat)) {
+                    $ctx->{hold_failed_event} = $stat;
+                } else {
+                    $self->apache->log->info(
+                        "attempt to create hold returned $stat"
+                    );
+                }
+
+                $ctx->{could_override} = $self->test_could_override;
+            }
+        } else { # hold *check* failed
+            $ctx->{hold_failed} = 1; # XXX process the events, etc
+            $ctx->{hold_failed_event} = $allowed->{last_event};
+        }
+
+        # hold permit failed
+    }
+
+    return Apache2::Const::OK;
+}
+
+
+sub fetch_user_circs {
+    my $self = shift;
+    my $flesh = shift; # flesh bib data, etc.
+    my $circ_ids = shift;
+    my $limit = shift;
+    my $offset = shift;
+
+    my $e = $self->editor;
+
+    my @circ_ids;
+
+    if($circ_ids) {
+        @circ_ids = @$circ_ids;
+
+    } else {
+
+        my $circ_data = $U->simplereq(
+            'open-ils.actor', 
+            'open-ils.actor.user.checked_out',
+            $e->authtoken, 
+            $e->requestor->id
+        );
+
+        @circ_ids =  ( @{$circ_data->{overdue}}, @{$circ_data->{out}} );
+
+        if($limit or $offset) {
+            @circ_ids = grep { defined $_ } @circ_ids[0..($offset + $limit - 1)];
+        }
+    }
+
+    return [] unless @circ_ids;
+
+    my $qflesh = {
+        flesh => 3,
+        flesh_fields => {
+            circ => ['target_copy'],
+            acp => ['call_number'],
+            acn => ['record']
+        }
+    };
+
+    $e->xact_begin;
+    my $circs = $e->search_action_circulation(
+        [{id => \@circ_ids}, ($flesh) ? $qflesh : {}], {substream => 1});
+
+    my @circs;
+    for my $circ (@$circs) {
+        push(@circs, {
+            circ => $circ, 
+            marc_xml => ($flesh and $circ->target_copy->call_number->id != -1) ? 
+                XML::LibXML->new->parse_string($circ->target_copy->call_number->record->marc) : 
+                undef  # pre-cat copy, use the dummy title/author instead
+        });
+    }
+    $e->xact_rollback;
+
+    # make sure the final list is in the correct order
+    my @sorted_circs;
+    for my $id (@circ_ids) {
+        push(
+            @sorted_circs,
+            (grep { $_->{circ}->id == $id } @circs)
+        );
+    }
+
+    return \@sorted_circs;
+}
+
+
+sub handle_circ_renew {
+    my $self = shift;
+    my $action = shift;
+    my $ctx = $self->ctx;
+
+    my @renew_ids = $self->cgi->param('circ');
+
+    my $circs = $self->fetch_user_circs(0, ($action eq 'renew') ? [@renew_ids] : undef);
+
+    # TODO: fire off renewal calls in batches to speed things up
+    my @responses;
+    for my $circ (@$circs) {
+
+        my $evt = $U->simplereq(
+            'open-ils.circ', 
+            'open-ils.circ.renew',
+            $self->editor->authtoken,
+            {
+                patron_id => $self->editor->requestor->id,
+                copy_id => $circ->{circ}->target_copy,
+                opac_renewal => 1
+            }
+        );
+
+        # TODO return these, then insert them into the circ data 
+        # blob that is shoved into the template for each circ
+        # so the template won't have to match them
+        push(@responses, {copy => $circ->{circ}->target_copy, evt => $evt});
+    }
+
+    return @responses;
+}
+
+
+sub load_myopac_circs {
+    my $self = shift;
+    my $e = $self->editor;
+    my $ctx = $self->ctx;
+
+    $ctx->{circs} = [];
+    my $limit = $self->cgi->param('limit') || 0; # 0 == unlimited
+    my $offset = $self->cgi->param('offset') || 0;
+    my $action = $self->cgi->param('action') || '';
+
+    # perform the renewal first if necessary
+    my @results = $self->handle_circ_renew($action) if $action =~ /renew/;
+
+    $ctx->{circs} = $self->fetch_user_circs(1, undef, $limit, $offset);
+
+    my $success_renewals = 0;
+    my $failed_renewals = 0;
+    for my $data (@{$ctx->{circs}}) {
+        my ($resp) = grep { $_->{copy} == $data->{circ}->target_copy->id } @results;
+
+        if($resp) {
+            my $evt = ref($resp->{evt}) eq 'ARRAY' ? $resp->{evt}->[0] : $resp->{evt};
+            $data->{renewal_response} = $evt;
+            $success_renewals++ if $evt->{textcode} eq 'SUCCESS';
+            $failed_renewals++ if $evt->{textcode} ne 'SUCCESS';
+        }
+    }
+
+    $ctx->{success_renewals} = $success_renewals;
+    $ctx->{failed_renewals} = $failed_renewals;
+
+    return Apache2::Const::OK;
+}
+
+sub load_myopac_circ_history {
+    my $self = shift;
+    my $e = $self->editor;
+    my $ctx = $self->ctx;
+    my $limit = $self->cgi->param('limit') || 15;
+    my $offset = $self->cgi->param('offset') || 0;
+
+    $ctx->{circ_history_limit} = $limit;
+    $ctx->{circ_history_offset} = $offset;
+
+    my $circ_ids = $e->json_query({
+        select => {
+            au => [{
+                column => 'id', 
+                transform => 'action.usr_visible_circs', 
+                result_field => 'id'
+            }]
+        },
+        from => 'au',
+        where => {id => $e->requestor->id}, 
+        limit => $limit,
+        offset => $offset
+    });
+
+    $ctx->{circs} = $self->fetch_user_circs(1, [map { $_->{id} } @$circ_ids]);
+    return Apache2::Const::OK;
+}
+
+# TODO: action.usr_visible_holds does not return cancelled holds.  Should it?
+sub load_myopac_hold_history {
+    my $self = shift;
+    my $e = $self->editor;
+    my $ctx = $self->ctx;
+    my $limit = $self->cgi->param('limit') || 15;
+    my $offset = $self->cgi->param('offset') || 0;
+    $ctx->{hold_history_limit} = $limit;
+    $ctx->{hold_history_offset} = $offset;
+
+    my $hold_ids = $e->json_query({
+        select => {
+            au => [{
+                column => 'id', 
+                transform => 'action.usr_visible_holds', 
+                result_field => 'id'
+            }]
+        },
+        from => 'au',
+        where => {id => $e->requestor->id}, 
+        limit => $limit,
+        offset => $offset
+    });
+
+    $ctx->{holds} = $self->fetch_user_holds([map { $_->{id} } @$hold_ids], 0, 1, 0);
+    return Apache2::Const::OK;
+}
+
+sub load_myopac_payment_form {
+    my $self = shift;
+    my $r;
+
+    $r = $self->prepare_fines(undef, undef, [$self->cgi->param('xact'), $self->cgi->param('xact_misc')]) and return $r;
+    $r = $self->prepare_extended_user_info and return $r;
+
+    return Apache2::Const::OK;
+}
+
+# TODO: add other filter options as params/configs/etc.
+sub load_myopac_payments {
+    my $self = shift;
+    my $limit = $self->cgi->param('limit') || 20;
+    my $offset = $self->cgi->param('offset') || 0;
+    my $e = $self->editor;
+
+    $self->ctx->{payment_history_limit} = $limit;
+    $self->ctx->{payment_history_offset} = $offset;
+
+    my $args = {};
+    $args->{limit} = $limit if $limit;
+    $args->{offset} = $offset if $offset;
+
+    if (my $max_age = $self->ctx->{get_org_setting}->(
+        $e->requestor->home_ou, "opac.payment_history_age_limit"
+    )) {
+        my $min_ts = DateTime->now(
+            "time_zone" => DateTime::TimeZone->new("name" => "local"),
+        )->subtract("seconds" => interval_to_seconds($max_age))->iso8601();
+        
+        $logger->info("XXX min_ts: $min_ts");
+        $args->{"where"} = {"payment_ts" => {">=" => $min_ts}};
+    }
+
+    $self->ctx->{payments} = $U->simplereq(
+        'open-ils.actor',
+        'open-ils.actor.user.payments.retrieve.atomic',
+        $e->authtoken, $e->requestor->id, $args);
+
+    return Apache2::Const::OK;
+}
+
+sub load_myopac_pay {
+    my $self = shift;
+    my $r;
+
+    $r = $self->prepare_fines(undef, undef, [$self->cgi->param('xact'), $self->cgi->param('xact_misc')]) and
+        return $r;
+
+    # balance_owed is computed specifically from the fines we're trying
+    # to pay in this case.
+    if ($self->ctx->{fines}->{balance_owed} <= 0) {
+        $self->apache->log->info(
+            sprintf("Can't pay non-positive balance. xacts selected: (%s)",
+                join(", ", map(int, $self->cgi->param("xact"), $self->cgi->param('xact_misc'))))
+        );
+        return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    my $cc_args = {"where_process" => 1};
+
+    $cc_args->{$_} = $self->cgi->param($_) for (qw/
+        number cvv2 expire_year expire_month billing_first
+        billing_last billing_address billing_city billing_state
+        billing_zip
+    /);
+
+    my $args = {
+        "cc_args" => $cc_args,
+        "userid" => $self->ctx->{user}->id,
+        "payment_type" => "credit_card_payment",
+        "payments" => $self->prepare_fines_for_payment   # should be safe after self->prepare_fines
+    };
+
+    my $resp = $U->simplereq("open-ils.circ", "open-ils.circ.money.payment",
+        $self->editor->authtoken, $args, $self->ctx->{user}->last_xact_id
+    );
+
+    $self->ctx->{"payment_response"} = $resp;
+
+    unless ($resp->{"textcode"}) {
+        $self->ctx->{printable_receipt} = $U->simplereq(
+           "open-ils.circ", "open-ils.circ.money.payment_receipt.print",
+           $self->editor->authtoken, $resp->{payments}
+        );
+    }
+
+    return Apache2::Const::OK;
+}
+
+sub load_myopac_receipt_print {
+    my $self = shift;
+
+    $self->ctx->{printable_receipt} = $U->simplereq(
+       "open-ils.circ", "open-ils.circ.money.payment_receipt.print",
+       $self->editor->authtoken, [$self->cgi->param("payment")]
+    );
+
+    return Apache2::Const::OK;
+}
+
+sub load_myopac_receipt_email {
+    my $self = shift;
+
+    # The following ML method doesn't actually check whether the user in
+    # question has an email address, so we do.
+    if ($self->ctx->{user}->email) {
+        $self->ctx->{email_receipt_result} = $U->simplereq(
+           "open-ils.circ", "open-ils.circ.money.payment_receipt.email",
+           $self->editor->authtoken, [$self->cgi->param("payment")]
+        );
+    } else {
+        $self->ctx->{email_receipt_result} =
+            new OpenILS::Event("PATRON_NO_EMAIL_ADDRESS");
+    }
+
+    return Apache2::Const::OK;
+}
+
+sub prepare_fines {
+    my ($self, $limit, $offset, $id_list) = @_;
+
+    # XXX TODO: check for failure after various network calls
+
+    # It may be unclear, but this result structure lumps circulation and
+    # reservation fines together, and keeps grocery fines separate.
+    $self->ctx->{"fines"} = {
+        "circulation" => [],
+        "grocery" => [],
+        "total_paid" => 0,
+        "total_owed" => 0,
+        "balance_owed" => 0
+    };
+
+    my $cstore = OpenSRF::AppSession->create('open-ils.cstore');
+
+    # TODO: This should really be a ML call, but the existing calls 
+    # return an excessive amount of data and don't offer streaming
+
+    my %paging = ($limit or $offset) ? (limit => $limit, offset => $offset) : ();
+
+    my $req = $cstore->request(
+        'open-ils.cstore.direct.money.open_billable_transaction_summary.search',
+        {
+            usr => $self->editor->requestor->id,
+            balance_owed => {'!=' => 0},
+            ($id_list && @$id_list ? ("id" => $id_list) : ()),
+        },
+        {
+            flesh => 4,
+            flesh_fields => {
+                mobts => [qw/grocery circulation reservation/],
+                bresv => ['target_resource_type'],
+                brt => ['record'],
+                mg => ['billings'],
+                mb => ['btype'],
+                circ => ['target_copy'],
+                acp => ['call_number'],
+                acn => ['record']
+            },
+            order_by => { mobts => 'xact_start' },
+            %paging
+        }
+    );
+
+    my @total_keys = qw/total_paid total_owed balance_owed/;
+    $self->ctx->{"fines"}->{@total_keys} = (0, 0, 0);
+
+    while(my $resp = $req->recv) {
+        my $mobts = $resp->content;
+        my $circ = $mobts->circulation;
+
+        my $last_billing;
+        if($mobts->grocery) {
+            my @billings = sort { $a->billing_ts cmp $b->billing_ts } @{$mobts->grocery->billings};
+            $last_billing = pop(@billings);
+        }
+
+        # XXX TODO confirm that the following, and the later division by 100.0
+        # to get a floating point representation once again, is sufficiently
+        # "money-safe" math.
+        $self->ctx->{"fines"}->{$_} += int($mobts->$_ * 100) for (@total_keys);
+
+        my $marc_xml = undef;
+        if ($mobts->xact_type eq 'reservation' and
+            $mobts->reservation->target_resource_type->record) {
+            $marc_xml = XML::LibXML->new->parse_string(
+                $mobts->reservation->target_resource_type->record->marc
+            );
+        } elsif ($mobts->xact_type eq 'circulation' and
+            $circ->target_copy->call_number->id != -1) {
+            $marc_xml = XML::LibXML->new->parse_string(
+                $circ->target_copy->call_number->record->marc
+            );
+        }
+
+        push(
+            @{$self->ctx->{"fines"}->{$mobts->grocery ? "grocery" : "circulation"}},
+            {
+                xact => $mobts,
+                last_grocery_billing => $last_billing,
+                marc_xml => $marc_xml
+            } 
+        );
+    }
+
+    $cstore->kill_me;
+
+    $self->ctx->{"fines"}->{$_} /= 100.0 for (@total_keys);
+    return;
+}
+
+sub prepare_fines_for_payment {
+    # This assumes $self->prepare_fines has already been run
+    my ($self) = @_;
+
+    my @results = ();
+    if ($self->ctx->{fines}) {
+        push @results, [$_->{xact}->id, $_->{xact}->balance_owed] foreach (
+            @{$self->ctx->{fines}->{circulation}},
+            @{$self->ctx->{fines}->{grocery}}
+        );
+    }
+
+    return \@results;
+}
+
+sub load_myopac_main {
+    my $self = shift;
+    my $limit = $self->cgi->param('limit') || 0;
+    my $offset = $self->cgi->param('offset') || 0;
+
+    return $self->prepare_fines($limit, $offset) || Apache2::Const::OK;
+}
+
+sub load_myopac_update_email {
+    my $self = shift;
+    my $e = $self->editor;
+    my $ctx = $self->ctx;
+    my $email = $self->cgi->param('email') || '';
+
+    # needed for most up-to-date email address
+    if (my $r = $self->prepare_extended_user_info) { return $r };
+
+    return Apache2::Const::OK 
+        unless $self->cgi->request_method eq 'POST';
+
+    unless($email =~ /.+\@.+\..+/) { # TODO better regex?
+        $ctx->{invalid_email} = $email;
+        return Apache2::Const::OK;
+    }
+
+    my $stat = $U->simplereq(
+        'open-ils.actor', 
+        'open-ils.actor.user.email.update', 
+        $e->authtoken, $email);
+
+    unless ($self->cgi->param("redirect_to")) {
+        my $url = $self->apache->unparsed_uri;
+        $url =~ s/update_email/prefs/;
+
+        return $self->generic_redirect($url);
+    }
+
+    return $self->generic_redirect;
+}
+
+sub load_myopac_update_username {
+    my $self = shift;
+    my $e = $self->editor;
+    my $ctx = $self->ctx;
+    my $username = $self->cgi->param('username') || '';
+
+    return Apache2::Const::OK 
+        unless $self->cgi->request_method eq 'POST';
+
+    unless($username and $username !~ /\s/) { # any other username restrictions?
+        $ctx->{invalid_username} = $username;
+        return Apache2::Const::OK;
+    }
+
+    if($username ne $e->requestor->usrname) {
+
+        my $evt = $U->simplereq(
+            'open-ils.actor', 
+            'open-ils.actor.user.username.update', 
+            $e->authtoken, $username);
+
+        if($U->event_equals($evt, 'USERNAME_EXISTS')) {
+            $ctx->{username_exists} = $username;
+            return Apache2::Const::OK;
+        }
+    }
+
+    my $url = $self->apache->unparsed_uri;
+    $url =~ s/update_username/prefs/;
+
+    return $self->generic_redirect($url);
+}
+
+sub load_myopac_update_password {
+    my $self = shift;
+    my $e = $self->editor;
+    my $ctx = $self->ctx;
+
+    return Apache2::Const::OK 
+        unless $self->cgi->request_method eq 'POST';
+
+    my $current_pw = $self->cgi->param('current_pw') || '';
+    my $new_pw = $self->cgi->param('new_pw') || '';
+    my $new_pw2 = $self->cgi->param('new_pw2') || '';
+
+    unless($new_pw eq $new_pw2) {
+        $ctx->{password_nomatch} = 1;
+        return Apache2::Const::OK;
+    }
+
+    my $pw_regex = $ctx->{get_org_setting}->($e->requestor->home_ou, 'global.password_regex');
+
+    if($pw_regex and $new_pw !~ /$pw_regex/) {
+        $ctx->{password_invalid} = 1;
+        return Apache2::Const::OK;
+    }
+
+    my $evt = $U->simplereq(
+        'open-ils.actor', 
+        'open-ils.actor.user.password.update', 
+        $e->authtoken, $new_pw, $current_pw);
+
+
+    if($U->event_equals($evt, 'INCORRECT_PASSWORD')) {
+        $ctx->{password_incorrect} = 1;
+        return Apache2::Const::OK;
+    }
+
+    my $url = $self->apache->unparsed_uri;
+    $url =~ s/update_password/prefs/;
+
+    return $self->generic_redirect($url);
+}
+
+sub load_myopac_bookbags {
+    my $self = shift;
+    my $e = $self->editor;
+    my $ctx = $self->ctx;
+
+    $e->xact_begin; # replication...
+
+    my $rv = $self->load_mylist;
+    unless($rv eq Apache2::Const::OK) {
+        $e->rollback;
+        return $rv;
+    }
+
+    my $args = {
+        order_by => {cbreb => 'name'},
+        limit => $self->cgi->param('limit') || 10,
+        offset => $self->cgi->param('offset') || 0
+    };
+
+    $ctx->{bookbags} = $e->search_container_biblio_record_entry_bucket(
+        [
+            {owner => $self->editor->requestor->id, btype => 'bookbag'},
+            {"flesh" => 1, "flesh_fields" => {"cbreb" => ["items"]}, %$args}
+        ], 
+        {substream => 1}
+    );
+
+    if(!$ctx->{bookbags}) {
+        $e->rollback;
+        return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+    }
+    
+    # get unique record IDs
+    my %rec_ids = ();
+    foreach my $bbag (@{$ctx->{bookbags}}) {
+        foreach my $rec_id (
+            map { $_->target_biblio_record_entry } @{$bbag->items}
+        ) {
+            $rec_ids{$rec_id} = 1;
+        }
+    }
+
+    $ctx->{bookbags_marc_xml} = $self->fetch_marc_xml_by_id([keys %rec_ids]);
+
+    $e->rollback;
+    return Apache2::Const::OK;
+}
+
+
+# actions are create, delete, show, hide, rename, add_rec, delete_item
+# CGI is action, list=list_id, add_rec/record=bre_id, del_item=bucket_item_id, name=new_bucket_name
+sub load_myopac_bookbag_update {
+    my ($self, $action, $list_id) = @_;
+    my $e = $self->editor;
+    my $cgi = $self->cgi;
+
+    $action ||= $cgi->param('action');
+    $list_id ||= $cgi->param('list');
+
+    my @add_rec = $cgi->param('add_rec') || $cgi->param('record');
+    my @del_item = $cgi->param('del_item');
+    my $shared = $cgi->param('shared');
+    my $name = $cgi->param('name');
+    my $success = 0;
+    my $list;
+
+    if($action eq 'create') {
+        $list = Fieldmapper::container::biblio_record_entry_bucket->new;
+        $list->name($name);
+        $list->owner($e->requestor->id);
+        $list->btype('bookbag');
+        $list->pub($shared ? 't' : 'f');
+        $success = $U->simplereq('open-ils.actor', 
+            'open-ils.actor.container.create', $e->authtoken, 'biblio', $list)
+
+    } else {
+
+        $list = $e->retrieve_container_biblio_record_entry_bucket($list_id);
+
+        return Apache2::Const::HTTP_BAD_REQUEST unless 
+            $list and $list->owner == $e->requestor->id;
+    }
+
+    if($action eq 'delete') {
+        $success = $U->simplereq('open-ils.actor', 
+            'open-ils.actor.container.full_delete', $e->authtoken, 'biblio', $list_id);
+
+    } elsif($action eq 'show') {
+        unless($U->is_true($list->pub)) {
+            $list->pub('t');
+            $success = $U->simplereq('open-ils.actor', 
+                'open-ils.actor.container.update', $e->authtoken, 'biblio', $list);
+        }
+
+    } elsif($action eq 'hide') {
+        if($U->is_true($list->pub)) {
+            $list->pub('f');
+            $success = $U->simplereq('open-ils.actor', 
+                'open-ils.actor.container.update', $e->authtoken, 'biblio', $list);
+        }
+
+    } elsif($action eq 'rename') {
+        if($name) {
+            $list->name($name);
+            $success = $U->simplereq('open-ils.actor', 
+                'open-ils.actor.container.update', $e->authtoken, 'biblio', $list);
+        }
+
+    } elsif($action eq 'add_rec') {
+        foreach my $add_rec (@add_rec) {
+            my $item = Fieldmapper::container::biblio_record_entry_bucket_item->new;
+            $item->bucket($list_id);
+            $item->target_biblio_record_entry($add_rec);
+            $success = $U->simplereq('open-ils.actor', 
+                'open-ils.actor.container.item.create', $e->authtoken, 'biblio', $item);
+            last unless $success;
+        }
+
+    } elsif($action eq 'del_item') {
+        foreach (@del_item) {
+            $success = $U->simplereq(
+                'open-ils.actor',
+                'open-ils.actor.container.item.delete', $e->authtoken, 'biblio', $_
+            );
+            last unless $success;
+        }
+    }
+
+    return $self->generic_redirect if $success;
+
+    $self->ctx->{bucket_action} = $action;
+    $self->ctx->{bucket_action_failed} = 1;
+    return Apache2::Const::OK;
+}
+
+1
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Container.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Container.pm
new file mode 100644 (file)
index 0000000..7f87104
--- /dev/null
@@ -0,0 +1,130 @@
+package OpenILS::WWW::EGCatLoader;
+use strict; use warnings;
+use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+use constant COOKIE_ANON_CACHE => 'anoncache';
+use constant ANON_CACHE_MYLIST => 'mylist';
+
+# Retrieve the users cached records AKA 'My List'
+# Returns an empty list if there are no cached records
+sub fetch_mylist {
+    my ($self, $with_marc_xml) = @_;
+
+    my $list = [];
+    my $cache_key = $self->cgi->cookie(COOKIE_ANON_CACHE);
+
+    if($cache_key) {
+
+        $list = $U->simplereq(
+            'open-ils.actor',
+            'open-ils.actor.anon_cache.get_value', 
+            $cache_key, ANON_CACHE_MYLIST);
+
+        if(!$list) {
+            $cache_key = undef;
+            $list = [];
+        }
+    }
+
+    my $marc_xml;
+    if ($with_marc_xml) {
+        $marc_xml = $self->fetch_marc_xml_by_id($list);
+    }
+
+    return ($cache_key, $list, $marc_xml);
+}
+
+
+# Adds a record (by id) to My List, creating a new anon cache + list if necessary.
+sub load_mylist_add {
+    my $self = shift;
+    my $rec_id = $self->cgi->param('record');
+
+    my ($cache_key, $list) = $self->fetch_mylist;
+    push(@$list, $rec_id);
+
+    $cache_key = $U->simplereq(
+        'open-ils.actor',
+        'open-ils.actor.anon_cache.set_value', 
+        $cache_key, ANON_CACHE_MYLIST, $list);
+
+    return $self->mylist_action_redirect($cache_key);
+}
+
+# Removes a record ID from My List, or moves to an actual bookbag
+sub load_mylist_move {
+    my $self = shift;
+    my @rec_ids = $self->cgi->param('record');
+
+    my ($cache_key, $list) = $self->fetch_mylist;
+    return $self->mylist_action_redirect unless $cache_key;
+
+    my @keep;
+    foreach my $id (@$list) { push(@keep, $id) unless grep { $id eq $_ } @rec_ids; }
+
+    $cache_key = $U->simplereq(
+        'open-ils.actor',
+        'open-ils.actor.anon_cache.set_value', 
+        $cache_key, ANON_CACHE_MYLIST, \@keep
+    );
+
+    if ($self->ctx->{user} and $self->cgi->param('action') =~ /^\d+$/) {
+        # in this case, action becomes list_id
+        $self->load_myopac_bookbag_update('add_rec', $self->cgi->param('action'));
+        # XXX TODO: test for failure of above
+    }
+
+    return $self->mylist_action_redirect($cache_key);
+}
+
+sub load_cache_clear {
+    my $self = shift;
+    $self->clear_anon_cache;
+    return $self->mylist_action_redirect;
+}
+
+# Wipes the entire anonymous cache, including My List
+sub clear_anon_cache {
+    my $self = shift;
+    my $field = shift;
+
+    my $cache_key = $self->cgi->cookie(COOKIE_ANON_CACHE) or return;
+
+    $U->simplereq(
+        'open-ils.actor',
+        'open-ils.actor.anon_cache.delete_session', $cache_key)
+        if $cache_key;
+
+}
+
+# Called after an anon-cache / My List action occurs.  Redirect
+# to the redirect_url (cgi param) or referrer or home.
+sub mylist_action_redirect {
+    my $self = shift;
+    my $cache_key = shift;
+
+    return $self->generic_redirect(
+        undef, 
+        $self->cgi->cookie(
+            -name => COOKIE_ANON_CACHE,
+            -path => '/',
+            -value => ($cache_key) ? $cache_key : '',
+            -expires => ($cache_key) ? undef : '-1h'
+        )
+    );
+}
+
+sub load_mylist {
+    my ($self) = shift;
+    (undef, $self->ctx->{mylist}, $self->ctx->{mylist_marc_xml}) =
+        $self->fetch_mylist(1);
+
+    return Apache2::Const::OK;
+}
+
+1;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Record.pm
new file mode 100644 (file)
index 0000000..533cb7e
--- /dev/null
@@ -0,0 +1,280 @@
+package OpenILS::WWW::EGCatLoader;
+use strict; use warnings;
+use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+# context additions: 
+#   record : bre object
+sub load_record {
+    my $self = shift;
+    my $ctx = $self->ctx;
+    $ctx->{page} = 'record';
+
+    my $org = $self->cgi->param('loc') || $ctx->{aou_tree}->()->id;
+    my $depth = $self->cgi->param('depth') || 0;
+    my $copy_limit = int($self->cgi->param('copy_limit') || 10);
+    my $copy_offset = int($self->cgi->param('copy_offset') || 0);
+
+    my $rec_id = $ctx->{page_args}->[0]
+        or return Apache2::Const::HTTP_BAD_REQUEST;
+
+    # run copy retrieval in parallel to bib retrieval
+    # XXX unapi
+    my $cstore = OpenSRF::AppSession->create('open-ils.cstore');
+    my $copy_rec = $cstore->request(
+        'open-ils.cstore.json_query.atomic', 
+        $self->mk_copy_query($rec_id, $org, $depth, $copy_limit, $copy_offset)
+    );
+
+    my (undef, @rec_data) = $self->get_records_and_facets([$rec_id], undef, {flesh => '{holdings_xml,mra,acp}'});
+    $ctx->{bre_id} = $rec_data[0]->{id};
+    $ctx->{marc_xml} = $rec_data[0]->{marc_xml};
+
+    $ctx->{copies} = $copy_rec->gather(1);
+    $ctx->{copy_limit} = $copy_limit;
+    $ctx->{copy_offset} = $copy_offset;
+
+    $ctx->{have_holdings_to_show} = 0;
+    $self->get_hold_copy_summary($rec_id, $org);
+
+    $cstore->kill_me;
+
+    # XXX TODO we'll also need conditional logic to show MFHD-based holdings
+    if (
+        $ctx->{get_org_setting}->
+            ($org, "opac.fully_compressed_serial_holdings")
+    ) {
+        $ctx->{holding_summaries} =
+            $self->get_holding_summaries($rec_id, $org, $depth);
+
+        $ctx->{have_holdings_to_show} =
+            scalar(@{$ctx->{holding_summaries}->{basic}}) ||
+            scalar(@{$ctx->{holding_summaries}->{index}}) ||
+            scalar(@{$ctx->{holding_summaries}->{supplement}});
+    }
+
+    my %expandies = (
+        marchtml => sub {
+            $ctx->{marchtml} = $self->mk_marc_html($rec_id);
+        },
+        issues => sub {
+            $ctx->{expanded_holdings} =
+                $self->get_expanded_holdings($rec_id, $org, $depth)
+                if $ctx->{have_holdings_to_show};
+        },
+        cnbrowse => sub {
+            $self->prepare_browse_call_numbers();
+        }
+    );
+
+    my @expand = $self->cgi->param('expand');
+    if (grep {$_ eq 'all'} @expand) {
+        $ctx->{expand_all} = 1;
+        $expandies{$_}->() for keys %expandies;
+
+    } else {
+        for my $exp (@expand) {
+            $ctx->{"expand_$exp"} = 1;
+            $expandies{$exp}->() if exists $expandies{$exp};
+        }
+    }
+
+    return Apache2::Const::OK;
+}
+
+sub mk_copy_query {
+    my $self = shift;
+    my $rec_id = shift;
+    my $org = shift;
+    my $depth = shift;
+    my $copy_limit = shift;
+    my $copy_offset = shift;
+
+    my $query = {
+        select => {
+            acp => ['id', 'barcode', 'circ_lib', 'create_date', 'age_protect', 'holdable'],
+            acpl => [
+                {column => 'name', alias => 'copy_location'},
+                {column => 'holdable', alias => 'location_holdable'}
+            ],
+            ccs => [
+                {column => 'name', alias => 'copy_status'},
+                {column => 'holdable', alias => 'status_holdable'}
+            ],
+            acn => [
+                {column => 'label', alias => 'call_number_label'},
+                {column => 'id', alias => 'call_number'}
+            ],
+            circ => ['due_date'],
+        },
+
+        from => {
+            acp => {
+                acn => {
+                    join => {bre => {filter => {id => $rec_id }}},
+                    filter => {deleted => 'f'}
+                },
+                circ => { # If the copy is circulating, retrieve the open circ
+                    type => 'left',
+                    filter => {checkin_time => undef}
+                },
+                acpl => {},
+                ccs => {},
+                aou => {}
+            }
+        },
+
+        where => {'+acp' => {deleted => 'f' }},
+
+        order_by => [
+            {class => 'aou', field => 'name'}, 
+            {class => 'acn', field => 'label'}
+        ],
+
+        limit => $copy_limit,
+        offset => $copy_offset
+    };
+
+    # XXX In the future, $sort_org should be understood to be an abstration
+    # that refers to something configurable, not necessariyl orig_loc.
+
+    if (my $sort_org = $self->ctx->{orig_loc}) {
+        unshift @{$query->{order_by}}, {
+            class => 'acp', field => 'circ_lib', transform => 'numeric_eq',
+            params => [$sort_org], direction => 'desc'
+        };
+    }
+
+    if($org != $self->ctx->{aou_tree}->()->id) { 
+        # no need to add the org join filter if we're not actually filtering
+        $query->{from}->{acp}->{aou} = {
+            fkey => 'circ_lib',
+            field => 'id',
+            filter => {
+                id => {
+                    in => {
+                        select => {aou => [{
+                            column => 'id', 
+                            transform => 'actor.org_unit_descendants', 
+                            result_field => 'id', 
+                            params => [$depth]
+                        }]},
+                        from => 'aou',
+                        where => {id => $org}
+                    }
+                }
+            }
+        }
+    };
+
+    # Filter hidden items if this is the public catalog
+    unless($self->ctx->{is_staff}) { 
+        $query->{where}->{'+acp'}->{opac_visible} = 't';
+        $query->{from}->{'acp'}->{'acpl'}->{filter} = {opac_visible => 't'};
+        $query->{from}->{'acp'}->{'ccs'}->{filter} = {opac_visible => 't'};
+    }
+
+    return $query;
+}
+
+sub mk_marc_html {
+    my($self, $rec_id) = @_;
+
+    # could be optimized considerably by performing the xslt on the already fetched record
+    return $U->simplereq(
+        'open-ils.search', 
+        'open-ils.search.biblio.record.html', $rec_id, 1);
+}
+
+sub get_holding_summaries {
+    my ($self, $rec_id, $org, $depth) = @_;
+
+    my $serial = create OpenSRF::AppSession("open-ils.serial");
+    my $result = $serial->request(
+        "open-ils.serial.bib.summary_statements",
+        $rec_id, {"org_id" => $org, "depth" => $depth}
+    )->gather(1);
+
+    $serial->kill_me;
+    return $result;
+}
+
+sub get_expanded_holdings {
+    my ($self, $rec_id, $org, $depth) = @_;
+
+    my $holding_limit = int($self->cgi->param("holding_limit") || 10);
+    my $holding_offset = int($self->cgi->param("holding_offset") || 0);
+    my $type = $self->cgi->param("expand_holding_type");
+
+    my $serial =  create OpenSRF::AppSession("open-ils.serial");
+    my $result = $serial->request(
+        "open-ils.serial.received_siss.retrieve.by_bib.atomic",
+        $rec_id, {
+            "ou" => $org, "depth" => $depth,
+            "limit" => $holding_limit, "offset" => $holding_offset,
+            "type" => $type
+        }
+    )->gather(1);
+
+    $serial->kill_me;
+    return $result;
+}
+
+sub any_call_number_label {
+    my ($self) = @_;
+
+    if ($self->ctx->{copies} and @{$self->ctx->{copies}}) {
+        return $self->ctx->{copies}->[0]->{call_number_label};
+    } else {
+        return;
+    }
+}
+
+sub prepare_browse_call_numbers {
+    my ($self) = @_;
+
+    my $cn = ($self->cgi->param("cn") || $self->any_call_number_label) or
+        return [];
+
+    my $org_unit = $self->ctx->{get_aou}->($self->cgi->param('loc')) ||
+        $self->ctx->{aou_tree}->();
+
+    my $supercat = create OpenSRF::AppSession("open-ils.supercat");
+    my $results = $supercat->request(
+        "open-ils.supercat.call_number.browse", 
+        $cn, $org_unit->shortname, 9, $self->cgi->param("cnoffset")
+    )->gather(1) || [];
+
+    $supercat->kill_me;
+
+    $self->ctx->{browsed_call_numbers} = [
+        map {
+            $_->record->marc(
+                (new XML::LibXML)->parse_string($_->record->marc)
+            );
+            $_;
+        } @$results
+    ];
+    $self->ctx->{browsing_ou} = $org_unit;
+}
+
+sub get_hold_copy_summary {
+    my ($self, $rec_id, $org) = @_;
+    
+    my $search = OpenSRF::AppSession->create('open-ils.search');
+    my $req1 = $search->request(
+        'open-ils.search.biblio.record.copy_count', $org, $rec_id); 
+
+    $self->ctx->{record_hold_count} = $U->simplereq(
+        'open-ils.circ', 'open-ils.circ.bre.holds.count', $rec_id);
+
+    $self->ctx->{copy_summary} = $req1->recv->content;
+
+    $search->kill_me;
+}
+
+1;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm
new file mode 100644 (file)
index 0000000..dcf4cc8
--- /dev/null
@@ -0,0 +1,364 @@
+package OpenILS::WWW::EGCatLoader;
+use strict; use warnings;
+use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+use OpenSRF::Utils::JSON;
+use Data::Dumper;
+$Data::Dumper::Indent = 0;
+my $U = 'OpenILS::Application::AppUtils';
+
+
+sub _prepare_biblio_search_basics {
+    my ($cgi) = @_;
+
+    return $cgi->param('query') unless $cgi->param('qtype');
+
+    my %parts;
+    my @part_names = qw/qtype contains query bool/;
+    $parts{$_} = [ $cgi->param($_) ] for (@part_names);
+
+    my $full_query = '';
+    for (my $i = 0; $i < scalar @{$parts{'qtype'}}; $i++) {
+        my ($qtype, $contains, $query, $bool) = map { $parts{$_}->[$i] } @part_names;
+
+        next unless $query =~ /\S/;
+
+        # This stuff probably will need refined or rethought to better handle
+        # the weird things Real Users will surely type in.
+        $contains = "" unless defined $contains; # silence warning
+        if ($contains eq 'nocontains') {
+            $query =~ s/"//g;
+            $query = ('"' . $query . '"') if index $query, ' ';
+            $query = '-' . $query;
+        } elsif ($contains eq 'phrase') {
+            $query =~ s/"//g;
+            $query = ('"' . $query . '"') if index $query, ' ';
+        } elsif ($contains eq 'exact') {
+            $query =~ s/[\^\$]//g;
+            $query = '^' . $query . '$';
+        }
+        $query = "$qtype:$query" unless $qtype eq 'keyword' and $i == 0;
+
+        $bool = ($bool and $bool eq 'or') ? '||' : '&&';
+        $full_query = $full_query ? "($full_query $bool $query)" : $query;
+    }
+
+    return $full_query;
+}
+
+sub _prepare_biblio_search {
+    my ($cgi, $ctx) = @_;
+
+    my $query = _prepare_biblio_search_basics($cgi) || '';
+
+    $query = ('#' . $_ . ' ' . $query) foreach ($cgi->param('modifier'));
+
+    # filters
+    foreach (grep /^fi:/, $cgi->param) {
+        /:(-?\w+)$/ or next;
+        my $term = join(",", $cgi->param($_));
+        $query .= " $1($term)" if length $term;
+    }
+
+    if ($cgi->param('sort')) {
+        my ($axis, $desc) = split /\./, $cgi->param('sort');
+        $query .= " sort($axis)";
+        $query .= '#descending' if $desc;
+    }
+
+    if ($cgi->param('pubdate') && $cgi->param('date1')) {
+        if ($cgi->param('pubdate') eq 'between') {
+            $query .= ' between(' . $cgi->param('date1');
+            $query .= ',' .  $cgi->param('date2') if $cgi->param('date2');
+            $query .= ')';
+        } elsif ($cgi->param('pubdate') eq 'is') {
+            $query .= ' between(' . $cgi->param('date1') .
+                ',' .  $cgi->param('date1') . ')';  # sic, date1 twice
+        } else {
+            $query .= ' ' . $cgi->param('pubdate') .
+                '(' . $cgi->param('date1') . ')';
+        }
+    }
+
+    my $site;
+    my $org = $cgi->param('loc');
+    if (defined($org) and $org ne '' and ($org ne $ctx->{aou_tree}->()->id) and not $query =~ /site\(\S+\)/) {
+        $site = $ctx->{get_aou}->($org)->shortname;
+        $query .= " site($site)";
+    }
+
+    if(!$site) {
+        ($site) = ($query =~ /site\(([^\)]+)\)/);
+        $site ||= $ctx->{aou_tree}->()->shortname;
+    }
+
+
+    my $depth;
+    if (defined($cgi->param('depth')) and not $query =~ /depth\(\d+\)/) {
+        $depth = defined $cgi->param('depth') ?
+            $cgi->param('depth') : $ctx->{get_aou}->($site)->ou_type->depth;
+        $query .= " depth($depth)";
+    }
+
+    return ($query, $site, $depth);
+}
+
+sub _get_search_limit {
+    my $self = shift;
+
+    # param takes precedence
+    my $limit = $self->cgi->param('limit');
+    return $limit if $limit;
+
+    if($self->editor->requestor) {
+        # See if the user has a hit count preference
+        my $lset = $self->editor->search_actor_user_setting({
+            usr => $self->editor->requestor->id, 
+            name => 'opac.hits_per_page'
+        })->[0];
+        return OpenSRF::Utils::JSON->JSON2perl($lset->value) if $lset;
+    }
+
+    return 10; # default
+}
+
+# context additions: 
+#   page_size
+#   hit_count
+#   records : list of bre's and copy-count objects
+sub load_rresults {
+    my $self = shift;
+    my $cgi = $self->cgi;
+    my $ctx = $self->ctx;
+    my $e = $self->editor;
+
+    $ctx->{page} = 'rresult';
+
+    # Special alternative searches here.  This could all stand to be cleaner.
+    if ($cgi->param("_special")) {
+        return $self->marc_expert_search if scalar($cgi->param("tag"));
+        return $self->item_barcode_shortcut if (
+            $cgi->param("qtype") and ($cgi->param("qtype") eq "item_barcode")
+        );
+        return $self->call_number_browse_standalone if (
+            $cgi->param("qtype") and ($cgi->param("qtype") eq "cnbrowse")
+        );
+    }
+
+    my $page = $cgi->param('page') || 0;
+    my $facet = $cgi->param('facet');
+    my $limit = $self->_get_search_limit;
+    my $loc = $cgi->param('loc') || $ctx->{aou_tree}->()->id;
+    my $offset = $page * $limit;
+    my $metarecord = $cgi->param('metarecord');
+    my $results; 
+
+    my ($query, $site, $depth) = _prepare_biblio_search($cgi, $ctx);
+
+    if ($metarecord) {
+
+        # TODO: other limits, like SVF/format, etc.
+        $results = $U->simplereq(
+            'open-ils.search', 
+            'open-ils.search.biblio.metarecord_to_records',
+            $metarecord, {org => $loc, depth => $depth}
+        );
+
+        # force the metarecord result blob to match the format of regular search results
+        $results->{ids} = [map { [$_] } @{$results->{ids}}]; 
+
+    } else {
+
+        return $self->generic_redirect unless $query;
+
+        # Limit and offset will stay here. Everything else should be part of
+        # the query string, not special args.
+        my $args = {'limit' => $limit, 'offset' => $offset};
+
+        # Stuff these into the TT context so that templates can use them in redrawing forms
+        $ctx->{processed_search_query} = $query;
+
+        $query = "$query $facet" if $facet; # TODO
+
+        $logger->activity("EGWeb: [search] $query");
+
+        try {
+
+            my $method = 'open-ils.search.biblio.multiclass.query';
+            $method .= '.staff' if $ctx->{is_staff};
+            $results = $U->simplereq('open-ils.search', $method, $args, $query, 1);
+
+        } catch Error with {
+            my $err = shift;
+            $logger->error("multiclass search error: $err");
+            $results = {count => 0, ids => []};
+        };
+    }
+
+    my $rec_ids = [map { $_->[0] } @{$results->{ids}}];
+
+    $ctx->{records} = [];
+    $ctx->{search_facets} = {};
+    $ctx->{page_size} = $limit;
+    $ctx->{hit_count} = $results->{count};
+
+    return Apache2::Const::OK if @$rec_ids == 0;
+
+    my ($facets, @data) = $self->get_records_and_facets(
+        $rec_ids, $results->{facet_key}, 
+        {
+            flesh => '{holdings_xml,mra,acp}',
+            site => $site,
+            depth => $depth
+        }
+    );
+
+    # shove recs into context in search results order
+    for my $rec_id (@$rec_ids) {
+        push(
+            @{$ctx->{records}},
+            grep { $_->{id} == $rec_id } @data
+        );
+    }
+
+    $ctx->{search_facets} = $facets;
+
+    return Apache2::Const::OK;
+}
+
+# Searching by barcode is a special search that does /not/ respect any other
+# of the usual search parameters, not even the ones for sorting and paging!
+sub item_barcode_shortcut {
+    my ($self) = @_;
+
+    my $method = "open-ils.search.multi_home.bib_ids.by_barcode";
+    if (my $search = create OpenSRF::AppSession("open-ils.search")) {
+        my $rec_ids = $search->request(
+            $method, $self->cgi->param("query")
+        )->gather(1);
+        $search->kill_me;
+
+        if (ref $rec_ids ne 'ARRAY') {
+
+            if($U->event_equals($rec_ids, 'ASSET_COPY_NOT_FOUND')) {
+                $rec_ids = [];
+
+            } else {
+                if (defined $U->event_code($rec_ids)) {
+                    $self->apache->log->warn(
+                        "$method returned event: " . $U->event_code($rec_ids)
+                    );
+                } else {
+                    $self->apache->log->warn(
+                        "$method returned something unexpected: $rec_ids"
+                    );
+                }
+                return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+            }
+        }
+
+        my ($facets, @data) = $self->get_records_and_facets(
+            $rec_ids, undef, {flesh => "{holdings_xml,mra}"}
+        );
+
+        $self->ctx->{records} = [@data];
+        $self->ctx->{search_facets} = {};
+        $self->ctx->{hit_count} = scalar @data;
+        $self->ctx->{page_size} = $self->ctx->{hit_count};
+
+        return Apache2::Const::OK;
+    } {
+        $self->apache->log->warn("couldn't connect to open-ils.search");
+        return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+    }
+}
+
+# like item_barcode_search, this can't take all the usual search params, but
+# this one will at least do site, limit and page
+sub marc_expert_search {
+    my ($self) = @_;
+
+    my @tags = $self->cgi->param("tag");
+    my @subfields = $self->cgi->param("subfield");
+    my @terms = $self->cgi->param("term");
+
+    my $query = [];
+    for (my $i = 0; $i < scalar @tags; $i++) {
+        next if ($tags[$i] eq "" || $subfields[$i] eq "" || $terms[$i] eq "");
+        push @$query, {
+            "term" => $terms[$i],
+            "restrict" => [{"tag" => $tags[$i], "subfield" => $subfields[$i]}]
+        };
+    }
+
+    $logger->info("query for expert search: " . Dumper($query));
+    # loc, limit and offset
+    my $page = $self->cgi->param("page") || 0;
+    my $limit = $self->_get_search_limit;
+    my $org_unit = $self->cgi->param("loc") || $self->ctx->{aou_tree}->()->id;
+    my $offset = $page * $limit;
+
+    if (my $search = create OpenSRF::AppSession("open-ils.search")) {
+        my $results = $search->request(
+            "open-ils.search.biblio.marc", {
+                "searches" => $query, "org_unit" => $org_unit
+            }, $limit, $offset
+        )->gather(1);
+        $search->kill_me;
+
+        if (defined $U->event_code($results)) {
+            $self->apache->log->warn(
+                "open-ils.search.biblio.marc returned event: " .
+                $U->event_code($results)
+            );
+            return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        my ($facets, @data) = $self->get_records_and_facets(
+            # filter out nulls that will turn up here
+            [ grep { $_ } @{$results->{ids}} ],
+            undef, {flesh => "{holdings_xml,mra}"}
+        );
+
+        $self->ctx->{records} = [@data];
+        $self->ctx->{search_facets} = {};
+
+        $self->ctx->{page_size} = $limit;
+        $self->ctx->{hit_count} = $results->{count};
+
+        return Apache2::Const::OK;
+    } else {
+        $self->apache->log->warn("couldn't connect to open-ils.search");
+        return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+    }
+}
+
+sub call_number_browse_standalone {
+    my ($self) = @_;
+
+    if (my $cnfrag = $self->cgi->param("query")) {
+        my $url = sprintf(
+            'http%s://%s%s/cnbrowse?cn=%s',
+            $self->cgi->https ? "s" : "",
+            $self->apache->hostname,
+            $self->ctx->{opac_root},
+            $cnfrag # XXX some kind of escaping needed here?
+        );
+        return $self->generic_redirect($url);
+    } else {
+        return $self->generic_redirect; # return to search page
+    }
+}
+
+sub load_cnbrowse {
+    my ($self) = @_;
+
+    $self->prepare_browse_call_numbers();
+
+    return Apache2::Const::OK;
+}
+
+1;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm
new file mode 100644 (file)
index 0000000..6af3a7a
--- /dev/null
@@ -0,0 +1,250 @@
+package OpenILS::WWW::EGCatLoader;
+use strict; use warnings;
+use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+use OpenSRF::MultiSession;
+my $U = 'OpenILS::Application::AppUtils';
+
+my $ro_object_subs; # cached subs
+our %cache = ( # cached data
+    map => {aou => {}}, # others added dynamically as needed
+    list => {},
+    search => {},
+    org_settings => {}
+);
+
+sub init_ro_object_cache {
+    my $self = shift;
+    my $e = $self->editor;
+    my $ctx = $self->ctx;
+
+    if($ro_object_subs) {
+        # subs have been built.  insert into the context then move along.
+        $ctx->{$_} = $ro_object_subs->{$_} for keys %$ro_object_subs;
+        return;
+    }
+
+    # make all "field_safe" classes accesible by default in the template context
+    my @classes = grep {
+        ($Fieldmapper::fieldmap->{$_}->{field_safe} || '') =~ /true/i
+    } keys %{ $Fieldmapper::fieldmap };
+
+    for my $class (@classes) {
+
+        my $hint = $Fieldmapper::fieldmap->{$class}->{hint};
+        next if $hint eq 'aou'; # handled separately
+
+        my $ident_field =  $Fieldmapper::fieldmap->{$class}->{identity};
+        (my $eclass = $class) =~ s/Fieldmapper:://o;
+        $eclass =~ s/::/_/g;
+
+        my $list_key = "${hint}_list";
+        my $get_key = "get_$hint";
+        my $search_key = "search_$hint";
+
+        # Retrieve the full set of objects with class $hint
+        $ro_object_subs->{$list_key} = sub {
+            my $method = "retrieve_all_$eclass";
+            $cache{list}{$hint} = $e->$method() unless $cache{list}{$hint};
+            return $cache{list}{$hint};
+        };
+    
+        # locate object of class $hint with Ident field $id
+        $cache{map}{$hint} = {};
+        $ro_object_subs->{$get_key} = sub {
+            my $id = shift;
+            return $cache{map}{$hint}{$id} if $cache{map}{$hint}{$id}; 
+            ($cache{map}{$hint}{$id}) = grep { $_->$ident_field eq $id } @{$ro_object_subs->{$list_key}->()};
+            return $cache{map}{$hint}{$id};
+        };
+
+        # search for objects of class $hint where field=value
+        $cache{search}{$hint} = {};
+        $ro_object_subs->{$search_key} = sub {
+            my ($field, $val) = @_;
+            my $method = "search_$eclass";
+            $cache{search}{$hint}{$field} = {} unless $cache{search}{$hint}{$field};
+            $cache{search}{$hint}{$field}{$val} = $e->$method({$field => $val}) 
+                unless $cache{search}{$hint}{$field}{$val};
+            return $cache{search}{$hint}{$field}{$val};
+        };
+    }
+
+    $ro_object_subs->{aou_tree} = sub {
+
+        # fetch the org unit tree
+        unless($cache{aou_tree}) {
+            my $tree = $e->search_actor_org_unit([
+                           {   parent_ou => undef},
+                           {   flesh            => -1,
+                                   flesh_fields    => {aou =>  ['children']},
+                                   order_by        => {aou => 'name'}
+                           }
+                   ])->[0];
+
+            # flesh the org unit type for each org unit
+            # and simultaneously set the id => aou map cache
+            sub flesh_aout {
+                my $node = shift;
+                my $ro_object_subs = shift;
+                $node->ou_type( $ro_object_subs->{get_aout}->($node->ou_type) );
+                $cache{map}{aou}{$node->id} = $node;
+                flesh_aout($_, $ro_object_subs) foreach @{$node->children};
+            };
+            flesh_aout($tree, $ro_object_subs);
+
+            $cache{aou_tree} = $tree;
+        }
+
+        return $cache{aou_tree};
+    };
+
+    # Add a special handler for the tree-shaped org unit cache
+    $ro_object_subs->{get_aou} = sub {
+        my $org_id = shift;
+        return undef unless defined $org_id;
+        $ro_object_subs->{aou_tree}->(); # force the org tree to load
+        return $cache{map}{aou}{$org_id};
+    };
+
+    # turns an ISO date into something TT can understand
+    $ro_object_subs->{parse_datetime} = sub {
+        my $date = shift;
+        $date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($date));
+        return sprintf(
+            "%0.2d:%0.2d:%0.2d %0.2d-%0.2d-%0.4d",
+            $date->hour,
+            $date->minute,
+            $date->second,
+            $date->day,
+            $date->month,
+            $date->year
+        );
+    };
+
+    # retrieve and cache org unit setting values
+    $ro_object_subs->{get_org_setting} = sub {
+        my($org_id, $setting) = @_;
+
+        $cache{org_settings}{$org_id} = {} 
+            unless $cache{org_settings}{$org_id};
+
+        $cache{org_settings}{$org_id}{$setting} = 
+            $U->ou_ancestor_setting_value($org_id, $setting)
+                unless exists $cache{org_settings}{$org_id}{$setting};
+
+        return $cache{org_settings}{$org_id}{$setting};
+    };
+
+    $ctx->{$_} = $ro_object_subs->{$_} for keys %$ro_object_subs;
+}
+
+sub generic_redirect {
+    my $self = shift;
+    my $url = shift;
+    my $cookie = shift; # can be an array of cgi.cookie's
+
+    $self->apache->print(
+        $self->cgi->redirect(
+            -url => $url || 
+                $self->cgi->param('redirect_to') || 
+                $self->ctx->{referer} || 
+                $self->ctx->{home_page},
+            -cookie => $cookie
+        )
+    );
+
+    return Apache2::Const::REDIRECT;
+}
+
+sub get_records_and_facets {
+    my ($self, $rec_ids, $facet_key, $unapi_args) = @_;
+
+    $unapi_args ||= {};
+    $unapi_args->{site} ||= $self->ctx->{aou_tree}->()->shortname;
+    $unapi_args->{depth} ||= $self->ctx->{aou_tree}->()->ou_type->depth;
+    $unapi_args->{flesh_depth} ||= 5;
+
+    my @data;
+    my $ses = OpenSRF::MultiSession->new(
+        app => 'open-ils.cstore',
+        cap => 10, # XXX config
+        success_handler => sub {
+            my($self, $req) = @_;
+            my $data = $req->{response}->[0]->content;
+            my $xml = XML::LibXML->new->parse_string($data->{'unapi.bre'})->documentElement;
+            my $bre_id =  $xml->find('*[@tag="901"]/*[@code="c"]')->[0]->textContent;
+            push(@data, {id => $bre_id, marc_xml => $xml});
+        }
+    );
+
+    $ses->request(
+        'open-ils.cstore.json_query',
+         {from => [
+            'unapi.bre', $_, 'marcxml','record', 
+            $unapi_args->{flesh}, 
+            $unapi_args->{site}, 
+            $unapi_args->{depth}, 
+            $unapi_args->{flesh_depth}, 
+        ]}
+    ) for @$rec_ids;
+
+    # collect the facet data
+    my $search = OpenSRF::AppSession->create('open-ils.search');
+    my $facet_req = $search->request(
+        'open-ils.search.facet_cache.retrieve', $facet_key, 10
+    ) if $facet_key;
+
+    # gather up the unapi recs
+    $ses->session_wait(1);
+
+    my $facets;
+    if ($facet_key) {
+        $facets = $facet_req->gather(1);
+        $facets->{$_} = {
+            cmf => $self->ctx->{get_cmf}->($_),
+            data => $facets->{$_}
+        } for keys %$facets;    # quick-n-dirty
+    } else {
+        $facets = undef;
+    }
+
+    $search->kill_me;
+
+    return ($facets, @data);
+}
+
+# TODO: blend this code w/ ^-- get_records_and_facets
+sub fetch_marc_xml_by_id {
+    my ($self, $id_list) = @_;
+    $id_list = [$id_list] unless ref($id_list);
+
+    {
+        no warnings qw/numeric/;
+        $id_list = [map { int $_ } @$id_list];
+        $id_list = [grep { $_ > 0} @$id_list];
+    };
+
+    return {} if scalar(@$id_list) < 1;
+
+    # I'm just sure there needs to be some more efficient way to get all of
+    # this.
+    my $results = $self->editor->json_query({
+        "select" => {"bre" => ["id", "marc"]},
+        "from" => {"bre" => {}},
+        "where" => {"id" => $id_list}
+    }, {substream => 1}) or return $self->editor->die_event;
+
+    my $marc_xml = {};
+    for my $r (@$results) {
+        $marc_xml->{$r->{"id"}} =
+            (new XML::LibXML)->parse_string($r->{"marc"});
+    }
+
+    return $marc_xml;
+}
+
+1;
index ec765af..8226be7 100644 (file)
@@ -4,23 +4,26 @@ use Template;
 use XML::Simple;
 use XML::LibXML;
 use File::stat;
+use Encode;
 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';
-use constant OILS_HTTP_COOKIE_LOCALE => 'oils:locale';
+use constant OILS_HTTP_COOKIE_SKIN => 'eg_skin';
+use constant OILS_HTTP_COOKIE_THEME => 'eg_theme';
+use constant OILS_HTTP_COOKIE_LOCALE => 'eg_locale';
 
 my $web_config;
 my $web_config_file;
 my $web_config_edit_time;
+my %lh_cache; # locale handlers
 
 sub import {
     my $self = shift;
     $web_config_file = shift || '';
     unless(-r $web_config_file) {
-        warn "Invalid web config $web_config_file";
+        warn "Invalid web config $web_config_file\n";
         return;
     }
     check_web_config();
@@ -32,26 +35,92 @@ 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($template, $page_args, $as_xml) = find_template($r, $base, $ctx);
+    $ctx->{page_args} = $page_args;
+
+    my $stat = run_context_loader($r, $ctx);
+
+    return $stat unless $stat == Apache2::Const::OK;
     return Apache2::Const::DECLINED unless $template;
 
     $template = $ctx->{skin} . "/$template";
-    $ctx->{page_args} = $page_args;
-    $r->content_type('text/html; encoding=utf8');
+
+    my $text_handler = set_text_handler($ctx, $r);
 
     my $tt = Template->new({
         OUTPUT => ($as_xml) ?  sub { parse_as_xml($r, $ctx, @_); } : $r,
         INCLUDE_PATH => $ctx->{template_paths},
+        DEBUG => $ctx->{debug_template},
+        PLUGINS => {
+            EGI18N => 'OpenILS::WWW::EGWeb::I18NFilter',
+            CGI_utf8 => 'OpenILS::WWW::EGWeb::CGI_utf8'
+        },
+        FILTERS => {
+            # Register a dynamic filter factory for our locale::maketext generator
+            l => [
+                sub {
+                    my($ctx, @args) = @_;
+                    return sub { $text_handler->(shift(), @args); }
+                }, 1
+            ]
+        }
     });
 
-    unless($tt->process($template, {ctx => $ctx})) {
-        $r->log->warn('Template error: ' . $tt->error);
+    $ctx->{encode_utf8} = sub {return encode_utf8(shift())};
+
+    unless($tt->process($template, {ctx => $ctx, ENV => \%ENV, l => $text_handler})) {
+        $r->log->warn('egweb: template error: ' . $tt->error);
         return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
     }
 
     return Apache2::Const::OK;
 }
 
+sub set_text_handler {
+    my $ctx = shift;
+    my $r = shift;
+
+    my $locale = $ctx->{locale};
+    $locale =~ s/-/_/g;
+
+    $r->log->debug("egweb: messages locale = $locale");
+
+    unless($lh_cache{$locale}) {
+        $r->log->info("egweb: Unsupported locale: $locale");
+        $lh_cache{$locale} = $lh_cache{'en_US'};
+    }
+
+    return sub { return $lh_cache{$locale}->maketext(@_); };
+}
+
+
+
+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("egweb: Context Loader error: $@");
+        return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    $r->log->debug("egweb: context loader resulted in status $stat");
+    return $stat;
+}
+
 sub parse_as_xml {
     my $r = shift;
     my $ctx = shift;
@@ -65,9 +134,9 @@ 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->log->error("egweb: $err");
         $r->content_type('text/plain; encoding=utf8');
         $r->print("\n$err\n\n$data");
     };
@@ -79,16 +148,23 @@ 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';
     $ctx->{theme} = $cgi->cookie(OILS_HTTP_COOKIE_THEME) || 'default';
+
     $ctx->{locale} = 
         $cgi->cookie(OILS_HTTP_COOKIE_LOCALE) || 
         parse_accept_lang($r->headers_in->get('Accept-Language')) || 'en-US';
-    $r->log->debug('skin = ' . $ctx->{skin} . ' : theme = ' . 
-        $ctx->{theme} . ' : locale = ' . $ctx->{locale});
+
+    my $mprefix = $ctx->{media_prefix};
+    if($mprefix and $mprefix !~ /^http/ and $mprefix !~ /^\//) {
+        # if a hostname is provided /w no protocol, match the protocol to the current page
+        $ctx->{media_prefix} = ($cgi->https) ? "https://$mprefix" : "http://$mprefix";
+    }
+
     return $ctx;
 }
 
@@ -146,7 +222,7 @@ sub find_template {
             last unless $localpath;
             for my $tpath (@{$ctx->{template_paths}}) {
                 my $fpath = "$tpath/$skin/$localpath.$ext";
-                $r->log->debug("looking at possible template $fpath");
+                $r->log->debug("egweb: looking at possible template $fpath");
                 if(-r $fpath) {
                     $template = "$localpath.$ext";
                     last;
@@ -161,12 +237,12 @@ sub find_template {
 
         # no template configured or found
         unless($template) {
-            $r->log->warn("No template configured for path $path");
+            $r->log->debug("egweb: No template configured for path $path");
             return ();
         }
     }
 
-    $r->log->debug("template = $template : page args = @$page_args");
+    $r->log->debug("egweb: template = $template : page args = @$page_args");
     return ($template, $page_args, $as_xml);
 }
 
@@ -176,12 +252,46 @@ sub check_web_config {
     my $r = shift;
     my $epoch = stat($web_config_file)->mtime;
     unless($web_config_edit_time and $web_config_edit_time == $epoch) {
-        $r->log->debug("Reloading web config after edit...") if $r;
+        $r->log->debug("egweb: Reloading web config after edit...") if $r;
         $web_config_edit_time = $epoch;
         $web_config = parse_config($web_config_file);
     }
 }
 
+# Create an I18N sub-module for each supported locale
+# Each module creates its own MakeText lexicon by parsing .po/.mo files
+sub load_locale_handlers {
+    my $ctx = shift;
+    my $locales = $ctx->{locales};
+
+    $locales->{en_US} = {} unless exists $locales->{en_US};
+
+    for my $lang (keys %$locales) {
+        my $messages = $locales->{$lang};
+        $messages = '' if ref $messages; # empty {}
+
+        # TODO Can we do this without eval?
+        my $eval = <<EVAL;
+            package OpenILS::WWW::EGWeb::I18N::$lang;
+            use base 'OpenILS::WWW::EGWeb::I18N';
+            if(\$messages) {
+                use Locale::Maketext::Lexicon::Gettext;
+                if(open F, '$messages') {
+                    our %Lexicon = (%Lexicon, %{ Locale::Maketext::Lexicon::Gettext->parse(<F>) });
+                    close F;
+                } else {
+                    warn "EGWeb: unable to open messages file: $messages"; 
+                }
+            }
+EVAL
+        eval $eval;
+        warn "$@\n" if $@; # TODO better logging
+        $lh_cache{$lang} = "OpenILS::WWW::EGWeb::I18N::$lang"->new;
+    }
+}
+
+
+
 sub parse_config {
     my $cfg_file = shift;
     my $data = XML::Simple->new->XMLin($cfg_file);
@@ -191,9 +301,12 @@ sub parse_config {
     $ctx->{media_prefix} = (ref $data->{media_prefix}) ? '' : $data->{media_prefix};
     $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->{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};
+    $ctx->{locales} = $data->{locales};
+    load_locale_handlers($ctx);
 
     my $tpaths = $data->{template_paths}->{path};
     $tpaths = [$tpaths] unless ref $tpaths;
@@ -217,7 +330,7 @@ sub parse_config {
         }
     }
 
-    return {ctx => $ctx, handlers => $handlers};
+    return {base_ctx => $ctx, handlers => $handlers};
 }
 
 package PathConfig;
@@ -226,5 +339,9 @@ sub new {
     return bless(\%args, $class);
 }
 
+# base class for all supported locales
+package OpenILS::WWW::EGWeb::I18N;
+use base 'Locale::Maketext';
+our %Lexicon = (_AUTO => 1);
 
 1;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb/CGI_utf8.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb/CGI_utf8.pm
new file mode 100644 (file)
index 0000000..0239e1b
--- /dev/null
@@ -0,0 +1,44 @@
+package OpenILS::WWW::EGWeb::CGI_utf8;
+
+# The code in this module is copied from (except for a tiny modification)
+# Template::Plugin::CGI, which is written by:
+#
+# Andy Wardley E<lt>abw@wardley.orgE<gt> L<http://wardley.org/>
+#
+# Copyright (C) 1996-2007 Andy Wardley.  All Rights Reserved.
+#
+# This module is free software; you can redistribute it and/or
+# modify it under the same terms as Perl itself.
+
+use strict;
+use warnings;
+use base 'Template::Plugin';
+use CGI qw(:all -utf8);
+
+sub new {
+    my $class   = shift;
+    my $context = shift;
+    new CGI(@_);
+}
+
+# monkeypatch CGI::params() method to Do The Right Thing in TT land
+
+sub CGI::params {
+    my $self = shift;
+    local $" = ', ';
+
+    return $self->{ _TT_PARAMS } ||= do {
+        # must call Vars() in a list context to receive
+        # plain list of key/vals rather than a tied hash
+        my $params = { $self->Vars() };
+
+        # convert any null separated values into lists
+        @$params{ keys %$params } = map { 
+            /\0/ ? [ split /\0/ ] : $_ 
+        } values %$params;
+
+        $params;
+    };
+}
+
+1;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb/I18NFilter.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb/I18NFilter.pm
new file mode 100644 (file)
index 0000000..6b6c6a8
--- /dev/null
@@ -0,0 +1,12 @@
+package OpenILS::WWW::EGWeb::I18NFilter;
+use Template::Plugin::Filter;
+use base qw(Template::Plugin::Filter);
+our $DYNAMIC = 1;
+
+sub filter {
+    my ($self, $text, $args) = @_;
+    return $maketext->($text, @$args);
+}
+
+1;
+
index c76cbf3..99667d0 100644 (file)
@@ -51,53 +51,90 @@ sub parse_ips_file {
 }
 
 
+my %org_cache;
 sub handler {
+       my $apache = shift;
 
+       my $cgi = CGI->new( $apache );
+       my $port = $cgi->server_port();
+       my $hostname = $cgi->server_name();
+    my $proto = ($cgi->https) ? 'https' : 'http';
        my $user_ip = $ENV{REMOTE_ADDR};
-       my $apache_obj = shift;
-       my $cgi = CGI->new( $apache_obj );
 
+    # Apache config values
+       my $skin = $apache->dir_config('OILSRedirectSkin') || 'default';
+       my $depth = $apache->dir_config('OILSRedirectDepth');
+       my $locale = $apache->dir_config('OILSRedirectLocale') || 'en-US';
+    my $use_tt = ($apache->dir_config('OILSRedirectTpac') || '') =~ /true/i;
+    my $orig_loc;
 
-       my $skin = $apache_obj->dir_config('OILSRedirectSkin') || 'default';
-       my $depth = $apache_obj->dir_config('OILSRedirectDepth');
-       my $locale = $apache_obj->dir_config('OILSRedirectLocale') || 'en-US';
+    $apache->log->debug("Redirector sees client frim $user_ip");
 
-       my $hostname = $cgi->server_name();
-       my $port                = $cgi->server_port();
+    # parse the IP file
+       my ($shortname, $nskin, $nhostname) = redirect_libs($user_ip);
 
-       my $proto = "http";
-       if($cgi->https) { $proto = "https"; }
+       if ($shortname) { # we have a config
 
-       my $url = "$proto://$hostname:$port/opac/$locale/skin/$skin/xml/index.xml";
-       my $path = $apache_obj->path_info();
+        # Read any override vars from the ips txt file
+               if ($nskin =~ m/[^\s]/) { $skin = $nskin; }
+               if ($nhostname =~ m/[^\s]/) { $hostname = $nhostname; }
 
-       $logger->debug("Apache client connecting from $user_ip");
+        if($org_cache{$shortname}) {
+            $orig_loc = $org_cache{$shortname};
 
-       my ($shortname, $nskin, $nhostname) = redirect_libs($user_ip);
-       if ($shortname) {
+        } else {
 
-               if ($nskin =~ m/[^\s]/) { $skin = $nskin; }
-               if ($nhostname =~ m/[^\s]/) { $hostname = $nhostname; }
+                   my $session = OpenSRF::AppSession->create("open-ils.actor");
+                   my $org = $session->request(
+                'open-ils.actor.org_unit.retrieve_by_shortname',
+                           $shortname)->gather(1);
+
+            $org_cache{$shortname} = $orig_loc = $org->id if $org;
+        }
+       }
 
-               $logger->info("Apache redirecting $user_ip to $shortname with skin $skin and host $hostname");
-               my $session = OpenSRF::AppSession->create("open-ils.actor");
+    my $url = "$proto://$hostname:$port";
 
-               $url = "$proto://$hostname:$port/opac/$locale/skin/$skin/xml/index.xml";
+    if($use_tt) {
 
-               my $org = $session->request(
-            'open-ils.actor.org_unit.retrieve_by_shortname',
-                        $shortname)->gather(1);
+        $url .= "/eg/opac/home";
+        $url .= "?orig_loc=$orig_loc" if $orig_loc;
 
-               if($org) { 
-            $url .= "?ol=" . $org->id; 
+=head potential locale/skin implementation
+        if($locale ne 'en-US') {
+            $apache->headers_out->add(
+                "Set-Cookie" => $cgi->cookie(
+                    -name => "oils:locale", # see EGWeb.pm
+                    -path => "/eg",
+                    -value => $locale,
+                    -expires => undef
+                )
+            );
+        }
+
+        if($skin ne 'default') {
+            $apache->headers_out->add(
+                "Set-Cookie" => $cgi->cookie(
+                    -name => "oils:skin", # see EGWeb.pm
+                    -path => "/eg",
+                    -value => $skin,
+                    -expires => undef
+                )
+            );
+        }
+=cut
+
+    } else {
+        $url .= "/opac/$locale/skin/$skin/xml/index.xml";
+        if($orig_loc) {
+            $url .= "?ol=" . $orig_loc;
             $url .= "&d=$depth" if defined $depth;
         }
-       }
+    }
 
-       print "Location: $url\n\n"; 
+    $logger->info("Apache redirecting $user_ip to $url");
+    $apache->headers_out->add('Location' => "$url");
        return Apache2::Const::REDIRECT;
-
-       return print_page($url);
 }
 
 sub redirect_libs {
@@ -113,7 +150,7 @@ sub redirect_libs {
                 my $range = new Net::IP( $block->[0] . ' - ' . $block->[1] );
                 if( $source_ip->overlaps($range)==$IP_A_IN_B_OVERLAP ||
                     $source_ip->overlaps($range)==$IP_IDENTICAL ) {
-                    return ($shortname, $block->[2], $block->[3]);
+                    return ($shortname, $block->[2] || '', $block->[3] || '');
                 }
             }
         }
@@ -121,40 +158,4 @@ sub redirect_libs {
        return 0;
 }
 
-
-sub print_page {
-
-       my $url = shift;
-
-       print "Content-type: text/html; charset=utf-8\n\n";
-       print <<"       HTML";
-       <html>
-               <head>
-                       <meta HTTP-EQUIV='Refresh' CONTENT="0; URL=$url"/> 
-                       <style  TYPE="text/css">
-                               .loading_div {
-                                       text-align:center;
-                                       margin-top:30px;
-                               font-weight:bold;
-                                               background: lightgrey;
-                                       color:black;
-                                       width:100%;
-                               }
-                       </style>
-               </head>
-               <body>
-                       <br/><br/>
-                       <div class="loading_div">
-                               <h4>Loading...</h4>
-                       </div>
-                       <br/><br/>
-                       <center><img src='/opac/images/main_logo.jpg'/></center>
-               </body>
-       </html>
-       HTML
-
-       return Apache2::Const::OK;
-}
-
-
 1;
diff --git a/Open-ILS/src/perlmods/lib/Template/Plugin/ResolverResolver.pm b/Open-ILS/src/perlmods/lib/Template/Plugin/ResolverResolver.pm
new file mode 100644 (file)
index 0000000..88109c2
--- /dev/null
@@ -0,0 +1,98 @@
+#
+# OpenILS::Template::Plugin::ResolverResolver
+#
+# DESCRIPTION
+#
+#   Simple Template Toolkit Plugin which hooks into Dan Scott's Resolver
+#
+# AUTHOR
+#   Art Rhyno <http://projectconifer.ca>
+#
+# COPYRIGHT
+#   Copyright (C) 2011
+#
+# LICENSE
+# GNU General Public License v2 or later
+#
+#============================================================================
+
+package Template::Plugin::ResolverResolver;
+
+use strict;
+use warnings;
+use base 'Template::Plugin';
+use OpenILS::Application::ResolverResolver;
+use OpenILS::Application;
+use base qw/OpenILS::Application/;
+use OpenSRF::AppSession;
+
+
+our $VERSION = 0.9;
+
+sub load {
+    my ( $class, $context ) = @_;
+    return $class;
+}   
+
+sub new { 
+    my ( $class, $context, @params ) = @_;
+
+    bless { _CONTEXT => $context, }, $class;   
+} 
+
+# monkeypatch ResolverResolver::params() method to Do The Right Thing in TT land
+
+sub ResolverResolver::params {
+    my $self = shift;
+    local $" = ', ';
+
+    return $self->{ _TT_PARAMS } ||= do {
+        # must call Vars() in a list context to receive
+        # plain list of key/vals rather than a tied hash
+        my $params = { $self->Vars() };
+
+        # convert any null separated values into lists
+        @$params{ keys %$params } = map { 
+            /\0/ ? [ split /\0/ ] : $_ 
+        } values %$params;
+
+        $params;
+    };
+}
+
+sub resolve_issn
+{
+    my ($class, $c, $baseurl) = @_;
+
+    if (length($c) <= 9) {
+           my $session = OpenSRF::AppSession->create("open-ils.resolver");
+       
+           my $request = $session->request("open-ils.resolver.resolve_holdings.raw", "issn", $c, $baseurl)->gather();
+           if ($request) {
+                 return $request;
+           }
+           $session->disconnect();
+    }
+       
+    return "";
+}
+
+sub resolve_isbn
+{
+    my ($class, $c, $baseurl) = @_;
+
+    my $session = OpenSRF::AppSession->create("open-ils.resolver");
+       
+    my $request = $session->request("open-ils.resolver.resolve_holdings.raw", "isbn", $c, $baseurl)->gather();
+       
+    if ($request) {
+            return $request;
+    }
+    $session->disconnect();
+       
+    return "";
+}
+
+
+1;
+
index 7f50cdb..2b39e7b 100644 (file)
@@ -2764,8 +2764,14 @@ INSERT into config.org_unit_setting_type
 ( 'org.patron_opt_default',
     oils_i18n_gettext( 'org.patron_opt_default', 'Circ: Patron Opt-In Default', 'coust', 'label'),
     oils_i18n_gettext( 'org.patron_opt_default', 'This is the default depth at which a patron is opted in; it is calculated as an org unit relative to the current workstation.', 'coust', 'label'),
-    'integer')
-,( 
+    'integer'),
+
+( 'opac.payment_history_age_limit',
+    oils_i18n_gettext( 'opac.payment_history_age_limit', 'OPAC: Payment History Age Limit', 'coust', 'label'),
+    oils_i18n_gettext( 'opac.payment_history_age_limit', 'The OPAC should not display payments by patrons that are older than any interval defined here.', 'coust', 'label'),
+    'interval'),
+
+( 
         'ui.circ.billing.uncheck_bills_and_unfocus_payment_box',
         oils_i18n_gettext(
             'ui.circ.billing.uncheck_bills_and_unfocus_payment_box',
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.opac_payment_history_age_limit.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.opac_payment_history_age_limit.sql
new file mode 100644 (file)
index 0000000..f8790ef
--- /dev/null
@@ -0,0 +1,17 @@
+-- Evergreen DB patch XXXX.data.opac_payment_history_age_limit.sql
+
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT into config.org_unit_setting_type (name, label, description, datatype)
+VALUES (
+    'opac.payment_history_age_limit',
+    oils_i18n_gettext('opac.payment_history_age_limit',
+        'OPAC: Payment History Age Limit', 'coust', 'label'),
+    oils_i18n_gettext('opac.payment_history_age_limit',
+        'The OPAC should not display payments by patrons that are older than any interval defined here.', 'coust', 'label'),
+    'interval'
+);
+
+COMMIT;
diff --git a/Open-ILS/src/support-scripts/test-scripts/unapi_bench.pl b/Open-ILS/src/support-scripts/test-scripts/unapi_bench.pl
new file mode 100755 (executable)
index 0000000..5b38b28
--- /dev/null
@@ -0,0 +1,336 @@
+#!/usr/bin/perl
+require '../oils_header.pl';
+use strict; use warnings;
+use Time::HiRes qw/time usleep/;
+use Data::Dumper;
+use OpenSRF::Utils::JSON;
+use OpenILS::Utils::CStoreEditor;
+use XML::LibXML;
+
+#-----------------------------------------------------------------------------
+# Does a checkout, renew, and checkin 
+#-----------------------------------------------------------------------------
+
+my @recs = (1,2,3,4,5,6,7,8,9,10);
+
+osrf_connect(shift() || '/openils/conf/opensrf_core.xml');
+
+my $e = OpenILS::Utils::CStoreEditor->new;
+
+sub xptext {
+    my($node, $path) = @_;
+    #my $res = $node->findnodes($path);
+    my $res = $node->find($path);
+    return '' unless $res and $res->[0];
+    return $res->[0]->textContent;
+}
+
+sub get_bib_attrs {
+    my $xml = shift;
+    return {
+        isbn => xptext($xml, '*[@tag="020"]/*[@code="a"]'),
+        upc => xptext($xml,'*[@tag="024"]/*[@code="a"]'),
+        issn => xptext($xml,'*[@tag="022"]/*[@code="a"]'),
+        title => xptext($xml,'*[@tag="245"]/*[@code="a"]'),
+        author => xptext($xml,'*[@tag="100"]/*[@code="a"]'),
+        publisher => xptext($xml,'*[@tag="260"]/*[@code="b"]'),
+        pubdate => xptext($xml,'*[@tag="260"]/*[@code="c"]'),
+        edition => xptext($xml,'*[@tag="250"]/*[@code="a"]'),
+    };
+}
+
+sub unapi {
+    my @recs = @_;
+    my $start = time();
+
+    my $ses1 = OpenSRF::AppSession->create('open-ils.cstore');
+    my $ses2 = OpenSRF::AppSession->create('open-ils.cstore');
+    my $ses3 = OpenSRF::AppSession->create('open-ils.cstore');
+    my ($req1, $req2, $req3);
+
+    my %records;
+    while(@recs) {
+        my ($id1, $id2, $id3) = (pop @recs, pop @recs, pop @recs);
+
+        for my $r ($req1, $req2, $req3) {
+            if($r) {
+                my $data = $r->gather(1);
+                my $xml = XML::LibXML->new->parse_string($data->{'unapi.bre'});
+                $xml = $xml->documentElement;
+                my $attrs = get_bib_attrs($xml);
+                my $rec_id =  xptext($xml,'*[@tag="901"]/*[@code="c"]');
+                $records{$rec_id}{$_} = $attrs->{$_} for keys %$attrs;
+
+                my $rvols = [];
+                for my $volnode ($xml->findnodes('//*[local-name()="volumes"]/*[local-name()="volume"]')) {
+                    my $vol = {}; 
+                    $vol->{copies} = [];
+                    $vol->{label} = $volnode->getAttribute('label');
+                    for my $copynode ($volnode->getElementsByLocalName('copy')) {
+                        my $copy = {};   
+                        $copy->{barcode} = $copynode->getAttribute('barcode');
+                        push(@{$vol->{copies}}, $copy);
+                    }
+                    push(@{$records{$rec_id}->{volumes}}, $vol);
+                }
+
+            }
+        }
+
+        $req1 = ($id1) ? $ses1->request('open-ils.cstore.json_query', {from => ['unapi.bre', $id1, 'marcxml', 'record', '{holdings_xml,acp}', 'CONS']}) : undef;
+        $req2 = ($id2) ? $ses1->request('open-ils.cstore.json_query', {from => ['unapi.bre', $id2, 'marcxml', 'record', '{holdings_xml,acp}', 'CONS']}) : undef;
+        $req3 = ($id3) ? $ses1->request('open-ils.cstore.json_query', {from => ['unapi.bre', $id3, 'marcxml', 'record', '{holdings_xml,acp}', 'CONS']}) : undef;
+    }
+
+
+    for my $r ($req1, $req2, $req3) {
+        if($r) {
+            my $data = $r->gather(1);
+            my $xml = XML::LibXML->new->parse_string($data->{'unapi.bre'});
+            $xml = $xml->documentElement;
+            my $attrs = get_bib_attrs($xml);
+            my $rec_id =  xptext($xml,'*[@tag="901"]/*[@code="c"]');
+            $records{$rec_id}{$_} = $attrs->{$_} for keys %$attrs;
+
+            my $rvols = [];
+            for my $volnode ($xml->findnodes('//*[local-name()="volumes"]/*[local-name()="volume"]')) {
+                my $vol = {}; 
+                $vol->{copies} = [];
+                $vol->{label} = $volnode->getAttribute('label');
+                for my $copynode ($volnode->getElementsByLocalName('copy')) {
+                    my $copy = {};   
+                    $copy->{barcode} = $copynode->getAttribute('barcode');
+                    push(@{$vol->{copies}}, $copy);
+                }
+                push(@{$records{$rec_id}->{volumes}}, $vol);
+            }
+
+        }
+    }
+
+    my $duration = time() - $start;
+
+    for my $rec_id (keys %records) {
+        my $rec = $records{$rec_id};
+        print sprintf("%d [%s] has %d volumes and %d copies\n",
+            $rec_id, $rec->{title}, 
+            scalar(@{$rec->{volumes}}),
+            scalar(map { @{$_->{copies}} } @{$rec->{volumes}}));
+    }
+
+    #note, unapi.biblio_record_entry_feed per record performs the same as unapi.bre pre record
+    print "\nunapi 'unapi.bre' duration is $duration\n\n";
+}
+
+sub unapi_spread {
+    my @recs = @_;
+    my %records;
+    my $start = time();
+
+    my @reqs;
+    for my $rec_id (@recs) {
+
+        my $ses = OpenSRF::AppSession->create('open-ils.cstore');
+        my $req = $ses->request(
+            'open-ils.cstore.json_query', 
+            {from => ['unapi.bre', $rec_id, 'marcxml', 'record', '{holdings_xml,acp}', 'CONS']});
+
+        push(@reqs, $req);
+    }
+
+    for my $req (@reqs) {
+
+        my $data = $req->gather(1);
+        my $xml = XML::LibXML->new->parse_string($data->{'unapi.bre'});
+        $xml = $xml->documentElement;
+        my $attrs = get_bib_attrs($xml);
+        my $rec_id =  xptext($xml,'*[@tag="901"]/*[@code="c"]');
+        $records{$rec_id}{$_} = $attrs->{$_} for keys %$attrs;
+
+        my $rvols = [];
+        for my $volnode ($xml->findnodes('//*[local-name()="volumes"]/*[local-name()="volume"]')) {
+            my $vol = {}; 
+            $vol->{copies} = [];
+            $vol->{label} = $volnode->getAttribute('label');
+            for my $copynode ($volnode->getElementsByLocalName('copy')) {
+                my $copy = {};   
+                $copy->{barcode} = $copynode->getAttribute('barcode');
+                push(@{$vol->{copies}}, $copy);
+            }
+            push(@{$records{$rec_id}->{volumes}}, $vol);
+        }
+    }
+
+    my $duration = time() - $start;
+
+    for my $rec_id (keys %records) {
+        my $rec = $records{$rec_id};
+        print sprintf("%d [%s] has %d volumes and %d copies\n",
+            $rec_id, $rec->{title}, 
+            scalar(@{$rec->{volumes}}),
+            scalar(map { @{$_->{copies}} } @{$rec->{volumes}}));
+    }
+
+    #note, unapi.biblio_record_entry_feed per record performs the same as unapi.bre pre record
+    print "\nunapi 'unapi.bre' spread duration is $duration\n\n";
+}
+
+
+
+sub unapi_batch {
+    my @recs = @_;
+    my $start = time();
+
+    my $data = $e->json_query({from => ['unapi.biblio_record_entry_feed', "{".join(',',@recs)."}", 'marcxml', '{holdings_xml,acp}', 'CONS']})->[0];
+    my $xml = XML::LibXML->new->parse_string($data->{'unapi.biblio_record_entry_feed'});
+
+    my %records;
+    for my $rec_xml ($xml->documentElement->getElementsByLocalName('record')) { 
+
+        my $attrs = get_bib_attrs($rec_xml);
+        my $rec_id =  xptext($rec_xml,'*[@tag="901"]/*[@code="c"]');
+        #print "REC = $rec_xml : $rec_id : " . $attrs->{title} . "\n" . $rec_xml->toString . "\n";
+        $records{$rec_id}{$_} = $attrs->{$_} for keys %$attrs;
+
+        my $rvols = [];
+        for my $volnode ($rec_xml->findnodes('//*[local-name()="volumes"]/*[local-name()="volume"]')) {
+            my $vol = {}; 
+            $vol->{copies} = [];
+            $vol->{label} = $volnode->getAttribute('label');
+            for my $copynode ($volnode->getElementsByLocalName('copy')) {
+                my $copy = {};   
+                $copy->{barcode} = $copynode->getAttribute('barcode');
+                push(@{$vol->{copies}}, $copy);
+            }
+            push(@{$records{$rec_id}->{volumes}}, $vol);
+        }
+    }
+
+    my $duration = time() - $start;
+
+    for my $rec_id (keys %records) {
+        my $rec = $records{$rec_id};
+        print sprintf("%d [%s] has %d volumes and %d copies\n",
+            $rec_id, $rec->{title}, 
+            scalar(@{$rec->{volumes}}),
+            scalar(map { @{$_->{copies}} } @{$rec->{volumes}}));
+    }
+    print "\nunapi 'batch feed' duration is $duration\n\n";
+}
+
+sub direct_spread {
+    my @recs = @_;
+    my %records;
+    my $start = time();
+
+    my $query = {
+        flesh => 4, 
+        flesh_fields => {
+            bre => ['call_numbers'], 
+            acn => ['copies', 'uris'], 
+            acp => ['location', 'stat_cat_entries', 'parts'],
+            ascecm => ['stat_cat', 'stat_cat_entry'],
+            acpm => ['part']
+        }
+    };
+
+    my @reqs;
+    for my $rec_id (@recs) {
+        my $ses = OpenSRF::AppSession->create('open-ils.cstore');
+        my $req = $ses->request(
+            'open-ils.cstore.direct.biblio.record_entry.search', {id => $rec_id}, $query);
+        push(@reqs, $req);
+    }
+
+    $records{$_}{counts} = $e->json_query({from => ['asset.record_copy_count', 1, $_, 0]})->[0] for @recs;
+    for my $req (@reqs) {
+        my $bre = $req->gather(1);
+        my $xml = XML::LibXML->new->parse_string($bre->marc)->documentElement;
+        my $attrs = get_bib_attrs($xml);
+        $records{$bre->id}{record} = $bre;
+        $records{$bre->id}{$_} = $attrs->{$_} for keys %$attrs;
+    }
+
+    my $duration = time() - $start;
+
+    for my $rec_id (keys %records) {
+        my $rec = $records{$rec_id};
+        print sprintf("%d [%s] has %d volumes and %d copies\n",
+            $rec_id, $rec->{title}, 
+            scalar(@{$rec->{record}->call_numbers}), 
+            scalar(map { @{$_->copies} } @{$rec->{record}->call_numbers}));
+    }
+
+    print "\n'direct' spread calls processing duration is $duration\n\n";
+}
+
+
+sub direct {
+    my @recs = @_;
+    my %records;
+
+    my $start = time();
+
+    my $ses1 = OpenSRF::AppSession->create('open-ils.cstore');
+    my $ses2 = OpenSRF::AppSession->create('open-ils.cstore');
+    my $ses3 = OpenSRF::AppSession->create('open-ils.cstore');
+    my ($req1, $req2, $req3);
+
+    my $query = {
+        flesh => 5, 
+        flesh_fields => {
+            bre => ['call_numbers'], 
+            acn => ['copies', 'uris'], 
+            acp => ['location', 'stat_cat_entries', 'parts'],
+            ascecm => ['stat_cat', 'stat_cat_entry'],
+            acpm => ['part']
+        }
+    };
+
+    my $first = 1;
+    while(@recs) {
+        my ($id1, $id2, $id3) = (pop @recs, pop @recs, pop @recs);
+
+        for my $r ($req1, $req2, $req3) {
+            last unless $r;
+            my $bre = $r->gather(1);
+            my $xml = XML::LibXML->new->parse_string($bre->marc)->documentElement;
+            my $attrs = get_bib_attrs($xml);
+            $records{$bre->id}{record} = $bre;
+            $records{$bre->id}{$_} = $attrs->{$_} for keys %$attrs;
+        }
+
+        $req1 = ($id1) ? $ses1->request('open-ils.cstore.direct.biblio.record_entry.search', {id => $id1}, $query) : undef;
+        $req2 = ($id2) ? $ses1->request('open-ils.cstore.direct.biblio.record_entry.search', {id => $id2}, $query) : undef;
+        $req3 = ($id3) ? $ses1->request('open-ils.cstore.direct.biblio.record_entry.search', {id => $id3}, $query) : undef;
+        
+        if($first) {
+            $records{$_}{counts} = $e->json_query({from => ['asset.record_copy_count', 1, $_, 0]})->[0] for @recs;
+            $first = 0;
+        }
+    }
+
+    for my $r ($req1, $req2, $req3) {
+        last unless $r;
+        my $bre = $r->gather(1);
+        my $xml = XML::LibXML->new->parse_string($bre->marc)->documentElement;
+        my $attrs = get_bib_attrs($xml);
+        $records{$bre->id}{record} = $bre;
+        $records{$bre->id}{$_} = $attrs->{$_} for keys %$attrs;
+    }
+
+
+    my $duration = time() - $start;
+
+    for my $rec_id (keys %records) {
+        my $rec = $records{$rec_id};
+        print sprintf("%d [%s] has %d volumes and %d copies\n",
+            $rec_id, $rec->{title}, 
+            scalar(@{$rec->{record}->call_numbers}), 
+            scalar(map { @{$_->copies} } @{$rec->{record}->call_numbers}));
+    }
+
+    print "\n'direct' calls processing duration is $duration\n\n";
+}
+
+for (0..1) { direct(@recs); unapi(@recs); unapi_batch(@recs); unapi_spread(@recs); direct_spread(@recs); }
diff --git a/Open-ILS/src/templates/base.tt2 b/Open-ILS/src/templates/base.tt2
new file mode 100644 (file)
index 0000000..d4f4ebb
--- /dev/null
@@ -0,0 +1,27 @@
+[%- ctx.final_dtd = 
+    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' -%]
+[%- IF !ctx.dtd; ctx.dtd = ctx.final_dtd; END -%]
+[% ctx.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" />
+        <link rel='stylesheet' type='text/css'
+            href='[% ctx.media_prefix %]/css/skin/[% ctx.skin %].css' />
+        <link rel='stylesheet' type='text/css'
+            href='[% ctx.media_prefix %]/css/theme/[% ctx.skin %].css' />
+           <script language='javascript' src='/IDL2js' type='text/javascript'></script>
+        <script type="text/javascript">var djConfig = {parseOnLoad:true,isDebug:false};</script>
+        <script type="text/javascript" src="/js/dojo/dojo/dojo.js"></script>
+        <script type="text/javascript" src="[% ctx.media_prefix %]/js/dojo/dojo/openils_dojo.js"></script>
+        <script type="text/javascript" src="[% ctx.media_prefix %]/js/dojo/opensrf/md5.js"></script>
+        <script type="text/javascript">
+            var oilsBasePath = '[% ctx.base_path %]';
+        </script>
+        <script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/base.js"></script>
+    </head>
+    <body class='tundra'>
+        [% INCLUDE login.tt2 %] <!-- shared login page -->
+        [% content %] <!-- Page body -->
+    </body>
+</html>
diff --git a/Open-ILS/src/templates/default/acq/common/claim_dialog.tt2 b/Open-ILS/src/templates/default/acq/common/claim_dialog.tt2
new file mode 100644 (file)
index 0000000..741a8a6
--- /dev/null
@@ -0,0 +1,60 @@
+<div style="width: 300px; height: 300px; overflow: auto;">
+    <script
+        src="[% ctx.media_prefix %]/js/ui/default/acq/common/claim_dialog.js">
+    </script>
+    <div><big>Claims</big></div>
+    <div>Against item:
+        <span id="acq-lit-li-claim-dia-li-title"></span>
+        (<span id="acq-lit-li-claim-dia-li-id"></span>)
+    </div>
+    <div id="acq-lit-li-claim-dia-show" class="hidden">
+        <ul id="acq-lit-li-claim-dia-lid-list">
+            <li name="lid">
+                <span name="barcode"></span> /
+                <span name="recvd"></span>
+                <ul name="claims">
+                    <li name="claim">
+                        <span name="type"></span>
+                        <a name="voucher"
+                            href="javascript:void(0);">Show Voucher</a>
+                    </li>
+                </ul>
+            </li>
+        </ul>
+        <hr />
+    </div>
+    <div id="acq-lit-li-claim-dia-initiate" class="hidden">
+        <div><big>Initiate New Claims</big></div>
+        <div id="acq-lit-li-claim-dia-lid-list-init">
+            <div name="lid_to_claim">
+                <input type="checkbox" name="claimable_lid" />
+                <label name="claimable_lid_label">
+                    <span name="barcode"></span> /
+                    <span name="recvd"></span>
+                </label>
+            </div>
+        </div>
+        <hr />
+        <div id="acqclet-display" class="hidden">
+            <div><big>Select Claim Action</big></div>
+            <table>
+                <tbody id="acqclet-tbody">
+                    <tr name="acqclet-template">
+                        <td>
+                            <input type="checkbox" name="acqclet-checkbox" />
+                        </td>
+                        <td style="padding-left: 1em;">
+                            <label name="acqclet-label">
+                                (${ou}) ${code} <em>${description}</em>
+                                <span style="color: #069;">
+                                    ${library_initiated}</span>
+                            </label>
+                        </td>
+                    </tr>
+                </tbody>
+            </table>
+            <hr />
+        </div>
+        <button id="acq-lit-li-claim-dia-claim">Claim selected</button>
+    </div>
+</div>
diff --git a/Open-ILS/src/templates/default/acq/common/final_claim_dialog.tt2 b/Open-ILS/src/templates/default/acq/common/final_claim_dialog.tt2
new file mode 100644 (file)
index 0000000..882741c
--- /dev/null
@@ -0,0 +1,8 @@
+<div id="acq-eligible-claim-controls">
+    <label for="acq-eligible-claim-type">Claim type:</label>
+    <span id="acq-eligible-claim-type"></span>
+    <label for="acq-eligible-claim-note">Note:</label>
+    <input dojoType="dijit.form.TextBox" id="acq-eligible-claim-note" />
+    <button id="acq-eligible-claim-submit">Claim</button>
+</div>
+
diff --git a/Open-ILS/src/templates/default/acq/common/info.tt2 b/Open-ILS/src/templates/default/acq/common/info.tt2
new file mode 100644 (file)
index 0000000..6fc5456
--- /dev/null
@@ -0,0 +1,24 @@
+[% which_lc = which | lower %]
+    <div id="acq-[% which_lc %]-info-div" class="hidden">
+        <div class="acq-menu-bar">
+            <div dojoType="dijit.form.Button" id="acq-[% which_lc %]-info-back-button">&#x2196; [% IF which == "Lit" %]Return[% ELSE %]Hide[% END %]</div>
+        </div>
+        <table>
+            <tbody id="acq-[% which_lc %]-info-tbody">
+                <tr id="acq-[% which_lc %]-info-row"><td name="label"/><td name="value"/></tr>
+            </tbody>
+        </table>
+[% IF which == "Lit" %]
+        <div class="hidden" id="acq-[% which_lc %]-info-related">
+            Show the <a name="rel_link" href="#"><span name="related_number"></span> lineitem(s)</a> related to the same bibliographic record.
+        </div>
+        <div style="margin-top:40px;">
+            <h3 id="acq-[% which_lc %]-marc-order-record-label">MARC Order Record</h3>
+            <h3 id="acq-[% which_lc %]-marc-real-record-label">MARC ILS Record</h3>
+            <div>
+                <div dojoType="dijit.form.Button" jsId="acq[% which %]EditOrderMarc" class="hidden">Edit MARC Order Record</div>
+            </div>
+            <div id="acq-[% which_lc %]-marc-div" style="margin-top:20px;"> </div>
+        </div>
+[% END %]
+    </div>
diff --git a/Open-ILS/src/templates/default/acq/common/inv_dialog.tt2 b/Open-ILS/src/templates/default/acq/common/inv_dialog.tt2
new file mode 100644 (file)
index 0000000..3bec75c
--- /dev/null
@@ -0,0 +1,30 @@
+<script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/default/acq/common/inv_dialog.js">
+</script>
+<big><strong>Choose invoice</strong></big>
+<table class="acq-link-invoice-dialog">
+    <tr>
+        <th>
+            <label for="acq-[% which %]-link-invoice-inv_ident">
+                Invoice #
+            </label>
+        </th>
+        <td>
+            <input id="acq-[% which %]-link-invoice-inv_ident"
+                dojoType="dijit.form.TextBox" />
+        </td>
+    </tr>
+    <tr>
+        <th>
+            <label for="acq-[% which %]-link-invoice-provider">Provider</label>
+        </th>
+        <td>
+            <span id="acq-[% which %]-link-invoice-provider"></span>
+        </td>
+    </tr>
+    <tr>
+        <td colspan="2" style="text-align: center;">
+            <button id="acq-[% which %]-link-invoice-link"
+                dojoType="dijit.form.Button" type="submit">Link</button>
+        </td>
+    </tr>
+</table>
diff --git a/Open-ILS/src/templates/default/acq/common/jubgrid.tt2 b/Open-ILS/src/templates/default/acq/common/jubgrid.tt2
new file mode 100644 (file)
index 0000000..ce13d56
--- /dev/null
@@ -0,0 +1,209 @@
+[%#-
+This template creates a split screen Dojo layout. The top frame
+of the screen holds a list of of JUBs, or titles. Clicking on a
+title in the top frame will load the purchase details for all the
+copies on order for that title into the bottom frame.
+
+To create a display for a set of JUBs, create a Dojo store and
+model for the set of JUBs, then place the following lines in your
+HTML where you want the display to appear:
+
+    <%namespace file='/oils/default/common/jubgrid.html' name='jubgrid'/>
+    ${jubgrid.jubgrid('dom_prefix', 'grid_jsid')}
+
+where 'dom_prefix' is a string that will be used as the prefix
+for the DOM notes that are created by this template, and
+'grid_jsid' is a valid JavaScript identifier that will name the
+DOM node to which the list of JUBs will be attached.  For example
+
+    ${jubgrid.jubgrid('oils-acq-picklist', 'pickListGrid', hideDetails)}
+
+will create a Dojo grid with the DOM id of
+
+    'oils-acq-picklist-JUB-grid'
+
+and a jsid of
+
+    pickListGrid
+
+To fill the grid with data, call the javascript function
+
+    JUBGrid.populate(grid_jsid, model)
+
+'grid_jsid' is the same javascript id that was used to
+instantiate the template, and model is a javascript variable
+pointing to the JUB model (and store) that you have created.
+-#%]
+
+[% UNLESS hide_details %]
+<div dojoType='dijit.layout.ContentPane' style='height:100%;'>
+[% END %]
+
+    <style type='text/css'>
+        .grid_container {width: 100%; height: 100%;}
+    </style>
+
+    <script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/acq/common/jubgrid.js'> </script>
+    <script type="text/javascript" src='[% ctx.media_prefix %]/js/dojo/openils/CopyLocation.js'> </script>
+    <script type="text/javascript">
+    JUBGrid.getPO = function(rowIndex) {
+        var data = JUBGrid.jubGrid.model.getRow(rowIndex);
+        if (!(data && data.purchase_order)) return '';
+        return "<a href='[% ctx.base_path %]/acq/po/view/" + data.purchase_order+"'>"+data.purchase_order+"</a>";
+    }
+    JUBGrid.jubGridLayout = [{
+        //noscroll: true,
+        cells: [[
+            {name: 'ID', field: 'id', width:'auto'},
+            {name: 'Title', width: "180px", get:JUBGrid.getJUBTitle},
+            {name: 'Author', get:JUBGrid.getJUBAuthor, width:'auto'},
+            {name: 'ISBN', get:JUBGrid.getJUBIsbn, width:'auto'},
+            {name: 'Pubdate', get:JUBGrid.getJUBPubdate, width:'auto'},
+            {name: 'Actual Price', 
+                field:'actual_price',
+                get:JUBGrid.getJUBActualPrice,
+                editor:dojox.grid.editors.Dijit, width:'auto', 
+                editorClass: "dijit.form.CurrencyTextBox" 
+            },
+            {name: 'Estimated Price', 
+                field:'estimated_price',
+                get:JUBGrid.getJUBEstimatedPrice, width:'auto',
+                editor:dojox.grid.editors.Dijit, 
+                editorClass: "dijit.form.CurrencyTextBox" 
+            },
+            {name: 'Vendor', width:'auto',
+            field: 'provider', get:JUBGrid.getProvider,
+            editor:openils.editors.ProviderSelectEditor,
+           },
+            {name: 'No. Copies', field: 'item_count', width:'auto'},
+            {name: 'State', field: 'state', width:'auto'},
+            {name: 'PO', get:JUBGrid.getPO, width:'auto'}
+        ]]
+    }];
+
+    JUBGrid.jubDetailGridLayout = [{
+        cells: [[
+            {name:"ID", field:"id"},
+            {name:"Fund", field:"fund",
+                get:JUBGrid.getLIDFundCode,
+                editor: openils.editors.FundSelectEditor,
+            },
+            {name:"Branch", field:"owning_lib",
+                   get:JUBGrid.getLIDLibName,
+                   editor: openils.editors.OrgUnitSelectEditor
+               },
+            {name:"Barcode", field:"barcode", width:'auto',
+                editor:dojox.grid.editors.Dijit, 
+                editorClass: "dijit.form.TextBox" 
+               },
+            {name:"Call Number", field:"cn_label", width:'auto',
+                editor:dojox.grid.editors.Dijit, 
+                editorClass: "dijit.form.TextBox" 
+               },
+            {name:"Shelving Location", field:"location", width:'auto',
+                editor:openils.editors.CopyLocationSelectEditor,
+                get:JUBGrid.getCopyLocation
+               },
+            {name:"Receive Time", width:'auto',
+                get:JUBGrid.getRecvTime
+               },
+        ]]
+    }];
+
+    JUBGrid.jubDetailGridLayoutReadOnly = [{
+        cells: [[
+            {name:'ID', field:"id"},
+            {name:'Fund', field:"fund",
+             get:JUBGrid.getLIDFundCode,
+            },
+            {name:'Branch', field:"owning_lib",
+                   get:JUBGrid.getLIDLibName,
+               },
+           {name:'Barcode', field:"barcode", width:'auto'},
+            {name:'Call Number', field:"cn_label", width:'auto'},
+           {name:'Shelving Location', field:"location", 
+                width:'auto', get:JUBGrid.getCopyLocation},
+        ]]
+    }];
+    </script>
+
+[% UNLESS hide_details %]
+    <!-- button bar for lineitems -->
+    <script type="text/javascript">JUBGrid.showDetails = true;</script>
+    <div id="[% domprefix %]-container" class='container'
+        dojoType="dijit.layout.ContentPane" sizeMin="" sizeShare="">
+        <div dojoType="dijit.layout.ContentPane"
+             id='[% domprefix %]-jub-buttonbar'>
+            <button dojoType="dijit.form.Button" onclick="JUBGrid.approveJUB">
+                Approve Selected Titles
+            </button>
+            <button dojoType="dijit.form.Button" onclick="JUBGrid.removeSelectedJUBs">
+                Remove Selected Titles
+            </button>
+        </div>
+    </div>
+    <div style='height:40%;'>
+[% ELSE %]
+    <div style='height:100%;'>
+[% END %]
+        <div structure='JUBGrid.jubGridLayout' jsid='[% grid_jsid %]' class='grid_container'
+            dojoType='dojox.Grid' id="[% domprefix %]-JUB-grid">
+        </div>
+    </div>
+[% UNLESS hide_details %]
+    <!-- button bar for lineitem details -->
+    <div dojoType="dijit.layout.ContentPane" sizeMin="" sizeShare="" class='container'>
+        <div dojoType="dijit.layout.ContentPane" id='[% domprefix %]-details-buttonbar'>
+            <div dojoType="dijit.form.DropDownButton">
+                <span>New Copy</span>
+                <div dojoType="dijit.TooltipDialog" execute="JUBGrid.createLID(arguments[0]);">
+                    <script type='dojo/connect' event='onOpen'>
+                        new openils.User().buildPermOrgSelector('MANAGE_FUND', copyOwnerSelect);
+                        openils.acq.Fund.buildPermFundSelector('MANAGE_FUND', acqlidFund);
+                    </script>
+                    <table class="dijitTooltipTable">
+                        <tr>
+                            <td><label for="fund">Fund: </label></td>
+                            <td>
+                                <input dojoType="openils.widget.FundSelector"
+                                jsId="acqlidFund" searchAttr="name" autocomplete="true" name="fund"></input>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td><label for="owning_lib">Location: </label></td>
+                            <td><input dojoType="openils.widget.OrgUnitFilteringSelect"
+                                jsId="copyOwnerSelect"
+                                searchAttr="shortname"
+                                name="owning_lib" autocomplete="true"
+                                labelAttr="shortname"></input>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td colspan="2" align="center">
+                                <button dojotype="dijit.form.Button" type="submit">
+                                Create
+                                </button>
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+            <button dojoType='dijit.form.Button' onclick='JUBGrid.deleteLID'>
+                Delete Selected Copy
+            </button>
+            <button dojoType='dijit.form.Button' onclick='JUBGrid.receiveLID'>
+                Mark Selected Copies Received
+            </button>
+        </div>
+    </div>
+    <!-- end button bar -->
+
+    <div style='height:40%;'>
+           <div class='grid_container'>
+            <div structure='JUBGrid.jubDetailGridLayout' jsid="JUBGrid.jubDetailGrid" dojoType="dojox.Grid"
+                id='[% domprefix %]-details-grid'>
+            </div>
+        </div>
+    </div>
+</div>
+[% END %]
diff --git a/Open-ILS/src/templates/default/acq/common/li_table.tt2 b/Open-ILS/src/templates/default/acq/common/li_table.tt2
new file mode 100644 (file)
index 0000000..28ef757
--- /dev/null
@@ -0,0 +1,471 @@
+<script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/default/acq/common/base64.js"> </script>
+<script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/acq/common/li_table.js'> </script>
+<script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/acq/financial/claim_voucher.js'> </script>
+<div id='acq-lit-table-container'>
+    <div id='acq-lit-table-div' class='hidden'>
+
+        <!-- Lineitem (bib record) list -->
+        <table id='acq-lit-table' class='oils-generic-table'>
+            <thead>
+                <tr>
+                    <th colspan='0'>
+                        <table style='width:100%;'>
+                            <tr>
+                                <td>
+                                    <span>
+                                        <select id="acq-lit-li-actions-selector">
+                                            <option mask='*'  value='_'>--Actions--</option>
+                                            <option mask='sr' value='save_picklist'>Save Items To Selection List</option>
+                                            <option mask='pl' value='selector_ready'>Mark Ready for Selector</option>
+                                            <option mask='pl' value='order_ready'>Mark Ready for Order</option>
+                                            <option mask='*'  value='delete_selected'>Delete Selected Items</option>
+                                            <option mask='*'  value='add_brief_record'>Add Brief Record</option>
+                                            <option mask='*'  value='export_attr_list'>Export Single Attribute List</option>
+                                            <option mask='*'  value='batch_apply_funds'>Apply Funds to Selected Items</option>
+                                            <option mask='po' value='' disabled='disabled'>----PO----</option>
+                                            <option mask='sr|pl' value='create_order'>Create Purchase Order</option>
+                                            <option mask='po' value='create_assets'>Load Bibs and Items</option>
+                                            <option mask='po' value='cancel_lineitems'>Cancel Selected Lineitems</option>
+                                            <option mask='po' value='change_claim_policy'>Change Claim Policy</option>
+                                            <option mask='po' value='receive_po'>Mark Purchase Order as Received</option>
+                                            <option mask='po' value='rollback_receive_po'>Un-Receive Purchase Order</option>
+                                            <option mask='po' value='print_po'>Print Purchase Order</option>
+                                            <option mask='po' value='po_history'>View PO History</option>
+                                        </select>
+                                        <span id="acq-lit-export-attr-holder" class="hidden">
+                                            <input dojoType="dijit.form.FilteringSelect" id="acq-lit-export-attr" jsId="acqLitExportAttrSelector" labelAttr="description" searchAttr="description" />
+                                            <span dojoType="dijit.form.Button" jsId="acqLitExportAttrButton">Export List</span>
+                                        </span>
+                                        <span id="acq-lit-cancel-reason" class="hidden">
+                                            <span id="acq-lit-cancel-reason-selector"></span>
+                                            <span dojoType="dijit.form.Button" jsId="acqLitCancelLineitemsButton">Cancel Line Items</span>
+                                        </span>
+                                    </span>
+                                    <span id='acq-lit-generic-progress' class='hidden'>
+                                        <span dojoType="dijit.ProgressBar" style="width:300px" jsId="litGenericProgress"></span>
+                                    </span>
+                                </td>
+                                <td>
+                                    <div style='width:100%;text-align:right;'>
+                                        <span style='padding-right:15px;'>
+                                            <a href='javascript:void(0);' id='acq-lit-prev' style='visibility:hidden'>&#171; Previous</a>
+                                            <a href='javascript:void(0);' id='acq-lit-next' style='visibility:hidden'>Next &#187;</a>
+                                        </span>
+                                    </div>
+                                </td>
+                            </tr>
+                        </table>
+                    </th>
+                </tr>
+            </thead>
+            <tbody><tr><td colspan='0' style='height:20px;'/></tr></tbody>
+            <tbody style='font-weight:bold;border:1px solid #aaa;'>
+                <tr>
+                    <td><span><a id='acq-lit-select-toggle' href='javascript:void(0);'>&#x2713</a></span></td>
+                    <td>Line Items</td>
+                    <td>Items</td>
+                    <td>Notes</td>
+                    <td>Actions</td>
+                    <td>Status</td>
+                    <td>Estimated Price</td>
+                </tr>
+            </tbody>
+            <tbody id='acq-lit-tbody'>
+                <tr id='acq-lit-row' class='acq-lit-row'>
+                    <td name='selector'><input type='checkbox' name='selectbox'/></td>
+                    <td style='width:75%;'>
+                        <table style='width:100%;'>
+                            <tbody>
+                                <tr>
+                                    <td rowspan='3' style='width:43px;'><img style='width:40px;height:65px;' name='jacket'></td>
+                                    <td style='width:70%;font-weight:bold;'>
+                                        <span name="bib_origin" class="hidden">
+                                            <img src="/opac/images/book-icon.png" />
+                                        </span><a attr='title' href='javascript:void(0);'></a>
+                                    </td>
+                                    <td rowspan='2' style='text-align:right'>
+                                    </td>
+                                </tr>
+                                <tr class='acq-lit-alt-row'>
+                                    <td colspan='0'>
+                                        <span attr='author'></span>
+                                        <span attr='isbn'></span>
+                                        <span attr='issn'></span>
+                                        <span attr='edition'></span>
+                                        <span attr='pubdate'></span>
+                                        <span attr='publisher'></span>
+                                        <span name='source_label'></span>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td colspan='0'>
+                                        <span name="liid"># </span> 
+                                        <span name="catalog" class='hidden'> | <a title='Show In Catalog' name="catalog_link" href="javascript:void(0);">&#x279F; catalog</a></span> 
+                                        <span name="link_to_catalog" class='hidden'> | <a title='Link To Catalog Record' name="link_to_catalog_link" href="javascript:void(0);">&#x27BE; link to catalog</a></span> 
+                                        <span name="worksheet"> | <a title='Generate Worksheet' name="worksheet_link" href="javascript:void(0);">&#x270D; worksheet</a></span>
+                                        <span name='pl' class='hidden'> | <a title='Select List' name='pl_link' href='javascript:void(0);'>&#x2756; </a></span>
+                                        <span name='po' class='hidden'> | <a title='Purchase Order' name='po_link' href='javascript:void(0);'>&#x2318; </a></span>
+                                    </td>
+                                </tr>
+                            </tbody>
+                        </table>
+                    </td>
+                    <td><a title='FOOOBAR' name='copieslink' href='javascript:void(0);'>Copies(<span name='count'>0</span>)</a></td>
+                    <td>
+                        <a name='noteslink' href='javascript:void(0);'>Notes(<span name='notes_count'>0</span>)</a><span name="notes_alert_flag"></span>
+                    </td>
+                    <td>
+                        <select name='actions'>
+                            <option name='action_none'>-- Actions --</option>
+                            <option name='action_mark_recv' disabled='disabled'>Mark Received</option>
+                            <option name='action_mark_unrecv' disabled='disabled'>Un-Receive</option>
+                            <option name='action_update_barcodes' disabled='disabled'>Update Barcodes</option>
+                            <option name='action_holdings_maint' disabled='disabled'>Holdings Maint.</option>
+                            <option name='action_new_invoice' disabled='disabled'>New Invoice</option>
+                            <option name='action_link_invoice' disabled='disabled'>Link to Invoice</option>
+                            <option name='action_view_invoice' disabled='disabled'>View Invoice(s)</option>
+                            <option name='action_view_claim_policy'>Apply Claim Policy</option>
+                            <option name='action_manage_claims' disabled='disabled'>Claims</option>
+                            <option name='action_view_history'>View History</option>
+                        </select>
+                    </td>
+                    <td><span name='li_state'></span></td>
+                    <td><input type='text' size='8' name='price'/></td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
+
+    <!-- Bib record / Lineitem info table -->
+    [% INCLUDE "default/acq/common/info.tt2" which = "Lit" %]
+
+    <!-- Lineitem notes table -->
+    [% INCLUDE "default/acq/common/notes.tt2" which = "Lit" %]
+
+    <!-- Copies table -->
+    <div id='acq-lit-li-details' class='hidden'>
+
+        <div id='acq-lit-copies-li-summary'></div>
+
+        <h3>Add/Edit Items</h3>
+        <hr/>
+
+        <div class='acq-lit-li-menu-bar'>
+            <table style='width:100%'>
+                <tr>
+                    <td class="acq-lit-li-menu-left">
+                        <div dojoType='dijit.form.Button' id='acq-lit-copies-back-button' scrollOnFocus='false'>&#x2196; Return</div>
+                        <span style='margin-left:10px;padding-left:10px;border-left:2px solid #aaa;'>
+                            Item Count: 
+                            <input dojoType='dijit.form.NumberTextBox' jsId='acqLitCopyCountInput'  
+                                constraints="{min:0,max:1000,places:0}" style='width:40px' value='0'></input> 
+                            <div dojoType='dijit.form.Button' jsId='acqLitAddCopyCount' scrollOnFocus='false'>Go</div>
+                        </span>
+                        <span style='margin-left:10px;padding-left:10px;border-left:2px solid #aaa;'>
+                            <div dojoType='dijit.form.Button' jsId='acqLitSaveCopies' scrollOnFocus='false'>Save Changes</div>
+                        </span>
+                        <span id='acq-lit-update-copies-progress' class='hidden'>
+                            <span dojoType="dijit.ProgressBar" style="width:300px" jsId="litUpdateCopiesProgress"></span>
+                        </span>
+                    </td>
+                </tr>
+            </table>
+        </div>
+
+        <hr/>
+        <table id='acq-lit-distrib-formula-table'>
+            <tbody id='acq-lit-distrib-formula-tbody'>
+                <tr id='acq-lit-distrib-form-row'>
+                    <td colspan='0'>
+                        <span>Distribution Formulas</span>
+                        <div name='selector'></div>
+                        <div name='set_button'></div>
+                        <div name="reset_button"></div>
+                    </td>
+                </tr>
+            </tbody>
+            <tbody id="acq-lit-distrib-applied-tbody" class="hidden">
+                <tr>
+                    <td colspan="5" id="acq-lit-distrib-applied-heading">
+                        Distribution formulas applied to this lineitem:
+                    </td>
+                </tr>
+                <tr id="acq-lit-distrib-applied-row" class="acq-lit-distrib-applied-row">
+                    <th></th>
+                    <td colspan="4"></td>
+                </tr>
+            </tbody>
+        <table>
+
+        <table id='acq-lit-li-details-table'>
+            <tbody><tr><td class='acq-lit-table-spacer' colspan='0'/></tr></tbody>
+            <tbody style='font-weight:bold;'>
+                <tr>
+                    <td style='margin-top:30px;'>Owning Branch</td>
+                    <td>Shelving Location</td>
+                    <td>Collection Code</td>
+                    <td>Fund</td>
+                    <td>Circ Modifier</td>
+                    <td>Callnumber</td>
+                    <td colspan='0'></td>
+                </tr>
+            </tbody>
+            <tbody style='background-color:#ddd;'>
+                <tr id='acq-lit-li-details-batch-row'>
+                    <td><div name='owning_lib'></div></td>
+                    <td><div name='location'></div></td>
+                    <td><div name='collection_code'></div></td>
+                    <td><div name='fund'></div></td>
+                    <td><div name='circ_modifier'></div></td>
+                    <td><div name='cn_label'></div></td>
+                    <td colspan='3' style='text-align:left;'>
+                        <div dojoType='dijit.form.Button' jsId='acqLitBatchUpdateCopies' scrollOnFocus='false'>Batch Update</div>
+                    </td>
+                </tr>
+            </tbody>
+
+
+            <tbody><tr><td class='acq-lit-table-spacer' colspan='0'></td></tr></tbody>
+            <tbody style='font-weight:bold;'>
+                <tr>
+                    <td style='margin-top:30px;'>Owning Branch</td>
+                    <td>Shelving Location</td>
+                    <td>Collection Code</td>
+                    <td>Fund</td>
+                    <td>Circ Modifier</td>
+                    <td>Callnumber</td>
+                    <td>Barcode</td>
+                    <td>Notes</td>
+                    <td colspan='0'></td>
+                </tr>
+            </tbody>
+            <tbody id='acq-lit-li-details-tbody' class='oils-generic-table'>
+                <tr id='acq-lit-li-details-row'>
+                    <td><div name='owning_lib'></div></td>
+                    <td><div name='location'></div></td>
+                    <td><div name='collection_code'></div></td>
+                    <td><div name='fund'></div></td>
+                    <td><div name='circ_modifier'></div></td>
+                    <td><div name='cn_label'></div></td>
+                    <td><div name='barcode'></div></td>
+                    <td><div name='note'></div></td>
+                    <td><a href="javascript:void(0);" name="receive">Mark&nbsp;Received</a><a href="javascript:void(0);" name="unreceive">Un-Receive</a>&nbsp;<a href="javascript:void(0);" name="cancel">Cancel</a><span class="hidden" name="cancel_reason"></span>&nbsp;<a href="javascript:void(0);" name="claim">Claim</a></td>
+                    <td><div name='delete' dojoType='dijit.form.Button' style='color:red;' scrollOnFocus='false'>X</div></td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
+
+
+    <!-- Copies table -->
+    <div id='acq-lit-real-copies-div' class='hidden'>
+        <h2>Copies</h2>
+
+        <div class='acq-lit-li-menu-bar'>
+            <table style='width:100%'>
+                <tr>
+                    <td style='text-align:left;'>
+                        <div dojoType='dijit.form.Button' id='acq-lit-real-copies-back-button' scrollOnFocus='false'>&#x2196; Return</div>
+                    </td>
+                    <td style='text-align:right;'>
+                        <span>
+                            <div dojoType='dijit.form.Button' jsId='acqLitSaveRealCopies' scrollOnFocus='false'>Save Changes</div>
+                        </span>
+                    </td>
+                </tr>
+            </table>
+        </div>
+
+        <table id='acq-lit-real-copies-table'>
+            <tbody style='font-weight:bold;'>
+                <tr>
+                    <td style='margin-top:30px;'>Owning Branch</td>
+                    <td>Shelving Location</td>
+                    <td>Circ Modifier</td>
+                    <td>Callnumber</td>
+                    <td>Barcode</td>
+                    <td colspan='0'></td>
+                </tr>
+            </tbody>
+            <tbody id='acq-lit-real-copies-tbody' class='oils-generic-table'>
+                <tr id='acq-lit-real-copies-row'>
+                    <td><div name='owning_lib'></div></td>
+                    <td><div name='location'></div></td>
+                    <td><div name='circ_modifier'></div></td>
+                    <td><div name='label'></div></td>
+                    <td><div name='barcode'></div></td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
+
+    <div class="hidden">
+        <div jsId="acqLitLinkInvoiceDialog" dojoType="dijit.Dialog">
+            [% INCLUDE "default/acq/common/inv_dialog.tt2" which = "li" %]
+        </div>
+    </div>